/*
 * FixScript v0.3 - http://www.fixscript.org/
 * Copyright (c) 2018-2020 Martin Dvorak <jezek2@advel.cz>
 *
 * This software is provided 'as-is', without any express or implied warranty.
 * In no event will the authors be held liable for any damages arising from
 * the use of this software.
 *
 * Permission is granted to anyone to use this software for any purpose, 
 * including commercial applications, and to alter it and redistribute it
 * freely, subject to the following restrictions:
 *
 * 1. The origin of this software must not be misrepresented; you must not
 *    claim that you wrote the original software. If you use this software
 *    in a product, an acknowledgment in the product documentation would be
 *    appreciated but is not required.
 * 2. Altered source versions must be plainly marked as such, and must not be
 *    misrepresented as being the original software.
 * 3. This notice may not be removed or altered from any source distribution.
 */

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <stdint.h>
#include <string.h>
#include <errno.h>
#include <limits.h>
#include <math.h>
#if defined(_WIN32)
#include <windows.h>
#elif defined(__APPLE__)
#include <mach/mach_time.h>
#else
#include <time.h>
#endif
#include "fixscript.h"

#ifdef _WIN32
#undef ERROR
#endif

#define MAX_IMPORT_RECURSION    100
#define MAX_STACK_SIZE          4096
#define MAX_COMPARE_RECURSION   50
#define MAX_DUMP_RECURSION      50
#define MAX_CLONE_RECURSION     50
#define MAX_SERIALIZE_RECURSION 50
#define ARRAYS_GROW_CUTOFF      4096
#define FUNC_REF_OFFSET         ((1<<23)-256*1024)

#define PARAMS_ON_STACK 16

#define MIN(a, b) ((a)<(b)? (a):(b))
#define MAX(a, b) ((a)>(b)? (a):(b))

typedef struct {
   void **data;
   int size, len, slots;
} StringHash;

typedef struct {
   void **data;
   int size, len;
} DynArray;

typedef struct {
   char *data;
   int size, len;
} String;

typedef struct {
   union {
      int *flags;
      union {
         HandleFreeFunc handle_free;
         HandleFunc handle_func;
      };
   };
   union {
      int *data;
      unsigned char *byte_data;
      unsigned short *short_data;
      void *handle_ptr;
   };
   int size, len;
   union {
      int hash_slots;
      int type;
   };
   unsigned int ext_refcnt : 25;
   unsigned int reachable : 1;
   unsigned int is_string : 1;
   unsigned int is_handle : 2;
   unsigned int is_static : 1;
   unsigned int is_shared : 1;
   unsigned int has_weak_refs : 1;
} Array;

typedef struct {
   volatile int refcnt;
   int type;
   HandleFreeFunc free_func;
   void *free_data;
} SharedArrayHeader;

typedef struct {
   int pc;
   int line;
} LineEntry;

struct Heap {
   Array *data;
   int size;
   int next_idx;
   int64_t total_size, total_cap;

   DynArray roots;
   DynArray ext_roots;

   unsigned char *bytecode;
   int bytecode_size;

   LineEntry *lines;
   int lines_size;

   StringHash scripts;
   int cur_import_recursion;

   DynArray functions;

   DynArray native_functions;
   StringHash native_functions_hash;

   DynArray error_stack;

   uint64_t perf_start_time;
   uint64_t perf_last_time;

   int handle_created;

   LoadScriptFunc cur_load_func;
   void *cur_load_data;

   StringHash weak_refs;
   uint64_t weak_id_cnt;

   StringHash shared_arrays;
   DynArray user_data;
};

enum {
   LOCALS_IDX = 1,
   STACK_IDX
};

enum {
   ARR_HASH  = 0,
   ARR_INT   = -1,    /* 0x00000000 - 1 */
   ARR_BYTE  = -257,  /* 0xFFFFFF00 - 1 */
   ARR_SHORT = -65537 /* 0xFFFF0000 - 1 */
};

#define ARRAY_NEEDS_UPGRADE(arr, value) ((value) & (((unsigned int)(arr)->type) + 1U))
#define ARRAY_SHARED_HEADER(arr) ((SharedArrayHeader *)(((char *)(arr)->flags) - sizeof(SharedArrayHeader)))

enum {
   SER_INT          = 1,
   SER_FLOAT        = 2,
   SER_REF          = 3,
   SER_ARRAY        = 4,
   SER_ARRAY_BYTE   = 5,
   SER_ARRAY_SHORT  = 6,
   SER_ARRAY_INT    = 7,
   SER_STRING_BYTE  = 8,
   SER_STRING_SHORT = 9,
   SER_STRING_INT   = 10,
   SER_HASH         = 11,
};

typedef struct Constant {
   Value value;
   int local;
   Script *ref_script;
   struct Constant *ref_constant;
} Constant;

typedef struct {
   int id;
   int addr;
   int num_params;
   int local;
   Script *script;
   int lines_start, lines_end;
} Function;

typedef struct {
   NativeFunc func;
   void *data;
   int id;
   int num_params;
   int bytecode_ident_pc;
} NativeFunction;

struct Script {
   DynArray imports;
   StringHash constants;
   StringHash locals;
   StringHash functions;
};

enum {
   TOK_IDENT,
   TOK_FUNC_REF,
   TOK_NUMBER,
   TOK_HEX_NUMBER,
   TOK_FLOAT_NUMBER,
   TOK_CHAR,
   TOK_STRING,
   TOK_SYMBOL,
   TOK_SYMBOL2,
   TOK_SYMBOL3,
   TOK_SYMBOL4,
   TOK_UNKNOWN,

   KW_DO,
   KW_IF,
   KW_FOR,
   KW_VAR,
   KW_USE,
   KW_ELSE,
   KW_CASE,
   KW_WHILE,
   KW_BREAK,
   KW_CONST,
   KW_RETURN,
   KW_IMPORT,
   KW_SWITCH,
   KW_DEFAULT,
   KW_FUNCTION,
   KW_CONTINUE
};

typedef struct {
   const char *cur;
   int line;
   int type;
   const char *value;
   int len;
   int num_chars, num_utf8_bytes, max_num_value;
   const char *error;
   int again;
   const char *tokens_src;
   Value *cur_token, *tokens_end;
} Tokenizer;

enum {
   TOK_type,
   TOK_off,
   TOK_len,
   TOK_line,
   TOK_SIZE
};

typedef struct {
   Tokenizer tok;
   unsigned char *buf;
   int buf_size, buf_len;
   int last_buf_pos;
   DynArray lines;
   Heap *heap;
   Script *script;
   int stack_pos;
   StringHash variables;
   int has_vars;
   int long_jumps, long_func_refs;
   StringHash const_strings;
   StringHash import_aliases;
   LoadScriptFunc load_func;
   void *load_data;
   const char *fname;

   int has_break, has_continue;
   int continue_pc;
   int break_stack_pos, continue_stack_pos;
   DynArray break_jumps;
   DynArray continue_jumps;

   DynArray func_refs;
   
   char *tokens_src;
   Value *tokens_arr;
   Value *tokens_end;
   Value tokens_src_val;
   Value tokens_arr_val;
} Parser;

typedef struct {
   int has_break, has_continue;
   int continue_pc;
   int break_stack_pos, continue_stack_pos;
   int break_jumps_len;
   int continue_jumps_len;
} LoopState;

typedef struct {
   int used;
   int functions_len;
   int locals_len;
} ScriptState;
 
typedef struct {
   char *script_name;
   char *func_name;
} FuncRefHandle;

typedef struct WeakRefHandle {
   uint64_t id;
   int target;
   int value;
   int container;
   Value key;
   struct WeakRefHandle *next;
} WeakRefHandle;

#ifdef _WIN32
#define CopyContext fixscript_CopyContext
#endif

typedef struct {
   Heap *dest, *src;
   Value map;
   int recursion_limit;
   int err;
   LoadScriptFunc load_func;
   void *load_data;
   char **error_msg;
} CopyContext;

enum {
   ET_HASH,
   ET_STRING,
   ET_FLOAT,
   ET_BLOCK
};

enum {
   BT_NORMAL,
   BT_FOR,
   BT_EXPR
};

enum {
   BC_POP,
   BC_POPN,
   BC_LOADN,
   BC_STOREN,
   BC_ADD,
   BC_SUB,
   BC_MUL,
   BC_ADD_MOD,
   BC_SUB_MOD,
   BC_MUL_MOD,
   BC_DIV,
   BC_REM,
   BC_SHL,
   BC_SHR,
   BC_USHR,
   BC_AND,
   BC_OR,
   BC_XOR,
   BC_LT,
   BC_LE,
   BC_GT,
   BC_GE,
   BC_EQ,
   BC_NE,
   BC_EQ_VALUE,
   BC_NE_VALUE,
   BC_BITNOT,
   BC_LOGNOT,
   BC_INC,
   BC_DEC,
   BC_FLOAT_ADD,
   BC_FLOAT_SUB,
   BC_FLOAT_MUL,
   BC_FLOAT_DIV,
   BC_FLOAT_LT,
   BC_FLOAT_LE,
   BC_FLOAT_GT,
   BC_FLOAT_GE,
   BC_FLOAT_EQ,
   BC_FLOAT_NE,
   BC_RETURN,
   BC_RETURN2,
   BC_CALL_DIRECT,
   BC_CALL_DYNAMIC,
   BC_CALL_NATIVE,
   BC_CALL2_DIRECT,
   BC_CALL2_DYNAMIC,
   BC_CALL2_NATIVE,
   BC_CLEAN_CALL2,
   BC_CREATE_ARRAY,
   BC_CREATE_HASH,
   BC_ARRAY_GET,
   BC_ARRAY_SET,
   BC_ARRAY_APPEND,
   BC_HASH_GET,
   BC_HASH_SET,

   BC_CONST_P8      = 0x38,
   BC_CONST_N8      = 0x39,
   BC_CONST_P16     = 0x3A,
   BC_CONST_N16     = 0x3B,
   BC_CONST_I32     = 0x3C,
   BC_CONST_F32     = 0x3D,
   BC_CONSTM1       = 0x3E,
   BC_CONST0        = 0x3F,
   BC_BRANCH0       = 0x60,
   BC_JUMP0         = 0x68,
   BC_BRANCH_LONG   = 0x70,
   BC_JUMP_LONG     = 0x71,
   BC_LOOP_I8       = 0x72,
   BC_LOOP_I16      = 0x73,
   BC_LOOP_I32      = 0x74,
   BC_LOAD_LOCAL    = 0x75,
   BC_STORE_LOCAL   = 0x76,
   BC_SWITCH        = 0x77,
   BC_LENGTH        = 0x78,
   BC_CONST_STRING  = 0x79,
   BC_STRING_CONCAT = 0x7A,
   BC_EXTENDED      = 0x7D,
   BC_CONST63       = 0x7E,
   BC_CONST64       = 0x7F,
   BC_STOREM64      = 0x80,
   BC_LOADM64       = 0xC0
};

enum {
   BC_EXT_MIN,
   BC_EXT_MAX,
   BC_EXT_ABS,
   BC_EXT_ADD32,
   BC_EXT_SUB32,
   BC_EXT_MUL64,
   BC_EXT_UMUL64,
   BC_EXT_MUL64_LONG,
   BC_EXT_DIV64,
   BC_EXT_UDIV64,
   BC_EXT_REM64,
   BC_EXT_UREM64,
   BC_EXT_FLOAT,
   BC_EXT_INT,
   BC_EXT_FABS,
   BC_EXT_FMIN,
   BC_EXT_FMAX,
   BC_EXT_FLOOR,
   BC_EXT_CEIL,
   BC_EXT_ROUND,
   BC_EXT_POW,
   BC_EXT_SQRT,
   BC_EXT_CBRT,
   BC_EXT_EXP,
   BC_EXT_LN,
   BC_EXT_LOG2,
   BC_EXT_LOG10,
   BC_EXT_SIN,
   BC_EXT_COS,
   BC_EXT_ASIN,
   BC_EXT_ACOS,
   BC_EXT_TAN,
   BC_EXT_ATAN,
   BC_EXT_ATAN2,
   BC_EXT_DBL_FLOAT,
   BC_EXT_DBL_INT,
   BC_EXT_DBL_CONV_DOWN,
   BC_EXT_DBL_CONV_UP,
   BC_EXT_DBL_ADD,
   BC_EXT_DBL_SUB,
   BC_EXT_DBL_MUL,
   BC_EXT_DBL_DIV,
   BC_EXT_DBL_CMP_LT,
   BC_EXT_DBL_CMP_LE,
   BC_EXT_DBL_CMP_GT,
   BC_EXT_DBL_CMP_GE,
   BC_EXT_DBL_CMP_EQ,
   BC_EXT_DBL_CMP_NE,
   BC_EXT_DBL_FABS,
   BC_EXT_DBL_FMIN,
   BC_EXT_DBL_FMAX,
   BC_EXT_DBL_FLOOR,
   BC_EXT_DBL_CEIL,
   BC_EXT_DBL_ROUND,
   BC_EXT_DBL_POW,
   BC_EXT_DBL_SQRT,
   BC_EXT_DBL_CBRT,
   BC_EXT_DBL_EXP,
   BC_EXT_DBL_LN,
   BC_EXT_DBL_LOG2,
   BC_EXT_DBL_LOG10,
   BC_EXT_DBL_SIN,
   BC_EXT_DBL_COS,
   BC_EXT_DBL_ASIN,
   BC_EXT_DBL_ACOS,
   BC_EXT_DBL_TAN,
   BC_EXT_DBL_ATAN,
   BC_EXT_DBL_ATAN2,
   BC_EXT_IS_INT,
   BC_EXT_IS_FLOAT,
   BC_EXT_IS_ARRAY,
   BC_EXT_IS_STRING,
   BC_EXT_IS_HASH,
   BC_EXT_IS_SHARED,
   BC_EXT_IS_FUNCREF,
   BC_EXT_IS_WEAKREF,
   BC_EXT_IS_HANDLE
};
   
static const Constant zero_const = { { 0, 0 }, 1 };
static const Constant one_const = { { 1, 0 }, 1 };

#define FUNC_REF_HANDLE_TYPE INT_MAX
#define WEAK_REF_HANDLE_TYPE (INT_MAX-1)
static volatile int native_handles_alloc_cnt = INT_MAX-1;
static volatile int heap_keys_cnt = 0;

#define ASSUME(name,expr) typedef char assume_##name[(expr)? 1 : -1]

ASSUME(short_is_2_bytes, sizeof(short) == 2);
ASSUME(int_is_4_bytes, sizeof(int) == 4);

#define FLAGS_SIZE(size) (((size)+31) >> 5)
#define FLAGS_IDX(idx) ((idx) >> 5)
#define FLAGS_ARR(arr, idx) (arr)->flags[FLAGS_IDX(idx)]
#define FLAGS_BIT(idx) (1 << ((idx) & 31))
#define IS_ARRAY(arr, idx) (FLAGS_ARR(arr, idx) & FLAGS_BIT(idx))
#define SET_IS_ARRAY(arr, idx) FLAGS_ARR(arr, idx) |= FLAGS_BIT(idx)
#define CLEAR_IS_ARRAY(arr, idx) FLAGS_ARR(arr, idx) &= ~FLAGS_BIT(idx)
#define ASSIGN_IS_ARRAY(arr, idx, value) ((value)? (SET_IS_ARRAY(arr, idx)) : (CLEAR_IS_ARRAY(arr, idx)))

#define HAS_DATA(arr, idx) IS_ARRAY(arr, (1<<(arr)->size) + (idx))
#define SET_HAS_DATA(arr, idx) SET_IS_ARRAY(arr, (1<<(arr)->size) + (idx))
#define CLEAR_HAS_DATA(arr, idx) CLEAR_IS_ARRAY(arr, (1<<(arr)->size) + (idx))

#define DIRECT_IS_ARRAY(flags, idx) ((flags)[FLAGS_IDX(idx)] & FLAGS_BIT(idx))
#define DIRECT_SET_IS_ARRAY(flags, idx) (flags)[FLAGS_IDX(idx)] |= FLAGS_BIT(idx)
#define DIRECT_CLEAR_IS_ARRAY(flags, idx) (flags)[FLAGS_IDX(idx)] &= ~FLAGS_BIT(idx)
#define DIRECT_ASSIGN_IS_ARRAY(flags, idx, value) ((value)? (DIRECT_SET_IS_ARRAY(flags, idx)) : (DIRECT_CLEAR_IS_ARRAY(flags, idx)))


#ifdef FIXEMBED_TOKEN_DUMP
void fixembed_dump_tokens(const char *fname, Tokenizer *tok);
#endif

#ifndef _WIN32
float fminf(float x, float y);
float fmaxf(float x, float y);
float roundf(float x);
float log2f(float x);

double fmin(double x, double y);
double fmax(double x, double y);
double round(double x);
double log2(double x);
#endif


#if !defined(__GNUC__) && defined(_WIN32)
static inline int __sync_add_and_fetch(volatile int *ptr, int amount)
{
   if (amount == 1) {
      return InterlockedIncrement((volatile LONG *)ptr);
   }
   else {
      return InterlockedExchangeAdd((volatile LONG *)ptr, amount) + amount;
   }
}

static inline int __sync_sub_and_fetch(volatile int *ptr, int amount)
{
   if (amount == 1) {
      return InterlockedDecrement((volatile LONG *)ptr);
   }
   else {
      return InterlockedExchangeAdd((volatile LONG *)ptr, -amount) - amount;
   }
}

static inline int __sync_val_compare_and_swap(volatile int *ptr, int old_value, int new_value)
{
   return InterlockedCompareExchange((volatile LONG *)ptr, new_value, old_value) == old_value;
}
#endif


static void *malloc_array(int nmemb, int size)
{
   int64_t mul = ((int64_t)nmemb) * ((int64_t)size);
   if (mul < 0 || mul > INTPTR_MAX) {
      return NULL;
   }
   return malloc((size_t)mul);
}


static void *realloc_array(void *ptr, int nmemb, int size)
{
   int64_t mul = ((int64_t)nmemb) * ((int64_t)size);
   if (mul < 0 || mul > INTPTR_MAX) {
      return NULL;
   }
   return realloc(ptr, (size_t)mul);
}


static char *string_dup(const char *s, size_t n)
{
   char *d;

   d = malloc(n+1);
   if (!d) return NULL;
   memcpy(d, s, n);
   d[n] = '\0';
   return d;
}


static char *string_format(const char *fmt, ...)
{
   va_list ap;
   int len;
   char *s;

   va_start(ap, fmt);
   len = vsnprintf(NULL, 0, fmt, ap);
   va_end(ap);
   if (len < 0) return NULL;
   
   s = malloc(len+1);
   if (!s) return NULL;

   va_start(ap, fmt);
   vsnprintf(s, len+1, fmt, ap);
   va_end(ap);
   return s;
}


static int string_append(String *str, const char *fmt, ...)
{
   va_list ap;
   int len;
   int new_size;
   char *new_data;

   va_start(ap, fmt);
   len = vsnprintf(NULL, 0, fmt, ap);
   va_end(ap);
   if (len < 0) return 0;

   if (str->len + len + 1 > str->size) {
      new_size = (str->size == 0? 16 : str->size);
      while (str->len + len + 1 > new_size) {
         if (new_size >= (1<<30)) return 0;
         new_size <<= 1;
      }
      new_data = realloc(str->data, new_size);
      if (!new_data) return 0;
      str->data = new_data;
      str->size = new_size;
   }

   va_start(ap, fmt);
   vsnprintf(str->data + str->len, len+1, fmt, ap);
   va_end(ap);

   str->len += len;
   return 1;
}


static void *string_hash_set(StringHash *hash, char *key, void *value)
{
   StringHash new_hash;
   int i, idx;
   unsigned int keyhash = 5381;
   unsigned char *s;
   void *old_val;
   
   if (hash->slots >= (hash->size >> 2)) {
      new_hash.size = hash->size;
      if (hash->len >= (hash->size >> 2)) {
         new_hash.size <<= 1;
      }
      if (new_hash.size == 0) {
         new_hash.size = 4*2;
      }
      new_hash.len = 0;
      new_hash.slots = 0;
      new_hash.data = calloc(new_hash.size, sizeof(void *));
      for (i=0; i<hash->size; i+=2) {
         if (hash->data[i+0]) {
            if (hash->data[i+1]) {
               string_hash_set(&new_hash, hash->data[i+0], hash->data[i+1]);
            }
            else {
               free(hash->data[i+0]);
            }
         }
      }
      free(hash->data);
      *hash = new_hash;
   }

   s = (unsigned char *)key;
   while (*s) {
      keyhash = ((keyhash << 5) + keyhash) + *s++;
   }

   idx = (keyhash << 1) & (hash->size-1);
   for (;;) {
      if (!hash->data[idx+0]) break;
      if (!strcmp(hash->data[idx+0], key)) {
         free(hash->data[idx+0]);
         old_val = hash->data[idx+1];
         hash->data[idx+0] = key;
         hash->data[idx+1] = value;
         if (old_val) hash->len--;
         if (value) hash->len++;
         return old_val;
      }
      idx = (idx + 2) & (hash->size-1);
   }

   if (!value) {
      free(key);
      return NULL;
   }

   hash->len++;
   hash->slots++;
   hash->data[idx+0] = key;
   hash->data[idx+1] = value;
   return NULL;
}


static void *string_hash_get(StringHash *hash, const char *key)
{
   int idx;
   unsigned int keyhash = 5381;
   unsigned char *s;

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

   s = (unsigned char *)key;
   while (*s) {
      keyhash = ((keyhash << 5) + keyhash) + *s++;
   }

   idx = (keyhash << 1) & (hash->size-1);
   for (;;) {
      if (!hash->data[idx+0]) break;
      if (!strcmp(hash->data[idx+0], key)) {
         return hash->data[idx+1];
      }
      idx = (idx + 2) & (hash->size-1);
   }
   return NULL;
}


static const char *string_hash_find_name(StringHash *hash, void *value)
{
   int i;

   for (i=0; i<hash->size; i+=2) {
      if (hash->data[i+1] == value) {
         return hash->data[i+0];
      }
   }
   return NULL;
}


static int dynarray_add(DynArray *arr, void *value)
{
   void *new_data;
   int new_size;

   if (arr->len == arr->size) {
      new_size = arr->size == 0? 4 : arr->size;
      if (new_size >= (1<<30)) {
         return FIXSCRIPT_ERR_OUT_OF_MEMORY;
      }
      new_size <<= 1;
      new_data = realloc_array(arr->data, new_size, sizeof(void *));
      if (!new_data) {
         return FIXSCRIPT_ERR_OUT_OF_MEMORY;
      }
      arr->data = new_data;
      arr->size = new_size;
   }

   arr->data[arr->len++] = value;
   return FIXSCRIPT_SUCCESS;
}


static void dynarray_remove_value_fast(DynArray *arr, void *value)
{
   int i;

   for (i=0; i<arr->len; i++) {
      if (arr->data[i] == value) {
         arr->data[i] = arr->data[--arr->len];
         return;
      }
   }
}


static inline int get_low_mask(int num_bits)
{
   return ~(-1 << num_bits);
}


static inline int get_high_mask(int num_bits)
{
   return ~((uint32_t)-1 >> (uint32_t)num_bits);
}


static inline int get_middle_mask(int start, int end)
{
   return get_low_mask(end - start) << start;
}


static void flags_clear_range(Array *arr, int off, int len)
{
   int start = off;
   int end = off+len;
   int inner_start = (start+31) & ~31;
   int inner_end = end & ~31;

   if (inner_end >= inner_start) {
      if (inner_start - start > 0) {
         arr->flags[start >> 5] &= ~get_high_mask(inner_start - start);
      }
      memset(&arr->flags[inner_start >> 5], 0, (inner_end - inner_start) >> (5-2));
      if (end - inner_end > 0) {
         arr->flags[inner_end >> 5] &= ~get_low_mask(end - inner_end);
      }
   }
   else {
      arr->flags[start >> 5] &= ~get_middle_mask(start & 31, end & 31);
   }
}


static int flags_is_array_clear_in_range(Array *arr, int off, int len)
{
   int start = off;
   int end = off+len;
   int inner_start = (start+31) & ~31;
   int inner_end = end & ~31;
   int i, n;

   if (inner_end >= inner_start) {
      if (inner_start - start > 0) {
         if (arr->flags[start >> 5] & get_high_mask(inner_start - start)) {
            return 0;
         }
      }
      for (i=(inner_start >> 5), n=(inner_end - inner_start) >> 5; i<n; i++) {
         if (arr->flags[i]) return 0;
      }
      if (end - inner_end > 0) {
         if (arr->flags[end >> 5] & get_low_mask(end - inner_end)) {
            return 0;
         }
      }
   }
   else {
      if (arr->flags[start >> 5] & get_middle_mask(start & 31, end & 31)) {
         return 0;
      }
   }
   return 1;
}


static int bitarray_size(int elem_size, int count)
{
   return ((elem_size * count + 31) >> 5)+1;
}


static void bitarray_set(int *array, int elem_size, int index, int value)
{
   unsigned int *arr = (unsigned int *)array;
   int idx = (elem_size * index) >> 5;
   int off = (elem_size * index) & 31;
   int mask = (1 << elem_size) - 1;
   uint64_t val = arr[idx] | ((uint64_t)arr[idx+1] << 32);

   val &= ~((uint64_t)mask << off);
   val |= (uint64_t)(value & mask) << off;

   arr[idx+0] = (unsigned int)val;
   arr[idx+1] = (unsigned int)(val >> 32);
}


static int bitarray_get(int *array, int elem_size, int index)
{
   unsigned int *arr = (unsigned int *)array;
   int idx = (elem_size * index) >> 5;
   int off = (elem_size * index) & 31;
   int mask = (1 << elem_size) - 1;
   uint64_t val = arr[idx] | ((uint64_t)arr[idx+1] << 32);

   return (val >> off) & mask;
}


////////////////////////////////////////////////////////////////////////
// Heap management:
////////////////////////////////////////////////////////////////////////


static void *func_ref_handle_func(Heap *heap, int op, void *p1, void *p2)
{
   FuncRefHandle *handle = p1, *copy;
   char *s;
   int len;
   
   switch (op) {
      case HANDLE_OP_FREE:
         free(handle->script_name);
         free(handle->func_name);
         free(handle);
         break;

      case HANDLE_OP_COPY:
         copy = calloc(1, sizeof(FuncRefHandle));
         if (!copy) return NULL;
         copy->script_name = strdup(handle->script_name);
         copy->func_name = strdup(handle->func_name);
         if (!copy->script_name || !copy->func_name) {
            func_ref_handle_func(heap, HANDLE_OP_FREE, copy, NULL);
            return NULL;
         }
         return copy;

      case HANDLE_OP_TO_STRING:
         len = strlen(handle->script_name) + strlen(handle->func_name) + 32;
         s = malloc(len);
         if (!s) return NULL;
         snprintf(s, len, "<%s:%s> [unresolved]", handle->script_name, handle->func_name);
         return s;
   }

   return NULL;
}


static void *weak_ref_handle_func(Heap *heap, int op, void *p1, void *p2)
{
   WeakRefHandle *handle = p1, *hash_handle, **prev;
   char buf[64];
   
   switch (op) {
      case HANDLE_OP_FREE:
         if (handle->target) {
            snprintf(buf, sizeof(buf), "%d", handle->target);
            hash_handle = string_hash_get(&heap->weak_refs, buf);
            if (hash_handle == handle) {
               string_hash_set(&heap->weak_refs, strdup(buf), handle->next);
               if (!handle->next) {
                  heap->data[handle->target].has_weak_refs = 0;
               }
            }
            else {
               prev = &hash_handle->next;
               for (;;) {
                  hash_handle = hash_handle->next;
                  if (!hash_handle) break;
                  if (hash_handle == handle) {
                     *prev = handle->next;
                     break;
                  }
               }
            }
         }
         free(handle);
         break;

      case HANDLE_OP_COMPARE:
         return (void *)(intptr_t)(handle->id == ((WeakRefHandle *)p2)->id);

      case HANDLE_OP_HASH:
         return (void *)(intptr_t)((int)handle->id ^ (int)(handle->id >> 32));

      case HANDLE_OP_TO_STRING:
         if (handle->target) {
            snprintf(buf, sizeof(buf), "(weak reference to #%d)", handle->target);
         }
         else {
            snprintf(buf, sizeof(buf), "(empty weak reference)");
         }
         return strdup(buf);
   
      case HANDLE_OP_MARK_REFS:
         if (handle->container) {
            fixscript_mark_ref(heap, (Value) { handle->container, 1 });
            if (handle->key.is_array != 2) {
               fixscript_mark_ref(heap, handle->key);
            }
         }
         break;
   }
   return NULL;
}


static void add_root(Heap *heap, Value value)
{
   int old_size = heap->roots.size;
   dynarray_add(&heap->roots, (void *)(intptr_t)value.value);
   heap->total_size += (int64_t)(heap->roots.size - old_size) * sizeof(void *);
}


static void clear_roots(Heap *heap)
{
   heap->roots.len = 0;
}


static inline int get_array_value(Array *arr, int idx)
{
   if (arr->type == ARR_BYTE) {
      return arr->byte_data[idx];
   }
   else if (arr->type == ARR_SHORT) {
      return arr->short_data[idx];
   }
   else {
      return arr->data[idx];
   }
}


static inline void set_array_value(Array *arr, int idx, int value)
{
   if (arr->type == ARR_BYTE) {
      arr->byte_data[idx] = value;
   }
   else if (arr->type == ARR_SHORT) {
      arr->short_data[idx] = value;
   }
   else {
      arr->data[idx] = value;
   }
}


static void mark_array(Heap *heap, Array *arr)
{
   int i, val, len;

   if (arr->reachable) {
      return;
   }

   arr->reachable = 1;
   if (arr->is_handle || arr->is_shared) {
      if (arr->is_handle == 2) {
         arr->handle_func(heap, HANDLE_OP_MARK_REFS, arr->handle_ptr, NULL);
      }
      return;
   }
   
   len = arr->hash_slots >= 0? (1 << arr->size) : arr->len;
   for (i=0; i<len; i++) {
      if (IS_ARRAY(arr, i)) {
         val = get_array_value(arr, i);
         if (val > 0 && val < heap->size) {
            mark_array(heap, &heap->data[val]);
         }
      }
   }
}


static int collect_heap(Heap *heap, int *hash_removal)
{
   SharedArrayHeader *sah;
   WeakRefHandle *wrh, *orig_wrh, *hash_wrh, **prev;
   Array *arr, *new_data;
   Value container;
   int i, err, num_reclaimed=0, elem_size, max_index=0, new_size, num_used=0;
   char buf[64];
   
   mark_array(heap, &heap->data[LOCALS_IDX]);
   mark_array(heap, &heap->data[STACK_IDX]);
   for (i=0; i<heap->roots.len; i++) {
      mark_array(heap, &heap->data[(intptr_t)heap->roots.data[i]]);
   }
   for (i=0; i<heap->ext_roots.len; i++) {
      mark_array(heap, &heap->data[(intptr_t)heap->ext_roots.data[i]]);
   }
   
   for (i=1; i<heap->size; i++) {
      arr = &heap->data[i];
      if (arr->len != -1 && !arr->reachable && !arr->is_static) {
         if (arr->is_handle) {
            if (arr->is_handle == 2) {
               arr->handle_func(heap, HANDLE_OP_FREE, arr->handle_ptr, NULL);
            }
            else if (arr->handle_free) {
               arr->handle_free(arr->handle_ptr);
            }
         }
         else if (arr->is_shared) {
            if (arr->flags) {
               sah = ARRAY_SHARED_HEADER(arr);
               elem_size = arr->type == ARR_BYTE? 1 : arr->type == ARR_SHORT? 2 : 4;
               snprintf(buf, sizeof(buf), "%p,%d,%d,%p", arr->data, arr->len, elem_size, sah->free_data);
               string_hash_set(&heap->shared_arrays, strdup(buf), NULL);
               if (__sync_sub_and_fetch(&sah->refcnt, 1) == 0) {
                  if (sah->free_func) {
                     sah->free_func(sah->free_data);
                  }
                  free(sah);
               }
               heap->total_size -= (int64_t)FLAGS_SIZE(arr->size) * sizeof(int) + (int64_t)arr->size * elem_size;
            }
         }
         else {
            free(arr->flags);
            if (arr->type == ARR_BYTE) {
               free(arr->byte_data);
               heap->total_size -= (int64_t)FLAGS_SIZE(arr->size) * sizeof(int) + (int64_t)arr->size * sizeof(unsigned char);
            }
            else if (arr->type == ARR_SHORT) {
               free(arr->short_data);
               heap->total_size -= (int64_t)FLAGS_SIZE(arr->size) * sizeof(int) + (int64_t)arr->size * sizeof(unsigned short);
            }
            else {
               free(arr->data);
               if (arr->hash_slots >= 0) {
                  heap->total_size -= ((int64_t)FLAGS_SIZE((1<<arr->size)*2) + (int64_t)bitarray_size(arr->size-1, 1<<arr->size)) * sizeof(int) + (int64_t)(1 << arr->size) * sizeof(int);
               }
               else {
                  heap->total_size -= (int64_t)FLAGS_SIZE(arr->size) * sizeof(int) + (int64_t)arr->size * sizeof(int);
               }
            }
         }
         if (arr->has_weak_refs) {
            snprintf(buf, sizeof(buf), "%d", i);
            hash_wrh = string_hash_get(&heap->weak_refs, buf);
            orig_wrh = hash_wrh;
            prev = &hash_wrh;
            for (wrh = hash_wrh; wrh; prev = &wrh->next, wrh = wrh->next) {
               if (wrh->container) {
                  container = (Value) { wrh->container, 1 };
                  if (fixscript_is_hash(heap, container)) {
                     if (wrh->key.is_array == 2) {
                        fixscript_remove_hash_elem(heap, container, (Value) { wrh->value, 1 }, NULL);
                     }
                     else {
                        fixscript_remove_hash_elem(heap, container, wrh->key, NULL);
                        wrh->key.is_array = 2;
                     }
                     wrh->container = 0;
                     wrh->target = 0;
                     *prev = wrh->next;
                     if (hash_removal) {
                        *hash_removal = 1;
                     }
                  }
                  else {
                     if (wrh->key.is_array == 2) {
                        err = fixscript_append_array_elem(heap, container, (Value) { wrh->value, 1 });
                     }
                     else {
                        err = fixscript_append_array_elem(heap, container, wrh->key);
                        if (!err) {
                           wrh->key.is_array = 2;
                        }
                     }
                     if (!err) {
                        wrh->container = 0;
                        wrh->target = 0;
                        *prev = wrh->next;
                     }
                  }
               }
               else {
                  wrh->target = 0;
                  *prev = wrh->next;
               }
            }
            if (hash_wrh != orig_wrh) {
               string_hash_set(&heap->weak_refs, strdup(buf), hash_wrh);
            }
            else {
               arr->flags = NULL;
               arr->data = NULL;
               arr->size = 0;
               arr->len = 0;
               arr->type = ARR_BYTE;
               continue;
            }
         }
         arr->len = -1;
         if (num_reclaimed++ == 0) {
            heap->next_idx = i;
         }
      }

      arr->reachable = 0;

      if (arr->len != -1) {
         max_index = i;
         num_used++;
      }
   }

   if (heap->size > ARRAYS_GROW_CUTOFF) {
      new_size = (max_index + ARRAYS_GROW_CUTOFF) & ~(ARRAYS_GROW_CUTOFF-1);
      if (heap->size - num_used < ARRAYS_GROW_CUTOFF) {
         new_size += ARRAYS_GROW_CUTOFF;
      }
      
      if (new_size < heap->size) {
         new_data = realloc_array(heap->data, new_size, sizeof(Array));
         if (new_data) {
            heap->total_size -= (int64_t)(heap->size - new_size) * sizeof(Array);
            heap->data = new_data;
            heap->size = new_size;
            if (heap->next_idx >= new_size) {
               heap->next_idx = 1;
            }
         }
      }
   }

   return num_reclaimed;
}


static void reclaim_array(Heap *heap, int idx, Array *arr)
{
   int i;

   if (!arr) {
      arr = &heap->data[idx];
   }
   free(arr->flags);
   if (arr->type == ARR_BYTE) {
      free(arr->byte_data);
      heap->total_size -= (int64_t)FLAGS_SIZE(arr->size) * sizeof(int) + (int64_t)arr->size * sizeof(unsigned char);
   }
   else if (arr->type == ARR_SHORT) {
      free(arr->short_data);
      heap->total_size -= (int64_t)FLAGS_SIZE(arr->size) * sizeof(int) + (int64_t)arr->size * sizeof(unsigned short);
   }
   else {
      free(arr->data);
      if (arr->hash_slots >= 0) {
         heap->total_size -= ((int64_t)FLAGS_SIZE((1<<arr->size)*2) + (int64_t)bitarray_size(arr->size-1, 1<<arr->size)) * sizeof(int) + (int64_t)(1 << arr->size) * sizeof(int);
      }
      else {
         heap->total_size -= (int64_t)FLAGS_SIZE(arr->size) * sizeof(int) + (int64_t)arr->size * sizeof(int);
      }
   }
   arr->len = -1;

   for (i=0; i<heap->roots.len; i++) {
      if ((intptr_t)heap->roots.data[i] == idx) {
         heap->roots.data[i] = heap->roots.data[--heap->roots.len];
         break;
      }
   }
}


void fixscript_collect_heap(Heap *heap)
{
   int hash_removal;

   clear_roots(heap);

   do {
      hash_removal = 0;
      collect_heap(heap, &hash_removal);
   }
   while (hash_removal);
}


static Value create_array(Heap *heap, int type, int size)
{
   int new_size, alloc_size;
   Array *new_data;
   int i, idx = -1, collected = -1;
   Array *arr;

   if (heap->total_size > heap->total_cap) {
      collect_heap(heap, NULL);
      while (heap->total_size + (heap->total_size >> 2) > heap->total_cap) {
         heap->total_cap <<= 1;
      }
      while (heap->total_size < (heap->total_cap >> 2) && heap->total_cap > 1) {
         heap->total_cap >>= 1;
      }
   }
   
   for (i=heap->next_idx; i<heap->size; i++) {
      if (heap->data[i].len == -1) {
         idx = i;
         heap->next_idx = i+1;
         break;
      }
   }

   if (idx == -1 && (collected = collect_heap(heap, NULL)) > 0) {
      idx = heap->next_idx++;
   }

   if (idx == -1 || (collected > 0 && (heap->size - collected) >= heap->size - (heap->size >> 2))) {
      if (heap->size >= FUNC_REF_OFFSET) {
         return fixscript_int(0);
      }
      if (idx == -1) idx = heap->size;
      new_size = heap->size >= ARRAYS_GROW_CUTOFF? heap->size + ARRAYS_GROW_CUTOFF : heap->size << 1;
      new_data = realloc_array(heap->data, new_size, sizeof(Array));
      if (!new_data) {
         return fixscript_int(0);
      }
      heap->total_size += (int64_t)(new_size - heap->size) * sizeof(Array);
      heap->data = new_data;
      for (i=heap->size; i<new_size; i++) {
         arr = &heap->data[i];
         arr->len = -1;
         arr->reachable = 0;
      }
      heap->size = new_size;
   }

   arr = &heap->data[idx];
   if (size > 0) {
      if (type == ARR_HASH && size >= 30) {
         return fixscript_int(0);
      }
      alloc_size = (type == ARR_HASH? (1 << size) : size);

      arr->flags = malloc_array(type == ARR_HASH? FLAGS_SIZE(alloc_size*2) + bitarray_size(size-1, alloc_size) : FLAGS_SIZE(alloc_size), sizeof(int));
      if (!arr->flags) return fixscript_int(0);

      if (type == ARR_BYTE) {
         arr->byte_data = malloc_array(alloc_size, sizeof(unsigned char));
         if (!arr->byte_data) {
            free(arr->flags);
            return fixscript_int(0);
         }
         heap->total_size += (int64_t)FLAGS_SIZE(alloc_size) * sizeof(int) + (int64_t)alloc_size * sizeof(unsigned char);
      }
      else if (type == ARR_SHORT) {
         arr->short_data = malloc_array(alloc_size, sizeof(unsigned short));
         if (!arr->short_data) {
            free(arr->flags);
            return fixscript_int(0);
         }
         heap->total_size += (int64_t)FLAGS_SIZE(alloc_size) * sizeof(int) + (int64_t)alloc_size * sizeof(unsigned short);
      }
      else {
         arr->data = malloc_array(alloc_size, sizeof(int));
         if (!arr->data) {
            free(arr->flags);
            return fixscript_int(0);
         }
         if (type == ARR_HASH) {
            heap->total_size += ((int64_t)FLAGS_SIZE(alloc_size*2) + (int64_t)bitarray_size(size-1, alloc_size)) * sizeof(int) + (int64_t)alloc_size * sizeof(int);
         }
         else {
            heap->total_size += (int64_t)FLAGS_SIZE(alloc_size) * sizeof(int) + (int64_t)alloc_size * sizeof(int);
         }
      }
      arr->size = size;
   }
   else {
      arr->flags = NULL;
      arr->data = NULL;
      arr->size = 0;
   }

   arr->type = type;
   arr->len = 0;
   arr->ext_refcnt = 0;
   arr->is_string = 0;
   arr->is_handle = 0;
   arr->is_static = 0;
   arr->is_shared = 0;
   arr->has_weak_refs = 0;
   return (Value) { idx, 1 };
}


Value fixscript_create_array(Heap *heap, int len)
{
   Value value;
   Array *arr;
   
   if (len < 0) {
      return fixscript_int(0);
   }
   value = create_array(heap, ARR_BYTE, len);
   if (!value.is_array) return value;
   add_root(heap, value);
   if (len > 0) {
      arr = &heap->data[value.value];
      arr->len = len;
      memset(arr->flags, 0, FLAGS_SIZE(len) * sizeof(int));
      memset(arr->byte_data, 0, len);
   }
   return value;
}


Value fixscript_create_byte_array(Heap *heap, const char *buf, int len)
{
   Value arr_val;
   Array *arr;
   
   arr_val = create_array(heap, ARR_BYTE, len);
   if (!arr_val.is_array) return arr_val;
   add_root(heap, arr_val);
   arr = &heap->data[arr_val.value];

   arr->len = len;
   memset(arr->flags, 0, FLAGS_SIZE(len) * sizeof(int));
   memcpy(arr->byte_data, buf, len);
   return arr_val;
}


Value fixscript_create_shared_array(Heap *heap, int len, int elem_size)
{
   void *ptr = calloc(len, elem_size);
   if (!ptr) {
      return fixscript_int(0);
   }
   return fixscript_create_shared_array_from(heap, -1, ptr, len, elem_size, free, ptr);
}


Value fixscript_create_shared_array_from(Heap *heap, int type, void *ptr, int len, int elem_size, HandleFreeFunc free_func, void *data)
{
   SharedArrayHeader *sah;
   Value value;
   Array *arr;
   int arr_type;
   char buf[64];

   switch (elem_size) {
      case 1: arr_type = ARR_BYTE; break;
      case 2: arr_type = ARR_SHORT; break;
      case 4: arr_type = ARR_INT; break;

      default:
         if (free_func) {
            free_func(data);
         }
         return fixscript_int(0);
   }

   snprintf(buf, sizeof(buf), "%p,%d,%d,%p", ptr, len, elem_size, data);
   value.value = (intptr_t)string_hash_get(&heap->shared_arrays, buf);
   if (value.value) {
      value.is_array = 1;
      return value;
   }
   
   value = create_array(heap, arr_type, 0);
   if (!value.value) {
      if (free_func) {
         free_func(data);
      }
      return value;
   }

   arr = &heap->data[value.value];
   arr->data = ptr;
   arr->flags = calloc(1, sizeof(SharedArrayHeader) + FLAGS_SIZE(len) * sizeof(int));

   if (!arr->flags) {
      if (free_func) {
         free_func(data);
      }
      arr->data = NULL;
      reclaim_array(heap, value.value, arr);
      return fixscript_int(0);
   }

   arr->len = len;
   arr->size = len;
   arr->flags = (int *)(((char *)arr->flags) + sizeof(SharedArrayHeader));
   arr->is_shared = 1;

   sah = ARRAY_SHARED_HEADER(arr);
   sah->refcnt = 1;
   sah->type = type;
   sah->free_func = free_func;
   sah->free_data = data;

   string_hash_set(&heap->shared_arrays, strdup(buf), (void *)(intptr_t)value.value);
   
   heap->total_size += (int64_t)FLAGS_SIZE(len) * sizeof(int) + (int64_t)len * elem_size;

   add_root(heap, value);
   heap->handle_created = 1;
   return value;
}


int fixscript_set_array_length(Heap *heap, Value arr_val, int len)
{
   Array *arr;
   int new_size;
   int *new_flags;
   void *new_data;

   if (!arr_val.is_array || arr_val.value <= 0 || arr_val.value >= heap->size) {
      return FIXSCRIPT_ERR_INVALID_ACCESS;
   }

   arr = &heap->data[arr_val.value];
   if (arr->len == -1 || arr->hash_slots >= 0) {
      return FIXSCRIPT_ERR_INVALID_ACCESS;
   }

   if (arr->is_static) {
      return FIXSCRIPT_ERR_STATIC_WRITE;
   }

   if (arr->is_shared) {
      return FIXSCRIPT_ERR_INVALID_SHARED_ARRAY_OPERATION;
   }

   if (len < 0) {
      return FIXSCRIPT_ERR_OUT_OF_BOUNDS;
   }

   if (len > arr->size) {
      new_size = arr->size == 0? 2 : arr->size;
      do {
         if (new_size >= (1<<30)) {
            return FIXSCRIPT_ERR_OUT_OF_MEMORY;
         }
         new_size <<= 1;
      }
      while (len > new_size);

      new_flags = realloc_array(arr->flags, FLAGS_SIZE(new_size), sizeof(int));
      if (!new_flags) {
         return FIXSCRIPT_ERR_OUT_OF_MEMORY;
      }
      arr->flags = new_flags;
      
      if (arr->type == ARR_BYTE) {
         new_data = realloc_array(arr->byte_data, new_size, sizeof(unsigned char));
         if (!new_data) {
            return FIXSCRIPT_ERR_OUT_OF_MEMORY;
         }
         arr->byte_data = new_data;
         heap->total_size += (int64_t)(FLAGS_SIZE(new_size) - FLAGS_SIZE(arr->size)) * sizeof(int);
         heap->total_size += (int64_t)(new_size - arr->size) * sizeof(unsigned char);
      }
      else if (arr->type == ARR_SHORT) {
         new_data = realloc_array(arr->short_data, new_size, sizeof(unsigned short));
         if (!new_data) {
            return FIXSCRIPT_ERR_OUT_OF_MEMORY;
         }
         arr->short_data = new_data;
         heap->total_size += (int64_t)(FLAGS_SIZE(new_size) - FLAGS_SIZE(arr->size)) * sizeof(int);
         heap->total_size += (int64_t)(new_size - arr->size) * sizeof(unsigned short);
      }
      else {
         new_data = realloc_array(arr->data, new_size, sizeof(int));
         if (!new_data) {
            return FIXSCRIPT_ERR_OUT_OF_MEMORY;
         }
         arr->data = new_data;
         heap->total_size += (int64_t)(FLAGS_SIZE(new_size) - FLAGS_SIZE(arr->size)) * sizeof(int);
         heap->total_size += (int64_t)(new_size - arr->size) * sizeof(int);
      }

      arr->size = new_size;
   }

   if (len > arr->len) {
      // note: integer overflow can't happen because it wouldn't get past the allocation code
      if (arr->type == ARR_BYTE) {
         memset(&arr->byte_data[arr->len], 0, (len - arr->len) * sizeof(unsigned char));
      }
      else if (arr->type == ARR_SHORT) {
         memset(&arr->short_data[arr->len], 0, (len - arr->len) * sizeof(unsigned short));
      }
      else {
         memset(&arr->data[arr->len], 0, (len - arr->len) * sizeof(int));
      }
      flags_clear_range(arr, arr->len, len - arr->len);
   }
   arr->len = len;

   return FIXSCRIPT_SUCCESS;
}


int fixscript_get_array_length(Heap *heap, Value arr_val, int *len)
{
   Array *arr;

   if (!arr_val.is_array || arr_val.value <= 0 || arr_val.value >= heap->size) {
      return FIXSCRIPT_ERR_INVALID_ACCESS;
   }

   arr = &heap->data[arr_val.value];
   if (arr->len == -1) {
      return FIXSCRIPT_ERR_INVALID_ACCESS;
   }

   *len = arr->len;
   return FIXSCRIPT_SUCCESS;
}


int fixscript_is_array(Heap *heap, Value arr_val)
{
   Array *arr;

   if (!arr_val.is_array || arr_val.value <= 0 || arr_val.value >= heap->size) {
      return 0;
   }

   arr = &heap->data[arr_val.value];
   if (arr->len == -1 || arr->hash_slots >= 0) {
      return 0;
   }

   return 1;
}


static int upgrade_array(Heap *heap, Array *arr, int int_val)
{
   unsigned short *short_data;
   int *data;
   int i;

   if (arr->is_shared) {
      return FIXSCRIPT_ERR_INVALID_SHARED_ARRAY_OPERATION;
   }
   
   if (arr->type == ARR_BYTE) {
      if (int_val >= 0 && int_val <= 0xFFFF) {
         short_data = malloc_array(arr->size, sizeof(unsigned short));
         if (!short_data) {
            return FIXSCRIPT_ERR_OUT_OF_MEMORY;
         }
         for (i=0; i<arr->len; i++) {
            short_data[i] = arr->byte_data[i];
         }
         free(arr->byte_data);
         arr->short_data = short_data;
         arr->type = ARR_SHORT;
         heap->total_size += (int64_t)arr->size * 1;
      }
      else {
         data = malloc_array(arr->size, sizeof(int));
         if (!data) {
            return FIXSCRIPT_ERR_OUT_OF_MEMORY;
         }
         for (i=0; i<arr->len; i++) {
            data[i] = arr->byte_data[i];
         }
         free(arr->byte_data);
         arr->data = data;
         arr->type = ARR_INT;
         heap->total_size += (int64_t)arr->size * 3;
      }
   }
   else if (arr->type == ARR_SHORT) {
      data = malloc_array(arr->size, sizeof(int));
      if (!data) {
         return FIXSCRIPT_ERR_OUT_OF_MEMORY;
      }
      for (i=0; i<arr->len; i++) {
         data[i] = arr->short_data[i];
      }
      free(arr->short_data);
      arr->data = data;
      arr->type = ARR_INT;
      heap->total_size += (int64_t)arr->size * 2;
   }

   return FIXSCRIPT_SUCCESS;
}


static int expand_array(Heap *heap, Array *arr, int idx)
{
   int new_size;
   int *new_flags;
   void *new_data;

   if (arr->is_shared) {
      return FIXSCRIPT_ERR_INVALID_SHARED_ARRAY_OPERATION;
   }

   new_size = arr->size == 0? 2 : arr->size;
   do {
      if (new_size >= (1<<30)) {
         return FIXSCRIPT_ERR_OUT_OF_MEMORY;
      }
      new_size <<= 1;
   }
   while (idx >= new_size);

   new_flags = realloc_array(arr->flags, FLAGS_SIZE(new_size), sizeof(int));
   if (!new_flags) {
      return FIXSCRIPT_ERR_OUT_OF_MEMORY;
   }
   arr->flags = new_flags;

   if (arr->type == ARR_BYTE) {
      new_data = realloc_array(arr->byte_data, new_size, sizeof(unsigned char));
      if (!new_data) {
         return FIXSCRIPT_ERR_OUT_OF_MEMORY;
      }
      arr->byte_data = new_data;
      heap->total_size += (int64_t)(FLAGS_SIZE(new_size) - FLAGS_SIZE(arr->size)) * sizeof(int);
      heap->total_size += (int64_t)(new_size - arr->size) * sizeof(unsigned char);
   }
   else if (arr->type == ARR_SHORT) {
      new_data = realloc_array(arr->short_data, new_size, sizeof(unsigned short));
      if (!new_data) {
         return FIXSCRIPT_ERR_OUT_OF_MEMORY;
      }
      arr->short_data = new_data;
      heap->total_size += (int64_t)(FLAGS_SIZE(new_size) - FLAGS_SIZE(arr->size)) * sizeof(int);
      heap->total_size += (int64_t)(new_size - arr->size) * sizeof(unsigned short);
   }
   else {
      new_data = realloc_array(arr->data, new_size, sizeof(int));
      if (!new_data) {
         return FIXSCRIPT_ERR_OUT_OF_MEMORY;
      }
      arr->data = new_data;
      heap->total_size += (int64_t)(FLAGS_SIZE(new_size) - FLAGS_SIZE(arr->size)) * sizeof(int);
      heap->total_size += (int64_t)(new_size - arr->size) * sizeof(int);
   }

   arr->size = new_size;

   return FIXSCRIPT_SUCCESS;
}


int fixscript_set_array_elem(Heap *heap, Value arr_val, int idx, Value value)
{
   Array *arr;
   int ret;

   if (!arr_val.is_array || arr_val.value <= 0 || arr_val.value >= heap->size) {
      return FIXSCRIPT_ERR_INVALID_ACCESS;
   }

   arr = &heap->data[arr_val.value];
   if (arr->len == -1 || arr->hash_slots >= 0) {
      return FIXSCRIPT_ERR_INVALID_ACCESS;
   }

   if (arr->is_static) {
      return FIXSCRIPT_ERR_STATIC_WRITE;
   }

   if (arr->is_shared && !fixscript_is_int(value) && !fixscript_is_float(value)) {
      return FIXSCRIPT_ERR_INVALID_SHARED_ARRAY_OPERATION;
   }

   if (idx < 0 || idx >= arr->len) {
      return FIXSCRIPT_ERR_OUT_OF_BOUNDS;
   }

   if (ARRAY_NEEDS_UPGRADE(arr, value.value)) {
      ret = upgrade_array(heap, arr, value.value);
      if (ret != FIXSCRIPT_SUCCESS) {
         return ret;
      }
   }

   set_array_value(arr, idx, value.value);
   ASSIGN_IS_ARRAY(arr, idx, value.is_array);
   return FIXSCRIPT_SUCCESS;
}


int fixscript_get_array_elem(Heap *heap, Value arr_val, int idx, Value *value)
{
   Array *arr;

   if (!arr_val.is_array || arr_val.value <= 0 || arr_val.value >= heap->size) {
      return FIXSCRIPT_ERR_INVALID_ACCESS;
   }

   arr = &heap->data[arr_val.value];
   if (arr->len == -1 || arr->hash_slots >= 0) {
      return FIXSCRIPT_ERR_INVALID_ACCESS;
   }

   if (idx < 0 || idx >= arr->len) {
      return FIXSCRIPT_ERR_OUT_OF_BOUNDS;
   }

   *value = (Value) { get_array_value(arr, idx), IS_ARRAY(arr, idx) != 0 };
   return FIXSCRIPT_SUCCESS;
}


int fixscript_append_array_elem(Heap *heap, Value arr_val, Value value)
{
   int len, err;

   err = fixscript_get_array_length(heap, arr_val, &len);
   if (err) {
      return err;
   }

   err = fixscript_set_array_length(heap, arr_val, len+1);
   if (err) {
      return err;
   }
   
   return fixscript_set_array_elem(heap, arr_val, len, value);
}


int fixscript_get_array_range(Heap *heap, Value arr_val, int off, int len, Value *values)
{
   Array *arr;
   int i, idx;

   if (!arr_val.is_array || arr_val.value <= 0 || arr_val.value >= heap->size) {
      return FIXSCRIPT_ERR_INVALID_ACCESS;
   }

   arr = &heap->data[arr_val.value];
   if (arr->len == -1 || arr->hash_slots >= 0) {
      return FIXSCRIPT_ERR_INVALID_ACCESS;
   }

   for (i=0; i<len; i++) {
      idx = off+i;
      if (idx < 0 || idx >= arr->len) {
         return FIXSCRIPT_ERR_OUT_OF_BOUNDS;
      }
      values[i] = (Value) { get_array_value(arr, idx), IS_ARRAY(arr, idx) != 0 };
   }

   return FIXSCRIPT_SUCCESS;
}


int fixscript_set_array_range(Heap *heap, Value arr_val, int off, int len, Value *values)
{
   Array *arr;
   int i, idx, ret;
   unsigned int max_value;

   if (!arr_val.is_array || arr_val.value <= 0 || arr_val.value >= heap->size) {
      return FIXSCRIPT_ERR_INVALID_ACCESS;
   }

   arr = &heap->data[arr_val.value];
   if (arr->len == -1 || arr->hash_slots >= 0) {
      return FIXSCRIPT_ERR_INVALID_ACCESS;
   }

   if (arr->is_static) {
      return FIXSCRIPT_ERR_STATIC_WRITE;
   }

   if (arr->is_shared) {
      for (i=0; i<len; i++)  {
         if (!fixscript_is_int(values[i]) && !fixscript_is_float(values[i])) {
            return FIXSCRIPT_ERR_INVALID_SHARED_ARRAY_OPERATION;
         }
      }
   }

   if (arr->type == ARR_BYTE || arr->type == ARR_SHORT) {
      max_value = 0;
      for (i=0; i<len; i++) {
         if ((unsigned int)values[i].value > max_value) {
            max_value = values[i].value;
         }
      }
      if (ARRAY_NEEDS_UPGRADE(arr, max_value)) {
         ret = upgrade_array(heap, arr, max_value);
         if (ret != FIXSCRIPT_SUCCESS) {
            return ret;
         }
      }
   }

   for (i=0; i<len; i++) {
      idx = off+i;
      if (idx < 0 || idx >= arr->len) {
         return FIXSCRIPT_ERR_OUT_OF_BOUNDS;
      }
      set_array_value(arr, idx, values[i].value);
      ASSIGN_IS_ARRAY(arr, idx, values[i].is_array);
   }

   return FIXSCRIPT_SUCCESS;
}


int fixscript_get_array_bytes(Heap *heap, Value arr_val, int off, int len, char *bytes)
{
   Array *arr;
   int i, value;

   if (!arr_val.is_array || arr_val.value <= 0 || arr_val.value >= heap->size) {
      return FIXSCRIPT_ERR_INVALID_ACCESS;
   }

   arr = &heap->data[arr_val.value];
   if (arr->len == -1 || arr->hash_slots >= 0) {
      return FIXSCRIPT_ERR_INVALID_ACCESS;
   }

   if (off < 0 || len < 0 || ((int64_t)off) + ((int64_t)len) > ((int64_t)arr->len)) {
      return FIXSCRIPT_ERR_OUT_OF_BOUNDS;
   }

   if (!arr->is_shared && !flags_is_array_clear_in_range(arr, off, len)) {
      return FIXSCRIPT_ERR_INVALID_BYTE_ARRAY;
   }

   if (arr->type == ARR_BYTE) {
      memcpy(bytes, arr->byte_data + off, len);
   }
   else {
      for (i=0; i<len; i++) {
         value = get_array_value(arr, off+i);
         if (value < 0 || value > 255) {
            return FIXSCRIPT_ERR_INVALID_BYTE_ARRAY;
         }
         bytes[i] = value;
      }
   }

   return FIXSCRIPT_SUCCESS;
}


int fixscript_set_array_bytes(Heap *heap, Value arr_val, int off, int len, char *bytes)
{
   Array *arr;
   int i;

   if (!arr_val.is_array || arr_val.value <= 0 || arr_val.value >= heap->size) {
      return FIXSCRIPT_ERR_INVALID_ACCESS;
   }

   arr = &heap->data[arr_val.value];
   if (arr->len == -1 || arr->hash_slots >= 0) {
      return FIXSCRIPT_ERR_INVALID_ACCESS;
   }

   if (arr->is_static) {
      return FIXSCRIPT_ERR_STATIC_WRITE;
   }

   if (off < 0 || len < 0 || ((int64_t)off) + ((int64_t)len) > ((int64_t)arr->len)) {
      return FIXSCRIPT_ERR_OUT_OF_BOUNDS;
   }

   if (arr->type == ARR_BYTE) {
      memcpy(arr->byte_data + off, bytes, len);
   }
   else {
      for (i=0; i<len; i++) {
         set_array_value(arr, off+i, (uint32_t)(uint8_t)bytes[i]);
      }
   }
   if (!arr->is_shared) {
      flags_clear_range(arr, off, len);
   }

   return FIXSCRIPT_SUCCESS;
}


int fixscript_copy_array(Heap *heap, Value dest, int dest_off, Value src, int src_off, int count)
{
   int buf_size = 1024;
   Value *values;
   int num, err = FIXSCRIPT_SUCCESS;

   if (dest_off < 0 || src_off < 0 || count < 0) {
      return FIXSCRIPT_ERR_OUT_OF_BOUNDS;
   }

   values = malloc_array(MIN(count, buf_size), sizeof(Value));
   if (!values) {
      return FIXSCRIPT_ERR_OUT_OF_MEMORY;
   }

   if (dest.value == src.value && dest_off > src_off) {
      while (count > 0) {
         num = MIN(count, buf_size);

         err = fixscript_get_array_range(heap, src, src_off + count - num, num, values);
         if (err) break;

         err = fixscript_set_array_range(heap, dest, dest_off + count - num, num, values);
         if (err) break;

         count -= num;
      }
   }
   else {
      while (count > 0) {
         num = MIN(count, buf_size);

         err = fixscript_get_array_range(heap, src, src_off, num, values);
         if (err) break;

         err = fixscript_set_array_range(heap, dest, dest_off, num, values);
         if (err) break;

         src_off += num;
         dest_off += num;
         count -= num;
      }
   }

   free(values);
   return err;
}


void *fixscript_get_shared_array_data(Heap *heap, Value arr_val, int *len, int *elem_size, void **data, int expected_type, int *actual_type)
{
   SharedArrayHeader *sah;
   Array *arr;

   if (!arr_val.is_array || arr_val.value <= 0 || arr_val.value >= heap->size) {
      return NULL;
   }

   arr = &heap->data[arr_val.value];
   if (arr->len == -1 || arr->hash_slots >= 0 || !arr->is_shared) {
      return NULL;
   }

   sah = ARRAY_SHARED_HEADER(arr);
   if (expected_type >= 0 && sah->type != expected_type) {
      return NULL;
   }
   if (len) *len = arr->len;
   if (elem_size) *elem_size = arr->type == ARR_BYTE? 1 : arr->type == ARR_SHORT? 2 : 4;
   if (data) *data = sah->free_data;
   if (actual_type) *actual_type = sah->type;
   return arr->data;
}


int fixscript_lock_array(Heap *heap, Value arr_val, int off, int len, void **data, int elem_size, int read)
{
   Array *arr;
   int i, err, arr_elem, value;
   char *buf;

   if (!arr_val.is_array || arr_val.value <= 0 || arr_val.value >= heap->size) {
      return FIXSCRIPT_ERR_INVALID_ACCESS;
   }

   arr = &heap->data[arr_val.value];
   if (arr->len == -1 || arr->hash_slots >= 0) {
      return FIXSCRIPT_ERR_INVALID_ACCESS;
   }

   if (off < 0 || len < 0 || ((int64_t)off) + ((int64_t)len) > ((int64_t)arr->len)) {
      return FIXSCRIPT_ERR_OUT_OF_BOUNDS;
   }

   if (elem_size != 1 && elem_size != 2 && elem_size != 4) {
      return FIXSCRIPT_ERR_INVALID_ACCESS;
   }

   add_root(heap, arr_val);

   arr_elem = arr->type == ARR_BYTE? 1 : arr->type == ARR_SHORT? 2 : 4;
   if (arr->is_shared && arr_elem == elem_size) {
      *data = arr->byte_data + off*elem_size;
      return FIXSCRIPT_SUCCESS;
   }

   if ((int64_t)len * (int64_t)elem_size > INT_MAX) {
      return FIXSCRIPT_ERR_OUT_OF_BOUNDS;
   }

   buf = malloc(len * elem_size);
   if (!buf) {
      return FIXSCRIPT_ERR_OUT_OF_MEMORY;
   }

   if (!read) {
      *data = buf;
      return FIXSCRIPT_SUCCESS;
   }

   if (arr_elem == elem_size) {
      memcpy(buf, arr->byte_data + off*elem_size, len*elem_size);
   }
   else {
      if (elem_size > arr_elem) {
         err = upgrade_array(heap, arr, elem_size == 2? 65535 : -1);
         if (err) {
            free(buf);
            return err;
         }
      }
      if (elem_size == 1) {
         for (i=0; i<len; i++) {
            value = get_array_value(arr, off+i);
            if (value < 0 || value > 255) {
               free(buf);
               return FIXSCRIPT_ERR_INVALID_BYTE_ARRAY;
            }
            buf[i] = value;
         }
      }
      else if (elem_size == 2) {
         for (i=0; i<len; i++) {
            value = get_array_value(arr, off+i);
            if (value < 0 || value > 65535) {
               free(buf);
               return FIXSCRIPT_ERR_INVALID_SHORT_ARRAY;
            }
            ((uint16_t *)buf)[i] = value;
         }
      }
      else {
         for (i=0; i<len; i++) {
            ((int *)buf)[i] = get_array_value(arr, off+i);
         }
      }
   }

   *data = buf;
   return FIXSCRIPT_SUCCESS;
}


void fixscript_unlock_array(Heap *heap, Value arr_val, int off, int len, void **data, int elem_size, int write)
{
   Array *arr;
   int i, arr_elem;
   char *buf = *data;

   *data = NULL;

   arr = &heap->data[arr_val.value];
   arr_elem = arr->type == ARR_BYTE? 1 : arr->type == ARR_SHORT? 2 : 4;

   if (arr->is_shared && arr_elem == elem_size) {
      return;
   }

   if (!write) {
      free(buf);
      return;
   }

   if (arr_elem == elem_size) {
      memcpy(arr->byte_data + off*elem_size, buf, len*elem_size);
   }
   else {
      if (elem_size == 1) {
         for (i=0; i<len; i++) {
            set_array_value(arr, off+i, buf[i]);
         }
      }
      else if (elem_size == 2) {
         for (i=0; i<len; i++) {
            set_array_value(arr, off+i, ((uint16_t *)buf)[i]);
         }
      }
      else {
         for (i=0; i<len; i++) {
            set_array_value(arr, off+i, ((int *)buf)[i]);
         }
      }
      if (!arr->is_shared) {
         flags_clear_range(arr, off, len);
      }
   }
   free(buf);
}


Value fixscript_create_string(Heap *heap, const char *s, int len)
{
   Value arr_val;
   Array *arr;
   int i, j, dest_len = 0;
   unsigned int c, max_value = 0;
   unsigned char c2, c3, c4;
   
   if (!s && len != 0) {
      return fixscript_int(0);
   }

   for (i=0; i < len || (len < 0 && s[i]); i++) {
      c = (unsigned char)s[i];
      if ((c & 0x80) == 0) {
         // nothing
      }
      else if ((c & 0xE0) == 0xC0 && (i+1 < len || (len < 0 && s[i+1]))) {
         c2 = s[++i];
         c = ((c & 0x1F) << 6) | (c2 & 0x3F);
         if (c < 0x80) {
            c = 0xFFFD;
         }
      }
      else if ((c & 0xF0) == 0xE0 && (i+2 < len || (len < 0 && s[i+1] && s[i+2]))) {
         c2 = s[++i];
         c3 = s[++i];
         c = ((c & 0x0F) << 12) | ((c2 & 0x3F) << 6) | (c3 & 0x3F);
         if (c < 0x800) {
            c = 0xFFFD;
         }
      }
      else if ((c & 0xF8) == 0xF0 && (i+3 < len || (len < 0 && s[i+1] && s[i+2] && s[i+3]))) {
         c2 = s[++i];
         c3 = s[++i];
         c4 = s[++i];
         c = ((c & 0x07) << 18) | ((c2 & 0x3F) << 12) | ((c3 & 0x3F) << 6) | (c4 & 0x3F);
         if (c < 0x10000 || c > 0x10FFFF) {
            c = 0xFFFD;
         }
      }
      else {
         c = 0xFFFD;
      }

      if (c >= 0xD800 && c <= 0xDFFF) {
         c = 0xFFFD;
      }

      if (c > max_value) {
         max_value = c;
      }
      dest_len++;
   }

   if (len < 0) {
      len = i;
   }

   arr_val = create_array(heap, max_value > 0xFFFF? ARR_INT : max_value > 0xFF? ARR_SHORT : ARR_BYTE, dest_len);
   if (!arr_val.is_array) return arr_val;
   add_root(heap, arr_val);
   arr = &heap->data[arr_val.value];
   arr->len = dest_len;
   arr->is_string = 1;
   memset(arr->flags, 0, FLAGS_SIZE(dest_len) * sizeof(int));

   for (i=0, j=0; i<len; i++, j++) {
      c = s[i];
      if ((c & 0x80) == 0) {
         // nothing
      }
      else if ((c & 0xE0) == 0xC0 && (i+1 < len || (len < 0 && s[i+1]))) {
         c2 = s[++i];
         c = ((c & 0x1F) << 6) | (c2 & 0x3F);
      }
      else if ((c & 0xF0) == 0xE0 && (i+2 < len || (len < 0 && s[i+1] && s[i+2]))) {
         c2 = s[++i];
         c3 = s[++i];
         c = ((c & 0x0F) << 12) | ((c2 & 0x3F) << 6) | (c3 & 0x3F);
      }
      else if ((c & 0xF8) == 0xF0 && (i+3 < len || (len < 0 && s[i+1] && s[i+2] && s[i+3]))) {
         c2 = s[++i];
         c3 = s[++i];
         c4 = s[++i];
         c = ((c & 0x07) << 18) | ((c2 & 0x3F) << 12) | ((c3 & 0x3F) << 6) | (c4 & 0x3F);
      }
      else {
         c = 0xFFFD;
      }

      if (c >= 0xD800 && c <= 0xDFFF) {
         c = 0xFFFD;
      }

      set_array_value(arr, j, c);
   }
   return arr_val;
}


Value fixscript_create_string_utf16(Heap *heap, const unsigned short *s, int len)
{
   Value arr_val;
   Array *arr;
   int i, j, dest_len = 0;
   unsigned int c, c2, max_value = 0;
   
   if (!s && len != 0) {
      return fixscript_int(0);
   }

   for (i=0; i < len || (len < 0 && s[i]); i++) {
      c = s[i];
      if (c >= 0xD800 && c <= 0xDBFF && (i+1 < len || (len < 0 && s[i+1]))) {
         c2 = s[i+1];
         if (c2 >= 0xDC00 && c2 <= 0xDFFF) {
            c = ((c - 0xD800) << 10) | (c2 - 0xDC00);
            i++;
         }
      }
      if (c >= 0xD800 && c <= 0xDFFF) {
         c = 0xFFFD;
      }
      if (c > max_value) {
         max_value = c;
      }
      dest_len++;
   }

   if (len < 0) {
      len = i;
   }

   arr_val = create_array(heap, max_value > 0xFFFF? ARR_INT : max_value > 0xFF? ARR_SHORT : ARR_BYTE, dest_len);
   if (!arr_val.is_array) return arr_val;
   add_root(heap, arr_val);
   arr = &heap->data[arr_val.value];
   arr->len = dest_len;
   arr->is_string = 1;
   memset(arr->flags, 0, FLAGS_SIZE(dest_len) * sizeof(int));

   for (i=0, j=0; i<len; i++, j++) {
      c = s[i];
      if (c >= 0xD800 && c <= 0xDBFF && (i+1 < len)) {
         c2 = s[i+1];
         if (c2 >= 0xDC00 && c2 <= 0xDFFF) {
            c = 0x10000 + (((c - 0xD800) << 10) | (c2 - 0xDC00));
            i++;
         }
      }
      if (c >= 0xD800 && c <= 0xDFFF) {
         c = 0xFFFD;
      }
      set_array_value(arr, j, c);
   }
   return arr_val;
}


int fixscript_get_string(Heap *heap, Value str_val, int str_off, int str_len, char **str_out, int *len_out)
{
   Array *arr;
   char *s;
   int i, len, c;

   if (!str_val.is_array || str_val.value <= 0 || str_val.value >= heap->size) {
      *str_out = NULL;
      if (len_out) *len_out = 0;
      return FIXSCRIPT_ERR_INVALID_ACCESS;
   }

   arr = &heap->data[str_val.value];
   if (arr->len == -1 || arr->hash_slots >= 0) {
      *str_out = NULL;
      if (len_out) *len_out = 0;
      return FIXSCRIPT_ERR_INVALID_ACCESS;
   }

   if (str_len < 0) {
      str_len = arr->len;
   }

   if (str_off < 0 || str_len < 0 || (int64_t)str_off + (int64_t)str_len > arr->len) {
      *str_out = NULL;
      if (len_out) *len_out = 0;
      return FIXSCRIPT_ERR_OUT_OF_BOUNDS;
   }

   for (i=0, len=0; i<str_len; i++, len++) {
      c = get_array_value(arr, str_off+i);
      if (c == 0 && !len_out) {
         *str_out = NULL;
         if (len_out) *len_out = 0;
         return FIXSCRIPT_ERR_INVALID_NULL_STRING;
      }
      
      if (c < 0 || c > 0x10FFFF) {
         c = 0xFFFD;
      }
      if (c >= 0xD800 && c <= 0xDFFF) {
         c = 0xFFFD;
      }
      
      if (c >= 0x10000) {
         len += 3;
      }
      else if (c >= 0x800) {
         len += 2;
      }
      else if (c >= 0x80) {
         len++;
      }
   }

   s = malloc(len+1);
   if (!s) {
      *str_out = NULL;
      if (len_out) *len_out = 0;
      return FIXSCRIPT_ERR_OUT_OF_MEMORY;
   }

   for (i=0, len=0; i<str_len; i++) {
      c = get_array_value(arr, str_off+i);
      
      if (c < 0 || c > 0x10FFFF) {
         c = 0xFFFD;
      }
      if (c >= 0xD800 && c <= 0xDFFF) {
         c = 0xFFFD;
      }
      
      if (c >= 0x10000) {
         s[len++] = (c >> 18) | 0xF0;
         s[len++] = ((c >> 12) & 0x3F) | 0x80;
         s[len++] = ((c >> 6) & 0x3F) | 0x80;
         s[len++] = (c & 0x3F) | 0x80;
      }
      else if (c >= 0x800) {
         s[len++] = (c >> 12) | 0xE0;
         s[len++] = ((c >> 6) & 0x3F) | 0x80;
         s[len++] = (c & 0x3F) | 0x80;
      }
      else if (c >= 0x80) {
         s[len++] = (c >> 6) | 0xC0;
         s[len++] = (c & 0x3F) | 0x80;
      }
      else {
         s[len++] = c;
      }
   }

   s[len] = 0;
   *str_out = s;
   if (len_out) *len_out = len;
   return FIXSCRIPT_SUCCESS;
}


int fixscript_get_string_utf16(Heap *heap, Value str_val, int str_off, int str_len, unsigned short **str_out, int *len_out)
{
   Array *arr;
   unsigned short *s;
   int i, len, c;

   if (!str_val.is_array || str_val.value <= 0 || str_val.value >= heap->size) {
      *str_out = NULL;
      if (len_out) *len_out = 0;
      return FIXSCRIPT_ERR_INVALID_ACCESS;
   }

   arr = &heap->data[str_val.value];
   if (arr->len == -1 || arr->hash_slots >= 0) {
      *str_out = NULL;
      if (len_out) *len_out = 0;
      return FIXSCRIPT_ERR_INVALID_ACCESS;
   }

   if (str_len < 0) {
      str_len = arr->len;
   }

   if (str_off < 0 || str_len < 0 || (int64_t)str_off + (int64_t)str_len > arr->len) {
      *str_out = NULL;
      if (len_out) *len_out = 0;
      return FIXSCRIPT_ERR_OUT_OF_BOUNDS;
   }

   for (i=0, len=0; i<str_len; i++, len++) {
      c = get_array_value(arr, str_off+i);
      if (c == 0 && !len_out) {
         *str_out = NULL;
         if (len_out) *len_out = 0;
         return FIXSCRIPT_ERR_INVALID_NULL_STRING;
      }

      if (c < 0 || c > 0x10FFFF) {
         c = 0xFFFD;
      }
      if (c >= 0xD800 && c <= 0xDFFF) {
         c = 0xFFFD;
      }
      if (c > 0xFFFF) {
         len++;
      }
   }

   s = malloc((len+1) * sizeof(unsigned short));
   if (!s) {
      *str_out = NULL;
      if (len_out) *len_out = 0;
      return FIXSCRIPT_ERR_OUT_OF_MEMORY;
   }

   for (i=0, len=0; i<str_len; i++) {
      c = get_array_value(arr, str_off+i);
      if (c < 0 || c > 0x10FFFF) {
         c = 0xFFFD;
      }
      if (c >= 0xD800 && c <= 0xDFFF) {
         c = 0xFFFD;
      }
      if (c > 0xFFFF) {
         c -= 0x10000;
         s[len++] = 0xD800 + (c >> 10);
         s[len++] = 0xDC00 + (c & 0x3FF);
         continue;
      }
      s[len++] = c;
   }
   
   s[len] = 0;
   *str_out = s;
   if (len_out) *len_out = len;
   return FIXSCRIPT_SUCCESS;
}


int fixscript_is_string(Heap *heap, Value str_val)
{
   Array *arr;

   if (!str_val.is_array || str_val.value <= 0 || str_val.value >= heap->size) {
      return 0;
   }

   arr = &heap->data[str_val.value];
   if (arr->len == -1 || arr->hash_slots >= 0) {
      return 0;
   }

   return arr->is_string;
}


static unsigned int compute_hash(Heap *heap, Value value, int recursion_limit)
{
   Array *arr;
   int i, val;
   unsigned int hash = 0, entry_hash;
   
   if (recursion_limit <= 0) {
      return 0;
   }

   if (value.is_array) {
      if (value.value <= 0 || value.value >= heap->size) {
         return value.value;
      }
      arr = &heap->data[value.value];
      if (arr->len == -1) {
         return value.value;
      }
      
      if (arr->is_handle) {
         if (arr->is_handle == 2) {
            return (unsigned int)(intptr_t)arr->handle_func(heap, HANDLE_OP_HASH, arr->handle_ptr, NULL);
         }
         return value.value;
      }

      if (arr->hash_slots >= 0) {
         for (i=0; i<(1<<arr->size); i+=2) {
            if (HAS_DATA(arr, i+0) && HAS_DATA(arr, i+1)) {
               if (IS_ARRAY(arr, i+0)) {
                  entry_hash = compute_hash(heap, (Value) { arr->data[i+0], 1 }, recursion_limit-1);
               }
               else {
                  entry_hash = arr->data[i+0];
               }

               if (IS_ARRAY(arr, i+1)) {
                  entry_hash = entry_hash * 31 + compute_hash(heap, (Value) { arr->data[i+1], 1 }, recursion_limit-1);
               }
               else {
                  entry_hash = entry_hash * 31 + arr->data[i+1];
               }

               hash ^= entry_hash;
            }
         }
      }
      else {
         for (i=0; i<arr->len; i++) {
            val = get_array_value(arr, i);
            if (IS_ARRAY(arr, i)) {
               val = compute_hash(heap, (Value) { val, 1 }, recursion_limit-1);
            }
            hash = hash*31 + ((unsigned int)val);
         }
      }

      return hash;
   }

   return value.value;
}


static int get_hash_elem(Heap *heap, Array *arr, Value key_val, Value *value_val);

static int compare_values(Heap *heap, Value value1, Value value2, int recursion_limit)
{
   Array *arr1, *arr2;
   Value val1, val2;
   int i;
   
   if (recursion_limit <= 0) {
      return 0;
   }

   if (value1.is_array && !value2.is_array) {
      return 0;
   }

   if (!value1.is_array && value2.is_array) {
      return 0;
   }

   if (value1.is_array) {
      if (value1.value == value2.value) {
         return 1;
      }

      if (fixscript_is_float(value1) && fixscript_is_float(value2)) {
         return fixscript_get_float(value1) == fixscript_get_float(value2);
      }

      if (value1.value <= 0 || value1.value >= heap->size || value2.value <= 0 || value2.value >= heap->size) {
         return 0;
      }

      arr1 = &heap->data[value1.value];
      arr2 = &heap->data[value2.value];

      if (arr1->len != arr2->len || arr1->len == -1) {
         return 0;
      }

      if ((arr1->hash_slots >= 0 && arr2->hash_slots < 0) || (arr1->hash_slots < 0 && arr2->hash_slots >= 0)) {
         return 0;
      }

      if (arr1->is_handle || arr2->is_handle) {
         if (arr1->is_handle == 2 && arr2->is_handle == 2 && arr1->type == arr2->type) {
            return arr1->handle_func(heap, HANDLE_OP_COMPARE, arr1->handle_ptr, arr2->handle_ptr) != NULL;
         }
         return 0;
      }

      if (arr1->hash_slots >= 0) {
         for (i=0; i<(1<<arr1->size); i+=2) {
            if (HAS_DATA(arr1, i+0) && HAS_DATA(arr1, i+1)) {
               val1 = (Value) { arr1->data[i+1], IS_ARRAY(arr1, i+1) };
               if (get_hash_elem(heap, arr2, (Value) { arr1->data[i+0], IS_ARRAY(arr1, i+0) }, &val2) != FIXSCRIPT_SUCCESS) {
                  return 0;
               }
               
               if (!compare_values(heap, val1, val2, recursion_limit-1)) {
                  return 0;
               }
            }
         }
      }
      else {
         for (i=0; i<arr1->len; i++) {
            val1 = (Value) { get_array_value(arr1, i), IS_ARRAY(arr1, i) };
            val2 = (Value) { get_array_value(arr2, i), IS_ARRAY(arr2, i) };
            if (!compare_values(heap, val1, val2, recursion_limit-1)) {
               return 0;
            }
         }
      }

      return 1;
   }

   return (value1.value == value2.value);
}


static unsigned int rehash(unsigned int a)
{
   a = (a+0x7ed55d16) + (a<<12);
   a = (a^0xc761c23c) ^ (a>>19);
   a = (a+0x165667b1) + (a<<5);
   a = (a+0xd3a2646c) ^ (a<<9);
   a = (a+0xfd7046c5) + (a<<3);
   a = (a^0xb55a4f09) ^ (a>>16);
   return a;
}


static Value create_hash(Heap *heap)
{
   Value arr_val;
   Array *arr;
   
   arr_val = create_array(heap, ARR_HASH, 3); // 4 entries * 2 = 8 = 1<<3
   if (!arr_val.is_array) return arr_val;
   arr = &heap->data[arr_val.value];
   memset(arr->flags, 0, (FLAGS_SIZE((1<<arr->size) * 2) + bitarray_size(arr->size-1, 1<<arr->size)) * sizeof(int));
   memset(arr->data, 0, (1<<arr->size) * sizeof(int));
   return arr_val;
}


Value fixscript_create_hash(Heap *heap)
{
   Value arr_val;

   arr_val = create_hash(heap);
   add_root(heap, arr_val);
   return arr_val;
}


int fixscript_is_hash(Heap *heap, Value hash_val)
{
   Array *arr;

   if (!hash_val.is_array || hash_val.value <= 0 || hash_val.value >= heap->size) {
      return 0;
   }

   arr = &heap->data[hash_val.value];
   if (arr->len == -1 || arr->hash_slots < 0) {
      return 0;
   }

   return !arr->is_handle;
}


static int expand_hash(Heap *heap, Value hash_val, Array *arr)
{
   Array old;
   int i, err, idx;
   int old_flags_size, new_flags_size;
   int new_size;
   int *new_flags;
   int *new_data;

   old = *arr;

   new_size = arr->size;
   if (arr->len >= ((1<<new_size) >> 2)) {
      if (new_size >= 30) return FIXSCRIPT_ERR_OUT_OF_MEMORY;
      new_size++;
   }

   if (new_size >= 30) return FIXSCRIPT_ERR_OUT_OF_MEMORY;

   old_flags_size = FLAGS_SIZE((1<<arr->size) * 2) + bitarray_size(arr->size-1, 1<<arr->size);
   new_flags_size = FLAGS_SIZE((1<<new_size) * 2) + bitarray_size(new_size-1, 1<<new_size);

   new_flags = calloc(new_flags_size, sizeof(int));
   if (!new_flags) {
      return FIXSCRIPT_ERR_OUT_OF_MEMORY;
   }

   new_data = calloc((1<<new_size), sizeof(int));
   if (!new_data) {
      free(new_flags);
      return FIXSCRIPT_ERR_OUT_OF_MEMORY;
   }

   heap->total_size += (int64_t)(new_flags_size - old_flags_size) * sizeof(int);
   heap->total_size += (int64_t)((1<<new_size) - (1<<arr->size)) * sizeof(int);

   arr->len = 0;
   arr->size = new_size;
   arr->flags = new_flags;
   arr->data = new_data;
   arr->hash_slots = 0;

   for (i=0; i<old.hash_slots; i++) {
      idx = bitarray_get(&old.flags[FLAGS_SIZE((1<<old.size)*2)], old.size-1, i) << 1;

      if (HAS_DATA(&old, idx+0) && HAS_DATA(&old, idx+1)) {
         err = fixscript_set_hash_elem(heap, hash_val, (Value) { old.data[idx+0], IS_ARRAY(&old, idx+0) }, (Value) { old.data[idx+1], IS_ARRAY(&old, idx+1) });
         if (err != FIXSCRIPT_SUCCESS) {
            free(arr->flags);
            free(arr->data);
            *arr = old;
            return err;
         }
      }
   }

   free(old.flags);
   free(old.data);
   return FIXSCRIPT_SUCCESS;
}


static int set_hash_elem(Heap *heap, Value hash_val, Value key_val, Value value_val, int *key_was_present)
{
   Array *arr;
   int idx, err, mask;

   if (!hash_val.is_array || hash_val.value <= 0 || hash_val.value >= heap->size) {
      return FIXSCRIPT_ERR_INVALID_ACCESS;
   }

   arr = &heap->data[hash_val.value];
   if (arr->len == -1 || arr->hash_slots < 0 || arr->is_handle) {
      return FIXSCRIPT_ERR_INVALID_ACCESS;
   }

   if (arr->hash_slots >= ((1<<arr->size) >> 2)) {
      err = expand_hash(heap, hash_val, arr);
      if (err != FIXSCRIPT_SUCCESS) return err;
   }

   mask = (1<<arr->size)-1;
   idx = (rehash(compute_hash(heap, key_val, MAX_COMPARE_RECURSION)) << 1) & mask;
   for (;;) {
      if (!HAS_DATA(arr, idx+0)) break;

      if (HAS_DATA(arr, idx+1) && compare_values(heap, (Value) { arr->data[idx+0], IS_ARRAY(arr, idx+0) }, key_val, MAX_COMPARE_RECURSION)) {
         arr->data[idx+1] = value_val.value;
         ASSIGN_IS_ARRAY(arr, idx+1, value_val.is_array);
         if (key_was_present) {
            *key_was_present = 1;
         }
         return FIXSCRIPT_SUCCESS;
      }

      idx = (idx+2) & mask;
   }

   bitarray_set(&arr->flags[FLAGS_SIZE((1<<arr->size)*2)], arr->size-1, arr->hash_slots, idx >> 1);

   arr->len++;
   arr->hash_slots++;
   SET_HAS_DATA(arr, idx+0);
   SET_HAS_DATA(arr, idx+1);

   arr->data[idx+0] = key_val.value;
   ASSIGN_IS_ARRAY(arr, idx+0, key_val.is_array);

   arr->data[idx+1] = value_val.value;
   ASSIGN_IS_ARRAY(arr, idx+1, value_val.is_array);
   return FIXSCRIPT_SUCCESS;
}


int fixscript_set_hash_elem(Heap *heap, Value hash_val, Value key_val, Value value_val)
{
   return set_hash_elem(heap, hash_val, key_val, value_val, NULL);
}


static int get_hash_elem(Heap *heap, Array *arr, Value key_val, Value *value_val)
{
   int idx, mask;

   mask = (1<<arr->size)-1;
   idx = (rehash(compute_hash(heap, key_val, MAX_COMPARE_RECURSION)) << 1) & mask;

   for (;;) {
      if (!HAS_DATA(arr, idx+0)) break;

      if (HAS_DATA(arr, idx+1) && compare_values(heap, (Value) { arr->data[idx+0], IS_ARRAY(arr, idx+0) }, key_val, MAX_COMPARE_RECURSION)) {
         if (value_val) {
            *value_val = (Value) { arr->data[idx+1], IS_ARRAY(arr, idx+1) != 0 };
         }
         return FIXSCRIPT_SUCCESS;
      }

      idx = (idx+2) & mask;
   }

   if (value_val) {
      *value_val = fixscript_int(0);
   }
   return FIXSCRIPT_ERR_KEY_NOT_FOUND;
}


int fixscript_get_hash_elem(Heap *heap, Value hash_val, Value key_val, Value *value_val)
{
   Array *arr;

   if (!hash_val.is_array || hash_val.value <= 0 || hash_val.value >= heap->size) {
      if (value_val) {
         *value_val = fixscript_int(0);
      }
      return FIXSCRIPT_ERR_INVALID_ACCESS;
   }

   arr = &heap->data[hash_val.value];
   if (arr->len == -1 || arr->hash_slots < 0 || arr->is_handle) {
      if (value_val) {
         *value_val = fixscript_int(0);
      }
      return FIXSCRIPT_ERR_INVALID_ACCESS;
   }

   return get_hash_elem(heap, arr, key_val, value_val);
}


int fixscript_remove_hash_elem(Heap *heap, Value hash_val, Value key_val, Value *value_val)
{
   Array *arr;
   int idx, mask;

   if (!hash_val.is_array || hash_val.value <= 0 || hash_val.value >= heap->size) {
      if (value_val) {
         *value_val = fixscript_int(0);
      }
      return FIXSCRIPT_ERR_INVALID_ACCESS;
   }

   arr = &heap->data[hash_val.value];
   if (arr->len == -1 || arr->hash_slots < 0 || arr->is_handle) {
      if (value_val) {
         *value_val = fixscript_int(0);
      }
      return FIXSCRIPT_ERR_INVALID_ACCESS;
   }

   mask = (1<<arr->size)-1;
   idx = (rehash(compute_hash(heap, key_val, MAX_COMPARE_RECURSION)) << 1) & mask;

   for (;;) {
      if (!HAS_DATA(arr, idx+0)) break;

      if (HAS_DATA(arr, idx+1) && compare_values(heap, (Value) { arr->data[idx+0], IS_ARRAY(arr, idx+0) }, key_val, MAX_COMPARE_RECURSION)) {
         if (value_val) {
            *value_val = (Value) { arr->data[idx+1], IS_ARRAY(arr, idx+1) != 0 };
         }
         CLEAR_HAS_DATA(arr, idx+1);
         CLEAR_IS_ARRAY(arr, idx+0);
         CLEAR_IS_ARRAY(arr, idx+1);
         arr->data[idx+0] = 0;
         arr->data[idx+1] = 0;
         arr->len--;
         return FIXSCRIPT_SUCCESS;
      }

      idx = (idx+2) & mask;
   }

   if (value_val) {
      *value_val = fixscript_int(0);
   }
   return FIXSCRIPT_ERR_KEY_NOT_FOUND;
}


int fixscript_clear_hash(Heap *heap, Value hash_val)
{
   Array *arr;

   if (!hash_val.is_array || hash_val.value <= 0 || hash_val.value >= heap->size) {
      return FIXSCRIPT_ERR_INVALID_ACCESS;
   }

   arr = &heap->data[hash_val.value];
   if (arr->len == -1 || arr->hash_slots < 0 || arr->is_handle) {
      return FIXSCRIPT_ERR_INVALID_ACCESS;
   }

   memset(arr->flags, 0, (FLAGS_SIZE((1<<arr->size)*2) + bitarray_size(arr->size-1, 1<<arr->size)) * sizeof(int));
   memset(arr->data, 0, (1<<arr->size) * sizeof(int));
   arr->len = 0;
   arr->hash_slots = 0;
   return FIXSCRIPT_SUCCESS;
}


int fixscript_iter_hash(Heap *heap, Value hash_val, Value *key_val, Value *value_val, int *pos)
{
   Array *arr;
   int size, idx;

   if (!hash_val.is_array || hash_val.value <= 0 || hash_val.value >= heap->size) {
      return 0;
   }
   arr = &heap->data[hash_val.value];
   if (arr->len == -1 || arr->hash_slots < 0 || arr->is_handle) {
      return 0;
   }

   size = arr->hash_slots;

   while (*pos < size) {
      idx = bitarray_get(&arr->flags[FLAGS_SIZE((1<<arr->size)*2)], arr->size-1, *pos) << 1;
      if (HAS_DATA(arr, idx+0) && HAS_DATA(arr, idx+1)) break;
      (*pos)++;
   }

   if (*pos >= size) {
      return 0;
   }

   *key_val = (Value) { arr->data[idx+0], IS_ARRAY(arr, idx+0) != 0 };
   *value_val = (Value) { arr->data[idx+1], IS_ARRAY(arr, idx+1) != 0 };
   (*pos)++;
   return 1;
}


Value fixscript_create_handle(Heap *heap, int type, void *handle, HandleFreeFunc free_func)
{
   Value handle_val;
   Array *arr;

   if (!handle) {
      return fixscript_int(0);
   }

   if (type < 0) {
      if (free_func) {
         free_func(handle);
      }
      return fixscript_int(0);
   }

   handle_val = create_array(heap, type, 0);
   if (!handle_val.is_array) {
      if (free_func) {
         free_func(handle);
      }
      return handle_val;
   }
   add_root(heap, handle_val);
   arr = &heap->data[handle_val.value];
   arr->is_handle = 1;
   arr->handle_free = free_func;
   arr->handle_ptr = handle;
   heap->handle_created = 1;
   return handle_val;
}


Value fixscript_create_value_handle(Heap *heap, int type, void *handle, HandleFunc handle_func)
{
   Value handle_val;
   Array *arr;

   if (!handle) {
      return fixscript_int(0);
   }

   handle_val = fixscript_create_handle(heap, type, handle, NULL);
   if (!handle_val.value) {
      handle_func(heap, HANDLE_OP_FREE, handle, NULL);
      return handle_val;
   }

   arr = &heap->data[handle_val.value];
   arr->is_handle = 2;
   arr->handle_func = handle_func;
   return handle_val;
}


void *fixscript_get_handle(Heap *heap, Value handle_val, int expected_type, int *actual_type)
{
   Array *arr;

   if (!handle_val.is_array || handle_val.value <= 0 || handle_val.value >= heap->size) {
      if (actual_type) {
         *actual_type = -1;
      }
      return NULL;
   }

   arr = &heap->data[handle_val.value];
   if (arr->len == -1 || arr->hash_slots < 0 || !arr->is_handle) {
      if (actual_type) {
         *actual_type = -1;
      }
      return NULL;
   }

   if (actual_type) {
      *actual_type = arr->type;
   }
   if (expected_type >= 0 && arr->type != expected_type) {
      return NULL;
   }
   return arr->handle_ptr;
}


void fixscript_register_handle_types(volatile int *offset, int count)
{
   int new_offset;
   
   if (*offset == 0) {
      new_offset = __sync_sub_and_fetch(&native_handles_alloc_cnt, count);
      (void)__sync_val_compare_and_swap(offset, 0, new_offset);
   }
}


int fixscript_create_weak_ref(Heap *heap, Value value, Value *container, Value *key, Value *weak_ref)
{
   Array *arr;
   WeakRefHandle *handle = NULL, *hash_handle = NULL;
   Value handle_val;
   int is_hash=0, is_array=0, len, err;
   char buf[16];

   if (!value.value) {
      *weak_ref = fixscript_int(0);
      return FIXSCRIPT_SUCCESS;
   }

   if (!value.is_array || value.value <= 0 || value.value >= heap->size) {
      return FIXSCRIPT_ERR_INVALID_ACCESS;
   }

   if (container) {
      is_hash = fixscript_is_hash(heap, *container);
      is_array = fixscript_is_array(heap, *container);
      if (!is_hash && !is_array) {
         return FIXSCRIPT_ERR_INVALID_ACCESS;
      }
   }

   if (key && !container) {
      return FIXSCRIPT_ERR_INVALID_ACCESS;
   }

   arr = &heap->data[value.value];
   if (arr->len == -1) {
      return FIXSCRIPT_ERR_INVALID_ACCESS;
   }

   snprintf(buf, sizeof(buf), "%d", value.value);

   if (arr->has_weak_refs) {
      hash_handle = string_hash_get(&heap->weak_refs, buf);
      for (handle = hash_handle; handle; handle = handle->next) {
         if (!container && handle->container == 0) break;
         if (container && handle->container == container->value) {
            if (!key && handle->key.is_array == 2) break;
            if (key && handle->key.value == key->value && handle->key.is_array == key->is_array) {
               break;
            }
         }
      }
   }
   
   if (!handle) {
      if (is_array) {
         err = fixscript_append_array_elem(heap, *container, fixscript_int(-1));
         if (err) return err;

         err = fixscript_get_array_length(heap, *container, &len);
         if (err) return err;

         err = fixscript_set_array_length(heap, *container, len-1);
         if (err) return err;
      }

      handle = calloc(1, sizeof(WeakRefHandle));
      if (!handle) {
         return FIXSCRIPT_ERR_OUT_OF_MEMORY;
      }
      handle->id = heap->weak_id_cnt++;
      handle->target = value.value;
      handle->container = container? container->value : 0;
      if (key) {
         handle->key = *key;
      }
      else {
         handle->key.is_array = 2;
      }
      handle->next = hash_handle;
      string_hash_set(&heap->weak_refs, strdup(buf), handle);
      arr->has_weak_refs = 1;
      
      handle_val = fixscript_create_value_handle(heap, WEAK_REF_HANDLE_TYPE, handle, weak_ref_handle_func);
      if (!handle_val.value) {
         return FIXSCRIPT_ERR_OUT_OF_MEMORY;
      }

      handle->value = handle_val.value;
   }

   *weak_ref = (Value) { handle->value, 1 };
   return FIXSCRIPT_SUCCESS;
}


int fixscript_get_weak_ref(Heap *heap, Value weak_ref, Value *value)
{
   WeakRefHandle *handle;

   if (!weak_ref.value) {
      *value = fixscript_int(0);
      return FIXSCRIPT_SUCCESS;
   }
   
   handle = fixscript_get_handle(heap, weak_ref, WEAK_REF_HANDLE_TYPE, NULL);
   if (!handle) {
      return FIXSCRIPT_ERR_INVALID_ACCESS;
   }

   if (handle->target) {
      *value = (Value) { handle->target, 1 };
   }
   else {
      *value = fixscript_int(0);
   }
   return FIXSCRIPT_SUCCESS;
}


int fixscript_is_weak_ref(Heap *heap, Value weak_ref)
{
   return fixscript_get_handle(heap, weak_ref, WEAK_REF_HANDLE_TYPE, NULL) != NULL;
}


static void add_stack_entry(Heap *heap, Value trace, int pc)
{
   Function *func;
   NativeFunction *nfunc;
   Value elem;
   int i, j, line;
   char *s, *custom_func_name;
   const char *script_name, *func_name;
   Constant *constant;
   char *buf;

   for (i=0; i<heap->native_functions.len; i++) {
      nfunc = heap->native_functions.data[i];
      if (pc == nfunc->bytecode_ident_pc) {
         func_name = string_hash_find_name(&heap->native_functions_hash, nfunc);
         elem = fixscript_create_string(heap, func_name? func_name : "(replaced native function)", -1);
         fixscript_append_array_elem(heap, trace, elem);
         return;
      }
   }

   for (i=heap->functions.len-1; i > 0; i--) {
      func = heap->functions.data[i];
      if (pc >= func->addr) {
         script_name = string_hash_find_name(&heap->scripts, func->script);
         func_name = string_hash_find_name(&func->script->functions, func);

         custom_func_name = NULL;
         buf = malloc(9+strlen(func_name)+1);
         if (buf) {
            sprintf(buf, "function_%s", func_name);
            *strrchr(buf, '#') = '_';
            constant = string_hash_get(&func->script->constants, buf);
            if (constant && constant->local) {
               if (fixscript_get_string(heap, constant->value, 0, -1, &custom_func_name, NULL) == 0) {
                  func_name = custom_func_name;
               }
            }
            free(buf);
         }

         line = 0;
         for (j=func->lines_start; j<func->lines_end; j++) {
            if (pc == heap->lines[j].pc) {
               line = heap->lines[j].line;
               break;
            }
         }
         
         s = string_format("%s (%s:%d)", func_name, script_name, line);
         elem = fixscript_create_string(heap, s, -1);
         if (func_name[0]) {
            fixscript_append_array_elem(heap, trace, elem);
         }
         free(s);
         free(custom_func_name);
         return;
      }
   }
}

static Value create_error(Heap *heap, Value msg, int skip_last, int extra_pc)
{
   Array *stack;
   Value error, trace;
   int i, pc;

   error = fixscript_create_array(heap, 2);
   trace = fixscript_create_array(heap, 0);
   if (!error.value || !trace.value) {
      return msg.value? msg : fixscript_int(1);
   }

   fixscript_set_array_elem(heap, error, 0, msg);
   fixscript_set_array_elem(heap, error, 1, trace);

   if (extra_pc) {
      add_stack_entry(heap, trace, extra_pc);
   }
   
   stack = &heap->data[STACK_IDX];
   for (i=stack->len-(skip_last? 2:1); i>=0; i--) {
      if (IS_ARRAY(stack, i) && (stack->data[i] & (1<<31))) {
         pc = stack->data[i] & ~(1<<31);
         if (pc < (1<<23)) {
            add_stack_entry(heap, trace, pc);
            stack = &heap->data[STACK_IDX];
         }
      }
   }

   return error;
}


Value fixscript_create_error(Heap *heap, Value msg)
{
   return create_error(heap, msg, 0, 0);
}


Value fixscript_create_error_string(Heap *heap, const char *s)
{
   if (!s) {
      return fixscript_int(1);
   }
   return fixscript_create_error(heap, fixscript_create_string(heap, s, -1));
}


Value fixscript_error(Heap *heap, Value *error, int code)
{
   *error = fixscript_create_error_string(heap, fixscript_get_error_msg(code));
   return fixscript_int(0);
}


const char *fixscript_get_error_msg(int error_code)
{
   switch (error_code) {
      case FIXSCRIPT_ERR_INVALID_ACCESS:                 return "invalid array access";
      case FIXSCRIPT_ERR_INVALID_BYTE_ARRAY:             return "invalid byte array";
      case FIXSCRIPT_ERR_INVALID_SHORT_ARRAY:            return "invalid short array";
      case FIXSCRIPT_ERR_INVALID_NULL_STRING:            return "invalid null-terminated string";
      case FIXSCRIPT_ERR_STATIC_WRITE:                   return "write access to static string";
      case FIXSCRIPT_ERR_OUT_OF_BOUNDS:                  return "array out of bounds access";
      case FIXSCRIPT_ERR_OUT_OF_MEMORY:                  return "out of memory";
      case FIXSCRIPT_ERR_INVALID_SHARED_ARRAY_OPERATION: return "invalid shared array operation";
      case FIXSCRIPT_ERR_KEY_NOT_FOUND:                  return "hash key not found";
      case FIXSCRIPT_ERR_RECURSION_LIMIT:                return "recursion limit exceeded";
      case FIXSCRIPT_ERR_UNSERIALIZABLE_REF:             return "unserializable reference occurred";
      case FIXSCRIPT_ERR_BAD_FORMAT:                     return "bad format";
      case FIXSCRIPT_ERR_FUNC_REF_LOAD_ERROR:            return "script load error during resolving of function reference";
   }
   return NULL;
}


static Value builtin_log(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   char *s;
   int err;

   if (fixscript_is_string(heap, params[0])) {
      err = fixscript_get_string(heap, params[0], 0, -1, &s, NULL);
   }
   else {
      err = fixscript_to_string(heap, params[0], 0, &s, NULL);
   }
   if (err != FIXSCRIPT_SUCCESS) {
      return fixscript_error(heap, error, err);
   }

   fprintf(stderr, "%s\n", s);
   fflush(stderr);
   free(s);
   return fixscript_int(0);
}


static Value builtin_dump(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   NativeFunction *log_func;
   Value value;
   char *s;
   int len, err;
   
   err = fixscript_to_string(heap, params[0], 1, &s, &len);
   if (err != FIXSCRIPT_SUCCESS) {
      return fixscript_error(heap, error, err);
   }

   if (len > 0 && s[len-1] == '\n') {
      len--;
   }
   value = fixscript_create_string(heap, s, len);
   free(s);

   log_func = string_hash_get(&heap->native_functions_hash, "log#1");
   return log_func->func(heap, error, 1, &value, log_func->data);
}


static Value builtin_to_string(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   int newlines = 0, len, err;
   char *s;
   Value ret;
   
   if (num_params == 2 && params[1].value) {
      newlines = 1;
   }

   err = fixscript_to_string(heap, params[0], newlines, &s, &len);
   if (err != FIXSCRIPT_SUCCESS) {
      return fixscript_error(heap, error, err);
   }

   ret = fixscript_create_string(heap, s, len);
   free(s);
   return ret;
}


static Value builtin_error(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   return create_error(heap, params[0], 1, 0);
}


static Value builtin_clone(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   Value clone;
   int deep = (data != NULL);
   int err;

   err = fixscript_clone(heap, params[0], deep, &clone);
   if (err != FIXSCRIPT_SUCCESS) {
      return fixscript_error(heap, error, err);
   }

   return clone;
}


static Value builtin_array_create(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   Value value;
   int err, type, elem_size = 1;

   if (!fixscript_is_int(params[0]) || params[0].value < 0) {
      *error = fixscript_create_error_string(heap, "length must be positive integer");
      return fixscript_int(0);
   }

   if (num_params == 2) {
      if (!fixscript_is_int(params[1])) {
         *error = fixscript_create_error_string(heap, "element size must be integer");
         return fixscript_int(0);
      }
      elem_size = fixscript_get_int(params[1]);
   }

   switch (elem_size) {
      case 1: type = ARR_BYTE; break;
      case 2: type = ARR_SHORT; break;
      case 4: type = ARR_INT; break;
      default:
         *error = fixscript_create_error_string(heap, "element size must be 1, 2 or 4");
         return fixscript_int(0);
   }

   value = create_array(heap, type, params[0].value);
   if (!value.is_array) {
      return fixscript_error(heap, error, FIXSCRIPT_ERR_OUT_OF_MEMORY);
   }

   err = fixscript_set_array_length(heap, value, params[0].value);
   if (err) {
      return fixscript_error(heap, error, err);
   }

   add_root(heap, value);
   return value;
}


static Value builtin_array_create_shared(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   Value value;
   int elem_size;

   if (!fixscript_is_int(params[0]) || params[0].value < 0) {
      *error = fixscript_create_error_string(heap, "length must be positive integer");
      return fixscript_int(0);
   }

   if (!fixscript_is_int(params[1])) {
      *error = fixscript_create_error_string(heap, "element size must be integer");
      return fixscript_int(0);
   }

   elem_size = params[1].value;
   if (elem_size != 1 && elem_size != 2 && elem_size != 4) {
      *error = fixscript_create_error_string(heap, "element size must be 1, 2 or 4");
      return fixscript_int(0);
   }
   
   value = fixscript_create_shared_array(heap, params[0].value, elem_size);
   if (!value.value) {
      return fixscript_error(heap, error, FIXSCRIPT_ERR_OUT_OF_MEMORY);
   }
   return value;
}


static Value builtin_array_get_shared_count(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   Array *arr;

   if (!fixscript_is_array(heap, params[0])) {
      *error = fixscript_create_error_string(heap, "invalid value (not a shared array)");
      return fixscript_int(0);
   }

   arr = &heap->data[params[0].value];
   if (!arr->is_shared) {
      *error = fixscript_create_error_string(heap, "invalid value (not a shared array)");
      return fixscript_int(0);
   }

   return fixscript_int(ARRAY_SHARED_HEADER(arr)->refcnt);
}


static Value builtin_array_set_length(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   int is_object_create = (data != NULL);
   Value arr, len;
   int err;

   if (is_object_create && num_params == 1) {
      arr = fixscript_int(0);
      len = params[0];
   }
   else {
      arr = params[0];
      len = params[1];
   }

   if (!fixscript_is_int(len)) {
      *error = fixscript_create_error_string(heap, "length must be an integer");
      return fixscript_int(0);
   }

   if (fixscript_get_int(len) < 0) {
      *error = fixscript_create_error_string(heap, "length must not be negative");
      return fixscript_int(0);
   }

   if (is_object_create && num_params == 1) {
      arr = fixscript_create_array(heap, 0);
      if (!arr.value) {
         return fixscript_error(heap, error, FIXSCRIPT_ERR_OUT_OF_MEMORY);
      }
   }

   err = fixscript_set_array_length(heap, arr, fixscript_get_int(len));
   if (err != FIXSCRIPT_SUCCESS) {
      return fixscript_error(heap, error, err);
   }

   return is_object_create? arr : fixscript_int(0);
}


static Value builtin_array_copy(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   Value dest_val, src_val;
   int dest_off, src_off, count;
   int err = FIXSCRIPT_SUCCESS;

   if (!fixscript_is_int(params[1])) {
      *error = fixscript_create_error_string(heap, "dest_off must be an integer");
   }
   if (!fixscript_is_int(params[3])) {
      *error = fixscript_create_error_string(heap, "src_off must be an integer");
   }
   if (!fixscript_is_int(params[4])) {
      *error = fixscript_create_error_string(heap, "count must be an integer");
   }
   if (error->value) {
      return fixscript_int(0);
   }

   dest_val = params[0];
   dest_off = fixscript_get_int(params[1]);
   src_val = params[2];
   src_off = fixscript_get_int(params[3]);
   count = fixscript_get_int(params[4]);

   if (dest_off < 0) {
      *error = fixscript_create_error_string(heap, "negative dest_off");
      return fixscript_int(0);
   }
   if (src_off < 0) {
      *error = fixscript_create_error_string(heap, "negative src_off");
      return fixscript_int(0);
   }
   if (count < 0) {
      *error = fixscript_create_error_string(heap, "negative count");
      return fixscript_int(0);
   }

   err = fixscript_copy_array(heap, dest_val, dest_off, src_val, src_off, count);
   if (err) {
      return fixscript_error(heap, error, err);
   }
   return fixscript_int(0);
}


static Value builtin_array_fill(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   int buf_size = 1024;
   Value *values, array, value;
   int off, count;
   int i, num, err = FIXSCRIPT_SUCCESS;

   if (!fixscript_is_int(params[1])) {
      *error = fixscript_create_error_string(heap, "off must be an integer");
      return fixscript_int(0);
   }
   if (!fixscript_is_int(params[2])) {
      *error = fixscript_create_error_string(heap, "count must be an integer");
      return fixscript_int(0);
   }

   array = params[0];
   off = fixscript_get_int(params[1]);
   count = fixscript_get_int(params[2]);
   value = params[3];

   if (off < 0) {
      *error = fixscript_create_error_string(heap, "negative off");
      return fixscript_int(0);
   }
   if (count < 0) {
      *error = fixscript_create_error_string(heap, "negative count");
      return fixscript_int(0);
   }

   values = malloc_array(MIN(count, buf_size), sizeof(Value));
   if (!values) {
      *error = fixscript_create_error_string(heap, "out of memory");
      return fixscript_int(0);
   }

   for (i=0; i<MIN(count, buf_size); i++) {
      values[i] = value;
   }

   while (count > 0) {
      num = MIN(count, buf_size);

      err = fixscript_set_array_range(heap, array, off, num, values);
      if (err != FIXSCRIPT_SUCCESS) break;

      off += num;
      count -= num;
   }

   free(values);

   if (err != FIXSCRIPT_SUCCESS) {
      return fixscript_error(heap, error, err);
   }
   return fixscript_int(0);
}


static Value builtin_array_extract(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   Value array, new_array;
   int off, count;
   int ret;

   if (!fixscript_is_int(params[1])) {
      *error = fixscript_create_error_string(heap, "off must be an integer");
      return fixscript_int(0);
   }
   if (!fixscript_is_int(params[2])) {
      *error = fixscript_create_error_string(heap, "count must be an integer");
      return fixscript_int(0);
   }

   array = params[0];
   off = fixscript_get_int(params[1]);
   count = fixscript_get_int(params[2]);

   if (off < 0) {
      *error = fixscript_create_error_string(heap, "negative off");
      return fixscript_int(0);
   }
   if (count < 0) {
      *error = fixscript_create_error_string(heap, "negative count");
      return fixscript_int(0);
   }

   new_array = fixscript_create_array(heap, count);
   if (!new_array.value) {
      *error = fixscript_create_error_string(heap, "out of memory");
      return fixscript_int(0);
   }
   if (fixscript_is_array(heap, array)) {
      heap->data[new_array.value].is_string = heap->data[array.value].is_string;
   }

   ret = fixscript_copy_array(heap, new_array, 0, array, off, count);
   if (ret != FIXSCRIPT_SUCCESS) {
      return fixscript_error(heap, error, ret);
   }

   return new_array;
}


static Value builtin_array_insert(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   Value array, value;
   int off;
   int err, len;
   int64_t new_len;

   if (!fixscript_is_int(params[1])) {
      *error = fixscript_create_error_string(heap, "off must be an integer");
      return fixscript_int(0);
   }

   array = params[0];
   off = fixscript_get_int(params[1]);
   value = params[2];

   if (off < 0) {
      *error = fixscript_create_error_string(heap, "negative off");
      return fixscript_int(0);
   }

   err = fixscript_get_array_length(heap, array, &len);
   if (err) {
      return fixscript_error(heap, error, err);
   }

   new_len = ((int64_t)len) + ((int64_t)1);
   if (new_len > INT_MAX) {
      *error = fixscript_create_error_string(heap, "array out of bounds access");
      return fixscript_int(0);
   }

   err = fixscript_set_array_length(heap, array, (int)new_len);
   if (err) {
      return fixscript_error(heap, error, err);
   }

   err = fixscript_copy_array(heap, array, off + 1, array, off, len - off);
   if (err) {
      return fixscript_error(heap, error, err);
   }

   err = fixscript_set_array_elem(heap, array, off, value);
   if (err) {
      return fixscript_error(heap, error, err);
   }

   return fixscript_int(0);
}


static Value builtin_array_append(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   int64_t sum;
   int err, len1, len2, off;

   err = fixscript_get_array_length(heap, params[0], &len1);
   if (err) {
      return fixscript_error(heap, error, err);
   }

   if (num_params == 4) {
      off = fixscript_get_int(params[2]);
      len2 = fixscript_get_int(params[3]);
      if (off < 0 || len2 < 0) {
         *error = fixscript_create_error_string(heap, "negative offset or count");
         return fixscript_int(0);
      }
   }
   else {
      off = 0;
      err = fixscript_get_array_length(heap, params[1], &len2);
      if (err) {
         return fixscript_error(heap, error, err);
      }
   }

   sum = (int64_t)len1 + (int64_t)len2;
   if (sum > 0xFFFFFFFF) {
      return fixscript_error(heap, error, FIXSCRIPT_ERR_OUT_OF_MEMORY);
   }

   err = fixscript_set_array_length(heap, params[0], (int)sum);
   if (!err) {
      err = fixscript_copy_array(heap, params[0], len1, params[1], off, len2);
   }
   if (err) {
      return fixscript_error(heap, error, err);
   }
   return fixscript_int(0);
}


static Value builtin_array_replace_range(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   int err, start, end, off, len, remove_len, old_len;

   start = fixscript_get_int(params[1]);
   end = fixscript_get_int(params[2]);
   if (start < 0 || end < 0) {
      *error = fixscript_create_error_string(heap, "negative start or end");
      return fixscript_int(0);
   }
   if (start > end) {
      *error = fixscript_create_error_string(heap, "invalid range");
      return fixscript_int(0);
   }

   if (num_params == 6) {
      off = fixscript_get_int(params[4]);
      len = fixscript_get_int(params[5]);
      if (off < 0 || len < 0) {
         *error = fixscript_create_error_string(heap, "negative offset or length");
         return fixscript_int(0);
      }
   }
   else {
      off = 0;
      err = fixscript_get_array_length(heap, params[3], &len);
      if (err) {
         return fixscript_error(heap, error, err);
      }
   }

   err = fixscript_get_array_length(heap, params[0], &old_len);
   if (err) {
      return fixscript_error(heap, error, err);
   }

   remove_len = end - start;
   if (len >= remove_len) {
      err = fixscript_set_array_length(heap, params[0], old_len + (len - remove_len));
      if (!err) {
         err = fixscript_copy_array(heap, params[0], start + len, params[0], end, old_len - end);
      }
   }
   else {
      err = fixscript_copy_array(heap, params[0], start + len, params[0], end, old_len - end);
      if (!err) {
         err = fixscript_set_array_length(heap, params[0], old_len + (len - remove_len));
      }
   }

   if (!err) {
      err = fixscript_copy_array(heap, params[0], start, params[3], off, len);
   }
   if (err) {
      return fixscript_error(heap, error, err);
   }
   return fixscript_int(0);
}


static Value builtin_array_insert_array(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   Value p[6];

   if (num_params == 3) {
      p[0] = params[0];
      p[1] = params[1];
      p[2] = params[1];
      p[3] = params[2];
      return builtin_array_replace_range(heap, error, 4, p, NULL);
   }
   else {
      p[0] = params[0];
      p[1] = params[1];
      p[2] = params[1];
      p[3] = params[2];
      p[4] = params[3];
      p[5] = params[4];
      return builtin_array_replace_range(heap, error, 6, p, NULL);
   }
}


static Value builtin_array_remove(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   Value array;
   int off, count;
   int err, len;
   int64_t new_len;

   if (!fixscript_is_int(params[1])) {
      *error = fixscript_create_error_string(heap, "off must be an integer");
      return fixscript_int(0);
   }
   if (num_params == 3 && !fixscript_is_int(params[2])) {
      *error = fixscript_create_error_string(heap, "count must be an integer");
      return fixscript_int(0);
   }

   array = params[0];
   off = fixscript_get_int(params[1]);
   count = (num_params == 3? fixscript_get_int(params[2]) : 1);

   if (off < 0) {
      *error = fixscript_create_error_string(heap, "negative off");
      return fixscript_int(0);
   }
   if (count < 0) {
      *error = fixscript_create_error_string(heap, "negative count");
      return fixscript_int(0);
   }

   err = fixscript_get_array_length(heap, array, &len);
   if (err) {
      return fixscript_error(heap, error, err);
   }

   new_len = ((int64_t)len) - ((int64_t)count);
   if (new_len < 0) {
      *error = fixscript_create_error_string(heap, "array out of bounds access");
      return fixscript_int(0);
   }

   err = fixscript_copy_array(heap, array, off, array, off + count, len - off - count);
   if (err) {
      return fixscript_error(heap, error, err);
   }

   err = fixscript_set_array_length(heap, array, (int)new_len);
   if (err) {
      return fixscript_error(heap, error, err);
   }

   return fixscript_int(0);
}


static Value builtin_string_parse_single(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   Value default_val = fixscript_int(0);
   Value result;
   char *s, *end;
   long int int_val;
   int err, off, len, has_default=0, valid;

   if (num_params == 3 || num_params == 4) {
      off = fixscript_get_int(params[1]);
      len = fixscript_get_int(params[2]);
      if (len < 0) {
         *error = fixscript_create_error_string(heap, "negative length");
         return fixscript_int(0);
      }
   }
   else {
      off = 0;
      err = fixscript_get_array_length(heap, params[0], &len);
      if (err) {
         return fixscript_error(heap, error, err);
      }
   }

   if (num_params == 2 || num_params == 4) {
      has_default = 1;
      default_val = params[num_params-1];
   }

   err = fixscript_get_string(heap, params[0], off, len, &s, NULL);
   if (err) {
      if (err == FIXSCRIPT_ERR_INVALID_NULL_STRING && has_default) {
         return default_val;
      }
      return fixscript_error(heap, error, err);
   }

   if (data == (void *)0) {
      valid = 1;
      errno = 0;
      int_val = strtol(s, &end, 10);
      if (errno != 0 || *end != '\0') {
         valid = 0;
      }
      else if (sizeof(long int) > sizeof(int)) {
         if (int_val < INT_MIN || int_val > INT_MAX) {
            valid = 0;
         }
      }
      result = fixscript_int(int_val);
   }
   else {
      result = fixscript_float((float)strtod(s, &end));
      valid = (*end == '\0');
   }

   free(s);

   if (!valid || len == 0) {
      if (has_default) {
         return default_val;
      }
      *error = fixscript_create_error_string(heap, "parse error");
      return fixscript_int(0);
   }
   
   return result;
}


static Value builtin_string_parse_double(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   Value default_lo, default_hi, result;
   int has_default=0, valid=0;
   char *s, *end;
   int64_t long_val;
   union {
      double d;
      uint64_t i;
   } u;
   int err, off, len;

   default_lo = fixscript_int(0);
   default_hi = fixscript_int(0);

   if (num_params == 1) {
      off = 0;
      err = fixscript_get_array_length(heap, params[0], &len);
      if (err) {
         return fixscript_error(heap, error, err);
      }
   }
   else {
      off = fixscript_get_int(params[1]);
      len = fixscript_get_int(params[2]);
      if (len < 0) {
         *error = fixscript_create_error_string(heap, "negative length");
         return fixscript_int(0);
      }
      if (num_params == 5) {
         has_default = 1;
         default_lo = params[3];
         default_hi = params[4];
      }
   }

   err = fixscript_get_string(heap, params[0], off, len, &s, NULL);
   if (err || len == 0) {
      if (has_default) {
         *error = default_hi;
         result = default_lo;
      }
      else {
         *error = fixscript_create_error_string(heap, "parse error");
         result = fixscript_int(0);
      }
      return result;
   }

   if (data == (void *)0) {
      errno = 0;
      long_val = strtoll(s, &end, 10);
      if (errno == 0 && *end == '\0') {
         valid = 1;
         *error = fixscript_int((int)(((uint64_t)long_val) >> 32));
         result = fixscript_int((int)long_val);
      }
   }
   else {
      u.d = strtod(s, &end);
      if (*end == '\0') {
         valid = 1;
         *error = fixscript_int((int)((u.i) >> 32));
         result = fixscript_int((int)u.i);
      }
   }

   free(s);

   if (!valid) {
      if (has_default) {
         *error = default_hi;
         result = default_lo;
      }
      else {
         *error = fixscript_create_error_string(heap, "parse error");
         result = fixscript_int(0);
      }
   }

   return result;
}


static Value builtin_string_from_double(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   Value value_lo, value_hi, result;
   char buf[64], *s;
   union {
      double d;
      uint64_t i;
   } u;
   int err, len, len2;

   if (num_params == 3) {
      value_lo = params[1];
      value_hi = params[2];
   }
   else {
      value_lo = params[0];
      value_hi = params[1];
   }

   u.i = (((uint64_t)(uint32_t)value_hi.value) << 32) | (uint64_t)(uint32_t)value_lo.value;
   if (data == (void *)0) {
      #ifdef _WIN32
      snprintf(buf, sizeof(buf), "%I64d", u.i);
      #else
      snprintf(buf, sizeof(buf), "%lld", u.i);
      #endif
   }
   else {
      snprintf(buf, sizeof(buf), "%.9g", u.d);
      s = strstr(buf, "e+");
      if (s) {
         memmove(s+1, s+2, strlen(s)-1);
      }
      if (!strchr(buf, '.') && !strchr(buf, 'e')) {
         len = strlen(buf);
         if (len+2 < sizeof(buf)) {
            strcpy(buf+len, ".0");
         }
      }
   }

   if (num_params == 3) {
      result = params[0];

      err = fixscript_get_array_length(heap, result, &len);
      if (err) {
         return fixscript_error(heap, error, err);
      }

      len2 = strlen(buf);
      err = fixscript_set_array_length(heap, result, len+len2);
      if (err) {
         return fixscript_error(heap, error, err);
      }

      err = fixscript_set_array_bytes(heap, result, len, len2, buf);
      if (err) {
         return fixscript_error(heap, error, err);
      }
   }
   else {
      result = fixscript_create_string(heap, buf, -1);
      if (!result.value) {
         return fixscript_error(heap, error, FIXSCRIPT_ERR_OUT_OF_MEMORY);
      }
   }
   
   return result;
}


static Value builtin_string_from_utf8(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   Value arr, off, len, result, str, func_params[2];
   char *bytes;
   int err;

   if (num_params == 2 || num_params == 4) {
      result = *params++;
   }
   else {
      result = fixscript_int(0);
   }
   arr = *params++;
   if (num_params == 3 || num_params == 4) {
      off = *params++;
      len = *params++;

      if (off.value < 0) {
         *error = fixscript_create_error_string(heap, "negative offset");
         return fixscript_int(0);
      }
      if (len.value < 0) {
         *error = fixscript_create_error_string(heap, "negative length");
         return fixscript_int(0);
      }
   }
   else {
      off.value = 0;
      err = fixscript_get_array_length(heap, arr, &len.value);
      if (err) {
         return fixscript_error(heap, error, err);
      }
   }

   err = fixscript_lock_array(heap, arr, off.value, len.value, (void **)&bytes, 1, 1);
   if (err) {
      return fixscript_error(heap, error, err);
   }

   str = fixscript_create_string(heap, bytes, len.value);
   if (!str.value) err = FIXSCRIPT_ERR_OUT_OF_MEMORY;
   
   if (!err) {
      if (result.value) {
         func_params[0] = result;
         func_params[1] = str;
         builtin_array_append(heap, error, 2, func_params, NULL);
         reclaim_array(heap, str.value, NULL);
         if (error->value) {
            fixscript_unlock_array(heap, arr, off.value, len.value, (void **)&bytes, 1, 0);
            return fixscript_int(0);
         }
      }
      else {
         result = str;
      }
   }

   fixscript_unlock_array(heap, arr, off.value, len.value, (void **)&bytes, 1, 0);

   if (err) {
      return fixscript_error(heap, error, err);
   }
   return result;
}


static Value builtin_string_to_utf8(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   Value str, off, len, result, shared, func_params[2];
   char *bytes;
   int err, bytes_len;

   if (num_params == 2 || num_params == 4) {
      result = *params++;
   }
   else {
      result = fixscript_int(0);
   }
   str = *params++;
   if (num_params == 3 || num_params == 4) {
      off = *params++;
      len = *params++;

      if (off.value < 0) {
         *error = fixscript_create_error_string(heap, "negative offset");
         return fixscript_int(0);
      }
      if (len.value < 0) {
         *error = fixscript_create_error_string(heap, "negative length");
         return fixscript_int(0);
      }
   }
   else {
      off.value = 0;
      len.value = -1;
   }

   err = fixscript_get_string(heap, str, off.value, len.value, &bytes, &bytes_len);
   if (err) {
      return fixscript_error(heap, error, err);
   }

   if (result.value) {
      shared = fixscript_create_shared_array_from(heap, -1, bytes, bytes_len, 1, NULL, NULL);
      if (!shared.value) {
         free(bytes);
         return fixscript_error(heap, error, FIXSCRIPT_ERR_OUT_OF_MEMORY);
      }
      
      func_params[0] = result;
      func_params[1] = shared;
      builtin_array_append(heap, error, 2, func_params, NULL);
      if (error->value) {
         free(bytes);
         return fixscript_int(0);
      }
   }
   else {
      result = fixscript_create_byte_array(heap, bytes, bytes_len);
      if (!result.value) {
         free(bytes);
         return fixscript_error(heap, error, FIXSCRIPT_ERR_OUT_OF_MEMORY);
      }
   }

   free(bytes);

   if (err) {
      return fixscript_error(heap, error, err);
   }
   return result;
}


static Value builtin_weakref_create(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   Value ret;
   int err;
   
   err = fixscript_create_weak_ref(heap, params[0], num_params >= 2? &params[1] : NULL, num_params >= 3? &params[2] : NULL, &ret);
   if (err) {
      return fixscript_error(heap, error, err);
   }
   return ret;
}


static Value builtin_weakref_get(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   Value ret;
   int err;

   err = fixscript_get_weak_ref(heap, params[0], &ret);
   if (err) {
      return fixscript_error(heap, error, err);
   }
   return ret;
}


static Value builtin_hash_get(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   Value value;
   int err;

   err = fixscript_get_hash_elem(heap, params[0], params[1], &value);
   if (err == FIXSCRIPT_ERR_KEY_NOT_FOUND) {
      return params[2];
   }
   if (err) {
      return fixscript_error(heap, error, err);
   }
   return value;
}


static Value builtin_hash_entry(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   Value hash_val = params[0];
   int idx = params[1].value;
   Array *arr;
   int err;

   if (idx < 0) {
      *error = fixscript_int(0);
      return fixscript_int(0);
   }

   if (!hash_val.is_array || hash_val.value <= 0 || hash_val.value >= heap->size) {
      *error = fixscript_int(0);
      return fixscript_int(0);
   }

   arr = &heap->data[hash_val.value];
   if (arr->len == -1 || arr->hash_slots < 0 || arr->is_handle) {
      *error = fixscript_int(0);
      return fixscript_int(0);
   }

   if (idx >= arr->len) {
      *error = fixscript_int(0);
      return fixscript_int(0);
   }

   // TODO: adjust indicies on entry removal instead of rehashing during retrieving of the entry
   if (arr->hash_slots != arr->len) {
      err = expand_hash(heap, hash_val, arr);

      // provide correct results (albeit slower) in case of error (out of memory):
      if (err != FIXSCRIPT_SUCCESS) {
         Value key, value;
         int pos = 0;
         while (fixscript_iter_hash(heap, hash_val, &key, &value, &pos)) {
            if (idx-- == 0) {
               *error = value;
               return key;
            }
         }
         *error = fixscript_int(0);
         return fixscript_int(0);
      }
   }
   
   idx = bitarray_get(&arr->flags[FLAGS_SIZE((1<<arr->size)*2)], arr->size-1, idx) << 1;
   *error = (Value) { arr->data[idx+1], IS_ARRAY(arr, idx+1) != 0 };
   return (Value) { arr->data[idx+0], IS_ARRAY(arr, idx+0) != 0 };
}


static Value builtin_hash_contains(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   Value hash_val = params[0];
   Value key_val = params[1];
   int err;
   
   err = fixscript_get_hash_elem(heap, hash_val, key_val, NULL);
   if (err == FIXSCRIPT_ERR_KEY_NOT_FOUND) {
      return fixscript_int(0);
   }
   
   if (err != FIXSCRIPT_SUCCESS) {
      return fixscript_error(heap, error, err);
   }

   return fixscript_int(1);
}


static Value builtin_hash_remove(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   Value hash_val = params[0];
   Value key_val = params[1];
   Value value_val;
   int err;
   
   err = fixscript_remove_hash_elem(heap, hash_val, key_val, &value_val);
   if (err != FIXSCRIPT_SUCCESS) {
      return fixscript_error(heap, error, err);
   }

   return value_val;
}


static Value builtin_hash_get_values(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   Value hash_val = params[0];
   Value key, value;
   Value arr_val;
   int mode = (intptr_t)data;
   int err, len, pos = 0, idx = 0;

   err = fixscript_get_array_length(heap, hash_val, &len);
   if (err) {
      return fixscript_error(heap, error, err);
   }

   if (mode == 2) {
      if (len >= (1<<30)) {
         return fixscript_error(heap, error, FIXSCRIPT_ERR_OUT_OF_MEMORY);
      }
      len <<= 1;
   }
   
   arr_val = fixscript_create_array(heap, len);
   if (!arr_val.value) {
      return fixscript_error(heap, error, FIXSCRIPT_ERR_OUT_OF_MEMORY);
   }
   
   while (fixscript_iter_hash(heap, hash_val, &key, &value, &pos)) {
      if (mode == 0 || mode == 2) {
         err = fixscript_set_array_elem(heap, arr_val, idx++, key);
         if (err != FIXSCRIPT_SUCCESS) {
            return fixscript_error(heap, error, err);
         }
      }
      if (mode == 1 || mode == 2) {
         err = fixscript_set_array_elem(heap, arr_val, idx++, value);
         if (err != FIXSCRIPT_SUCCESS) {
            return fixscript_error(heap, error, err);
         }
      }
   }

   return arr_val;
}


static Value builtin_hash_clear(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   int err;

   err = fixscript_clear_hash(heap, params[0]);
   if (err != FIXSCRIPT_SUCCESS) {
      return fixscript_error(heap, error, err);
   }
   return fixscript_int(0);
}


static Value builtin_heap_collect(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   fixscript_collect_heap(heap);
   return fixscript_int(0);
}


static Value builtin_heap_size(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   long long size = fixscript_heap_size(heap);
   if (size > INT_MAX) {
      return fixscript_int(INT_MAX);
   }
   return fixscript_int((int)size);
}


static int get_time(uint64_t *time)
{
#if defined(_WIN32)
   uint64_t freq;
   QueryPerformanceFrequency((LARGE_INTEGER *)&freq);
   QueryPerformanceCounter((LARGE_INTEGER *)time);
   *time = (*time) * 1000000 / freq;
   return 1;
#elif defined(__APPLE__)
   mach_timebase_info_data_t info;
   *time = mach_absolute_time();
   mach_timebase_info(&info);
   *time = *time * info.numer / info.denom / 1000;
   return 1;
#else
   struct timespec ts;
   if (clock_gettime(CLOCK_REALTIME, &ts) == -1) {
      return 0;
   }
   *time = ((uint64_t)ts.tv_sec) * 1000000 + ((uint64_t)ts.tv_nsec / 1000L);
   return 1;
#endif
}


static Value builtin_perf_log(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   NativeFunction *log_func;
   Value value;
   uint64_t cur_time;
   char *s, *msg;
   int err;
   
   if (!get_time(&cur_time)) {
      return fixscript_int(0);
   }

   if (num_params == 0) {
      heap->perf_start_time = cur_time;
      heap->perf_last_time = cur_time;
      return fixscript_int(0);
   }

   if (heap->perf_start_time == 0) {
      heap->perf_start_time = cur_time;
      heap->perf_last_time = cur_time;
   }

   if (fixscript_is_string(heap, params[0])) {
      err = fixscript_get_string(heap, params[0], 0, -1, &s, NULL);
   }
   else {
      err = fixscript_to_string(heap, params[0], 0, &s, NULL);
   }
   if (err != FIXSCRIPT_SUCCESS) {
      return fixscript_error(heap, error, err);
   }
   
   if (heap->perf_last_time == heap->perf_start_time) {
      msg = string_format("%s [%.3f ms]", s, (cur_time - heap->perf_last_time)/1000.0);
   }
   else {
      msg = string_format("%s [%.3f ms, %.3f ms]", s, (cur_time - heap->perf_last_time)/1000.0, (cur_time - heap->perf_start_time)/1000.0);
   }
   free(s);
   if (!msg) {
      return fixscript_error(heap, error, FIXSCRIPT_ERR_OUT_OF_MEMORY);
   }

   value = fixscript_create_string(heap, msg, -1);
   if (!value.value) {
      return fixscript_error(heap, error, FIXSCRIPT_ERR_OUT_OF_MEMORY);
   }

   heap->perf_last_time = cur_time;

   log_func = string_hash_get(&heap->native_functions_hash, "log#1");
   return log_func->func(heap, error, 1, &value, log_func->data);
}


static Value builtin_serialize(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   Value buf, value;
   int err;

   if (num_params == 2) {
      buf = params[0];
      value = params[1];
   }
   else {
      buf = fixscript_int(0);
      value = params[0];
   }
   
   err = fixscript_serialize(heap, &buf, value);
   if (err != FIXSCRIPT_SUCCESS) {
      return fixscript_error(heap, error, err);
   }

   return buf;
}


static Value builtin_unserialize(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   Value buf, value, off_ref = fixscript_int(0), tmp;
   int err, off, len;

   buf = params[0];

   if (num_params == 3) {
      if (!fixscript_is_int(params[1])) {
         *error = fixscript_create_error_string(heap, "off must be an integer");
         return fixscript_int(0);
      }
      if (!fixscript_is_int(params[2])) {
         *error = fixscript_create_error_string(heap, "len must be an integer");
         return fixscript_int(0);
      }
      off = params[1].value;
      len = params[2].value;
   }
   else if (num_params == 2) {
      if (!fixscript_is_array(heap, params[1])) {
         *error = fixscript_create_error_string(heap, "off_ref must be an array");
         return fixscript_int(0);
      }
      off_ref = params[1];
      err = fixscript_get_array_elem(heap, off_ref, 0, &tmp);
      if (err != FIXSCRIPT_SUCCESS) {
         if (err == FIXSCRIPT_ERR_OUT_OF_BOUNDS) {
            *error = fixscript_create_error_string(heap, "off_ref must have at least one integer element");
            return fixscript_int(0);
         }
         return fixscript_error(heap, error, err);
      }
      if (!fixscript_is_int(tmp)) {
         *error = fixscript_create_error_string(heap, "off_ref must have at least one integer element");
         return fixscript_int(0);
      }
      off = tmp.value;
      len = -1;
   }
   else {
      off = 0;
      err = fixscript_get_array_length(heap, buf, &len);
      if (err) return fixscript_error(heap, error, err);
   }
   
   err = fixscript_unserialize(heap, buf, &off, len, &value);
   if (err != FIXSCRIPT_SUCCESS) {
      return fixscript_error(heap, error, err);
   }

   if (num_params == 2) {
      err = fixscript_set_array_elem(heap, off_ref, 0, fixscript_int(off));
      if (err != FIXSCRIPT_SUCCESS) return fixscript_error(heap, error, err);
   }

   return value;
}


static Value builtin_script_query(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   Script *script;
   Value values[64];
   Constant *constant;
   Function *func;
   Value key, value;
   char *name, *s, *s2, *error_str;
   int i, err, cnt, total_cnt, len;

   if (!heap->cur_load_func) {
      *error = fixscript_create_error_string(heap, "cannot be called outside token processing");
      return fixscript_int(0);
   }

   if (heap->cur_import_recursion >= MAX_IMPORT_RECURSION) {
      *error = fixscript_create_error_string(heap, "maximum import recursion limit reached");
      return fixscript_int(0);
   }

   err = fixscript_get_string(heap, params[0], 0, -1, &name, NULL);
   if (err) {
      return fixscript_error(heap, error, err);
   }

   script = (void *)1;
   script = heap->cur_load_func(heap, name, &error_str, heap->cur_load_data);
   free(name);
   if (!script) {
      len = strlen(error_str);
      if (len > 0 && error_str[len-1] == '\n') {
         error_str[len-1] = 0;
      }
      *error = fixscript_create_error_string(heap, error_str);
      free(error_str);
      return fixscript_int(0);
   }

   if (params[1].value) {
      value = fixscript_create_string(heap, string_hash_find_name(&heap->scripts, script), -1);
      err = fixscript_get_array_length(heap, value, &len);
      if (!err) {
         err = fixscript_set_array_length(heap, params[1], len);
      }
      if (!err) {
         err = fixscript_copy_array(heap, params[1], 0, value, 0, len);
      }
      if (err) {
         return fixscript_error(heap, error, err);
      }
   }

   if (params[2].value) {
      for (i=0; i<script->constants.size; i+=2) {
         if (script->constants.data[i+0]) {
            constant = script->constants.data[i+1];
            s = script->constants.data[i+0];
            if (constant->local) {
               s = malloc(1+strlen(s)+1);
               if (!s) {
                  return fixscript_error(heap, error, FIXSCRIPT_ERR_OUT_OF_MEMORY);
               }
               strcpy(s, "@");
               strcat(s, script->constants.data[i+0]);
            }
            key = fixscript_create_string(heap, s, -1);
            if (constant->local) {
               free(s);
            }
            if (!key.value) {
               return fixscript_error(heap, error, FIXSCRIPT_ERR_OUT_OF_MEMORY);
            }
            if (constant->ref_script && constant->ref_constant) {
               value = fixscript_create_array(heap, 3);
               values[0] = constant->value;
               values[1] = fixscript_create_string(heap, string_hash_find_name(&heap->scripts, constant->ref_script), -1);
               s = (char *)string_hash_find_name(&constant->ref_script->constants, constant->ref_constant);
               if (constant->ref_constant->local) {
                  s2 = s;
                  s = malloc(1+strlen(s)+1);
                  if (!s) {
                     return fixscript_error(heap, error, FIXSCRIPT_ERR_OUT_OF_MEMORY);
                  }
                  strcpy(s, "@");
                  strcat(s, s2);
               }
               values[2] = fixscript_create_string(heap, s, -1);
               if (constant->ref_constant->local) {
                  free(s);
               }
               if (!values[1].value || !values[2].value) {
                  return fixscript_error(heap, error, FIXSCRIPT_ERR_OUT_OF_MEMORY);
               }
               err = fixscript_set_array_range(heap, value, 0, 3, values);
               if (err) {
                  return fixscript_error(heap, error, err);
               }
            }
            else {
               value = constant->value;
            }
            err = fixscript_set_hash_elem(heap, params[2], key, value);
            if (err) {
               return fixscript_error(heap, error, err);
            }
         }
      }
   }

   if (params[3].value) {
      for (i=0; i<script->locals.size; i+=2) {
         if (script->locals.data[i+0] && (intptr_t)script->locals.data[i+1] > 0) {
            value = fixscript_create_string(heap, script->locals.data[i+0], -1);
            if (!value.value) {
               return fixscript_error(heap, error, FIXSCRIPT_ERR_OUT_OF_MEMORY);
            }
            err = fixscript_append_array_elem(heap, params[3], value);
            if (err) {
               return fixscript_error(heap, error, err);
            }
         }
      }
   }

   if (params[4].value) {
      cnt = 0;
      total_cnt = 0;
      for (i=0; ; i+=2) {
         if (i >= script->functions.size || cnt == 64) {
            err = fixscript_set_array_length(heap, params[4], total_cnt + cnt);
            if (!err) {
               err = fixscript_set_array_range(heap, params[4], total_cnt, cnt, values);
            }
            if (err) {
               return fixscript_error(heap, error, err);
            }
            total_cnt += cnt;
            cnt = 0;
            if (i >= script->functions.size) break;
         }
         
         if (script->functions.data[i+0]) {
            func = script->functions.data[i+1];
            if (!func->local) {
               values[cnt] = fixscript_create_string(heap, script->functions.data[i+0], -1);
               if (!values[cnt].value) {
                  return fixscript_error(heap, error, FIXSCRIPT_ERR_OUT_OF_MEMORY);
               }
               cnt++;
            }
         }
      }
   }

   return fixscript_int(0);
}


static int next_token(Tokenizer *tok);
static char *get_token_string(Tokenizer *tok);
static int extract_tokens(Tokenizer *tok, Heap *heap, Value tokens_val, int src_off);

static Value builtin_tokens_parse(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   Tokenizer tok;
   char *src;
   int i, err, off, len, src_off, line;

   if (!heap->cur_load_func) {
      *error = fixscript_create_error_string(heap, "cannot be called outside token processing");
      return fixscript_int(0);
   }

   if (!fixscript_is_array(heap, params[0]) || fixscript_is_string(heap, params[0])) {
      *error = fixscript_create_error_string(heap, "tokens must be an array");
      return fixscript_int(0);
   }

   if (num_params == 6) {
      off = fixscript_get_int(params[3]);
      len = fixscript_get_int(params[4]);
      line = fixscript_get_int(params[5]);
   }
   else {
      off = 0;
      err = fixscript_get_array_length(heap, params[2], &len);
      if (err) {
         return fixscript_error(heap, error, err);
      }
      line = fixscript_get_int(params[3]);
   }

   src = malloc(len+1);
   if (!src) {
      return fixscript_error(heap, error, FIXSCRIPT_ERR_OUT_OF_MEMORY);
   }

   err = fixscript_get_array_bytes(heap, params[2], off, len, src);
   if (err) {
      return fixscript_error(heap, error, err);
   }
   for (i=0; i<len; i++) {
      if (src[i] == 0) {
         free(src);
         return fixscript_error(heap, error, FIXSCRIPT_ERR_INVALID_NULL_STRING);
      }
   }
   src[len] = 0;

   err = fixscript_get_array_length(heap, params[1], &src_off);
   if (err) {
      free(src);
      return fixscript_error(heap, error, err);
   }
   err = fixscript_set_array_length(heap, params[1], src_off + len);
   if (!err) {
      err = fixscript_set_array_bytes(heap, params[1], src_off, len, src);
   }
   if (err) {
      free(src);
      return fixscript_error(heap, error, err);
   }

   memset(&tok, 0, sizeof(Tokenizer));
   tok.cur = src;
   tok.line = line;

   if (!extract_tokens(&tok, heap, params[0], src_off) || *tok.cur != '\0') {
      free(src);
      *error = fixscript_create_error_string(heap, "syntax error");
      return fixscript_int(0);
   }
   free(src);

   return fixscript_int(0);
}


static Value builtin_token_parse_string(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   Tokenizer tok;
   Value ret;
   char *src, *result;
   int i, err, off, len;

   if (!heap->cur_load_func) {
      *error = fixscript_create_error_string(heap, "cannot be called outside token processing");
      return fixscript_int(0);
   }

   if (num_params == 3) {
      off = fixscript_get_int(params[1]);
      len = fixscript_get_int(params[2]);
   }
   else {
      off = 0;
      err = fixscript_get_array_length(heap, params[0], &len);
      if (err) {
         return fixscript_error(heap, error, err);
      }
   }

   src = malloc(len+1);
   if (!src) {
      return fixscript_error(heap, error, FIXSCRIPT_ERR_OUT_OF_MEMORY);
   }

   err = fixscript_get_array_bytes(heap, params[0], off, len, src);
   if (err) {
      return fixscript_error(heap, error, err);
   }
   for (i=0; i<len; i++) {
      if (src[i] == 0) {
         free(src);
         return fixscript_error(heap, error, FIXSCRIPT_ERR_INVALID_NULL_STRING);
      }
   }
   src[len] = 0;

   memset(&tok, 0, sizeof(Tokenizer));
   tok.cur = src;

   if (!next_token(&tok) || *tok.cur != 0 || (tok.type != TOK_STRING && tok.type != TOK_CHAR)) {
      free(src);
      *error = fixscript_create_error_string(heap, "syntax error");
      return fixscript_int(0);
   }

   tok.type = TOK_STRING;
   result = get_token_string(&tok);
   free(src);

   for (i=0; i<tok.num_utf8_bytes; i++) {
      if ((unsigned char)result[i] == 0xFF) {
         result[i] = 0;
      }
   }
   ret = fixscript_create_string(heap, result, tok.num_utf8_bytes);
   free(result);
   if (!ret.value) {
      return fixscript_error(heap, error, FIXSCRIPT_ERR_OUT_OF_MEMORY);
   }

   return ret;
}


static char get_hex_char(int value)
{
   if (value >= 0 && value <= 9) return '0' + value;
   return 'a'+(value-10);
}


static Value builtin_token_escape_string(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   Value ret;
   char *str, *dest, *p;
   int i, err, len, dest_len;

   if (!heap->cur_load_func) {
      *error = fixscript_create_error_string(heap, "cannot be called outside token processing");
      return fixscript_int(0);
   }

   err = fixscript_get_string(heap, params[0], 0, -1, &str, &len);
   if (err) {
      return fixscript_error(heap, error, err);
   }

   dest_len = 2;
   for (i=0; i<len; i++) {
      switch (str[i]) {
         case '\r':
         case '\n':
         case '\t':
         case '\\':
         case '\'':
         case '\"':
            dest_len += 2;
            break;
            
         default:
            if (str[i] >= 0 && str[i] < 32) {
               dest_len += 6;
            }
            else {
               dest_len++;
            }
            break;
      }
   }

   dest = malloc(dest_len);
   p = dest;
   *p++ = '\"';
   for (i=0; i<len; i++) {
      switch (str[i]) {
         case '\r': *p++ = '\\'; *p++ = 'r'; break;
         case '\n': *p++ = '\\'; *p++ = 'n'; break;
         case '\t': *p++ = '\\'; *p++ = 't'; break;
         case '\\': *p++ = '\\'; *p++ = '\\'; break;
         case '\'': *p++ = '\\'; *p++ = '\''; break;
         case '\"': *p++ = '\\'; *p++ = '\"'; break;
            
         default:
            if (str[i] >= 0 && str[i] < 32) {
               *p++ = '\\';
               *p++ = 'u';
               *p++ = '0';
               *p++ = '0';
               *p++ = get_hex_char(str[i] >> 4);
               *p++ = get_hex_char(str[i] & 0xF);
            }
            else {
               *p++ = str[i];
            }
            break;
      }
   }
   *p++ = '\"';
   free(str);

   ret = fixscript_create_byte_array(heap, dest, dest_len);
   free(dest);
   if (!ret.value) {
      return fixscript_error(heap, error, FIXSCRIPT_ERR_OUT_OF_MEMORY);
   }

   heap->data[ret.value].is_string = 1;
   return ret;
}


Heap *fixscript_create_heap()
{
   Heap *heap;
   int i;

   heap = calloc(1, sizeof(Heap));
   heap->size = 256;
   heap->data = calloc(heap->size, sizeof(Array));
   heap->next_idx = 1;
   for (i=0; i<heap->size; i++) {
      heap->data[i].len = -1;
   }
   heap->total_size = sizeof(Heap) + heap->size*sizeof(Array);
   heap->total_cap = 16384;

   // reserve index 0 for PC to return from the interpreter:
   heap->bytecode = calloc(1, 1);
   heap->bytecode_size = 1;

   create_array(heap, ARR_INT, 8); // locals
   create_array(heap, ARR_INT, 8); // stack

   // reserve index 0 so the allocated indicies can be used as values in string hash:
   fixscript_set_array_length(heap, (Value) { LOCALS_IDX, 1 }, 1);

   // reserve index 0 so function references are not confused with false:
   dynarray_add(&heap->functions, NULL);

   fixscript_register_native_func(heap, "log#1", builtin_log, NULL);
   fixscript_register_native_func(heap, "dump#1", builtin_dump, NULL);
   fixscript_register_native_func(heap, "to_string#1", builtin_to_string, NULL);
   fixscript_register_native_func(heap, "to_string#2", builtin_to_string, NULL);
   fixscript_register_native_func(heap, "error#1", builtin_error, NULL);
   fixscript_register_native_func(heap, "clone#1", builtin_clone, (void *)0);
   fixscript_register_native_func(heap, "clone_deep#1", builtin_clone, (void *)1);
   fixscript_register_native_func(heap, "array_create#1", builtin_array_create, NULL);
   fixscript_register_native_func(heap, "array_create#2", builtin_array_create, NULL);
   fixscript_register_native_func(heap, "array_create_shared#2", builtin_array_create_shared, NULL);
   fixscript_register_native_func(heap, "array_get_shared_count#1", builtin_array_get_shared_count, NULL);
   fixscript_register_native_func(heap, "array_set_length#2", builtin_array_set_length, NULL);
   fixscript_register_native_func(heap, "array_copy#5", builtin_array_copy, NULL);
   fixscript_register_native_func(heap, "array_fill#4", builtin_array_fill, NULL);
   fixscript_register_native_func(heap, "array_extract#3", builtin_array_extract, NULL);
   fixscript_register_native_func(heap, "array_insert#3", builtin_array_insert, NULL);
   fixscript_register_native_func(heap, "array_insert_array#3", builtin_array_insert_array, NULL);
   fixscript_register_native_func(heap, "array_insert_array#5", builtin_array_insert_array, NULL);
   fixscript_register_native_func(heap, "array_append#2", builtin_array_append, NULL);
   fixscript_register_native_func(heap, "array_append#4", builtin_array_append, NULL);
   fixscript_register_native_func(heap, "array_replace_range#4", builtin_array_replace_range, NULL);
   fixscript_register_native_func(heap, "array_replace_range#6", builtin_array_replace_range, NULL);
   fixscript_register_native_func(heap, "array_remove#2", builtin_array_remove, NULL);
   fixscript_register_native_func(heap, "array_remove#3", builtin_array_remove, NULL);
   fixscript_register_native_func(heap, "string_parse_int#1", builtin_string_parse_single, (void *)0);
   fixscript_register_native_func(heap, "string_parse_int#2", builtin_string_parse_single, (void *)0);
   fixscript_register_native_func(heap, "string_parse_int#3", builtin_string_parse_single, (void *)0);
   fixscript_register_native_func(heap, "string_parse_int#4", builtin_string_parse_single, (void *)0);
   fixscript_register_native_func(heap, "string_parse_float#1", builtin_string_parse_single, (void *)1);
   fixscript_register_native_func(heap, "string_parse_float#2", builtin_string_parse_single, (void *)1);
   fixscript_register_native_func(heap, "string_parse_float#3", builtin_string_parse_single, (void *)1);
   fixscript_register_native_func(heap, "string_parse_float#4", builtin_string_parse_single, (void *)1);
   fixscript_register_native_func(heap, "string_parse_long#1", builtin_string_parse_double, (void *)0);
   fixscript_register_native_func(heap, "string_parse_long#3", builtin_string_parse_double, (void *)0);
   fixscript_register_native_func(heap, "string_parse_long#5", builtin_string_parse_double, (void *)0);
   fixscript_register_native_func(heap, "string_parse_double#1", builtin_string_parse_double, (void *)1);
   fixscript_register_native_func(heap, "string_parse_double#3", builtin_string_parse_double, (void *)1);
   fixscript_register_native_func(heap, "string_parse_double#5", builtin_string_parse_double, (void *)1);
   fixscript_register_native_func(heap, "string_from_long#2", builtin_string_from_double, (void *)0);
   fixscript_register_native_func(heap, "string_from_long#3", builtin_string_from_double, (void *)0);
   fixscript_register_native_func(heap, "string_from_double#2", builtin_string_from_double, (void *)1);
   fixscript_register_native_func(heap, "string_from_double#3", builtin_string_from_double, (void *)1);
   fixscript_register_native_func(heap, "string_from_utf8#1", builtin_string_from_utf8, NULL);
   fixscript_register_native_func(heap, "string_from_utf8#2", builtin_string_from_utf8, NULL);
   fixscript_register_native_func(heap, "string_from_utf8#3", builtin_string_from_utf8, NULL);
   fixscript_register_native_func(heap, "string_from_utf8#4", builtin_string_from_utf8, NULL);
   fixscript_register_native_func(heap, "string_to_utf8#1", builtin_string_to_utf8, NULL);
   fixscript_register_native_func(heap, "string_to_utf8#2", builtin_string_to_utf8, NULL);
   fixscript_register_native_func(heap, "string_to_utf8#3", builtin_string_to_utf8, NULL);
   fixscript_register_native_func(heap, "string_to_utf8#4", builtin_string_to_utf8, NULL);
   fixscript_register_native_func(heap, "object_create#1", builtin_array_set_length, (void *)1);
   fixscript_register_native_func(heap, "object_extend#2", builtin_array_set_length, (void *)1);
   fixscript_register_native_func(heap, "weakref_create#1", builtin_weakref_create, NULL);
   fixscript_register_native_func(heap, "weakref_create#2", builtin_weakref_create, NULL);
   fixscript_register_native_func(heap, "weakref_create#3", builtin_weakref_create, NULL);
   fixscript_register_native_func(heap, "weakref_get#1", builtin_weakref_get, NULL);
   fixscript_register_native_func(heap, "hash_get#3", builtin_hash_get, NULL);
   fixscript_register_native_func(heap, "hash_entry#2", builtin_hash_entry, NULL);
   fixscript_register_native_func(heap, "hash_contains#2", builtin_hash_contains, NULL);
   fixscript_register_native_func(heap, "hash_remove#2", builtin_hash_remove, NULL);
   fixscript_register_native_func(heap, "hash_keys#1", builtin_hash_get_values, (void *)0);
   fixscript_register_native_func(heap, "hash_values#1", builtin_hash_get_values, (void *)1);
   fixscript_register_native_func(heap, "hash_pairs#1", builtin_hash_get_values, (void *)2);
   fixscript_register_native_func(heap, "hash_clear#1", builtin_hash_clear, NULL);
   fixscript_register_native_func(heap, "heap_collect#0", builtin_heap_collect, NULL);
   fixscript_register_native_func(heap, "heap_size#0", builtin_heap_size, NULL);
   fixscript_register_native_func(heap, "perf_reset#0", builtin_perf_log, NULL);
   fixscript_register_native_func(heap, "perf_log#1", builtin_perf_log, NULL);
   fixscript_register_native_func(heap, "serialize#1", builtin_serialize, NULL);
   fixscript_register_native_func(heap, "serialize#2", builtin_serialize, NULL);
   fixscript_register_native_func(heap, "unserialize#1", builtin_unserialize, NULL);
   fixscript_register_native_func(heap, "unserialize#2", builtin_unserialize, NULL);
   fixscript_register_native_func(heap, "unserialize#3", builtin_unserialize, NULL);
   fixscript_register_native_func(heap, "script_query#5", builtin_script_query, NULL);
   fixscript_register_native_func(heap, "tokens_parse#4", builtin_tokens_parse, NULL);
   fixscript_register_native_func(heap, "tokens_parse#6", builtin_tokens_parse, NULL);
   fixscript_register_native_func(heap, "token_parse_string#1", builtin_token_parse_string, NULL);
   fixscript_register_native_func(heap, "token_parse_string#3", builtin_token_parse_string, NULL);
   fixscript_register_native_func(heap, "token_escape_string#1", builtin_token_escape_string, NULL);
   return heap;
}


static void free_function(Function *func)
{
   free(func);
}


static void free_script(Script *script)
{
   int i;

   free(script->imports.data);

   for (i=0; i<script->constants.size; i+=2) {
      if (script->constants.data[i+0]) {
         free(script->constants.data[i+0]);
         free(script->constants.data[i+1]);
      }
   }
   free(script->constants.data);

   for (i=0; i<script->locals.size; i+=2) {
      if (script->locals.data[i+0]) {
         free(script->locals.data[i+0]);
      }
   }
   free(script->locals.data);

   for (i=0; i<script->functions.size; i+=2) {
      if (script->functions.data[i+0]) {
         free(script->functions.data[i+0]);
         free_function(script->functions.data[i+1]);
      }
   }
   free(script->functions.data);

   free(script);
}


void fixscript_free_heap(Heap *heap)
{
   Array *arr;
   HandleFreeFunc handle_free;
   HandleFunc handle_func;
   SharedArrayHeader *sah;
   void *handle_ptr;
   int i, handle_type;

   while (heap->handle_created) {
      heap->handle_created = 0;

      for (i=0; i<heap->size; i++) {
         arr = &heap->data[i];
         if (arr->len == -1) continue;
         if (arr->is_handle) {
            handle_type = arr->is_handle;
            if (handle_type == 2) {
               handle_func = arr->handle_func;
               arr->handle_func = NULL;
               arr->is_handle = 1;
               arr->handle_free = NULL;
            }
            else {
               handle_free = arr->handle_free;
               arr->handle_free = NULL;
            }
            handle_ptr = arr->handle_ptr;
            arr->handle_ptr = NULL;

            if (handle_type == 2) {
               handle_func(heap, HANDLE_OP_FREE, handle_ptr, NULL);
            }
            else if (handle_free) {
               handle_free(handle_ptr);
            }
         }
         if (arr->is_shared) {
            sah = ARRAY_SHARED_HEADER(arr);
            if (__sync_sub_and_fetch(&sah->refcnt, 1) == 0) {
               if (sah->free_func) {
                  sah->free_func(sah->free_data);
               }
               free(sah);
            }
            arr->data = NULL;
            arr->flags = NULL;
            arr->size = 0;
            arr->len = 0;
         }
      }
   }

   for (i=0; i<heap->size; i++) {
      arr = &heap->data[i];
      if (arr->len != -1 && !arr->is_handle && !arr->is_shared) {
         free(arr->flags);
         free(arr->data);
      }
   }
   free(heap->data);

   free(heap->roots.data);
   free(heap->ext_roots.data);
   free(heap->bytecode);
   free(heap->lines);

   for (i=0; i<heap->scripts.size; i+=2) {
      if (heap->scripts.data[i+0]) {
         free(heap->scripts.data[i+0]);
         free_script(heap->scripts.data[i+1]);
      }
   }
   free(heap->scripts.data);

   free(heap->functions.data);

   for (i=0; i<heap->native_functions.len; i++) {
      free(heap->native_functions.data[i]);
   }
   free(heap->native_functions.data);

   for (i=0; i<heap->native_functions_hash.size; i+=2) {
      if (heap->native_functions_hash.data[i+0]) {
         free(heap->native_functions_hash.data[i+0]);
      }
   }
   free(heap->native_functions_hash.data);

   free(heap->error_stack.data);

   for (i=0; i<heap->weak_refs.size; i+=2) {
      if (heap->weak_refs.data[i+0]) {
         free(heap->weak_refs.data[i+0]);
      }
   }
   free(heap->weak_refs.data);

   for (i=0; i<heap->shared_arrays.size; i+=2) {
      if (heap->shared_arrays.data[i+0]) {
         free(heap->shared_arrays.data[i+0]);
      }
   }
   free(heap->shared_arrays.data);

   for (i=0; i<heap->user_data.len; i+=2) {
      if (heap->user_data.data[i+0] && heap->user_data.data[i+1]) {
         ((HandleFreeFunc)heap->user_data.data[i+1])(heap->user_data.data[i+0]);
      }
   }
   free(heap->user_data.data);
   
   free(heap);
}


long long fixscript_heap_size(Heap *heap)
{
#if 0
   Array *arr;
   int i, size, alloc_size, used=0, block=0;
   char buf[256];
   
   buf[0] = 0;
   size = sizeof(Heap) + heap->size * sizeof(Array) + heap->roots.size * sizeof(void *) + heap->ext_roots.size * sizeof(void *);
   for (i=0; i<heap->size; i++) {
      arr = &heap->data[i];
      if (arr->len != -1 && !arr->is_handle) {
         alloc_size = arr->size;
         if (arr->hash_slots >= 0) {
            alloc_size = 1<<arr->size;
            size += (FLAGS_SIZE(alloc_size*2) + bitarray_size(arr->size-1, alloc_size)) * sizeof(int); // flags
         }
         else {
            size += FLAGS_SIZE(arr->size) * sizeof(int); // flags
         }
         if (arr->type == ARR_BYTE) {
            size += alloc_size * sizeof(unsigned char);
         }
         else if (arr->type == ARR_SHORT) {
            size += alloc_size * sizeof(unsigned short);
         }
         else {
            size += alloc_size * sizeof(int);
         }
      }
      if (arr->len != -1) {
         used++;
         block = 1;
      }
      if ((i % 4096) == 4095) {
         strcat(buf, block? "1": "0");
         block = 0;
      }
   }

   if (size != heap->total_size) {
      fprintf(stderr, "heap size mismatch %lld != %d (delta: %lld)\n", heap->total_size, size, heap->total_size - size);
      fflush(stderr);
   }
   else {
      fprintf(stderr, "heap size = %lld (%d/%d = %lld, blocks=%s)\n", heap->total_size, used, heap->size, (int64_t)heap->size * sizeof(Array), buf);
      fflush(stderr);
   }
#endif

   return heap->total_size;
}


void fixscript_adjust_heap_size(Heap *heap, int64_t relative_change)
{
   heap->total_size += relative_change;
}


void fixscript_ref(Heap *heap, Value value)
{
   Array *arr;
   int old_size;

   if (!value.is_array || value.value <= 0 || value.value >= heap->size) return;
   arr = &heap->data[value.value];
   if (arr->len == -1) return;

   if (arr->ext_refcnt++ == 0) {
      old_size = heap->ext_roots.size;
      dynarray_add(&heap->ext_roots, (void *)(intptr_t)value.value);
      heap->total_size += (int64_t)(heap->ext_roots.size - old_size) * sizeof(void *);
   }
}


void fixscript_unref(Heap *heap, Value value)
{
   Array *arr;

   if (!value.is_array || value.value <= 0 || value.value >= heap->size) return;
   arr = &heap->data[value.value];
   if (arr->len == -1) return;

   if (--arr->ext_refcnt == 0) {
      dynarray_remove_value_fast(&heap->ext_roots, (void *)(intptr_t)value.value);
   }
}


typedef struct {
   HandleFreeFunc free_func;
   void *free_data;
} CleanupData;

static void call_cleanup(void *data)
{
   CleanupData *cd = data;

   cd->free_func(cd->free_data);
   free(cd);
}


void fixscript_register_cleanup(Heap *heap, HandleFreeFunc free_func, void *data)
{
   CleanupData *cd;

   cd = malloc(sizeof(CleanupData));
   cd->free_func = free_func;
   cd->free_data = data;
   fixscript_ref(heap, fixscript_create_shared_array_from(heap, -1, cd, 0, 1, call_cleanup, cd));
}


void fixscript_register_heap_key(volatile int *key)
{
   int new_key;
   
   if (*key == 0) {
      new_key = __sync_add_and_fetch(&heap_keys_cnt, 1);
      (void)__sync_val_compare_and_swap(key, 0, new_key);
   }
}


int fixscript_set_heap_data(Heap *heap, int key, void *data, HandleFreeFunc free_func)
{
   int err;
   void **p1, **p2;

   if (key <= 0) {
      return FIXSCRIPT_ERR_KEY_NOT_FOUND;
   }
   
   while (heap->user_data.len <= key*2+1) {
      err = dynarray_add(&heap->user_data, NULL);
      if (err) {
         if (data && free_func) {
            free_func(data);
         }
         return err;
      }
   }

   p1 = &heap->user_data.data[key*2+0];
   p2 = &heap->user_data.data[key*2+1];

   if ((*p1) && (*p2)) {
      ((HandleFreeFunc)(*p2))(*p1);
   }

   *p1 = data;
   *p2 = free_func;
   return FIXSCRIPT_SUCCESS;
}


void *fixscript_get_heap_data(Heap *heap, int key)
{
   if (key*2+0 < heap->user_data.len) {
      return heap->user_data.data[key*2+0];
   }
   return NULL;
}


void fixscript_mark_ref(Heap *heap, Value value)
{
   Array *arr;

   if (value.is_array && value.value > 0 && value.value < heap->size) {
      arr = &heap->data[value.value];
      if (arr->len != -1) {
         mark_array(heap, arr);
      }
   }
}


static int clone_value(Heap *dest, Heap *src, Value value, Value map, Value *clone, int recursion_limit, LoadScriptFunc load_func, void *load_data, char **error_msg);

Value fixscript_copy_ref(void *ctx, Value value)
{
   CopyContext *cc = ctx;
   Value clone;

   if (cc->err) {
      return fixscript_int(0);
   }
   cc->err = clone_value(cc->dest, cc->src, value, cc->map, &clone, cc->recursion_limit, cc->load_func, cc->load_data, cc->error_msg);
   return clone;
}


static int indent(String *str, int level)
{
   int i;
   
   for (i=0; i<level; i++) {
      if (!string_append(str, "  ")) return 0;
   }
   return 1;
}


static int dump_value(Heap *heap, String *str, DynArray *stack, Value value, int newlines, int level)
{
   Value elem_val, key_val;
   Function *func;
   const char *script_name, *func_name;
   int i, len, err, ok, hash_pos, type, func_id;
   char *s;
   char buf[32];
   
   if (level >= MAX_DUMP_RECURSION) {
      if (!string_append(str, "(recursion limit reached)")) return FIXSCRIPT_ERR_OUT_OF_MEMORY;
      return FIXSCRIPT_SUCCESS;
   }
   
   if (value.is_array) {
      if (fixscript_is_float(value)) {
         snprintf(buf, sizeof(buf), "%.9g", fixscript_get_float(value));
         s = strstr(buf, "e+");
         if (s) {
            memmove(s+1, s+2, strlen(s)-1);
         }
         if (!string_append(str, "%s", buf)) return FIXSCRIPT_ERR_OUT_OF_MEMORY;
         if (!strchr(buf, '.') && !strchr(buf, 'e')) {
            if (!string_append(str, ".0")) return FIXSCRIPT_ERR_OUT_OF_MEMORY;
         }
      }
      else if (fixscript_is_string(heap, value)) {
         // TODO: escape characters
         err = fixscript_get_string(heap, value, 0, -1, &s, NULL);
         if (err != FIXSCRIPT_SUCCESS) return err;
         ok = string_append(str, "\"%s\"", s);
         free(s);
         if (!ok) return FIXSCRIPT_ERR_OUT_OF_MEMORY;
      }
      else if (fixscript_is_hash(heap, value)) {
         for (i=0; i<stack->len; i++) {
            if (value.value == (intptr_t)stack->data[i]) {
               if (!string_append(str, "(hash reference -%d)", stack->len-i)) return FIXSCRIPT_ERR_OUT_OF_MEMORY;
               return FIXSCRIPT_SUCCESS;
            }
         }

         err = fixscript_get_array_length(heap, value, &len);
         if (err) return err;
         if (len == 0) {
            if (!string_append(str, "{}")) return FIXSCRIPT_ERR_OUT_OF_MEMORY;
            return FIXSCRIPT_SUCCESS;
         }

         if (!string_append(str, newlines? "{\n" : "{ ")) return FIXSCRIPT_ERR_OUT_OF_MEMORY;

         err = dynarray_add(stack, (void *)(intptr_t)value.value);
         if (err != FIXSCRIPT_SUCCESS) return err;

         hash_pos = 0;
         i = 0;

         while (fixscript_iter_hash(heap, value, &key_val, &elem_val, &hash_pos)) {
            if (i++) {
               if (!string_append(str, newlines? ",\n" : ", ")) return FIXSCRIPT_ERR_OUT_OF_MEMORY;
            }
            if (newlines) {
               if (!indent(str, level+1)) return FIXSCRIPT_ERR_OUT_OF_MEMORY;
            }

            err = dump_value(heap, str, stack, key_val, newlines, level+1);
            if (err != FIXSCRIPT_SUCCESS) return err;

            if (!string_append(str, ": ")) return FIXSCRIPT_ERR_OUT_OF_MEMORY;

            err = dump_value(heap, str, stack, elem_val, newlines, level+1);
            if (err != FIXSCRIPT_SUCCESS) return err;
         }

         stack->len--;

         if (newlines) {
            if (!string_append(str, "\n")) return FIXSCRIPT_ERR_OUT_OF_MEMORY;
         }
         if (newlines) {
            if (!indent(str, level)) return FIXSCRIPT_ERR_OUT_OF_MEMORY;
         }
         if (!string_append(str, newlines? "}" : " }")) return FIXSCRIPT_ERR_OUT_OF_MEMORY;
      }
      else if (fixscript_is_array(heap, value)) {
         for (i=0; i<stack->len; i++) {
            if (value.value == (intptr_t)stack->data[i]) {
               if (!string_append(str, "(array reference -%d)", stack->len-i)) return FIXSCRIPT_ERR_OUT_OF_MEMORY;
               return FIXSCRIPT_SUCCESS;
            }
         }

         err = fixscript_get_array_length(heap, value, &len);
         if (err) return err;
         if (len == 0) {
            if (!string_append(str, "[]")) return FIXSCRIPT_ERR_OUT_OF_MEMORY;
            return FIXSCRIPT_SUCCESS;
         }
         
         if (!string_append(str, newlines? "[\n" : "[")) return FIXSCRIPT_ERR_OUT_OF_MEMORY;

         err = dynarray_add(stack, (void *)(intptr_t)value.value);
         if (err != FIXSCRIPT_SUCCESS) return err;

         for (i=0; i<len; i++) {
            err = fixscript_get_array_elem(heap, value, i, &elem_val);
            if (err != FIXSCRIPT_SUCCESS) return err;

            if (newlines) {
               if (!indent(str, level+1)) return FIXSCRIPT_ERR_OUT_OF_MEMORY;
            }

            err = dump_value(heap, str, stack, elem_val, newlines, level+1);
            if (err != FIXSCRIPT_SUCCESS) return err;

            if (newlines) {
               if (!string_append(str, (i < len-1)? ",\n" : "\n")) return FIXSCRIPT_ERR_OUT_OF_MEMORY;
            }
            else if (i < len-1) {
               if (!string_append(str, ", ")) return FIXSCRIPT_ERR_OUT_OF_MEMORY;
            }
         }

         stack->len--;

         if (newlines) {
            if (!indent(str, level)) return FIXSCRIPT_ERR_OUT_OF_MEMORY;
         }
         if (!string_append(str, "]")) return FIXSCRIPT_ERR_OUT_OF_MEMORY;
      }
      else {
         fixscript_get_handle(heap, value, -1, &type);
         if (type >= 0) {
            Array *arr = &heap->data[value.value];
            s = NULL;
            if (arr->is_handle == 2) {
               s = arr->handle_func(heap, HANDLE_OP_TO_STRING, arr->handle_ptr, NULL);
            }
            if (s) {
               err = !string_append(str, "%s", s);
               free(s);
               if (err) {
                  return FIXSCRIPT_ERR_OUT_OF_MEMORY;
               }
            }
            else {
               if (!string_append(str, "(native handle #%d)", value.value)) return FIXSCRIPT_ERR_OUT_OF_MEMORY;
            }
         }
         else {
            func_id = value.value - FUNC_REF_OFFSET;
            if (func_id > 0 && func_id < heap->functions.len) {
               func = heap->functions.data[func_id];
               script_name = string_hash_find_name(&heap->scripts, func->script);
               func_name = string_hash_find_name(&func->script->functions, func);
               if (script_name && func_name) {
                  if (!string_append(str, "<%s:%s>", script_name, func_name)) return FIXSCRIPT_ERR_OUT_OF_MEMORY;
               }
               else {
                  if (!string_append(str, "(invalid function reference)")) return FIXSCRIPT_ERR_OUT_OF_MEMORY;
               }
            }
            else {
               if (!string_append(str, "(invalid)")) return FIXSCRIPT_ERR_OUT_OF_MEMORY;
            }
         }
      }
   }
   else {
      if (!string_append(str, "%d", value.value)) return FIXSCRIPT_ERR_OUT_OF_MEMORY;
   }
   return FIXSCRIPT_SUCCESS;
}


int fixscript_dump_value(Heap *heap, Value value, int newlines)
{
   char *s;
   int err;
   
   err = fixscript_to_string(heap, value, newlines, &s, NULL);
   if (err != FIXSCRIPT_SUCCESS) {
      fprintf(stderr, "error while dumping value (%s)\n", fixscript_get_error_msg(err));
      fflush(stderr);
      return err;
   }
   fprintf(stderr, "%s", s);
   fflush(stderr);
   free(s);
   return FIXSCRIPT_SUCCESS;
}


int fixscript_to_string(Heap *heap, Value value, int newlines, char **str_out, int *len_out)
{
   String str;
   DynArray stack;
   int err;

   memset(&str, 0, sizeof(String));
   memset(&stack, 0, sizeof(DynArray));

   err = dump_value(heap, &str, &stack, value, newlines, 0);
   if (err != FIXSCRIPT_SUCCESS) goto error;

   if (newlines) {
      if (!string_append(&str, "\n")) {
         err = FIXSCRIPT_ERR_OUT_OF_MEMORY;
         goto error;
      }
   }

   *str_out = str.data;
   if (len_out) *len_out = str.len;
   free(stack.data);
   return FIXSCRIPT_SUCCESS;

error:
   free(str.data);
   free(stack.data);
   *str_out = NULL;
   if (len_out) *len_out = 0;
   return err;
}


static inline int byte_array_append(Heap *heap, Array *buf, int *off, int count)
{
   int64_t new_len;
   int err;

   new_len = ((int64_t)*off) + (int64_t)count;
   if (count < 0) {
      return FIXSCRIPT_ERR_OUT_OF_MEMORY;
   }
   if (new_len > INT_MAX) {
      return FIXSCRIPT_ERR_OUT_OF_MEMORY;
   }
   if (new_len > buf->size) {
      err = expand_array(heap, buf, ((int)new_len)-1);
      if (err != FIXSCRIPT_SUCCESS) return err;
   }
   flags_clear_range(buf, *off, count);
   buf->len = (int)new_len;
   return FIXSCRIPT_SUCCESS;
}


static int clone_value(Heap *dest, Heap *src, Value value, Value map, Value *clone, int recursion_limit, LoadScriptFunc load_func, void *load_data, char **error_msg)
{
   SharedArrayHeader *sah;
   WeakRefHandle *wrh;
   int buf_size = 1024;
   Value *values;
   Value arr_val, hash_val, entry_key, entry_value, ref_value, handle_val;
   Array *arr, *new_arr;
   Function *func;
   FuncRefHandle *frh;
   Script *script;
   const char *script_name, *func_name;
   void *new_ptr;
   char *s, *p;
   char buf[64];
   int i, err, len, num, off, count, pos, type, func_id;

   if (recursion_limit <= 0) {
      return FIXSCRIPT_ERR_RECURSION_LIMIT;
   }

   if (fixscript_is_int(value) || fixscript_is_float(value)) {
      *clone = value;
      return FIXSCRIPT_SUCCESS;
   }

   if (map.value) {
      err = fixscript_get_hash_elem(dest, map, fixscript_int(value.value), &ref_value);
      if (err == FIXSCRIPT_SUCCESS) {
         *clone = ref_value;
         return FIXSCRIPT_SUCCESS;
      }
      if (err != FIXSCRIPT_ERR_KEY_NOT_FOUND) {
         return err;
      }
   }

   if (fixscript_is_array(src, value)) {
      err = fixscript_get_array_length(src, value, &len);
      if (err) return err;

      arr = &src->data[value.value];
      if (arr->is_shared) {
         if (dest != src) {
            sah = ARRAY_SHARED_HEADER(arr);

            snprintf(buf, sizeof(buf), "%p,%d,%d,%p", arr->data, arr->len, arr->type == ARR_BYTE? 1 : arr->type == ARR_SHORT? 2 : 4, sah->free_data);
            arr_val.value = (intptr_t)string_hash_get(&dest->shared_arrays, buf);
            if (arr_val.value) {
               arr_val.is_array = 1;
               *clone = arr_val;
               return FIXSCRIPT_SUCCESS;
            }

            arr_val = create_array(dest, arr->type, 0);
            if (!arr_val.value) {
               return FIXSCRIPT_ERR_OUT_OF_MEMORY;
            }

            new_arr = &dest->data[arr_val.value];
            new_arr->len = arr->len;
            new_arr->size = arr->size;
            new_arr->data = arr->data;
            new_arr->flags = arr->flags;
            new_arr->is_shared = 1;
            __sync_add_and_fetch(&sah->refcnt, 1);
   
            string_hash_set(&dest->shared_arrays, strdup(buf), (void *)(intptr_t)arr_val.value);

            *clone = arr_val;
            return FIXSCRIPT_SUCCESS;
         }
         *clone = value;
         return FIXSCRIPT_SUCCESS;
      }

      if (fixscript_is_string(src, value)) {
         arr_val = fixscript_create_string(dest, NULL, 0);
      }
      else {
         arr_val = fixscript_create_array(dest, 0);
      }
      if (!arr_val.value) return FIXSCRIPT_ERR_OUT_OF_MEMORY;

      err = fixscript_set_array_length(dest, arr_val, len);
      if (err) return err;

      if (map.value) {
         err = fixscript_set_hash_elem(dest, map, fixscript_int(value.value), arr_val);
         if (err != FIXSCRIPT_SUCCESS) return err;
      }

      off = 0;
      count = len;

      values = malloc_array(MIN(count, buf_size), sizeof(Value));
      if (!values) {
         return FIXSCRIPT_ERR_OUT_OF_MEMORY;
      }

      err = FIXSCRIPT_SUCCESS;

      while (count > 0) {
         num = MIN(count, buf_size);

         err = fixscript_get_array_range(src, value, off, num, values);
         if (err) break;

         if (map.value) {
            for (i=0; i<num; i++) {
               err = clone_value(dest, src, values[i], map, &values[i], recursion_limit-1, load_func, load_data, error_msg);
               if (err) break;
            }
            if (err) break;
         }

         err = fixscript_set_array_range(dest, arr_val, off, num, values);
         if (err) break;

         off += num;
         count -= num;
      }

      free(values);
      if (err) return err;

      *clone = arr_val;
      return FIXSCRIPT_SUCCESS;
   }

   if (fixscript_is_hash(src, value)) {
      hash_val = fixscript_create_hash(dest);
      if (!hash_val.value) return FIXSCRIPT_ERR_OUT_OF_MEMORY;

      if (map.value) {
         err = fixscript_set_hash_elem(dest, map, fixscript_int(value.value), hash_val);
         if (err != FIXSCRIPT_SUCCESS) return err;
      }

      pos = 0;
      while (fixscript_iter_hash(src, value, &entry_key, &entry_value, &pos)) {
         if (map.value) {
            err = clone_value(dest, src, entry_key, map, &entry_key, recursion_limit-1, load_func, load_data, error_msg);
            if (err) return err;

            err = clone_value(dest, src, entry_value, map, &entry_value, recursion_limit-1, load_func, load_data, error_msg);
            if (err) return err;
         }

         err = fixscript_set_hash_elem(dest, hash_val, entry_key, entry_value);
         if (err) return err;
      }

      *clone = hash_val;
      return FIXSCRIPT_SUCCESS;
   }
   
   fixscript_get_handle(src, value, -1, &type);
   if (type >= 0) {
      arr = &src->data[value.value];
      
      if (type == FUNC_REF_HANDLE_TYPE && load_func) {
         frh = arr->handle_ptr;
         script = fixscript_get(dest, frh->script_name);
         if (!script) {
            s = strdup(frh->script_name);
            if (!s) return FIXSCRIPT_ERR_OUT_OF_MEMORY;
            p = strrchr(s, '.');
            if (p) *p = 0;
            script = load_func(dest, s, error_msg, load_data);
            free(s);
         }
         if (!script) {
            if (error_msg) {
               len = strlen(*error_msg);
               if (len > 0 && (*error_msg)[len-1] == '\n') {
                  (*error_msg)[len-1] = 0;
               }
            }
            return FIXSCRIPT_ERR_FUNC_REF_LOAD_ERROR;
         }
         *clone = fixscript_get_function(dest, script, frh->func_name);
         if (!clone->value) {
            return FIXSCRIPT_ERR_UNSERIALIZABLE_REF;
         }
         if (map.value) {
            err = fixscript_set_hash_elem(dest, map, fixscript_int(value.value), *clone);
            if (err != FIXSCRIPT_SUCCESS) return err;
         }
         return FIXSCRIPT_SUCCESS;
      }
      else if (type == WEAK_REF_HANDLE_TYPE) {
         wrh = arr->handle_ptr;
         if (wrh->target) {
            entry_value = (Value) { wrh->target, 1 };
            hash_val = (Value) { wrh->container, 1 };
            entry_key = wrh->key;

            if (map.value) {
               err = clone_value(dest, src, entry_value, map, &entry_value, recursion_limit-1, load_func, load_data, error_msg);
               if (err) return err;

               if (hash_val.value) {
                  err = clone_value(dest, src, hash_val, map, &hash_val, recursion_limit-1, load_func, load_data, error_msg);
                  if (err) return err;
               }

               if (entry_key.is_array != 2) {
                  err = clone_value(dest, src, entry_key, map, &entry_key, recursion_limit-1, load_func, load_data, error_msg);
                  if (err) return err;
               }
            }

            err = fixscript_create_weak_ref(dest, entry_value, hash_val.value? &hash_val : NULL, entry_key.is_array != 2? &entry_key : NULL, &handle_val);
            if (err) return err;
         }
         else {
            wrh = calloc(1, sizeof(WeakRefHandle));
            wrh->id = dest->weak_id_cnt++;
      
            handle_val = fixscript_create_value_handle(dest, WEAK_REF_HANDLE_TYPE, wrh, weak_ref_handle_func);
            if (!handle_val.value) {
               return FIXSCRIPT_ERR_OUT_OF_MEMORY;
            }

            wrh->value = handle_val.value;
            wrh->key.is_array = 2;
         }

         if (map.value) {
            err = fixscript_set_hash_elem(dest, map, fixscript_int(value.value), handle_val);
            if (err) return err;
         }

         *clone = handle_val;
         return FIXSCRIPT_SUCCESS;
      }
      
      if (arr->is_handle == 2) {
         new_ptr = arr->handle_func(src, HANDLE_OP_COPY, arr->handle_ptr, dest);
         if (!new_ptr) {
            return FIXSCRIPT_ERR_UNSERIALIZABLE_REF;
         }
         handle_val = fixscript_create_value_handle(dest, type, new_ptr, arr->handle_func);
         if (!handle_val.value) {
            return FIXSCRIPT_ERR_OUT_OF_MEMORY;
         }

         if (map.value) {
            err = fixscript_set_hash_elem(dest, map, fixscript_int(value.value), handle_val);
            if (err != FIXSCRIPT_SUCCESS) return err;
         }

         if (map.value) {
            CopyContext cc;
            cc.dest = dest;
            cc.src = src;
            cc.map = map;
            cc.recursion_limit = recursion_limit-1;
            cc.err = 0;
            cc.load_func = load_func;
            cc.load_data = load_data;
            cc.error_msg = error_msg;
            arr->handle_func(dest, HANDLE_OP_COPY_REFS, new_ptr, &cc);
            if (cc.err) {
               return cc.err;
            }
         }
      
         *clone = handle_val;
         return FIXSCRIPT_SUCCESS;
      }
      
      return FIXSCRIPT_ERR_UNSERIALIZABLE_REF;
   }

   func_id = value.value - FUNC_REF_OFFSET;
   if (func_id > 0 && func_id < src->functions.len) {
      if (dest != src) {
         func = src->functions.data[func_id];
         script_name = string_hash_find_name(&src->scripts, func->script);
         func_name = string_hash_find_name(&func->script->functions, func);
   
         if (!script_name || !func_name) {
            return FIXSCRIPT_ERR_UNSERIALIZABLE_REF;
         }

         if (load_func) {
            script = fixscript_get(dest, script_name);
            if (!script) {
               s = strdup(script_name);
               if (!s) return FIXSCRIPT_ERR_OUT_OF_MEMORY;
               p = strrchr(s, '.');
               if (p) *p = 0;
               script = load_func(dest, s, error_msg, load_data);
               free(s);
            }
            if (!script) {
               if (error_msg) {
                  len = strlen(*error_msg);
                  if (len > 0 && (*error_msg)[len-1] == '\n') {
                     (*error_msg)[len-1] = 0;
                  }
               }
               return FIXSCRIPT_ERR_FUNC_REF_LOAD_ERROR;
            }
            *clone = fixscript_get_function(dest, script, func_name);
            if (!clone->value) {
               return FIXSCRIPT_ERR_UNSERIALIZABLE_REF;
            }
         }
         else {
            frh = calloc(1, sizeof(FuncRefHandle));
            if (!frh) {
               return FIXSCRIPT_ERR_OUT_OF_MEMORY;
            }
            frh->script_name = strdup(script_name);
            frh->func_name = strdup(func_name);
            if (!frh->script_name || !frh->func_name) {
               func_ref_handle_func(dest, HANDLE_OP_FREE, frh, NULL);
               return FIXSCRIPT_ERR_OUT_OF_MEMORY;
            }
            *clone = fixscript_create_value_handle(dest, FUNC_REF_HANDLE_TYPE, frh, func_ref_handle_func);
            if (!clone->value) {
               return FIXSCRIPT_ERR_OUT_OF_MEMORY;
            }
         }
         
         if (map.value) {
            err = fixscript_set_hash_elem(dest, map, fixscript_int(value.value), *clone);
            if (err != FIXSCRIPT_SUCCESS) return err;
         }
         return FIXSCRIPT_SUCCESS;
      }
      else {
         *clone = value;
         return FIXSCRIPT_SUCCESS;
      }
   }

   return FIXSCRIPT_ERR_UNSERIALIZABLE_REF;
}


int fixscript_clone(Heap *heap, Value value, int deep, Value *clone)
{
   if (deep) {
      return fixscript_clone_between(heap, heap, value, clone, NULL, NULL, NULL);
   }
   return clone_value(heap, heap, value, fixscript_int(0), clone, MAX_CLONE_RECURSION, NULL, NULL, NULL);
}


int fixscript_clone_between(Heap *dest, Heap *src, Value value, Value *clone, LoadScriptFunc load_func, void *load_data, char **error_msg)
{
   Value map;
   int ret;

   if (error_msg) {
      *error_msg = NULL;
   }
   map = fixscript_create_hash(dest);
   if (!map.value) {
      return FIXSCRIPT_ERR_OUT_OF_MEMORY;
   }
   fixscript_ref(dest, map);
   ret = clone_value(dest, src, value, map, clone, MAX_CLONE_RECURSION, load_func, load_data, error_msg);
   fixscript_unref(dest, map);
   reclaim_array(dest, map.value, NULL);
   return ret;
}


static inline void serialize_byte(Array *buf, int *off, uint8_t value)
{
   buf->byte_data[(*off)++] = value;
}


static inline void serialize_short(Array *buf, int *off, uint16_t value)
{
   buf->byte_data[(*off)++] = (value) & 0xFF;
   buf->byte_data[(*off)++] = (value >> 8) & 0xFF;
}


static inline void serialize_int(Array *buf, int *off, uint32_t value)
{
   buf->byte_data[(*off)++] = (value) & 0xFF;
   buf->byte_data[(*off)++] = (value >> 8) & 0xFF;
   buf->byte_data[(*off)++] = (value >> 16) & 0xFF;
   buf->byte_data[(*off)++] = (value >> 24);
}


static int serialize_value(Heap *heap, Array *buf, int *off, Value map, Value value, int recursion_limit)
{
   Value hash_key, hash_value, ref_value, arr_value;
   Array *arr;
   int is_float, val;
   int i, len, err, little_endian_test, max_val;
   int64_t sum;

   if (recursion_limit <= 0) {
      return FIXSCRIPT_ERR_RECURSION_LIMIT;
   }

   is_float = 0;
   if (fixscript_is_int(value) || (is_float = fixscript_is_float(value))) {
      err = byte_array_append(heap, buf, off, 5);
      if (err != FIXSCRIPT_SUCCESS) return err;

      serialize_byte(buf, off, is_float? SER_FLOAT : SER_INT);
      val = value.value;
      if (is_float) {
         // normalize NaNs:
         if (((val >> 23) & 0xFF) == 0xFF && (val & ((1<<23)-1))) {
            val = (val & ~((1<<23)-1)) | (1 << 22);
         }
      }
      serialize_int(buf, off, val);
      return FIXSCRIPT_SUCCESS;
   }

   err = fixscript_get_hash_elem(heap, map, fixscript_int(value.value), &ref_value);
   if (err == FIXSCRIPT_SUCCESS) {
      err = byte_array_append(heap, buf, off, 5);
      if (err != FIXSCRIPT_SUCCESS) return err;

      serialize_byte(buf, off, SER_REF);
      serialize_int(buf, off, ref_value.value);
      return FIXSCRIPT_SUCCESS;
   }
   if (err != FIXSCRIPT_ERR_KEY_NOT_FOUND) {
      return err;
   }

   err = fixscript_get_array_length(heap, map, &len);
   if (err) return err;

   err = fixscript_set_hash_elem(heap, map, fixscript_int(value.value), fixscript_int(len));
   if (err != FIXSCRIPT_SUCCESS) return err;

   if (fixscript_is_hash(heap, value)) {
      err = fixscript_get_array_length(heap, value, &len);
      if (err) return err;
      
      err = byte_array_append(heap, buf, off, 5);
      if (err != FIXSCRIPT_SUCCESS) return err;

      serialize_byte(buf, off, SER_HASH);
      serialize_int(buf, off, len);
      
      i = 0;
      while (fixscript_iter_hash(heap, value, &hash_key, &hash_value, &i)) {
         err = serialize_value(heap, buf, off, map, hash_key, recursion_limit-1);
         if (err) return err;

         err = serialize_value(heap, buf, off, map, hash_value, recursion_limit-1);
         if (err) return err;
      }
      return FIXSCRIPT_SUCCESS;
   }

   if (fixscript_is_array(heap, value)) {
      err = fixscript_get_array_length(heap, value, &len);
      if (err) return err;

      arr = &heap->data[value.value];
      
      err = byte_array_append(heap, buf, off, 5);
      if (err != FIXSCRIPT_SUCCESS) return err;

      if (arr->type == ARR_BYTE && flags_is_array_clear_in_range(arr, 0, len)) {
         serialize_byte(buf, off, fixscript_is_string(heap, value)? SER_STRING_BYTE : SER_ARRAY_BYTE);
         serialize_int(buf, off, len);
      
         err = byte_array_append(heap, buf, off, len);
         if (err != FIXSCRIPT_SUCCESS) return err;

         memcpy(buf->byte_data + *off, arr->byte_data, len);
         (*off) += len;
         return FIXSCRIPT_SUCCESS;
      }

      if (arr->type == ARR_SHORT && flags_is_array_clear_in_range(arr, 0, len)) {
         max_val = 0;
         for (i=0; i<len; i++) {
            max_val |= arr->short_data[i];
         }
         
         if (max_val & ~0xFF) {
            serialize_byte(buf, off, fixscript_is_string(heap, value)? SER_STRING_SHORT : SER_ARRAY_SHORT);
            sum = ((int64_t)len) << 1;
         }
         else {
            serialize_byte(buf, off, fixscript_is_string(heap, value)? SER_STRING_BYTE : SER_ARRAY_BYTE);
            sum = len;
         }
         serialize_int(buf, off, len);
      
         if (sum > INT_MAX) return FIXSCRIPT_ERR_OUT_OF_MEMORY;

         err = byte_array_append(heap, buf, off, (int)sum);
         if (err != FIXSCRIPT_SUCCESS) return err;

         if (max_val & ~0xFF) {
            little_endian_test = 1;
            if (*(char *)&little_endian_test == 1) {
               memcpy(buf->byte_data + *off, arr->short_data, (int)sum);
               (*off) += (int)sum;
            }
            else {
               for (i=0; i<len; i++) {
                  serialize_short(buf, off, arr->short_data[i]);
               }
            }
         }
         else {
            for (i=0; i<len; i++) {
               serialize_byte(buf, off, (uint8_t)arr->short_data[i]);
            }
         }
         return FIXSCRIPT_SUCCESS;
      }

      if (flags_is_array_clear_in_range(arr, 0, len)) {
         max_val = 0;
         for (i=0; i<len; i++) {
            max_val |= arr->data[i];
         }

         if (max_val & ~0xFFFF) {
            serialize_byte(buf, off, fixscript_is_string(heap, value)? SER_STRING_INT : SER_ARRAY_INT);
            sum = ((int64_t)len) << 2;
         }
         else if (max_val & ~0xFF) {
            serialize_byte(buf, off, fixscript_is_string(heap, value)? SER_STRING_SHORT : SER_ARRAY_SHORT);
            sum = ((int64_t)len) << 1;
         }
         else {
            serialize_byte(buf, off, fixscript_is_string(heap, value)? SER_STRING_BYTE : SER_ARRAY_BYTE);
            sum = len;
         }
         serialize_int(buf, off, len);
      
         if (sum > INT_MAX) return FIXSCRIPT_ERR_OUT_OF_MEMORY;

         err = byte_array_append(heap, buf, off, (int)sum);
         if (err != FIXSCRIPT_SUCCESS) return err;

         if (max_val & ~0xFFFF) {
            little_endian_test = 1;
            if (*(char *)&little_endian_test == 1) {
               memcpy(buf->byte_data + *off, arr->data, (int)sum);
               (*off) += (int)sum;
            }
            else {
               for (i=0; i<len; i++) {
                  serialize_int(buf, off, arr->data[i]);
               }
            }
         }
         else if (max_val & ~0xFF) {
            for (i=0; i<len; i++) {
               serialize_short(buf, off, arr->data[i]);
            }
         }
         else {
            for (i=0; i<len; i++) {
               serialize_byte(buf, off, arr->data[i]);
            }
         }
         return FIXSCRIPT_SUCCESS;
      }

      if (fixscript_is_string(heap, value)) {
         return FIXSCRIPT_ERR_UNSERIALIZABLE_REF;
      }

      serialize_byte(buf, off, SER_ARRAY);
      serialize_int(buf, off, len);

      for (i=0; i<len; i++) {
         arr_value = (Value) { get_array_value(arr, i), IS_ARRAY(arr, i) != 0 };

         is_float = 0;
         if (fixscript_is_int(arr_value) || (is_float = fixscript_is_float(arr_value))) {
            err = byte_array_append(heap, buf, off, 5);
            if (err != FIXSCRIPT_SUCCESS) return err;

            serialize_byte(buf, off, is_float? SER_FLOAT : SER_INT);
            val = arr_value.value;
            // normalize NaNs:
            if (((val >> 23) & 0xFF) == 0xFF && (val & ((1<<23)-1))) {
               val = (val & ~((1<<23)-1)) | (1 << 22);
            }
            serialize_int(buf, off, val);
         }
         else {
            err = serialize_value(heap, buf, off, map, arr_value, recursion_limit-1);
            if (err) return err;
         }
      }
      return FIXSCRIPT_SUCCESS;
   }

   return FIXSCRIPT_ERR_UNSERIALIZABLE_REF;
}


int fixscript_serialize(Heap *heap, Value *buf_val, Value value)
{
   Array *buf;
   Value map;
   int off, orig_len, err;
   
   if (!buf_val->value) {
      *buf_val = fixscript_create_array(heap, 0);
      if (!buf_val->value) {
         return FIXSCRIPT_ERR_OUT_OF_MEMORY;
      }
   }

   map = fixscript_create_hash(heap);
   if (!map.value) {
      return FIXSCRIPT_ERR_OUT_OF_MEMORY;
   }

   if (!buf_val->is_array || buf_val->value <= 0 || buf_val->value >= heap->size) {
      return FIXSCRIPT_ERR_INVALID_ACCESS;
   }

   buf = &heap->data[buf_val->value];
   if (buf->len == -1 || buf->hash_slots >= 0) {
      return FIXSCRIPT_ERR_INVALID_ACCESS;
   }
   if (buf->type != ARR_BYTE) {
      return FIXSCRIPT_ERR_INVALID_BYTE_ARRAY;
   }

   off = buf->len;
   orig_len = buf->len;
   err = serialize_value(heap, buf, &off, map, value, MAX_SERIALIZE_RECURSION);
   if (err != FIXSCRIPT_SUCCESS) {
      buf->len = orig_len;
   }
   reclaim_array(heap, map.value, NULL);
   return err;
}


static inline int unserialize_byte(const unsigned char **buf, int *remaining, int *value)
{
   if (*remaining < 1) {
      return FIXSCRIPT_ERR_BAD_FORMAT;
   }
   *value = (unsigned int)(*buf)[0];
   (*buf)++;
   (*remaining)--;
   return FIXSCRIPT_SUCCESS;
}


static inline int unserialize_int(const unsigned char **buf, int *remaining, int *value)
{
   if (*remaining < 4) {
      return FIXSCRIPT_ERR_BAD_FORMAT;
   }
   *value = (*buf)[0] | ((*buf)[1] << 8) | ((*buf)[2] << 16) | ((*buf)[3] << 24);
   (*buf) += 4;
   (*remaining) -= 4;
   return FIXSCRIPT_SUCCESS;
}


static int unserialize_value(Heap *heap, const unsigned char **buf, int *remaining, Value list, Value *value, int recursion_limit)
{
   Array *arr;
   Value array, array_value, hash, hash_key, hash_value;
   int i, err, type, flt, ref, len, int_val, little_endian_test, max_val, key_was_present;
   int64_t sum;

   err = unserialize_byte(buf, remaining, &type);
   if (err != FIXSCRIPT_SUCCESS) return err;

   switch (type) {
      case SER_INT: {
         err = unserialize_int(buf, remaining, &int_val);
         if (err != FIXSCRIPT_SUCCESS) return err;

         value->value = int_val;
         value->is_array = 0;
         return FIXSCRIPT_SUCCESS;
      }

      case SER_FLOAT: {
         err = unserialize_int(buf, remaining, &int_val);
         if (err != FIXSCRIPT_SUCCESS) return err;

         flt = int_val & ~(1<<31);
         if (flt > 0 && flt < (1<<23)) {
            return FIXSCRIPT_ERR_BAD_FORMAT;
         }
         if ((flt >> 23) == 0xFF) {
            flt &= (1<<23)-1;
            if (flt != 0 && flt != (1<<22)) {
               return FIXSCRIPT_ERR_BAD_FORMAT;
            }
         }
         value->value = int_val;
         value->is_array = 1;
         return FIXSCRIPT_SUCCESS;
      }

      case SER_REF: {
         err = unserialize_int(buf, remaining, &ref);
         if (err != FIXSCRIPT_SUCCESS) return err;

         err = fixscript_get_array_elem(heap, list, ref, value);
         if (err != FIXSCRIPT_SUCCESS) return err;

         if (!value->value) {
            return FIXSCRIPT_ERR_BAD_FORMAT;
         }
         return FIXSCRIPT_SUCCESS;
      }

      case SER_ARRAY:
      case SER_ARRAY_BYTE:
      case SER_ARRAY_SHORT:
      case SER_ARRAY_INT:
      case SER_STRING_BYTE:
      case SER_STRING_SHORT:
      case SER_STRING_INT: {
         err = unserialize_int(buf, remaining, &len);
         if (err != FIXSCRIPT_SUCCESS) return err;

         if (len < 0) {
            return FIXSCRIPT_ERR_BAD_FORMAT;
         }

         if (type == SER_ARRAY && len == 0) {
            return FIXSCRIPT_ERR_BAD_FORMAT;
         }

         if (type >= SER_ARRAY && type <= SER_ARRAY_INT) {
            array = fixscript_create_array(heap, 0);
         }
         else {
            array = fixscript_create_string(heap, "", 0);
         }
         if (!array.value) {
            return FIXSCRIPT_ERR_OUT_OF_MEMORY;
         }

         arr = &heap->data[array.value];
         if (type == SER_ARRAY || type == SER_ARRAY_INT || type == SER_STRING_INT) {
            arr->type = ARR_INT;
         }
         else if (type == SER_ARRAY_SHORT || type == SER_STRING_SHORT) {
            arr->type = ARR_SHORT;
         }

         err = fixscript_append_array_elem(heap, list, array);
         if (err != FIXSCRIPT_SUCCESS) return err;

         err = fixscript_set_array_length(heap, array, len);
         if (err != FIXSCRIPT_SUCCESS) return err;

         if (type == SER_ARRAY_BYTE || type == SER_STRING_BYTE) {
            if (*remaining < len) return FIXSCRIPT_ERR_BAD_FORMAT;
            memcpy(arr->byte_data, *buf, len);
            (*buf) += len;
            (*remaining) -= len;
            *value = array;
            return FIXSCRIPT_SUCCESS;
         }

         if (type == SER_ARRAY_SHORT || type == SER_STRING_SHORT) {
            sum = ((int64_t)len) << 1;
            if (sum > INT_MAX) return FIXSCRIPT_ERR_OUT_OF_MEMORY;
            if (*remaining < sum) return FIXSCRIPT_ERR_BAD_FORMAT;
            little_endian_test = 1;
            if (*(char *)&little_endian_test == 1) {
               memcpy(arr->short_data, *buf, (int)sum);
            }
            else {
               for (i=0; i<len; i++) {
                  arr->short_data[i] = (*buf)[i*2+0] | ((*buf)[i*2+1] << 8);
               }
            }
            (*buf) += (int)sum;
            (*remaining) -= (int)sum;
            *value = array;

            max_val = 0;
            for (i=0; i<len; i++) {
               max_val |= arr->short_data[i];
            }
            if ((max_val & ~0xFF) == 0) {
               return FIXSCRIPT_ERR_BAD_FORMAT;
            }
            return FIXSCRIPT_SUCCESS;
         }

         if (type == SER_ARRAY_INT || type == SER_STRING_INT) {
            sum = ((int64_t)len) << 2;
            if (sum > INT_MAX) return FIXSCRIPT_ERR_OUT_OF_MEMORY;
            if (*remaining < sum) return FIXSCRIPT_ERR_BAD_FORMAT;
            little_endian_test = 1;
            if (*(char *)&little_endian_test == 1) {
               memcpy(arr->data, *buf, (int)sum);
            }
            else {
               for (i=0; i<len; i++) {
                  arr->data[i] = (*buf)[i*4+0] | ((*buf)[i*4+1] << 8) | ((*buf)[i*4+2] << 16) | ((*buf)[i*4+3] << 24);
               }
            }
            (*buf) += (int)sum;
            (*remaining) -= (int)sum;
            *value = array;

            max_val = 0;
            for (i=0; i<len; i++) {
               max_val |= arr->data[i];
            }
            if ((max_val & ~0xFFFF) == 0) {
               return FIXSCRIPT_ERR_BAD_FORMAT;
            }
            return FIXSCRIPT_SUCCESS;
         }

         for (i=0; i<len; i++) {
            err = unserialize_value(heap, buf, remaining, list, &array_value, recursion_limit-1);
            if (err != FIXSCRIPT_SUCCESS) return err;
            
            err = fixscript_set_array_elem(heap, array, i, array_value);
            if (err != FIXSCRIPT_SUCCESS) return err;
         }

         *value = array;
         return FIXSCRIPT_SUCCESS;
      }

      case SER_HASH: {
         err = unserialize_int(buf, remaining, &len);
         if (err != FIXSCRIPT_SUCCESS) return err;

         if (len < 0) {
            return FIXSCRIPT_ERR_BAD_FORMAT;
         }

         hash = fixscript_create_hash(heap);
         if (!hash.value) {
            return FIXSCRIPT_ERR_OUT_OF_MEMORY;
         }

         err = fixscript_append_array_elem(heap, list, hash);
         if (err != FIXSCRIPT_SUCCESS) return err;

         key_was_present = 0;
         for (i=0; i<len; i++) {
            err = unserialize_value(heap, buf, remaining, list, &hash_key, recursion_limit-1);
            if (err != FIXSCRIPT_SUCCESS) return err;

            err = unserialize_value(heap, buf, remaining, list, &hash_value, recursion_limit-1);
            if (err != FIXSCRIPT_SUCCESS) return err;
            
            err = set_hash_elem(heap, hash, hash_key, hash_value, &key_was_present);
            if (err != FIXSCRIPT_SUCCESS) return err;
            if (key_was_present) return FIXSCRIPT_ERR_BAD_FORMAT;
         }

         *value = hash;
         return FIXSCRIPT_SUCCESS;
      }
   }

   return FIXSCRIPT_ERR_BAD_FORMAT;
}


int fixscript_unserialize(Heap *heap, Value buf_val, int *off, int len, Value *value)
{
   Value list;
   Array *arr;
   const unsigned char *buf, *byte_data;
   int err, remaining, unspec_len;

   if (!fixscript_is_array(heap, buf_val)) {
      return FIXSCRIPT_ERR_INVALID_ACCESS;
   }

   arr = &heap->data[buf_val.value];
   if (arr->type != ARR_BYTE) {
      return FIXSCRIPT_ERR_INVALID_BYTE_ARRAY;
   }

   if (*off < 0) {
      return FIXSCRIPT_ERR_OUT_OF_BOUNDS;
   }

   unspec_len = (len < 0);
   if (unspec_len) {
      len = arr->len - *off;
      if (len < 0) {
         return FIXSCRIPT_ERR_OUT_OF_BOUNDS;
      }
   }

   if (((int64_t)*off) + ((int64_t)len) > arr->len) {
      return FIXSCRIPT_ERR_OUT_OF_BOUNDS;
   }

   byte_data = arr->byte_data;
   buf = byte_data + *off;
   remaining = len;
   
   list = fixscript_create_array(heap, 0);
   if (!list.value) {
      return FIXSCRIPT_ERR_OUT_OF_MEMORY;
   }

   err = unserialize_value(heap, &buf, &remaining, list, value, MAX_SERIALIZE_RECURSION);
   *off = buf - byte_data;
   if (err == FIXSCRIPT_SUCCESS && !unspec_len && remaining != 0) {
      err = FIXSCRIPT_ERR_BAD_FORMAT;
   }
   reclaim_array(heap, list.value, NULL);
   return err;
}


int fixscript_serialize_to_array(Heap *heap, char **buf, int *len_out, Value value)
{
   Value buf_val;
   Array *arr;
   int err, len;

   buf_val = fixscript_create_array(heap, 0);
   if (!buf_val.value) return FIXSCRIPT_ERR_OUT_OF_MEMORY;

   if (!len_out) {
      err = fixscript_set_array_length(heap, buf_val, sizeof(int));
      if (err != FIXSCRIPT_SUCCESS) return err;
   }

   err = fixscript_serialize(heap, &buf_val, value);
   if (err != FIXSCRIPT_SUCCESS) return err;

   err = fixscript_get_array_length(heap, buf_val, &len);
   if (err) return err;
   
   arr = &heap->data[buf_val.value];
   if (arr->type != ARR_BYTE) return FIXSCRIPT_ERR_INVALID_BYTE_ARRAY;

   if (!len_out) {
      len -= sizeof(int);
      memcpy(arr->byte_data, &len, sizeof(int));
   }
   else {
      *len_out = len;
   }

   *buf = (char *)arr->byte_data;
   arr->byte_data = NULL;
   reclaim_array(heap, buf_val.value, arr);
   return FIXSCRIPT_SUCCESS;
}


int fixscript_unserialize_from_array(Heap *heap, const char *buf, int *off_out, int len, Value *value)
{
   Value buf_val;
   Array *arr;
   int err, off;

   buf_val = fixscript_create_array(heap, 0);
   if (!buf_val.value) return FIXSCRIPT_ERR_OUT_OF_MEMORY;

   if (len < 0) {
      memcpy(&len, buf, sizeof(int));
      buf += sizeof(int);
   }

   arr = &heap->data[buf_val.value];
   if (arr->type != ARR_BYTE) return FIXSCRIPT_ERR_INVALID_BYTE_ARRAY;

   arr->flags = calloc(FLAGS_SIZE(len), sizeof(int));
   arr->byte_data = (unsigned char *)buf;
   arr->size = len;
   arr->len = len;
   heap->total_size += (int64_t)FLAGS_SIZE(arr->size) * sizeof(int) + (int64_t)arr->size;

   off = 0;
   err = fixscript_unserialize(heap, buf_val, &off, off_out? -1 : len, value);
   if (err == FIXSCRIPT_SUCCESS && off_out) {
      *off_out = off;
   }

   arr = &heap->data[buf_val.value];
   arr->byte_data = NULL;
   reclaim_array(heap, buf_val.value, arr);
   return err;
}


////////////////////////////////////////////////////////////////////////
// Tokenizer:
////////////////////////////////////////////////////////////////////////


static int is_ident(char c)
{
   return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c == '_');
}


static int is_digit(char c)
{
   return (c >= '0' && c <= '9');
}


static int is_hex_digit(char c)
{
   return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
}


static int get_hex_digit(char c)
{
   if (c >= '0' && c <= '9') return c - '0';
   if (c >= 'a' && c <= 'f') return c - 'a' + 10;
   if (c >= 'A' && c <= 'F') return c - 'A' + 10;
   return -1;
}


static int is_whitespace(char c)
{
   return (c == ' ' || c == '\r' || c == '\n' || c == '\t');
}


static int is_newline(char c)
{
   return (c == '\r' || c == '\n');
}


static int is_symbol(char c)
{
   switch (c) {
      case '(':
      case ')':
      case '{':
      case '}':
      case ']':
      case ',':
      case ';':
      case '~':
      case ':':
      case '@':
      case '?':
         return 1;

      case '+':
      case '-':
      case '*':
      case '/':
      case '%':
      case '&':
      case '|':
      case '^':
      case '.':
      case '[':
         return 2;

      case '<':
      case '=':
      case '!':
         return 3;

      case '>':
         return 4;
   }
   return 0;
}


static int is_symbol1(char c)
{
   if (c == '.') return 0;
   return 1;
}


static int is_symbol2(char c1, char c2)
{
   switch (c1) {
      case '+': if (c2 == '=' || c2 == '+') return 1; break;
      case '-': if (c2 == '=' || c2 == '-' || c2 == '>') return 1; break;
      case '*': if (c2 == '=') return 1; break;
      case '/': if (c2 == '=') return 1; break;
      case '%': if (c2 == '=') return 1; break;
      case '&': if (c2 == '=' || c2 == '&') return 1; break;
      case '|': if (c2 == '=' || c2 == '|') return 1; break;
      case '^': if (c2 == '=') return 1; break;
      case '<': if (c2 == '=' || c2 == '<') return 1; break;
      case '>': if (c2 == '=' || c2 == '>') return 1; break;
      case '=': if (c2 == '=') return 1; break;
      case '!': if (c2 == '=') return 1; break;
      case '[': if (c2 == ']') return 1; break;
      case '.': if (c2 == '.') return 1; break;
   }
   return 0;
}


static int is_symbol3(char c1, char c2, char c3)
{
   if (c1 == '=' && c2 == '=' && c3 == '=') return 1;
   if (c1 == '!' && c2 == '=' && c3 == '=') return 1;
   if (c1 == '<' && c2 == '<' && c3 == '=') return 1;
   if (c1 == '>' && c2 == '>' && c3 == '=') return 1;
   if (c1 == '>' && c2 == '>' && c3 == '>') return 1;
   return 0;
}


static int is_symbol4(char c1, char c2, char c3, char c4)
{
   if (c1 == '>' && c2 == '>' && c3 == '>' && c4 == '=') return 1;
   return 0;
}


static int is_unknown(char c)
{
   if (is_ident(c)) return 0;
   if (is_digit(c)) return 0;
   if (is_whitespace(c)) return 0;
   if (is_symbol(c)) return 0;
   if (c == '\'' || c == '"' || c == 0) return 0;
   return 1;
}


static int set_value(Tokenizer *tok, const char *start, int type)
{
   tok->value = start;
   tok->len = tok->cur - start;
   tok->type = type;
   
   if (tok->cur_token) {
      if (type != tok->cur_token[TOK_type].value) {
         tok->error = "token type mismatch";
         return 0;
      }
      if (tok->cur != tok->tokens_src + tok->cur_token[TOK_off].value + tok->cur_token[TOK_len].value) {
         tok->error = "token length mismatch";
         return 0;
      }
      tok->cur_token += TOK_SIZE;
   }
   return 1;
}


static void skip_whitespace(Tokenizer *tok)
{
   while (is_whitespace(*tok->cur)) {
      if (*tok->cur == '\n') {
         tok->line++;
      }
      tok->cur++;
   }
}


static int next_token(Tokenizer *tok)
{
   const char *start;
   int cnt, c, len, closed;
   char end_char;
   const char *end_ptr = NULL;
   
   if (tok->again) {
      if (tok->again == 2 || tok->error) {
         return 0;
      }
      tok->again = 0;
      return 1;
   }

   if (tok->cur_token) {
      if (tok->cur_token == tok->tokens_end) {
         tok->again = 2;
         return 0;
      }
      tok->cur = tok->tokens_src + tok->cur_token[TOK_off].value;
      end_ptr = tok->cur + tok->cur_token[TOK_len].value;
      tok->line = tok->cur_token[TOK_line].value;
   }
   else {
      for (;;) {
         skip_whitespace(tok);

         if (tok->cur[0] == '/' && tok->cur[1] == '/') {
            tok->cur += 2;
            while (*tok->cur && !is_newline(*tok->cur)) tok->cur++;
            if (*tok->cur == '\r') tok->cur++;
            if (*tok->cur == '\n') {
               tok->cur++;
               tok->line++;
            }
            continue;
         }

         if (tok->cur[0] == '/' && tok->cur[1] == '*') {
            tok->cur += 2;
            while (tok->cur[0] != '*' || tok->cur[1] != '/') {
               if (*tok->cur == '\n') {
                  tok->line++;
               }
               tok->cur++;
            }
            tok->cur += 2;
            continue;
         }

         break;
      }
   }

   if (*tok->cur == '\0') {
      tok->again = 2;
      return 0;
   }

   start = tok->cur;

   if (is_ident(*tok->cur)) {
      while ((is_ident(*tok->cur) || is_digit(*tok->cur)) && (!end_ptr || tok->cur < end_ptr)) {
         tok->cur++;
      }
      if (*tok->cur == '#' && (!end_ptr || tok->cur+1 < end_ptr) && is_digit(tok->cur[1])) {
         tok->cur++;
         while (is_digit(*tok->cur) && (!end_ptr || tok->cur < end_ptr)) {
            tok->cur++;
         }
         return set_value(tok, start, TOK_FUNC_REF);
      }

      tok->type = TOK_IDENT;
      len = tok->cur - start;
      switch (len) {
         case 2:
            if (!strncmp(start, "do", len)) { tok->type = KW_DO; break; }
            if (!strncmp(start, "if", len)) { tok->type = KW_IF; break; }
            break;

         case 3:
            if (!strncmp(start, "for", len)) { tok->type = KW_FOR; break; }
            if (!strncmp(start, "var", len)) { tok->type = KW_VAR; break; }
            if (!strncmp(start, "use", len)) { tok->type = KW_USE; break; }
            break;

         case 4:
            if (!strncmp(start, "else", len)) { tok->type = KW_ELSE; break; }
            if (!strncmp(start, "case", len)) { tok->type = KW_CASE; break; }
            break;

         case 5:
            if (!strncmp(start, "while", len)) { tok->type = KW_WHILE; break; }
            if (!strncmp(start, "break", len)) { tok->type = KW_BREAK; break; }
            if (!strncmp(start, "const", len)) { tok->type = KW_CONST; break; }
            break;

         case 6:
            if (!strncmp(start, "return", len)) { tok->type = KW_RETURN; break; }
            if (!strncmp(start, "import", len)) { tok->type = KW_IMPORT; break; }
            if (!strncmp(start, "switch", len)) { tok->type = KW_SWITCH; break; }
            break;

         case 7:
            if (!strncmp(start, "default", len)) { tok->type = KW_DEFAULT; break; }
            break;

         case 8:
            if (!strncmp(start, "function", len)) { tok->type = KW_FUNCTION; break; }
            if (!strncmp(start, "continue", len)) { tok->type = KW_CONTINUE; break; }
            break;
      }
      return set_value(tok, start, tok->type);
   }

   if (tok->cur[0] == '0' && tok->cur[1] == 'x' && (!end_ptr || tok->cur+1 < end_ptr)) {
      if (!is_hex_digit(tok->cur[2]) || (end_ptr && tok->cur+2 >= end_ptr)) {
         tok->error = "invalid hexadecimal constant";
         return 0;
      }
      tok->cur += 3;
      while (is_hex_digit(*tok->cur) && (!end_ptr || tok->cur < end_ptr)) tok->cur++;
      return set_value(tok, start, TOK_HEX_NUMBER);
   }

   if (is_digit(*tok->cur) || (tok->cur[0] == '-' && is_digit(tok->cur[1]) && (!end_ptr || tok->cur+1 < end_ptr))) {
      tok->type = TOK_NUMBER;
      tok->cur++;
      while (is_digit(*tok->cur) && (!end_ptr || tok->cur < end_ptr)) tok->cur++;
      if (*tok->cur == '.' && (!end_ptr || tok->cur < end_ptr)) {
         tok->type = TOK_FLOAT_NUMBER;
         tok->cur++;
         if (!is_digit(*tok->cur) || (end_ptr && tok->cur >= end_ptr)) {
            tok->error = "invalid float constant";
            return 0;
         }
         while (is_digit(*tok->cur) && (!end_ptr || tok->cur < end_ptr)) tok->cur++;
      }
      if ((*tok->cur == 'e' || *tok->cur == 'E') && (!end_ptr || tok->cur < end_ptr)) {
         tok->type = TOK_FLOAT_NUMBER;
         tok->cur++;
         if (*tok->cur == '-' && (!end_ptr || tok->cur < end_ptr)) tok->cur++;
         if (!is_digit(*tok->cur) || (end_ptr && tok->cur >= end_ptr)) {
            tok->error = "invalid float constant";
            return 0;
         }
         while (is_digit(*tok->cur) && (!end_ptr || tok->cur < end_ptr)) tok->cur++;
      }
      return set_value(tok, start, tok->type);
   }

   if (*tok->cur == '\'' || *tok->cur == '"') {
      end_char = *tok->cur++;
      tok->num_chars = 0;
      tok->num_utf8_bytes = 0;
      tok->max_num_value = 0xFF;
      closed = 0;

      while (*tok->cur && (!end_ptr || tok->cur < end_ptr)) {
         if (*tok->cur == end_char) {
            tok->cur++;
            closed = 1;
            break;
         }
         if (end_char == '\'' && tok->num_chars >= 1) {
            tok->error = "multiple characters in char literal";
            return 0;
         }
         if (*tok->cur == '\\') {
            tok->cur++;
            switch ((!end_ptr || tok->cur < end_ptr)? *tok->cur : '\0') {
               case 'r':
               case 'n':
               case 't':
               case '\\':
               case '\'':
               case '"':
                  tok->num_utf8_bytes++;
                  break;

               case 'u':
                  if (tok->cur[1] && tok->cur[2] && tok->cur[3] && tok->cur[4] &&
                      is_hex_digit(tok->cur[1]) && is_hex_digit(tok->cur[2]) && is_hex_digit(tok->cur[3]) && is_hex_digit(tok->cur[4]) &&
                      (!end_ptr || tok->cur+4 < end_ptr))
                  {
                     c = (get_hex_digit(tok->cur[1]) << 12) |
                         (get_hex_digit(tok->cur[2]) << 8) |
                         (get_hex_digit(tok->cur[3]) << 4) |
                         (get_hex_digit(tok->cur[4]));
                     if (c >= 0xD800 && c <= 0xDFFF) {
                        tok->error = "illegal code point";
                        return 0;
                     }
                     if (c < 0x80) {
                        tok->num_utf8_bytes++;
                     }
                     else if (c < 0x800) {
                        tok->num_utf8_bytes += 2;
                     }
                     else {
                        tok->num_utf8_bytes += 3;
                     }
                     if (c > tok->max_num_value) {
                        tok->max_num_value = c;
                     }
                     tok->cur += 4;
                  }
                  else {
                     tok->error = "bad escape sequence";
                     return 0;
                  }
                  break;

               case 'U':
                  if (tok->cur[1] && tok->cur[2] && tok->cur[3] && tok->cur[4] && tok->cur[5] && tok->cur[6] &&
                      is_hex_digit(tok->cur[1]) && is_hex_digit(tok->cur[2]) && is_hex_digit(tok->cur[3]) && is_hex_digit(tok->cur[4]) && is_hex_digit(tok->cur[5]) && is_hex_digit(tok->cur[6]) &&
                      (!end_ptr || tok->cur+6 < end_ptr))
                  {
                     c = (get_hex_digit(tok->cur[1]) << 20) |
                         (get_hex_digit(tok->cur[2]) << 16) |
                         (get_hex_digit(tok->cur[3]) << 12) |
                         (get_hex_digit(tok->cur[4]) << 8) |
                         (get_hex_digit(tok->cur[5]) << 4) |
                         (get_hex_digit(tok->cur[6]));
                     if (c > 0x10FFFF) {
                        tok->error = "illegal code point";
                        return 0;
                     }
                     if (c >= 0xD800 && c <= 0xDFFF) {
                        tok->error = "illegal code point";
                        return 0;
                     }
                     if (c < 0x80) {
                        tok->num_utf8_bytes++;
                     }
                     else if (c < 0x800) {
                        tok->num_utf8_bytes += 2;
                     }
                     else if (c < 0x10000) {
                        tok->num_utf8_bytes += 3;
                     }
                     else {
                        tok->num_utf8_bytes += 4;
                     }
                     if (c > tok->max_num_value) {
                        tok->max_num_value = c;
                     }
                     tok->cur += 6;
                  }
                  else {
                     tok->error = "bad escape sequence";
                     return 0;
                  }
                  break;

               default:
                  tok->error = "bad escape sequence";
                  return 0;
            }
            tok->cur++;
            tok->num_chars++;
            continue;
         }

         if (*tok->cur == '\r' || *tok->cur == '\n') {
            tok->error = end_char == '\''? "unclosed char literal" : "unclosed string literal";
            return 0;
         }

         if ((tok->cur[0] & 0x80) == 0) {
            c = tok->cur[0];
            tok->cur++;
            tok->num_utf8_bytes++;
         }
         else if ((tok->cur[0] & 0xE0) == 0xC0 && (tok->cur[1] & 0xC0) == 0x80 && (!end_ptr || tok->cur+1 < end_ptr)) {
            c = ((tok->cur[0] & 0x1F) << 6) | (tok->cur[1] & 0x3F);
            if (c < 0x80) {
               tok->error = "illegal UTF-8 sequence";
               return 0;
            }
            tok->cur += 2;
            tok->num_utf8_bytes += 2;
         }
         else if ((tok->cur[0] & 0xF0) == 0xE0 && (tok->cur[1] & 0xC0) == 0x80 && (tok->cur[2] & 0xC0) == 0x80 && (!end_ptr || tok->cur+2 < end_ptr)) {
            c = ((tok->cur[0] & 0x0F) << 12) | ((tok->cur[1] & 0x3F) << 6) | (tok->cur[2] & 0x3F);
            if (c < 0x800) {
               tok->error = "illegal UTF-8 sequence";
               return 0;
            }
            tok->cur += 3;
            tok->num_utf8_bytes += 3;
         }
         else if ((tok->cur[0] & 0xF8) == 0xF0 && (tok->cur[1] & 0xC0) == 0x80 && (tok->cur[2] & 0xC0) == 0x80 && (tok->cur[3] & 0xC0) == 0x80 && (!end_ptr || tok->cur+3 < end_ptr)) {
            c = ((tok->cur[0] & 0x07) << 18) | ((tok->cur[1] & 0x3F) << 12) | ((tok->cur[2] & 0x3F) << 6) | (tok->cur[3] & 0x3F);
            if (c < 0x10000 || c > 0x10FFFF) {
               tok->error = "illegal UTF-8 sequence";
               return 0;
            }
            tok->cur += 4;
            tok->num_utf8_bytes += 4;
         }
         else {
            tok->error = "illegal UTF-8 sequence";
            return 0;
         }

         if (c >= 0xD800 && c <= 0xDFFF) {
            tok->error = "illegal UTF-8 sequence";
            return 0;
         }

         if (c > tok->max_num_value) {
            tok->max_num_value = c;
         }

         tok->num_chars++;
      }

      if (!closed) {
         tok->error = end_char == '\''? "unclosed char literal" : "unclosed string literal";
         return 0;
      }

      if (end_char == '\'' && tok->num_chars == 0) {
         tok->error = "empty char literal";
         return 0;
      }

      return set_value(tok, start, end_char == '\''? TOK_CHAR : TOK_STRING);
   }

   cnt = is_symbol(*tok->cur);
   if (cnt >= 4 && tok->cur[1] && tok->cur[2] && is_symbol4(tok->cur[0], tok->cur[1], tok->cur[2], tok->cur[3]) && (!end_ptr || tok->cur+3 < end_ptr)) {
      tok->cur += 4;
      return set_value(tok, start, TOK_SYMBOL4);
   }
   if (cnt >= 3 && tok->cur[1] && is_symbol3(tok->cur[0], tok->cur[1], tok->cur[2]) && (!end_ptr || tok->cur+2 < end_ptr)) {
      tok->cur += 3;
      return set_value(tok, start, TOK_SYMBOL3);
   }
   if (cnt >= 2 && is_symbol2(tok->cur[0], tok->cur[1]) && (!end_ptr || tok->cur+1 < end_ptr)) {
      tok->cur += 2;
      return set_value(tok, start, TOK_SYMBOL2);
   }
   if (cnt >= 1 && is_symbol1(tok->cur[0])) {
      tok->cur++;
      return set_value(tok, start, TOK_SYMBOL);
   }

   tok->cur++;
   while (is_unknown(*tok->cur) && (!end_ptr || tok->cur < end_ptr)) {
      tok->cur++;
   }

   return set_value(tok, start, TOK_UNKNOWN);
}


// note: zero character is escaped as 0xFF (beware of signed chars when testing for it)
static char *get_token_string(Tokenizer *tok)
{
   const char *s, *end;
   char *out, *out_buf;
   int c;
   
   if (tok->type != TOK_STRING) {
      return NULL;
   }

   out = out_buf = malloc(tok->num_utf8_bytes+1);
   s = tok->value+1;
   end = tok->value+tok->len-1;
   while (s < end) {
      if (*s == '\\') {
         s++;
         switch (*s++) {
            case 'r': *out++ = '\r'; break;
            case 'n': *out++ = '\n'; break;
            case 't': *out++ = '\t'; break;
            case '\\': *out++ = '\\'; break;
            case '\'': *out++ = '\''; break;
            case '"': *out++ = '\"'; break;

            case 'u':
               c = (get_hex_digit(s[0]) << 12) |
                   (get_hex_digit(s[1]) << 8) |
                   (get_hex_digit(s[2]) << 4) |
                   (get_hex_digit(s[3]));
               s += 4;
               if (c >= 0x800) {
                  *out++ = (c >> 12) | 0xE0;
                  *out++ = ((c >> 6) & 0x3F) | 0x80;
                  *out++ = (c & 0x3F) | 0x80;
               }
               else if (c >= 0x80) {
                  *out++ = (c >> 6) | 0xC0;
                  *out++ = (c & 0x3F) | 0x80;
               }
               else {
                  *out++ = c == 0? 0xFF : c;
               }
               break;

            case 'U':
               c = (get_hex_digit(s[0]) << 20) |
                   (get_hex_digit(s[1]) << 16) |
                   (get_hex_digit(s[2]) << 12) |
                   (get_hex_digit(s[3]) << 8) |
                   (get_hex_digit(s[4]) << 4) |
                   (get_hex_digit(s[5]));
               s += 6;
               if (c >= 0x10000) {
                  *out++ = (c >> 18) | 0xF0;
                  *out++ = ((c >> 12) & 0x3F) | 0x80;
                  *out++ = ((c >> 6) & 0x3F) | 0x80;
                  *out++ = (c & 0x3F) | 0x80;
               }
               else if (c >= 0x800) {
                  *out++ = (c >> 12) | 0xE0;
                  *out++ = ((c >> 6) & 0x3F) | 0x80;
                  *out++ = (c & 0x3F) | 0x80;
               }
               else if (c >= 0x80) {
                  *out++ = (c >> 6) | 0xC0;
                  *out++ = (c & 0x3F) | 0x80;
               }
               else {
                  *out++ = c == 0? 0xFF : c;
               }
               break;
         }
      }
      else {
         *out++ = *s++;
      }
   }
   out_buf[tok->num_utf8_bytes] = '\0';
   return out_buf;
}


static int get_token_char(Tokenizer *tok)
{
   const char *s, *end;
   
   if (tok->type != TOK_CHAR) {
      return -1;
   }

   s = tok->value+1;
   end = tok->value+tok->len-1;
   while (s < end) {
      if (*s == '\\') {
         s++;
         switch (*s++) {
            case 'r': return '\r';
            case 'n': return '\n';
            case 't': return '\t';
            case '\\': return '\\';
            case '\'': return '\'';
            case '"': return '"';

            case 'u':
               return (get_hex_digit(s[0]) << 12) |
                      (get_hex_digit(s[1]) << 8) |
                      (get_hex_digit(s[2]) << 4) |
                      (get_hex_digit(s[3]));

            case 'U':
               return (get_hex_digit(s[0]) << 20) |
                      (get_hex_digit(s[1]) << 16) |
                      (get_hex_digit(s[2]) << 12) |
                      (get_hex_digit(s[3]) << 8) |
                      (get_hex_digit(s[4]) << 4) |
                      (get_hex_digit(s[5]));
         }
      }
      else {
         if ((s[0] & 0x80) == 0) {
            return s[0];
         }
         else if ((s[0] & 0xE0) == 0xC0 && (s[1] & 0xC0) == 0x80) {
            return ((s[0] & 0x1F) << 6) | (s[1] & 0x3F);
         }
         else if ((s[0] & 0xF0) == 0xE0 && (s[1] & 0xC0) == 0x80 && (s[2] & 0xC0) == 0x80) {
            return ((s[0] & 0x0F) << 12) | ((s[1] & 0x3F) << 6) | (s[2] & 0x3F);
         }
         else if ((s[0] & 0xF8) == 0xF0 && (s[1] & 0xC0) == 0x80 && (s[2] & 0xC0) == 0x80 && (s[3] & 0xC0) == 0x80) {
            return ((s[0] & 0x07) << 18) | ((s[1] & 0x3F) << 12) | ((s[2] & 0x3F) << 6) | (s[3] & 0x3F);
         }
         return -1;
      }
   }
   return -1;
}


static void undo_token(Tokenizer *tok)
{
   if (tok->again == 0) {
      tok->again = 1;
   }
}


////////////////////////////////////////////////////////////////////////
// Parser:
////////////////////////////////////////////////////////////////////////


static void buf_append(Parser *par, unsigned char bytecode)
{
   if (par->buf_len == par->buf_size) {
      par->buf_size <<= 1;
      par->buf = realloc(par->buf, par->buf_size);
   }
   par->last_buf_pos = par->buf_len;
   par->buf[par->buf_len++] = bytecode;
}


static void buf_append_int(Parser *par, int value)
{
   union {
      int i;
      unsigned char c[4];
   } int_val;
   int i;

   int_val.i = value;
   for (i=0; i<4; i++) {
      buf_append(par, int_val.c[i]);
   }
}


static void buf_append_const(Parser *par, int value)
{
   union {
      unsigned short s;
      unsigned char c[2];
   } short_val;

   int last_buf_pos = par->buf_len;

   if (value >= -1 && (value <= 32 || value == 63 || value == 64)) {
      buf_append(par, BC_CONST0+value);
      return;
   }
   if (value > 0 && value <= 256) {
      buf_append(par, BC_CONST_P8);
      buf_append(par, value - 1);
      par->last_buf_pos = last_buf_pos;
      return;
   }
   if (value < 0 && value >= -256) {
      buf_append(par, BC_CONST_N8);
      buf_append(par, (-value) - 1);
      par->last_buf_pos = last_buf_pos;
      return;
   }
   if (value > 0 && value <= 65536) {
      buf_append(par, BC_CONST_P16);
      short_val.s = value - 1;
      buf_append(par, short_val.c[0]);
      buf_append(par, short_val.c[1]);
      par->last_buf_pos = last_buf_pos;
      return;
   }
   if (value < 0 && value >= -65536) {
      buf_append(par, BC_CONST_N16);
      short_val.s = (-value) - 1;
      buf_append(par, short_val.c[0]);
      buf_append(par, short_val.c[1]);
      par->last_buf_pos = last_buf_pos;
      return;
   }

   buf_append(par, BC_CONST_I32);
   buf_append_int(par, value);
   par->last_buf_pos = last_buf_pos;
}


static void buf_append_const_float(Parser *par, int value)
{
   int last_buf_pos = par->buf_len;

   buf_append(par, BC_CONST_F32);
   buf_append_int(par, value);
   par->last_buf_pos = last_buf_pos;
}


static void buf_append_load(Parser *par, int pos)
{
   int last_buf_pos;

   if (pos >= -64 && pos <= -1) {
      buf_append(par, BC_LOADM64+64+pos);
   }
   else {
      last_buf_pos = par->buf_len;
      buf_append_const(par, pos-1);
      buf_append(par, BC_LOADN);
      par->last_buf_pos = last_buf_pos;
   }
}


static void buf_append_store(Parser *par, int pos)
{
   int last_buf_pos;

   if (pos >= -64 && pos <= -1) {
      buf_append(par, BC_STOREM64+64+pos);
   }
   else {
      last_buf_pos = par->buf_len;
      buf_append_const(par, pos-1);
      buf_append(par, BC_STOREN);
      par->last_buf_pos = last_buf_pos;
   }
}


static void buf_append_load_local_var(Parser *par, int local_var)
{
   int last_buf_pos;

   last_buf_pos = par->buf_len;
   buf_append(par, BC_LOAD_LOCAL);
   buf_append_int(par, local_var);
   par->last_buf_pos = last_buf_pos;
}


static void buf_append_store_local_var(Parser *par, int local_var)
{
   int last_buf_pos;

   last_buf_pos = par->buf_len;
   buf_append(par, BC_STORE_LOCAL);
   buf_append_int(par, local_var);
   par->last_buf_pos = last_buf_pos;
}


static void buf_append_pop(Parser *par, int num)
{
   int last_buf_pos;

   if (num == 1) {
      buf_append(par, BC_POP);
   }
   else if (num == 2) {
      buf_append(par, BC_POP);
      buf_append(par, BC_POP);
      par->last_buf_pos--;
   }
   else if (num > 2) {
      last_buf_pos = par->buf_len;
      buf_append_const(par, num);
      buf_append(par, BC_POPN);
      par->last_buf_pos = last_buf_pos;
   }
}


static int buf_append_branch(Parser *par, int type)
{
   if (par->long_jumps) {
      switch (type) {
         case BC_BRANCH0: type = BC_BRANCH_LONG; break;
         case BC_JUMP0: type = BC_JUMP_LONG; break;
      }
      buf_append(par, type);
      buf_append_int(par, 0);
      par->last_buf_pos -= 4;
      return par->last_buf_pos;
   }
   else {
      buf_append(par, type);
      buf_append(par, 0);
      par->last_buf_pos--;
      return par->last_buf_pos;
   }
}


static int buf_update_branch(Parser *par, int pos)
{
   union {
      int i;
      unsigned char c[4];
   } int_val;
   int value = par->buf_len - (pos+(par->long_jumps? 5 : 2));

   if (value < 0) {
      par->tok.error = "internal error: negative jump target";
      return 0;
   }

   if (par->long_jumps) {
      int_val.i = value;
      par->buf[pos+1] = int_val.c[0];
      par->buf[pos+2] = int_val.c[1];
      par->buf[pos+3] = int_val.c[2];
      par->buf[pos+4] = int_val.c[3];
      return 1;
   }
   else {
      if (value >= 2048) {
         par->long_jumps = 1;
         return 0;
      }
      par->buf[pos+0] += value >> 8;
      par->buf[pos+1] = value & 0xFF;
      return 1;
   }
}


static void buf_append_loop(Parser *par, int pos)
{
   union {
      unsigned short s;
      unsigned char c[2];
   } short_val;
   int value = par->buf_len - pos + 1;

   if (value <= 0xFF) {
      buf_append(par, BC_LOOP_I8);
      buf_append(par, value);
      par->last_buf_pos--;
   }
   else if (value <= 0xFFFF) {
      buf_append(par, BC_LOOP_I16);
      short_val.s = value;
      buf_append(par, short_val.c[0]);
      buf_append(par, short_val.c[1]);
      par->last_buf_pos -= 2;
   }
   else {
      buf_append(par, BC_LOOP_I32);
      buf_append_int(par, value);
      par->last_buf_pos -= 4;
   }
}


static int buf_is_const(Parser *par, int pos, int *value, int *is_float)
{
   union {
      unsigned short s;
      unsigned char c[2];
   } short_val;

   union {
      int i;
      unsigned char c[4];
   } int_val;

   unsigned char bc = par->buf[pos];

   if (is_float) {
      *is_float = 0;
   }

   if (bc >= BC_CONSTM1 && (bc <= BC_CONST0+32 || bc == BC_CONST0+63 || bc == BC_CONST0+64)) {
      *value = (int)bc - 0x3F;
      return 1;
   }
   else if (bc == BC_CONST_P8) {
      *value = (int)par->buf[pos+1] + 1;
      return 2;
   }
   else if (bc == BC_CONST_N8) {
      *value = -((int)par->buf[pos+1] + 1);
      return 2;
   }
   else if (bc == BC_CONST_P16) {
      short_val.c[0] = par->buf[pos+1];
      short_val.c[1] = par->buf[pos+2];
      *value = (int)short_val.s + 1;
      return 3;
   }
   else if (bc == BC_CONST_N16) {
      short_val.c[0] = par->buf[pos+1];
      short_val.c[1] = par->buf[pos+2];
      *value = -((int)short_val.s + 1);
      return 3;
   }
   else if (bc == BC_CONST_I32 || bc == BC_CONST_F32) {
      int_val.c[0] = par->buf[pos+1];
      int_val.c[1] = par->buf[pos+2];
      int_val.c[2] = par->buf[pos+3];
      int_val.c[3] = par->buf[pos+4];
      *value = int_val.i;
      if (bc == BC_CONST_F32 && is_float) {
         *is_float = 1;
      }
      return 5;
   }

   return 0;
}


static int buf_is_load(Parser *par, int pos, int *value)
{
   unsigned char bc = par->buf[pos];
   int len;

   if (bc >= BC_LOADM64) {
      *value = (signed char)bc;
      return 1;
   }
   else if ((len = buf_is_const(par, pos, value, NULL))) {
      if (par->buf[pos+len] == BC_LOADN) {
         *value += 1;
         return 1;
      }
   }
   return 0;
}


static int buf_is_load_local_var(Parser *par, int pos, int *local_var)
{
   union {
      int i;
      unsigned char c[4];
   } int_val;

   unsigned char bc = par->buf[pos];

   if (bc == BC_LOAD_LOCAL) {
      int_val.c[0] = par->buf[pos+1];
      int_val.c[1] = par->buf[pos+2];
      int_val.c[2] = par->buf[pos+3];
      int_val.c[3] = par->buf[pos+4];
      *local_var = int_val.i;
      return 1;
   }
   return 0;
}


static int buf_set_call2(Parser *par, int *weak_call)
{
   unsigned char *bc = &par->buf[par->last_buf_pos];

   *weak_call = 0;
   if (*bc == BC_CALL_DIRECT) {
      *bc = BC_CALL2_DIRECT;
      return 1;
   }
   if (*bc == BC_CALL_DYNAMIC) {
      *bc = BC_CALL2_DYNAMIC;
      return 1;
   }
   if (*bc == BC_CALL_NATIVE) {
      *bc = BC_CALL2_NATIVE;
      return 1;
   }
   if (*bc == BC_RETURN2) {
      par->buf_len--;
      *weak_call = 1;
      return 1;
   }
   return 0;
}


static int get_const_string(Parser *par, const char *s)
{
   char *tmp;
   int i, len, value;

   value = (intptr_t)string_hash_get(&par->const_strings, s);
   if (!value) {
      tmp = strdup(s);
      if (tmp) {
         len = strlen(tmp);
         for (i=0; i<len; i++) {
            if ((unsigned char)tmp[i] == 0xFF) {
               tmp[i] = 0;
            }
         }
         value = fixscript_create_string(par->heap, tmp, len).value;
         if (value) {
            par->heap->data[value].is_static = 1;
            string_hash_set(&par->const_strings, strdup(s), (void *)(intptr_t)value);
         }
         free(tmp);
      }
   }

   if (!value) {
      par->tok.error = "not enough memory for constant string";
   }
   return value;
}


static void enter_loop(Parser *par, LoopState *state, int has_break, int has_continue, int continue_pc)
{
   if (has_break) {
      state->has_break = par->has_break;
      state->break_stack_pos = par->break_stack_pos;
      state->break_jumps_len = par->break_jumps.len;
      par->has_break = 1;
      par->break_stack_pos = par->stack_pos;
   }
   if (has_continue) {
      state->has_continue = par->has_continue;
      state->continue_pc = par->continue_pc;
      state->continue_stack_pos = par->continue_stack_pos;
      state->continue_jumps_len = par->continue_jumps.len;
      par->has_continue = 1;
      par->continue_pc = continue_pc;
      par->continue_stack_pos = par->stack_pos;
   }
}


static int leave_loop_break(Parser *par, LoopState *state)
{
   int i;

   for (i=state->break_jumps_len; i<par->break_jumps.len; i++) {
      if (!buf_update_branch(par, (intptr_t)par->break_jumps.data[i])) return 0;
   }
   par->has_break = state->has_break;
   par->break_stack_pos = state->break_stack_pos;
   par->break_jumps.len = state->break_jumps_len;
   return 1;
}


static int leave_loop_continue(Parser *par, LoopState *state)
{
   int i;

   for (i=state->continue_jumps_len; i<par->continue_jumps.len; i++) {
      if (!buf_update_branch(par, (intptr_t)par->continue_jumps.data[i])) return 0;
   }
   par->has_continue = state->has_continue;
   par->continue_pc = state->continue_pc;
   par->continue_stack_pos = state->continue_stack_pos;
   par->continue_jumps.len = state->continue_jumps_len;
   return 1;
}


static void add_line_info(Parser *par)
{
   dynarray_add(&par->lines, (void *)(intptr_t)(par->heap->bytecode_size + par->buf_len));
   dynarray_add(&par->lines, (void *)(intptr_t)par->tok.line);
}


static void remove_line_info(Parser *par)
{
   if (par->lines.len >= 2) {
      par->lines.len -= 2;
   }
}


static int has_next(Parser *par)
{
   if (next_token(&par->tok)) {
      undo_token(&par->tok);
      return 1;
   }
   return 0;
}


static int expect_type(Parser *par, int type, const char *error)
{
   if (!next_token(&par->tok)) {
      undo_token(&par->tok);
      if (!par->tok.error) {
         par->tok.error = error;
      }
      return 0;
   }
   if (par->tok.type != type) {
      undo_token(&par->tok);
      par->tok.error = error;
      return 0;
   }
   return 1;
}


static int expect_symbol(Parser *par, char sym, const char *error)
{
   if (expect_type(par, TOK_SYMBOL, error)) {
      if (par->tok.value[0] != sym) {
         undo_token(&par->tok);
         par->tok.error = error;
         return 0;
      }
      return 1;
   }
   return 0;
}


static int expect_symbol2(Parser *par, char sym1, char sym2, const char *error)
{
   if (expect_type(par, TOK_SYMBOL2, error)) {
      if (par->tok.value[0] != sym1 || par->tok.value[1] != sym2) {
         undo_token(&par->tok);
         par->tok.error = error;
         return 0;
      }
      return 1;
   }
   return 0;
}


static int expect_symbol3(Parser *par, char sym1, char sym2, char sym3, const char *error)
{
   if (expect_type(par, TOK_SYMBOL3, error)) {
      if (par->tok.value[0] != sym1 || par->tok.value[1] != sym2 || par->tok.value[2] != sym3) {
         undo_token(&par->tok);
         par->tok.error = error;
         return 0;
      }
      return 1;
   }
   return 0;
}


static int expect_symbol4(Parser *par, char sym1, char sym2, char sym3, char sym4, const char *error)
{
   if (expect_type(par, TOK_SYMBOL4, error)) {
      if (par->tok.value[0] != sym1 || par->tok.value[1] != sym2 || par->tok.value[2] != sym3 || par->tok.value[3] != sym4) {
         undo_token(&par->tok);
         par->tok.error = error;
         return 0;
      }
      return 1;
   }
   return 0;
}


static Constant *find_constant(Script *script, const char *name, int used_import_alias, int *conflict, Script **script_out);
static int parse_primary_expression(Parser *par);
static int parse_expression(Parser *par);
static int parse_block(Parser *par, int type);
static int parse_constant(Parser *par, Value *value, int int_only);
static int parse_statement(Parser *par, const char *error);

static int extract_tokens(Tokenizer *tok, Heap *heap, Value tokens_val, int src_off)
{
   Value values[64 * TOK_SIZE];
   const char *src;
   int err, cnt, total_cnt, has_token;

   err = fixscript_get_array_length(heap, tokens_val, &total_cnt);
   if (err) return 0;
   total_cnt /= TOK_SIZE;

   src = tok->cur;
   cnt = 0;
   for (;;) {
      has_token = next_token(tok);
      if (!has_token || cnt == 64) {
         err = fixscript_set_array_length(heap, tokens_val, (total_cnt + cnt) * TOK_SIZE);
         if (!err) {
            err = fixscript_set_array_range(heap, tokens_val, total_cnt * TOK_SIZE, cnt * TOK_SIZE, values);
         }
         if (err) {
            return 0;
         }
         total_cnt += cnt;
         cnt = 0;
         if (!has_token) break;
      }
      values[cnt*TOK_SIZE + TOK_type] = fixscript_int(tok->type);
      values[cnt*TOK_SIZE + TOK_off] = fixscript_int(tok->value - src + src_off);
      values[cnt*TOK_SIZE + TOK_len] = fixscript_int(tok->len);
      values[cnt*TOK_SIZE + TOK_line] = fixscript_int(tok->line);
      cnt++;
   }

   return 1;
}


static int parse_use_inner(Parser *par, Script *script, char **error_msg)
{
   Value fname_val, tokens_val, source_val, error_val;
   Value *new_tokens_arr = NULL, *new_tokens_end, *cur_token;
   const char *src;
   char *new_tokens_src = NULL, *s;
   int i, err, len, remaining, old_line;

   if (par->tokens_arr_val.value) {
      source_val = par->tokens_src_val;
      tokens_val = par->tokens_arr_val;

      remaining = par->tok.tokens_end - par->tok.cur_token;

      err = fixscript_get_array_length(par->heap, tokens_val, &len);
      if (err) {
         par->tok.error = fixscript_get_error_msg(err);
         goto error;
      }

      err = fixscript_copy_array(par->heap, tokens_val, 0, tokens_val, len - remaining, remaining);
      if (!err) {
         err = fixscript_set_array_length(par->heap, tokens_val, remaining);
      }
      if (err) {
         par->tok.error = fixscript_get_error_msg(err);
         goto error;
      }
   }
   else {
      src = par->tok.cur;

      source_val = fixscript_create_byte_array(par->heap, src, strlen(src));
      tokens_val = fixscript_create_array(par->heap, 0);
      if (!source_val.value || !tokens_val.value) {
         par->tok.error = "out of memory";
         goto error;
      }

      par->heap->data[source_val.value].is_string = 1;

      old_line = par->tok.line;
      if (!extract_tokens(&par->tok, par->heap, tokens_val, 0)) {
         par->tok.error = "out of memory";
         goto error;
      }
      if (*par->tok.cur != '\0') {
         par->tok.error = "syntax error";
         goto error;
      }
      par->tok.line = old_line;

      fixscript_ref(par->heap, source_val);
      fixscript_ref(par->heap, tokens_val);
      par->tokens_src_val = source_val;
      par->tokens_arr_val = tokens_val;
   }

   fname_val = fixscript_create_string(par->heap, par->fname, -1);
   if (!fname_val.value) {
      par->tok.error = "out of memory";
      goto error;
   }

   fixscript_run(par->heap, script, "process_tokens#3", &error_val, fname_val, tokens_val, source_val);
   if (error_val.value) {
      if (error_msg && *error_msg == NULL) {
         fixscript_to_string(par->heap, error_val, 1, &s, NULL);
         *error_msg = string_format("%s(%d): %s\n", par->fname, par->tok.line, s);
         free(s);
      }
      goto error;
   }

   err = fixscript_get_array_length(par->heap, tokens_val, &len);
   if (err) {
      par->tok.error = fixscript_get_error_msg(err);
      goto error;
   }
   if (len % TOK_SIZE != 0) {
      par->tok.error = "invalid token array length (must be divisible by token size)";
      goto error;
   }

   new_tokens_arr = malloc(len * sizeof(Value));
   if (!new_tokens_arr) {
      par->tok.error = "out of memory";
      goto error;
   }

   err = fixscript_get_array_range(par->heap, tokens_val, 0, len, new_tokens_arr);
   if (err) {
      par->tok.error = fixscript_get_error_msg(err);
      goto error;
   }
   new_tokens_end = new_tokens_arr + len;

   err = fixscript_get_array_length(par->heap, source_val, &len);
   if (err) {
      par->tok.error = fixscript_get_error_msg(err);
      goto error;
   }

   for (cur_token = new_tokens_arr; cur_token < new_tokens_end; cur_token += TOK_SIZE) {
      if (cur_token[TOK_off].value < 0 || cur_token[TOK_len].value < 1 || (int64_t)cur_token[TOK_off].value + (int64_t)cur_token[TOK_len].value > (int64_t)len) {
         par->tok.error = "invalid token offset or length";
         goto error;
      }
   }

   new_tokens_src = malloc(len + 1);
   if (!new_tokens_src) {
      par->tok.error = "out of memory";
      goto error;
   }

   err = fixscript_get_array_bytes(par->heap, source_val, 0, len, new_tokens_src);
   if (!err) {
      for (i=0; i<len; i++) {
         if (new_tokens_src[i] == 0) {
            err = FIXSCRIPT_ERR_INVALID_NULL_STRING;
            break;
         }
      }
   }
   if (err) {
      par->tok.error = fixscript_get_error_msg(err);
      goto error;
   }

   new_tokens_src[len] = 0;

   par->tok.tokens_src = new_tokens_src;
   par->tok.cur_token = new_tokens_arr;
   par->tok.tokens_end = new_tokens_end;
   par->tok.again = 0;

   free(par->tokens_src);
   free(par->tokens_arr);
   par->tokens_src = new_tokens_src;
   par->tokens_arr = new_tokens_arr;
   par->tokens_end = new_tokens_end;
   new_tokens_src = NULL;
   new_tokens_arr = NULL;
   return 1;

error:
   free(new_tokens_src);
   free(new_tokens_arr);
   return 0;
}


static int parse_import(Parser *par, char **error_msg, int is_use)
{
   char *fname, *s;
   Script *script;
   int i;
   
   if (!expect_type(par, TOK_STRING, "expected script name")) return 0;
   
   if (!par->load_func) {
      par->tok.error = "can't import scripts with no load script callback defined";
      return 0;
   }

   if (par->heap->cur_import_recursion >= MAX_IMPORT_RECURSION) {
      par->tok.error = "maximum import recursion limit reached";
      return 0;
   }
   
   fname = get_token_string(&par->tok);
   for (i=strlen(fname)-1; i>=0; i--) {
      if ((unsigned char)fname[i] == 0xFF) {
         par->tok.error = "invalid import script name";
         return 0;
      }
   }
   script = par->load_func(par->heap, fname, error_msg, par->load_data);
   free(fname);
   if (!script) {
      return 0;
   }

   if (is_use) {
      if (!parse_use_inner(par, script, error_msg)) return 0;
   }
   else {
      for (i=0; i<par->script->imports.len; i++) {
         if (par->script->imports.data[i] == script) {
            par->tok.error = "duplicate import";
            return 0;
         }
      }

      dynarray_add(&par->script->imports, script);

      if (expect_symbol(par, ':', NULL)) {
         if (!expect_type(par, TOK_IDENT, "expected identifier")) return 0;

         s = string_dup(par->tok.value, par->tok.len);
         string_hash_set(&par->import_aliases, s, script);
      }
   }

   if (!expect_symbol(par, ';', "expected ';'")) return 0;
   return 1;
}


static int parse_constant_define_inner(Parser *par, int *inc_value)
{
   int local = 0, conflict;
   Value value;
   char *name = NULL, *s;
   Constant *constant, *prev_constant, *ref_constant = NULL;
   Script *script = NULL;

   if (expect_symbol(par, '@', NULL)) {
      local = 1;
   }

   if (!expect_type(par, TOK_IDENT, "expected identifier")) goto error;

   name = string_dup(par->tok.value, par->tok.len);

   if (inc_value) {
      if (expect_symbol(par, '=', NULL)) {
         if (expect_type(par, TOK_IDENT, NULL)) {
            s = string_dup(par->tok.value, par->tok.len);
            script = NULL;

            if (expect_symbol(par, ':', NULL)) {
               script = string_hash_get(&par->import_aliases, s);

               if (script) {
                  free(s);

                  if (!expect_type(par, TOK_IDENT, "expected identifier")) return 0;
                  s = string_dup(par->tok.value, par->tok.len);
               }
               else {
                  undo_token(&par->tok);
               }
            }

            constant = find_constant(script? script : par->script, s, script != NULL, &conflict, &script);
            free(s);
            if (conflict) {
               par->tok.error = "declaration of constant in multiple imports";
               return 0;
            }

            if (!constant) {
               par->tok.error = "unknown constant name";
               return 0;
            }

            if (!fixscript_is_int(constant->value)) {
               if (!par->tok.error) {
                  par->tok.error = "expected integer constant";
               }
               goto error;
            }

            value = constant->value;
            *inc_value = value.value;
            if (constant != &zero_const && constant != &one_const) {
               ref_constant = constant;
            }
         }
         else {
            if (!parse_constant(par, &value, 1)) {
               if (!par->tok.error) {
                  par->tok.error = "expected integer constant";
               }
               goto error;
            }
            *inc_value = value.value;
         }
      }
      else {
         if (*inc_value == INT_MAX) {
            par->tok.error = "integer overflow in autoincrement constant";
            goto error;
         }
         value = fixscript_int(++(*inc_value));
      }
   }
   else {
      if (!expect_symbol(par, '=', "expected '='")) goto error;

      if (expect_type(par, TOK_IDENT, NULL)) {
         s = string_dup(par->tok.value, par->tok.len);
         script = NULL;

         if (expect_symbol(par, ':', NULL)) {
            script = string_hash_get(&par->import_aliases, s);

            if (script) {
               free(s);

               if (!expect_type(par, TOK_IDENT, "expected identifier")) return 0;
               s = string_dup(par->tok.value, par->tok.len);
            }
            else {
               undo_token(&par->tok);
            }
         }

         constant = find_constant(script? script : par->script, s, script != NULL, &conflict, &script);
         free(s);
         if (conflict) {
            par->tok.error = "declaration of constant in multiple imports";
            return 0;
         }

         if (!constant) {
            par->tok.error = "unknown constant name";
            return 0;
         }

         value = constant->value;
         if (constant != &zero_const && constant != &one_const) {
            ref_constant = constant;
         }
      }
      else {
         if (!parse_constant(par, &value, 0)) {
            if (!par->tok.error) {
               par->tok.error = "expected integer, float or string constant";
            }
            goto error;
         }
      }
   }

   constant = malloc(sizeof(Constant));
   constant->value = value;
   constant->local = local;
   constant->ref_script = script;
   constant->ref_constant = ref_constant;

   prev_constant = string_hash_set(&par->script->constants, name, constant);
   name = NULL;

   if (prev_constant) {
      free(prev_constant);
      par->tok.error = "duplicate constant";
      goto error;
   }

   return 1;

error:
   free(name);
   return 0;
}


static int parse_constant_define(Parser *par)
{
   int inc_value;

   if (expect_symbol(par, '{', NULL)) {
      inc_value = -1;
      do {
         if (!parse_constant_define_inner(par, &inc_value)) return 0;
      }
      while (expect_symbol(par, ',', NULL));

      if (!expect_symbol(par, '}', "expected ',' or '}'")) return 0;
   }
   else {
      if (!parse_constant_define_inner(par, NULL)) return 0;
   }

   if (!expect_symbol(par, ';', "expected ';'")) return 0;
   return 1;
}


static int parse_local_var(Parser *par)
{
   Value locals = (Value) { LOCALS_IDX, 1 };
   char *name;
   int err, idx=0, local;

   do {
      local = 0;
      if (expect_symbol(par, '@', NULL)) {
         local = 1;
      }

      if (!expect_type(par, TOK_IDENT, "expected identifier")) return 0;

      name = string_dup(par->tok.value, par->tok.len);
      err = fixscript_get_array_length(par->heap, locals, &idx);
      if (!err) {
         err = fixscript_set_array_length(par->heap, locals, idx+1);
      }
      if (err) {
         par->tok.error = "internal error: locals index assign";
         return 0;
      }
      if (local) {
         idx = -idx;
      }

      if (string_hash_set(&par->script->locals, name, (void *)(intptr_t)idx)) {
         par->tok.error = "duplicate local variable";
         return 0;
      }
   }
   while (expect_symbol(par, ',', NULL));

   if (!expect_symbol(par, ';', "expected ';'")) return 0;
   return 1;
}


static Function *find_function(Script *script, const char *name, int used_import_alias, int *conflict)
{
   Function *func, *found_func = NULL;
   Script *s;
   int i;
   
   *conflict = 0;

   func = string_hash_get(&script->functions, name);
   if (func) {
      if (used_import_alias && func->local) {
         return NULL;
      }
      return func;
   }

   if (!used_import_alias) {
      for (i=0; i<script->imports.len; i++) {
         s = script->imports.data[i];
         func = string_hash_get(&s->functions, name);
         if (func && !func->local) {
            if (found_func) {
               *conflict = 1;
               return NULL;
            }
            found_func = func;
         }
      }
   }

   return found_func;
}


static Constant *find_constant(Script *script, const char *name, int used_import_alias, int *conflict, Script **script_out)
{
   Constant *constant, *found_constant = NULL;
   Script *s;
   int i;
   
   *conflict = 0;

   constant = string_hash_get(&script->constants, name);
   if (constant) {
      if (used_import_alias && constant->local) {
         return NULL;
      }
      if (script_out) {
         *script_out = script;
      }
      return constant;
   }

   if (!used_import_alias) {
      for (i=0; i<script->imports.len; i++) {
         s = script->imports.data[i];
         constant = string_hash_get(&s->constants, name);
         if (constant && !constant->local) {
            if (found_constant) {
               *conflict = 1;
               return NULL;
            }
            if (script_out) {
               *script_out = s;
            }
            found_constant = constant;
         }
      }
   }
   
   if (found_constant) {
      return found_constant;
   }

   if (script_out) {
      *script_out = script;
   }

   if (!strcmp(name, "null") || !strcmp(name, "false")) {
      return (Constant *)&zero_const;
   }
   if (!strcmp(name, "true")) {
      return (Constant *)&one_const;
   }

   return NULL;
}


static int find_local_var(Script *script, const char *name, int used_import_alias, int *conflict)
{
   Script *s;
   int i, local_var, found_local_var = 0;
   
   *conflict = 0;

   local_var = (intptr_t)string_hash_get(&script->locals, name);
   if (local_var) {
      if (used_import_alias && local_var < 0) {
         return 0;
      }
      return local_var < 0? -local_var : local_var;
   }

   if (!used_import_alias) {
      for (i=0; i<script->imports.len; i++) {
         s = script->imports.data[i];
         local_var = (intptr_t)string_hash_get(&s->locals, name);
         if (local_var > 0) {
            if (found_local_var) {
               *conflict = 1;
               return 0;
            }
            found_local_var = local_var;
         }
      }
   }

   return found_local_var;
}


static int parse_variable_or_function(Parser *par, Script *script, const char *name, int weak_call)
{
   Constant *constant;
   Function *func = NULL;
   NativeFunction *nfunc;
   char *s, *func_name = NULL;
   int var_stack_pos, local_var, num, value;
   int intrinsic_type, num_args, needs_line_info, is_iround, conflict, has_float_shorthand;

   if (!weak_call) {
      if (!script) {
         var_stack_pos = (intptr_t)string_hash_get(&par->variables, name);
         if (var_stack_pos) {
            buf_append_load(par, var_stack_pos - par->stack_pos);
            par->stack_pos++;
            return 1;
         }
      }

      local_var = find_local_var(script? script : par->script, name, script != NULL, &conflict);
      if (conflict) {
         par->tok.error = "declaration of local variable in multiple imports";
         return 0;
      }
      if (local_var) {
         buf_append_load_local_var(par, local_var);
         par->stack_pos++;
         return 1;
      }

      constant = find_constant(script? script : par->script, name, script != NULL, &conflict, NULL);
      if (conflict) {
         par->tok.error = "declaration of constant in multiple imports";
         return 0;
      }
      if (constant) {
         if (fixscript_is_int(constant->value)) {
            buf_append_const(par, constant->value.value);
         }
         else if (fixscript_is_float(constant->value)) {
            buf_append_const_float(par, constant->value.value);
         }
         else {
            buf_append_const(par, constant->value.value);
            buf_append(par, BC_CONST_STRING);
         }
         par->stack_pos++;
         return 1;
      }
   }
   else {
      if (!expect_symbol(par, '(', "expected '('")) return 0;
      undo_token(&par->tok);
   }

   if (!weak_call) {
      intrinsic_type = -1;
      needs_line_info = 0;
      is_iround = 0;
      has_float_shorthand = 0;

      switch (strlen(name)) {
         case 2:
            if (!strcmp(name, "ln")) { intrinsic_type = BC_EXT_LN; num_args = 1; break; }
            break;

         case 3:
            if (!strcmp(name, "min")) { intrinsic_type = BC_EXT_MIN; num_args = 2; break; }
            if (!strcmp(name, "max")) { intrinsic_type = BC_EXT_MAX; num_args = 2; break; }
            if (!strcmp(name, "abs")) { intrinsic_type = BC_EXT_ABS; num_args = 1; needs_line_info = 1; break; }
            if (!strcmp(name, "int")) { intrinsic_type = BC_EXT_INT; num_args = 1; break; }
            if (!strcmp(name, "pow")) { intrinsic_type = BC_EXT_POW; num_args = 2; break; }
            if (!strcmp(name, "exp")) { intrinsic_type = BC_EXT_EXP; num_args = 1; break; }
            if (!strcmp(name, "sin")) { intrinsic_type = BC_EXT_SIN; num_args = 1; break; }
            if (!strcmp(name, "cos")) { intrinsic_type = BC_EXT_COS; num_args = 1; break; }
            if (!strcmp(name, "tan")) { intrinsic_type = BC_EXT_TAN; num_args = 1; break; }
            break;

         case 4:
            if (!strcmp(name, "fmin")) { intrinsic_type = BC_EXT_FMIN; num_args = 2; break; }
            if (!strcmp(name, "fmax")) { intrinsic_type = BC_EXT_FMAX; num_args = 2; break; }
            if (!strcmp(name, "fabs")) { intrinsic_type = BC_EXT_FABS; num_args = 1; break; }
            if (!strcmp(name, "ceil")) { intrinsic_type = BC_EXT_CEIL; num_args = 1; break; }
            if (!strcmp(name, "sqrt")) { intrinsic_type = BC_EXT_SQRT; num_args = 1; break; }
            if (!strcmp(name, "cbrt")) { intrinsic_type = BC_EXT_CBRT; num_args = 1; break; }
            if (!strcmp(name, "log2")) { intrinsic_type = BC_EXT_LOG2; num_args = 1; break; }
            if (!strcmp(name, "asin")) { intrinsic_type = BC_EXT_ASIN; num_args = 1; break; }
            if (!strcmp(name, "acos")) { intrinsic_type = BC_EXT_ACOS; num_args = 1; break; }
            if (!strcmp(name, "atan")) { intrinsic_type = BC_EXT_ATAN; num_args = 1; break; }
            break;

         case 5:
            if (!strcmp(name, "add32")) { intrinsic_type = 0x100 + BC_ADD_MOD; num_args = 2; break; }
            if (!strcmp(name, "sub32")) { intrinsic_type = 0x100 + BC_SUB_MOD; num_args = 2; break; }
            if (!strcmp(name, "mul32")) { intrinsic_type = 0x100 + BC_MUL_MOD; num_args = 2; break; }
            if (!strcmp(name, "float")) { intrinsic_type = BC_EXT_FLOAT; num_args = 1; break; }
            if (!strcmp(name, "floor")) { intrinsic_type = BC_EXT_FLOOR; num_args = 1; break; }
            if (!strcmp(name, "round")) { intrinsic_type = BC_EXT_ROUND; num_args = 1; break; }
            if (!strcmp(name, "log10")) { intrinsic_type = BC_EXT_LOG10; num_args = 1; break; }
            if (!strcmp(name, "atan2")) { intrinsic_type = BC_EXT_ATAN2; num_args = 2; break; }
            if (!strcmp(name, "fconv")) { intrinsic_type = BC_EXT_DBL_CONV_DOWN; num_args = 2; break; }
            break;

         case 6:
            if (!strcmp(name, "length")) { intrinsic_type = 0x100 + BC_LENGTH; num_args = 1; needs_line_info = 1; break; }
            if (!strcmp(name, "iround")) { intrinsic_type = BC_EXT_ROUND; num_args = 1; is_iround = 1; break; }
            if (!strcmp(name, "is_int")) { intrinsic_type = BC_EXT_IS_INT; num_args = 1; break; }
            break;

         case 7:
            if (!strcmp(name, "is_hash")) { intrinsic_type = BC_EXT_IS_HASH; num_args = 1; break; }
            if (!strcmp(name, "fcmp_lt")) { intrinsic_type = BC_EXT_DBL_CMP_LT; num_args = 4; has_float_shorthand = 1; break; }
            if (!strcmp(name, "fcmp_le")) { intrinsic_type = BC_EXT_DBL_CMP_LE; num_args = 4; has_float_shorthand = 1; break; }
            if (!strcmp(name, "fcmp_gt")) { intrinsic_type = BC_EXT_DBL_CMP_GT; num_args = 4; has_float_shorthand = 1; break; }
            if (!strcmp(name, "fcmp_ge")) { intrinsic_type = BC_EXT_DBL_CMP_GE; num_args = 4; has_float_shorthand = 1; break; }
            if (!strcmp(name, "fcmp_eq")) { intrinsic_type = BC_EXT_DBL_CMP_EQ; num_args = 4; has_float_shorthand = 1; break; }
            if (!strcmp(name, "fcmp_ne")) { intrinsic_type = BC_EXT_DBL_CMP_NE; num_args = 4; has_float_shorthand = 1; break; }
            break;

         case 8:
            if (!strcmp(name, "is_float")) { intrinsic_type = BC_EXT_IS_FLOAT; num_args = 1; break; }
            if (!strcmp(name, "is_array")) { intrinsic_type = BC_EXT_IS_ARRAY; num_args = 1; break; }
            break;

         case 9:
            if (!strcmp(name, "is_string")) { intrinsic_type = BC_EXT_IS_STRING; num_args = 1; break; }
            if (!strcmp(name, "is_shared")) { intrinsic_type = BC_EXT_IS_SHARED; num_args = 1; break; }
            if (!strcmp(name, "is_handle")) { intrinsic_type = BC_EXT_IS_HANDLE; num_args = 1; break; }
            break;

         case 10:
            if (!strcmp(name, "is_funcref")) { intrinsic_type = BC_EXT_IS_FUNCREF; num_args = 1; break; }
            if (!strcmp(name, "is_weakref")) { intrinsic_type = BC_EXT_IS_WEAKREF; num_args = 1; break; }
            break;
      }

      if (intrinsic_type != -1) {
         if (!expect_symbol(par, '(', "expected '('")) return 0;

         num = 0;
         if (!expect_symbol(par, ')', NULL)) {
            do {
               if (!parse_expression(par)) return 0;
               num++;
            }
            while (expect_symbol(par, ',', NULL));

            if (!expect_symbol(par, ')', "expected ')' or ','")) return 0;
         }

         if ((intrinsic_type == 0x100 + BC_ADD_MOD || intrinsic_type == 0x100 + BC_SUB_MOD) && num == 3) {
            buf_append(par, BC_EXTENDED);
            buf_append(par, intrinsic_type == 0x100 + BC_ADD_MOD? BC_EXT_ADD32 : BC_EXT_SUB32);
            buf_append(par, BC_POP);
            par->stack_pos -= num - 1;
            return 1;
         }

         if (has_float_shorthand) {
            if (num != num_args && num != num_args-1) {
               par->tok.error = "improper number of function parameters";
               return 0;
            }
         }
         else if (num != num_args) {
            par->tok.error = "improper number of function parameters";
            return 0;
         }

         if (intrinsic_type >= 0x100) {
            buf_append(par, intrinsic_type - 0x100);
         }
         else {
            if (has_float_shorthand && num == num_args-1) {
               buf_append(par, BC_EXTENDED);
               buf_append(par, BC_EXT_DBL_CONV_UP);
            }
            buf_append(par, BC_EXTENDED);
            buf_append(par, intrinsic_type);
            if (is_iround) {
               buf_append(par, BC_EXTENDED);
               buf_append(par, BC_EXT_INT);
            }
            par->last_buf_pos--;
         }
         if (needs_line_info) {
            add_line_info(par);
         }
         par->stack_pos -= num - 1;
         return 1;
      }
   }
   
   if (expect_symbol(par, '(', NULL)) {
      buf_append_const(par, 0);
      par->stack_pos++;

      num = 0;
      if (!expect_symbol(par, ')', NULL)) {
         do {
            if (!parse_expression(par)) return 0;
            num++;
         }
         while (expect_symbol(par, ',', NULL));

         if (!expect_symbol(par, ')', "expected ')' or ','")) return 0;
      }

      s = string_format("%s#%d", name, num);
      nfunc = string_hash_get(&par->heap->native_functions_hash, s);
      if (!weak_call) {
         func = find_function(script? script : par->script, s, script != NULL, &conflict);
         if (func) nfunc = NULL;
         if (conflict) {
            free(s);
            par->tok.error = "declaration of function in multiple imports";
            return 0;
         }
      }

      func_name = s;
      par->stack_pos -= num;

      if (weak_call && !nfunc) {
         free(func_name);

         s = string_format("native function %s#%d is not present (%s:%d)", name, num, par->fname, par->tok.line);
         value = get_const_string(par, s);
         free(s);
         if (!value) return 0;

         buf_append_pop(par, 1+num);
         buf_append_const(par, 0);
         buf_append_const(par, value);
         buf_append(par, BC_CONST_STRING);
         buf_append(par, BC_RETURN2);
         return 1;
      }

      if (nfunc) {
         if (weak_call) {
            free(func_name);
            buf_append_const(par, nfunc->id);
         }
         else {
            if (par->long_func_refs) {
               buf_append(par, BC_CONST_I32);
               dynarray_add(&par->func_refs, func_name);
               dynarray_add(&par->func_refs, (void *)(intptr_t)par->buf_len);
               dynarray_add(&par->func_refs, (void *)(intptr_t)par->tok.line);
               buf_append_int(par, nfunc->id);
            }
            else {
               buf_append_const(par, nfunc->id);
               dynarray_add(&par->func_refs, func_name);
               dynarray_add(&par->func_refs, (void *)0);
               dynarray_add(&par->func_refs, (void *)0);
            }
         }
         buf_append(par, BC_CALL_NATIVE);
      }
      else {
         if (func) {
            if (!script && func->script != par->script) {
               if (par->long_func_refs) {
                  buf_append(par, BC_CONST_I32);
                  dynarray_add(&par->func_refs, func_name);
                  dynarray_add(&par->func_refs, (void *)(intptr_t)par->buf_len);
                  dynarray_add(&par->func_refs, (void *)(intptr_t)par->tok.line);
                  buf_append_int(par, func->id);
               }
               else {
                  buf_append_const(par, func->id);
                  dynarray_add(&par->func_refs, func_name);
                  dynarray_add(&par->func_refs, (void *)0);
                  dynarray_add(&par->func_refs, (void *)0);
               }
            }
            else {
               buf_append_const(par, func->id);
               free(func_name);
            }
         }
         else {
            buf_append(par, BC_CONST_I32);
            dynarray_add(&par->func_refs, func_name);
            dynarray_add(&par->func_refs, (void *)(intptr_t)(par->buf_len | (1<<31)));
            dynarray_add(&par->func_refs, (void *)(intptr_t)par->tok.line);
            buf_append_int(par, 0);
         }
         buf_append(par, BC_CALL_DIRECT);
      }
      add_line_info(par);
      return 1;
   }

   par->tok.error = "undefined variable name";
   return 0;
}


static int parse_extended_float_operator(Parser *par)
{
   int type = -1;

   if (!parse_primary_expression(par)) return 0;

   for (;;) {
      if (type != -1 && expect_symbol(par, '}', NULL)) {
         break;
      }
      
      if (expect_symbol(par, '+', NULL) || expect_symbol(par, '-', NULL)) {
         if (type != -1 && type != BC_FLOAT_ADD && type != BC_FLOAT_SUB) {
            par->tok.error = "can't mix additive and multiplicative operations in a single extended operator";
            return 0;
         }
         type = par->tok.value[0] == '+'? BC_FLOAT_ADD : BC_FLOAT_SUB;
         if (!parse_primary_expression(par)) return 0;
         buf_append(par, type);
         par->stack_pos--;
         continue;
      }

      if (expect_type(par, TOK_NUMBER, NULL) || expect_type(par, TOK_FLOAT_NUMBER, NULL)) {
         if (par->tok.value[0] == '-') {
            undo_token(&par->tok);
            if (type != -1 && type != BC_FLOAT_ADD && type != BC_FLOAT_SUB) {
               par->tok.error = "can't mix additive and multiplicative operations in a single extended operator";
               return 0;
            }
            type = BC_FLOAT_ADD;
            if (!parse_primary_expression(par)) return 0;
            buf_append(par, type);
            par->stack_pos--;
            continue;
         }
         undo_token(&par->tok);
      }

      if (expect_symbol(par, '*', NULL) || expect_symbol(par, '/', NULL)) {
         if (type != -1 && type != BC_FLOAT_MUL && type != BC_FLOAT_DIV) {
            par->tok.error = "can't mix additive and multiplicative operations in a single extended operator";
            return 0;
         }
         type = par->tok.value[0] == '*'? BC_FLOAT_MUL : BC_FLOAT_DIV;
         if (!parse_primary_expression(par)) return 0;
         buf_append(par, type);
         par->stack_pos--;
         continue;
      }

      if (type == -1 && expect_symbol(par, '<', NULL)) {
         if (!parse_primary_expression(par)) return 0;
         buf_append(par, BC_FLOAT_LT);
         par->stack_pos--;
         if (!expect_symbol(par, '}', NULL)) return 0;
         return 1;
      }

      if (type == -1 && expect_symbol2(par, '<', '=', NULL)) {
         if (!parse_primary_expression(par)) return 0;
         buf_append(par, BC_FLOAT_LE);
         par->stack_pos--;
         if (!expect_symbol(par, '}', NULL)) return 0;
         return 1;
      }

      if (type == -1 && expect_symbol(par, '>', NULL)) {
         if (!parse_primary_expression(par)) return 0;
         buf_append(par, BC_FLOAT_GT);
         par->stack_pos--;
         if (!expect_symbol(par, '}', NULL)) return 0;
         return 1;
      }

      if (type == -1 && expect_symbol2(par, '>', '=', NULL)) {
         if (!parse_primary_expression(par)) return 0;
         buf_append(par, BC_FLOAT_GE);
         par->stack_pos--;
         if (!expect_symbol(par, '}', NULL)) return 0;
         return 1;
      }

      if (type == -1 && expect_symbol2(par, '=', '=', NULL)) {
         if (!parse_primary_expression(par)) return 0;
         buf_append(par, BC_FLOAT_EQ);
         par->stack_pos--;
         if (!expect_symbol(par, '}', NULL)) return 0;
         return 1;
      }

      if (type == -1 && expect_symbol2(par, '!', '=', NULL)) {
         if (!parse_primary_expression(par)) return 0;
         buf_append(par, BC_FLOAT_NE);
         par->stack_pos--;
         if (!expect_symbol(par, '}', NULL)) return 0;
         return 1;
      }

      if (type == BC_FLOAT_ADD || type == BC_FLOAT_SUB) {
         par->tok.error = "expected '+' or '-'";
      }
      else if (type == BC_FLOAT_MUL || type == BC_FLOAT_DIV) {
         par->tok.error = "expected '*' or '/'";
      }
      else {
         par->tok.error = "expected '+', '-', '*', '/', '<', '<=', '>', '>=', '==' or '!='";
      }
      return 0;
   }

   return 1;
}


static int parse_extended_operator(Parser *par)
{
   Tokenizer save_tok;
   int num = 0, type = -1, level = 0;

   save_tok = par->tok;
   if (expect_symbol(par, '}', NULL)) {
      type = ET_HASH;
   }
   else if (expect_symbol(par, '=', NULL)) {
      type = ET_BLOCK;
   }
   else {
      while (next_token(&par->tok)) {
         switch (par->tok.type) {
            case TOK_SYMBOL:
               switch (par->tok.value[0]) {
                  case '(':
                  case '{':
                  case '[':
                     level++;
                     break;

                  case ')':
                  case '}':
                  case ']':
                     if (--level < 0) goto end;
                     if (level == 0 && par->tok.value[0] == '}') {
                        if (expect_symbol(par, '=', NULL)) {
                           type = ET_BLOCK;
                           goto end;
                        }
                     }
                     break;

                  case '+':
                  case '-':
                  case '*':
                  case '/':
                  case '<':
                  case '>':
                     if (level == 0 && type == -1) {
                        type = ET_FLOAT;
                     }
                     break;

                  case ':':
                     if (level == 0 && type == -1) {
                        type = ET_HASH;
                     }
                     break;

                  case ',':
                     if (level == 0 && (type == -1 || type == ET_FLOAT)) {
                        type = ET_STRING;
                     }
                     break;

                  case ';':
                     if (level == 0 && expect_symbol(par, '=', NULL)) {
                        type = ET_BLOCK;
                        goto end;
                     }
                     break;
               }
               break;

            case TOK_SYMBOL2:
               switch (par->tok.value[0]) {
                  case '<':
                  case '>':
                  case '=':
                  case '!':
                     if (level == 0 && par->tok.value[1] == '=' && type == -1) {
                        type = ET_FLOAT;
                     }
                     break;
               }
               break;

            case TOK_NUMBER:
            case TOK_FLOAT_NUMBER:
               if (level == 0 && par->tok.value[0] == '-' && type == -1) {
                  type = ET_FLOAT;
               }
               break;
         }
      }
   }
end:
   par->tok = save_tok;
   if (type == -1) {
      type = ET_STRING;
   }

   if (type == ET_FLOAT) {
      return parse_extended_float_operator(par);
   }

   if (type == ET_BLOCK) {
      return parse_block(par, BT_EXPR);
   }

   for (;;) {
      if (expect_symbol(par, '}', NULL)) {
         break;
      }

      if (num > 0) {
         if (!expect_symbol(par, ',', "expected ','")) return 0;
      }

      if (!parse_expression(par)) return 0;

      if (type == ET_HASH) {
         if (!expect_symbol(par, ':', "expected ':'")) return 0;
         if (!parse_expression(par)) return 0;
      }
      num++;
   }

   if (type == ET_HASH) {
      buf_append_const(par, num);
      buf_append(par, BC_CREATE_HASH);
      add_line_info(par);
      par->stack_pos -= num*2-1;
   }
   else if (type == ET_STRING) {
      buf_append_const(par, num);
      buf_append(par, BC_STRING_CONCAT);
      add_line_info(par);
      par->stack_pos -= num-1;
   }
   else {
      par->tok.error = "internal error: unhandled type of extended operator";
      return 0;
   }
   
   return 1;
}


static int parse_constant(Parser *par, Value *value, int int_only)
{
   char *s, *end;
   long int val;
   unsigned long int uval;
   float fval;
   int ret, valid;

   if (expect_type(par, TOK_NUMBER, NULL)) {
      s = string_dup(par->tok.value, par->tok.len);
      valid = 1;
      errno = 0;
      val = strtol(s, &end, 10);
      if (errno != 0 || *end != '\0') {
         valid = 0;
      }
      else if (sizeof(long int) > sizeof(int)) {
         if (val < INT_MIN || val > INT_MAX) {
            valid = 0;
         }
      }
      free(s);

      if (!valid) {
         par->tok.error = "integer constant out of range";
         return 0;
      }
      *value = fixscript_int(val);
      return 1;
   }

   if (expect_type(par, TOK_HEX_NUMBER, NULL)) {
      s = string_dup(par->tok.value, par->tok.len);
      valid = 1;
      errno = 0;
      uval = strtoul(s, &end, 16);
      if (errno != 0 || *end != '\0') {
         valid = 0;
      }
      else if (sizeof(unsigned long int) > sizeof(unsigned int)) {
         if (uval > UINT_MAX) {
            valid = 0;
         }
      }
      free(s);

      if (!valid) {
         par->tok.error = "hexadecimal constant out of range";
         return 0;
      }
      *value = fixscript_int(uval);
      return 1;
   }

   if (expect_type(par, TOK_CHAR, NULL)) {
      *value = fixscript_int(get_token_char(&par->tok));
      if (value->value < 0) {
         par->tok.error = "internal error while parsing char";
         return 0;
      }
      return 1;
   }
   
   if (int_only) {
      return 0;
   }

   if (expect_type(par, TOK_FLOAT_NUMBER, NULL)) {
      s = string_dup(par->tok.value, par->tok.len);
      fval = (float)strtod(s, &end);
      valid = (*end == '\0');
      free(s);

      if (!valid) {
         par->tok.error = "invalid float constant";
         return 0;
      }
      *value = fixscript_float(fval);
      return 1;
   }

   if (expect_type(par, TOK_STRING, NULL)) {
      s = get_token_string(&par->tok);
      ret = get_const_string(par, s);
      free(s);
      if (!ret) return 0;
      
      *value = (Value) { ret, 1 };
      return 1;
   }

   return 0;
}


static int parse_primary_prefix_expression(Parser *par)
{
   char *s;
   int num, ret, weak_call, conflict;
   Function *func;
   Script *script;
   Value value;
   Tokenizer save_tok;

   if (parse_constant(par, &value, 0)) {
      if (fixscript_is_int(value)) {
         buf_append_const(par, value.value);
      }
      else if (fixscript_is_float(value)) {
         buf_append_const_float(par, value.value);
      }
      else {
         buf_append_const(par, value.value);
         buf_append(par, BC_CONST_STRING);
      }
      par->stack_pos++;
      return 1;
   }
   else if (par->tok.error) {
      return 0;
   }

   if (expect_symbol(par, '(', NULL)) {
      if (!parse_expression(par)) return 0;
      if (!expect_symbol(par, ')', "expected ')'")) return 0;
      return 1;
   }

   if (expect_type(par, TOK_IDENT, NULL) || expect_symbol(par, '@', NULL)) {
      weak_call = (par->tok.type == TOK_SYMBOL);
      if (weak_call) {
         if (!expect_type(par, TOK_IDENT, "expected identifier")) return 0;
      }

      s = string_dup(par->tok.value, par->tok.len);
      script = NULL;

      save_tok = par->tok;
      if (expect_symbol(par, ':', NULL) && (expect_type(par, TOK_IDENT, NULL) || expect_type(par, TOK_FUNC_REF, NULL))) {
         script = string_hash_get(&par->import_aliases, s);
      }
      par->tok = save_tok;

      if (script) {
         free(s);

         if (!expect_symbol(par, ':', "internal error when parsing import alias")) return 0;

         if (expect_type(par, TOK_FUNC_REF, NULL)) {
            s = string_dup(par->tok.value, par->tok.len);
            func = find_function(script, s, 1, &conflict);
            free(s);
            if (conflict) {
               par->tok.error = "declaration of function in multiple imports";
               return 0;
            }

            if (!func) {
               par->tok.error = "undefined function name";
               return 0;
            }

            buf_append_const_float(par, FUNC_REF_OFFSET + func->id);
            par->stack_pos++;
            return 1;
         }

         if (!expect_type(par, TOK_IDENT, "expected identifier")) return 0;
         s = string_dup(par->tok.value, par->tok.len);
      }

      ret = parse_variable_or_function(par, script, s, weak_call);
      free(s);
      return ret;
   }

   if (expect_type(par, TOK_FUNC_REF, NULL)) {
      s = string_dup(par->tok.value, par->tok.len);
      func = find_function(par->script, s, 0, &conflict);
      if (conflict) {
         free(s);
         par->tok.error = "declaration of function in multiple imports";
         return 0;
      }

      if (func) {
         if (func->script != par->script) {
            if (par->long_func_refs) {
               buf_append(par, BC_CONST_F32);
               dynarray_add(&par->func_refs, s);
               dynarray_add(&par->func_refs, (void *)(intptr_t)par->buf_len);
               dynarray_add(&par->func_refs, (void *)(intptr_t)(par->tok.line | (1<<31)));
               buf_append_int(par, FUNC_REF_OFFSET + func->id);
               par->last_buf_pos = par->buf_len - 5;
            }
            else {
               buf_append_const_float(par, FUNC_REF_OFFSET + func->id);
               dynarray_add(&par->func_refs, s);
               dynarray_add(&par->func_refs, (void *)0);
               dynarray_add(&par->func_refs, (void *)0);
            }
         }
         else {
            buf_append_const_float(par, FUNC_REF_OFFSET + func->id);
            free(s);
         }
      }
      else {
         buf_append(par, BC_CONST_F32);
         dynarray_add(&par->func_refs, s);
         dynarray_add(&par->func_refs, (void *)(intptr_t)(par->buf_len | (1<<31)));
         dynarray_add(&par->func_refs, (void *)(intptr_t)(par->tok.line | (1<<31)));
         buf_append_int(par, 0);
         par->last_buf_pos = par->buf_len - 5;
      }

      par->stack_pos++;
      return 1;
   }

   if (expect_symbol2(par, '[', ']', NULL)) {
      buf_append_const(par, 0);
      buf_append(par, BC_CREATE_ARRAY);
      add_line_info(par);
      par->stack_pos++;
      return 1;
   }

   if (expect_symbol(par, '[', NULL)) {
      num = 0;
      for (;;) {
         if (expect_symbol(par, ']', NULL)) {
            buf_append_const(par, num);
            buf_append(par, BC_CREATE_ARRAY);
            add_line_info(par);
            par->stack_pos -= num-1;
            break;
         }

         if (num > 0) {
            if (!expect_symbol(par, ',', "expected ','")) return 0;
         }
         if (!parse_expression(par)) return 0;
         num++;
      }
      return 1;
   }

   if (expect_symbol(par, '{', NULL)) {
      return parse_extended_operator(par);
   }

   if (!par->tok.error) {
      par->tok.error = "expected value";
   }
   return 0;
}


static int parse_primary_expression(Parser *par)
{
   Constant *constant;
   Script *script;
   char *s;
   int num, conflict;

   if (!parse_primary_prefix_expression(par)) return 0;
   
   for (;;) {
      if (expect_symbol(par, '(', NULL)) {
         num = 0;
         if (!expect_symbol(par, ')', NULL)) {
            do {
               if (!parse_expression(par)) return 0;
               num++;
            }
            while (expect_symbol(par, ',', NULL));

            if (!expect_symbol(par, ')', "expected ')' or ','")) return 0;
         }

         buf_append_const(par, num);
         buf_append(par, BC_CALL_DYNAMIC);
         add_line_info(par);
         par->stack_pos -= num;
         continue;
      }

      if (expect_symbol(par, '[', NULL)) {
         if (!parse_expression(par)) return 0;
         if (!expect_symbol(par, ']', "expected ']'")) return 0;

         buf_append(par, BC_ARRAY_GET);
         add_line_info(par);
         par->stack_pos--;
         continue;
      }

      if (expect_symbol2(par, '-', '>', NULL)) {
         if (!expect_type(par, TOK_IDENT, "expected named constant")) return 0;

         s = string_dup(par->tok.value, par->tok.len);
         script = NULL;

         if (expect_symbol(par, ':', NULL)) {
            script = string_hash_get(&par->import_aliases, s);

            if (script) {
               free(s);

               if (!expect_type(par, TOK_IDENT, "expected named constant")) return 0;
               s = string_dup(par->tok.value, par->tok.len);
            }
            else {
               undo_token(&par->tok);
            }
         }

         constant = find_constant(script? script : par->script, s, script != NULL, &conflict, NULL);
         free(s);
         if (conflict) {
            par->tok.error = "declaration of constant in multiple imports";
            return 0;
         }

         if (!constant) {
            par->tok.error = "unknown constant name";
            return 0;
         }

         if (!fixscript_is_int(constant->value)) {
            par->tok.error = "constant must be integer";
            return 0;
         }
         
         buf_append_const(par, constant->value.value);
         par->stack_pos++;

         buf_append(par, BC_ARRAY_GET);
         add_line_info(par);
         par->stack_pos--;
         continue;
      }

      if (expect_symbol(par, '{', NULL)) {
         if (!parse_expression(par)) return 0;
         if (!expect_symbol(par, '}', "expected '}'")) return 0;

         buf_append(par, BC_HASH_GET);
         add_line_info(par);
         par->stack_pos--;
         continue;
      }
      break;
   }

   return 1;
}


static int parse_unary_expression(Parser *par)
{
   int inc, value;

   if (expect_symbol(par, '~', NULL)) {
      if (!parse_unary_expression(par)) return 0;
      buf_append(par, BC_BITNOT);
      return 1;
   }

   if (expect_symbol(par, '!', NULL)) {
      if (!parse_unary_expression(par)) return 0;
      buf_append(par, BC_LOGNOT);
      return 1;
   }

   if (expect_symbol(par, '+', NULL)) {
      return parse_unary_expression(par);
   }

   if (expect_symbol(par, '-', NULL)) {
      buf_append_const(par, 0);
      par->stack_pos++;
      if (!parse_unary_expression(par)) return 0;
      buf_append(par, BC_SUB);
      add_line_info(par);
      par->stack_pos--;
      return 1;
   }

   if (expect_symbol2(par, '+', '+', NULL) || expect_symbol2(par, '-', '-', NULL)) {
      inc = (par->tok.value[0] == '+');
      if (!parse_primary_expression(par)) return 0;

      if (buf_is_load(par, par->last_buf_pos, &value)) {
         if (value >= -128 && value < 0) {
            par->buf_len = par->last_buf_pos;
            buf_append(par, inc? BC_INC : BC_DEC);
            buf_append(par, value);
            add_line_info(par);
            buf_append_load(par, value);
         }
         else {
            buf_append_const(par, 1);
            buf_append(par, inc? BC_ADD : BC_SUB);
            add_line_info(par);
            buf_append_load(par, -1);
            buf_append_store(par, value-2);
         }
      }
      else if (buf_is_load_local_var(par, par->last_buf_pos, &value)) {
         buf_append_const(par, 1);
         buf_append(par, inc? BC_ADD : BC_SUB);
         add_line_info(par);
         buf_append_load(par, -1);
         buf_append_store_local_var(par, value);
      }
      else if (par->buf[par->last_buf_pos] == BC_ARRAY_GET) {
         remove_line_info(par);
         par->buf_len = par->last_buf_pos;
         buf_append_load(par, -2);
         buf_append_load(par, -2);
         buf_append(par, BC_ARRAY_GET);
         add_line_info(par);
         buf_append_const(par, 1);
         buf_append(par, inc? BC_ADD : BC_SUB);
         add_line_info(par);
         buf_append(par, BC_ARRAY_SET);
         add_line_info(par);
         buf_append_load(par, 2);
      }
      else if (par->buf[par->last_buf_pos] == BC_HASH_GET) {
         remove_line_info(par);
         par->buf_len = par->last_buf_pos;
         buf_append_load(par, -2);
         buf_append_load(par, -2);
         buf_append(par, BC_HASH_GET);
         add_line_info(par);
         buf_append_const(par, 1);
         buf_append(par, inc? BC_ADD : BC_SUB);
         add_line_info(par);
         buf_append(par, BC_HASH_SET);
         add_line_info(par);
         buf_append_load(par, 2);
      }
      else {
         par->tok.error = "invalid assignment destination";
         return 0;
      }
      return 1;
   }
   
   if (!parse_primary_expression(par)) return 0;

   if (expect_symbol2(par, '+', '+', NULL) || expect_symbol2(par, '-', '-', NULL)) {
      inc = (par->tok.value[0] == '+');

      if (buf_is_load(par, par->last_buf_pos, &value)) {
         if (value-1 >= -128 && value-1 < 0) {
            buf_append(par, inc? BC_INC : BC_DEC);
            buf_append(par, value-1);
            add_line_info(par);
         }
         else {
            buf_append_load(par, -1);
            buf_append_const(par, 1);
            buf_append(par, inc? BC_ADD : BC_SUB);
            add_line_info(par);
            buf_append_store(par, value-2);
         }
      }
      else if (buf_is_load_local_var(par, par->last_buf_pos, &value)) {
         buf_append_load(par, -1);
         buf_append_const(par, 1);
         buf_append(par, inc? BC_ADD : BC_SUB);
         add_line_info(par);
         buf_append_store_local_var(par, value);
      }
      else if (par->buf[par->last_buf_pos] == BC_ARRAY_GET) {
         remove_line_info(par);
         par->buf_len = par->last_buf_pos;
         buf_append_load(par, -2);
         buf_append_load(par, -2);
         buf_append(par, BC_ARRAY_GET);
         add_line_info(par);
         buf_append_load(par, -3);
         buf_append_load(par, -3);
         buf_append_load(par, -3);
         buf_append_const(par, 1);
         buf_append(par, inc? BC_ADD : BC_SUB);
         add_line_info(par);
         buf_append(par, BC_ARRAY_SET);
         add_line_info(par);
         buf_append_store(par, -3);
         buf_append_pop(par, 1);
      }
      else if (par->buf[par->last_buf_pos] == BC_HASH_GET) {
         remove_line_info(par);
         par->buf_len = par->last_buf_pos;
         buf_append_load(par, -2);
         buf_append_load(par, -2);
         buf_append(par, BC_HASH_GET);
         add_line_info(par);
         buf_append_load(par, -3);
         buf_append_load(par, -3);
         buf_append_load(par, -3);
         buf_append_const(par, 1);
         buf_append(par, inc? BC_ADD : BC_SUB);
         add_line_info(par);
         buf_append(par, BC_HASH_SET);
         add_line_info(par);
         buf_append_store(par, -3);
         buf_append_pop(par, 1);
      }
      else {
         par->tok.error = "invalid assignment destination";
         return 0;
      }
      return 1;
   }

   return 1;
}


static int parse_multiplicative_expression(Parser *par)
{
   if (!parse_unary_expression(par)) return 0;

   for (;;) {
      if (expect_symbol(par, '*', NULL)) {
         if (!parse_unary_expression(par)) return 0;
         buf_append(par, BC_MUL);
         add_line_info(par);
         par->stack_pos--;
         continue;
      }
      if (expect_symbol(par, '/', NULL)) {
         if (!parse_unary_expression(par)) return 0;
         buf_append(par, BC_DIV);
         add_line_info(par);
         par->stack_pos--;
         continue;
      }
      if (expect_symbol(par, '%', NULL)) {
         if (!parse_unary_expression(par)) return 0;
         buf_append(par, BC_REM);
         add_line_info(par);
         par->stack_pos--;
         continue;
      }
      break;
   }

   return 1;
}


static int parse_additive_expression(Parser *par)
{
   if (!parse_multiplicative_expression(par)) return 0;
   
   for (;;) {
      if (expect_symbol(par, '+', NULL)) {
         if (!parse_multiplicative_expression(par)) return 0;
         buf_append(par, BC_ADD);
         add_line_info(par);
         par->stack_pos--;
         continue;
      }
      if (expect_symbol(par, '-', NULL)) {
         if (!parse_multiplicative_expression(par)) return 0;
         buf_append(par, BC_SUB);
         add_line_info(par);
         par->stack_pos--;
         continue;
      }
      if (expect_type(par, TOK_NUMBER, NULL) || expect_type(par, TOK_FLOAT_NUMBER, NULL)) {
         if (par->tok.value[0] == '-') {
            undo_token(&par->tok);
            if (!parse_primary_prefix_expression(par)) return 0;
            buf_append(par, BC_ADD);
            add_line_info(par);
            par->stack_pos--;
            continue;
         }
         undo_token(&par->tok);
      }
      break;
   }

   return 1;
}


static int parse_bitwise_expression(Parser *par)
{
   if (!parse_additive_expression(par)) return 0;
   
   for (;;) {
      if (expect_symbol2(par, '<', '<', NULL)) {
         if (!parse_additive_expression(par)) return 0;
         buf_append(par, BC_SHL);
         par->stack_pos--;
         continue;
      }
      if (expect_symbol2(par, '>', '>', NULL)) {
         if (!parse_additive_expression(par)) return 0;
         buf_append(par, BC_SHR);
         par->stack_pos--;
         continue;
      }
      if (expect_symbol3(par, '>', '>', '>', NULL)) {
         if (!parse_additive_expression(par)) return 0;
         buf_append(par, BC_USHR);
         par->stack_pos--;
         continue;
      }
      if (expect_symbol(par, '&', NULL)) {
         if (!parse_additive_expression(par)) return 0;
         buf_append(par, BC_AND);
         par->stack_pos--;
         continue;
      }
      if (expect_symbol(par, '|', NULL)) {
         if (!parse_additive_expression(par)) return 0;
         buf_append(par, BC_OR);
         par->stack_pos--;
         continue;
      }
      if (expect_symbol(par, '^', NULL)) {
         if (!parse_additive_expression(par)) return 0;
         buf_append(par, BC_XOR);
         par->stack_pos--;
         continue;
      }
      break;
   }

   return 1;
}


static int parse_comparison_expression(Parser *par)
{
   if (!parse_bitwise_expression(par)) return 0;
   
   for (;;) {
      if (expect_symbol(par, '<', NULL)) {
         if (!parse_bitwise_expression(par)) return 0;
         buf_append(par, BC_LT);
         par->stack_pos--;
         continue;
      }
      if (expect_symbol2(par, '<', '=', NULL)) {
         if (!parse_bitwise_expression(par)) return 0;
         buf_append(par, BC_LE);
         par->stack_pos--;
         continue;
      }
      if (expect_symbol(par, '>', NULL)) {
         if (!parse_bitwise_expression(par)) return 0;
         buf_append(par, BC_GT);
         par->stack_pos--;
         continue;
      }
      if (expect_symbol2(par, '>', '=', NULL)) {
         if (!parse_bitwise_expression(par)) return 0;
         buf_append(par, BC_GE);
         par->stack_pos--;
         continue;
      }
      if (expect_symbol3(par, '=', '=', '=', NULL)) {
         if (!parse_bitwise_expression(par)) return 0;
         buf_append(par, BC_EQ);
         par->stack_pos--;
         continue;
      }
      if (expect_symbol3(par, '!', '=', '=', NULL)) {
         if (!parse_bitwise_expression(par)) return 0;
         buf_append(par, BC_NE);
         par->stack_pos--;
         continue;
      }
      if (expect_symbol2(par, '=', '=', NULL)) {
         if (!parse_bitwise_expression(par)) return 0;
         buf_append(par, BC_EQ_VALUE);
         par->stack_pos--;
         continue;
      }
      if (expect_symbol2(par, '!', '=', NULL)) {
         if (!parse_bitwise_expression(par)) return 0;
         buf_append(par, BC_NE_VALUE);
         par->stack_pos--;
         continue;
      }
      break;
   }

   return 1;
}


static int parse_logical_expression(Parser *par)
{
   int skip_pos;

   if (!parse_comparison_expression(par)) return 0;
   
   for (;;) {
      if (expect_symbol2(par, '&', '&', NULL) || expect_symbol2(par, '|', '|', NULL)) {
         buf_append_load(par, -1);
         if (par->tok.value[0] == '|') {
            buf_append(par, BC_LOGNOT);
         }
         skip_pos = buf_append_branch(par, BC_BRANCH0);
         buf_append_pop(par, 1);
         par->stack_pos--;
         if (!parse_comparison_expression(par)) return 0;
         if (!buf_update_branch(par, skip_pos)) return 0;
         continue;
      }
      break;
   }

   return 1;
}


static int parse_ternary_expression(Parser *par)
{
   int skip_pos, end_pos;

   if (!parse_logical_expression(par)) return 0;
   
   if (expect_symbol(par, '?', NULL)) {
      skip_pos = buf_append_branch(par, BC_BRANCH0);
      par->stack_pos--;
      
      if (!parse_expression(par)) return 0;
      
      if (!expect_symbol(par, ':', "expected ':'")) return 0;
      
      end_pos = buf_append_branch(par, BC_JUMP0);
      if (!buf_update_branch(par, skip_pos)) return 0;
      par->stack_pos--;
      
      if (!parse_expression(par)) return 0;

      if (!buf_update_branch(par, end_pos)) return 0;
   }

   return 1;
}


static int replace_simple_incdec(Parser *par)
{
   char *name;
   int inc, var_stack_pos, value;
   
   if (expect_symbol2(par, '+', '+', NULL) || expect_symbol2(par, '-', '-', NULL)) {
      inc = (par->tok.value[0] == '+');

      if (!expect_type(par, TOK_IDENT, NULL)) return 0;

      name = string_dup(par->tok.value, par->tok.len);
      var_stack_pos = (intptr_t)string_hash_get(&par->variables, name);
      value = var_stack_pos - par->stack_pos;
      free(name);

      if (!var_stack_pos || value < -128 || value >= 0) return 0;

      if (!expect_symbol(par, ',', NULL) && !expect_symbol(par, ';', NULL) && !expect_symbol(par, ')', NULL)) {
         return 0;
      }
      undo_token(&par->tok);

      buf_append(par, inc? BC_INC : BC_DEC);
      buf_append(par, value);
      add_line_info(par);
      return 1;
   }

   if (!expect_type(par, TOK_IDENT, NULL)) return 0;

   name = string_dup(par->tok.value, par->tok.len);
   var_stack_pos = (intptr_t)string_hash_get(&par->variables, name);
   value = var_stack_pos - par->stack_pos;
   free(name);
      
   if (!var_stack_pos || value < -128 || value >= 0) return 0;

   if (!expect_symbol2(par, '+', '+', NULL) && !expect_symbol2(par, '-', '-', NULL)) return 0;
   inc = (par->tok.value[0] == '+');

   if (!expect_symbol(par, ',', NULL) && !expect_symbol(par, ';', NULL) && !expect_symbol(par, ')', NULL)) {
      return 0;
   }
   undo_token(&par->tok);
   
   buf_append(par, inc? BC_INC : BC_DEC);
   buf_append(par, value);
   add_line_info(par);
   return 1;
}


static int parse_assignment_expression(Parser *par, int statement)
{
   Tokenizer save_tok;
   int value, type, needs_line_info;

   if (statement) {
      save_tok = par->tok;
      if (replace_simple_incdec(par)) return 1;
      par->tok = save_tok;
   }

   if (!parse_ternary_expression(par)) return 0;

   type = -1;
   needs_line_info = 0;

   if (expect_symbol(par, '=', NULL)) {
      type = BC_EQ;
   }
   else if (expect_type(par, TOK_SYMBOL2, NULL)) {
      undo_token(&par->tok);
      if (expect_symbol2(par, '+', '=', NULL)) {
         type = BC_ADD;
         needs_line_info = 1;
      }
      else if (expect_symbol2(par, '-', '=', NULL)) {
         type = BC_SUB;
         needs_line_info = 1;
      }
      else if (expect_symbol2(par, '*', '=', NULL)) {
         type = BC_MUL;
         needs_line_info = 1;
      }
      else if (expect_symbol2(par, '/', '=', NULL)) {
         type = BC_DIV;
         needs_line_info = 1;
      }
      else if (expect_symbol2(par, '%', '=', NULL)) {
         type = BC_REM;
         needs_line_info = 1;
      }
      else if (expect_symbol2(par, '&', '=', NULL)) {
         type = BC_AND;
      }
      else if (expect_symbol2(par, '|', '=', NULL)) {
         type = BC_OR;
      }
      else if (expect_symbol2(par, '^', '=', NULL)) {
         type = BC_XOR;
      }
   }
   else if (expect_symbol3(par, '<', '<', '=', NULL)) {
      type = BC_SHL;
   }
   else if (expect_symbol3(par, '>', '>', '=', NULL)) {
      type = BC_SHR;
   }
   else if (expect_symbol4(par, '>', '>', '>', '=', NULL)) {
      type = BC_USHR;
   }

   if (type != -1) {
      if (buf_is_load(par, par->last_buf_pos, &value)) {
         if (type == BC_EQ) {
            par->buf_len = par->last_buf_pos;
            par->stack_pos--;
            if (!parse_expression(par)) return 0;
         }
         else {
            if (!parse_expression(par)) return 0;
            buf_append(par, type);
            if (needs_line_info) {
               add_line_info(par);
            }
            par->stack_pos--;
         }
         if (statement) {
            buf_append_store(par, value-1);
            par->stack_pos--;
         }
         else {
            buf_append_load(par, -1);
            buf_append_store(par, value-2);
         }
      }
      else if (buf_is_load_local_var(par, par->last_buf_pos, &value)) {
         if (type == BC_EQ) {
            par->buf_len = par->last_buf_pos;
            par->stack_pos--;
            if (!parse_expression(par)) return 0;
         }
         else {
            if (!parse_expression(par)) return 0;
            buf_append(par, type);
            if (needs_line_info) {
               add_line_info(par);
            }
            par->stack_pos--;
         }
         if (statement) {
            buf_append_store_local_var(par, value);
            par->stack_pos--;
         }
         else {
            buf_append_load(par, -1);
            buf_append_store_local_var(par, value);
         }
      }
      else if (par->buf[par->last_buf_pos] == BC_ARRAY_GET) {
         remove_line_info(par);
         par->buf_len = par->last_buf_pos;
         par->stack_pos++;
         if (type == BC_EQ) {
            if (!parse_expression(par)) return 0;
         }
         else {
            buf_append_load(par, -2);
            buf_append_load(par, -2);
            buf_append(par, BC_ARRAY_GET);
            add_line_info(par);
            par->stack_pos++;
            if (!parse_expression(par)) return 0;
            buf_append(par, type);
            if (needs_line_info) {
               add_line_info(par);
            }
            par->stack_pos--;
         }
         if (statement) {
            buf_append(par, BC_ARRAY_SET);
            add_line_info(par);
            par->stack_pos -= 3;
         }
         else {
            buf_append(par, BC_ARRAY_SET);
            add_line_info(par);
            buf_append_load(par, 2);
            par->stack_pos -= 2;
         }
      }
      else if (par->buf[par->last_buf_pos] == BC_HASH_GET) {
         remove_line_info(par);
         par->buf_len = par->last_buf_pos;
         par->stack_pos++;
         if (type == BC_EQ) {
            if (!parse_expression(par)) return 0;
         }
         else {
            buf_append_load(par, -2);
            buf_append_load(par, -2);
            buf_append(par, BC_HASH_GET);
            add_line_info(par);
            par->stack_pos++;
            if (!parse_expression(par)) return 0;
            buf_append(par, type);
            if (needs_line_info) {
               add_line_info(par);
            }
            par->stack_pos--;
         }
         if (statement) {
            buf_append(par, BC_HASH_SET);
            add_line_info(par);
            par->stack_pos -= 3;
         }
         else {
            buf_append(par, BC_HASH_SET);
            add_line_info(par);
            buf_append_load(par, 2);
            par->stack_pos -= 2;
         }
      }
      else {
         par->tok.error = "invalid assignment destination";
         return 0;
      }
   }
   else if (expect_symbol2(par, '[', ']', NULL)) {
      if (!expect_symbol(par, '=', "expected '='")) return 0;
      if (!parse_expression(par)) return 0;
      buf_append(par, BC_ARRAY_APPEND);
      add_line_info(par);
      par->stack_pos -= 2;
      return 1;
   }
   else if (statement) {
      buf_append_pop(par, 1);
      par->stack_pos--;
      return 1;
   }

   return 1;
}


static int parse_expression(Parser *par)
{
   if (!parse_assignment_expression(par, 0)) return 0;
   return 1;
}


static int put_variable(Parser *par, char *name, int stack_pos)
{
   int old_stack_pos;

   old_stack_pos = (intptr_t)string_hash_set(&par->variables, name, (void *)(intptr_t)stack_pos);
   if (old_stack_pos) {
      par->tok.error = "duplicate variable name in current scope";
      return 0;
   }
   par->has_vars = 1;
   return 1;
}


static int parse_var_init(Parser *par)
{
   char *name;
   
   do {
      if (!expect_type(par, TOK_IDENT, "expected variable name")) return 0;
      name = string_dup(par->tok.value, par->tok.len);

      if (expect_symbol(par, '=', NULL)) {
         if (!parse_expression(par)) {
            free(name);
            return 0;
         }
      }
      else {
         buf_append_const(par, 0);
         par->stack_pos++;
      }

      if (!put_variable(par, name, par->stack_pos-1)) return 0;
   }
   while (expect_symbol(par, ',', NULL));

   return 1;
}


static int parse_var_call2(Parser *par, int assign)
{
   char *name1 = NULL, *name2 = NULL;
   int ret, weak_call = 0; // prevents uninitialized warning
   int intrinsic_type, needs_line_info, num, num_args, is_iround, var_stack_pos, has_float_shorthand;

   if (!expect_type(par, TOK_IDENT, "expected variable name")) goto error;
   name1 = string_dup(par->tok.value, par->tok.len);

   if (!expect_symbol(par, ',', "expected ','")) goto error;

   if (!expect_type(par, TOK_IDENT, "expected variable name")) goto error;
   name2 = string_dup(par->tok.value, par->tok.len);

   if (!expect_symbol(par, ')', "expected ')'")) goto error;
   if (!expect_symbol(par, '=', "expected '='")) goto error;

   intrinsic_type = -1;
   needs_line_info = 0;

   if (expect_type(par, TOK_IDENT, NULL)) {
      num_args = 4;
      is_iround = 0;
      has_float_shorthand = 0;

      switch (par->tok.len) {
         case 2:
            if (!strncmp(par->tok.value, "ln", par->tok.len)) { intrinsic_type = BC_EXT_DBL_LN; num_args = 2; break; }
            break;

         case 3:
            if (!strncmp(par->tok.value, "int", par->tok.len)) { intrinsic_type = BC_EXT_DBL_INT; num_args = 2; break; }
            if (!strncmp(par->tok.value, "pow", par->tok.len)) { intrinsic_type = BC_EXT_DBL_POW; num_args = 4; has_float_shorthand = 1; break; }
            if (!strncmp(par->tok.value, "exp", par->tok.len)) { intrinsic_type = BC_EXT_DBL_EXP; num_args = 2; break; }
            if (!strncmp(par->tok.value, "sin", par->tok.len)) { intrinsic_type = BC_EXT_DBL_SIN; num_args = 2; break; }
            if (!strncmp(par->tok.value, "cos", par->tok.len)) { intrinsic_type = BC_EXT_DBL_COS; num_args = 2; break; }
            if (!strncmp(par->tok.value, "tan", par->tok.len)) { intrinsic_type = BC_EXT_DBL_TAN; num_args = 2; break; }
            break;

         case 4:
            if (!strncmp(par->tok.value, "fadd", par->tok.len)) { intrinsic_type = BC_EXT_DBL_ADD; num_args = 4; has_float_shorthand = 1; break; }
            if (!strncmp(par->tok.value, "fsub", par->tok.len)) { intrinsic_type = BC_EXT_DBL_SUB; num_args = 4; has_float_shorthand = 1; break; }
            if (!strncmp(par->tok.value, "fmul", par->tok.len)) { intrinsic_type = BC_EXT_DBL_MUL; num_args = 4; has_float_shorthand = 1; break; }
            if (!strncmp(par->tok.value, "fdiv", par->tok.len)) { intrinsic_type = BC_EXT_DBL_DIV; num_args = 4; has_float_shorthand = 1; break; }
            if (!strncmp(par->tok.value, "fabs", par->tok.len)) { intrinsic_type = BC_EXT_DBL_FABS; num_args = 2; break; }
            if (!strncmp(par->tok.value, "fmin", par->tok.len)) { intrinsic_type = BC_EXT_DBL_FMIN; num_args = 4; has_float_shorthand = 1; break; }
            if (!strncmp(par->tok.value, "fmax", par->tok.len)) { intrinsic_type = BC_EXT_DBL_FMAX; num_args = 4; has_float_shorthand = 1; break; }
            if (!strncmp(par->tok.value, "ceil", par->tok.len)) { intrinsic_type = BC_EXT_DBL_CEIL; num_args = 2; break; }
            if (!strncmp(par->tok.value, "sqrt", par->tok.len)) { intrinsic_type = BC_EXT_DBL_SQRT; num_args = 2; break; }
            if (!strncmp(par->tok.value, "cbrt", par->tok.len)) { intrinsic_type = BC_EXT_DBL_CBRT; num_args = 2; break; }
            if (!strncmp(par->tok.value, "log2", par->tok.len)) { intrinsic_type = BC_EXT_DBL_LOG2; num_args = 2; break; }
            if (!strncmp(par->tok.value, "asin", par->tok.len)) { intrinsic_type = BC_EXT_DBL_ASIN; num_args = 2; break; }
            if (!strncmp(par->tok.value, "acos", par->tok.len)) { intrinsic_type = BC_EXT_DBL_ACOS; num_args = 2; break; }
            if (!strncmp(par->tok.value, "atan", par->tok.len)) { intrinsic_type = BC_EXT_DBL_ATAN; num_args = 2; break; }
            break;

         case 5:
            if (!strncmp(par->tok.value, "add32", par->tok.len)) { intrinsic_type = BC_EXT_ADD32; break; }
            if (!strncmp(par->tok.value, "sub32", par->tok.len)) { intrinsic_type = BC_EXT_SUB32; break; }
            if (!strncmp(par->tok.value, "mul64", par->tok.len)) { intrinsic_type = BC_EXT_MUL64; break; }
            if (!strncmp(par->tok.value, "div64", par->tok.len)) { intrinsic_type = BC_EXT_DIV64; needs_line_info = 1; break; }
            if (!strncmp(par->tok.value, "rem64", par->tok.len)) { intrinsic_type = BC_EXT_REM64; needs_line_info = 1; break; }
            if (!strncmp(par->tok.value, "float", par->tok.len)) { intrinsic_type = BC_EXT_DBL_FLOAT; num_args = 2; break; }
            if (!strncmp(par->tok.value, "fconv", par->tok.len)) { intrinsic_type = BC_EXT_DBL_CONV_UP; num_args = 1; break; }
            if (!strncmp(par->tok.value, "floor", par->tok.len)) { intrinsic_type = BC_EXT_DBL_FLOOR; num_args = 2; break; }
            if (!strncmp(par->tok.value, "round", par->tok.len)) { intrinsic_type = BC_EXT_DBL_ROUND; num_args = 2; break; }
            if (!strncmp(par->tok.value, "log10", par->tok.len)) { intrinsic_type = BC_EXT_DBL_LOG10; num_args = 2; break; }
            if (!strncmp(par->tok.value, "atan2", par->tok.len)) { intrinsic_type = BC_EXT_DBL_ATAN2; num_args = 4; break; }
            break;

         case 6:
            if (!strncmp(par->tok.value, "umul64", par->tok.len)) { intrinsic_type = BC_EXT_UMUL64; num_args = 2; break; }
            if (!strncmp(par->tok.value, "udiv64", par->tok.len)) { intrinsic_type = BC_EXT_UDIV64; needs_line_info = 1; break; }
            if (!strncmp(par->tok.value, "urem64", par->tok.len)) { intrinsic_type = BC_EXT_UREM64; needs_line_info = 1; break; }
            if (!strncmp(par->tok.value, "iround", par->tok.len)) { intrinsic_type = BC_EXT_DBL_ROUND; num_args = 2; is_iround = 1; break; }
            break;
      }

      if (intrinsic_type == -1) {
         undo_token(&par->tok);
      }
   }

   if (intrinsic_type != -1) {
      if (!expect_symbol(par, '(', "expected '('")) goto error;

      num = 0;
      if (!expect_symbol(par, ')', NULL)) {
         do {
            if (!parse_expression(par)) goto error;
            num++;
         }
         while (expect_symbol(par, ',', NULL));

         if (!expect_symbol(par, ')', "expected ')' or ','")) goto error;
      }

      if (intrinsic_type == BC_EXT_ADD32 || intrinsic_type == BC_EXT_SUB32) {
         if (num != 2 && num != 3) {
            par->tok.error = "improper number of function parameters";
            goto error;
         }
      }
      else if (intrinsic_type == BC_EXT_MUL64) {
         if (num != 2 && num != 4) {
            par->tok.error = "improper number of function parameters";
            goto error;
         }
         if (num == 4) {
            intrinsic_type = BC_EXT_MUL64_LONG;
         }
      }
      else if (has_float_shorthand) {
         if (num != num_args && num != num_args-1) {
            par->tok.error = "improper number of function parameters";
            goto error;
         }
      }
      else {
         if (num != num_args) {
            par->tok.error = "improper number of function parameters";
            goto error;
         }
      }

      if ((intrinsic_type == BC_EXT_ADD32 || intrinsic_type == BC_EXT_SUB32) && num == 2) {
         buf_append_const(par, 0);
      }
      if (has_float_shorthand && num == num_args-1) {
         buf_append(par, BC_EXTENDED);
         buf_append(par, BC_EXT_DBL_CONV_UP);
      }
      buf_append(par, BC_EXTENDED);
      buf_append(par, intrinsic_type);
      if (is_iround) {
         buf_append(par, BC_EXTENDED);
         buf_append(par, BC_EXT_DBL_INT);
      }
      if (needs_line_info) {
         add_line_info(par);
      }
      par->last_buf_pos--;
      par->stack_pos -= num - 1;
   }
   else {
      if (!parse_primary_expression(par)) goto error;

      if (!buf_set_call2(par, &weak_call)) {
         par->tok.error = "last expression must be function call";
         goto error;
      }
   }

   if (assign) {
      if (intrinsic_type == -1 && !weak_call) {
         buf_append(par, BC_CLEAN_CALL2);
      }
      par->stack_pos++;

      var_stack_pos = (intptr_t)string_hash_get(&par->variables, name2);
      free(name2);
      name2 = NULL;
      if (!var_stack_pos) {
         par->tok.error = "undefined variable name";
         goto error;
      }
      buf_append_store(par, var_stack_pos - par->stack_pos);
      par->stack_pos--;

      var_stack_pos = (intptr_t)string_hash_get(&par->variables, name1);
      free(name1);
      name1 = NULL;
      if (!var_stack_pos) {
         par->tok.error = "undefined variable name";
         goto error;
      }
      buf_append_store(par, var_stack_pos - par->stack_pos);
      par->stack_pos--;
   }
   else {
      ret = put_variable(par, name1, par->stack_pos-1);
      name1 = NULL;
      if (!ret) goto error;
   
      if (intrinsic_type == -1 && !weak_call) {
         buf_append(par, BC_CLEAN_CALL2);
      }
      par->stack_pos++;
   
      ret = put_variable(par, name2, par->stack_pos-1);
      name2 = NULL;
      if (!ret) goto error;
   }

   return 1;

error:
   free(name1);
   free(name2);
   return 0;
}


static int parse_case_constant(Parser *par, int *int_value)
{
   Script *script;
   Constant *constant;
   Value value;
   char *s;
   int conflict;

   if (expect_type(par, TOK_IDENT, NULL)) {
      s = string_dup(par->tok.value, par->tok.len);
      script = NULL;

      if (expect_symbol(par, ':', NULL)) {
         script = string_hash_get(&par->import_aliases, s);

         if (script) {
            free(s);

            if (!expect_type(par, TOK_IDENT, "expected identifier")) return 0;
            s = string_dup(par->tok.value, par->tok.len);
         }
         else {
            undo_token(&par->tok);
         }
      }

      constant = find_constant(script? script : par->script, s, script != NULL, &conflict, NULL);
      free(s);
      if (conflict) {
         par->tok.error = "declaration of constant in multiple imports";
         return 0;
      }

      if (!constant) {
         par->tok.error = "unknown constant name";
         return 0;
      }

      if (!fixscript_is_int(constant->value)) {
         return 0;
      }

      *int_value = constant->value.value;
      return 1; 
   }

   if (!parse_constant(par, &value, 1)) {
      if (!par->tok.error) {
         par->tok.error = "expected integer constant";
      }
      return 0;
   }

   *int_value = value.value;
   return 1;
}


static int compare_switch_cases(const void *ptr1, const void *ptr2)
{
   void **case1 = (void *)ptr1;
   void **case2 = (void *)ptr2;
   int value1 = (intptr_t)case1[0];
   int value2 = (intptr_t)case2[0];
   return value1 < value2? -1 : value1 > value2? +1 : 0;
}


static int parse_switch(Parser *par)
{
   union {
      int i;
      unsigned char c[4];
   } int_val;

   DynArray cases;
   LoopState loop;
   int i, value, value2, is_range, pc, default_pc = -1, ret = 0, prev_value = 0;
   int switch_pos, default_pc_pos, end_pos, aligned;

   memset(&cases, 0, sizeof(DynArray));

   if (!expect_symbol(par, '(', "expected '('")) goto error;

   if (!parse_expression(par)) goto error;

   buf_append(par, BC_SWITCH);
   switch_pos = par->buf_len;
   buf_append_int(par, 0);
   par->stack_pos--;

   if (!expect_symbol(par, ')', "expected ')'")) goto error;
   if (!expect_symbol(par, '{', "expected '{'")) goto error;

   enter_loop(par, &loop, 1, 0, 0);

   while (!expect_symbol(par, '}', NULL)) {
      pc = par->heap->bytecode_size + par->buf_len;

      if (expect_type(par, KW_CASE, NULL)) {
         for (;;) {
            is_range = 0;
            if (!parse_case_constant(par, &value)) goto error;
            if (expect_symbol2(par, '.', '.', NULL)) {
               if (!parse_case_constant(par, &value2)) goto error;
               is_range = 1;
            }
            
            if (is_range) {
               if (value >= value2) {
                  par->tok.error = "invalid range";
                  goto error;
               }
               dynarray_add(&cases, (void *)(intptr_t)value);
               dynarray_add(&cases, (void *)(intptr_t)(-pc));
               dynarray_add(&cases, (void *)(intptr_t)value2);
               dynarray_add(&cases, (void *)0);
            }
            else {
               dynarray_add(&cases, (void *)(intptr_t)value);
               dynarray_add(&cases, (void *)(intptr_t)pc);
            }

            if (expect_symbol(par, ',', NULL)) continue;
            break;
         }
         if (!expect_symbol(par, ':', "expected ':'")) goto error;
      }
      else if (expect_type(par, KW_DEFAULT, NULL)) {
         if (!expect_symbol(par, ':', "expected ':'")) goto error;
         if (default_pc != -1) {
            par->tok.error = "duplicate default case";
            goto error;
         }
         default_pc = pc;
      }
      else {
         if (expect_symbol(par, '{', NULL)) {
            if (!parse_block(par, BT_NORMAL)) goto error;
         }
         else {
            if (!parse_statement(par, "expected statement, 'case', 'default' or '}'")) goto error;
         }
      }
   }

   if (default_pc == -1 && cases.len == 0) {
      par->tok.error = "empty switch";
      goto error;
   }

   qsort(cases.data, cases.len/2, 2*sizeof(void *), compare_switch_cases);

   for (i=0; i<cases.len; i+=2) {
      value = (intptr_t)cases.data[i+0];
      pc = (intptr_t)cases.data[i+1];
      if (i > 0 && value == prev_value) {
         par->tok.error = "duplicate case value";
         goto error;
      }
      if (pc < 0) {
         if (i+2 >= cases.len || (intptr_t)cases.data[i+2+1] != 0) {
            par->tok.error = "intersection of ranges";
            goto error;
         }
      }
      if (pc == 0) {
         if (i == 0 || (intptr_t)cases.data[i-2+1] > 0) {
            par->tok.error = "intersection of ranges";
            goto error;
         }
      }
      prev_value = value;
   }

   end_pos = buf_append_branch(par, BC_JUMP0);
   
   pc = par->heap->bytecode_size + par->buf_len;
   aligned = (pc + 3) & ~3;
   for (i=0; i<aligned-pc; i++) {
      buf_append(par, 0);
   }
   int_val.i = (aligned >> 2) + 2;
   for (i=0; i<4; i++) {
      par->buf[switch_pos+i] = int_val.c[i];
   }

   buf_append_int(par, cases.len/2);
   default_pc_pos = par->buf_len;
   buf_append_int(par, default_pc);
   for (i=0; i<cases.len; i++) {
      buf_append_int(par, (intptr_t)cases.data[i]);
   }

   if (!buf_update_branch(par, end_pos)) goto error;
   if (!leave_loop_break(par, &loop)) goto error;

   if (default_pc == -1) {
      int_val.i = par->heap->bytecode_size + par->buf_len;
      for (i=0; i<4; i++) {
         par->buf[default_pc_pos+i] = int_val.c[i];
      }
   }
   
   ret = 1;

error:
   free(cases.data);
   return ret;
}


static int parse_statement(Parser *par, const char *error)
{
   LoopState loop;
   int num;
   int skip_pos, end_pos, start_pc;

   if (expect_type(par, KW_RETURN, NULL)) {
      num = 1;
      if (expect_symbol(par, ';', NULL)) {
         buf_append_const(par, 0);
         par->stack_pos++;
      }
      else {
         if (!parse_expression(par)) return 0;
         if (expect_symbol(par, ',', NULL)) {
            if (!parse_expression(par)) return 0;
            num = 2;
         }
         if (!expect_symbol(par, ';', "expected ';'")) return 0;
      }
      if (num == 2) {
         buf_append(par, BC_RETURN2);
      }
      else {
         buf_append_const(par, par->stack_pos-1);
         buf_append(par, BC_RETURN);
      }
      par->stack_pos -= num;
      return 1;
   }

   if (expect_type(par, KW_IF, NULL)) {
      if (!expect_symbol(par, '(', "expected '('")) return 0;

      if (!parse_expression(par)) return 0;
      skip_pos = buf_append_branch(par, BC_BRANCH0);
      par->stack_pos--;

      if (!expect_symbol(par, ')', "expected ')'")) return 0;
      
      if (expect_symbol(par, '{', NULL)) {
         if (!parse_block(par, BT_NORMAL)) return 0;
         if (expect_type(par, KW_ELSE, NULL)) {
            end_pos = buf_append_branch(par, BC_JUMP0);
            if (!buf_update_branch(par, skip_pos)) return 0;
            
            if (expect_symbol(par, '{', NULL)) {
               if (!parse_block(par, BT_NORMAL)) return 0;
            }
            else {
               if (!parse_statement(par, "expected statement")) return 0;
            }

            if (!buf_update_branch(par, end_pos)) return 0;
         }
         else {
            if (!buf_update_branch(par, skip_pos)) return 0;
         }
      }
      else {
         if (!parse_statement(par, "expected statement")) return 0;
         if (!buf_update_branch(par, skip_pos)) return 0;
      }
      return 1;
   }

   if (expect_type(par, KW_FOR, NULL)) {
      return parse_block(par, BT_FOR);
   }

   if (expect_type(par, KW_WHILE, NULL)) {
      if (!expect_symbol(par, '(', "expected '('")) return 0;
      
      start_pc = par->buf_len;

      if (!parse_expression(par)) return 0;

      end_pos = buf_append_branch(par, BC_BRANCH0);
      par->stack_pos--;

      if (!expect_symbol(par, ')', "expected ')'")) return 0;

      enter_loop(par, &loop, 1, 1, start_pc);

      if (expect_symbol(par, '{', NULL)) {
         if (!parse_block(par, BT_NORMAL)) return 0;
      }
      else {
         if (!parse_statement(par, "expected statement")) return 0;
      }
      
      if (!leave_loop_continue(par, &loop)) return 0;
      buf_append_loop(par, start_pc);
      if (!leave_loop_break(par, &loop)) return 0;

      if (!buf_update_branch(par, end_pos)) return 0;
      return 1;
   }

   if (expect_type(par, KW_DO, NULL)) {
      start_pc = par->buf_len;
      enter_loop(par, &loop, 1, 1, 0);

      if (!expect_symbol(par, '{', "expected '{'")) return 0;
      if (!parse_block(par, BT_NORMAL)) return 0;

      if (!leave_loop_continue(par, &loop)) return 0;

      if (!expect_type(par, KW_WHILE, "expected 'while'")) return 0;
      if (!expect_symbol(par, '(', "expected '('")) return 0;
      if (!parse_expression(par)) return 0;
      if (!expect_symbol(par, ')', "expected ')'")) return 0;
      if (!expect_symbol(par, ';', "expected ';'")) return 0;

      end_pos = buf_append_branch(par, BC_BRANCH0);
      par->stack_pos--;

      buf_append_loop(par, start_pc);
      if (!leave_loop_break(par, &loop)) return 0;

      if (!buf_update_branch(par, end_pos)) return 0;
      return 1;
   }

   if (expect_type(par, KW_BREAK, NULL)) {
      if (!par->has_break) {
         par->tok.error = "no loop or switch in current scope";
         return 0;
      }
      buf_append_pop(par, par->stack_pos - par->break_stack_pos);
      end_pos = buf_append_branch(par, BC_JUMP0);
      dynarray_add(&par->break_jumps, (void *)(intptr_t)end_pos);
      if (!expect_symbol(par, ';', "expected ';'")) return 0;
      return 1;
   }

   if (expect_type(par, KW_CONTINUE, NULL)) {
      if (!par->has_continue) {
         par->tok.error = "no loop in current scope";
         return 0;
      }
      buf_append_pop(par, par->stack_pos - par->continue_stack_pos);
      if (par->continue_pc != 0) {
         buf_append_loop(par, par->continue_pc);
      }
      else {
         skip_pos = buf_append_branch(par, BC_JUMP0);
         dynarray_add(&par->continue_jumps, (void *)(intptr_t)skip_pos);
      }
      if (!expect_symbol(par, ';', "expected ';'")) return 0;
      return 1;
   }

   if (expect_type(par, KW_SWITCH, NULL)) {
      return parse_switch(par);
   }

   if (parse_assignment_expression(par, 1)) {
      if (!expect_symbol(par, ';', "expected ';'")) return 0;
      return 1;
   }

   if (!par->tok.error) {
      par->tok.error = error;
   }
   return 0;
}


static int parse_for_update(Parser *par)
{
   do {
      if (!parse_assignment_expression(par, 1)) return 0;
   }
   while (expect_symbol(par, ',', NULL));

   return 1;
}


static int parse_for_inner(Parser *par)
{
   LoopState loop;
   Tokenizer update_tok, end_tok;
   int start_pc, end_pos = -1, has_update, level;

   update_tok = par->tok; // prevents uninitialized warning

   if (!expect_symbol(par, '(', "expected '('")) return 0;

   if (expect_type(par, KW_VAR, NULL)) {
      if (!parse_var_init(par)) return 0;
      if (!expect_symbol(par, ';', "expected ';'")) return 0;
   }
   else if (!expect_symbol(par, ';', NULL)) {
      do {
         if (!parse_assignment_expression(par, 1)) return 0;
      }
      while (expect_symbol(par, ',', NULL));

      if (!expect_symbol(par, ';', "expected ';' or ','")) return 0;
   }

   start_pc = par->buf_len;
   
   if (!expect_symbol(par, ';', NULL)) {
      if (!parse_expression(par)) return 0;
      end_pos = buf_append_branch(par, BC_BRANCH0);
      par->stack_pos--;
      if (!expect_symbol(par, ';', "expected ';'")) return 0;
   }

   has_update = 0;
   if (!expect_symbol(par, ')', NULL)) {
      has_update = 1;

      update_tok = par->tok;
      level = 0;
      while (next_token(&par->tok)) {
         switch (par->tok.type) {
            case TOK_SYMBOL:
               switch (par->tok.value[0]) {
                  case '(':
                  case '{':
                  case '[':
                     level++;
                     break;

                  case ')':
                  case '}':
                  case ']':
                     if (--level < 0) {
                        undo_token(&par->tok);
                        goto end;
                     }
                     break;
               }
               break;
         }
      }
      end:;

      if (!expect_symbol(par, ')', "expected ')'")) return 0;
   }

   enter_loop(par, &loop, 1, 1, !has_update? start_pc : 0);

   if (expect_symbol(par, '{', NULL)) {
      if (!parse_block(par, BT_NORMAL)) return 0;
   }
   else {
      if (!parse_statement(par, "expected statement")) return 0;
   }

   if (!leave_loop_continue(par, &loop)) return 0;

   if (has_update) {
      end_tok = par->tok;
      par->tok = update_tok;
      if (!parse_for_update(par)) return 0;
      if (!expect_symbol(par, ')', "expected ')'")) return 0;
      par->tok = end_tok;
   }

   buf_append_loop(par, start_pc);

   if (!leave_loop_break(par, &loop)) return 0;
   if (end_pos != -1 && !buf_update_branch(par, end_pos)) return 0;
   return 1;
}


static int parse_block_inner(Parser *par, int *expr_has_ret)
{
   Tokenizer save_tok;
   int found;

   while (!expect_symbol(par, '}', NULL)) {
      if (expect_type(par, KW_VAR, NULL)) {
         if (expect_symbol(par, '(', NULL)) {
            if (!parse_var_call2(par, 0)) return 0;
            if (!expect_symbol(par, ';', "expected ';'")) return 0;
            continue;
         }
         if (!parse_var_init(par)) return 0;
         if (!expect_symbol(par, ';', "expected ';'")) return 0;
         continue;
      }

      if (expect_symbol(par, '(', NULL)) {
         found = 0;
         save_tok = par->tok;
         if (expect_type(par, TOK_IDENT, NULL) && expect_symbol(par, ',', NULL)) {
            found = 1;
         }
         par->tok = save_tok;
         if (found) {
            if (!parse_var_call2(par, 1)) return 0;
            if (!expect_symbol(par, ';', "expected ';'")) return 0;
            continue;
         }
         else {
            undo_token(&par->tok);
         }
      }

      if (expect_symbol(par, '{', NULL)) {
         if (!parse_block(par, BT_NORMAL)) return 0;
         continue;
      }

      if (expr_has_ret && expect_symbol(par, '=', NULL)) {
         if (!parse_expression(par)) return 0;
         if (!expect_symbol(par, '}', "expected '}'")) return 0;
         *expr_has_ret = 1;
         return 1;
      }

      if (!parse_statement(par, "expected statement or '}'")) return 0;
   }
   
   return 1;
}


static int parse_block(Parser *par, int type)
{
   int i, ret, old_has_vars, old_stack_pos, var_stack_pos, num;
   int expr_has_ret = 0;

   old_has_vars = par->has_vars;
   old_stack_pos = par->stack_pos;
   par->has_vars = 0;

   if (type == BT_FOR) {
      ret = parse_for_inner(par);
   }
   else {
      ret = parse_block_inner(par, type == BT_EXPR? &expr_has_ret : NULL);
   }

   if (type == BT_EXPR && ret && !expr_has_ret) {
      par->tok.error = "statement expression must provide output value";
      return 0;
   }
   
   if (par->has_vars) {
      num = 0;
      for (i=0; i<par->variables.size; i+=2) {
         if (par->variables.data[i+0] && par->variables.data[i+1]) {
            var_stack_pos = (intptr_t)par->variables.data[i+1];
            if (var_stack_pos >= old_stack_pos) {
               par->variables.data[i+1] = NULL;
               par->variables.len--;
               num++;
            }
         }
      }
      if (expr_has_ret) {
         buf_append_store(par, -num-1);
         buf_append_pop(par, num-1);
      }
      else {
         buf_append_pop(par, num);
      }
      par->stack_pos -= num;
   }

   par->has_vars = old_has_vars;
   return ret;
}


static int parse_function_inner(Parser *par, Function *func, const char *func_name)
{
   Function *old_func;
#ifndef FIXEMBED_TOKEN_DUMP
   NativeFunction *native_func;
#endif
   char *name;
   int old_stack_pos;

   par->stack_pos = 1;

   if (!expect_symbol(par, '(', "expected '('")) return 0;

   while (expect_type(par, TOK_IDENT, NULL)) {
      if (++func->num_params > 255) {
         par->tok.error = "more than 255 parameters";
         return 0;
      }
      name = string_dup(par->tok.value, par->tok.len);
      old_stack_pos = (intptr_t)string_hash_set(&par->variables, name, (void *)(intptr_t)par->stack_pos++);
      if (old_stack_pos) {
         par->tok.error = "duplicate parameter name";
         return 0;
      }
      
      if (expect_symbol(par, ',', NULL)) {
         if (!expect_type(par, TOK_IDENT, "expected parameter name")) return 0;
         undo_token(&par->tok);
         continue;
      }

      if (!expect_symbol(par, ')', "expected ')', ',' or parameter name")) return 0;
      undo_token(&par->tok);
      break;
   }

   if (!expect_symbol(par, ')', "expected ')' or parameter name")) return 0;

   name = string_format("%s#%d", func_name, func->num_params);

   if (expect_symbol(par, ';', NULL)) {
#ifndef FIXEMBED_TOKEN_DUMP
      native_func = string_hash_get(&par->heap->native_functions_hash, name);
      if (!native_func) {
         par->tok.error = "native function not present";
      }
#endif
      free(name);
      return 2;
   }

   old_func = string_hash_set(&par->script->functions, name, func);
   if (old_func) {
      free_function(old_func);
      par->tok.error = "duplicate function name";
      return 0;
   }

   if (!expect_symbol(par, '{', "expected '{' or ';'")) return 0;
   if (!parse_block(par, BT_NORMAL)) return 0;
   
   buf_append_const(par, 0);
   par->stack_pos++;
   buf_append_const(par, par->stack_pos-1);
   buf_append(par, BC_RETURN);
   par->stack_pos--;

   if (par->stack_pos != 1 + func->num_params) {
      par->tok.error = "internal error: stack misalignment";
      return 0;
   }

   return 1;
}


static int parse_function(Parser *par)
{
   Function *func;
   char *name;
   int i, ret, local = 0;
   
   if (expect_symbol(par, '@', NULL)) {
      local = 1;
   }
   
   if (!expect_type(par, TOK_IDENT, "expected identifier")) return 0;

   func = calloc(1, sizeof(Function));
   func->id = par->heap->functions.len;
   dynarray_add(&par->heap->functions, func);
   func->addr = par->heap->bytecode_size + par->buf_len;
   func->local = local;
   func->script = par->script;
   func->lines_start = par->heap->lines_size + par->lines.len/2;

   name = string_dup(par->tok.value, par->tok.len);
   ret = parse_function_inner(par, func, name);
   free(name);

   if (ret == 2) {
      par->heap->functions.len--;
      free_function(func);
      ret = par->tok.error? 0:1;
   }
   else {
      func->lines_end = par->heap->lines_size + par->lines.len/2;
   }

   for (i=0; i<par->variables.size; i+=2) {
      if (par->variables.data[i+0]) {
         free(par->variables.data[i+0]);
         par->variables.data[i+0] = NULL;
         par->variables.data[i+1] = NULL;
      }
   }

   return ret;
}


static int parse_script_inner(Parser *par)
{
   int first = 1;

   while (expect_type(par, KW_VAR, NULL)) {
      if (!parse_local_var(par)) return 0;
   }

   while (has_next(par)) {
      if (!expect_type(par, KW_FUNCTION, first? "expected 'function' or 'import' keyword" : "expected 'function' keyword")) {
         return 0;
      }
      if (!parse_function(par)) return 0;
      first = 0;
   }

   return 1;
}


static void save_script_state(Parser *par, ScriptState *state)
{
   state->used = 1;
   state->functions_len = par->heap->functions.len;
   state->locals_len = par->heap->data[LOCALS_IDX].len;
}


static void restore_script_state(Parser *par, ScriptState *state)
{
   if (state->used) {
      par->heap->functions.len = state->functions_len;
      par->heap->data[LOCALS_IDX].len = state->locals_len;
   }
}


static int parse_script(Parser *par, char **error_msg, ScriptState *state)
{
   while (expect_type(par, KW_USE, NULL)) {
      if (!parse_import(par, error_msg, 1)) return 0;
   }

#ifdef FIXEMBED_TOKEN_DUMP
   if (!par->long_jumps && !par->long_func_refs) {
      fixembed_dump_tokens(par->fname, &par->tok);
   }
#endif

   while (expect_type(par, KW_IMPORT, NULL)) {
      if (!parse_import(par, error_msg, 0)) return 0;
   }

   while (expect_type(par, KW_CONST, NULL)) {
      if (!parse_constant_define(par)) return 0;
   }

   save_script_state(par, state);
   return parse_script_inner(par);
}


static Script *load_script(Heap *heap, const char *src, const char *fname, char **error_msg, int long_jumps, int long_func_refs, LoadScriptFunc load_func, void *load_data, Parser *reuse_tokens)
{
   Script *script;
   Parser par;
   ScriptState state;
   LoadScriptFunc prev_load_func;
   void *prev_load_data;
   int i;

   script = string_hash_get(&heap->scripts, fname);
   if (script) {
      return script;
   }

   script = calloc(1, sizeof(Script));

   memset(&par, 0, sizeof(Parser));
   par.tok.cur = src;
   par.tok.line = 1;
   par.buf_size = 1024;
   par.buf = malloc(par.buf_size);
   par.heap = heap;
   par.script = script;
   par.long_jumps = long_jumps;
   par.long_func_refs = long_func_refs;
   par.load_func = load_func;
   par.load_data = load_data;
   par.fname = fname;

   if (reuse_tokens && reuse_tokens->tokens_src) {
      par.tokens_src = reuse_tokens->tokens_src;
      par.tokens_arr = reuse_tokens->tokens_arr;
      par.tokens_end = reuse_tokens->tokens_end;
      par.tokens_src_val = reuse_tokens->tokens_src_val;
      par.tokens_arr_val = reuse_tokens->tokens_arr_val;
   
      par.tok.tokens_src = par.tokens_src;
      par.tok.cur_token = par.tokens_arr + TOK_SIZE;
      par.tok.tokens_end = par.tokens_end;
      par.tok.again = 0;
   }

   if (error_msg) {
      *error_msg = NULL;
   }

   heap->cur_import_recursion++;

   prev_load_func = heap->cur_load_func;
   prev_load_data = heap->cur_load_data;
   heap->cur_load_func = load_func;
   heap->cur_load_data = load_data;

   memset(&state, 0, sizeof(ScriptState));
   if (!parse_script(&par, error_msg, &state)) {
      if (error_msg && *error_msg == NULL) {
         *error_msg = string_format("%s(%d): %s\n", fname, par.tok.line, par.tok.error);
      }
      restore_script_state(&par, &state);
      free_script(script);
      script = NULL;
   }

   heap->cur_import_recursion--;

   heap->cur_load_func = prev_load_func;
   heap->cur_load_data = prev_load_data;

   if (script) {
      for (i=0; i<par.func_refs.len; i+=3) {
         char *func_name = par.func_refs.data[i+0];
         int buf_off = (intptr_t)par.func_refs.data[i+1];
         int line_num = (intptr_t)par.func_refs.data[i+2];
         int empty = buf_off & (1<<31);
         int func_ref = line_num & (1<<31);
         Function *func = string_hash_get(&script->functions, func_name);
         int func_id;
         unsigned char *bc;

         buf_off &= ~(1<<31);
         line_num &= ~(1<<31);

         if (func) {
            if (buf_off == 0) {
               par.long_func_refs = 1;
               restore_script_state(&par, &state);
               free_script(script);
               script = NULL;
               break;
            }
            func_id = func->id;
            if (func_ref) {
               func_id += FUNC_REF_OFFSET;
            }
            memcpy(&par.buf[buf_off], &func_id, sizeof(int));
            if (!func_ref) {
               bc = &par.buf[buf_off+4];
               if (*bc == BC_CALL2_DIRECT || *bc == BC_CALL2_NATIVE) {
                  *bc = BC_CALL2_DIRECT;
               }
               else {
                  *bc = BC_CALL_DIRECT;
               }
            }
         }
         else if (empty) {
#ifndef FIXEMBED_TOKEN_DUMP
            if (error_msg && *error_msg == NULL) {
               *error_msg = string_format("%s(%d): %s\n", fname, line_num, "undefined function");
            }
            restore_script_state(&par, &state);
            free_script(script);
            script = NULL;
            break;
#endif
         }
      }
   }

   if (script) {
      if (heap->bytecode_size + par.buf_len > (1<<23)) {
         if (error_msg && *error_msg == NULL) {
            *error_msg = string_format("%s: maximum bytecode limit reached\n", fname);
         }
         restore_script_state(&par, &state);
         free_script(script);
         script = NULL;
      }
      else {
         heap->bytecode = realloc(heap->bytecode, heap->bytecode_size + par.buf_len);
         memcpy(heap->bytecode + heap->bytecode_size, par.buf, par.buf_len);
         heap->bytecode_size += par.buf_len;

         heap->lines = realloc(heap->lines, (heap->lines_size + par.lines.len/2) * sizeof(LineEntry));
         for (i=0; i<par.lines.len; i+=2) {
            heap->lines[heap->lines_size++] = (LineEntry) { (intptr_t)par.lines.data[i+0], (intptr_t)par.lines.data[i+1] };
         }

         string_hash_set(&heap->scripts, strdup(fname), script);
      }
   }

   free(par.buf);
   free(par.lines.data);

   for (i=0; i<par.variables.size; i+=2) {
      if (par.variables.data[i+0]) {
         free(par.variables.data[i+0]);
      }
   }
   free(par.variables.data);

   for (i=0; i<par.const_strings.size; i+=2) {
      if (par.const_strings.data[i+0]) {
         free(par.const_strings.data[i+0]);
         if (!script) {
            heap->data[(intptr_t)par.const_strings.data[i+1]].is_static = 0;
         }
      }
   }
   free(par.const_strings.data);

   for (i=0; i<par.import_aliases.size; i+=2) {
      if (par.import_aliases.data[i+0]) {
         free(par.import_aliases.data[i+0]);
      }
   }
   free(par.import_aliases.data);

   free(par.break_jumps.data);
   free(par.continue_jumps.data);

   for (i=0; i<par.func_refs.len; i+=3) {
      free(par.func_refs.data[i+0]);
   }
   free(par.func_refs.data);

   if (!script && ((par.long_jumps && !long_jumps) || (par.long_func_refs && !long_func_refs))) {
      if (par.long_jumps) {
         par.long_func_refs = 1;
      }
      if (error_msg && *error_msg)  {
         free(*error_msg);
         *error_msg = NULL;
      }
      return load_script(heap, src, fname, error_msg, par.long_jumps, par.long_func_refs, load_func, load_data, &par);
   }
   
   free(par.tokens_src);
   free(par.tokens_arr);
   fixscript_unref(heap, par.tokens_src_val);
   fixscript_unref(heap, par.tokens_arr_val);

   if (!script) {
      collect_heap(heap, NULL);
   }

   return script;
}


Script *fixscript_load(Heap *heap, const char *src, const char *fname, char **error_msg, LoadScriptFunc load_func, void *load_data)
{
   return load_script(heap, src, fname, error_msg, 0, 0, load_func, load_data, NULL);
}


static int is_forbidden_name(const char *name, int len)
{
   char buf[4];
   int i;

   for (i=0; i<len; i++) {
      if (name[i] == '.') {
         len = i;
         break;
      }
   }

   if (len < 3 || len > 4) return 0;

   for (i=0; i<len; i++) {
      if (name[i] >= 'a' && name[i] <= 'z') {
         buf[i] = name[i] - 'a' + 'A';
      }
      else {
         buf[i] = name[i];
      }
   }

   if (len == 3 && memcmp(buf, "CON", 3) == 0) return 1;
   if (len == 3 && memcmp(buf, "PRN", 3) == 0) return 1;
   if (len == 3 && memcmp(buf, "AUX", 3) == 0) return 1;
   if (len == 3 && memcmp(buf, "NUL", 3) == 0) return 1;
   if (len == 4 && memcmp(buf, "COM", 3) == 0 && buf[3] >= '0' && buf[3] <= '9') return 1;
   if (len == 4 && memcmp(buf, "LPT", 3) == 0 && buf[3] >= '0' && buf[3] <= '9') return 1;
   return 0;
}


static int is_valid_path(const char *path)
{
   const char *cur = path;
   const char *last = path;

   if (*path == 0) return 0;

   for (cur = path; *cur; cur++) {
      if (*cur >= 'A' && *cur <= 'Z') continue;
      if (*cur >= 'a' && *cur <= 'z') continue;
      if (*cur >= '0' && *cur <= '9') continue;
      if (*cur == '-' || *cur == '_' || *cur == ' ') continue;

      if (*cur == '.') {
         if (cur == last) return 0;
         if (cur[1] == '/' || cur[1] == 0) return 0;
         continue;
      }
      
      if (*cur == '/') {
         if (cur == last) return 0;
         if (cur[1] == '/') return 0;
         if (is_forbidden_name(last, cur-last)) return 0;
         last = cur+1;
         continue;
      }

      return 0;
   }

   if (is_forbidden_name(last, cur-last)) return 0;
   return 1;
}


Script *fixscript_load_file(Heap *heap, const char *name, char **error_msg, const char *dirname)
{
   FILE *f;
   char *src = NULL;
   int src_size = 0;
   int buf_size = 4096;
   char *buf = NULL;
   int read;
   Script *script = NULL;
   char *sname = NULL, *fname = NULL;

   if (!is_valid_path(name)) {
      if (error_msg) {
         *error_msg = string_format("invalid script file name %s given", name);
      }
      return NULL;
   }

   sname = string_format("%s.fix", name);
   script = fixscript_get(heap, sname);
   if (script) {
      free(sname);
      return script;
   }

   fname = string_format("%s/%s.fix", dirname, name);
   f = fopen(fname, "rb");
   if (!f) {
      if (error_msg) {
         *error_msg = string_format("script %s not found", name);
      }
      goto error;
   }

   buf = malloc(buf_size);

   while ((read = fread(buf, 1, buf_size, f)) > 0) {
      src = realloc(src, src_size + read + 1);
      memcpy(src + src_size, buf, read);
      src_size += read;
   }

   if (ferror(f)) {
      fclose(f);
      if (error_msg) {
         *error_msg = string_format("reading of script %s failed", name);
      }
      goto error;
   }
   fclose(f);

   if (src) {
      src[src_size] = 0;
   }
   else {
      src = strdup("");
   }
   
   script = fixscript_load(heap, src, sname, error_msg, (LoadScriptFunc)fixscript_load_file, (void *)dirname);

error:
   free(sname);
   free(fname);
   free(buf);
   free(src);
   return script;
}


static int uncompress_script(const char *in, char **dest_out)
{
   char *out = NULL;
   int in_size, out_size;
   int in_idx = 0, out_idx = 0;
   int literal_len, match_len, match_off, amount;
   uint8_t token, b;
   uint16_t offset;

   memcpy(&in_size, &in[0], sizeof(int));
   memcpy(&out_size, &in[4], sizeof(int));
   in += 8;

   out = malloc(out_size+1);
   if (!out) goto error;

   while (in_idx < in_size) {
      token = in[in_idx++];

      literal_len = token >> 4;
      if (literal_len == 15) {
         do {
            if (in_idx >= in_size) goto error;
            b = in[in_idx++];
            literal_len += b;
            if (literal_len > out_size) goto error;
         }
         while (b == 255);
      }
      if (literal_len > 0) {
         if (in_idx + literal_len > in_size) goto error;
         if (out_idx + literal_len > out_size) goto error;
         memcpy(out + out_idx, in + in_idx, literal_len);
         in_idx += literal_len;
         out_idx += literal_len;
      }
      
      if (in_idx == in_size) break;

      if (in_idx+2 > in_size) goto error;
      memcpy(&offset, in + in_idx, 2);
      in_idx += 2;
      if (offset == 0) goto error;

      match_off = out_idx - offset;
      if (match_off < 0) goto error;

      match_len = (token & 0xF) + 4;
      if (match_len == 19) {
         do {
            if (in_idx >= in_size) goto error;
            b = in[in_idx++];
            match_len += b;
            if (match_len > out_size) goto error;
         }
         while (b == 255);
      }
      if (out_idx + match_len > out_size) goto error;

      if (match_off + match_len <= out_idx) {
         memcpy(out + out_idx, out + match_off, match_len);
         out_idx += match_len;
      }
      else {
         amount = out_idx - match_off;
         while (match_len > 0) {
            if (amount > match_len) {
               amount = match_len;
            }
            memcpy(out + out_idx, out + match_off, amount);
            out_idx += amount;
            match_len -= amount;
         }
      }
   }

   if (out_idx != out_size) goto error;

   out[out_size] = '\0';
   *dest_out = out;
   return 1;

error:
   free(out);
   return 0;
}


Script *fixscript_load_embed(Heap *heap, const char *name, char **error_msg, const char * const * const embed_files)
{
   const char * const *p;
   const char *src = NULL;
   Script *script;
   char *fname, *uncompressed = NULL;

   fname = string_format("%s.fix", name);
   script = fixscript_get(heap, fname);
   if (script) {
      free(fname);
      return script;
   }

   for (p = embed_files; *p; p += 2) {
      if (!strcmp(*p, fname)) {
         src = *(p+1);
         break;
      }
   }

   if (!src) {
      free(fname);
      if (error_msg) {
         *error_msg = string_format("script %s not found", name);
      }
      return NULL;
   }

   if ((uint8_t)src[0] == 0xFF) {
      if (!uncompress_script(src+1, &uncompressed)) {
         free(fname);
         if (error_msg) {
            *error_msg = string_format("script %s cannot be uncompressed", name);
         }
         return NULL;
      }
      src = uncompressed;
   }

   script = fixscript_load(heap, src, fname, error_msg, (LoadScriptFunc)fixscript_load_embed, (void *)embed_files);
   free(fname);
   free(uncompressed);
   return script;
}


Script *fixscript_resolve_existing(Heap *heap, const char *name, char **error_msg, void *data)
{
   if (error_msg) {
      *error_msg = string_format("tried to load script %s.fix during resolving of function references with loading disabled", name);
   }
   return NULL;
}


Script *fixscript_get(Heap *heap, const char *fname)
{
   return string_hash_get(&heap->scripts, fname);
}


char *fixscript_get_script_name(Heap *heap, Script *script)
{
   const char *name = string_hash_find_name(&heap->scripts, script);
   if (!name) {
      return NULL;
   }
   return strdup(name);
}


Value fixscript_get_function(Heap *heap, Script *script, const char *func_name)
{
   Function *func = string_hash_get(&script->functions, func_name);
   return (Value) {func? FUNC_REF_OFFSET + func->id : 0, func? 1:0};
}


int fixscript_get_function_name(Heap *heap, Value func_val, char **script_name_out, char **func_name_out, int *num_params_out)
{
   int func_id = func_val.value - FUNC_REF_OFFSET;
   Function *func;
   const char *script_name, *func_name;

   if (!func_val.is_array || func_id < 1 || func_id >= heap->functions.len) {
      return FIXSCRIPT_ERR_INVALID_ACCESS;
   }

   func = heap->functions.data[func_id];
   script_name = string_hash_find_name(&heap->scripts, func->script);
   func_name = string_hash_find_name(&func->script->functions, func);
   if (!script_name || !func_name) {
      return FIXSCRIPT_ERR_INVALID_ACCESS;
   }

   if (script_name_out) {
      *script_name_out = strdup(script_name);
   }
   if (func_name_out) {
      *func_name_out = strdup(func_name);
   }
   if (num_params_out) {
      *num_params_out = func->num_params;
   }
   return FIXSCRIPT_SUCCESS;
}


static int expand_stack(Heap *heap, Array *stack)
{
   int new_size;
   int *new_flags;
   void *new_data;
   
   if (stack->size >= MAX_STACK_SIZE) {
      return 0;
   }
   new_size = stack->size << 1;

   new_flags = realloc_array(stack->flags, FLAGS_SIZE(new_size), sizeof(int));
   if (!new_flags) {
      return 0;
   }
   stack->flags = new_flags;

   new_data = realloc_array(stack->data, new_size, sizeof(int));
   if (!new_data) {
      return 0;
   }
   stack->data = new_data;

   heap->total_size += (int64_t)(FLAGS_SIZE(new_size) - FLAGS_SIZE(stack->size)) * sizeof(int);
   heap->total_size += (int64_t)(new_size - stack->size) * sizeof(int);

   stack->size = new_size;
   return 1;
}


static int emit_error(Heap *heap, const char *msg, int pc)
{
   Array *stack;
   Value msg_val, error_val;
   int error_pc, stack_base;

   msg_val = fixscript_create_string(heap, msg, -1);
   if (!msg_val.is_array) return -1;

   error_val = create_error(heap, msg_val, 0, pc);
   if (!error_val.is_array) return -1;
   
   error_pc = (intptr_t)heap->error_stack.data[heap->error_stack.len-2];
   stack_base = (intptr_t)heap->error_stack.data[heap->error_stack.len-1];
   heap->error_stack.len -= 2;

   stack = &heap->data[STACK_IDX];
   while (stack_base+2 > stack->size) {
      if (!expand_stack(heap, stack)) return -2;
   }
   stack->len = stack_base+2;

   stack->data[stack_base+0] = 0;
   CLEAR_IS_ARRAY(stack, stack_base+0);

   stack->data[stack_base+1] = error_val.value;
   SET_IS_ARRAY(stack, stack_base+1);

   return error_pc;
}


static int run_bytecode(Heap *heap, int pc)
{
#ifdef __GNUC__
   #define DUP2(a) a, a
   #define DUP4(a) DUP2(a), DUP2(a)
   #define DUP7(a) DUP4(a), DUP2(a), a
   #define DUP8(a) DUP4(a), DUP4(a)
   #define DUP16(a) DUP8(a), DUP8(a)
   #define DUP32(a) DUP16(a), DUP16(a)
   #define DUP64(a) DUP32(a), DUP32(a)
   static void *dispatch[256] = {
      &&op_pop,
      &&op_popn,
      &&op_loadn,
      &&op_storen,
      &&op_add,
      &&op_sub,
      &&op_mul,
      &&op_add_mod,
      &&op_sub_mod,
      &&op_mul_mod,
      &&op_div,
      &&op_rem,
      &&op_shl,
      &&op_shr,
      &&op_ushr,
      &&op_and,
      &&op_or,
      &&op_xor,
      &&op_lt,
      &&op_le,
      &&op_gt,
      &&op_ge,
      &&op_eq,
      &&op_ne,
      &&op_eq_value,
      &&op_eq_value,
      &&op_bitnot,
      &&op_lognot,
      &&op_inc,
      &&op_dec,
      &&op_float_add,
      &&op_float_sub,
      &&op_float_mul,
      &&op_float_div,
      &&op_float_lt,
      &&op_float_le,
      &&op_float_gt,
      &&op_float_ge,
      &&op_float_eq,
      &&op_float_ne,
      &&op_return,
      &&op_return2,
      &&op_call_direct,
      &&op_call_dynamic,
      &&op_call_native,
      &&op_call2_direct,
      &&op_call2_dynamic,
      &&op_call_native,
      &&op_clean_call2,
      &&op_create_array,
      &&op_create_hash,
      &&op_array_get,
      &&op_array_set,
      &&op_array_append,
      &&op_hash_get,
      &&op_hash_set,
      
      &&op_const_p8,
      &&op_const_n8,
      &&op_const_p16,
      &&op_const_n16,
      &&op_const_i32,
      &&op_const_f32,
      &&op_const,
      &&op_const,
      
      DUP32(&&op_const),

      &&op_branch0,
      DUP7(&&op_branch),
      &&op_jump0,
      DUP7(&&op_jump),
      &&op_branch_long,
      &&op_jump_long,
      &&op_loop_i8,
      &&op_loop_i16,
      &&op_loop_i32,

      &&op_load_local,
      &&op_store_local,
      &&op_switch,
      &&op_length,
      &&op_const_string,
      &&op_string_concat,
      DUP2(&&op_unused),

      &&op_extended,
      
      DUP2(&&op_const),

      DUP64(&&op_store),
      DUP64(&&op_load)
   };
   #undef DUP2
   #undef DUP4
   #undef DUP7
   #undef DUP8
   #undef DUP16
   #undef DUP32
   #undef DUP64
   static void *ext_dispatch[77] = {
      &&op_ext_min,
      &&op_ext_max,
      &&op_ext_abs,
      &&op_ext_add32,
      &&op_ext_sub32,
      &&op_ext_mul64,
      &&op_ext_umul64,
      &&op_ext_mul64_long,
      &&op_ext_div64,
      &&op_ext_udiv64,
      &&op_ext_rem64,
      &&op_ext_urem64,
      &&op_ext_float,
      &&op_ext_int,
      &&op_ext_fabs,
      &&op_ext_fmin,
      &&op_ext_fmax,
      &&op_ext_floor,
      &&op_ext_ceil,
      &&op_ext_round,
      &&op_ext_pow,
      &&op_ext_sqrt,
      &&op_ext_cbrt,
      &&op_ext_exp,
      &&op_ext_ln,
      &&op_ext_log2,
      &&op_ext_log10,
      &&op_ext_sin,
      &&op_ext_cos,
      &&op_ext_asin,
      &&op_ext_acos,
      &&op_ext_tan,
      &&op_ext_atan,
      &&op_ext_atan2,
      &&op_ext_dbl_float,
      &&op_ext_dbl_int,
      &&op_ext_dbl_conv_down,
      &&op_ext_dbl_conv_up,
      &&op_ext_dbl_add,
      &&op_ext_dbl_sub,
      &&op_ext_dbl_mul,
      &&op_ext_dbl_div,
      &&op_ext_dbl_cmp_lt,
      &&op_ext_dbl_cmp_le,
      &&op_ext_dbl_cmp_gt,
      &&op_ext_dbl_cmp_ge,
      &&op_ext_dbl_cmp_eq,
      &&op_ext_dbl_cmp_ne,
      &&op_ext_dbl_fabs,
      &&op_ext_dbl_fmin,
      &&op_ext_dbl_fmax,
      &&op_ext_dbl_floor,
      &&op_ext_dbl_ceil,
      &&op_ext_dbl_round,
      &&op_ext_dbl_pow,
      &&op_ext_dbl_sqrt,
      &&op_ext_dbl_cbrt,
      &&op_ext_dbl_exp,
      &&op_ext_dbl_ln,
      &&op_ext_dbl_log2,
      &&op_ext_dbl_log10,
      &&op_ext_dbl_sin,
      &&op_ext_dbl_cos,
      &&op_ext_dbl_asin,
      &&op_ext_dbl_acos,
      &&op_ext_dbl_tan,
      &&op_ext_dbl_atan,
      &&op_ext_dbl_atan2,
      &&op_ext_is_int,
      &&op_ext_is_float,
      &&op_ext_is_array,
      &&op_ext_is_string,
      &&op_ext_is_hash,
      &&op_ext_is_shared,
      &&op_ext_is_funcref,
      &&op_ext_is_weakref,
      &&op_ext_is_handle
   };
   #define DISPATCH() goto *dispatch[bc = bytecode[pc++]];
   //#define DISPATCH() bc = bytecode[pc++]; printf("bc=%02X stack=%d\n", bc, stack->len); goto *dispatch[bc];
   #define EXT_DISPATCH() goto *ext_dispatch[bytecode[pc++]];
#else
   #define DISPATCH() \
      switch (bc = bytecode[pc++]) { \
         case 0x00: goto op_pop; \
         case 0x01: goto op_popn; \
         case 0x02: goto op_loadn; \
         case 0x03: goto op_storen; \
         case 0x04: goto op_add; \
         case 0x05: goto op_sub; \
         case 0x06: goto op_mul; \
         case 0x07: goto op_add_mod; \
         case 0x08: goto op_sub_mod; \
         case 0x09: goto op_mul_mod; \
         case 0x0A: goto op_div; \
         case 0x0B: goto op_rem; \
         case 0x0C: goto op_shl; \
         case 0x0D: goto op_shr; \
         case 0x0E: goto op_ushr; \
         case 0x0F: goto op_and; \
         case 0x10: goto op_or; \
         case 0x11: goto op_xor; \
         case 0x12: goto op_lt; \
         case 0x13: goto op_le; \
         case 0x14: goto op_gt; \
         case 0x15: goto op_ge; \
         case 0x16: goto op_eq; \
         case 0x17: goto op_ne; \
         case 0x18: goto op_eq_value; \
         case 0x19: goto op_eq_value; \
         case 0x1A: goto op_bitnot; \
         case 0x1B: goto op_lognot; \
         case 0x1C: goto op_inc; \
         case 0x1D: goto op_dec; \
         case 0x1E: goto op_float_add; \
         case 0x1F: goto op_float_sub; \
         case 0x20: goto op_float_mul; \
         case 0x21: goto op_float_div; \
         case 0x22: goto op_float_lt; \
         case 0x23: goto op_float_le; \
         case 0x24: goto op_float_gt; \
         case 0x25: goto op_float_ge; \
         case 0x26: goto op_float_eq; \
         case 0x27: goto op_float_ne; \
         case 0x28: goto op_return; \
         case 0x29: goto op_return2; \
         case 0x2A: goto op_call_direct; \
         case 0x2B: goto op_call_dynamic; \
         case 0x2C: goto op_call_native; \
         case 0x2D: goto op_call2_direct; \
         case 0x2E: goto op_call2_dynamic; \
         case 0x2F: goto op_call_native; \
         case 0x30: goto op_clean_call2; \
         case 0x31: goto op_create_array; \
         case 0x32: goto op_create_hash; \
         case 0x33: goto op_array_get; \
         case 0x34: goto op_array_set; \
         case 0x35: goto op_array_append; \
         case 0x36: goto op_hash_get; \
         case 0x37: goto op_hash_set; \
         \
         case 0x38: goto op_const_p8; \
         case 0x39: goto op_const_n8; \
         case 0x3A: goto op_const_p16; \
         case 0x3B: goto op_const_n16; \
         case 0x3C: goto op_const_i32; \
         case 0x3D: goto op_const_f32; \
         case 0x3E: goto op_const; \
         case 0x3F: goto op_const; \
         \
         case 0x40: case 0x41: case 0x42: case 0x43: case 0x44: case 0x45: case 0x46: case 0x47: \
         case 0x48: case 0x49: case 0x4A: case 0x4B: case 0x4C: case 0x4D: case 0x4E: case 0x4F: \
         case 0x50: case 0x51: case 0x52: case 0x53: case 0x54: case 0x55: case 0x56: case 0x57: \
         case 0x58: case 0x59: case 0x5A: case 0x5B: case 0x5C: case 0x5D: case 0x5E: case 0x5F: \
            goto op_const; \
         \
         case 0x60: goto op_branch0; \
         case 0x61: case 0x62: case 0x63: case 0x64: case 0x65: case 0x66: case 0x67: \
            goto op_branch; \
         case 0x68: goto op_jump0; \
         case 0x69: case 0x6A: case 0x6B: case 0x6C: case 0x6D: case 0x6E: case 0x6F: \
            goto op_jump; \
         case 0x70: goto op_branch_long; \
         case 0x71: goto op_jump_long; \
         case 0x72: goto op_loop_i8; \
         case 0x73: goto op_loop_i16; \
         case 0x74: goto op_loop_i32; \
         \
         case 0x75: goto op_load_local; \
         case 0x76: goto op_store_local; \
         case 0x77: goto op_switch; \
         case 0x78: goto op_length; \
         case 0x79: goto op_const_string; \
         case 0x7A: goto op_string_concat; \
         case 0x7B: case 0x7C: \
            goto op_unused; \
         \
         case 0x7D: goto op_extended; \
         \
         case 0x7E: case 0x7F: \
            goto op_const; \
         \
         case 0x80: case 0x81: case 0x82: case 0x83: case 0x84: case 0x85: case 0x86: case 0x87: \
         case 0x88: case 0x89: case 0x8A: case 0x8B: case 0x8C: case 0x8D: case 0x8E: case 0x8F: \
         case 0x90: case 0x91: case 0x92: case 0x93: case 0x94: case 0x95: case 0x96: case 0x97: \
         case 0x98: case 0x99: case 0x9A: case 0x9B: case 0x9C: case 0x9D: case 0x9E: case 0x9F: \
         case 0xA0: case 0xA1: case 0xA2: case 0xA3: case 0xA4: case 0xA5: case 0xA6: case 0xA7: \
         case 0xA8: case 0xA9: case 0xAA: case 0xAB: case 0xAC: case 0xAD: case 0xAE: case 0xAF: \
         case 0xB0: case 0xB1: case 0xB2: case 0xB3: case 0xB4: case 0xB5: case 0xB6: case 0xB7: \
         case 0xB8: case 0xB9: case 0xBA: case 0xBB: case 0xBC: case 0xBD: case 0xBE: case 0xBF: \
            goto op_store; \
         case 0xC0: case 0xC1: case 0xC2: case 0xC3: case 0xC4: case 0xC5: case 0xC6: case 0xC7: \
         case 0xC8: case 0xC9: case 0xCA: case 0xCB: case 0xCC: case 0xCD: case 0xCE: case 0xCF: \
         case 0xD0: case 0xD1: case 0xD2: case 0xD3: case 0xD4: case 0xD5: case 0xD6: case 0xD7: \
         case 0xD8: case 0xD9: case 0xDA: case 0xDB: case 0xDC: case 0xDD: case 0xDE: case 0xDF: \
         case 0xE0: case 0xE1: case 0xE2: case 0xE3: case 0xE4: case 0xE5: case 0xE6: case 0xE7: \
         case 0xE8: case 0xE9: case 0xEA: case 0xEB: case 0xEC: case 0xED: case 0xEE: case 0xEF: \
         case 0xF0: case 0xF1: case 0xF2: case 0xF3: case 0xF4: case 0xF5: case 0xF6: case 0xF7: \
         case 0xF8: case 0xF9: case 0xFA: case 0xFB: case 0xFC: case 0xFD: case 0xFE: case 0xFF: \
            goto op_load; \
      }
   #define EXT_DISPATCH() \
      switch (bytecode[pc++]) { \
         case 0x00: goto op_ext_min; \
         case 0x01: goto op_ext_max; \
         case 0x02: goto op_ext_abs; \
         case 0x03: goto op_ext_add32; \
         case 0x04: goto op_ext_sub32; \
         case 0x05: goto op_ext_mul64; \
         case 0x06: goto op_ext_umul64; \
         case 0x07: goto op_ext_mul64_long; \
         case 0x08: goto op_ext_div64; \
         case 0x09: goto op_ext_udiv64; \
         case 0x0A: goto op_ext_rem64; \
         case 0x0B: goto op_ext_urem64; \
         case 0x0C: goto op_ext_float; \
         case 0x0D: goto op_ext_int; \
         case 0x0E: goto op_ext_fabs; \
         case 0x0F: goto op_ext_fmin; \
         case 0x10: goto op_ext_fmax; \
         case 0x11: goto op_ext_floor; \
         case 0x12: goto op_ext_ceil; \
         case 0x13: goto op_ext_round; \
         case 0x14: goto op_ext_pow; \
         case 0x15: goto op_ext_sqrt; \
         case 0x16: goto op_ext_cbrt; \
         case 0x17: goto op_ext_exp; \
         case 0x18: goto op_ext_ln; \
         case 0x19: goto op_ext_log2; \
         case 0x1A: goto op_ext_log10; \
         case 0x1B: goto op_ext_sin; \
         case 0x1C: goto op_ext_cos; \
         case 0x1D: goto op_ext_asin; \
         case 0x1E: goto op_ext_acos; \
         case 0x1F: goto op_ext_tan; \
         case 0x20: goto op_ext_atan; \
         case 0x21: goto op_ext_atan2; \
         case 0x22: goto op_ext_dbl_float; \
         case 0x23: goto op_ext_dbl_int; \
         case 0x24: goto op_ext_dbl_conv_down; \
         case 0x25: goto op_ext_dbl_conv_up; \
         case 0x26: goto op_ext_dbl_add; \
         case 0x27: goto op_ext_dbl_sub; \
         case 0x28: goto op_ext_dbl_mul; \
         case 0x29: goto op_ext_dbl_div; \
         case 0x2A: goto op_ext_dbl_cmp_lt; \
         case 0x2B: goto op_ext_dbl_cmp_le; \
         case 0x2C: goto op_ext_dbl_cmp_gt; \
         case 0x2D: goto op_ext_dbl_cmp_ge; \
         case 0x2E: goto op_ext_dbl_cmp_eq; \
         case 0x2F: goto op_ext_dbl_cmp_ne; \
         case 0x30: goto op_ext_dbl_fabs; \
         case 0x31: goto op_ext_dbl_fmin; \
         case 0x32: goto op_ext_dbl_fmax; \
         case 0x33: goto op_ext_dbl_floor; \
         case 0x34: goto op_ext_dbl_ceil; \
         case 0x35: goto op_ext_dbl_round; \
         case 0x36: goto op_ext_dbl_pow; \
         case 0x37: goto op_ext_dbl_sqrt; \
         case 0x38: goto op_ext_dbl_cbrt; \
         case 0x39: goto op_ext_dbl_exp; \
         case 0x3A: goto op_ext_dbl_ln; \
         case 0x3B: goto op_ext_dbl_log2; \
         case 0x3C: goto op_ext_dbl_log10; \
         case 0x3D: goto op_ext_dbl_sin; \
         case 0x3E: goto op_ext_dbl_cos; \
         case 0x3F: goto op_ext_dbl_asin; \
         case 0x40: goto op_ext_dbl_acos; \
         case 0x41: goto op_ext_dbl_tan; \
         case 0x42: goto op_ext_dbl_atan; \
         case 0x43: goto op_ext_dbl_atan2; \
         case 0x44: goto op_ext_is_int; \
         case 0x45: goto op_ext_is_float; \
         case 0x46: goto op_ext_is_array; \
         case 0x47: goto op_ext_is_string; \
         case 0x48: goto op_ext_is_hash; \
         case 0x49: goto op_ext_is_shared; \
         case 0x4A: goto op_ext_is_funcref; \
         case 0x4B: goto op_ext_is_weakref; \
         case 0x4C: goto op_ext_is_handle; \
      }
#endif

   unsigned char *bytecode = heap->bytecode;
   unsigned char bc;
   Array *stack;
   int *stack_data;
   int *stack_flags;
   Value params_on_stack[PARAMS_ON_STACK];

   #define REINIT() \
      stack = &heap->data[STACK_IDX]; \
      stack_data = stack->data; \
      stack_flags = stack->flags;

   #define ERROR(msg) \
      pc = emit_error(heap, msg, pc); \
      if (pc <= 0) return (pc < 0? 0 : 1); \
      REINIT(); \
      DISPATCH();
      
   REINIT();
   DISPATCH();
   for (;;) {
      op_pop: {
         stack->len--;
         DISPATCH();
      }

      op_popn: {
         int val = stack_data[stack->len-1] + 1;
         stack->len -= val;
         DISPATCH();
      }

      op_loadn: {
         int val = stack_data[stack->len-1];
         stack_data[stack->len-1] = stack_data[stack->len + val];
         DIRECT_ASSIGN_IS_ARRAY(stack_flags, stack->len-1, DIRECT_IS_ARRAY(stack_flags, stack->len + val));
         DISPATCH();
      }

      op_storen: {
         int val = stack_data[stack->len-1];
         stack_data[stack->len + val] = stack_data[stack->len-2];
         DIRECT_ASSIGN_IS_ARRAY(stack_flags, stack->len + val, DIRECT_IS_ARRAY(stack_flags, stack->len-2));
         stack->len -= 2;
         DISPATCH();
      }

      #define INT_OP(label, expr) \
      label: { \
         int val1 = stack_data[stack->len-2]; \
         int val2 = stack_data[stack->len-1]; \
         stack->len--; \
         stack_data[stack->len-1] = expr; \
         DIRECT_CLEAR_IS_ARRAY(stack_flags, stack->len-1); \
         DISPATCH(); \
      }

      #define INT_CHECKED_OP(label, expr) \
      label: { \
         int64_t val1 = stack_data[stack->len-2]; \
         int64_t val2 = stack_data[stack->len-1]; \
         int64_t result = expr; \
         if (result < INT_MIN || result > INT_MAX) { \
            ERROR("integer overflow"); \
         } \
         stack->len--; \
         stack_data[stack->len-1] = (int)result; \
         DIRECT_CLEAR_IS_ARRAY(stack_flags, stack->len-1); \
         DISPATCH(); \
      }

      INT_CHECKED_OP(op_add, val1 + val2)
      INT_CHECKED_OP(op_sub, val1 - val2)
      INT_CHECKED_OP(op_mul, val1 * val2)

      INT_OP(op_add_mod, ((unsigned int)val1) + ((unsigned int)val2))
      INT_OP(op_sub_mod, ((unsigned int)val1) - ((unsigned int)val2))
      INT_OP(op_mul_mod, ((unsigned int)val1) * ((unsigned int)val2))
      
      op_div: {
         int val1 = stack_data[stack->len-2];
         int val2 = stack_data[stack->len-1];
         if (val2 == 0) {
            ERROR("division by zero");
         }
         if (val2 == -1 && val1 == INT_MIN) {
            ERROR("integer overflow");
         }
         stack->len--;
         stack_data[stack->len-1] = val1 / val2;
         DIRECT_CLEAR_IS_ARRAY(stack_flags, stack->len-1);
         DISPATCH();
      }
      
      op_rem: {
         int val1 = stack_data[stack->len-2];
         int val2 = stack_data[stack->len-1];
         if (val2 == 0) {
            ERROR("division by zero");
         }
         stack->len--;
         stack_data[stack->len-1] = val1 % val2;
         DIRECT_CLEAR_IS_ARRAY(stack_flags, stack->len-1);
         DISPATCH();
      }

      INT_OP(op_shl,  val1 << (val2 & 31))
      INT_OP(op_shr,  val1 >> (val2 & 31))
      INT_OP(op_ushr, ((unsigned int)val1) >> (((unsigned int)val2) & 31))
      INT_OP(op_and,  val1 & val2)
      INT_OP(op_or,   val1 | val2)
      INT_OP(op_xor,  val1 ^ val2)

      INT_OP(op_lt, val1 < val2)
      INT_OP(op_le, val1 <= val2)
      INT_OP(op_gt, val1 > val2)
      INT_OP(op_ge, val1 >= val2)
      INT_OP(op_eq, val1 == val2)
      INT_OP(op_ne, val1 != val2)

      op_eq_value: {
         int val1 = stack_data[stack->len-2];
         int val2 = stack_data[stack->len-1];
         int is_array1 = DIRECT_IS_ARRAY(stack_flags, stack->len-2);
         int is_array2 = DIRECT_IS_ARRAY(stack_flags, stack->len-1);
         int ret = 1;
         stack->len--;

         if ((is_array1 && !is_array2) || (!is_array1 && is_array2)) {
            ret = 0;
         }
         else if (val1 != val2) {
            if (!compare_values(heap, (Value) { val1, is_array1 }, (Value) { val2, is_array2 }, MAX_COMPARE_RECURSION)) {
               ret = 0;
            }
         }

         if (bc == BC_NE_VALUE) {
            ret = !ret;
         }
         
         stack_data[stack->len-1] = ret;
         DIRECT_CLEAR_IS_ARRAY(stack_flags, stack->len-1);
         DISPATCH();
      }

      #define INT_UNARY_OP(label, expr) \
      label: { \
         int val = stack_data[stack->len-1]; \
         stack_data[stack->len-1] = expr; \
         DIRECT_CLEAR_IS_ARRAY(stack_flags, stack->len-1); \
         DISPATCH(); \
      }
      
      INT_UNARY_OP(op_bitnot, ~val)
      INT_UNARY_OP(op_lognot, !val)

      op_inc: {
         int pos = (signed char)bytecode[pc++];
         int val = stack_data[stack->len+pos];
         if (val == INT_MAX) {
            ERROR("integer overflow");
         }
         stack_data[stack->len+pos] = ((unsigned int)val) + 1U;
         DIRECT_CLEAR_IS_ARRAY(stack_flags, stack->len+pos);
         DISPATCH();
      }

      op_dec: {
         int pos = (signed char)bytecode[pc++];
         int val = stack_data[stack->len+pos];
         if (val == INT_MIN) {
            ERROR("integer overflow");
         }
         stack_data[stack->len+pos] = ((unsigned int)val) - 1U;
         DIRECT_CLEAR_IS_ARRAY(stack_flags, stack->len+pos);
         DISPATCH();
      }

      #define FLOAT_OP(label, expr) \
      label: { \
         union { \
            unsigned int i; \
            float f; \
         } u; \
         float val1, val2; \
         u.i = stack_data[stack->len-2]; \
         val1 = u.f; \
         u.i = stack_data[stack->len-1]; \
         val2 = u.f; \
         stack->len--; \
         u.f = expr; \
         /* flush denormals to zero: */ \
         if ((u.i & (0xFF << 23)) == 0) { \
            u.i &= ~((1<<23)-1); \
         } \
         stack_data[stack->len-1] = u.i; \
         DIRECT_SET_IS_ARRAY(stack_flags, stack->len-1); \
         DISPATCH(); \
      }

      #define FLOAT_UNARY_OP(label, expr) \
      label: { \
         union { \
            unsigned int i; \
            float f; \
         } u; \
         float val; \
         u.i = stack_data[stack->len-1]; \
         val = u.f; \
         u.f = expr; \
         /* flush denormals to zero: */ \
         if ((u.i & (0xFF << 23)) == 0) { \
            u.i &= ~((1<<23)-1); \
         } \
         stack_data[stack->len-1] = u.i; \
         DIRECT_SET_IS_ARRAY(stack_flags, stack->len-1); \
         DISPATCH(); \
      }

      #define FLOAT_CMP_OP(label, expr) \
      label: { \
         union { \
            unsigned int i; \
            float f; \
         } u; \
         float val1, val2; \
         u.i = stack_data[stack->len-2]; \
         val1 = u.f; \
         u.i = stack_data[stack->len-1]; \
         val2 = u.f; \
         stack->len--; \
         stack_data[stack->len-1] = expr; \
         DIRECT_CLEAR_IS_ARRAY(stack_flags, stack->len-1); \
         DISPATCH(); \
      }

      FLOAT_OP(op_float_add, val1 + val2)
      FLOAT_OP(op_float_sub, val1 - val2)
      FLOAT_OP(op_float_mul, val1 * val2)
      FLOAT_OP(op_float_div, val1 / val2)
      FLOAT_CMP_OP(op_float_lt, val1 < val2)
      FLOAT_CMP_OP(op_float_le, val1 <= val2)
      FLOAT_CMP_OP(op_float_gt, val1 > val2)
      FLOAT_CMP_OP(op_float_ge, val1 >= val2)
      FLOAT_CMP_OP(op_float_eq, val1 == val2)
      FLOAT_CMP_OP(op_float_ne, val1 != val2)
      
      op_return: {
         int num = stack_data[stack->len-1];
         int ret = stack_data[stack->len-2];
         int is_array = DIRECT_IS_ARRAY(stack_flags, stack->len-2);
         int ret_pc;
         stack->len -= num+1;
         ret_pc = stack_data[stack->len-1] & ~(1<<31);
         stack_data[stack->len-1] = ret;
         DIRECT_ASSIGN_IS_ARRAY(stack_flags, stack->len-1, is_array);
         if (ret_pc == 0) {
            break;
         }
         pc = ret_pc;
         DISPATCH();
      }
      
      op_return2: {
         int ret1 = stack_data[stack->len-2];
         int ret2 = stack_data[stack->len-1];
         int is_array1 = DIRECT_IS_ARRAY(stack_flags, stack->len-2);
         int is_array2 = DIRECT_IS_ARRAY(stack_flags, stack->len-1);
         int error_pc, stack_base;

         error_pc = (intptr_t)heap->error_stack.data[heap->error_stack.len-2];
         stack_base = (intptr_t)heap->error_stack.data[heap->error_stack.len-1];
         heap->error_stack.len -= 2;

         stack_data[stack_base+0] = ret1;
         DIRECT_ASSIGN_IS_ARRAY(stack_flags, stack_base+0, is_array1);

         stack_data[stack_base+1] = ret2;
         DIRECT_ASSIGN_IS_ARRAY(stack_flags, stack_base+1, is_array2);

         stack->len = stack_base+2;

         if (error_pc == 0) {
            return 1;
         }
         pc = error_pc;
         DISPATCH();
      }

      op_call_direct: {
         int func_id = stack_data[stack->len-1];
         Function *func;
         func = heap->functions.data[func_id];
         stack_data[stack->len - func->num_params-2] = pc | (1<<31);
         DIRECT_SET_IS_ARRAY(stack_flags, stack->len - func->num_params-2);
         stack->len--;
         pc = func->addr;
         DISPATCH();
      }

      op_call_dynamic: {
         int num_params = stack_data[stack->len-1];
         int func_id = stack_data[stack->len-num_params-2] - FUNC_REF_OFFSET;
         int is_array = DIRECT_IS_ARRAY(stack_flags, stack->len-num_params-2);
         Function *func;
         if (!is_array || func_id < 1 || func_id >= heap->functions.len) {
            ERROR("invalid function reference");
         }
         func = heap->functions.data[func_id];
         if (num_params != func->num_params) {
            ERROR("improper number of function parameters");
         }
         stack_data[stack->len - func->num_params-2] = pc | (1<<31);
         DIRECT_SET_IS_ARRAY(stack_flags, stack->len - func->num_params-2);
         stack->len--;
         pc = func->addr;
         DISPATCH();
      }

      op_call_native: {
         Value ret, error, *params;
         NativeFunction *nfunc;
         int i, base, nfunc_id;
         int error_pc, stack_base;
         
         nfunc_id = stack_data[stack->len-1];
         nfunc = heap->native_functions.data[nfunc_id];
         if (bc == BC_CALL2_NATIVE) {
            dynarray_add(&heap->error_stack, (void *)(intptr_t)(pc+1));
            dynarray_add(&heap->error_stack, (void *)(intptr_t)(stack->len - nfunc->num_params-2));
         }
         base = stack->len - nfunc->num_params - 1;
         stack_data[base-1] = pc | (1<<31);
         DIRECT_SET_IS_ARRAY(stack_flags, base-1);
         stack_data[stack->len-1] = nfunc->bytecode_ident_pc | (1<<31);
         DIRECT_SET_IS_ARRAY(stack_flags, stack->len-1);
         
         params = nfunc->num_params > PARAMS_ON_STACK? malloc(nfunc->num_params * sizeof(Value)) : params_on_stack;
         for (i=0; i<nfunc->num_params; i++) {
            params[i].value = stack_data[base+i];
            params[i].is_array = DIRECT_IS_ARRAY(stack_flags, base+i) != 0;
         }
         error = fixscript_int(0);
         ret = nfunc->func(heap, &error, nfunc->num_params, params, nfunc->data);
         if (nfunc->num_params > PARAMS_ON_STACK) {
            free(params);
         }
         clear_roots(heap);
         REINIT();
         bytecode = heap->bytecode;
         
         if (error.value) {
            error_pc = (intptr_t)heap->error_stack.data[heap->error_stack.len-2];
            stack_base = (intptr_t)heap->error_stack.data[heap->error_stack.len-1];
            heap->error_stack.len -= 2;

            stack_data[stack_base+0] = ret.value;
            DIRECT_ASSIGN_IS_ARRAY(stack_flags, stack_base+0, ret.is_array);

            stack_data[stack_base+1] = error.value;
            DIRECT_ASSIGN_IS_ARRAY(stack_flags, stack_base+1, error.is_array);

            stack->len = stack_base+2;

            if (error_pc == 0) {
               return 1;
            }
            pc = error_pc;
         }
         else {
            stack->len = base;
            stack_data[stack->len-1] = ret.value;
            DIRECT_ASSIGN_IS_ARRAY(stack_flags, stack->len-1, ret.is_array);
         }
         DISPATCH();
      }

      op_call2_direct: {
         int func_id = stack_data[stack->len-1];
         Function *func;
         func = heap->functions.data[func_id];
         stack_data[stack->len - func->num_params-2] = pc | (1<<31);
         DIRECT_SET_IS_ARRAY(stack_flags, stack->len - func->num_params-2);
         dynarray_add(&heap->error_stack, (void *)(intptr_t)(pc+1));
         dynarray_add(&heap->error_stack, (void *)(intptr_t)(stack->len - func->num_params-2));
         stack->len--;
         pc = func->addr;
         DISPATCH();
      }

      op_call2_dynamic: {
         int num_params = stack_data[stack->len-1];
         int func_id = stack_data[stack->len-num_params-2] - FUNC_REF_OFFSET;
         int is_array = DIRECT_IS_ARRAY(stack_flags, stack->len-num_params-2);
         Function *func;
         if (!is_array || func_id < 1 || func_id >= heap->functions.len) {
            ERROR("invalid function reference");
         }
         func = heap->functions.data[func_id];
         if (num_params != func->num_params) {
            ERROR("improper number of function parameters");
         }
         stack_data[stack->len - func->num_params-2] = pc | (1<<31);
         DIRECT_SET_IS_ARRAY(stack_flags, stack->len - func->num_params-2);
         dynarray_add(&heap->error_stack, (void *)(intptr_t)(pc+1));
         dynarray_add(&heap->error_stack, (void *)(intptr_t)(stack->len - func->num_params-2));
         stack->len--;
         pc = func->addr;
         DISPATCH();
      }

      op_clean_call2: {
         // note: no need to expand stack as there was at least 1 more value added during the preceding call
         DIRECT_CLEAR_IS_ARRAY(stack_flags, stack->len);
         stack_data[stack->len++] = 0;
         heap->error_stack.len -= 2;
         DISPATCH();
      }
      
      op_create_array: {
         int i, num, base;
         unsigned int val, max_value = 0;
         Value arr_val;
         Array *arr;
         
         num = stack_data[stack->len-1];
         base = stack->len - (num+1);
         for (i=0; i<num; i++) {
            val = stack_data[base+i];
            if ((unsigned int)val > max_value) {
               max_value = val;
            }
         }
         arr_val = create_array(heap, max_value <= 0xFF? ARR_BYTE : max_value <= 0xFFFF? ARR_SHORT : ARR_INT, num);
         if (!arr_val.is_array) {
            ERROR("out of memory");
         }
         REINIT();
         arr = &heap->data[arr_val.value];
         arr->len = num;
         for (i=0; i<num; i++) {
            set_array_value(arr, i, stack_data[base+i]);
            ASSIGN_IS_ARRAY(arr, i, DIRECT_IS_ARRAY(stack_flags, base+i));
         }
         DIRECT_SET_IS_ARRAY(stack_flags, base);
         stack_data[base] = arr_val.value;
         stack->len = base+1;
         DISPATCH();
      }

      op_create_hash: {
         Value hash_val;
         Value key, value;
         int i, num, base, err;
         
         num = stack_data[stack->len-1];
         base = stack->len - (num*2+1);
         hash_val = create_hash(heap);
         if (!hash_val.is_array) {
            ERROR("out of memory");
         }
         REINIT();
         for (i=0; i<num; i++) {
            key = (Value) { stack_data[base+i*2+0], DIRECT_IS_ARRAY(stack_flags, base+i*2+0) };
            value = (Value) { stack_data[base+i*2+1], DIRECT_IS_ARRAY(stack_flags, base+i*2+1) };
            err = fixscript_set_hash_elem(heap, hash_val, key, value);
            if (err != FIXSCRIPT_SUCCESS) {
               ERROR(fixscript_get_error_msg(err));
            }
         }
         DIRECT_SET_IS_ARRAY(stack_flags, base);
         stack_data[base] = hash_val.value;
         stack->len = base+1;
         DISPATCH();
      }

      op_array_get: {
         Array *arr;
         int arr_val = stack_data[stack->len-2];
         int arr_is_array = DIRECT_IS_ARRAY(stack_flags, stack->len-2);
         int idx = stack_data[stack->len-1];
         stack->len--;

         if (!arr_is_array || arr_val <= 0 || arr_val >= heap->size) {
            ERROR("invalid array access");
         }

         arr = &heap->data[arr_val];
         if (arr->len == -1 || arr->hash_slots >= 0) {
            ERROR("invalid array access");
         }

         if (idx < 0 || idx >= arr->len) {
            ERROR("array out of bounds access");
         }

         DIRECT_ASSIGN_IS_ARRAY(stack_flags, stack->len-1, IS_ARRAY(arr, idx));
         stack_data[stack->len-1] = get_array_value(arr, idx);
         DISPATCH();
      }

      op_array_set: {
         Array *arr;
         int arr_val = stack_data[stack->len-3];
         int arr_is_array = DIRECT_IS_ARRAY(stack_flags, stack->len-3);
         int idx = stack_data[stack->len-2];
         int value = stack_data[stack->len-1];
         int value_is_array = DIRECT_IS_ARRAY(stack_flags, stack->len-1);
         int err;
         stack->len -= 3;

         if (!arr_is_array || arr_val <= 0 || arr_val >= heap->size) {
            ERROR("invalid array access");
         }

         arr = &heap->data[arr_val];
         if (arr->len == -1 || arr->hash_slots >= 0) {
            ERROR("invalid array access");
         }

         if (arr->is_static) {
            ERROR("write access to static string");
         }

         if (arr->is_shared && value_is_array && ((unsigned int)value) > 0 && ((unsigned int)value) < (1 << 23)) {
            ERROR("invalid shared array operation");
         }

         if (idx < 0 || idx >= arr->len) {
            ERROR("array out of bounds access");
         }

         if (ARRAY_NEEDS_UPGRADE(arr, value)) {
            err = upgrade_array(heap, arr, value);
            if (err != FIXSCRIPT_SUCCESS) {
               if (err == FIXSCRIPT_ERR_INVALID_SHARED_ARRAY_OPERATION) {
                  ERROR("invalid shared array operation");
               }
               else {
                  ERROR("out of memory");
               }
            }
         }

         ASSIGN_IS_ARRAY(arr, idx, value_is_array);
         set_array_value(arr, idx, value);
         DISPATCH();
      }

      op_array_append: {
         Array *arr;
         int arr_val = stack_data[stack->len-2];
         int arr_is_array = DIRECT_IS_ARRAY(stack_flags, stack->len-2);
         int value = stack_data[stack->len-1];
         int value_is_array = DIRECT_IS_ARRAY(stack_flags, stack->len-1);
         stack->len -= 2;

         if (!arr_is_array || arr_val <= 0 || arr_val >= heap->size) {
            ERROR("invalid array access");
         }

         arr = &heap->data[arr_val];
         if (arr->len == -1 || arr->hash_slots >= 0) {
            ERROR("invalid array access");
         }

         if (arr->is_static) {
            ERROR("write access to static string");
         }

         if (arr->is_shared) {
            ERROR("invalid shared array operation");
         }

         if (ARRAY_NEEDS_UPGRADE(arr, value)) {
            if (upgrade_array(heap, arr, value) != FIXSCRIPT_SUCCESS) {
               ERROR("out of memory");
            }
         }

         if (arr->len == arr->size) {
            if (expand_array(heap, arr, arr->len) != FIXSCRIPT_SUCCESS) {
               ERROR("out of memory");
            }
         }

         ASSIGN_IS_ARRAY(arr, arr->len, value_is_array);
         set_array_value(arr, arr->len++, value);
         DISPATCH();
      }

      op_hash_get: {
         Array *arr;
         int hash_val = stack_data[stack->len-2];
         int hash_is_array = DIRECT_IS_ARRAY(stack_flags, stack->len-2);
         int key_val = stack_data[stack->len-1];
         int key_is_array = DIRECT_IS_ARRAY(stack_flags, stack->len-1);
         int err;
         Value value;
         stack->len--;

         if (!hash_is_array || hash_val <= 0 || hash_val >= heap->size) {
            ERROR("invalid hash access");
         }

         arr = &heap->data[hash_val];
         if (arr->len == -1 || arr->hash_slots < 0 || arr->is_handle) {
            ERROR("invalid hash access");
         }

         err = get_hash_elem(heap, arr, (Value) { key_val, key_is_array }, &value);
         if (err != FIXSCRIPT_SUCCESS) {
            ERROR(fixscript_get_error_msg(err));
         }

         stack_data[stack->len-1] = value.value;
         DIRECT_ASSIGN_IS_ARRAY(stack_flags, stack->len-1, value.is_array);
         DISPATCH();
      }

      op_hash_set: {
         Array *arr;
         int hash_val = stack_data[stack->len-3];
         int hash_is_array = DIRECT_IS_ARRAY(stack_flags, stack->len-3);
         int key_val = stack_data[stack->len-2];
         int key_is_array = DIRECT_IS_ARRAY(stack_flags, stack->len-2);
         int value = stack_data[stack->len-1];
         int value_is_array = DIRECT_IS_ARRAY(stack_flags, stack->len-1);
         int err;
         stack->len -= 3;

         if (!hash_is_array || hash_val <= 0 || hash_val >= heap->size) {
            ERROR("invalid hash access");
         }

         arr = &heap->data[hash_val];
         if (arr->len == -1 || arr->hash_slots < 0 || arr->is_handle) {
            ERROR("invalid hash access");
         }

         err = fixscript_set_hash_elem(heap, (Value) { hash_val, hash_is_array }, (Value) { key_val, key_is_array }, (Value) { value, value_is_array });
         if (err != FIXSCRIPT_SUCCESS) {
            ERROR(fixscript_get_error_msg(err));
         }
         DISPATCH();
      }

      op_const_p8: {
         int val = (int)bytecode[pc++] + 1;
         if (stack->len == stack->size) {
            if (!expand_stack(heap, stack)) return 0;
            REINIT();
         }
         DIRECT_CLEAR_IS_ARRAY(stack_flags, stack->len);
         stack_data[stack->len++] = val;
         DISPATCH();
      }

      op_const_n8: {
         int val = -((int)bytecode[pc++]+1);
         if (stack->len == stack->size) {
            if (!expand_stack(heap, stack)) return 0;
            REINIT();
         }
         DIRECT_CLEAR_IS_ARRAY(stack_flags, stack->len);
         stack_data[stack->len++] = val;
         DISPATCH();
      }

      op_const_p16: {
         unsigned short short_val;
         int val;
         memcpy(&short_val, &bytecode[pc], sizeof(unsigned short));
         pc += 2;
         val = (int)short_val + 1;
         if (stack->len == stack->size) {
            if (!expand_stack(heap, stack)) return 0;
            REINIT();
         }
         DIRECT_CLEAR_IS_ARRAY(stack_flags, stack->len);
         stack_data[stack->len++] = val;
         DISPATCH();
      }

      op_const_n16: {
         unsigned short short_val;
         int val;
         memcpy(&short_val, &bytecode[pc], sizeof(unsigned short));
         pc += 2;
         val = -((int)short_val + 1);
         if (stack->len == stack->size) {
            if (!expand_stack(heap, stack)) return 0;
            REINIT();
         }
         DIRECT_CLEAR_IS_ARRAY(stack_flags, stack->len);
         stack_data[stack->len++] = val;
         DISPATCH();
      }

      op_const_i32: {
         int val;
         memcpy(&val, &bytecode[pc], sizeof(int));
         pc += 4;
         if (stack->len == stack->size) {
            if (!expand_stack(heap, stack)) return 0;
            REINIT();
         }
         DIRECT_CLEAR_IS_ARRAY(stack_flags, stack->len);
         stack_data[stack->len++] = val;
         DISPATCH();
      }

      op_const_f32: {
         unsigned int val;
         memcpy(&val, &bytecode[pc], sizeof(int));
         pc += 4;
         if (stack->len == stack->size) {
            if (!expand_stack(heap, stack)) return 0;
            REINIT();
         }
         DIRECT_SET_IS_ARRAY(stack_flags, stack->len);
         stack_data[stack->len++] = val;
         DISPATCH();
      }

      op_const: {
         int val = (int)bc - 0x3F;
         if (stack->len == stack->size) {
            if (!expand_stack(heap, stack)) return 0;
            REINIT();
         }
         DIRECT_CLEAR_IS_ARRAY(stack_flags, stack->len);
         stack_data[stack->len++] = val;
         DISPATCH();
      }

      op_branch0: {
         int val = stack_data[--stack->len];
         int inc = (int)bytecode[pc++];
         if (!val) {
            pc += inc;
         }
         DISPATCH();
      }

      op_jump0: {
         int inc = (int)bytecode[pc++];
         pc += inc;
         DISPATCH();
      }

      op_branch: {
         int val = stack_data[--stack->len];
         int inc = (((int)bc & 7) << 8) | (int)bytecode[pc++];
         if (!val) {
            pc += inc;
         }
         DISPATCH();
      }

      op_jump: {
         int inc = (((int)bc & 7) << 8) | (int)bytecode[pc++];
         pc += inc;
         DISPATCH();
      }

      op_branch_long: {
         int val = stack_data[--stack->len];
         int inc;
         memcpy(&inc, &bytecode[pc], sizeof(int));
         pc += 4;
         if (!val) {
            pc += inc;
         }
         DISPATCH();
      }

      op_jump_long: {
         int inc;
         memcpy(&inc, &bytecode[pc], sizeof(int));
         pc += 4 + inc;
         DISPATCH();
      }

      op_loop_i8: {
         int dec = (int)bytecode[pc];
         pc -= dec;
         DISPATCH();
      }

      op_loop_i16: {
         unsigned short dec;
         memcpy(&dec, &bytecode[pc], sizeof(unsigned short));
         pc -= (int)dec;
         DISPATCH();
      }

      op_loop_i32: {
         int dec;
         memcpy(&dec, &bytecode[pc], sizeof(int));
         pc -= dec;
         DISPATCH();
      }

      op_load_local: {
         Array *arr;
         int idx;
         memcpy(&idx, &bytecode[pc], sizeof(int));
         pc += 4;
         arr = &heap->data[LOCALS_IDX];
         if (stack->len == stack->size) {
            if (!expand_stack(heap, stack)) return 0;
            REINIT();
         }
         DIRECT_ASSIGN_IS_ARRAY(stack_flags, stack->len, IS_ARRAY(arr, idx));
         stack_data[stack->len++] = arr->data[idx];
         DISPATCH();
      }

      op_store_local: {
         int val = stack_data[stack->len-1];
         int is_array = DIRECT_IS_ARRAY(stack_flags, stack->len-1);
         Array *arr;
         int idx;
         
         stack->len--;
         memcpy(&idx, &bytecode[pc], sizeof(int));
         pc += 4;
         arr = &heap->data[LOCALS_IDX];
         arr->data[idx] = val;
         ASSIGN_IS_ARRAY(arr, idx, is_array);
         DISPATCH();
      }

      op_switch: {
         int val = stack_data[stack->len-1];
         int table_idx;
         int *table;
         int size, default_pc, case_pc;
         int i;

         memcpy(&table_idx, &bytecode[pc], sizeof(int));
         pc += 4;
         table = &((int *)bytecode)[table_idx];
         size = table[-2];
         default_pc = table[-1];
         pc = default_pc;
         if (size > 0) {
            // TODO: change to binary search
            for (i=size-1; i>=0; i--) {
               if (val >= table[i*2+0]) {
                  case_pc = table[i*2+1];
                  if (case_pc == 0) {
                     if (val != table[i*2+0]) {
                        break;
                     }
                     pc = -table[(i-1)*2+1];
                  }
                  else if (case_pc < 0) {
                     pc = -case_pc;
                  }
                  else if (val == table[i*2+0]) {
                     pc = case_pc;
                  }
                  break;
               }
            }
         }
         stack->len--;
         DISPATCH();
      }

      op_length: {
         Array *arr;
         int arr_val = stack_data[stack->len-1];
         int arr_is_array = DIRECT_IS_ARRAY(stack_flags, stack->len-1);

         if (!arr_is_array || arr_val <= 0 || arr_val >= heap->size) {
            ERROR("invalid array or hash access");
         }

         arr = &heap->data[arr_val];
         if (arr->len == -1) {
            ERROR("invalid array or hash access");
         }

         DIRECT_CLEAR_IS_ARRAY(stack_flags, stack->len-1);
         stack_data[stack->len-1] = arr->len;
         DISPATCH();
      }

      op_const_string: {
         DIRECT_SET_IS_ARRAY(stack_flags, stack->len-1);
         DISPATCH();
      }

      op_string_concat: {
         Value value, result;
         int i, num, base, total_len = 0, len, err;
         struct {
            char *str;
            int len;
         } *strings = NULL;
         char *s;

         num = stack_data[stack->len-1];
         base = stack->len - (num+1);

         strings = calloc(num, sizeof(*strings));
         if (!strings) {
            ERROR("out of memory");
         }

         for (i=0; i<num; i++) {
            value = (Value) { stack_data[base+i], DIRECT_IS_ARRAY(stack_flags, base+i) };
            if (!fixscript_is_string(heap, value)) {
               err = fixscript_to_string(heap, value, 0, &strings[i].str, &len);
            }
            else {
               err = fixscript_get_string(heap, value, 0, -1, &strings[i].str, &len);
            }
            if (err != FIXSCRIPT_SUCCESS) {
               for (i=0; i<num; i++) {
                  free(strings[i].str);
               }
               free(strings);
               ERROR(fixscript_get_error_msg(err));
            }
            strings[i].len = len;
            total_len += len;
         }
         stack->len = base;

         s = malloc(total_len);
         if (!s) {
            for (i=0; i<num; i++) {
               free(strings[i].str);
            }
            free(strings);
            ERROR("out of memory");
         }

         for (i=0, len=0; i<num; i++) {
            memcpy(s + len, strings[i].str, strings[i].len);
            len += strings[i].len;
         }

         result = fixscript_create_string(heap, s, total_len);

         for (i=0; i<num; i++) {
            free(strings[i].str);
         }
         free(strings);
         free(s);

         if (!result.value) {
            return 0;
         }

         REINIT();

         stack_data[base] = result.value;
         DIRECT_ASSIGN_IS_ARRAY(stack_flags, base, result.is_array);
         stack->len++;
         DISPATCH();
      }

      op_store: {
         int pos = (signed char)(bc) + 0x40;
         stack_data[stack->len+pos] = stack_data[stack->len-1];
         DIRECT_ASSIGN_IS_ARRAY(stack_flags, stack->len+pos, DIRECT_IS_ARRAY(stack_flags, stack->len-1));
         stack->len--;
         DISPATCH();
      }

      op_load: {
         int pos = (signed char)bc;
         if (stack->len == stack->size) {
            if (!expand_stack(heap, stack)) return 0;
            REINIT();
         }
         stack_data[stack->len] = stack_data[stack->len + pos];
         DIRECT_ASSIGN_IS_ARRAY(stack_flags, stack->len, DIRECT_IS_ARRAY(stack_flags, stack->len + pos));
         stack->len++;
         DISPATCH();
      }

      op_extended: {
         EXT_DISPATCH()
      }

      op_unused: {
         return 0;
      }

      // extended bytecodes:

      INT_OP(op_ext_min, val1 < val2? val1 : val2)
      INT_OP(op_ext_max, val1 > val2? val1 : val2)

      op_ext_abs: {
         int val = stack_data[stack->len-1];
         if (val == INT_MIN) {
            ERROR("integer overflow");
         }
         stack_data[stack->len-1] = val < 0? -val : val;
         DIRECT_CLEAR_IS_ARRAY(stack_flags, stack->len-1);
         DISPATCH();
      }
      
      #define INT_ADD32(label,expr) \
      label: { \
         uint64_t val1 = (uint32_t)stack_data[stack->len-3]; \
         uint64_t val2 = (uint32_t)stack_data[stack->len-2]; \
         uint64_t val3 = (uint32_t)stack_data[stack->len-1]; \
         uint64_t result = expr; \
         stack->len--; \
         stack_data[stack->len-2] = (int)result; \
         stack_data[stack->len-1] = (int)((result >> 32) & 1); \
         DIRECT_CLEAR_IS_ARRAY(stack_flags, stack->len-2); \
         DIRECT_CLEAR_IS_ARRAY(stack_flags, stack->len-1); \
         DISPATCH(); \
      }

      INT_ADD32(op_ext_add32, val1 + val2 + (val3 & 1))
      INT_ADD32(op_ext_sub32, val1 - val2 - (val3 & 1))
      
      #define INT_MUL64(label,type,expr) \
      label: { \
         type val1 = stack_data[stack->len-2]; \
         type val2 = stack_data[stack->len-1]; \
         type result = expr; \
         stack_data[stack->len-2] = (int)result; \
         stack_data[stack->len-1] = (int)(((uint64_t)result) >> 32); \
         DIRECT_CLEAR_IS_ARRAY(stack_flags, stack->len-2); \
         DIRECT_CLEAR_IS_ARRAY(stack_flags, stack->len-1); \
         DISPATCH(); \
      }

      INT_MUL64(op_ext_mul64, int64_t, val1 * val2)
      INT_MUL64(op_ext_umul64, uint64_t, (uint64_t)(uint32_t)val1 * (uint64_t)(uint32_t)val2)
      
      #define INT_OP64(label,type,expr,check) \
      label: { \
         type val1_lo = stack_data[stack->len-4]; \
         type val1_hi = stack_data[stack->len-3]; \
         type val2_lo = stack_data[stack->len-2]; \
         type val2_hi = stack_data[stack->len-1]; \
         type val1 = ((uint32_t)val1_lo) | (val1_hi << 32); \
         type val2 = ((uint32_t)val2_lo) | (val2_hi << 32); \
         check \
         type result = expr; \
         stack->len -= 2; \
         stack_data[stack->len-2] = (int)result; \
         stack_data[stack->len-1] = (int)(((uint64_t)result) >> 32); \
         DIRECT_CLEAR_IS_ARRAY(stack_flags, stack->len-2); \
         DIRECT_CLEAR_IS_ARRAY(stack_flags, stack->len-1); \
         DISPATCH(); \
      }

      INT_OP64(op_ext_mul64_long, int64_t, val1 * val2, /* nothing */)

      #define DIV_CHECK \
         if (val2 == 0) { \
            ERROR("division by zero"); \
         }

      #define DIV_OVERFLOW_CHECK \
         if (val2 == -1 && val1 == INT64_MIN) { \
            ERROR("integer overflow"); \
         }

      INT_OP64(op_ext_div64, int64_t, val1 / val2, DIV_CHECK DIV_OVERFLOW_CHECK)
      INT_OP64(op_ext_udiv64, uint64_t, val1 / val2, DIV_CHECK)
      INT_OP64(op_ext_rem64, int64_t, val1 % val2, DIV_CHECK DIV_OVERFLOW_CHECK)
      INT_OP64(op_ext_urem64, uint64_t, val1 % val2, DIV_CHECK)

      op_ext_float: {
         union {
            unsigned int i;
            float f;
         } u;
         u.f = (float)stack_data[stack->len-1];
         // flush denormals to zero:
         if ((u.i & (0xFF << 23)) == 0) {
            u.i &= ~((1<<23)-1);
         }
         stack_data[stack->len-1] = u.i;
         DIRECT_SET_IS_ARRAY(stack_flags, stack->len-1);
         DISPATCH();
      }

      op_ext_int: {
         union {
            int i;
            float f;
         } u;
         u.i = stack_data[stack->len-1];
         stack_data[stack->len-1] = (int)u.f;
         DIRECT_CLEAR_IS_ARRAY(stack_flags, stack->len-1);
         DISPATCH();
      }

      FLOAT_UNARY_OP(op_ext_fabs, fabsf(val))
      FLOAT_OP(op_ext_fmin, fminf(val1, val2))
      FLOAT_OP(op_ext_fmax, fmaxf(val1, val2))
      FLOAT_UNARY_OP(op_ext_floor, floorf(val))
      FLOAT_UNARY_OP(op_ext_ceil, ceilf(val))
      FLOAT_UNARY_OP(op_ext_round, roundf(val))
      FLOAT_OP(op_ext_pow, powf(val1, val2))
      FLOAT_UNARY_OP(op_ext_sqrt, sqrtf(val))
      FLOAT_UNARY_OP(op_ext_cbrt, cbrtf(val))
      FLOAT_UNARY_OP(op_ext_exp, expf(val))
      FLOAT_UNARY_OP(op_ext_ln, logf(val))
      FLOAT_UNARY_OP(op_ext_log2, log2f(val))
      FLOAT_UNARY_OP(op_ext_log10, log10f(val))
      FLOAT_UNARY_OP(op_ext_sin, sinf(val))
      FLOAT_UNARY_OP(op_ext_cos, cosf(val))
      FLOAT_UNARY_OP(op_ext_asin, asinf(val))
      FLOAT_UNARY_OP(op_ext_acos, acosf(val))
      FLOAT_UNARY_OP(op_ext_tan, tanf(val))
      FLOAT_UNARY_OP(op_ext_atan, atanf(val))
      FLOAT_OP(op_ext_atan2, atan2f(val1, val2))

      op_ext_dbl_float: {
         union {
            uint64_t i;
            double f;
         } u;
         uint32_t lo, hi;
         lo = stack_data[stack->len-2];
         hi = stack_data[stack->len-1];
         u.f = (double)(int64_t)(((uint64_t)lo) | (((uint64_t)hi)<<32));
         stack_data[stack->len-2] = (uint32_t)u.i;
         stack_data[stack->len-1] = (uint32_t)(u.i>>32);
         DIRECT_CLEAR_IS_ARRAY(stack_flags, stack->len-2);
         DIRECT_CLEAR_IS_ARRAY(stack_flags, stack->len-1);
         DISPATCH();
      }

      op_ext_dbl_int: {
         union {
            uint64_t i;
            double f;
         } u;
         uint32_t lo, hi;
         int64_t result;
         lo = stack_data[stack->len-2];
         hi = stack_data[stack->len-1];
         u.i = (((uint64_t)lo) | (((uint64_t)hi)<<32));
         result = (int64_t)u.f;
         stack_data[stack->len-2] = (uint32_t)(uint64_t)result;
         stack_data[stack->len-1] = (uint32_t)(((uint64_t)result) >> 32);
         DIRECT_CLEAR_IS_ARRAY(stack_flags, stack->len-2);
         DIRECT_CLEAR_IS_ARRAY(stack_flags, stack->len-1);
         DISPATCH();
      }

      op_ext_dbl_conv_down: {
         union {
            unsigned int i;
            float f;
         } u32;
         union {
            uint64_t i;
            double f;
         } u;
         uint32_t lo, hi;
         lo = stack_data[stack->len-2];
         hi = stack_data[stack->len-1];
         u.i = ((uint64_t)lo) | (((uint64_t)hi)<<32);
         u32.f = (float)u.f;
         // flush denormals to zero:
         if ((u32.i & (0xFF << 23)) == 0) {
            u32.i &= ~((1<<23)-1);
         }
         stack_data[stack->len-2] = u32.i;
         DIRECT_SET_IS_ARRAY(stack_flags, stack->len-2);
         stack->len--;
         DISPATCH();
      }

      op_ext_dbl_conv_up: {
         union {
            unsigned int i;
            float f;
         } u32;
         union {
            uint64_t i;
            double f;
         } u;
         if (stack->len == stack->size) {
            if (!expand_stack(heap, stack)) return 0;
            REINIT();
         }
         u32.i = stack_data[stack->len-1];
         u.f = u32.f;
         stack->len++;
         stack_data[stack->len-2] = (uint32_t)u.i;
         stack_data[stack->len-1] = (uint32_t)(u.i >> 32);
         DIRECT_CLEAR_IS_ARRAY(stack_flags, stack->len-2);
         DIRECT_CLEAR_IS_ARRAY(stack_flags, stack->len-1);
         DISPATCH();
      }

      #define DOUBLE_OP(label, expr) \
      label: { \
         union { \
            uint64_t i; \
            double f; \
         } u; \
         uint32_t lo, hi; \
         double val1, val2; \
         lo = stack_data[stack->len-4]; \
         hi = stack_data[stack->len-3]; \
         u.i = ((uint64_t)lo) | (((uint64_t)hi)<<32); \
         val1 = u.f; \
         lo = stack_data[stack->len-2]; \
         hi = stack_data[stack->len-1]; \
         u.i = ((uint64_t)lo) | (((uint64_t)hi)<<32); \
         val2 = u.f; \
         stack->len -= 2; \
         u.f = expr; \
         stack_data[stack->len-2] = (uint32_t)u.i; \
         stack_data[stack->len-1] = (uint32_t)(u.i >> 32); \
         DIRECT_CLEAR_IS_ARRAY(stack_flags, stack->len-2); \
         DIRECT_CLEAR_IS_ARRAY(stack_flags, stack->len-1); \
         DISPATCH(); \
      }

      #define DOUBLE_UNARY_OP(label, expr) \
      label: { \
         union { \
            uint64_t i; \
            double f; \
         } u; \
         uint32_t lo, hi; \
         double val; \
         lo = stack_data[stack->len-2]; \
         hi = stack_data[stack->len-1]; \
         u.i = ((uint64_t)lo) | (((uint64_t)hi)<<32); \
         val = u.f; \
         u.f = expr; \
         stack_data[stack->len-2] = (uint32_t)u.i; \
         stack_data[stack->len-1] = (uint32_t)(u.i >> 32); \
         DIRECT_CLEAR_IS_ARRAY(stack_flags, stack->len-2); \
         DIRECT_CLEAR_IS_ARRAY(stack_flags, stack->len-1); \
         DISPATCH(); \
      }

      #define DOUBLE_CMP_OP(label, expr) \
      label: { \
         union { \
            uint64_t i; \
            double f; \
         } u; \
         uint32_t lo, hi; \
         double val1, val2; \
         lo = stack_data[stack->len-4]; \
         hi = stack_data[stack->len-3]; \
         u.i = ((uint64_t)lo) | (((uint64_t)hi)<<32); \
         val1 = u.f; \
         lo = stack_data[stack->len-2]; \
         hi = stack_data[stack->len-1]; \
         u.i = ((uint64_t)lo) | (((uint64_t)hi)<<32); \
         val2 = u.f; \
         stack->len -= 3; \
         stack_data[stack->len-1] = expr; \
         DIRECT_CLEAR_IS_ARRAY(stack_flags, stack->len-1); \
         DISPATCH(); \
      }

      DOUBLE_OP(op_ext_dbl_add, val1 + val2)
      DOUBLE_OP(op_ext_dbl_sub, val1 - val2)
      DOUBLE_OP(op_ext_dbl_mul, val1 * val2)
      DOUBLE_OP(op_ext_dbl_div, val1 / val2)

      DOUBLE_CMP_OP(op_ext_dbl_cmp_lt, val1 < val2)
      DOUBLE_CMP_OP(op_ext_dbl_cmp_le, val1 <= val2)
      DOUBLE_CMP_OP(op_ext_dbl_cmp_gt, val1 > val2)
      DOUBLE_CMP_OP(op_ext_dbl_cmp_ge, val1 >= val2)
      DOUBLE_CMP_OP(op_ext_dbl_cmp_eq, val1 == val2)
      DOUBLE_CMP_OP(op_ext_dbl_cmp_ne, val1 != val2)

      DOUBLE_UNARY_OP(op_ext_dbl_fabs, fabs(val))
      DOUBLE_OP(op_ext_dbl_fmin, fmin(val1, val2))
      DOUBLE_OP(op_ext_dbl_fmax, fmax(val1, val2))
      DOUBLE_UNARY_OP(op_ext_dbl_floor, floor(val))
      DOUBLE_UNARY_OP(op_ext_dbl_ceil, ceil(val))
      DOUBLE_UNARY_OP(op_ext_dbl_round, round(val))
      DOUBLE_OP(op_ext_dbl_pow, pow(val1, val2))
      DOUBLE_UNARY_OP(op_ext_dbl_sqrt, sqrt(val))
      DOUBLE_UNARY_OP(op_ext_dbl_cbrt, cbrt(val))
      DOUBLE_UNARY_OP(op_ext_dbl_exp, exp(val))
      DOUBLE_UNARY_OP(op_ext_dbl_ln, log(val))
      DOUBLE_UNARY_OP(op_ext_dbl_log2, log2(val))
      DOUBLE_UNARY_OP(op_ext_dbl_log10, log10(val))
      DOUBLE_UNARY_OP(op_ext_dbl_sin, sin(val))
      DOUBLE_UNARY_OP(op_ext_dbl_cos, cos(val))
      DOUBLE_UNARY_OP(op_ext_dbl_asin, asin(val))
      DOUBLE_UNARY_OP(op_ext_dbl_acos, acos(val))
      DOUBLE_UNARY_OP(op_ext_dbl_tan, tan(val))
      DOUBLE_UNARY_OP(op_ext_dbl_atan, atan(val))
      DOUBLE_OP(op_ext_dbl_atan2, atan2(val1, val2))

      op_ext_is_int: {
         stack_data[stack->len-1] = DIRECT_IS_ARRAY(stack_flags, stack->len-1) == 0;
         DIRECT_CLEAR_IS_ARRAY(stack_flags, stack->len-1);
         DISPATCH();
      }

      op_ext_is_float: {
         unsigned int value = stack_data[stack->len-1];
         int is_array = DIRECT_IS_ARRAY(stack_flags, stack->len-1);
         stack_data[stack->len-1] = is_array && (value == 0 || value >= (1 << 23));
         DIRECT_CLEAR_IS_ARRAY(stack_flags, stack->len-1);
         DISPATCH();
      }

      op_ext_is_array:
      op_ext_is_string:
      op_ext_is_hash:
      op_ext_is_shared:
      op_ext_is_funcref:
      op_ext_is_weakref:
      op_ext_is_handle: {
         Array *arr;
         int value = stack_data[stack->len-1];
         int is_array = DIRECT_IS_ARRAY(stack_flags, stack->len-1);
         int result = 0;
         int ebc = bytecode[pc-1];
         int func_id;

         if (is_array && value > 0 && value < heap->size) {
            arr = &heap->data[value];
            if (arr->len != -1) {
               if (ebc == BC_EXT_IS_HANDLE) {
                  result = arr->is_handle != 0 && arr->type != FUNC_REF_HANDLE_TYPE;
               }
               else if (ebc == BC_EXT_IS_HASH) {
                  result = (arr->hash_slots >= 0 && !arr->is_handle);
               }
               else if (ebc == BC_EXT_IS_FUNCREF) {
                  result = arr->is_handle && arr->type == FUNC_REF_HANDLE_TYPE;
               }
               else if (ebc == BC_EXT_IS_WEAKREF) {
                  result = arr->is_handle && arr->type == WEAK_REF_HANDLE_TYPE;
               }
               else if (arr->hash_slots < 0) {
                  if (ebc == BC_EXT_IS_SHARED) {
                     result = arr->is_shared != 0;
                  }
                  else {
                     result = (ebc == BC_EXT_IS_STRING)? arr->is_string : 1;
                  }
               }
            }
         }
         else if (is_array && ebc == BC_EXT_IS_FUNCREF) {
            func_id = value - FUNC_REF_OFFSET;
            if (func_id > 0 && func_id < heap->functions.len) {
               result = 1;
            }
         }

         stack_data[stack->len-1] = result;
         DIRECT_CLEAR_IS_ARRAY(stack_flags, stack->len-1);
         DISPATCH();
      }
   }

   #undef REINIT
   #undef ERROR
   #undef INT_OP
   #undef INT_CHECKED_OP
   #undef INT_UNARY_OP
   #undef FLOAT_OP
   #undef FLOAT_UNARY_OP
   #undef FLOAT_CMP_OP
   #undef INT_ADD32
   #undef INT_MUL64
   #undef INT_OP64
   #undef DIV_CHECK
   #undef DIV_OVERFLOW_CHECK

   return 1;
}


static Value run(Heap *heap, Function *func, const char *func_name, Value *error, Value *args, va_list *ap)
{
   Value stack = (Value) { STACK_IDX, 1 };
   Value ret = fixscript_int(0);
   int i, value, is_array, stack_base=0, error_stack_base=0, num_results=0;
   char *s;

   fixscript_get_array_length(heap, stack, &stack_base);
   fixscript_append_array_elem(heap, stack, fixscript_int(0));
   if (args) {
      for (i=0; i<func->num_params; i++) {
         fixscript_append_array_elem(heap, stack, args[i]);
      }
   }
   else {
      for (i=0; i<func->num_params; i++) {
#if defined(__LP64__) || defined(__LLP64__)
         uint64_t val64 = va_arg(*ap, uint64_t);
         value = val64;
         is_array = val64 >> 32;
#else
         value = va_arg(*ap, int);
         is_array = va_arg(*ap, int);
#endif
         fixscript_append_array_elem(heap, stack, (Value) { value, is_array });
      }
   }

   clear_roots(heap);

   error_stack_base = heap->error_stack.len;
   dynarray_add(&heap->error_stack, (void *)0);
   dynarray_add(&heap->error_stack, (void *)(intptr_t)stack_base);

   if (!run_bytecode(heap, func->addr)) {
      fixscript_set_array_length(heap, stack, stack_base);
      heap->error_stack.len = error_stack_base;
      if (error) {
         *error = fixscript_create_string(heap, "stack overflow", -1);
         if (!error->value) {
            *error = fixscript_int(1);
         }
      }
      return fixscript_int(0);
   }

   fixscript_get_array_length(heap, stack, &num_results);
   num_results -= stack_base;

   if (num_results > 2) {
      if (error) {
         if (!func_name) {
            func_name = string_hash_find_name(&func->script->functions, func);
         }
         s = string_format("internal error: more than two results after call to function %s", func_name);
         *error = fixscript_create_string(heap, s, -1);
         free(s);
      }
      return fixscript_int(0);
   }
   if (num_results < 1) {
      if (error) {
         if (!func_name) {
            func_name = string_hash_find_name(&func->script->functions, func);
         }
         s = string_format("internal error: less than one result after call to function %s", func_name);
         *error = fixscript_create_string(heap, s, -1);
         free(s);
      }
      return fixscript_int(0);
   }

   if (error) {
      if (num_results == 2) {
         fixscript_get_array_elem(heap, stack, stack_base+1, error);
      }
      else {
         *error = fixscript_int(0);
      }
   }
   fixscript_get_array_elem(heap, stack, stack_base, &ret);
   fixscript_set_array_length(heap, stack, stack_base);
   heap->error_stack.len = error_stack_base;
   return ret;
}


static Value run_func(Heap *heap, Script *script, const char *func_name, Value *error, Value *args, va_list *ap)
{
   Function *func;
   char *s;

   if (!script) {
      clear_roots(heap);
      if (error) {
         *error = fixscript_create_string(heap, "script not provided", -1);
      }
      return fixscript_int(0);
   }

   func = string_hash_get(&script->functions, func_name);
   if (!func) {
      clear_roots(heap);
      if (error) {
         s = string_format("function %s not found", func_name);
         *error = fixscript_create_string(heap, s, -1);
         free(s);
      }
      return fixscript_int(0);
   }

   return run(heap, func, func_name, error, args, ap);
}


Value fixscript_run(Heap *heap, Script *script, const char *func_name, Value *error, ...)
{
   Value ret;
   va_list ap;

   va_start(ap, error);
   ret = run_func(heap, script, func_name, error, NULL, &ap);
   va_end(ap);
   return ret;
}


Value fixscript_run_args(Heap *heap, Script *script, const char *func_name, Value *error, Value *args)
{
   return run_func(heap, script, func_name, error, args, NULL);
}


static Value call_func(Heap *heap, Value func, int num_params, Value *error, Value *args, va_list *ap)
{
   Function *fn;
   int func_id = func.value - FUNC_REF_OFFSET;

   if (!func.is_array || func_id < 1 || func_id >= heap->functions.len) {
      if (error) {
         *error = fixscript_create_string(heap, "invalid function reference", -1);
      }
      return fixscript_int(0);
   }
   
   fn = heap->functions.data[func_id];
   if (num_params != fn->num_params) {
      if (error) {
         *error = fixscript_create_string(heap, "improper number of function parameters", -1);
      }
      return fixscript_int(0);
   }

   return run(heap, fn, NULL, error, args, ap);
}


Value fixscript_call(Heap *heap, Value func, int num_params, Value *error, ...)
{
   Value ret;
   va_list ap;

   va_start(ap, error);
   ret = call_func(heap, func, num_params, error, NULL, &ap);
   va_end(ap);
   return ret;
}


Value fixscript_call_args(Heap *heap, Value func, int num_params, Value *error, Value *args)
{
   return call_func(heap, func, num_params, error, args, NULL);
}


void fixscript_register_native_func(Heap *heap, const char *name, NativeFunc func, void *data)
{
   NativeFunction *nfunc;
   char *s;

   s = strrchr(name, '#');
   if (!s) return;

   nfunc = string_hash_get(&heap->native_functions_hash, name);
   if (nfunc) {
      nfunc->func = func;
      nfunc->data = data;
      return;
   }

   nfunc = malloc(sizeof(NativeFunction));
   nfunc->func = func;
   nfunc->data = data;
   nfunc->id = heap->native_functions.len;
   nfunc->num_params = atoi(s+1);
   nfunc->bytecode_ident_pc = heap->bytecode_size;
   dynarray_add(&heap->native_functions, nfunc);

   heap->bytecode = realloc(heap->bytecode, heap->bytecode_size+1);
   heap->bytecode[heap->bytecode_size++] = 0;

   string_hash_set(&heap->native_functions_hash, strdup(name), nfunc);
}


char *fixscript_dump_code(Heap *heap, Script *script, const char *func_name)
{
   struct SwitchTable {
      int start, end;
      struct SwitchTable *next;
   };
   String out;
   Function *func, *show_func;
   LineEntry *line;
   int i, pc, op, func_num=1, line_num=0;
   unsigned short short_val;
   int int_val;
   float float_val;
   int table_idx, size, default_pc;
   int *table;
   struct SwitchTable *switch_table = NULL, *new_switch_table;

   if (func_name) {
      if (!script) {
         return strdup("error: invalid script reference\n");
      }
      show_func = string_hash_get(&script->functions, func_name);
      if (!show_func) {
         return string_format("error: unknown function %s in %s\n", func_name, string_hash_find_name(&heap->scripts, script));
      }
   }
   else {
      show_func = NULL;
   }

   memset(&out, 0, sizeof(String));
   func = heap->functions.len > 1? heap->functions.data[1] : NULL;
   line = heap->lines_size > 0? &heap->lines[0] : NULL;

   if (show_func) {
      while (func != show_func) {
         func = heap->functions.data[++func_num];
      }
      line_num = show_func->lines_start;
      line = &heap->lines[line_num];
   }

   for (pc = (func? func->addr : 0); pc < heap->bytecode_size; pc++) {
      if (switch_table && pc == switch_table->start) {
         pc = switch_table->end;
         new_switch_table = switch_table->next;
         free(switch_table);
         switch_table = new_switch_table;
      }
      if (func && pc == func->addr) {
         if (func_num == -1) break;
         if (!string_append(&out, "\nfunction %s [%s]\n", string_hash_find_name(&func->script->functions, func), string_hash_find_name(&heap->scripts, func->script))) goto error;
         if (++func_num < heap->functions.len) {
            func = heap->functions.data[func_num];
         }
         else {
            func = NULL;
         }
         if (show_func) {
            func_num = -1;
         }
      }
      
      if (line && pc == line->pc) {
         if (!string_append(&out, "line=%d\n", line->line)) goto error;
         if (++line_num < heap->lines_size) {
            line = &heap->lines[line_num];
         }
         else {
            line = NULL;
         }
      }

      if (!string_append(&out, "%6d: ", pc)) goto error;
      op = heap->bytecode[pc];
      #define DUMP(...) if (!string_append(&out, __VA_ARGS__)) goto error; break
      #define DATA() (heap->bytecode[++pc])
      #define DATA_SBYTE() ((signed char)heap->bytecode[++pc])
      #define DATA_SHORT() *((unsigned short *)memcpy(&short_val, &heap->bytecode[(pc += 2)-1], sizeof(unsigned short)))
      #define DATA_INT() *((int *)memcpy(&int_val, &heap->bytecode[(pc += 4)-3], sizeof(int)))
      #define DATA_FLOAT() *((float *)memcpy(&float_val, &heap->bytecode[(pc += 4)-3], sizeof(float)))
      switch (op) {
         case BC_POP:           DUMP("pop");
         case BC_POPN:          DUMP("popn");
         case BC_LOADN:         DUMP("loadn");
         case BC_STOREN:        DUMP("storen");
         case BC_ADD:           DUMP("add");
         case BC_SUB:           DUMP("sub");
         case BC_MUL:           DUMP("mul");
         case BC_ADD_MOD:       DUMP("add_mod");
         case BC_SUB_MOD:       DUMP("sub_mod");
         case BC_MUL_MOD:       DUMP("mul_mod");
         case BC_DIV:           DUMP("div");
         case BC_REM:           DUMP("rem");
         case BC_SHL:           DUMP("shl");
         case BC_SHR:           DUMP("shr");
         case BC_USHR:          DUMP("ushr");
         case BC_AND:           DUMP("and");
         case BC_OR:            DUMP("or");
         case BC_XOR:           DUMP("xor");
         case BC_LT:            DUMP("lt");
         case BC_LE:            DUMP("le");
         case BC_GT:            DUMP("gt");
         case BC_GE:            DUMP("ge");
         case BC_EQ:            DUMP("eq");
         case BC_NE:            DUMP("ne");
         case BC_EQ_VALUE:      DUMP("eq_value");
         case BC_NE_VALUE:      DUMP("ne_value");
         case BC_BITNOT:        DUMP("bitnot");
         case BC_LOGNOT:        DUMP("lognot");
         case BC_INC:           DUMP("inc %d", DATA_SBYTE());
         case BC_DEC:           DUMP("dec %d", DATA_SBYTE());
         case BC_FLOAT_ADD:     DUMP("float_add");
         case BC_FLOAT_SUB:     DUMP("float_sub");
         case BC_FLOAT_MUL:     DUMP("float_mul");
         case BC_FLOAT_DIV:     DUMP("float_div");
         case BC_FLOAT_LT:      DUMP("float_lt");
         case BC_FLOAT_LE:      DUMP("float_le");
         case BC_FLOAT_GT:      DUMP("float_gt");
         case BC_FLOAT_GE:      DUMP("float_ge");
         case BC_FLOAT_EQ:      DUMP("float_eq");
         case BC_FLOAT_NE:      DUMP("float_ne");
         case BC_RETURN:        DUMP("return");
         case BC_RETURN2:       DUMP("return2");
         case BC_CALL_DIRECT:   DUMP("call_direct");
         case BC_CALL_DYNAMIC:  DUMP("call_dynamic");
         case BC_CALL_NATIVE:   DUMP("call_native");
         case BC_CALL2_DIRECT:  DUMP("call2_direct");
         case BC_CALL2_DYNAMIC: DUMP("call2_dynamic");
         case BC_CALL2_NATIVE:  DUMP("call2_native");
         case BC_CLEAN_CALL2:   DUMP("clean_call2");
         case BC_CREATE_ARRAY:  DUMP("create_array");
         case BC_CREATE_HASH:   DUMP("create_hash");
         case BC_ARRAY_GET:     DUMP("array_get");
         case BC_ARRAY_SET:     DUMP("array_set");
         case BC_ARRAY_APPEND:  DUMP("array_append");
         case BC_HASH_GET:      DUMP("hash_get");
         case BC_HASH_SET:      DUMP("hash_set");
         case BC_CONST_P8:      DUMP("const_p8 %d", DATA()+1);
         case BC_CONST_N8:      DUMP("const_n8 %d", -(DATA()+1));
         case BC_CONST_P16:     DUMP("const_p16 %d", DATA_SHORT()+1);
         case BC_CONST_N16:     DUMP("const_n16 %d", -(DATA_SHORT()+1));
         case BC_CONST_I32:     DUMP("const_i32 %d", DATA_INT());
         case BC_CONST_F32:     DUMP("const_f32 %f", DATA_FLOAT());

         case BC_BRANCH_LONG:
            int_val = DATA_INT();
            DUMP("branch_long %d => %d", int_val, pc+int_val+1);

         case BC_JUMP_LONG:
            int_val = DATA_INT();
            DUMP("jump_long %d => %d", int_val, pc+int_val+1);

         case BC_LOOP_I8:
            int_val = DATA();
            DUMP("loop_i8 %d => %d", int_val, pc-int_val+1-1);

         case BC_LOOP_I16:
            int_val = DATA_SHORT();
            DUMP("loop_i16 %d => %d", int_val, pc-int_val+1-2);

         case BC_LOOP_I32:
            int_val = DATA_INT();
            DUMP("loop_i32 %d => %d", int_val, pc-int_val+1-4);

         case BC_LOAD_LOCAL:  DUMP("load_local %d", DATA_INT());
         case BC_STORE_LOCAL: DUMP("store_local %d", DATA_INT());

         case BC_SWITCH:
            table_idx = DATA_INT();
            table = &((int *)heap->bytecode)[table_idx];
            size = table[-2];
            default_pc = table[-1];
            if (!string_append(&out, "switch table_start=%d table_end=%d default=%d\n", (table_idx-2)*4, (table_idx+size*2)*4, default_pc)) goto error;
            for (i=0; i<size; i++) {
               if (table[i*2+1] < 0) {
                  if (!string_append(&out, "        | case %d..%d => %d\n", table[i*2+0], table[(i+1)*2+0], -table[i*2+1])) goto error;
                  i++;
               }
               else {
                  if (!string_append(&out, "        | case %d => %d\n", table[i*2+0], table[i*2+1])) goto error;
               }
            }
            
            new_switch_table = malloc(sizeof(struct SwitchTable));
            new_switch_table->start = (table_idx-2)*4;
            new_switch_table->end = (table_idx+size*2)*4;
            new_switch_table->next = switch_table;
            switch_table = new_switch_table;
            continue;

         case BC_LENGTH:        DUMP("length");
         case BC_CONST_STRING:  DUMP("const_string");
         case BC_STRING_CONCAT: DUMP("string_concat");

         case BC_EXTENDED:
            op = DATA();
            switch (op) {
               case BC_EXT_MIN:           DUMP("min");
               case BC_EXT_MAX:           DUMP("max");
               case BC_EXT_ABS:           DUMP("abs");
               case BC_EXT_ADD32:         DUMP("add32");
               case BC_EXT_SUB32:         DUMP("sub32");
               case BC_EXT_MUL64:         DUMP("mul64");
               case BC_EXT_UMUL64:        DUMP("umul64");
               case BC_EXT_MUL64_LONG:    DUMP("mul64_long");
               case BC_EXT_DIV64:         DUMP("div64");
               case BC_EXT_UDIV64:        DUMP("udiv64");
               case BC_EXT_REM64:         DUMP("rem64");
               case BC_EXT_UREM64:        DUMP("urem64");
               case BC_EXT_FLOAT:         DUMP("float");
               case BC_EXT_INT:           DUMP("int");
               case BC_EXT_FABS:          DUMP("fabs");
               case BC_EXT_FMIN:          DUMP("fmin");
               case BC_EXT_FMAX:          DUMP("fmax");
               case BC_EXT_FLOOR:         DUMP("floor");
               case BC_EXT_CEIL:          DUMP("ceil");
               case BC_EXT_ROUND:         DUMP("round");
               case BC_EXT_POW:           DUMP("pow");
               case BC_EXT_SQRT:          DUMP("sqrt");
               case BC_EXT_CBRT:          DUMP("cbrt");
               case BC_EXT_EXP:           DUMP("exp");
               case BC_EXT_LN:            DUMP("ln");
               case BC_EXT_LOG2:          DUMP("log2");
               case BC_EXT_LOG10:         DUMP("log10");
               case BC_EXT_SIN:           DUMP("sin");
               case BC_EXT_COS:           DUMP("cos");
               case BC_EXT_ASIN:          DUMP("asin");
               case BC_EXT_ACOS:          DUMP("acos");
               case BC_EXT_TAN:           DUMP("tan");
               case BC_EXT_ATAN:          DUMP("atan");
               case BC_EXT_ATAN2:         DUMP("atan2");
               case BC_EXT_DBL_FLOAT:     DUMP("dbl_float");
               case BC_EXT_DBL_INT:       DUMP("dbl_int");
               case BC_EXT_DBL_CONV_DOWN: DUMP("dbl_conv_down");
               case BC_EXT_DBL_CONV_UP:   DUMP("dbl_conv_up");
               case BC_EXT_DBL_FABS:      DUMP("dbl_fabs");
               case BC_EXT_DBL_FMIN:      DUMP("dbl_fmin");
               case BC_EXT_DBL_FMAX:      DUMP("dbl_fmax");
               case BC_EXT_DBL_FLOOR:     DUMP("dbl_floor");
               case BC_EXT_DBL_CEIL:      DUMP("dbl_ceil");
               case BC_EXT_DBL_ROUND:     DUMP("dbl_round");
               case BC_EXT_DBL_POW:       DUMP("dbl_pow");
               case BC_EXT_DBL_SQRT:      DUMP("dbl_sqrt");
               case BC_EXT_DBL_CBRT:      DUMP("dbl_cbrt");
               case BC_EXT_DBL_EXP:       DUMP("dbl_exp");
               case BC_EXT_DBL_LN:        DUMP("dbl_ln");
               case BC_EXT_DBL_LOG2:      DUMP("dbl_log2");
               case BC_EXT_DBL_LOG10:     DUMP("dbl_log10");
               case BC_EXT_DBL_SIN:       DUMP("dbl_sin");
               case BC_EXT_DBL_COS:       DUMP("dbl_cos");
               case BC_EXT_DBL_ASIN:      DUMP("dbl_asin");
               case BC_EXT_DBL_ACOS:      DUMP("dbl_acos");
               case BC_EXT_DBL_TAN:       DUMP("dbl_tan");
               case BC_EXT_DBL_ATAN:      DUMP("dbl_atan");
               case BC_EXT_DBL_ATAN2:     DUMP("dbl_atan2");
               case BC_EXT_IS_INT:        DUMP("is_int");
               case BC_EXT_IS_FLOAT:      DUMP("is_float");
               case BC_EXT_IS_ARRAY:      DUMP("is_array");
               case BC_EXT_IS_STRING:     DUMP("is_string");
               case BC_EXT_IS_HASH:       DUMP("is_hash");
               case BC_EXT_IS_SHARED:     DUMP("is_shared");
               case BC_EXT_IS_HANDLE:     DUMP("is_handle");
               default:
                  DUMP("(unknown_extended=%d)", op);
            }
            break;

         default:
            if ((op >= BC_CONSTM1 && op <= BC_CONST0+32) || op == BC_CONST0+63 || op == BC_CONST0+64) {
               DUMP("const %d", op - BC_CONST0);
            }
            if (op >= BC_BRANCH0 && op <= BC_BRANCH0+7) {
               int_val = ((op & 7) << 8) | DATA();
               DUMP("branch %d => %d", int_val, pc+int_val+1);
            }
            if (op >= BC_JUMP0 && op <= BC_JUMP0+7) {
               int_val = ((op & 7) << 8) | DATA();
               DUMP("jump %d => %d", int_val, pc+int_val+1);
            }
            if (op >= BC_STOREM64 && op <= BC_STOREM64+63) {
               DUMP("store %d", op - BC_STOREM64 - 64);
            }
            if (op >= BC_LOADM64 && op <= BC_LOADM64+63) {
               DUMP("load %d", op - BC_LOADM64 - 64);
            }
            DUMP("(unknown=%d)", op);
      }
      #undef DUMP
      #undef DATA
      #undef DATA_SBYTE
      #undef DATA_SHORT
      #undef DATA_INT
      #undef DATA_FLOAT
      if (!string_append(&out, "\n")) goto error;
   }
   
   if (switch_table) {
      goto error;
   }
   return out.data;

error:
   while (switch_table) {
      new_switch_table = switch_table->next;
      free(switch_table);
      switch_table = new_switch_table;
   }
   free(out.data);
   return strdup("internal error occurred");
}


static void dump_heap_value(String *out, Heap *heap, Value value)
{
   if (fixscript_is_int(value)) {
      string_append(out, "%d", fixscript_get_int(value));
   }
   else if (fixscript_is_float(value)) {
      string_append(out, "%.9g", fixscript_get_float(value));
   }
   else {
      string_append(out, "#%d", value.value);
   }
}


char *fixscript_dump_heap(Heap *heap)
{
   String out;
   Array *arr;
   Value key, value;
   char *s;
   int i, j, used=0, len, num;

   memset(&out, 0, sizeof(String));

   for (i=1; i<heap->size; i++) {
      arr = &heap->data[i];
      if (arr->len != -1) used++;
   }

   string_append(&out, "used=%d size=%d\n", used, heap->size);

   for (i=1; i<heap->size; i++) {
      arr = &heap->data[i];
      if (arr->len == -1) continue;

      if (arr->ext_refcnt != 0) {
         string_append(&out, "#%d (ext=%d) = ", i, arr->ext_refcnt);
      }
      else {
         string_append(&out, "#%d = ", i);
      }

      if (arr->is_handle) {
         string_append(&out, "handle ptr=%p type=%d\n", arr->handle_ptr, arr->type);
      }
      else if (arr->hash_slots >= 0) {
         string_append(&out, "hash(");
         j = 0;
         num = 0;
         while (fixscript_iter_hash(heap, (Value) { i, 1 }, &key, &value, &j)) {
            if (num > 0) string_append(&out, ",");
            if (num >= 20) {
               string_append(&out, "...");
               break;
            }
            dump_heap_value(&out, heap, key);
            string_append(&out, "=>");
            dump_heap_value(&out, heap, value);
            num++;
         }
         string_append(&out, ")\n");
      }
      else if (arr->is_string) {
         s = NULL;
         len = 0;
         fixscript_get_string(heap, (Value) { i, 1 }, 0, -1, &s, &len);
         if (len >= 103) {
            s[100] = '.';
            s[101] = '.';
            s[102] = '.';
            s[103] = 0;
            len = 103;
         }
         for (j=0; j<len; j++) {
            if (s[j] == '\r') s[j] = '`';
            if (s[j] == '\n') s[j] = '`';
            if (s[j] == '\t') s[j] = '`';
         }
         string_append(&out, "string(len=%d/%d,\"%s\")\n", arr->len, arr->size, s);
         free(s);
      }
      else {
         if (arr->is_shared) {
            string_append(&out, "shared_array(");
         }
         else {
            string_append(&out, "array(");
         }
         string_append(&out, "len=%d/%d,", arr->len, arr->size);
         for (j=0; j<arr->len; j++) {
            if (j > 0) string_append(&out, ",");
            if (j >= 100) {
               string_append(&out, "...");
               break;
            }
            value = (Value) { get_array_value(arr, j), IS_ARRAY(arr, j) != 0 };
            dump_heap_value(&out, heap, value);
         }
         string_append(&out, ")\n");
      }
   }

   return out.data;
}
