#line 1 "duk_internal.h"
/*
 *  Top-level include file to be used for all (internal) source files.
 *
 *  Source files should not include individual header files, as they
 *  have not been designed to be individually included.
 */

#ifndef DUK_INTERNAL_H_INCLUDED
#define DUK_INTERNAL_H_INCLUDED

/*
 *  Feature selection defines (e.g. _XOPEN_SOURCE) must appear before any
 *  system headers are included.  On the other hand, some platform detection
 *  requires header inclusion (like <endian.h>).  The current approach is
 *  that duk_features.h is included first, and it is responsible for doing
 *  feature selection before including any other headers it needs.
 *
 *  In any case, all implementation files include duk_internal.h first, so
 *  feature selection defines get set correctly.
 */

#line 1 "duk_features.h"
/*
 *  Determine platform features, select feature selection defines
 *  (e.g. _XOPEN_SOURCE), and define DUK_USE_XXX defines which are
 *  (only) checked in Duktape internal code for activated features.
 *  Duktape feature selection is based on DUK_PROFILE, other user
 *  supplied defines, and automatic feature detection.
 *
 *  This is included before anything else.  Feature selection defines
 *  (e.g. _XOPEN_SOURCE) must be defined before any system headers
 *  are included.  This file is included by duk_internal.h before it
 *  includes anything else.  We're responsible for first setting feature
 *  selection defines but after that we can include other headers as
 *  needed.
 *
 *  Useful resources:
 *
 *    http://sourceforge.net/p/predef/wiki/Home/
 *    http://sourceforge.net/p/predef/wiki/Architectures/
 *    http://stackoverflow.com/questions/5919996/how-to-detect-reliably-mac-os-x-ios-linux-windows-in-c-preprocessor
 *
 *  FIXME: at the moment there is no direct way of configuring
 *  or overriding individual settings.
 */

#ifndef DUK_FEATURES_H_INCLUDED
#define DUK_FEATURES_H_INCLUDED

#line 1 "duk_rdtsc.h"
/*
 *  Provides the duk_rdtsc() inline function (if available).
 *
 *  See: http://www.mcs.anl.gov/~kazutomo/rdtsc.html
 */

#ifndef DUK_RDTSC_H_INCLUDED
#define DUK_RDTSC_H_INCLUDED

#if defined(__i386__)
static __inline__ unsigned long long duk_rdtsc(void) {
	unsigned long long int x;
	__asm__ volatile (".byte 0x0f, 0x31" : "=A" (x));
	return x;
}
#define  DUK_RDTSC_AVAILABLE 1
#elif defined(__x86_64__)
static __inline__ unsigned long long duk_rdtsc(void) {
	unsigned hi, lo;
	__asm__ __volatile__ ("rdtsc" : "=a"(lo), "=d"(hi));
	return ((unsigned long long) lo) | (((unsigned long long) hi) << 32);
}
#define  DUK_RDTSC_AVAILABLE 1
#else
/* not available */
#undef  DUK_RDTSC_AVAILABLE
#endif

#endif  /* DUK_RDTSC_H_INCLUDED */

#line 29 "duk_features.h"

/* FIXME: remove _DUK_C99 and _DUK_BSD, prefer no names beginning with
 * underscore.
 */

/*
 *  Compiler features
 */

#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)
#define  _DUK_C99
#else
#undef   _DUK_C99
#endif

/*
 *  Intermediate platform detection
 */

/* FIXME: reconcile with direct detection below */
#if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD) || \
    defined(__bsdi__) || defined(__DragonFly__)
#define  _DUK_BSD
#endif

/*
 *  Feature selection defines (e.g. _XOPEN_SOURCE)
 *
 *  MUST be set before ANY system headers are included!
 */

#if defined(__linux)
#define  _POSIX_C_SOURCE  200809L
#define  _GNU_SOURCE      /* e.g. getdate_r */
#define  _XOPEN_SOURCE    /* e.g. strptime */
#endif

/*
 *  Byte order and double memory layout detection
 *
 *  This needs to be done before choosing a default profile, as it affects
 *  profile selection.
 */

/* FIXME: Not very good detection right now, expect to find __BYTE_ORDER
 * and __FLOAT_WORD_ORDER or resort to GCC/ARM specifics.  Improve the
 * detection code and perhaps allow some compiler define to override the
 * detection for unhandled cases.
 */

#if defined(__APPLE__)
#include <architecture/byte_order.h>
#elif defined(_DUK_BSD)
#include <sys/endian.h>
#else
/* Linux and hopefully others */
#include <endian.h>
#endif

#include <limits.h>
#include <sys/param.h>

/* determine endianness variant: little-endian (LE), big-endian (BE), or "middle-endian" (ME) i.e. ARM */
#if (defined(__BYTE_ORDER) && defined(__LITTLE_ENDIAN) && (__BYTE_ORDER == __LITTLE_ENDIAN)) || \
    (defined(__LITTLE_ENDIAN__))
#if defined(__FLOAT_WORD_ORDER) && defined(__LITTLE_ENDIAN) && (__FLOAT_WORD_ORDER == __LITTLE_ENDIAN) || \
    (defined(__GNUC__) && !defined(__arm__))
#define DUK_USE_DOUBLE_LE
#elif (defined(__FLOAT_WORD_ORDER) && defined(__BIG_ENDIAN) && (__FLOAT_WORD_ORDER == __BIG_ENDIAN)) || \
      (defined(__GNUC__) && defined(__arm__))
#define DUK_USE_DOUBLE_ME
#else
#error unsupported: byte order is little endian but cannot determine IEEE double word order
#endif
#elif (defined(__BYTE_ORDER) && defined(__BIG_ENDIAN) && (__BYTE_ORDER == __BIG_ENDIAN)) || \
      (defined(__BIG_ENDIAN__))
#if (defined(__FLOAT_WORD_ORDER) && defined(__BIG_ENDIAN) && (__FLOAT_WORD_ORDER == __BIG_ENDIAN)) || \
    (defined(__GNUC__) && !defined(__arm__))
#define DUK_USE_DOUBLE_BE
#else
#error unsupported: byte order is big endian but cannot determine IEEE double word order
#endif
#else
#error unsupported: cannot determine byte order
#endif

#if !defined(DUK_USE_DOUBLE_LE) && !defined(DUK_USE_DOUBLE_ME) && !defined(DUK_USE_DOUBLE_BE)
#error unsupported: cannot determine IEEE double byte order variant
#endif

/*
 *  Check whether or not a packed duk_tval representation is possible
 */

/* best effort viability checks, not particularly accurate */
#if (defined(__WORDSIZE) && (__WORDSIZE == 32)) && \
    (defined(UINT_MAX) && (UINT_MAX == 4294967295))
#define DUK_USE_PACKED_TVAL_POSSIBLE
#else
#undef  DUK_USE_PACKED_TVAL_POSSIBLE
#endif

/*
 *  Support for unaligned accesses
 */

/* FIXME: currently just a hack for ARM, what would be a good way to detect? */
#if defined(__arm__) || defined(__thumb__) || defined(_ARM) || defined(_M_ARM)
#undef   DUK_USE_UNALIGNED_ACCESSES_POSSIBLE
#else
#define  DUK_USE_UNALIGNED_ACCESSES_POSSIBLE
#endif

/*
 *  Macro for suppressing warnings for potentially unreferenced variables.
 *  The variables can be actually unreferenced or unreferenced in some
 *  specific cases only; for instance, if a variable is only debug printed,
 *  it is unreferenced when debug printing is disabled.
 *
 *  (Introduced here because it's potentially compiler specific.)
 */

#define  DUK_UNREF(x)  do { \
		(void) (x); \
	} while (0)

/* 
 *  Profile processing
 *
 *  DUK_PROFILE values:
 *    0      custom
 *    100    FULL
 *    101    FULL_DEBUG
 *    200    MINIMAL
 *    201    MINIMAL_DEBUG
 *    300    TINY
 *    301    TINY_DEBUG
 *    400    PORTABLE        [tagged types]
 *    401    PORTABLE_DEBUG  [tagged types]
 *    500    TORTURE         [tagged types + torture]
 *    501    TORTURE_DEBUG   [tagged types + torture]
 */

#if !defined(DUK_PROFILE)
#if defined(DUK_USE_PACKED_TVAL_POSSIBLE)
#define  DUK_PROFILE  100
#else
#define  DUK_PROFILE  400
#endif
#endif

#if (DUK_PROFILE > 0)

/* start with the settings for the FULL profile */

#define  DUK_USE_SELF_TEST_TVAL
#define  DUK_USE_PACKED_TVAL
#undef   DUK_USE_FULL_TVAL
#define  DUK_USE_REFERENCE_COUNTING
#define  DUK_USE_DOUBLE_LINKED_HEAP
#define  DUK_USE_MARK_AND_SWEEP
#define  DUK_USE_AUGMENT_ERRORS
#define  DUK_USE_TRACEBACKS
#undef   DUK_USE_GC_TORTURE
#undef   DUK_USE_DEBUG
#undef   DUK_USE_DDEBUG
#undef   DUK_USE_DDDEBUG
#undef   DUK_USE_DPRINT_RDTSC                       /* feature determination below */
#define  DUK_USE_VERBOSE_ERRORS
#undef   DUK_USE_ASSERTIONS
#undef   DUK_USE_VARIADIC_MACROS                    /* feature determination below */
#define  DUK_USE_PROVIDE_DEFAULT_ALLOC_FUNCTIONS
#undef   DUK_USE_EXPLICIT_NULL_INIT
#define  DUK_USE_REGEXP_SUPPORT
#define  DUK_USE_STRICT_UTF8_SOURCE
#define  DUK_USE_OCTAL_SUPPORT
#define  DUK_USE_SOURCE_NONBMP
#define  DUK_USE_DPRINT_COLORS
#define  DUK_USE_BROWSER_LIKE
#define  DUK_USE_SECTION_B

/* unaligned accesses */
#ifdef DUK_USE_UNALIGNED_ACCESSES_POSSIBLE
#define  DUK_USE_HASHBYTES_UNALIGNED_U32_ACCESS
#define  DUK_USE_HOBJECT_UNALIGNED_LAYOUT
#else
#undef   DUK_USE_HASHBYTES_UNALIGNED_U32_ACCESS
#undef   DUK_USE_HOBJECT_UNALIGNED_LAYOUT
#endif

/* profile specific modifications */

#if (DUK_PROFILE == 100)
/* FULL */
#elif (DUK_PROFILE == 101)
/* FULL_DEBUG */
#define  DUK_USE_DEBUG
#undef   DUK_USE_DDEBUG
#undef   DUK_USE_DDDEBUG
#define  DUK_USE_ASSERTIONS
#elif (DUK_PROFILE == 200)
/* MINIMAL */
#undef   DUK_USE_VERBOSE_ERRORS
#elif (DUK_PROFILE == 201)
/* MINIMAL_DEBUG */
#define  DUK_USE_DEBUG
#undef   DUK_USE_DDEBUG
#undef   DUK_USE_DDDEBUG
#define  DUK_USE_ASSERTIONS
#elif (DUK_PROFILE == 300)
/* TINY */
#undef   DUK_USE_SELF_TEST_TVAL
#undef   DUK_USE_REFERENCE_COUNTING
#undef   DUK_USE_DOUBLE_LINKED_HEAP
#define  DUK_USE_MARK_AND_SWEEP
#undef   DUK_USE_AUGMENT_ERRORS
#undef   DUK_USE_TRACEBACKS
#undef   DUK_USE_VERBOSE_ERRORS
#elif (DUK_PROFILE == 301)
/* TINY_DEBUG */
#undef   DUK_USE_SELF_TEST_TVAL
#undef   DUK_USE_REFERENCE_COUNTING
#undef   DUK_USE_DOUBLE_LINKED_HEAP
#define  DUK_USE_MARK_AND_SWEEP
#undef   DUK_USE_AUGMENT_ERRORS
#undef   DUK_USE_TRACEBACKS
#define  DUK_USE_DEBUG
#undef   DUK_USE_DDEBUG
#undef   DUK_USE_DDDEBUG
#define  DUK_USE_ASSERTIONS
#undef   DUK_USE_VERBOSE_ERRORS
#elif (DUK_PROFILE == 400)
#undef   DUK_USE_PACKED_TVAL
#undef   DUK_USE_FULL_TVAL
#define  DUK_USE_EXPLICIT_NULL_INIT
#elif (DUK_PROFILE == 401)
#undef   DUK_USE_PACKED_TVAL
#undef   DUK_USE_FULL_TVAL
#undef   DUK_USE_GC_TORTURE
#define  DUK_USE_DEBUG
#undef   DUK_USE_DDEBUG
#undef   DUK_USE_DDDEBUG
#define  DUK_USE_ASSERTIONS
#define  DUK_USE_EXPLICIT_NULL_INIT
#elif (DUK_PROFILE == 500)
#undef   DUK_USE_PACKED_TVAL
#undef   DUK_USE_FULL_TVAL
#define  DUK_USE_GC_TORTURE
#elif (DUK_PROFILE == 501)
#undef   DUK_USE_PACKED_TVAL
#undef   DUK_USE_FULL_TVAL
#define  DUK_USE_GC_TORTURE
#define  DUK_USE_DEBUG
#undef   DUK_USE_DDEBUG
#undef   DUK_USE_DDDEBUG
#undef   DUK_USE_ASSERTIONS
#else
#error unknown DUK_PROFILE
#endif

/* FIXME: how to handle constants like these? */
#if defined(DUK_USE_TRACEBACKS) && !defined(DUK_OPT_TRACEBACK_DEPTH)
#define  DUK_OPT_TRACEBACK_DEPTH  10
#endif

/*
 *  Dynamically detected features
 */

#if defined(DUK_RDTSC_AVAILABLE) && defined(DUK_OPT_DPRINT_RDTSC)
#define  DUK_USE_DPRINT_RDTSC
#else
#undef  DUK_USE_DPRINT_RDTSC
#endif

#ifdef _DUK_C99
#define  DUK_USE_VARIADIC_MACROS
#else
#undef  DUK_USE_VARIADIC_MACROS
#endif

/* zero-size array at end of struct (char buf[0]) instead of C99 version (char buf[]) */
#ifdef _DUK_C99
#undef  DUK_USE_STRUCT_HACK
#else
#define  DUK_USE_STRUCT_HACK  /* non-portable */
#endif

/* FIXME: GCC pragma inside a function fails in some earlier GCC versions (e.g. gcc 4.5).
 * This is very approximate but allows clean builds for development right now.
 */
/* http://gcc.gnu.org/onlinedocs/cpp/Common-Predefined-Macros.html */
#if defined(__GNUC__) && defined(__GNUC_MINOR__) && (__GNUC__ == 4) && (__GNUC_MINOR__ >= 6)
#define  DUK_USE_GCC_PRAGMAS
#else
#undef  DUK_USE_GCC_PRAGMAS
#endif

/* Some math functions are C99 only.  This is also an issue with some
 * embedded environments using uclibc where uclibc has been configured
 * not to provide some functions.  For now, use replacements whenever
 * using uclibc.
 */
#if defined(_DUK_C99) && !defined(__UCLIBC__)
#define  DUK_USE_MATH_FMIN
#define  DUK_USE_MATH_FMAX
#define  DUK_USE_MATH_ROUND
#else
#undef  DUK_USE_MATH_FMIN
#undef  DUK_USE_MATH_FMAX
#undef  DUK_USE_MATH_ROUND
#endif

/*
 *  Date built-in platform primitive selection
 *
 *  This is a direct platform dependency which is difficult to eliminate.
 *  Select provider through defines, and then include necessary system
 *  headers so that duk_builtin_date.c compiles.
 *
 *  FIXME: add a way to provide custom functions to provide the critical
 *  primitives; this would be convenient when porting to unknown platforms
 *  (rather than muck with Duktape internals).
 */

/* NOW = getting current time (required)
 * TZO = getting local time offset (required)
 * PRS = parse datetime (optional)
 * FMT = format datetime (optional)
 */

#if defined(_WIN64)
/* Windows 64-bit */
#error WIN64 not supported
#elif defined(_WIN32) || defined(WIN32)
/* Windows 32-bit */
#error WIN32 not supported
#elif defined(__APPLE__)
/* Mac OSX, iPhone, Darwin */
#define  DUK_USE_DATE_NOW_GETTIMEOFDAY
#define  DUK_USE_DATE_TZO_GMTIME
#define  DUK_USE_DATE_PRS_STRPTIME
#define  DUK_USE_DATE_FMT_STRFTIME
#elif defined(__linux)
/* Linux (__unix also defined) */
#define  DUK_USE_DATE_NOW_GETTIMEOFDAY
#define  DUK_USE_DATE_TZO_GMTIME
#define  DUK_USE_DATE_PRS_STRPTIME
#define  DUK_USE_DATE_FMT_STRFTIME
#elif defined(__unix)
/* Other Unix */
#define  DUK_USE_DATE_NOW_GETTIMEOFDAY
#define  DUK_USE_DATE_TZO_GMTIME
#define  DUK_USE_DATE_PRS_STRPTIME
#define  DUK_USE_DATE_FMT_STRFTIME
#elif defined(__posix)
/* POSIX */
#define  DUK_USE_DATE_NOW_GETTIMEOFDAY
#define  DUK_USE_DATE_TZO_GMTIME
#define  DUK_USE_DATE_PRS_STRPTIME
#define  DUK_USE_DATE_FMT_STRFTIME
#else
#error platform not supported
#endif

#if defined(DUK_USE_DATE_NOW_GETTIMEOFDAY)
#include <sys/time.h>
#endif

#if defined(DUK_USE_DATE_TZO_GMTIME) || \
    defined(DUK_USE_DATE_PRS_STRPTIME) || \
    defined(DUK_USE_DATE_FMT_STRFTIME)
/* just a sanity check */
#if defined(__linux) && !defined(_XOPEN_SOURCE)
#error expected _XOPEN_SOURCE to be defined here
#endif
#include <time.h>
#endif

#else  /* DUK_PROFILE > 0 */

/*
 *  All DUK_USE_ defines must be defined manually, no compiler
 *  or platform feature detection.
 */

#endif  /* DUK_PROFILE > 0 */

/* FIXME: An alternative approach to customization would be to include
 * some user define file at this point.  The user file could then modify
 * the base settings.  Something like:
 * #ifdef DUK_CUSTOM_HEADER
 * #include "duk_custom.h"
 * #endif
 */

/*
 *  Sanity checks on defines
 */

#if defined(DUK_DDEBUG) && !defined(DUK_DEBUG)
#error DUK_DEBUG and DUK_DDEBUG should not be defined (obsolete)
#endif

#if defined(DUK_USE_DDEBUG) && !defined(DUK_USE_DEBUG)
#error DUK_USE_DDEBUG defined without DUK_USE_DEBUG
#endif

#if defined(DUK_USE_DDDEBUG) && !defined(DUK_USE_DEBUG)
#error DUK_USE_DDDEBUG defined without DUK_USE_DEBUG
#endif

#if defined(DUK_USE_DDDEBUG) && !defined(DUK_USE_DDEBUG)
#error DUK_USE_DDDEBUG defined without DUK_USE_DDEBUG
#endif

#if defined(DUK_USE_REFERENCE_COUNTING) && !defined(DUK_USE_DOUBLE_LINKED_HEAP)
#error DUK_USE_REFERENCE_COUNTING defined without DUK_USE_DOUBLE_LINKED_HEAP
#endif

#if defined(DUK_USE_GC_TORTURE) && !defined(DUK_USE_MARK_AND_SWEEP)
#error DUK_USE_GC_TORTURE defined without DUK_USE_MARK_AND_SWEEP
#endif

#endif  /* DUK_FEATURES_H_INCLUDED */

#line 23 "duk_internal.h"

/*
 *  System includes
 *
 *  This is currently the set of headers that we include on all platforms.
 *  Platform specific headers are included in duk_features.h.  The set of
 *  included files should be more precise, so perhaps these all should go
 *  to duk_features.h.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>  /* varargs */
#include <setjmp.h>
#include <stddef.h>  /* e.g. ptrdiff_t */
#include <stdint.h>

/*
 *  Duktape includes (other than duk_features.h)
 */

#line 1 "duk_misc.h"
/*
 *  Miscellaneous defines that don't fit elsewhere.
 */

#ifndef DUK_MISC_H_INCLUDED
#define DUK_MISC_H_INCLUDED

/*
 *  Misc constants
 */

#define  DUK_DOUBLE_2TO32  4294967296.0
#define  DUK_DOUBLE_2TO31  2147483648.0

/*
 *  Macro hackery to convert e.g. __LINE__ to a string without formatting,
 *  see: http://stackoverflow.com/questions/240353/convert-a-preprocessor-token-to-a-string
 */

#define  _DUK_STRINGIFY_HELPER(x)  #x
#define  DUK_MACRO_STRINGIFY(x)  _DUK_STRINGIFY_HELPER(x)

/*
 *  GCC specific compile time messages
 *
 *  Note: no semicolon should be used after these because they may appear e.g. at top level:
 *
 *      DUK_FIXME("this needs fixing")
 */

/* FIXME: make these conditional to a specific compiler option (don't want to see these normally) */

#if defined(__GNUC__) && defined(FIXME_COMMENTED_OUT)

/* http://gcc.gnu.org/onlinedocs/gcc-4.6.0/gcc/Diagnostic-Pragmas.html */
#define  _DUK_DO_PRAGMA(x)  _Pragma(#x)
#define  DUK_FIXME(x)       _DUK_DO_PRAGMA(message ("FIXME: " DUK_MACRO_STRINGIFY(x)))
#define  DUK_TODO(x)        _DUK_DO_PRAGMA(message ("TODO: " DUK_MACRO_STRINGIFY(x)))
#define  DUK_XXX(x)         _DUK_DO_PRAGMA(message ("XXX: " DUK_MACRO_STRINGIFY(x)))

#else

#define  DUK_FIXME(x)
#define  DUK_TODO(x)
#define  DUK_XXX(x)

#endif  /* __GNUC__ */

#endif  /* DUK_MISC_H_INCLUDED */

/* already included: duk_rdtsc.h */
#line 1 "duk_bittypes.h"
/*
 *  Bit types such as duk_u32.
 */

#ifndef DUK_BITTYPES_H_INCLUDED
#define DUK_BITTYPES_H_INCLUDED

/* FIXME: Is there a reason not to rely on C99 types only, and only fall
 * back to guessing if C99 types are not available?
 */

/* FIXME: How to do reasonable automatic detection on older compilers,
 * and how to allow user override?
 */

#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)
/* C99 */
#include <inttypes.h>
typedef uint8_t duk_u8;
typedef int8_t duk_i8;
typedef uint16_t duk_u16;
typedef int16_t duk_i16;
typedef uint32_t duk_u32;
typedef int32_t duk_i32;
#else
/* FIXME: need actual detection here */
typedef unsigned char duk_u8;
typedef signed char duk_i8;
typedef unsigned short duk_u16;
typedef signed short duk_i16;
typedef unsigned int duk_u32;
typedef signed int duk_i32;
#endif

#endif  /* DUK_BITTYPES_H_INCLUDED */

#line 1 "duk_jmpbuf.h"
/*
 *  Wrapper for jmp_buf.
 *
 *  This is used because jmp_buf is an array type for backward compatibility.
 *  Wrapping jmp_buf in a struct makes pointer references, sizeof, etc,
 *  behave more intuitively.
 *
 *  http://en.wikipedia.org/wiki/Setjmp.h#Member_types
 */

#ifndef DUK_JMPBUF_H_INCLUDED
#define DUK_JMPBUF_H_INCLUDED

#include <setjmp.h>

struct duk_jmpbuf {
	jmp_buf jb;
};

#endif  /* DUK_JMPBUF_H_INCLUDED */

#line 1 "duk_forwdecl.h"
/*
 *  Forward declarations for all Duktape structures.
 */

#ifndef DUK_FORWDECL_H_INCLUDED
#define DUK_FORWDECL_H_INCLUDED

/*
 *  Forward declarations
 */

struct duk_jmpbuf;

/* duk_tval intentionally skipped */
struct duk_heaphdr;
struct duk_heaphdr_string;
struct duk_hstring;
struct duk_hobject;
struct duk_hcompiledfunction;
struct duk_hnativefunction;
struct duk_hthread;
struct duk_hbuffer;
struct duk_hbuffer_fixed;
struct duk_hbuffer_dynamic;

struct duk_propaccessor;
union duk_propvalue;
struct duk_propdesc;

struct duk_heap;

struct duk_activation;
struct duk_catcher;
struct duk_strcache;
struct duk_ljstate;

#ifdef DUK_USE_DEBUG
struct duk_fixedbuffer;
#endif

struct duk_bitdecoder_ctx;
struct duk_bitencoder_ctx;

struct duk_token;
struct duk_re_token;
struct duk_lexer_point;
struct duk_lexer_ctx;

struct duk_compiler_instr;
struct duk_compiler_func;
struct duk_compiler_ctx;

struct duk_re_matcher_ctx;
struct duk_re_compiler_ctx;

typedef struct duk_jmpbuf duk_jmpbuf;

/* duk_tval intentionally skipped */
typedef struct duk_heaphdr duk_heaphdr;
typedef struct duk_heaphdr_string duk_heaphdr_string;
typedef struct duk_hstring duk_hstring;
typedef struct duk_hobject duk_hobject;
typedef struct duk_hcompiledfunction duk_hcompiledfunction;
typedef struct duk_hnativefunction duk_hnativefunction;
typedef struct duk_hthread duk_hthread;
typedef struct duk_hbuffer duk_hbuffer;
typedef struct duk_hbuffer_fixed duk_hbuffer_fixed;
typedef struct duk_hbuffer_dynamic duk_hbuffer_dynamic;

typedef struct duk_propaccessor duk_propaccessor;
typedef union duk_propvalue duk_propvalue;
typedef struct duk_propdesc duk_propdesc;
 
typedef struct duk_heap duk_heap;

typedef struct duk_activation duk_activation;
typedef struct duk_catcher duk_catcher;
typedef struct duk_strcache duk_strcache;
typedef struct duk_ljstate duk_ljstate;

#ifdef DUK_USE_DEBUG
typedef struct duk_fixedbuffer duk_fixedbuffer;
#endif

typedef struct duk_bitdecoder_ctx duk_bitdecoder_ctx;
typedef struct duk_bitencoder_ctx duk_bitencoder_ctx;

typedef struct duk_token duk_token;
typedef struct duk_re_token duk_re_token;
typedef struct duk_lexer_point duk_lexer_point;
typedef struct duk_lexer_ctx duk_lexer_ctx;

typedef struct duk_compiler_instr duk_compiler_instr;
typedef struct duk_compiler_func duk_compiler_func;
typedef struct duk_compiler_ctx duk_compiler_ctx;

typedef struct duk_re_matcher_ctx duk_re_matcher_ctx;
typedef struct duk_re_compiler_ctx duk_re_compiler_ctx;
	
#endif  /* DUK_FORWDECL_H_INCLUDED */

#line 1 "duk_js_bytecode.h"
/*
 *  Ecmascript bytecode
 */

#ifndef DUK_JS_BYTECODE_H_INCLUDED
#define DUK_JS_BYTECODE_H_INCLUDED

/* already included: duk_bittypes.h */

/*
 *  Logical instruction layout
 *  ==========================
 *
 *  !3!3!2!2!2!2!2!2!2!2!2!2!1!1!1!1!1!1!1!1!1!1! ! ! ! ! ! ! ! ! ! !
 *  !1!0!9!8!7!6!5!4!3!2!1!0!9!8!7!6!5!4!3!2!1!0!9!8!7!6!5!4!3!2!1!0!
 *  +---------------------------------------------------+-----------+
 *  !       C         !       B         !      A        !    OP     !
 *  +---------------------------------------------------+-----------+
 *
 *  OP (6 bits):  opcode (DUK_OP_*), access should be fastest
 *  A (8 bits):   typically a target register number
 *  B (9 bits):   typically first source register/constant number
 *  C (9 bits):   typically second source register/constant number
 *
 *  Some instructions combine BC or ABC together for larger parameter values.
 *  Signed integers (e.g. jump offsets) are encoded as unsigned, with an opcode
 *  specific bias.  B and C may denote a register or a constant, see
 *  DUK_BC_ISREG() and DUK_BC_ISCONST().
 *
 *  Note: macro naming is a bit misleading, e.g. "ABC" in macro name but
 *  the field layout is logically "CBA".
 */ 

typedef duk_u32 duk_instr;

#define  DUK_DEC_OP(x)               ((x) & 0x3f)
#define  DUK_DEC_A(x)                (((x) >> 6) & 0xff)
#define  DUK_DEC_B(x)                (((x) >> 14) & 0x1ff)
#define  DUK_DEC_C(x)                (((x) >> 23) & 0x1ff)
#define  DUK_DEC_BC(x)               (((x) >> 14) & 0x3ffff)
#define  DUK_DEC_ABC(x)              (((x) >> 6) & 0x3ffffff)

#define  DUK_ENC_OP_ABC(op,abc)      ((duk_instr) (((abc) << 6) | (op)))
#define  DUK_ENC_OP_A_BC(op,a,bc)    ((duk_instr) (((bc) << 14) | ((a) << 6) | (op)))
#define  DUK_ENC_OP_A_B_C(op,a,b,c)  ((duk_instr) (((c) << 23) | ((b) << 14) | ((a) << 6) | (op)))
#define  DUK_ENC_OP_A_B(op,a,b)      DUK_ENC_OP_A_B_C(op,a,b,0)
#define  DUK_ENC_OP_A(op,a)          DUK_ENC_OP_A_B_C(op,a,0,0)

#define  DUK_BC_OP_MIN               0
#define  DUK_BC_OP_MAX               0x3f
#define  DUK_BC_A_MIN                0
#define  DUK_BC_A_MAX                0xff
#define  DUK_BC_B_MIN                0
#define  DUK_BC_B_MAX                0x1ff
#define  DUK_BC_C_MIN                0
#define  DUK_BC_C_MAX                0x1ff
#define  DUK_BC_BC_MIN               0
#define  DUK_BC_BC_MAX               0x3ffff
#define  DUK_BC_ABC_MIN              0
#define  DUK_BC_ABC_MAX              0x3ffffff
#define  DUK_BC_EXTRAOP_MIN          DUK_BC_A_MIN
#define  DUK_BC_EXTRAOP_MAX          DUK_BC_A_MAX

#define  DUK_OP_LDREG                0 
#define  DUK_OP_STREG                1  /* FIXME: UNUSED */
#define  DUK_OP_LDCONST              2
#define  DUK_OP_LDINT                3
#define  DUK_OP_LDINTX               4  /* FIXME: UNUSED */
#define  DUK_OP_MPUTOBJ              5
#define  DUK_OP_MPUTARR              6
#define  DUK_OP_NEW                  7
#define  DUK_OP_REGEXP               8
#define  DUK_OP_CSREG                9
#define  DUK_OP_GETVAR               10
#define  DUK_OP_PUTVAR               11
#define  DUK_OP_DECLVAR              12
#define  DUK_OP_DELVAR               13
#define  DUK_OP_CSVAR                14
#define  DUK_OP_CLOSURE              15
#define  DUK_OP_GETPROP              16
#define  DUK_OP_PUTPROP              17
#define  DUK_OP_DELPROP              18
#define  DUK_OP_CSPROP               19
#define  DUK_OP_ADD                  20
#define  DUK_OP_SUB                  21
#define  DUK_OP_MUL                  22
#define  DUK_OP_DIV                  23
#define  DUK_OP_MOD                  24
#define  DUK_OP_UNM                  25
#define  DUK_OP_UNP                  26
#define  DUK_OP_INC                  27
#define  DUK_OP_DEC                  28
#define  DUK_OP_BAND                 29
#define  DUK_OP_BOR                  30
#define  DUK_OP_BXOR                 31
#define  DUK_OP_BASL                 32
#define  DUK_OP_BLSR                 33
#define  DUK_OP_BASR                 34
#define  DUK_OP_BNOT                 35
#define  DUK_OP_LNOT                 36
#define  DUK_OP_EQ                   37
#define  DUK_OP_NEQ                  38
#define  DUK_OP_SEQ                  39
#define  DUK_OP_SNEQ                 40
#define  DUK_OP_GT                   41
#define  DUK_OP_GE                   42
#define  DUK_OP_LT                   43
#define  DUK_OP_LE                   44
#define  DUK_OP_IF                   45
#define  DUK_OP_INSTOF               46
#define  DUK_OP_IN                   47
#define  DUK_OP_JUMP                 48
#define  DUK_OP_RETURN               49
#define  DUK_OP_CALL                 50
#define  DUK_OP_LABEL                51
#define  DUK_OP_ENDLABEL             52
#define  DUK_OP_BREAK                53
#define  DUK_OP_CONTINUE             54
#define  DUK_OP_TRYCATCH             55
#define  DUK_OP_56                   56
#define  DUK_OP_57                   57
#define  DUK_OP_58                   58
#define  DUK_OP_59                   59
#define  DUK_OP_60                   60
#define  DUK_OP_EXTRA                61
#define  DUK_OP_DEBUG                62
#define  DUK_OP_INVALID              63

/* DUK_OP_EXTRA, sub-operation in A */
#define  DUK_EXTRAOP_NOP             0
#define  DUK_EXTRAOP_LDTHIS          1
#define  DUK_EXTRAOP_LDUNDEF         2
#define  DUK_EXTRAOP_LDNULL          3
#define  DUK_EXTRAOP_LDBOOL          4
#define  DUK_EXTRAOP_NEWOBJ          5
#define  DUK_EXTRAOP_NEWARR          6
#define  DUK_EXTRAOP_SETALEN         7
#define  DUK_EXTRAOP_TYPEOF          8
#define  DUK_EXTRAOP_TYPEOFID        9
#define  DUK_EXTRAOP_TONUM           10
#define  DUK_EXTRAOP_INITENUM        11
#define  DUK_EXTRAOP_NEXTENUM        12
#define  DUK_EXTRAOP_INITSET         13
#define  DUK_EXTRAOP_INITGET         14
#define  DUK_EXTRAOP_ENDTRY          15
#define  DUK_EXTRAOP_ENDCATCH        16
#define  DUK_EXTRAOP_ENDFIN          17
#define  DUK_EXTRAOP_THROW           18
#define  DUK_EXTRAOP_INVLHS          19

/* DUK_OP_DEBUG, sub-operation in A */
#define  DUK_DEBUGOP_DUMPREG         0
#define  DUK_DEBUGOP_DUMPREGS        1
#define  DUK_DEBUGOP_DUMPTHREAD      2
#define  DUK_DEBUGOP_LOGMARK         3

/* DUK_OP_CALL flags in A */
#define  DUK_BC_CALL_FLAG_TAILCALL           (1 << 0)
#define  DUK_BC_CALL_FLAG_EVALCALL           (1 << 1)

/* DUK_OP_TRYCATCH flags in A */
#define  DUK_BC_TRYCATCH_FLAG_HAVE_CATCH     (1 << 0)
#define  DUK_BC_TRYCATCH_FLAG_HAVE_FINALLY   (1 << 1)
#define  DUK_BC_TRYCATCH_FLAG_CATCH_BINDING  (1 << 2)
#define  DUK_BC_TRYCATCH_FLAG_WITH_BINDING   (1 << 3)

/* DUK_OP_RETURN flags in A */
#define  DUK_BC_RETURN_FLAG_FAST             (1 << 0)
#define  DUK_BC_RETURN_FLAG_HAVE_RETVAL      (1 << 1)

/* DUK_OP_DECLVAR flags in A; bottom bits are reserved for propdesc flags (DUK_PROPDESC_FLAG_XXX) */
#define  DUK_BC_DECLVAR_FLAG_UNDEF_VALUE     (1 << 4)  /* use 'undefined' for value automatically */
#define  DUK_BC_DECLVAR_FLAG_FUNC_DECL       (1 << 5)  /* function declaration */

/* misc constants and helper macros */
#define  DUK_BC_REGLIMIT             256  /* if B/C is >= this value, refers to a const */
#define  DUK_BC_ISREG(x)             ((x) < DUK_BC_REGLIMIT)
#define  DUK_BC_ISCONST(x)           ((x) >= DUK_BC_REGLIMIT)
#define  DUK_BC_LDINT_BIAS           (1 << 17)
#define  DUK_BC_LDINTX_SHIFT         18
#define  DUK_BC_JUMP_BIAS            (1 << 25)

#endif  /* DUK_JS_BYTECODE_H_INCLUDED */

#line 1 "duk_lexer.h"
/*
 *  Lexer defines.
 */

#ifndef DUK_LEXER_H_INCLUDED
#define DUK_LEXER_H_INCLUDED

#line 1 "duk_strings.h"
/*
 *  Automatically generated by genstrings.py, do not edit!
 */

#ifndef __DUK_STRINGS_H
#define __DUK_STRINGS_H 1

extern char duk_strings_data[];

#define  DUK_STRDATA_DATA_LENGTH                                       1770
#define  DUK_STRDATA_MAX_STRLEN                                        24

#define  DUK_STRIDX_EMPTY_STRING                                       0                              /* '' */
#define  DUK_STRIDX_GLOBAL                                             1                              /* 'global' */
#define  DUK_STRIDX_UC_ARGUMENTS                                       2                              /* 'Arguments' */
#define  DUK_STRIDX_JSON                                               3                              /* 'JSON' */
#define  DUK_STRIDX_MATH                                               4                              /* 'Math' */
#define  DUK_STRIDX_ERROR                                              5                              /* 'Error' */
#define  DUK_STRIDX_REG_EXP                                            6                              /* 'RegExp' */
#define  DUK_STRIDX_DATE                                               7                              /* 'Date' */
#define  DUK_STRIDX_UC_NUMBER                                          8                              /* 'Number' */
#define  DUK_STRIDX_UC_BOOLEAN                                         9                              /* 'Boolean' */
#define  DUK_STRIDX_UC_STRING                                          10                             /* 'String' */
#define  DUK_STRIDX_ARRAY                                              11                             /* 'Array' */
#define  DUK_STRIDX_UC_FUNCTION                                        12                             /* 'Function' */
#define  DUK_STRIDX_UC_OBJECT                                          13                             /* 'Object' */
#define  DUK_STRIDX_JSON_EXT_NEGINF                                    14                             /* '{"_ninf":true}' */
#define  DUK_STRIDX_JSON_EXT_POSINF                                    15                             /* '{"_inf":true}' */
#define  DUK_STRIDX_JSON_EXT_NAN                                       16                             /* '{"_nan":true}' */
#define  DUK_STRIDX_JSON_EXT_UNDEFINED                                 17                             /* '{"_undefined":true}' */
#define  DUK_STRIDX_SLEEP                                              18                             /* 'sleep' */
#define  DUK_STRIDX_BASE64                                             19                             /* 'base64' */
#define  DUK_STRIDX_HEX                                                20                             /* 'hex' */
#define  DUK_STRIDX_DEC                                                21                             /* 'dec' */
#define  DUK_STRIDX_ENC                                                22                             /* 'enc' */
#define  DUK_STRIDX_GET_FINALIZER                                      23                             /* 'getFinalizer' */
#define  DUK_STRIDX_SET_FINALIZER                                      24                             /* 'setFinalizer' */
#define  DUK_STRIDX_TIME                                               25                             /* 'time' */
#define  DUK_STRIDX_GC                                                 26                             /* 'gc' */
#define  DUK_STRIDX_CURR                                               27                             /* 'curr' */
#define  DUK_STRIDX_RESUME                                             28                             /* 'resume' */
#define  DUK_STRIDX_SPAWN                                              29                             /* 'spawn' */
#define  DUK_STRIDX_REFC                                               30                             /* 'refc' */
#define  DUK_STRIDX_ADDR                                               31                             /* 'addr' */
#define  DUK_STRIDX_BUILD                                              32                             /* 'build' */
#define  DUK_STRIDX_VERSION                                            33                             /* 'version' */
#define  DUK_STRIDX_DUK                                                34                             /* '__duk__' */
#define  DUK_STRIDX_INT_REGBASE                                        35                             /* '\x00regbase' */
#define  DUK_STRIDX_INT_THREAD                                         36                             /* '\x00thread' */
#define  DUK_STRIDX_INT_FINALIZER                                      37                             /* '\x00finalizer' */
#define  DUK_STRIDX_INT_METATABLE                                      38                             /* '\x00metatable' */
#define  DUK_STRIDX_INT_CALLEE                                         39                             /* '\x00callee' */
#define  DUK_STRIDX_INT_MAP                                            40                             /* '\x00map' */
#define  DUK_STRIDX_INT_ARGS                                           41                             /* '\x00args' */
#define  DUK_STRIDX_INT_THIS                                           42                             /* '\x00this' */
#define  DUK_STRIDX_INT_FILENAME                                       43                             /* '\x00filename' */
#define  DUK_STRIDX_INT_PC2LINE                                        44                             /* '\x00pc2line' */
#define  DUK_STRIDX_INT_SOURCE                                         45                             /* '\x00source' */
#define  DUK_STRIDX_INT_VARENV                                         46                             /* '\x00varenv' */
#define  DUK_STRIDX_INT_LEXENV                                         47                             /* '\x00lexenv' */
#define  DUK_STRIDX_INT_VARMAP                                         48                             /* '\x00varmap' */
#define  DUK_STRIDX_INT_FORMALS                                        49                             /* '\x00formals' */
#define  DUK_STRIDX_INT_BYTECODE                                       50                             /* '\x00bytecode' */
#define  DUK_STRIDX_INT_NEXT                                           51                             /* '\x00next' */
#define  DUK_STRIDX_INT_TARGET                                         52                             /* '\x00target' */
#define  DUK_STRIDX_INT_VALUE                                          53                             /* '\x00value' */
#define  DUK_STRIDX_POINTER                                            54                             /* 'pointer' */
#define  DUK_STRIDX_BUFFER                                             55                             /* 'buffer' */
#define  DUK_STRIDX_TRUNC                                              56                             /* 'trunc' */
#define  DUK_STRIDX_TRACEBACK                                          57                             /* 'traceback' */
#define  DUK_STRIDX_CAUSE                                              58                             /* 'cause' */
#define  DUK_STRIDX_CODE                                               59                             /* 'code' */
#define  DUK_STRIDX_IS_NATIVE                                          60                             /* 'isNative' */
#define  DUK_STRIDX_LINE_NUMBER                                        61                             /* 'lineNumber' */
#define  DUK_STRIDX_FILE_NAME                                          62                             /* 'fileName' */
#define  DUK_STRIDX_PC                                                 63                             /* 'pc' */
#define  DUK_STRIDX_THROW_TYPE_ERROR                                   64                             /* 'ThrowTypeError' */
#define  DUK_STRIDX_CALLEE                                             65                             /* 'callee' */
#define  DUK_STRIDX_INVALID_DATE                                       66                             /* 'Invalid Date' */
#define  DUK_STRIDX_SPACE                                              67                             /* ' ' */
#define  DUK_STRIDX_COMMA                                              68                             /* ',' */
#define  DUK_STRIDX_MINUS_ZERO                                         69                             /* '-0' */
#define  DUK_STRIDX_PLUS_ZERO                                          70                             /* '+0' */
#define  DUK_STRIDX_ZERO                                               71                             /* '0' */
#define  DUK_STRIDX_MINUS_INFINITY                                     72                             /* '-Infinity' */
#define  DUK_STRIDX_PLUS_INFINITY                                      73                             /* '+Infinity' */
#define  DUK_STRIDX_INFINITY                                           74                             /* 'Infinity' */
#define  DUK_STRIDX_LC_OBJECT                                          75                             /* 'object' */
#define  DUK_STRIDX_LC_STRING                                          76                             /* 'string' */
#define  DUK_STRIDX_LC_NUMBER                                          77                             /* 'number' */
#define  DUK_STRIDX_LC_BOOLEAN                                         78                             /* 'boolean' */
#define  DUK_STRIDX_UNDEFINED                                          79                             /* 'undefined' */
#define  DUK_STRIDX_STRINGIFY                                          80                             /* 'stringify' */
#define  DUK_STRIDX_TAN                                                81                             /* 'tan' */
#define  DUK_STRIDX_SQRT                                               82                             /* 'sqrt' */
#define  DUK_STRIDX_SIN                                                83                             /* 'sin' */
#define  DUK_STRIDX_ROUND                                              84                             /* 'round' */
#define  DUK_STRIDX_RANDOM                                             85                             /* 'random' */
#define  DUK_STRIDX_POW                                                86                             /* 'pow' */
#define  DUK_STRIDX_MIN                                                87                             /* 'min' */
#define  DUK_STRIDX_MAX                                                88                             /* 'max' */
#define  DUK_STRIDX_LOG                                                89                             /* 'log' */
#define  DUK_STRIDX_FLOOR                                              90                             /* 'floor' */
#define  DUK_STRIDX_EXP                                                91                             /* 'exp' */
#define  DUK_STRIDX_COS                                                92                             /* 'cos' */
#define  DUK_STRIDX_CEIL                                               93                             /* 'ceil' */
#define  DUK_STRIDX_ATAN2                                              94                             /* 'atan2' */
#define  DUK_STRIDX_ATAN                                               95                             /* 'atan' */
#define  DUK_STRIDX_ASIN                                               96                             /* 'asin' */
#define  DUK_STRIDX_ACOS                                               97                             /* 'acos' */
#define  DUK_STRIDX_ABS                                                98                             /* 'abs' */
#define  DUK_STRIDX_SQRT2                                              99                             /* 'SQRT2' */
#define  DUK_STRIDX_SQRT1_2                                            100                            /* 'SQRT1_2' */
#define  DUK_STRIDX_PI                                                 101                            /* 'PI' */
#define  DUK_STRIDX_LOG10E                                             102                            /* 'LOG10E' */
#define  DUK_STRIDX_LOG2E                                              103                            /* 'LOG2E' */
#define  DUK_STRIDX_LN2                                                104                            /* 'LN2' */
#define  DUK_STRIDX_LN10                                               105                            /* 'LN10' */
#define  DUK_STRIDX_E                                                  106                            /* 'E' */
#define  DUK_STRIDX_MESSAGE                                            107                            /* 'message' */
#define  DUK_STRIDX_NAME                                               108                            /* 'name' */
#define  DUK_STRIDX_INPUT                                              109                            /* 'input' */
#define  DUK_STRIDX_INDEX                                              110                            /* 'index' */
#define  DUK_STRIDX_ESCAPED_EMPTY_REGEXP                               111                            /* '(?:)' */
#define  DUK_STRIDX_LAST_INDEX                                         112                            /* 'lastIndex' */
#define  DUK_STRIDX_MULTILINE                                          113                            /* 'multiline' */
#define  DUK_STRIDX_IGNORE_CASE                                        114                            /* 'ignoreCase' */
#define  DUK_STRIDX_SOURCE                                             115                            /* 'source' */
#define  DUK_STRIDX_TEST                                               116                            /* 'test' */
#define  DUK_STRIDX_EXEC                                               117                            /* 'exec' */
#define  DUK_STRIDX_TO_GMT_STRING                                      118                            /* 'toGMTString' */
#define  DUK_STRIDX_SET_YEAR                                           119                            /* 'setYear' */
#define  DUK_STRIDX_GET_YEAR                                           120                            /* 'getYear' */
#define  DUK_STRIDX_TO_JSON                                            121                            /* 'toJSON' */
#define  DUK_STRIDX_TO_ISO_STRING                                      122                            /* 'toISOString' */
#define  DUK_STRIDX_TO_UTC_STRING                                      123                            /* 'toUTCString' */
#define  DUK_STRIDX_SET_UTC_FULL_YEAR                                  124                            /* 'setUTCFullYear' */
#define  DUK_STRIDX_SET_FULL_YEAR                                      125                            /* 'setFullYear' */
#define  DUK_STRIDX_SET_UTC_MONTH                                      126                            /* 'setUTCMonth' */
#define  DUK_STRIDX_SET_MONTH                                          127                            /* 'setMonth' */
#define  DUK_STRIDX_SET_UTC_DATE                                       128                            /* 'setUTCDate' */
#define  DUK_STRIDX_SET_DATE                                           129                            /* 'setDate' */
#define  DUK_STRIDX_SET_UTC_HOURS                                      130                            /* 'setUTCHours' */
#define  DUK_STRIDX_SET_HOURS                                          131                            /* 'setHours' */
#define  DUK_STRIDX_SET_UTC_MINUTES                                    132                            /* 'setUTCMinutes' */
#define  DUK_STRIDX_SET_MINUTES                                        133                            /* 'setMinutes' */
#define  DUK_STRIDX_SET_UTC_SECONDS                                    134                            /* 'setUTCSeconds' */
#define  DUK_STRIDX_SET_SECONDS                                        135                            /* 'setSeconds' */
#define  DUK_STRIDX_SET_UTC_MILLISECONDS                               136                            /* 'setUTCMilliseconds' */
#define  DUK_STRIDX_SET_MILLISECONDS                                   137                            /* 'setMilliseconds' */
#define  DUK_STRIDX_SET_TIME                                           138                            /* 'setTime' */
#define  DUK_STRIDX_GET_TIMEZONE_OFFSET                                139                            /* 'getTimezoneOffset' */
#define  DUK_STRIDX_GET_UTC_MILLISECONDS                               140                            /* 'getUTCMilliseconds' */
#define  DUK_STRIDX_GET_MILLISECONDS                                   141                            /* 'getMilliseconds' */
#define  DUK_STRIDX_GET_UTC_SECONDS                                    142                            /* 'getUTCSeconds' */
#define  DUK_STRIDX_GET_SECONDS                                        143                            /* 'getSeconds' */
#define  DUK_STRIDX_GET_UTC_MINUTES                                    144                            /* 'getUTCMinutes' */
#define  DUK_STRIDX_GET_MINUTES                                        145                            /* 'getMinutes' */
#define  DUK_STRIDX_GET_UTC_HOURS                                      146                            /* 'getUTCHours' */
#define  DUK_STRIDX_GET_HOURS                                          147                            /* 'getHours' */
#define  DUK_STRIDX_GET_UTC_DAY                                        148                            /* 'getUTCDay' */
#define  DUK_STRIDX_GET_DAY                                            149                            /* 'getDay' */
#define  DUK_STRIDX_GET_UTC_DATE                                       150                            /* 'getUTCDate' */
#define  DUK_STRIDX_GET_DATE                                           151                            /* 'getDate' */
#define  DUK_STRIDX_GET_UTC_MONTH                                      152                            /* 'getUTCMonth' */
#define  DUK_STRIDX_GET_MONTH                                          153                            /* 'getMonth' */
#define  DUK_STRIDX_GET_UTC_FULL_YEAR                                  154                            /* 'getUTCFullYear' */
#define  DUK_STRIDX_GET_FULL_YEAR                                      155                            /* 'getFullYear' */
#define  DUK_STRIDX_GET_TIME                                           156                            /* 'getTime' */
#define  DUK_STRIDX_TO_LOCALE_TIME_STRING                              157                            /* 'toLocaleTimeString' */
#define  DUK_STRIDX_TO_LOCALE_DATE_STRING                              158                            /* 'toLocaleDateString' */
#define  DUK_STRIDX_TO_TIME_STRING                                     159                            /* 'toTimeString' */
#define  DUK_STRIDX_TO_DATE_STRING                                     160                            /* 'toDateString' */
#define  DUK_STRIDX_NOW                                                161                            /* 'now' */
#define  DUK_STRIDX_UTC                                                162                            /* 'UTC' */
#define  DUK_STRIDX_PARSE                                              163                            /* 'parse' */
#define  DUK_STRIDX_TO_PRECISION                                       164                            /* 'toPrecision' */
#define  DUK_STRIDX_TO_EXPONENTIAL                                     165                            /* 'toExponential' */
#define  DUK_STRIDX_TO_FIXED                                           166                            /* 'toFixed' */
#define  DUK_STRIDX_POSITIVE_INFINITY                                  167                            /* 'POSITIVE_INFINITY' */
#define  DUK_STRIDX_NEGATIVE_INFINITY                                  168                            /* 'NEGATIVE_INFINITY' */
#define  DUK_STRIDX_NAN                                                169                            /* 'NaN' */
#define  DUK_STRIDX_MIN_VALUE                                          170                            /* 'MIN_VALUE' */
#define  DUK_STRIDX_MAX_VALUE                                          171                            /* 'MAX_VALUE' */
#define  DUK_STRIDX_SUBSTR                                             172                            /* 'substr' */
#define  DUK_STRIDX_TRIM                                               173                            /* 'trim' */
#define  DUK_STRIDX_TO_LOCALE_UPPER_CASE                               174                            /* 'toLocaleUpperCase' */
#define  DUK_STRIDX_TO_UPPER_CASE                                      175                            /* 'toUpperCase' */
#define  DUK_STRIDX_TO_LOCALE_LOWER_CASE                               176                            /* 'toLocaleLowerCase' */
#define  DUK_STRIDX_TO_LOWER_CASE                                      177                            /* 'toLowerCase' */
#define  DUK_STRIDX_SUBSTRING                                          178                            /* 'substring' */
#define  DUK_STRIDX_SPLIT                                              179                            /* 'split' */
#define  DUK_STRIDX_SEARCH                                             180                            /* 'search' */
#define  DUK_STRIDX_REPLACE                                            181                            /* 'replace' */
#define  DUK_STRIDX_MATCH                                              182                            /* 'match' */
#define  DUK_STRIDX_LOCALE_COMPARE                                     183                            /* 'localeCompare' */
#define  DUK_STRIDX_CHAR_CODE_AT                                       184                            /* 'charCodeAt' */
#define  DUK_STRIDX_CHAR_AT                                            185                            /* 'charAt' */
#define  DUK_STRIDX_FROM_CHAR_CODE                                     186                            /* 'fromCharCode' */
#define  DUK_STRIDX_REDUCE_RIGHT                                       187                            /* 'reduceRight' */
#define  DUK_STRIDX_REDUCE                                             188                            /* 'reduce' */
#define  DUK_STRIDX_FILTER                                             189                            /* 'filter' */
#define  DUK_STRIDX_MAP                                                190                            /* 'map' */
#define  DUK_STRIDX_FOR_EACH                                           191                            /* 'forEach' */
#define  DUK_STRIDX_SOME                                               192                            /* 'some' */
#define  DUK_STRIDX_EVERY                                              193                            /* 'every' */
#define  DUK_STRIDX_LAST_INDEX_OF                                      194                            /* 'lastIndexOf' */
#define  DUK_STRIDX_INDEX_OF                                           195                            /* 'indexOf' */
#define  DUK_STRIDX_UNSHIFT                                            196                            /* 'unshift' */
#define  DUK_STRIDX_SPLICE                                             197                            /* 'splice' */
#define  DUK_STRIDX_SORT                                               198                            /* 'sort' */
#define  DUK_STRIDX_SLICE                                              199                            /* 'slice' */
#define  DUK_STRIDX_SHIFT                                              200                            /* 'shift' */
#define  DUK_STRIDX_REVERSE                                            201                            /* 'reverse' */
#define  DUK_STRIDX_PUSH                                               202                            /* 'push' */
#define  DUK_STRIDX_POP                                                203                            /* 'pop' */
#define  DUK_STRIDX_JOIN                                               204                            /* 'join' */
#define  DUK_STRIDX_CONCAT                                             205                            /* 'concat' */
#define  DUK_STRIDX_IS_ARRAY                                           206                            /* 'isArray' */
#define  DUK_STRIDX_LC_ARGUMENTS                                       207                            /* 'arguments' */
#define  DUK_STRIDX_CALLER                                             208                            /* 'caller' */
#define  DUK_STRIDX_BIND                                               209                            /* 'bind' */
#define  DUK_STRIDX_CALL                                               210                            /* 'call' */
#define  DUK_STRIDX_APPLY                                              211                            /* 'apply' */
#define  DUK_STRIDX_PROPERTY_IS_ENUMERABLE                             212                            /* 'propertyIsEnumerable' */
#define  DUK_STRIDX_IS_PROTOTYPE_OF                                    213                            /* 'isPrototypeOf' */
#define  DUK_STRIDX_HAS_OWN_PROPERTY                                   214                            /* 'hasOwnProperty' */
#define  DUK_STRIDX_VALUE_OF                                           215                            /* 'valueOf' */
#define  DUK_STRIDX_TO_LOCALE_STRING                                   216                            /* 'toLocaleString' */
#define  DUK_STRIDX_TO_STRING                                          217                            /* 'toString' */
#define  DUK_STRIDX_CONSTRUCTOR                                        218                            /* 'constructor' */
#define  DUK_STRIDX_ENUMERABLE                                         219                            /* 'enumerable' */
#define  DUK_STRIDX_CONFIGURABLE                                       220                            /* 'configurable' */
#define  DUK_STRIDX_WRITABLE                                           221                            /* 'writable' */
#define  DUK_STRIDX_VALUE                                              222                            /* 'value' */
#define  DUK_STRIDX_KEYS                                               223                            /* 'keys' */
#define  DUK_STRIDX_IS_EXTENSIBLE                                      224                            /* 'isExtensible' */
#define  DUK_STRIDX_IS_FROZEN                                          225                            /* 'isFrozen' */
#define  DUK_STRIDX_IS_SEALED                                          226                            /* 'isSealed' */
#define  DUK_STRIDX_PREVENT_EXTENSIONS                                 227                            /* 'preventExtensions' */
#define  DUK_STRIDX_FREEZE                                             228                            /* 'freeze' */
#define  DUK_STRIDX_SEAL                                               229                            /* 'seal' */
#define  DUK_STRIDX_DEFINE_PROPERTIES                                  230                            /* 'defineProperties' */
#define  DUK_STRIDX_DEFINE_PROPERTY                                    231                            /* 'defineProperty' */
#define  DUK_STRIDX_CREATE                                             232                            /* 'create' */
#define  DUK_STRIDX_GET_OWN_PROPERTY_NAMES                             233                            /* 'getOwnPropertyNames' */
#define  DUK_STRIDX_GET_OWN_PROPERTY_DESCRIPTOR                        234                            /* 'getOwnPropertyDescriptor' */
#define  DUK_STRIDX_GET_PROTOTYPE_OF                                   235                            /* 'getPrototypeOf' */
#define  DUK_STRIDX_PROTOTYPE                                          236                            /* 'prototype' */
#define  DUK_STRIDX_LENGTH                                             237                            /* 'length' */
#define  DUK_STRIDX_ALERT                                              238                            /* 'alert' */
#define  DUK_STRIDX_PRINT                                              239                            /* 'print' */
#define  DUK_STRIDX_UNESCAPE                                           240                            /* 'unescape' */
#define  DUK_STRIDX_ESCAPE                                             241                            /* 'escape' */
#define  DUK_STRIDX_ENCODE_URI_COMPONENT                               242                            /* 'encodeURIComponent' */
#define  DUK_STRIDX_ENCODE_URI                                         243                            /* 'encodeURI' */
#define  DUK_STRIDX_DECODE_URI_COMPONENT                               244                            /* 'decodeURIComponent' */
#define  DUK_STRIDX_DECODE_URI                                         245                            /* 'decodeURI' */
#define  DUK_STRIDX_IS_FINITE                                          246                            /* 'isFinite' */
#define  DUK_STRIDX_IS_NAN                                             247                            /* 'isNaN' */
#define  DUK_STRIDX_PARSE_FLOAT                                        248                            /* 'parseFloat' */
#define  DUK_STRIDX_PARSE_INT                                          249                            /* 'parseInt' */
#define  DUK_STRIDX_EVAL                                               250                            /* 'eval' */
#define  DUK_STRIDX_URI_ERROR                                          251                            /* 'URIError' */
#define  DUK_STRIDX_TYPE_ERROR                                         252                            /* 'TypeError' */
#define  DUK_STRIDX_SYNTAX_ERROR                                       253                            /* 'SyntaxError' */
#define  DUK_STRIDX_REFERENCE_ERROR                                    254                            /* 'ReferenceError' */
#define  DUK_STRIDX_RANGE_ERROR                                        255                            /* 'RangeError' */
#define  DUK_STRIDX_EVAL_ERROR                                         256                            /* 'EvalError' */
#define  DUK_STRIDX_BREAK                                              257                            /* 'break' */
#define  DUK_STRIDX_CASE                                               258                            /* 'case' */
#define  DUK_STRIDX_CATCH                                              259                            /* 'catch' */
#define  DUK_STRIDX_CONTINUE                                           260                            /* 'continue' */
#define  DUK_STRIDX_DEBUGGER                                           261                            /* 'debugger' */
#define  DUK_STRIDX_DEFAULT                                            262                            /* 'default' */
#define  DUK_STRIDX_DELETE                                             263                            /* 'delete' */
#define  DUK_STRIDX_DO                                                 264                            /* 'do' */
#define  DUK_STRIDX_ELSE                                               265                            /* 'else' */
#define  DUK_STRIDX_FINALLY                                            266                            /* 'finally' */
#define  DUK_STRIDX_FOR                                                267                            /* 'for' */
#define  DUK_STRIDX_LC_FUNCTION                                        268                            /* 'function' */
#define  DUK_STRIDX_IF                                                 269                            /* 'if' */
#define  DUK_STRIDX_IN                                                 270                            /* 'in' */
#define  DUK_STRIDX_INSTANCEOF                                         271                            /* 'instanceof' */
#define  DUK_STRIDX_NEW                                                272                            /* 'new' */
#define  DUK_STRIDX_RETURN                                             273                            /* 'return' */
#define  DUK_STRIDX_SWITCH                                             274                            /* 'switch' */
#define  DUK_STRIDX_THIS                                               275                            /* 'this' */
#define  DUK_STRIDX_THROW                                              276                            /* 'throw' */
#define  DUK_STRIDX_TRY                                                277                            /* 'try' */
#define  DUK_STRIDX_TYPEOF                                             278                            /* 'typeof' */
#define  DUK_STRIDX_VAR                                                279                            /* 'var' */
#define  DUK_STRIDX_VOID                                               280                            /* 'void' */
#define  DUK_STRIDX_WHILE                                              281                            /* 'while' */
#define  DUK_STRIDX_WITH                                               282                            /* 'with' */
#define  DUK_STRIDX_CLASS                                              283                            /* 'class' */
#define  DUK_STRIDX_CONST                                              284                            /* 'const' */
#define  DUK_STRIDX_ENUM                                               285                            /* 'enum' */
#define  DUK_STRIDX_EXPORT                                             286                            /* 'export' */
#define  DUK_STRIDX_EXTENDS                                            287                            /* 'extends' */
#define  DUK_STRIDX_IMPORT                                             288                            /* 'import' */
#define  DUK_STRIDX_SUPER                                              289                            /* 'super' */
#define  DUK_STRIDX_NULL                                               290                            /* 'null' */
#define  DUK_STRIDX_TRUE                                               291                            /* 'true' */
#define  DUK_STRIDX_FALSE                                              292                            /* 'false' */
#define  DUK_STRIDX_GET                                                293                            /* 'get' */
#define  DUK_STRIDX_SET                                                294                            /* 'set' */
#define  DUK_STRIDX_IMPLEMENTS                                         295                            /* 'implements' */
#define  DUK_STRIDX_INTERFACE                                          296                            /* 'interface' */
#define  DUK_STRIDX_LET                                                297                            /* 'let' */
#define  DUK_STRIDX_PACKAGE                                            298                            /* 'package' */
#define  DUK_STRIDX_PRIVATE                                            299                            /* 'private' */
#define  DUK_STRIDX_PROTECTED                                          300                            /* 'protected' */
#define  DUK_STRIDX_PUBLIC                                             301                            /* 'public' */
#define  DUK_STRIDX_STATIC                                             302                            /* 'static' */
#define  DUK_STRIDX_YIELD                                              303                            /* 'yield' */

#define  DUK_HEAP_STRING_EMPTY_STRING(heap)                            DUK_HEAP_GET_STRING((heap),DUK_STRIDX_EMPTY_STRING)
#define  DUK_HTHREAD_STRING_EMPTY_STRING(thr)                          DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_EMPTY_STRING)
#define  DUK_HEAP_STRING_GLOBAL(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_GLOBAL)
#define  DUK_HTHREAD_STRING_GLOBAL(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_GLOBAL)
#define  DUK_HEAP_STRING_UC_ARGUMENTS(heap)                            DUK_HEAP_GET_STRING((heap),DUK_STRIDX_UC_ARGUMENTS)
#define  DUK_HTHREAD_STRING_UC_ARGUMENTS(thr)                          DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_UC_ARGUMENTS)
#define  DUK_HEAP_STRING_JSON(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_JSON)
#define  DUK_HTHREAD_STRING_JSON(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_JSON)
#define  DUK_HEAP_STRING_MATH(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_MATH)
#define  DUK_HTHREAD_STRING_MATH(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_MATH)
#define  DUK_HEAP_STRING_ERROR(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_ERROR)
#define  DUK_HTHREAD_STRING_ERROR(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_ERROR)
#define  DUK_HEAP_STRING_REG_EXP(heap)                                 DUK_HEAP_GET_STRING((heap),DUK_STRIDX_REG_EXP)
#define  DUK_HTHREAD_STRING_REG_EXP(thr)                               DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_REG_EXP)
#define  DUK_HEAP_STRING_DATE(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_DATE)
#define  DUK_HTHREAD_STRING_DATE(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_DATE)
#define  DUK_HEAP_STRING_UC_NUMBER(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_UC_NUMBER)
#define  DUK_HTHREAD_STRING_UC_NUMBER(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_UC_NUMBER)
#define  DUK_HEAP_STRING_UC_BOOLEAN(heap)                              DUK_HEAP_GET_STRING((heap),DUK_STRIDX_UC_BOOLEAN)
#define  DUK_HTHREAD_STRING_UC_BOOLEAN(thr)                            DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_UC_BOOLEAN)
#define  DUK_HEAP_STRING_UC_STRING(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_UC_STRING)
#define  DUK_HTHREAD_STRING_UC_STRING(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_UC_STRING)
#define  DUK_HEAP_STRING_ARRAY(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_ARRAY)
#define  DUK_HTHREAD_STRING_ARRAY(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_ARRAY)
#define  DUK_HEAP_STRING_UC_FUNCTION(heap)                             DUK_HEAP_GET_STRING((heap),DUK_STRIDX_UC_FUNCTION)
#define  DUK_HTHREAD_STRING_UC_FUNCTION(thr)                           DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_UC_FUNCTION)
#define  DUK_HEAP_STRING_UC_OBJECT(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_UC_OBJECT)
#define  DUK_HTHREAD_STRING_UC_OBJECT(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_UC_OBJECT)
#define  DUK_HEAP_STRING_JSON_EXT_NEGINF(heap)                         DUK_HEAP_GET_STRING((heap),DUK_STRIDX_JSON_EXT_NEGINF)
#define  DUK_HTHREAD_STRING_JSON_EXT_NEGINF(thr)                       DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_JSON_EXT_NEGINF)
#define  DUK_HEAP_STRING_JSON_EXT_POSINF(heap)                         DUK_HEAP_GET_STRING((heap),DUK_STRIDX_JSON_EXT_POSINF)
#define  DUK_HTHREAD_STRING_JSON_EXT_POSINF(thr)                       DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_JSON_EXT_POSINF)
#define  DUK_HEAP_STRING_JSON_EXT_NAN(heap)                            DUK_HEAP_GET_STRING((heap),DUK_STRIDX_JSON_EXT_NAN)
#define  DUK_HTHREAD_STRING_JSON_EXT_NAN(thr)                          DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_JSON_EXT_NAN)
#define  DUK_HEAP_STRING_JSON_EXT_UNDEFINED(heap)                      DUK_HEAP_GET_STRING((heap),DUK_STRIDX_JSON_EXT_UNDEFINED)
#define  DUK_HTHREAD_STRING_JSON_EXT_UNDEFINED(thr)                    DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_JSON_EXT_UNDEFINED)
#define  DUK_HEAP_STRING_SLEEP(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SLEEP)
#define  DUK_HTHREAD_STRING_SLEEP(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SLEEP)
#define  DUK_HEAP_STRING_BASE64(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_BASE64)
#define  DUK_HTHREAD_STRING_BASE64(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_BASE64)
#define  DUK_HEAP_STRING_HEX(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_HEX)
#define  DUK_HTHREAD_STRING_HEX(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_HEX)
#define  DUK_HEAP_STRING_DEC(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_DEC)
#define  DUK_HTHREAD_STRING_DEC(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_DEC)
#define  DUK_HEAP_STRING_ENC(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_ENC)
#define  DUK_HTHREAD_STRING_ENC(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_ENC)
#define  DUK_HEAP_STRING_GET_FINALIZER(heap)                           DUK_HEAP_GET_STRING((heap),DUK_STRIDX_GET_FINALIZER)
#define  DUK_HTHREAD_STRING_GET_FINALIZER(thr)                         DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_GET_FINALIZER)
#define  DUK_HEAP_STRING_SET_FINALIZER(heap)                           DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SET_FINALIZER)
#define  DUK_HTHREAD_STRING_SET_FINALIZER(thr)                         DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SET_FINALIZER)
#define  DUK_HEAP_STRING_TIME(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TIME)
#define  DUK_HTHREAD_STRING_TIME(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TIME)
#define  DUK_HEAP_STRING_GC(heap)                                      DUK_HEAP_GET_STRING((heap),DUK_STRIDX_GC)
#define  DUK_HTHREAD_STRING_GC(thr)                                    DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_GC)
#define  DUK_HEAP_STRING_CURR(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_CURR)
#define  DUK_HTHREAD_STRING_CURR(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_CURR)
#define  DUK_HEAP_STRING_RESUME(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_RESUME)
#define  DUK_HTHREAD_STRING_RESUME(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_RESUME)
#define  DUK_HEAP_STRING_SPAWN(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SPAWN)
#define  DUK_HTHREAD_STRING_SPAWN(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SPAWN)
#define  DUK_HEAP_STRING_REFC(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_REFC)
#define  DUK_HTHREAD_STRING_REFC(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_REFC)
#define  DUK_HEAP_STRING_ADDR(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_ADDR)
#define  DUK_HTHREAD_STRING_ADDR(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_ADDR)
#define  DUK_HEAP_STRING_BUILD(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_BUILD)
#define  DUK_HTHREAD_STRING_BUILD(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_BUILD)
#define  DUK_HEAP_STRING_VERSION(heap)                                 DUK_HEAP_GET_STRING((heap),DUK_STRIDX_VERSION)
#define  DUK_HTHREAD_STRING_VERSION(thr)                               DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_VERSION)
#define  DUK_HEAP_STRING_DUK(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_DUK)
#define  DUK_HTHREAD_STRING_DUK(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_DUK)
#define  DUK_HEAP_STRING_INT_REGBASE(heap)                             DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INT_REGBASE)
#define  DUK_HTHREAD_STRING_INT_REGBASE(thr)                           DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INT_REGBASE)
#define  DUK_HEAP_STRING_INT_THREAD(heap)                              DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INT_THREAD)
#define  DUK_HTHREAD_STRING_INT_THREAD(thr)                            DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INT_THREAD)
#define  DUK_HEAP_STRING_INT_FINALIZER(heap)                           DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INT_FINALIZER)
#define  DUK_HTHREAD_STRING_INT_FINALIZER(thr)                         DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INT_FINALIZER)
#define  DUK_HEAP_STRING_INT_METATABLE(heap)                           DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INT_METATABLE)
#define  DUK_HTHREAD_STRING_INT_METATABLE(thr)                         DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INT_METATABLE)
#define  DUK_HEAP_STRING_INT_CALLEE(heap)                              DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INT_CALLEE)
#define  DUK_HTHREAD_STRING_INT_CALLEE(thr)                            DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INT_CALLEE)
#define  DUK_HEAP_STRING_INT_MAP(heap)                                 DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INT_MAP)
#define  DUK_HTHREAD_STRING_INT_MAP(thr)                               DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INT_MAP)
#define  DUK_HEAP_STRING_INT_ARGS(heap)                                DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INT_ARGS)
#define  DUK_HTHREAD_STRING_INT_ARGS(thr)                              DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INT_ARGS)
#define  DUK_HEAP_STRING_INT_THIS(heap)                                DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INT_THIS)
#define  DUK_HTHREAD_STRING_INT_THIS(thr)                              DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INT_THIS)
#define  DUK_HEAP_STRING_INT_FILENAME(heap)                            DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INT_FILENAME)
#define  DUK_HTHREAD_STRING_INT_FILENAME(thr)                          DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INT_FILENAME)
#define  DUK_HEAP_STRING_INT_PC2LINE(heap)                             DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INT_PC2LINE)
#define  DUK_HTHREAD_STRING_INT_PC2LINE(thr)                           DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INT_PC2LINE)
#define  DUK_HEAP_STRING_INT_SOURCE(heap)                              DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INT_SOURCE)
#define  DUK_HTHREAD_STRING_INT_SOURCE(thr)                            DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INT_SOURCE)
#define  DUK_HEAP_STRING_INT_VARENV(heap)                              DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INT_VARENV)
#define  DUK_HTHREAD_STRING_INT_VARENV(thr)                            DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INT_VARENV)
#define  DUK_HEAP_STRING_INT_LEXENV(heap)                              DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INT_LEXENV)
#define  DUK_HTHREAD_STRING_INT_LEXENV(thr)                            DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INT_LEXENV)
#define  DUK_HEAP_STRING_INT_VARMAP(heap)                              DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INT_VARMAP)
#define  DUK_HTHREAD_STRING_INT_VARMAP(thr)                            DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INT_VARMAP)
#define  DUK_HEAP_STRING_INT_FORMALS(heap)                             DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INT_FORMALS)
#define  DUK_HTHREAD_STRING_INT_FORMALS(thr)                           DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INT_FORMALS)
#define  DUK_HEAP_STRING_INT_BYTECODE(heap)                            DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INT_BYTECODE)
#define  DUK_HTHREAD_STRING_INT_BYTECODE(thr)                          DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INT_BYTECODE)
#define  DUK_HEAP_STRING_INT_NEXT(heap)                                DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INT_NEXT)
#define  DUK_HTHREAD_STRING_INT_NEXT(thr)                              DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INT_NEXT)
#define  DUK_HEAP_STRING_INT_TARGET(heap)                              DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INT_TARGET)
#define  DUK_HTHREAD_STRING_INT_TARGET(thr)                            DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INT_TARGET)
#define  DUK_HEAP_STRING_INT_VALUE(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INT_VALUE)
#define  DUK_HTHREAD_STRING_INT_VALUE(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INT_VALUE)
#define  DUK_HEAP_STRING_POINTER(heap)                                 DUK_HEAP_GET_STRING((heap),DUK_STRIDX_POINTER)
#define  DUK_HTHREAD_STRING_POINTER(thr)                               DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_POINTER)
#define  DUK_HEAP_STRING_BUFFER(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_BUFFER)
#define  DUK_HTHREAD_STRING_BUFFER(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_BUFFER)
#define  DUK_HEAP_STRING_TRUNC(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TRUNC)
#define  DUK_HTHREAD_STRING_TRUNC(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TRUNC)
#define  DUK_HEAP_STRING_TRACEBACK(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TRACEBACK)
#define  DUK_HTHREAD_STRING_TRACEBACK(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TRACEBACK)
#define  DUK_HEAP_STRING_CAUSE(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_CAUSE)
#define  DUK_HTHREAD_STRING_CAUSE(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_CAUSE)
#define  DUK_HEAP_STRING_CODE(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_CODE)
#define  DUK_HTHREAD_STRING_CODE(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_CODE)
#define  DUK_HEAP_STRING_IS_NATIVE(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_IS_NATIVE)
#define  DUK_HTHREAD_STRING_IS_NATIVE(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_IS_NATIVE)
#define  DUK_HEAP_STRING_LINE_NUMBER(heap)                             DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LINE_NUMBER)
#define  DUK_HTHREAD_STRING_LINE_NUMBER(thr)                           DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LINE_NUMBER)
#define  DUK_HEAP_STRING_FILE_NAME(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_FILE_NAME)
#define  DUK_HTHREAD_STRING_FILE_NAME(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_FILE_NAME)
#define  DUK_HEAP_STRING_PC(heap)                                      DUK_HEAP_GET_STRING((heap),DUK_STRIDX_PC)
#define  DUK_HTHREAD_STRING_PC(thr)                                    DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_PC)
#define  DUK_HEAP_STRING_THROW_TYPE_ERROR(heap)                        DUK_HEAP_GET_STRING((heap),DUK_STRIDX_THROW_TYPE_ERROR)
#define  DUK_HTHREAD_STRING_THROW_TYPE_ERROR(thr)                      DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_THROW_TYPE_ERROR)
#define  DUK_HEAP_STRING_CALLEE(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_CALLEE)
#define  DUK_HTHREAD_STRING_CALLEE(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_CALLEE)
#define  DUK_HEAP_STRING_INVALID_DATE(heap)                            DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INVALID_DATE)
#define  DUK_HTHREAD_STRING_INVALID_DATE(thr)                          DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INVALID_DATE)
#define  DUK_HEAP_STRING_SPACE(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SPACE)
#define  DUK_HTHREAD_STRING_SPACE(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SPACE)
#define  DUK_HEAP_STRING_COMMA(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_COMMA)
#define  DUK_HTHREAD_STRING_COMMA(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_COMMA)
#define  DUK_HEAP_STRING_MINUS_ZERO(heap)                              DUK_HEAP_GET_STRING((heap),DUK_STRIDX_MINUS_ZERO)
#define  DUK_HTHREAD_STRING_MINUS_ZERO(thr)                            DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_MINUS_ZERO)
#define  DUK_HEAP_STRING_PLUS_ZERO(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_PLUS_ZERO)
#define  DUK_HTHREAD_STRING_PLUS_ZERO(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_PLUS_ZERO)
#define  DUK_HEAP_STRING_ZERO(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_ZERO)
#define  DUK_HTHREAD_STRING_ZERO(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_ZERO)
#define  DUK_HEAP_STRING_MINUS_INFINITY(heap)                          DUK_HEAP_GET_STRING((heap),DUK_STRIDX_MINUS_INFINITY)
#define  DUK_HTHREAD_STRING_MINUS_INFINITY(thr)                        DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_MINUS_INFINITY)
#define  DUK_HEAP_STRING_PLUS_INFINITY(heap)                           DUK_HEAP_GET_STRING((heap),DUK_STRIDX_PLUS_INFINITY)
#define  DUK_HTHREAD_STRING_PLUS_INFINITY(thr)                         DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_PLUS_INFINITY)
#define  DUK_HEAP_STRING_INFINITY(heap)                                DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INFINITY)
#define  DUK_HTHREAD_STRING_INFINITY(thr)                              DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INFINITY)
#define  DUK_HEAP_STRING_LC_OBJECT(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LC_OBJECT)
#define  DUK_HTHREAD_STRING_LC_OBJECT(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LC_OBJECT)
#define  DUK_HEAP_STRING_LC_STRING(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LC_STRING)
#define  DUK_HTHREAD_STRING_LC_STRING(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LC_STRING)
#define  DUK_HEAP_STRING_LC_NUMBER(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LC_NUMBER)
#define  DUK_HTHREAD_STRING_LC_NUMBER(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LC_NUMBER)
#define  DUK_HEAP_STRING_LC_BOOLEAN(heap)                              DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LC_BOOLEAN)
#define  DUK_HTHREAD_STRING_LC_BOOLEAN(thr)                            DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LC_BOOLEAN)
#define  DUK_HEAP_STRING_UNDEFINED(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_UNDEFINED)
#define  DUK_HTHREAD_STRING_UNDEFINED(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_UNDEFINED)
#define  DUK_HEAP_STRING_STRINGIFY(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_STRINGIFY)
#define  DUK_HTHREAD_STRING_STRINGIFY(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_STRINGIFY)
#define  DUK_HEAP_STRING_TAN(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TAN)
#define  DUK_HTHREAD_STRING_TAN(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TAN)
#define  DUK_HEAP_STRING_SQRT(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SQRT)
#define  DUK_HTHREAD_STRING_SQRT(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SQRT)
#define  DUK_HEAP_STRING_SIN(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SIN)
#define  DUK_HTHREAD_STRING_SIN(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SIN)
#define  DUK_HEAP_STRING_ROUND(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_ROUND)
#define  DUK_HTHREAD_STRING_ROUND(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_ROUND)
#define  DUK_HEAP_STRING_RANDOM(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_RANDOM)
#define  DUK_HTHREAD_STRING_RANDOM(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_RANDOM)
#define  DUK_HEAP_STRING_POW(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_POW)
#define  DUK_HTHREAD_STRING_POW(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_POW)
#define  DUK_HEAP_STRING_MIN(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_MIN)
#define  DUK_HTHREAD_STRING_MIN(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_MIN)
#define  DUK_HEAP_STRING_MAX(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_MAX)
#define  DUK_HTHREAD_STRING_MAX(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_MAX)
#define  DUK_HEAP_STRING_LOG(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LOG)
#define  DUK_HTHREAD_STRING_LOG(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LOG)
#define  DUK_HEAP_STRING_FLOOR(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_FLOOR)
#define  DUK_HTHREAD_STRING_FLOOR(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_FLOOR)
#define  DUK_HEAP_STRING_EXP(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_EXP)
#define  DUK_HTHREAD_STRING_EXP(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_EXP)
#define  DUK_HEAP_STRING_COS(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_COS)
#define  DUK_HTHREAD_STRING_COS(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_COS)
#define  DUK_HEAP_STRING_CEIL(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_CEIL)
#define  DUK_HTHREAD_STRING_CEIL(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_CEIL)
#define  DUK_HEAP_STRING_ATAN2(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_ATAN2)
#define  DUK_HTHREAD_STRING_ATAN2(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_ATAN2)
#define  DUK_HEAP_STRING_ATAN(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_ATAN)
#define  DUK_HTHREAD_STRING_ATAN(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_ATAN)
#define  DUK_HEAP_STRING_ASIN(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_ASIN)
#define  DUK_HTHREAD_STRING_ASIN(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_ASIN)
#define  DUK_HEAP_STRING_ACOS(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_ACOS)
#define  DUK_HTHREAD_STRING_ACOS(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_ACOS)
#define  DUK_HEAP_STRING_ABS(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_ABS)
#define  DUK_HTHREAD_STRING_ABS(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_ABS)
#define  DUK_HEAP_STRING_SQRT2(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SQRT2)
#define  DUK_HTHREAD_STRING_SQRT2(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SQRT2)
#define  DUK_HEAP_STRING_SQRT1_2(heap)                                 DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SQRT1_2)
#define  DUK_HTHREAD_STRING_SQRT1_2(thr)                               DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SQRT1_2)
#define  DUK_HEAP_STRING_PI(heap)                                      DUK_HEAP_GET_STRING((heap),DUK_STRIDX_PI)
#define  DUK_HTHREAD_STRING_PI(thr)                                    DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_PI)
#define  DUK_HEAP_STRING_LOG10E(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LOG10E)
#define  DUK_HTHREAD_STRING_LOG10E(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LOG10E)
#define  DUK_HEAP_STRING_LOG2E(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LOG2E)
#define  DUK_HTHREAD_STRING_LOG2E(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LOG2E)
#define  DUK_HEAP_STRING_LN2(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LN2)
#define  DUK_HTHREAD_STRING_LN2(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LN2)
#define  DUK_HEAP_STRING_LN10(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LN10)
#define  DUK_HTHREAD_STRING_LN10(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LN10)
#define  DUK_HEAP_STRING_E(heap)                                       DUK_HEAP_GET_STRING((heap),DUK_STRIDX_E)
#define  DUK_HTHREAD_STRING_E(thr)                                     DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_E)
#define  DUK_HEAP_STRING_MESSAGE(heap)                                 DUK_HEAP_GET_STRING((heap),DUK_STRIDX_MESSAGE)
#define  DUK_HTHREAD_STRING_MESSAGE(thr)                               DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_MESSAGE)
#define  DUK_HEAP_STRING_NAME(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_NAME)
#define  DUK_HTHREAD_STRING_NAME(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_NAME)
#define  DUK_HEAP_STRING_INPUT(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INPUT)
#define  DUK_HTHREAD_STRING_INPUT(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INPUT)
#define  DUK_HEAP_STRING_INDEX(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INDEX)
#define  DUK_HTHREAD_STRING_INDEX(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INDEX)
#define  DUK_HEAP_STRING_ESCAPED_EMPTY_REGEXP(heap)                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_ESCAPED_EMPTY_REGEXP)
#define  DUK_HTHREAD_STRING_ESCAPED_EMPTY_REGEXP(thr)                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_ESCAPED_EMPTY_REGEXP)
#define  DUK_HEAP_STRING_LAST_INDEX(heap)                              DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LAST_INDEX)
#define  DUK_HTHREAD_STRING_LAST_INDEX(thr)                            DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LAST_INDEX)
#define  DUK_HEAP_STRING_MULTILINE(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_MULTILINE)
#define  DUK_HTHREAD_STRING_MULTILINE(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_MULTILINE)
#define  DUK_HEAP_STRING_IGNORE_CASE(heap)                             DUK_HEAP_GET_STRING((heap),DUK_STRIDX_IGNORE_CASE)
#define  DUK_HTHREAD_STRING_IGNORE_CASE(thr)                           DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_IGNORE_CASE)
#define  DUK_HEAP_STRING_SOURCE(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SOURCE)
#define  DUK_HTHREAD_STRING_SOURCE(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SOURCE)
#define  DUK_HEAP_STRING_TEST(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TEST)
#define  DUK_HTHREAD_STRING_TEST(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TEST)
#define  DUK_HEAP_STRING_EXEC(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_EXEC)
#define  DUK_HTHREAD_STRING_EXEC(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_EXEC)
#define  DUK_HEAP_STRING_TO_GMT_STRING(heap)                           DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TO_GMT_STRING)
#define  DUK_HTHREAD_STRING_TO_GMT_STRING(thr)                         DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TO_GMT_STRING)
#define  DUK_HEAP_STRING_SET_YEAR(heap)                                DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SET_YEAR)
#define  DUK_HTHREAD_STRING_SET_YEAR(thr)                              DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SET_YEAR)
#define  DUK_HEAP_STRING_GET_YEAR(heap)                                DUK_HEAP_GET_STRING((heap),DUK_STRIDX_GET_YEAR)
#define  DUK_HTHREAD_STRING_GET_YEAR(thr)                              DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_GET_YEAR)
#define  DUK_HEAP_STRING_TO_JSON(heap)                                 DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TO_JSON)
#define  DUK_HTHREAD_STRING_TO_JSON(thr)                               DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TO_JSON)
#define  DUK_HEAP_STRING_TO_ISO_STRING(heap)                           DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TO_ISO_STRING)
#define  DUK_HTHREAD_STRING_TO_ISO_STRING(thr)                         DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TO_ISO_STRING)
#define  DUK_HEAP_STRING_TO_UTC_STRING(heap)                           DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TO_UTC_STRING)
#define  DUK_HTHREAD_STRING_TO_UTC_STRING(thr)                         DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TO_UTC_STRING)
#define  DUK_HEAP_STRING_SET_UTC_FULL_YEAR(heap)                       DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SET_UTC_FULL_YEAR)
#define  DUK_HTHREAD_STRING_SET_UTC_FULL_YEAR(thr)                     DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SET_UTC_FULL_YEAR)
#define  DUK_HEAP_STRING_SET_FULL_YEAR(heap)                           DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SET_FULL_YEAR)
#define  DUK_HTHREAD_STRING_SET_FULL_YEAR(thr)                         DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SET_FULL_YEAR)
#define  DUK_HEAP_STRING_SET_UTC_MONTH(heap)                           DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SET_UTC_MONTH)
#define  DUK_HTHREAD_STRING_SET_UTC_MONTH(thr)                         DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SET_UTC_MONTH)
#define  DUK_HEAP_STRING_SET_MONTH(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SET_MONTH)
#define  DUK_HTHREAD_STRING_SET_MONTH(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SET_MONTH)
#define  DUK_HEAP_STRING_SET_UTC_DATE(heap)                            DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SET_UTC_DATE)
#define  DUK_HTHREAD_STRING_SET_UTC_DATE(thr)                          DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SET_UTC_DATE)
#define  DUK_HEAP_STRING_SET_DATE(heap)                                DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SET_DATE)
#define  DUK_HTHREAD_STRING_SET_DATE(thr)                              DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SET_DATE)
#define  DUK_HEAP_STRING_SET_UTC_HOURS(heap)                           DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SET_UTC_HOURS)
#define  DUK_HTHREAD_STRING_SET_UTC_HOURS(thr)                         DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SET_UTC_HOURS)
#define  DUK_HEAP_STRING_SET_HOURS(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SET_HOURS)
#define  DUK_HTHREAD_STRING_SET_HOURS(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SET_HOURS)
#define  DUK_HEAP_STRING_SET_UTC_MINUTES(heap)                         DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SET_UTC_MINUTES)
#define  DUK_HTHREAD_STRING_SET_UTC_MINUTES(thr)                       DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SET_UTC_MINUTES)
#define  DUK_HEAP_STRING_SET_MINUTES(heap)                             DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SET_MINUTES)
#define  DUK_HTHREAD_STRING_SET_MINUTES(thr)                           DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SET_MINUTES)
#define  DUK_HEAP_STRING_SET_UTC_SECONDS(heap)                         DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SET_UTC_SECONDS)
#define  DUK_HTHREAD_STRING_SET_UTC_SECONDS(thr)                       DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SET_UTC_SECONDS)
#define  DUK_HEAP_STRING_SET_SECONDS(heap)                             DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SET_SECONDS)
#define  DUK_HTHREAD_STRING_SET_SECONDS(thr)                           DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SET_SECONDS)
#define  DUK_HEAP_STRING_SET_UTC_MILLISECONDS(heap)                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SET_UTC_MILLISECONDS)
#define  DUK_HTHREAD_STRING_SET_UTC_MILLISECONDS(thr)                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SET_UTC_MILLISECONDS)
#define  DUK_HEAP_STRING_SET_MILLISECONDS(heap)                        DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SET_MILLISECONDS)
#define  DUK_HTHREAD_STRING_SET_MILLISECONDS(thr)                      DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SET_MILLISECONDS)
#define  DUK_HEAP_STRING_SET_TIME(heap)                                DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SET_TIME)
#define  DUK_HTHREAD_STRING_SET_TIME(thr)                              DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SET_TIME)
#define  DUK_HEAP_STRING_GET_TIMEZONE_OFFSET(heap)                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_GET_TIMEZONE_OFFSET)
#define  DUK_HTHREAD_STRING_GET_TIMEZONE_OFFSET(thr)                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_GET_TIMEZONE_OFFSET)
#define  DUK_HEAP_STRING_GET_UTC_MILLISECONDS(heap)                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_GET_UTC_MILLISECONDS)
#define  DUK_HTHREAD_STRING_GET_UTC_MILLISECONDS(thr)                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_GET_UTC_MILLISECONDS)
#define  DUK_HEAP_STRING_GET_MILLISECONDS(heap)                        DUK_HEAP_GET_STRING((heap),DUK_STRIDX_GET_MILLISECONDS)
#define  DUK_HTHREAD_STRING_GET_MILLISECONDS(thr)                      DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_GET_MILLISECONDS)
#define  DUK_HEAP_STRING_GET_UTC_SECONDS(heap)                         DUK_HEAP_GET_STRING((heap),DUK_STRIDX_GET_UTC_SECONDS)
#define  DUK_HTHREAD_STRING_GET_UTC_SECONDS(thr)                       DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_GET_UTC_SECONDS)
#define  DUK_HEAP_STRING_GET_SECONDS(heap)                             DUK_HEAP_GET_STRING((heap),DUK_STRIDX_GET_SECONDS)
#define  DUK_HTHREAD_STRING_GET_SECONDS(thr)                           DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_GET_SECONDS)
#define  DUK_HEAP_STRING_GET_UTC_MINUTES(heap)                         DUK_HEAP_GET_STRING((heap),DUK_STRIDX_GET_UTC_MINUTES)
#define  DUK_HTHREAD_STRING_GET_UTC_MINUTES(thr)                       DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_GET_UTC_MINUTES)
#define  DUK_HEAP_STRING_GET_MINUTES(heap)                             DUK_HEAP_GET_STRING((heap),DUK_STRIDX_GET_MINUTES)
#define  DUK_HTHREAD_STRING_GET_MINUTES(thr)                           DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_GET_MINUTES)
#define  DUK_HEAP_STRING_GET_UTC_HOURS(heap)                           DUK_HEAP_GET_STRING((heap),DUK_STRIDX_GET_UTC_HOURS)
#define  DUK_HTHREAD_STRING_GET_UTC_HOURS(thr)                         DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_GET_UTC_HOURS)
#define  DUK_HEAP_STRING_GET_HOURS(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_GET_HOURS)
#define  DUK_HTHREAD_STRING_GET_HOURS(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_GET_HOURS)
#define  DUK_HEAP_STRING_GET_UTC_DAY(heap)                             DUK_HEAP_GET_STRING((heap),DUK_STRIDX_GET_UTC_DAY)
#define  DUK_HTHREAD_STRING_GET_UTC_DAY(thr)                           DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_GET_UTC_DAY)
#define  DUK_HEAP_STRING_GET_DAY(heap)                                 DUK_HEAP_GET_STRING((heap),DUK_STRIDX_GET_DAY)
#define  DUK_HTHREAD_STRING_GET_DAY(thr)                               DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_GET_DAY)
#define  DUK_HEAP_STRING_GET_UTC_DATE(heap)                            DUK_HEAP_GET_STRING((heap),DUK_STRIDX_GET_UTC_DATE)
#define  DUK_HTHREAD_STRING_GET_UTC_DATE(thr)                          DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_GET_UTC_DATE)
#define  DUK_HEAP_STRING_GET_DATE(heap)                                DUK_HEAP_GET_STRING((heap),DUK_STRIDX_GET_DATE)
#define  DUK_HTHREAD_STRING_GET_DATE(thr)                              DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_GET_DATE)
#define  DUK_HEAP_STRING_GET_UTC_MONTH(heap)                           DUK_HEAP_GET_STRING((heap),DUK_STRIDX_GET_UTC_MONTH)
#define  DUK_HTHREAD_STRING_GET_UTC_MONTH(thr)                         DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_GET_UTC_MONTH)
#define  DUK_HEAP_STRING_GET_MONTH(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_GET_MONTH)
#define  DUK_HTHREAD_STRING_GET_MONTH(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_GET_MONTH)
#define  DUK_HEAP_STRING_GET_UTC_FULL_YEAR(heap)                       DUK_HEAP_GET_STRING((heap),DUK_STRIDX_GET_UTC_FULL_YEAR)
#define  DUK_HTHREAD_STRING_GET_UTC_FULL_YEAR(thr)                     DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_GET_UTC_FULL_YEAR)
#define  DUK_HEAP_STRING_GET_FULL_YEAR(heap)                           DUK_HEAP_GET_STRING((heap),DUK_STRIDX_GET_FULL_YEAR)
#define  DUK_HTHREAD_STRING_GET_FULL_YEAR(thr)                         DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_GET_FULL_YEAR)
#define  DUK_HEAP_STRING_GET_TIME(heap)                                DUK_HEAP_GET_STRING((heap),DUK_STRIDX_GET_TIME)
#define  DUK_HTHREAD_STRING_GET_TIME(thr)                              DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_GET_TIME)
#define  DUK_HEAP_STRING_TO_LOCALE_TIME_STRING(heap)                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TO_LOCALE_TIME_STRING)
#define  DUK_HTHREAD_STRING_TO_LOCALE_TIME_STRING(thr)                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TO_LOCALE_TIME_STRING)
#define  DUK_HEAP_STRING_TO_LOCALE_DATE_STRING(heap)                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TO_LOCALE_DATE_STRING)
#define  DUK_HTHREAD_STRING_TO_LOCALE_DATE_STRING(thr)                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TO_LOCALE_DATE_STRING)
#define  DUK_HEAP_STRING_TO_TIME_STRING(heap)                          DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TO_TIME_STRING)
#define  DUK_HTHREAD_STRING_TO_TIME_STRING(thr)                        DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TO_TIME_STRING)
#define  DUK_HEAP_STRING_TO_DATE_STRING(heap)                          DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TO_DATE_STRING)
#define  DUK_HTHREAD_STRING_TO_DATE_STRING(thr)                        DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TO_DATE_STRING)
#define  DUK_HEAP_STRING_NOW(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_NOW)
#define  DUK_HTHREAD_STRING_NOW(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_NOW)
#define  DUK_HEAP_STRING_UTC(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_UTC)
#define  DUK_HTHREAD_STRING_UTC(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_UTC)
#define  DUK_HEAP_STRING_PARSE(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_PARSE)
#define  DUK_HTHREAD_STRING_PARSE(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_PARSE)
#define  DUK_HEAP_STRING_TO_PRECISION(heap)                            DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TO_PRECISION)
#define  DUK_HTHREAD_STRING_TO_PRECISION(thr)                          DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TO_PRECISION)
#define  DUK_HEAP_STRING_TO_EXPONENTIAL(heap)                          DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TO_EXPONENTIAL)
#define  DUK_HTHREAD_STRING_TO_EXPONENTIAL(thr)                        DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TO_EXPONENTIAL)
#define  DUK_HEAP_STRING_TO_FIXED(heap)                                DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TO_FIXED)
#define  DUK_HTHREAD_STRING_TO_FIXED(thr)                              DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TO_FIXED)
#define  DUK_HEAP_STRING_POSITIVE_INFINITY(heap)                       DUK_HEAP_GET_STRING((heap),DUK_STRIDX_POSITIVE_INFINITY)
#define  DUK_HTHREAD_STRING_POSITIVE_INFINITY(thr)                     DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_POSITIVE_INFINITY)
#define  DUK_HEAP_STRING_NEGATIVE_INFINITY(heap)                       DUK_HEAP_GET_STRING((heap),DUK_STRIDX_NEGATIVE_INFINITY)
#define  DUK_HTHREAD_STRING_NEGATIVE_INFINITY(thr)                     DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_NEGATIVE_INFINITY)
#define  DUK_HEAP_STRING_NAN(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_NAN)
#define  DUK_HTHREAD_STRING_NAN(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_NAN)
#define  DUK_HEAP_STRING_MIN_VALUE(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_MIN_VALUE)
#define  DUK_HTHREAD_STRING_MIN_VALUE(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_MIN_VALUE)
#define  DUK_HEAP_STRING_MAX_VALUE(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_MAX_VALUE)
#define  DUK_HTHREAD_STRING_MAX_VALUE(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_MAX_VALUE)
#define  DUK_HEAP_STRING_SUBSTR(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SUBSTR)
#define  DUK_HTHREAD_STRING_SUBSTR(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SUBSTR)
#define  DUK_HEAP_STRING_TRIM(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TRIM)
#define  DUK_HTHREAD_STRING_TRIM(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TRIM)
#define  DUK_HEAP_STRING_TO_LOCALE_UPPER_CASE(heap)                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TO_LOCALE_UPPER_CASE)
#define  DUK_HTHREAD_STRING_TO_LOCALE_UPPER_CASE(thr)                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TO_LOCALE_UPPER_CASE)
#define  DUK_HEAP_STRING_TO_UPPER_CASE(heap)                           DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TO_UPPER_CASE)
#define  DUK_HTHREAD_STRING_TO_UPPER_CASE(thr)                         DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TO_UPPER_CASE)
#define  DUK_HEAP_STRING_TO_LOCALE_LOWER_CASE(heap)                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TO_LOCALE_LOWER_CASE)
#define  DUK_HTHREAD_STRING_TO_LOCALE_LOWER_CASE(thr)                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TO_LOCALE_LOWER_CASE)
#define  DUK_HEAP_STRING_TO_LOWER_CASE(heap)                           DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TO_LOWER_CASE)
#define  DUK_HTHREAD_STRING_TO_LOWER_CASE(thr)                         DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TO_LOWER_CASE)
#define  DUK_HEAP_STRING_SUBSTRING(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SUBSTRING)
#define  DUK_HTHREAD_STRING_SUBSTRING(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SUBSTRING)
#define  DUK_HEAP_STRING_SPLIT(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SPLIT)
#define  DUK_HTHREAD_STRING_SPLIT(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SPLIT)
#define  DUK_HEAP_STRING_SEARCH(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SEARCH)
#define  DUK_HTHREAD_STRING_SEARCH(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SEARCH)
#define  DUK_HEAP_STRING_REPLACE(heap)                                 DUK_HEAP_GET_STRING((heap),DUK_STRIDX_REPLACE)
#define  DUK_HTHREAD_STRING_REPLACE(thr)                               DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_REPLACE)
#define  DUK_HEAP_STRING_MATCH(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_MATCH)
#define  DUK_HTHREAD_STRING_MATCH(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_MATCH)
#define  DUK_HEAP_STRING_LOCALE_COMPARE(heap)                          DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LOCALE_COMPARE)
#define  DUK_HTHREAD_STRING_LOCALE_COMPARE(thr)                        DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LOCALE_COMPARE)
#define  DUK_HEAP_STRING_CHAR_CODE_AT(heap)                            DUK_HEAP_GET_STRING((heap),DUK_STRIDX_CHAR_CODE_AT)
#define  DUK_HTHREAD_STRING_CHAR_CODE_AT(thr)                          DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_CHAR_CODE_AT)
#define  DUK_HEAP_STRING_CHAR_AT(heap)                                 DUK_HEAP_GET_STRING((heap),DUK_STRIDX_CHAR_AT)
#define  DUK_HTHREAD_STRING_CHAR_AT(thr)                               DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_CHAR_AT)
#define  DUK_HEAP_STRING_FROM_CHAR_CODE(heap)                          DUK_HEAP_GET_STRING((heap),DUK_STRIDX_FROM_CHAR_CODE)
#define  DUK_HTHREAD_STRING_FROM_CHAR_CODE(thr)                        DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_FROM_CHAR_CODE)
#define  DUK_HEAP_STRING_REDUCE_RIGHT(heap)                            DUK_HEAP_GET_STRING((heap),DUK_STRIDX_REDUCE_RIGHT)
#define  DUK_HTHREAD_STRING_REDUCE_RIGHT(thr)                          DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_REDUCE_RIGHT)
#define  DUK_HEAP_STRING_REDUCE(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_REDUCE)
#define  DUK_HTHREAD_STRING_REDUCE(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_REDUCE)
#define  DUK_HEAP_STRING_FILTER(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_FILTER)
#define  DUK_HTHREAD_STRING_FILTER(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_FILTER)
#define  DUK_HEAP_STRING_MAP(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_MAP)
#define  DUK_HTHREAD_STRING_MAP(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_MAP)
#define  DUK_HEAP_STRING_FOR_EACH(heap)                                DUK_HEAP_GET_STRING((heap),DUK_STRIDX_FOR_EACH)
#define  DUK_HTHREAD_STRING_FOR_EACH(thr)                              DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_FOR_EACH)
#define  DUK_HEAP_STRING_SOME(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SOME)
#define  DUK_HTHREAD_STRING_SOME(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SOME)
#define  DUK_HEAP_STRING_EVERY(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_EVERY)
#define  DUK_HTHREAD_STRING_EVERY(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_EVERY)
#define  DUK_HEAP_STRING_LAST_INDEX_OF(heap)                           DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LAST_INDEX_OF)
#define  DUK_HTHREAD_STRING_LAST_INDEX_OF(thr)                         DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LAST_INDEX_OF)
#define  DUK_HEAP_STRING_INDEX_OF(heap)                                DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INDEX_OF)
#define  DUK_HTHREAD_STRING_INDEX_OF(thr)                              DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INDEX_OF)
#define  DUK_HEAP_STRING_UNSHIFT(heap)                                 DUK_HEAP_GET_STRING((heap),DUK_STRIDX_UNSHIFT)
#define  DUK_HTHREAD_STRING_UNSHIFT(thr)                               DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_UNSHIFT)
#define  DUK_HEAP_STRING_SPLICE(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SPLICE)
#define  DUK_HTHREAD_STRING_SPLICE(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SPLICE)
#define  DUK_HEAP_STRING_SORT(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SORT)
#define  DUK_HTHREAD_STRING_SORT(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SORT)
#define  DUK_HEAP_STRING_SLICE(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SLICE)
#define  DUK_HTHREAD_STRING_SLICE(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SLICE)
#define  DUK_HEAP_STRING_SHIFT(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SHIFT)
#define  DUK_HTHREAD_STRING_SHIFT(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SHIFT)
#define  DUK_HEAP_STRING_REVERSE(heap)                                 DUK_HEAP_GET_STRING((heap),DUK_STRIDX_REVERSE)
#define  DUK_HTHREAD_STRING_REVERSE(thr)                               DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_REVERSE)
#define  DUK_HEAP_STRING_PUSH(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_PUSH)
#define  DUK_HTHREAD_STRING_PUSH(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_PUSH)
#define  DUK_HEAP_STRING_POP(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_POP)
#define  DUK_HTHREAD_STRING_POP(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_POP)
#define  DUK_HEAP_STRING_JOIN(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_JOIN)
#define  DUK_HTHREAD_STRING_JOIN(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_JOIN)
#define  DUK_HEAP_STRING_CONCAT(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_CONCAT)
#define  DUK_HTHREAD_STRING_CONCAT(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_CONCAT)
#define  DUK_HEAP_STRING_IS_ARRAY(heap)                                DUK_HEAP_GET_STRING((heap),DUK_STRIDX_IS_ARRAY)
#define  DUK_HTHREAD_STRING_IS_ARRAY(thr)                              DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_IS_ARRAY)
#define  DUK_HEAP_STRING_LC_ARGUMENTS(heap)                            DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LC_ARGUMENTS)
#define  DUK_HTHREAD_STRING_LC_ARGUMENTS(thr)                          DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LC_ARGUMENTS)
#define  DUK_HEAP_STRING_CALLER(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_CALLER)
#define  DUK_HTHREAD_STRING_CALLER(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_CALLER)
#define  DUK_HEAP_STRING_BIND(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_BIND)
#define  DUK_HTHREAD_STRING_BIND(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_BIND)
#define  DUK_HEAP_STRING_CALL(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_CALL)
#define  DUK_HTHREAD_STRING_CALL(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_CALL)
#define  DUK_HEAP_STRING_APPLY(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_APPLY)
#define  DUK_HTHREAD_STRING_APPLY(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_APPLY)
#define  DUK_HEAP_STRING_PROPERTY_IS_ENUMERABLE(heap)                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_PROPERTY_IS_ENUMERABLE)
#define  DUK_HTHREAD_STRING_PROPERTY_IS_ENUMERABLE(thr)                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_PROPERTY_IS_ENUMERABLE)
#define  DUK_HEAP_STRING_IS_PROTOTYPE_OF(heap)                         DUK_HEAP_GET_STRING((heap),DUK_STRIDX_IS_PROTOTYPE_OF)
#define  DUK_HTHREAD_STRING_IS_PROTOTYPE_OF(thr)                       DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_IS_PROTOTYPE_OF)
#define  DUK_HEAP_STRING_HAS_OWN_PROPERTY(heap)                        DUK_HEAP_GET_STRING((heap),DUK_STRIDX_HAS_OWN_PROPERTY)
#define  DUK_HTHREAD_STRING_HAS_OWN_PROPERTY(thr)                      DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_HAS_OWN_PROPERTY)
#define  DUK_HEAP_STRING_VALUE_OF(heap)                                DUK_HEAP_GET_STRING((heap),DUK_STRIDX_VALUE_OF)
#define  DUK_HTHREAD_STRING_VALUE_OF(thr)                              DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_VALUE_OF)
#define  DUK_HEAP_STRING_TO_LOCALE_STRING(heap)                        DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TO_LOCALE_STRING)
#define  DUK_HTHREAD_STRING_TO_LOCALE_STRING(thr)                      DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TO_LOCALE_STRING)
#define  DUK_HEAP_STRING_TO_STRING(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TO_STRING)
#define  DUK_HTHREAD_STRING_TO_STRING(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TO_STRING)
#define  DUK_HEAP_STRING_CONSTRUCTOR(heap)                             DUK_HEAP_GET_STRING((heap),DUK_STRIDX_CONSTRUCTOR)
#define  DUK_HTHREAD_STRING_CONSTRUCTOR(thr)                           DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_CONSTRUCTOR)
#define  DUK_HEAP_STRING_ENUMERABLE(heap)                              DUK_HEAP_GET_STRING((heap),DUK_STRIDX_ENUMERABLE)
#define  DUK_HTHREAD_STRING_ENUMERABLE(thr)                            DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_ENUMERABLE)
#define  DUK_HEAP_STRING_CONFIGURABLE(heap)                            DUK_HEAP_GET_STRING((heap),DUK_STRIDX_CONFIGURABLE)
#define  DUK_HTHREAD_STRING_CONFIGURABLE(thr)                          DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_CONFIGURABLE)
#define  DUK_HEAP_STRING_WRITABLE(heap)                                DUK_HEAP_GET_STRING((heap),DUK_STRIDX_WRITABLE)
#define  DUK_HTHREAD_STRING_WRITABLE(thr)                              DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_WRITABLE)
#define  DUK_HEAP_STRING_VALUE(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_VALUE)
#define  DUK_HTHREAD_STRING_VALUE(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_VALUE)
#define  DUK_HEAP_STRING_KEYS(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_KEYS)
#define  DUK_HTHREAD_STRING_KEYS(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_KEYS)
#define  DUK_HEAP_STRING_IS_EXTENSIBLE(heap)                           DUK_HEAP_GET_STRING((heap),DUK_STRIDX_IS_EXTENSIBLE)
#define  DUK_HTHREAD_STRING_IS_EXTENSIBLE(thr)                         DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_IS_EXTENSIBLE)
#define  DUK_HEAP_STRING_IS_FROZEN(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_IS_FROZEN)
#define  DUK_HTHREAD_STRING_IS_FROZEN(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_IS_FROZEN)
#define  DUK_HEAP_STRING_IS_SEALED(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_IS_SEALED)
#define  DUK_HTHREAD_STRING_IS_SEALED(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_IS_SEALED)
#define  DUK_HEAP_STRING_PREVENT_EXTENSIONS(heap)                      DUK_HEAP_GET_STRING((heap),DUK_STRIDX_PREVENT_EXTENSIONS)
#define  DUK_HTHREAD_STRING_PREVENT_EXTENSIONS(thr)                    DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_PREVENT_EXTENSIONS)
#define  DUK_HEAP_STRING_FREEZE(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_FREEZE)
#define  DUK_HTHREAD_STRING_FREEZE(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_FREEZE)
#define  DUK_HEAP_STRING_SEAL(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SEAL)
#define  DUK_HTHREAD_STRING_SEAL(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SEAL)
#define  DUK_HEAP_STRING_DEFINE_PROPERTIES(heap)                       DUK_HEAP_GET_STRING((heap),DUK_STRIDX_DEFINE_PROPERTIES)
#define  DUK_HTHREAD_STRING_DEFINE_PROPERTIES(thr)                     DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_DEFINE_PROPERTIES)
#define  DUK_HEAP_STRING_DEFINE_PROPERTY(heap)                         DUK_HEAP_GET_STRING((heap),DUK_STRIDX_DEFINE_PROPERTY)
#define  DUK_HTHREAD_STRING_DEFINE_PROPERTY(thr)                       DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_DEFINE_PROPERTY)
#define  DUK_HEAP_STRING_CREATE(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_CREATE)
#define  DUK_HTHREAD_STRING_CREATE(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_CREATE)
#define  DUK_HEAP_STRING_GET_OWN_PROPERTY_NAMES(heap)                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_GET_OWN_PROPERTY_NAMES)
#define  DUK_HTHREAD_STRING_GET_OWN_PROPERTY_NAMES(thr)                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_GET_OWN_PROPERTY_NAMES)
#define  DUK_HEAP_STRING_GET_OWN_PROPERTY_DESCRIPTOR(heap)             DUK_HEAP_GET_STRING((heap),DUK_STRIDX_GET_OWN_PROPERTY_DESCRIPTOR)
#define  DUK_HTHREAD_STRING_GET_OWN_PROPERTY_DESCRIPTOR(thr)           DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_GET_OWN_PROPERTY_DESCRIPTOR)
#define  DUK_HEAP_STRING_GET_PROTOTYPE_OF(heap)                        DUK_HEAP_GET_STRING((heap),DUK_STRIDX_GET_PROTOTYPE_OF)
#define  DUK_HTHREAD_STRING_GET_PROTOTYPE_OF(thr)                      DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_GET_PROTOTYPE_OF)
#define  DUK_HEAP_STRING_PROTOTYPE(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_PROTOTYPE)
#define  DUK_HTHREAD_STRING_PROTOTYPE(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_PROTOTYPE)
#define  DUK_HEAP_STRING_LENGTH(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LENGTH)
#define  DUK_HTHREAD_STRING_LENGTH(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LENGTH)
#define  DUK_HEAP_STRING_ALERT(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_ALERT)
#define  DUK_HTHREAD_STRING_ALERT(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_ALERT)
#define  DUK_HEAP_STRING_PRINT(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_PRINT)
#define  DUK_HTHREAD_STRING_PRINT(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_PRINT)
#define  DUK_HEAP_STRING_UNESCAPE(heap)                                DUK_HEAP_GET_STRING((heap),DUK_STRIDX_UNESCAPE)
#define  DUK_HTHREAD_STRING_UNESCAPE(thr)                              DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_UNESCAPE)
#define  DUK_HEAP_STRING_ESCAPE(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_ESCAPE)
#define  DUK_HTHREAD_STRING_ESCAPE(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_ESCAPE)
#define  DUK_HEAP_STRING_ENCODE_URI_COMPONENT(heap)                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_ENCODE_URI_COMPONENT)
#define  DUK_HTHREAD_STRING_ENCODE_URI_COMPONENT(thr)                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_ENCODE_URI_COMPONENT)
#define  DUK_HEAP_STRING_ENCODE_URI(heap)                              DUK_HEAP_GET_STRING((heap),DUK_STRIDX_ENCODE_URI)
#define  DUK_HTHREAD_STRING_ENCODE_URI(thr)                            DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_ENCODE_URI)
#define  DUK_HEAP_STRING_DECODE_URI_COMPONENT(heap)                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_DECODE_URI_COMPONENT)
#define  DUK_HTHREAD_STRING_DECODE_URI_COMPONENT(thr)                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_DECODE_URI_COMPONENT)
#define  DUK_HEAP_STRING_DECODE_URI(heap)                              DUK_HEAP_GET_STRING((heap),DUK_STRIDX_DECODE_URI)
#define  DUK_HTHREAD_STRING_DECODE_URI(thr)                            DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_DECODE_URI)
#define  DUK_HEAP_STRING_IS_FINITE(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_IS_FINITE)
#define  DUK_HTHREAD_STRING_IS_FINITE(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_IS_FINITE)
#define  DUK_HEAP_STRING_IS_NAN(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_IS_NAN)
#define  DUK_HTHREAD_STRING_IS_NAN(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_IS_NAN)
#define  DUK_HEAP_STRING_PARSE_FLOAT(heap)                             DUK_HEAP_GET_STRING((heap),DUK_STRIDX_PARSE_FLOAT)
#define  DUK_HTHREAD_STRING_PARSE_FLOAT(thr)                           DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_PARSE_FLOAT)
#define  DUK_HEAP_STRING_PARSE_INT(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_PARSE_INT)
#define  DUK_HTHREAD_STRING_PARSE_INT(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_PARSE_INT)
#define  DUK_HEAP_STRING_EVAL(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_EVAL)
#define  DUK_HTHREAD_STRING_EVAL(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_EVAL)
#define  DUK_HEAP_STRING_URI_ERROR(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_URI_ERROR)
#define  DUK_HTHREAD_STRING_URI_ERROR(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_URI_ERROR)
#define  DUK_HEAP_STRING_TYPE_ERROR(heap)                              DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TYPE_ERROR)
#define  DUK_HTHREAD_STRING_TYPE_ERROR(thr)                            DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TYPE_ERROR)
#define  DUK_HEAP_STRING_SYNTAX_ERROR(heap)                            DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SYNTAX_ERROR)
#define  DUK_HTHREAD_STRING_SYNTAX_ERROR(thr)                          DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SYNTAX_ERROR)
#define  DUK_HEAP_STRING_REFERENCE_ERROR(heap)                         DUK_HEAP_GET_STRING((heap),DUK_STRIDX_REFERENCE_ERROR)
#define  DUK_HTHREAD_STRING_REFERENCE_ERROR(thr)                       DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_REFERENCE_ERROR)
#define  DUK_HEAP_STRING_RANGE_ERROR(heap)                             DUK_HEAP_GET_STRING((heap),DUK_STRIDX_RANGE_ERROR)
#define  DUK_HTHREAD_STRING_RANGE_ERROR(thr)                           DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_RANGE_ERROR)
#define  DUK_HEAP_STRING_EVAL_ERROR(heap)                              DUK_HEAP_GET_STRING((heap),DUK_STRIDX_EVAL_ERROR)
#define  DUK_HTHREAD_STRING_EVAL_ERROR(thr)                            DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_EVAL_ERROR)
#define  DUK_HEAP_STRING_BREAK(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_BREAK)
#define  DUK_HTHREAD_STRING_BREAK(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_BREAK)
#define  DUK_HEAP_STRING_CASE(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_CASE)
#define  DUK_HTHREAD_STRING_CASE(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_CASE)
#define  DUK_HEAP_STRING_CATCH(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_CATCH)
#define  DUK_HTHREAD_STRING_CATCH(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_CATCH)
#define  DUK_HEAP_STRING_CONTINUE(heap)                                DUK_HEAP_GET_STRING((heap),DUK_STRIDX_CONTINUE)
#define  DUK_HTHREAD_STRING_CONTINUE(thr)                              DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_CONTINUE)
#define  DUK_HEAP_STRING_DEBUGGER(heap)                                DUK_HEAP_GET_STRING((heap),DUK_STRIDX_DEBUGGER)
#define  DUK_HTHREAD_STRING_DEBUGGER(thr)                              DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_DEBUGGER)
#define  DUK_HEAP_STRING_DEFAULT(heap)                                 DUK_HEAP_GET_STRING((heap),DUK_STRIDX_DEFAULT)
#define  DUK_HTHREAD_STRING_DEFAULT(thr)                               DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_DEFAULT)
#define  DUK_HEAP_STRING_DELETE(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_DELETE)
#define  DUK_HTHREAD_STRING_DELETE(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_DELETE)
#define  DUK_HEAP_STRING_DO(heap)                                      DUK_HEAP_GET_STRING((heap),DUK_STRIDX_DO)
#define  DUK_HTHREAD_STRING_DO(thr)                                    DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_DO)
#define  DUK_HEAP_STRING_ELSE(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_ELSE)
#define  DUK_HTHREAD_STRING_ELSE(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_ELSE)
#define  DUK_HEAP_STRING_FINALLY(heap)                                 DUK_HEAP_GET_STRING((heap),DUK_STRIDX_FINALLY)
#define  DUK_HTHREAD_STRING_FINALLY(thr)                               DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_FINALLY)
#define  DUK_HEAP_STRING_FOR(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_FOR)
#define  DUK_HTHREAD_STRING_FOR(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_FOR)
#define  DUK_HEAP_STRING_LC_FUNCTION(heap)                             DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LC_FUNCTION)
#define  DUK_HTHREAD_STRING_LC_FUNCTION(thr)                           DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LC_FUNCTION)
#define  DUK_HEAP_STRING_IF(heap)                                      DUK_HEAP_GET_STRING((heap),DUK_STRIDX_IF)
#define  DUK_HTHREAD_STRING_IF(thr)                                    DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_IF)
#define  DUK_HEAP_STRING_IN(heap)                                      DUK_HEAP_GET_STRING((heap),DUK_STRIDX_IN)
#define  DUK_HTHREAD_STRING_IN(thr)                                    DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_IN)
#define  DUK_HEAP_STRING_INSTANCEOF(heap)                              DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INSTANCEOF)
#define  DUK_HTHREAD_STRING_INSTANCEOF(thr)                            DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INSTANCEOF)
#define  DUK_HEAP_STRING_NEW(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_NEW)
#define  DUK_HTHREAD_STRING_NEW(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_NEW)
#define  DUK_HEAP_STRING_RETURN(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_RETURN)
#define  DUK_HTHREAD_STRING_RETURN(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_RETURN)
#define  DUK_HEAP_STRING_SWITCH(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SWITCH)
#define  DUK_HTHREAD_STRING_SWITCH(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SWITCH)
#define  DUK_HEAP_STRING_THIS(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_THIS)
#define  DUK_HTHREAD_STRING_THIS(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_THIS)
#define  DUK_HEAP_STRING_THROW(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_THROW)
#define  DUK_HTHREAD_STRING_THROW(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_THROW)
#define  DUK_HEAP_STRING_TRY(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TRY)
#define  DUK_HTHREAD_STRING_TRY(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TRY)
#define  DUK_HEAP_STRING_TYPEOF(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TYPEOF)
#define  DUK_HTHREAD_STRING_TYPEOF(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TYPEOF)
#define  DUK_HEAP_STRING_VAR(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_VAR)
#define  DUK_HTHREAD_STRING_VAR(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_VAR)
#define  DUK_HEAP_STRING_VOID(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_VOID)
#define  DUK_HTHREAD_STRING_VOID(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_VOID)
#define  DUK_HEAP_STRING_WHILE(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_WHILE)
#define  DUK_HTHREAD_STRING_WHILE(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_WHILE)
#define  DUK_HEAP_STRING_WITH(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_WITH)
#define  DUK_HTHREAD_STRING_WITH(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_WITH)
#define  DUK_HEAP_STRING_CLASS(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_CLASS)
#define  DUK_HTHREAD_STRING_CLASS(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_CLASS)
#define  DUK_HEAP_STRING_CONST(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_CONST)
#define  DUK_HTHREAD_STRING_CONST(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_CONST)
#define  DUK_HEAP_STRING_ENUM(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_ENUM)
#define  DUK_HTHREAD_STRING_ENUM(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_ENUM)
#define  DUK_HEAP_STRING_EXPORT(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_EXPORT)
#define  DUK_HTHREAD_STRING_EXPORT(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_EXPORT)
#define  DUK_HEAP_STRING_EXTENDS(heap)                                 DUK_HEAP_GET_STRING((heap),DUK_STRIDX_EXTENDS)
#define  DUK_HTHREAD_STRING_EXTENDS(thr)                               DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_EXTENDS)
#define  DUK_HEAP_STRING_IMPORT(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_IMPORT)
#define  DUK_HTHREAD_STRING_IMPORT(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_IMPORT)
#define  DUK_HEAP_STRING_SUPER(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SUPER)
#define  DUK_HTHREAD_STRING_SUPER(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SUPER)
#define  DUK_HEAP_STRING_NULL(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_NULL)
#define  DUK_HTHREAD_STRING_NULL(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_NULL)
#define  DUK_HEAP_STRING_TRUE(heap)                                    DUK_HEAP_GET_STRING((heap),DUK_STRIDX_TRUE)
#define  DUK_HTHREAD_STRING_TRUE(thr)                                  DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_TRUE)
#define  DUK_HEAP_STRING_FALSE(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_FALSE)
#define  DUK_HTHREAD_STRING_FALSE(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_FALSE)
#define  DUK_HEAP_STRING_GET(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_GET)
#define  DUK_HTHREAD_STRING_GET(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_GET)
#define  DUK_HEAP_STRING_SET(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_SET)
#define  DUK_HTHREAD_STRING_SET(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_SET)
#define  DUK_HEAP_STRING_IMPLEMENTS(heap)                              DUK_HEAP_GET_STRING((heap),DUK_STRIDX_IMPLEMENTS)
#define  DUK_HTHREAD_STRING_IMPLEMENTS(thr)                            DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_IMPLEMENTS)
#define  DUK_HEAP_STRING_INTERFACE(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_INTERFACE)
#define  DUK_HTHREAD_STRING_INTERFACE(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_INTERFACE)
#define  DUK_HEAP_STRING_LET(heap)                                     DUK_HEAP_GET_STRING((heap),DUK_STRIDX_LET)
#define  DUK_HTHREAD_STRING_LET(thr)                                   DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_LET)
#define  DUK_HEAP_STRING_PACKAGE(heap)                                 DUK_HEAP_GET_STRING((heap),DUK_STRIDX_PACKAGE)
#define  DUK_HTHREAD_STRING_PACKAGE(thr)                               DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_PACKAGE)
#define  DUK_HEAP_STRING_PRIVATE(heap)                                 DUK_HEAP_GET_STRING((heap),DUK_STRIDX_PRIVATE)
#define  DUK_HTHREAD_STRING_PRIVATE(thr)                               DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_PRIVATE)
#define  DUK_HEAP_STRING_PROTECTED(heap)                               DUK_HEAP_GET_STRING((heap),DUK_STRIDX_PROTECTED)
#define  DUK_HTHREAD_STRING_PROTECTED(thr)                             DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_PROTECTED)
#define  DUK_HEAP_STRING_PUBLIC(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_PUBLIC)
#define  DUK_HTHREAD_STRING_PUBLIC(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_PUBLIC)
#define  DUK_HEAP_STRING_STATIC(heap)                                  DUK_HEAP_GET_STRING((heap),DUK_STRIDX_STATIC)
#define  DUK_HTHREAD_STRING_STATIC(thr)                                DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_STATIC)
#define  DUK_HEAP_STRING_YIELD(heap)                                   DUK_HEAP_GET_STRING((heap),DUK_STRIDX_YIELD)
#define  DUK_HTHREAD_STRING_YIELD(thr)                                 DUK_HTHREAD_GET_STRING((thr),DUK_STRIDX_YIELD)

#define  DUK_HEAP_NUM_STRINGS                                          304

#define  DUK_STRIDX_START_RESERVED                                     257
#define  DUK_STRIDX_START_STRICT_RESERVED                              295
#define  DUK_STRIDX_END_RESERVED                                       304                            /* exclusive endpoint */

#endif  /* __DUK_STRINGS_H */
#line 9 "duk_lexer.h"

typedef void (*duk_re_range_callback)(void *user, duk_u32 r1, duk_u32 r2, int direct);

/*
 *  A token is interpreted as any possible production of InputElementDiv
 *  and InputElementRegExp, see E5 Section 7 in its entirety.  Note that
 *  the E5 "Token" production does not cover all actual tokens of the
 *  language (which is explicitly stated in the specification, Section 7.5).
 *  Null and boolean literals are defined as part of both ReservedWord
 *  (E5 Section 7.6.1) and Literal (E5 Section 7.8) productions.  Here,
 *  null and boolean values have literal tokens, and are not reserved
 *  words.
 *
 *  Decimal literal negative/positive sign is -not- part of DUK_TOK_NUMBER.
 *  The number tokens always have a non-negative value.  The unary minus
 *  operator in "-1.0" is optimized during compilation to yield a single
 *  negative constant.
 *
 *  Token numbering is free except that reserved words are required to be
 *  in a continuous range and in a particular order.  See genstrings.py.
 */

#define  DUK_LEXER_INITCTX(ctx)        duk_lexer_initctx((ctx))

#define  DUK_LEXER_SETPOINT(ctx,pt)    duk_lexer_setpoint((ctx), (pt))

#define  DUK_LEXER_GETPOINT(ctx,pt)    do { (pt)->offset = (ctx)->offsets[0]; \
                                            (pt)->line = (ctx)->lines[0]; } while(0)

/* currently 6 characters of lookup are actually needed (duk_lexer.c) */
#define  DUK_LEXER_WINDOW_SIZE			8

#define  DUK_TOK_MINVAL				0

/* returned after EOF (infinite amount) */
#define  DUK_TOK_EOF				0

/* line terminator or multi-line comment with internal lineterm (E5 Sections 7.3, 7.4) */
#define  DUK_TOK_LINETERM			1

/* single-line comment or multi-line comment without internal lineterm (E5 Section 7.4) */
#define  DUK_TOK_COMMENT			2

/* identifier names (E5 Section 7.6) */
#define  DUK_TOK_IDENTIFIER			3

/* reserved words: keywords */
#define  DUK_TOK_START_RESERVED			4
#define  DUK_TOK_BREAK				4
#define  DUK_TOK_CASE				5
#define  DUK_TOK_CATCH				6
#define  DUK_TOK_CONTINUE			7
#define  DUK_TOK_DEBUGGER			8
#define  DUK_TOK_DEFAULT			9
#define  DUK_TOK_DELETE				10
#define  DUK_TOK_DO				11
#define  DUK_TOK_ELSE				12
#define  DUK_TOK_FINALLY			13
#define  DUK_TOK_FOR				14
#define  DUK_TOK_FUNCTION			15
#define  DUK_TOK_IF				16
#define  DUK_TOK_IN				17
#define  DUK_TOK_INSTANCEOF			18
#define  DUK_TOK_NEW				19
#define  DUK_TOK_RETURN				20
#define  DUK_TOK_SWITCH				21
#define  DUK_TOK_THIS				22
#define  DUK_TOK_THROW				23
#define  DUK_TOK_TRY				24
#define  DUK_TOK_TYPEOF				25
#define  DUK_TOK_VAR				26
#define  DUK_TOK_VOID				27
#define  DUK_TOK_WHILE				28
#define  DUK_TOK_WITH				29

/* reserved words: future reserved words */
#define  DUK_TOK_CLASS				30
#define  DUK_TOK_CONST				31
#define  DUK_TOK_ENUM				32
#define  DUK_TOK_EXPORT				33
#define  DUK_TOK_EXTENDS			34
#define  DUK_TOK_IMPORT				35
#define  DUK_TOK_SUPER				36

/* "get" and "set" are not reserved words in the spec, but behave like
 * reserved words, so we treat them as such.
*/
#define  DUK_TOK_NULL				37
#define  DUK_TOK_TRUE				38
#define  DUK_TOK_FALSE				39
#define  DUK_TOK_GET				40
#define  DUK_TOK_SET				41

/* reserved words: additional future reserved words in strict mode */
#define  DUK_TOK_START_STRICT_RESERVED		42  /* inclusive */
#define  DUK_TOK_IMPLEMENTS			42
#define  DUK_TOK_INTERFACE			43
#define  DUK_TOK_LET				44
#define  DUK_TOK_PACKAGE			45
#define  DUK_TOK_PRIVATE			46
#define  DUK_TOK_PROTECTED			47
#define  DUK_TOK_PUBLIC				48
#define  DUK_TOK_STATIC				49
#define  DUK_TOK_YIELD				50

#define	 DUK_TOK_END_RESERVED			51  /* exclusive */

/* punctuators (unlike the spec, also includes "/" and "/=") */
#define  DUK_TOK_LCURLY				51
#define  DUK_TOK_RCURLY				52
#define  DUK_TOK_LBRACKET			53
#define  DUK_TOK_RBRACKET			54
#define  DUK_TOK_LPAREN				55
#define  DUK_TOK_RPAREN				56
#define  DUK_TOK_PERIOD				57
#define  DUK_TOK_SEMICOLON			58
#define  DUK_TOK_COMMA				59
#define  DUK_TOK_LT				60
#define  DUK_TOK_GT				61
#define  DUK_TOK_LE				62
#define  DUK_TOK_GE				63
#define  DUK_TOK_EQ				64
#define  DUK_TOK_NEQ				65
#define  DUK_TOK_SEQ				66
#define  DUK_TOK_SNEQ				67
#define  DUK_TOK_ADD				68
#define  DUK_TOK_SUB				69
#define  DUK_TOK_MUL				70
#define  DUK_TOK_DIV				71
#define  DUK_TOK_MOD				72
#define  DUK_TOK_INCREMENT			73
#define  DUK_TOK_DECREMENT			74
#define  DUK_TOK_ALSHIFT			75  /* named "arithmetic" because result is signed */
#define  DUK_TOK_ARSHIFT			76
#define  DUK_TOK_RSHIFT				77
#define  DUK_TOK_BAND				78
#define  DUK_TOK_BOR				79
#define  DUK_TOK_BXOR				80
#define  DUK_TOK_LNOT				81
#define  DUK_TOK_BNOT				82
#define  DUK_TOK_LAND				83
#define  DUK_TOK_LOR				84
#define  DUK_TOK_QUESTION			85
#define  DUK_TOK_COLON				86
#define  DUK_TOK_EQUALSIGN			87
#define  DUK_TOK_ADD_EQ				88
#define  DUK_TOK_SUB_EQ				89
#define  DUK_TOK_MUL_EQ				90
#define  DUK_TOK_DIV_EQ				91
#define  DUK_TOK_MOD_EQ				92
#define  DUK_TOK_ALSHIFT_EQ			93
#define  DUK_TOK_ARSHIFT_EQ			94
#define  DUK_TOK_RSHIFT_EQ			95
#define  DUK_TOK_BAND_EQ			96
#define  DUK_TOK_BOR_EQ				97
#define  DUK_TOK_BXOR_EQ			98

/* literals (E5 Section 7.8), except null, true, false, which are treated
 * like reserved words (above).
 */
#define  DUK_TOK_NUMBER				99
#define  DUK_TOK_STRING				100
#define  DUK_TOK_REGEXP				101

#define  DUK_TOK_MAXVAL				101  /* inclusive */

/* Convert heap string index to a token (reserved words) */
#define  DUK_STRIDX_TO_TOK(x)			((x) - DUK_STRIDX_START_RESERVED + DUK_TOK_START_RESERVED)

/* Sanity check */
#if (DUK_TOK_MAXVAL > 255)
#error DUK_TOK_MAXVAL too large, code assumes it fits into 8 bits
#endif

/* Sanity checks for string and token defines */
#if (DUK_STRIDX_TO_TOK(DUK_STRIDX_BREAK) != DUK_TOK_BREAK)
#error mismatch in token defines
#endif
#if (DUK_STRIDX_TO_TOK(DUK_STRIDX_CASE) != DUK_TOK_CASE)
#error mismatch in token defines
#endif
#if (DUK_STRIDX_TO_TOK(DUK_STRIDX_CATCH) != DUK_TOK_CATCH)
#error mismatch in token defines
#endif
#if (DUK_STRIDX_TO_TOK(DUK_STRIDX_CONTINUE) != DUK_TOK_CONTINUE)
#error mismatch in token defines
#endif
#if (DUK_STRIDX_TO_TOK(DUK_STRIDX_DEBUGGER) != DUK_TOK_DEBUGGER)
#error mismatch in token defines
#endif
#if (DUK_STRIDX_TO_TOK(DUK_STRIDX_DEFAULT) != DUK_TOK_DEFAULT)
#error mismatch in token defines
#endif
#if (DUK_STRIDX_TO_TOK(DUK_STRIDX_DELETE) != DUK_TOK_DELETE)
#error mismatch in token defines
#endif
#if (DUK_STRIDX_TO_TOK(DUK_STRIDX_DO) != DUK_TOK_DO)
#error mismatch in token defines
#endif
#if (DUK_STRIDX_TO_TOK(DUK_STRIDX_ELSE) != DUK_TOK_ELSE)
#error mismatch in token defines
#endif
#if (DUK_STRIDX_TO_TOK(DUK_STRIDX_FINALLY) != DUK_TOK_FINALLY)
#error mismatch in token defines
#endif
#if (DUK_STRIDX_TO_TOK(DUK_STRIDX_FOR) != DUK_TOK_FOR)
#error mismatch in token defines
#endif
#if (DUK_STRIDX_TO_TOK(DUK_STRIDX_LC_FUNCTION) != DUK_TOK_FUNCTION)
#error mismatch in token defines
#endif
#if (DUK_STRIDX_TO_TOK(DUK_STRIDX_IF) != DUK_TOK_IF)
#error mismatch in token defines
#endif
#if (DUK_STRIDX_TO_TOK(DUK_STRIDX_IN) != DUK_TOK_IN)
#error mismatch in token defines
#endif
#if (DUK_STRIDX_TO_TOK(DUK_STRIDX_INSTANCEOF) != DUK_TOK_INSTANCEOF)
#error mismatch in token defines
#endif
#if (DUK_STRIDX_TO_TOK(DUK_STRIDX_NEW) != DUK_TOK_NEW)
#error mismatch in token defines
#endif
#if (DUK_STRIDX_TO_TOK(DUK_STRIDX_RETURN) != DUK_TOK_RETURN)
#error mismatch in token defines
#endif
#if (DUK_STRIDX_TO_TOK(DUK_STRIDX_SWITCH) != DUK_TOK_SWITCH)
#error mismatch in token defines
#endif
#if (DUK_STRIDX_TO_TOK(DUK_STRIDX_THIS) != DUK_TOK_THIS)
#error mismatch in token defines
#endif
#if (DUK_STRIDX_TO_TOK(DUK_STRIDX_THROW) != DUK_TOK_THROW)
#error mismatch in token defines
#endif
#if (DUK_STRIDX_TO_TOK(DUK_STRIDX_TRY) != DUK_TOK_TRY)
#error mismatch in token defines
#endif
#if (DUK_STRIDX_TO_TOK(DUK_STRIDX_TYPEOF) != DUK_TOK_TYPEOF)
#error mismatch in token defines
#endif
#if (DUK_STRIDX_TO_TOK(DUK_STRIDX_VAR) != DUK_TOK_VAR)
#error mismatch in token defines
#endif
#if (DUK_STRIDX_TO_TOK(DUK_STRIDX_VOID) != DUK_TOK_VOID)
#error mismatch in token defines
#endif
#if (DUK_STRIDX_TO_TOK(DUK_STRIDX_WHILE) != DUK_TOK_WHILE)
#error mismatch in token defines
#endif
#if (DUK_STRIDX_TO_TOK(DUK_STRIDX_WITH) != DUK_TOK_WITH)
#error mismatch in token defines
#endif
#if (DUK_STRIDX_TO_TOK(DUK_STRIDX_CLASS) != DUK_TOK_CLASS)
#error mismatch in token defines
#endif
#if (DUK_STRIDX_TO_TOK(DUK_STRIDX_CONST) != DUK_TOK_CONST)
#error mismatch in token defines
#endif
#if (DUK_STRIDX_TO_TOK(DUK_STRIDX_ENUM) != DUK_TOK_ENUM)
#error mismatch in token defines
#endif
#if (DUK_STRIDX_TO_TOK(DUK_STRIDX_EXPORT) != DUK_TOK_EXPORT)
#error mismatch in token defines
#endif
#if (DUK_STRIDX_TO_TOK(DUK_STRIDX_EXTENDS) != DUK_TOK_EXTENDS)
#error mismatch in token defines
#endif
#if (DUK_STRIDX_TO_TOK(DUK_STRIDX_IMPORT) != DUK_TOK_IMPORT)
#error mismatch in token defines
#endif
#if (DUK_STRIDX_TO_TOK(DUK_STRIDX_SUPER) != DUK_TOK_SUPER)
#error mismatch in token defines
#endif
#if (DUK_STRIDX_TO_TOK(DUK_STRIDX_NULL) != DUK_TOK_NULL)
#error mismatch in token defines
#endif
#if (DUK_STRIDX_TO_TOK(DUK_STRIDX_TRUE) != DUK_TOK_TRUE)
#error mismatch in token defines
#endif
#if (DUK_STRIDX_TO_TOK(DUK_STRIDX_FALSE) != DUK_TOK_FALSE)
#error mismatch in token defines
#endif
#if (DUK_STRIDX_TO_TOK(DUK_STRIDX_GET) != DUK_TOK_GET)
#error mismatch in token defines
#endif
#if (DUK_STRIDX_TO_TOK(DUK_STRIDX_SET) != DUK_TOK_SET)
#error mismatch in token defines
#endif
#if (DUK_STRIDX_TO_TOK(DUK_STRIDX_IMPLEMENTS) != DUK_TOK_IMPLEMENTS)
#error mismatch in token defines
#endif
#if (DUK_STRIDX_TO_TOK(DUK_STRIDX_INTERFACE) != DUK_TOK_INTERFACE)
#error mismatch in token defines
#endif
#if (DUK_STRIDX_TO_TOK(DUK_STRIDX_LET) != DUK_TOK_LET)
#error mismatch in token defines
#endif
#if (DUK_STRIDX_TO_TOK(DUK_STRIDX_PACKAGE) != DUK_TOK_PACKAGE)
#error mismatch in token defines
#endif
#if (DUK_STRIDX_TO_TOK(DUK_STRIDX_PRIVATE) != DUK_TOK_PRIVATE)
#error mismatch in token defines
#endif
#if (DUK_STRIDX_TO_TOK(DUK_STRIDX_PROTECTED) != DUK_TOK_PROTECTED)
#error mismatch in token defines
#endif
#if (DUK_STRIDX_TO_TOK(DUK_STRIDX_PUBLIC) != DUK_TOK_PUBLIC)
#error mismatch in token defines
#endif
#if (DUK_STRIDX_TO_TOK(DUK_STRIDX_STATIC) != DUK_TOK_STATIC)
#error mismatch in token defines
#endif
#if (DUK_STRIDX_TO_TOK(DUK_STRIDX_YIELD) != DUK_TOK_YIELD)
#error mismatch in token defines
#endif

/* Regexp tokens */
#define  DUK_RETOK_EOF                              0
#define  DUK_RETOK_DISJUNCTION                      1
#define  DUK_RETOK_QUANTIFIER                       2
#define  DUK_RETOK_ASSERT_START                     3
#define  DUK_RETOK_ASSERT_END                       4
#define  DUK_RETOK_ASSERT_WORD_BOUNDARY             5
#define  DUK_RETOK_ASSERT_NOT_WORD_BOUNDARY         6
#define  DUK_RETOK_ASSERT_START_POS_LOOKAHEAD       7
#define  DUK_RETOK_ASSERT_START_NEG_LOOKAHEAD       8
#define  DUK_RETOK_ATOM_PERIOD                      9
#define  DUK_RETOK_ATOM_CHAR                        10
#define  DUK_RETOK_ATOM_DIGIT                       11
#define  DUK_RETOK_ATOM_NOT_DIGIT                   12
#define  DUK_RETOK_ATOM_WHITE                       13
#define  DUK_RETOK_ATOM_NOT_WHITE                   14
#define  DUK_RETOK_ATOM_WORD_CHAR                   15
#define  DUK_RETOK_ATOM_NOT_WORD_CHAR               16
#define  DUK_RETOK_ATOM_BACKREFERENCE               17
#define  DUK_RETOK_ATOM_START_CAPTURE_GROUP         18
#define  DUK_RETOK_ATOM_START_NONCAPTURE_GROUP      19
#define  DUK_RETOK_ATOM_START_CHARCLASS             20
#define  DUK_RETOK_ATOM_START_CHARCLASS_INVERTED    21
#define  DUK_RETOK_ATOM_END_GROUP                   22

/* constants for duk_lexer_ctx.buf */
#define  DUK_LEXER_TEMP_BUF_INITIAL                 64
#define  DUK_LEXER_TEMP_BUF_LIMIT                   256

/* A token value.  Can be memcpy()'d, but note that slot1/slot2 values are on the valstack. */
struct duk_token {
	int t;			/* token type (with reserved word identification) */
	int t_nores;		/* token type (with reserved words as DUK_TOK_IDENTIFER) */
	double num;		/* numeric value of token */
	duk_hstring *str1;      /* string 1 of token (borrowed, stored to ctx->slot1_idx) */
	duk_hstring *str2;      /* string 2 of token (borrowed, stored to ctx->slot1_idx) */
	int num_escapes;	/* number of escapes and line continuations (for directive prologue) */
	int start_line;		/* start line of token (first char) */
	int end_line;		/* end line of token (char after last token char) */
	int lineterm;		/* token was preceded by a lineterm */
	int allow_auto_semi;    /* token allows automatic semicolon insertion (eof or preceded by newline) */
};

#define  DUK_RE_QUANTIFIER_INFINITE         ((duk_u32) 0xffffffffU)

/* A regexp token value. */
struct duk_re_token {
	int t;			/* token type */
	duk_u32 num;		/* numeric value (character, count) */
	duk_u32 qmin;
	duk_u32 qmax;
	int greedy;
};

/* A structure for 'snapshotting' a point for rewinding */
struct duk_lexer_point {
	int offset;
	int line;
};

/* Lexer context.  Same context is used for Ecmascript and Regexp parsing. */
struct duk_lexer_ctx {
	duk_hthread *thr;                       /* thread; minimizes argument passing */

	duk_u8 *input;
	int input_length;
	int window[DUK_LEXER_WINDOW_SIZE];	/* window of unicode code points */
	int offsets[DUK_LEXER_WINDOW_SIZE];	/* input offset for each char */
	int lines[DUK_LEXER_WINDOW_SIZE];	/* input lines for each char */
	int input_offset;			/* input offset for window leading edge (not window[0]) */
	int input_line;				/* input linenumber at input_offset (not window[0]), init to 1 */

	int slot1_idx;                          /* valstack slot for 1st token value */
	int slot2_idx;                          /* valstack slot for 2nd token value */
	int buf_idx;                            /* valstack slot for temp buffer */
	duk_hbuffer_dynamic *buf;               /* temp accumulation buffer (on valstack) */
};

/*
 *  Prototypes
 */

void duk_lexer_initctx(duk_lexer_ctx *lex_ctx);

void duk_lexer_setpoint(duk_lexer_ctx *lex_ctx, duk_lexer_point *pt);

void duk_lexer_parse_js_input_element(duk_lexer_ctx *lex_ctx,
                                      duk_token *out_token,
                                      int strict_mode,
                                      int regexp_mode);
#ifdef DUK_USE_REGEXP_SUPPORT
void duk_lexer_parse_re_token(duk_lexer_ctx *lex_ctx, duk_re_token *out_token);
void duk_lexer_parse_re_ranges(duk_lexer_ctx *lex_ctx, duk_re_range_callback gen_range, void *userdata);
#endif  /* DUK_USE_REGEXP_SUPPORT */

#endif  /* DUK_LEXER_H_INCLUDED */

#line 1 "duk_js_compiler.h"
/*
 *  Ecmascript compiler.
 */

#ifndef DUK_JS_COMPILER_H_INCLUDED
#define DUK_JS_COMPILER_H_INCLUDED

/* regexp compilation limits */
#define  DUK_COMPILER_RECURSION_LIMIT       50

/* maximum loopcount for peephole optimization */
#define  DUK_COMPILER_PEEPHOLE_MAXITER      3

/*
 *  Compiler intermediate values
 *
 *  Intermediate values describe either plain values (e.g. strings or
 *  numbers) or binary operations which have not yet been coerced into
 *  either a left-hand-side or right-hand-side role (e.g. object property).
 */

#define  DUK_IVAL_NONE          0   /* no value */
#define  DUK_IVAL_PLAIN         1   /* register, constant, or value */
#define  DUK_IVAL_ARITH         2   /* binary arithmetic; DUK_OP_ADD, DUK_OP_EQ, other binary ops */
#define  DUK_IVAL_PROP          3   /* property access */
#define  DUK_IVAL_VAR           4   /* variable access */

#define  DUK_ISPEC_NONE         0   /* no value */
#define  DUK_ISPEC_VALUE        1   /* value resides in 'valstack_idx' */
#define  DUK_ISPEC_REGCONST     2   /* value resides in a register or constant */

/* bit mask which indicates that a regconst is a constant instead of a register */
#define  DUK_JS_CONST_MARKER    0x80000000

typedef struct {
	int t;                      /* DUK_ISPEC_XXX */
	int regconst;
	int valstack_idx;           /* always set; points to a reserved valstack slot */
} duk_ispec;

typedef struct {
	/*
	 *  PLAIN: x1
	 *  ARITH: x1 <op> x2
	 *  PROP: x1.x2
	 *  VAR: x1 (name)
	 */

	int t;                      /* DUK_IVAL_XXX */
	int op;                     /* bytecode opcode for binary ops */
	duk_ispec x1;
	duk_ispec x2;
} duk_ivalue;

/*
 *  Bytecode instruction representation during compilation
 *
 *  Contains the actual instruction and (optionally) debug info.
 */

struct duk_compiler_instr {
	duk_instr ins;
#if 1  /* FIXME: line number tracking now always enabled, make optional */
	duk_u32 line;
#endif
};

/*
 *  Compiler state
 */

#define  MAX_MAPPED_REGS                  128  /* max regs mapped to arguments and variables */
#define  MAX_ACTIVE_LABELS                64

#define  DUK_LABEL_FLAG_ALLOW_BREAK       (1 << 0)
#define  DUK_LABEL_FLAG_ALLOW_CONTINUE    (1 << 1)

#define  DUK_DECL_TYPE_VAR                1
#define  DUK_DECL_TYPE_FUNC               2

/* FIXME: optimize to 16 bytes */
typedef struct {
	int flags;
	int label_id;           /* numeric label_id */
	duk_hstring *h_label;   /* borrowed label name */
	int catch_depth;        /* catch depth at point of definition */
	int pc_label;           /* pc of label statement:
	                         * pc+1: break jump site
	                         * pc+2: continue jump site
	                         */

	/* Fast jumps (which avoid longjmp) jump directly to the jump sites
	 * which are always known even while the iteration/switch statement
	 * is still being parsed.  A final peephole pass "straightens out"
	 * the jumps.
	 */
} duk_labelinfo;

/* Compiling state of one function, eventually converted to duk_hcompiledfunction */
struct duk_compiler_func {
	int is_function;                    /* is an actual function (not global/eval code) */
	int is_eval;                        /* is eval code */
	int is_global;                      /* is global code */
	int is_setget;                      /* is a setter/getter */
	int is_decl;                        /* is a function declaration (as opposed to function expression) */
	int is_strict;                      /* function is strict */
	int in_directive_prologue;          /* parsing in "directive prologue", recognize directives */
	int in_scanning;                    /* parsing in "scanning" phase (first pass) */
	int may_direct_eval;                /* function may call direct eval */
	int id_access_arguments;            /* function refers to 'arguments' identifier */
	int id_access_slow;                 /* function makes one or more slow path accesses */
	int is_arguments_shadowed;          /* argument/function declaration shadows 'arguments' */
	int num_formals;                    /* number of formal arguments */
	int reg_stmt_value;                 /* register for writing value of 'non-empty' statements (global or eval code) */

	int reject_regexp_in_adv;           /* reject RegExp literal on next advance() call; needed for handling IdentifierName productions */

	duk_hstring *h_name;                /* function name (borrowed reference), ends up in _name */

	int code_idx;
	duk_hbuffer_dynamic *h_code;        /* C array of duk_compiler_instr */

	int consts_idx;
	duk_hobject *h_consts;              /* array */

	int funcs_idx;
	duk_hobject *h_funcs;               /* array of function templates */

	/* record function and variable declarations in pass 1 */
	int decls_idx;
	duk_hobject *h_decls;               /* array of declarations: [ name1, val1, name2, val2, ... ]
	                                     * valN = (typeN) | (fnum << 8), where fnum is inner func number (0 for vars)
	                                     */

	/* active labels */
	int labelnames_idx;
	duk_hobject *h_labelnames;          /* array of label names */

	int labelinfos_idx;
	duk_hbuffer_dynamic *h_labelinfos;  /* C array of duk_labelinfo */

	int argnames_idx;                   /* array of formal argument names (-> _formals) */
	duk_hobject *h_argnames;

	/* variable map for pass 2 (identifier -> register number or null (unmapped)) */
	int varmap_idx;
	duk_hobject *h_varmap;

	/* temp reg handling */
	int temp_first;                     /* first register that is a temporary (below: variables) */
	int temp_next;                      /* next temporary register to allocate */
	int temp_max;                       /* highest value of temp_reg (temp_max - 1 is highest used reg) */

	/* statement id allocation (running counter) */
	int stmt_next;

	/* label handling */
	int label_next;

	/* catch stack book-keeping */
	int catch_depth;                    /* catch stack depth */

	/* with stack book-keeping (affects identifier lookups) */
	int with_depth;

	/* stats for current expression being parsed */
	int nud_count;
	int led_count;
	int paren_level;                    /* parenthesis count, 0 = top level */
	int expr_lhs;                       /* expression is left-hand-side compatible */
	int allow_in;                       /* current paren level allows 'in' token */
};

struct duk_compiler_ctx {
	duk_hthread *thr;

	/* filename being compiled (ends up in functions' '_filename' property) */
	duk_hstring *h_filename;            /* borrowed reference */

	/* lexing (tokenization) state (contains two valstack slot indices) */
	duk_lexer_ctx lex;

	/* current and previous token for parsing */
	duk_token prev_token;
	duk_token curr_token;
	int tok11_idx;                      /* curr_token slot1 (matches 'lex' slot1_idx) */
	int tok12_idx;                      /* curr_token slot2 (matches 'lex' slot2_idx) */
	int tok21_idx;                      /* prev_token slot1 */
	int tok22_idx;                      /* prev_token slot2 */

	/* recursion limit */
	int recursion_depth;
	int recursion_limit;

	/* current function being compiled (embedded instead of pointer for more compact access) */
	duk_compiler_func curr_func;
};

/*
 *  Prototypes
 */

#define  DUK_JS_COMPILE_FLAG_EVAL      (1 << 0)  /* source is eval code (not program) */
#define  DUK_JS_COMPILE_FLAG_STRICT    (1 << 1)  /* strict outer context */
#define  DUK_JS_COMPILE_FLAG_FUNCEXPR  (1 << 2)  /* source is a function expression (used for Function constructor) */

void duk_js_compile(duk_hthread *thr, int flags);

#endif  /* DUK_JS_COMPILER_H_INCLUDED */

#line 1 "duk_regexp.h"
/*
 *  Regular expression structs, constants, and bytecode defines.
 */

#ifndef DUK_REGEXP_H_INCLUDED
#define DUK_REGEXP_H_INCLUDED

/* already included: duk_bittypes.h */

/* maximum bytecode copies for {n,m} quantifiers */
#define  DUK_RE_MAX_ATOM_COPIES             1000

/* regexp compilation limits */
#define  DUK_RE_COMPILE_RECURSION_LIMIT     100

/* regexp execution limits */
#define  DUK_RE_EXECUTE_RECURSION_LIMIT     100
#define  DUK_RE_EXECUTE_STEPS_LIMIT         (1 * 1000 * 1000 * 1000)

/* regexp opcodes */
#define  DUK_REOP_MATCH                     1
#define  DUK_REOP_CHAR                      2
#define  DUK_REOP_PERIOD                    3
#define  DUK_REOP_RANGES                    4
#define  DUK_REOP_INVRANGES                 5
#define  DUK_REOP_JUMP                      6
#define  DUK_REOP_SPLIT1                    7
#define  DUK_REOP_SPLIT2                    8
#define  DUK_REOP_SQMINIMAL                 9
#define  DUK_REOP_SQGREEDY                  10
#define  DUK_REOP_SAVE                      11
#define  DUK_REOP_LOOKPOS                   12
#define  DUK_REOP_LOOKNEG                   13
#define  DUK_REOP_BACKREFERENCE             14
#define  DUK_REOP_ASSERT_START              15
#define  DUK_REOP_ASSERT_END                16
#define  DUK_REOP_ASSERT_WORD_BOUNDARY      17
#define  DUK_REOP_ASSERT_NOT_WORD_BOUNDARY  18

/* flags */
#define  DUK_RE_FLAG_GLOBAL                 (1 << 0)
#define  DUK_RE_FLAG_IGNORE_CASE            (1 << 1)
#define  DUK_RE_FLAG_MULTILINE              (1 << 2)

struct duk_re_matcher_ctx {
	duk_hthread *thr;

	duk_u32 re_flags;
	duk_u8 *input;
	duk_u8 *input_end;
	duk_u8 *bytecode;
	duk_u8 *bytecode_end;
	duk_u8 **saved;		/* allocated from valstack (fixed buffer) */
	duk_u32 nsaved;
	duk_u32 recursion_depth;
	duk_u32 steps_count;
	duk_u32 recursion_limit;
	duk_u32 steps_limit;
};

struct duk_re_compiler_ctx {
	duk_hthread *thr;

	duk_u32 re_flags;
	duk_lexer_ctx lex;
	duk_re_token curr_token;
	duk_hbuffer_dynamic *buf;
	duk_u32 captures;
	duk_u32 highest_backref;
	duk_u32 recursion_depth;
	duk_u32 recursion_limit;
	duk_u32 nranges;	/* internal temporary value, used for char classes */
};

/*
 *  Prototypes
 */

void duk_regexp_compile(duk_hthread *thr);
void duk_regexp_create_instance(duk_hthread *thr);
void duk_regexp_match(duk_hthread *thr);
void duk_regexp_match_force_global(duk_hthread *thr);  /* hacky helper for String.prototype.split() */

#endif  /* DUK_REGEXP_H_INCLUDED */

#line 1 "duk_tval.h"
/*
 *  Tagged type definition (duk_tval) and accessor macros.
 *
 *  Access all fields through the accessor macros, as the representation
 *  is quite tricky.
 *
 *  There are two packed type alternatives: an 8-byte representation
 *  based on an IEEE double (preferred for compactness), and a 12-byte
 *  representation (portability).  The latter is needed also in e.g.
 *  64-bit environments (it may pad to 16 bytes per value).
 *
 *  Selecting the tagged type format involves many trade-offs (memory
 *  use, size and performance of generated code, portability, etc),
 *  see doc/types.txt for a detailed discussion (especially of how the
 *  IEEE double format is used to pack tagged values).
 *
 *  NB: because macro arguments are often expressions, macros should
 *  avoid evaluating their argument more than once.
 */

/*
 *  IEEE double format summary:
 *
 *    seeeeeee eeeeffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff
 *       A        B        C        D        E        F        G        H
 *
 *    s       sign bit
 *    eee...  exponent field
 *    fff...  fraction
 *
 *  See http://en.wikipedia.org/wiki/Double_precision_floating-point_format.
 *
 *  At least three memory layouts are relevant here:
 *
 *    A B C D E F G H    Big endian (e.g. 68k)           _USE_BE_VARIANT
 *    H G F E D C B A    Little endian (e.g. x86)        _USE_LE_VARIANT
 *    D C B A H G F E    Middle/cross endian (e.g. ARM)  _USE_ME_VARIANT
 *
 *  ARM is a special case: ARM double values are in middle/cross endian
 *  format while ARM unsigned long long (64-bit) values are in standard
 *  little endian format (H G F E D C B A).  When a double is read as an
 *  unsigned long long from memory, the register will contain the (logical)
 *  value E F G H A B C D.  This requires some special handling below.
 *
 *  Indexes of various types (8-bit, 16-bit, 32-bit) in memory relative to
 *  the logical (big endian) order:
 *
 *  byte order     unsigned char     unsigned short     unsigned int
 *    BE             01234567         0123               01
 *    LE             76543210         3210               10
 *    ME (ARM)       32107654         1032               01
 */

#ifndef DUK_TVAL_H_INCLUDED
#define DUK_TVAL_H_INCLUDED

#include <math.h>  /* provides NAN with -std=c99 */

#ifndef  NAN
#error missing a define for NAN, try -std=c99 or fix this header
#endif

/* already included: duk_features.h */
/* already included: duk_forwdecl.h */

#ifdef DUK_USE_DOUBLE_LE
#define  _USE_LE_VARIANT
#endif
#ifdef DUK_USE_DOUBLE_ME
#define  _USE_ME_VARIANT
#endif
#ifdef DUK_USE_DOUBLE_BE
#define  _USE_BE_VARIANT
#endif

/* sanity */
#if !defined(_USE_LE_VARIANT) && !defined(_USE_ME_VARIANT) && !defined(_USE_BE_VARIANT)
#error unsupported: cannot determine byte order variant
#endif

/*
 *  Union to access IEEE double memory representation
 */

/* indexes of various types with respect to big endian (logical) layout */
#ifdef _USE_LE_VARIANT
#define  _DUK_IDX_ULL0   0
#define  _DUK_IDX_UI0    1
#define  _DUK_IDX_UI1    0
#define  _DUK_IDX_US0    3
#define  _DUK_IDX_US1    2
#define  _DUK_IDX_US2    1
#define  _DUK_IDX_US3    0
#define  _DUK_IDX_UC0    7
#define  _DUK_IDX_UC1    6
#define  _DUK_IDX_UC2    5
#define  _DUK_IDX_UC3    4
#define  _DUK_IDX_UC4    3
#define  _DUK_IDX_UC5    2
#define  _DUK_IDX_UC6    1
#define  _DUK_IDX_UC7    0
#define  _DUK_IDX_VP0    _DUK_IDX_UI0  /* packed tval */
#define  _DUK_IDX_VP1    _DUK_IDX_UI1  /* packed tval */
#endif
#ifdef _USE_BE_VARIANT
#define  _DUK_IDX_ULL0   0
#define  _DUK_IDX_UI0    0
#define  _DUK_IDX_UI1    1
#define  _DUK_IDX_US0    0
#define  _DUK_IDX_US1    1
#define  _DUK_IDX_US2    2
#define  _DUK_IDX_US3    3
#define  _DUK_IDX_UC0    0
#define  _DUK_IDX_UC1    1
#define  _DUK_IDX_UC2    2
#define  _DUK_IDX_UC3    3
#define  _DUK_IDX_UC4    4
#define  _DUK_IDX_UC5    5
#define  _DUK_IDX_UC6    6
#define  _DUK_IDX_UC7    7
#define  _DUK_IDX_VP0    _DUK_IDX_UI0  /* packed tval */
#define  _DUK_IDX_VP1    _DUK_IDX_UI1  /* packed tval */
#endif
#ifdef _USE_ME_VARIANT
#define  _DUK_IDX_ULL0   0  /* not directly applicable, byte order differs from a double */
#define  _DUK_IDX_UI0    0
#define  _DUK_IDX_UI1    1
#define  _DUK_IDX_US0    1
#define  _DUK_IDX_US1    0
#define  _DUK_IDX_US2    3
#define  _DUK_IDX_US3    2
#define  _DUK_IDX_UC0    3
#define  _DUK_IDX_UC1    2
#define  _DUK_IDX_UC2    1
#define  _DUK_IDX_UC3    0
#define  _DUK_IDX_UC4    7
#define  _DUK_IDX_UC5    6
#define  _DUK_IDX_UC6    5
#define  _DUK_IDX_UC7    4
#define  _DUK_IDX_VP0    _DUK_IDX_UI0  /* packed tval */
#define  _DUK_IDX_VP1    _DUK_IDX_UI1  /* packed tval */
#endif

/* Almost the same as a packed duk_tval, but only for accessing doubles e.g.
 * for numconv, which is needed regardless of duk_tval representation.
 */

union duk_double_union {
	double d;
	unsigned long long ull[1];
	unsigned int ui[2];
	unsigned short us[4];
	unsigned char uc[8];
};
typedef union duk_double_union duk_double_union;

/* macros for duk_numconv.c */
#define  DUK_DBLUNION_SET_DOUBLE(u,v)  do {  \
		(u)->d = (v); \
	} while (0)
#define  DUK_DBLUNION_SET_HIGH32(u,v)  do {  \
		(u)->ui[_DUK_IDX_UI0] = (unsigned int) (v); \
	} while (0)
#define  DUK_DBLUNION_SET_LOW32(u,v)  do {  \
		(u)->ui[_DUK_IDX_UI1] = (unsigned int) (v); \
	} while (0)
#define  DUK_DBLUNION_GET_DOUBLE(u)  ((u)->d)
#define  DUK_DBLUNION_GET_HIGH32(u)  ((u)->ui[_DUK_IDX_UI0])
#define  DUK_DBLUNION_GET_LOW32(u)   ((u)->ui[_DUK_IDX_UI1])

#ifdef DUK_USE_PACKED_TVAL
/* ======================================================================== */

/*
 *  Packed 8-byte representation
 */

/* sanity */
#if !defined(DUK_USE_PACKED_TVAL_POSSIBLE)
#error packed representation not supported
#endif

/* Use a union for bit manipulation to minimize aliasing issues in practice.
 * The C99 standard does not guarantee that this should work, but it's a very
 * widely supported practice for low level manipulation.
 */
union duk_tval {
	double d;
	unsigned long long ull[1];
	unsigned int ui[2];
	unsigned short us[4];
	unsigned char uc[8];
	void *vp[2];
};

typedef union duk_tval duk_tval;

/* tags */
#define  DUK_TAG_NORMALIZED_NAN    0x7ff8   /* the NaN variant we use */
/* avoid tag 0xfff0, no risk of confusion with negative infinity */
#define  DUK_TAG_UNDEFINED         0xfff1   /* embed: 0 or 1 (normal or unused) */
#define  DUK_TAG_NULL              0xfff2   /* embed: nothing */
#define  DUK_TAG_BOOLEAN           0xfff3   /* embed: 0 or 1 (false or true) */
/* DUK_TAG_NUMBER would logically go here, but it has multiple 'tags' */
#define  DUK_TAG_POINTER           0xfff4   /* embed: void ptr */
#define  DUK_TAG_STRING            0xfff5   /* embed: duk_hstring ptr */
#define  DUK_TAG_OBJECT            0xfff6   /* embed: duk_hobject ptr */
#define  DUK_TAG_BUFFER            0xfff7   /* embed: duk_hbuffer ptr */

/* for convenience */
#define  DUK_XTAG_UNDEFINED_ACTUAL 0xfff10000
#define  DUK_XTAG_UNDEFINED_UNUSED 0xfff10001
#define  DUK_XTAG_BOOLEAN_FALSE    0xfff30000
#define  DUK_XTAG_BOOLEAN_TRUE     0xfff30001

/*
 *  The ME variant below is specifically for ARM byte order, which has the
 *  feature that while doubles have a mixed byte order (32107654), unsigned
 *  long long values has a little endian byte order (76543210).  When writing
 *  a logical double value through a ULL pointer, the 32-bit words need to be
 *  swapped; hence the #ifdefs below for ULL writes with _USE_ME_VARIANT.
 *  This is not full ARM support but suffices for some environments.
 */

/* raw setters */
#ifdef _USE_ME_VARIANT
#define  _DUK_TVAL_SET_UNDEFINED_ACTUAL_FULL(v)  do { \
		(v)->ull[_DUK_IDX_ULL0] = (unsigned long long) DUK_XTAG_UNDEFINED_ACTUAL; \
	} while (0)
#else
#define  _DUK_TVAL_SET_UNDEFINED_ACTUAL_FULL(v)  do { \
		(v)->ull[_DUK_IDX_ULL0] = ((unsigned long long) DUK_XTAG_UNDEFINED_ACTUAL) << 32; \
	} while (0)
#endif

#define  _DUK_TVAL_SET_UNDEFINED_ACTUAL_NOTFULL(v)  do { \
		(v)->ui[_DUK_IDX_UI0] = (unsigned int) DUK_XTAG_UNDEFINED_ACTUAL; \
	} while (0)

#ifdef _USE_ME_VARIANT
#define  _DUK_TVAL_SET_UNDEFINED_UNUSED_FULL(v)  do { \
		(v)->ull[_DUK_IDX_ULL0] = (unsigned long long) DUK_XTAG_UNDEFINED_UNUSED; \
	} while (0)
#else
#define  _DUK_TVAL_SET_UNDEFINED_UNUSED_FULL(v)  do { \
		(v)->ull[_DUK_IDX_ULL0] = ((unsigned long long) DUK_XTAG_UNDEFINED_UNUSED) << 32; \
	} while (0)
#endif

#define  _DUK_TVAL_SET_UNDEFINED_UNUSED_NOTFULL(v)  do { \
		(v)->ui[_DUK_IDX_UI0] = (unsigned int) DUK_XTAG_UNDEFINED_UNUSED; \
	} while (0)

#ifdef _USE_ME_VARIANT
#define  _DUK_TVAL_SET_NULL_FULL(v)  do { \
		(v)->ull[_DUK_IDX_ULL0] = (((unsigned long long) DUK_TAG_NULL) << 16); \
	} while (0)
#else
#define  _DUK_TVAL_SET_NULL_FULL(v)  do { \
		(v)->ull[_DUK_IDX_ULL0] = (((unsigned long long) DUK_TAG_NULL) << 48); \
	} while (0)
#endif

/* Note: 16-bit initializer suffices (unlike for undefined/boolean) */
#define  _DUK_TVAL_SET_NULL_NOTFULL(v)  do { \
		(v)->us[_DUK_IDX_US0] = (unsigned short) DUK_TAG_NULL; \
	} while (0)

#ifdef _USE_ME_VARIANT
#define  _DUK_TVAL_SET_BOOLEAN_FULL(v,val)  do { \
		DUK_ASSERT((val) == 0 || (val) == 1); \
		(v)->ull[_DUK_IDX_ULL0] = (((unsigned long long) DUK_TAG_BOOLEAN) << 16) | ((unsigned long long) (val)); \
	} while (0)
#else
#define  _DUK_TVAL_SET_BOOLEAN_FULL(v,val)  do { \
		DUK_ASSERT((val) == 0 || (val) == 1); \
		(v)->ull[_DUK_IDX_ULL0] = (((unsigned long long) DUK_TAG_BOOLEAN) << 48) | (((unsigned long long) (val)) << 32); \
	} while (0)
#endif

#define  _DUK_TVAL_SET_BOOLEAN_NOTFULL(v,val)  do { \
		DUK_ASSERT((val) == 0 || (val) == 1); \
		(v)->ui[_DUK_IDX_UI0] = (((unsigned int) DUK_TAG_BOOLEAN) << 16) | ((unsigned int) (val)); \
	} while (0)

/* assumes that caller has normalized a possible NaN value of 'val', otherwise trouble ahead */
#define  _DUK_TVAL_SET_NUMBER_FULL(v,val)  do { \
		(v)->d = (double) (val); \
	} while (0)

/* no notfull variant */
#define  _DUK_TVAL_SET_NUMBER_NOTFULL(v,d)  _DUK_TVAL_SET_NUMBER_FULL(v,d)

/* two casts to avoid gcc warning: "warning: cast from pointer to integer of different size [-Wpointer-to-int-cast]" */
#ifdef _USE_ME_VARIANT
#define  _DUK_TVAL_SET_TAGGEDPOINTER(v,h,tag)  do { \
		(v)->ull[_DUK_IDX_ULL0] = (((unsigned long long) (tag)) << 16) | (((unsigned long long) (unsigned int) (h)) << 32); \
	} while (0)
#else
#define  _DUK_TVAL_SET_TAGGEDPOINTER(v,h,tag)  do { \
		(v)->ull[_DUK_IDX_ULL0] = (((unsigned long long) (tag)) << 48) | ((unsigned long long) (unsigned int) (h)); \
	} while (0)
#endif

#ifdef _USE_ME_VARIANT
#define  _DUK_TVAL_SET_NAN_FULL(v)  do { \
		(v)->ull[_DUK_IDX_ULL0] = 0x000000007ff80000ULL; \
	} while (0)
#else
#define  _DUK_TVAL_SET_NAN_FULL(v)  do { \
		(v)->ull[_DUK_IDX_ULL0] = 0x7ff8000000000000ULL; \
	} while (0)
#endif

#define  _DUK_TVAL_SET_NAN_NOTFULL(v)  do { \
		(v)->us[_DUK_IDX_US0] = 0x7ff8; \
	} while (0)

#define  _DUK_DOUBLE_SET_NAN_FULL(d)         _DUK_TVAL_SET_NAN_FULL((duk_tval *)(d))
#define  _DUK_DOUBLE_SET_NAN_NOTFULL(d)      _DUK_TVAL_SET_NAN_NOTFULL((duk_tval *)(d))

/* select actual setters */
#ifdef DUK_USE_FULL_TVAL
#define  DUK_TVAL_SET_UNDEFINED_ACTUAL(v)    _DUK_TVAL_SET_UNDEFINED_ACTUAL_FULL((v))
#define  DUK_TVAL_SET_UNDEFINED_UNUSED(v)    _DUK_TVAL_SET_UNDEFINED_UNUSED_FULL((v))
#define  DUK_TVAL_SET_NULL(v)                _DUK_TVAL_SET_NULL_FULL((v))
#define  DUK_TVAL_SET_BOOLEAN(v,i)           _DUK_TVAL_SET_BOOLEAN_FULL((v),(i))
#define  DUK_TVAL_SET_NUMBER(v,d)            _DUK_TVAL_SET_NUMBER_FULL((v),(d))
#define  DUK_TVAL_SET_NAN(v)                 _DUK_TVAL_SET_NAN_FULL((v))
#define  DUK_DOUBLE_SET_NAN(d)               _DUK_DOUBLE_SET_NAN_FULL((d))
#else
#define  DUK_TVAL_SET_UNDEFINED_ACTUAL(v)    _DUK_TVAL_SET_UNDEFINED_ACTUAL_NOTFULL((v))
#define  DUK_TVAL_SET_UNDEFINED_UNUSED(v)    _DUK_TVAL_SET_UNDEFINED_UNUSED_NOTFULL((v))
#define  DUK_TVAL_SET_NULL(v)                _DUK_TVAL_SET_NULL_NOTFULL((v))
#define  DUK_TVAL_SET_BOOLEAN(v,i)           _DUK_TVAL_SET_BOOLEAN_NOTFULL((v),(i))
#define  DUK_TVAL_SET_NUMBER(v,d)            _DUK_TVAL_SET_NUMBER_NOTFULL((v),(d))
#define  DUK_TVAL_SET_NAN(v)                 _DUK_TVAL_SET_NAN_NOTFULL((v))
#define  DUK_DOUBLE_SET_NAN(d)               _DUK_DOUBLE_SET_NAN_NOTFULL((d))
#endif

#define  DUK_TVAL_SET_STRING(v,h)            _DUK_TVAL_SET_TAGGEDPOINTER((v),(h),DUK_TAG_STRING)
#define  DUK_TVAL_SET_OBJECT(v,h)            _DUK_TVAL_SET_TAGGEDPOINTER((v),(h),DUK_TAG_OBJECT)
#define  DUK_TVAL_SET_BUFFER(v,h)            _DUK_TVAL_SET_TAGGEDPOINTER((v),(h),DUK_TAG_BUFFER)
#define  DUK_TVAL_SET_POINTER(v,p)           _DUK_TVAL_SET_TAGGEDPOINTER((v),(p),DUK_TAG_POINTER)

#define  DUK_TVAL_SET_TVAL(v,x)              do { *(v) = *(x); } while (0)

/* getters */
#define  DUK_TVAL_GET_BOOLEAN(v)             ((int) (v)->us[_DUK_IDX_US1])
#define  DUK_TVAL_GET_NUMBER(v)              ((v)->d)
#define  DUK_TVAL_GET_STRING(v)              ((duk_hstring *) (v)->vp[_DUK_IDX_VP1])
#define  DUK_TVAL_GET_OBJECT(v)              ((duk_hobject *) (v)->vp[_DUK_IDX_VP1])
#define  DUK_TVAL_GET_BUFFER(v)              ((duk_hbuffer *) (v)->vp[_DUK_IDX_VP1])
#define  DUK_TVAL_GET_POINTER(v)             ((void *) (v)->vp[_DUK_IDX_VP1])
#define  DUK_TVAL_GET_HEAPHDR(v)             ((duk_heaphdr *) (v)->vp[_DUK_IDX_VP1])

/* decoding */
#define  DUK_TVAL_GET_TAG(v)                 ((int) (v)->us[_DUK_IDX_US0])

#define  DUK_TVAL_IS_UNDEFINED(v)            (DUK_TVAL_GET_TAG((v)) == DUK_TAG_UNDEFINED)
#define  DUK_TVAL_IS_UNDEFINED_ACTUAL(v)     ((v)->ui[_DUK_IDX_UI0] == DUK_XTAG_UNDEFINED_ACTUAL)
#define  DUK_TVAL_IS_UNDEFINED_UNUSED(v)     ((v)->ui[_DUK_IDX_UI0] == DUK_XTAG_UNDEFINED_UNUSED)
#define  DUK_TVAL_IS_NULL(v)                 (DUK_TVAL_GET_TAG((v)) == DUK_TAG_NULL)
#define  DUK_TVAL_IS_BOOLEAN(v)              (DUK_TVAL_GET_TAG((v)) == DUK_TAG_BOOLEAN)
#define  DUK_TVAL_IS_BOOLEAN_TRUE(v)         ((v)->ui[_DUK_IDX_UI0] == DUK_XTAG_BOOLEAN_TRUE)
#define  DUK_TVAL_IS_BOOLEAN_FALSE(v)        ((v)->ui[_DUK_IDX_UI0] == DUK_XTAG_BOOLEAN_FALSE)
#define  DUK_TVAL_IS_STRING(v)               (DUK_TVAL_GET_TAG((v)) == DUK_TAG_STRING)
#define  DUK_TVAL_IS_OBJECT(v)               (DUK_TVAL_GET_TAG((v)) == DUK_TAG_OBJECT)
#define  DUK_TVAL_IS_BUFFER(v)               (DUK_TVAL_GET_TAG((v)) == DUK_TAG_BUFFER)
#define  DUK_TVAL_IS_POINTER(v)              (DUK_TVAL_GET_TAG((v)) == DUK_TAG_POINTER)
/* 0xfff0 is -Infinity */
#define  DUK_TVAL_IS_NUMBER(v)               (DUK_TVAL_GET_TAG((v)) <= 0xfff0)

#define  DUK_TVAL_IS_HEAP_ALLOCATED(v)       (DUK_TVAL_GET_TAG((v)) >= DUK_TAG_STRING)

/* misc */

/* Note: 
 *    - These take a double pointer as argument, so an explicit cast is needed
 *    - Full NaN check: exact determination whether argument is a NaN
 *    - Partial NaN check: works for all NaNs except those that begin with
 *      0x7ff0; since our normalized NaN begins with 0x7ff8, this check is
 *      reliable for normalized values
 */

/* XXX: reading unsigned int instead of unsigned short is one byte shorter on x86 :) */
#ifdef _USE_ME_VARIANT
#define  _DUK_DOUBLE_IS_NAN_FULL(d) \
	/* E == 0x7ff, F != 0 => NaN */ \
	(((((duk_tval *)(d))->us[_DUK_IDX_US0] & 0x7ff0) == 0x7ff0) && \
	 (((((duk_tval *)(d))->ull[_DUK_IDX_ULL0]) & 0x000fffffffffffffULL) != 0))
#else
#define  _DUK_DOUBLE_IS_NAN_FULL(d) \
	/* E == 0x7ff, F != 0 => NaN */ \
	(((((duk_tval *)(d))->us[_DUK_IDX_US0] & 0x7ff0) == 0x7ff0) && \
	 (((((duk_tval *)(d))->ull[_DUK_IDX_ULL0]) & 0xffffffff000fffffULL) != 0))
#endif

/* XXX: avoid possible double read? */
#define  _DUK_DOUBLE_IS_NAN_NOTFULL(d) \
	/* E == 0x7ff, topmost four bits of F != 0 => assume NaN */ \
	(((((duk_tval *)(d))->us[_DUK_IDX_US0] & 0x7ff0) == 0x7ff0) && \
	 ((((duk_tval *)(d))->us[_DUK_IDX_US0] & 0x000f) != 0x0000))

#define  _DUK_DOUBLE_NORMALIZE_NAN_CHECK_FULL(d)  do { \
		if (_DUK_DOUBLE_IS_NAN_FULL((d))) { \
			_DUK_DOUBLE_SET_NAN_FULL((d)); \
		} \
	} while (0)

#define  _DUK_DOUBLE_NORMALIZE_NAN_CHECK_NOTFULL(d)  do { \
		if (_DUK_DOUBLE_IS_NAN_NOTFULL((d))) { \
			_DUK_DOUBLE_SET_NAN_NOTFULL((d)); \
		} \
	} while (0)

#ifdef _USE_ME_VARIANT
#define  _DUK_DOUBLE_IS_NORMALIZED_NAN_FULL(d) \
	(((duk_tval *)(d))->ull[_DUK_IDX_ULL0] == 0x000000007ff80000ULL)
#else
#define  _DUK_DOUBLE_IS_NORMALIZED_NAN_FULL(d) \
	(((duk_tval *)(d))->ull[_DUK_IDX_ULL0] == 0x7ff8000000000000ULL)
#endif

#define  _DUK_DOUBLE_IS_NORMALIZED_NAN_NOTFULL(d) \
	/* E == 0x7ff, F == 8 => normalized NaN */ \
	(((duk_tval *)(d))->us[_DUK_IDX_US0] == 0x7ff8)

#ifdef DUK_USE_FULL_TVAL
#define  DUK_DOUBLE_NORMALIZE_NAN_CHECK(d)  _DUK_DOUBLE_NORMALIZE_NAN_CHECK_FULL((d))
#define  DUK_DOUBLE_IS_NAN(d)               _DUK_DOUBLE_IS_NAN_FULL((d))
#define  DUK_DOUBLE_IS_NORMALIZED_NAN(d)    _DUK_DOUBLE_IS_NORMALIZED_NAN_FULL((d))
#else
#define  DUK_DOUBLE_NORMALIZE_NAN_CHECK(d)  _DUK_DOUBLE_NORMALIZE_NAN_CHECK_NOTFULL((d))
#define  DUK_DOUBLE_IS_NAN(d)               _DUK_DOUBLE_IS_NAN_NOTFULL((d))
#define  DUK_DOUBLE_IS_NORMALIZED_NAN(d)    _DUK_DOUBLE_IS_NORMALIZED_NAN_NOTFULL((d))
#endif

/* this is used for assertions - it checks that the NaN is properly normalized */
#define  DUK_DOUBLE_IS_NORMALIZED(d) \
	(!DUK_DOUBLE_IS_NAN((d)) ||  /* either not a NaN */ \
	 DUK_DOUBLE_IS_NORMALIZED_NAN((d)))  /* or is a normalized NaN */

#else  /* DUK_USE_PACKED_TVAL */
/* ======================================================================== */

/*
 *  Portable 12-byte representation
 */

#ifdef DUK_USE_FULL_TVAL
#error no 'full' tagged values in 12-byte representation
#endif

typedef struct duk_tval_struct duk_tval;

struct duk_tval_struct {
	int t;
	union {
		double d;
		int i;
		void *voidptr;
		duk_hstring *hstring;
		duk_hobject *hobject;
		duk_hcompiledfunction *hcompiledfunction;
		duk_hnativefunction *hnativefunction;
		duk_hthread *hthread;
		duk_hbuffer *hbuffer;
		duk_heaphdr *heaphdr;
	} v;
};

#define  _DUK_TAG_NUMBER               0  /* not exposed */
#define  DUK_TAG_UNDEFINED             1
#define  DUK_TAG_NULL                  2
#define  DUK_TAG_BOOLEAN               3
#define  DUK_TAG_POINTER               4
#define  DUK_TAG_STRING                5
#define  DUK_TAG_OBJECT                6
#define  DUK_TAG_BUFFER                7

/* _DUK_TAG_NUMBER is intentionally first, as it is the default clause in code
 * to support the 8-byte representation.  Further, it is a non-heap-allocated
 * type so it should come before DUK_TAG_STRING.  Finally, it should not break
 * the tag value ranges covered by case-clauses in a switch-case.
 */

/* setters */
#define  DUK_TVAL_SET_UNDEFINED_ACTUAL(tv)  do { \
		(tv)->t = DUK_TAG_UNDEFINED; \
		(tv)->v.i = 0; \
	} while (0)

#define  DUK_TVAL_SET_UNDEFINED_UNUSED(tv)  do { \
		(tv)->t = DUK_TAG_UNDEFINED; \
		(tv)->v.i = 1; \
	} while (0)

#define  DUK_TVAL_SET_NULL(tv)  do { \
		(tv)->t = DUK_TAG_NULL; \
	} while (0)

#define  DUK_TVAL_SET_BOOLEAN(tv,val)  do { \
		(tv)->t = DUK_TAG_BOOLEAN; \
		(tv)->v.i = (val); \
	} while (0)

#define  DUK_TVAL_SET_NUMBER(tv,val)  do { \
		(tv)->t = _DUK_TAG_NUMBER; \
		(tv)->v.d = (val); \
	} while (0)

#define  DUK_TVAL_SET_STRING(tv,hptr)  do { \
		(tv)->t = DUK_TAG_STRING; \
		(tv)->v.hstring = (hptr); \
	} while (0)

#define  DUK_TVAL_SET_OBJECT(tv,hptr)  do { \
		(tv)->t = DUK_TAG_OBJECT; \
		(tv)->v.hobject = (hptr); \
	} while (0)

#define  DUK_TVAL_SET_BUFFER(tv,hptr)  do { \
		(tv)->t = DUK_TAG_BUFFER; \
		(tv)->v.hbuffer = (hptr); \
	} while (0)

#define  DUK_TVAL_SET_POINTER(tv,hptr)  do { \
		(tv)->t = DUK_TAG_POINTER; \
		(tv)->v.voidptr = (hptr); \
	} while (0)

#define  DUK_TVAL_SET_NAN(tv)  do { \
		/* in non-packed representation we don't care about which NaN is used */ \
		(tv)->t = _DUK_TAG_NUMBER; \
		(tv)->v.d = NAN; \
	} while (0)

#define  DUK_DOUBLE_SET_NAN(d)  do { \
		/* in non-packed representation we don't care about which NaN is used */ \
		/* assume d is 'double *' */ \
		(d)[0] = NAN; \
	} while (0)

#define  DUK_TVAL_SET_TVAL(v,x)              do { *(v) = *(x); } while (0)

/* getters */
#define  DUK_TVAL_GET_BOOLEAN(tv)           ((tv)->v.i)
#define  DUK_TVAL_GET_NUMBER(tv)            ((tv)->v.d)
#define  DUK_TVAL_GET_STRING(tv)            ((tv)->v.hstring)
#define  DUK_TVAL_GET_OBJECT(tv)            ((tv)->v.hobject)
#define  DUK_TVAL_GET_BUFFER(tv)            ((tv)->v.hbuffer)
#define  DUK_TVAL_GET_POINTER(tv)           ((tv)->v.voidptr)
#define  DUK_TVAL_GET_HEAPHDR(tv)           ((tv)->v.heaphdr)

/* decoding */
#define  DUK_TVAL_GET_TAG(tv)               ((tv)->t)
#define  DUK_TVAL_IS_NUMBER(tv)             ((tv)->t == _DUK_TAG_NUMBER)
#define  DUK_TVAL_IS_UNDEFINED(tv)          ((tv)->t == DUK_TAG_UNDEFINED)
#define  DUK_TVAL_IS_UNDEFINED_ACTUAL(tv)   (((tv)->t == DUK_TAG_UNDEFINED) && ((tv)->v.i == 0))
#define  DUK_TVAL_IS_UNDEFINED_UNUSED(tv)   (((tv)->t == DUK_TAG_UNDEFINED) && ((tv)->v.i != 0))
#define  DUK_TVAL_IS_NULL(tv)               ((tv)->t == DUK_TAG_NULL)
#define  DUK_TVAL_IS_BOOLEAN(tv)            ((tv)->t == DUK_TAG_BOOLEAN)
#define  DUK_TVAL_IS_BOOLEAN_TRUE(tv)       (((tv)->t == DUK_TAG_BOOLEAN) && ((tv)->v.i != 0))
#define  DUK_TVAL_IS_BOOLEAN_FALSE(tv)      (((tv)->t == DUK_TAG_BOOLEAN) && ((tv)->v.i == 0))
#define  DUK_TVAL_IS_STRING(tv)             ((tv)->t == DUK_TAG_STRING)
#define  DUK_TVAL_IS_OBJECT(tv)             ((tv)->t == DUK_TAG_OBJECT)
#define  DUK_TVAL_IS_BUFFER(tv)             ((tv)->t == DUK_TAG_BUFFER)
#define  DUK_TVAL_IS_POINTER(tv)            ((tv)->t == DUK_TAG_POINTER)

#define  DUK_TVAL_IS_HEAP_ALLOCATED(tv)     ((tv)->t >= DUK_TAG_STRING)

/* misc */
#define  DUK_DOUBLE_NORMALIZE_NAN_CHECK(d)  /* nop: no need to normalize */
#define  DUK_DOUBLE_IS_NAN(d)               (fpclassify(*(d)) == FP_NAN)
#define  DUK_DOUBLE_IS_NORMALIZED_NAN(d)    (fpclassify(*(d)) == FP_NAN)  /* all NaNs are considered normalized */
#define  DUK_DOUBLE_IS_NORMALIZED(d)        1  /* all doubles are considered normalized */

#endif  /* DUK_USE_PACKED_TVAL */

/*
 *  Convenience (independent of representation)
 */

#define  DUK_TVAL_SET_BOOLEAN_TRUE(v)        DUK_TVAL_SET_BOOLEAN(v, 1)
#define  DUK_TVAL_SET_BOOLEAN_FALSE(v)       DUK_TVAL_SET_BOOLEAN(v, 0)

/* undefine local defines */
#ifdef _USE_LE_VARIANT
#undef _USE_LE_VARIANT
#endif
#ifdef _USE_ME_VARIANT
#undef _USE_ME_VARIANT
#endif
#ifdef _USE_BE_VARIANT
#undef _USE_BE_VARIANT
#endif

#endif  /* DUK_TVAL_H_INCLUDED */

#line 1 "duk_heaphdr.h"
/*
 *  Heap header definition and assorted macros, including ref counting.
 *  Access all fields through the accessor macros.
 */

#ifndef DUK_HEAPHDR_H_INCLUDED
#define DUK_HEAPHDR_H_INCLUDED

/* already included: duk_forwdecl.h */
/* already included: duk_tval.h */

/*
 *  Common heap header
 *
 *  All heap objects share the same flags and refcount fields.  Objects other
 *  than strings also need to have a single or double linked list pointers
 *  for insertion into the "heap allocated" list.  Strings are held in the
 *  heap-wide string table so they don't need link pointers.
 *
 *  Technically, 'h_refcount' must be wide enough to guarantee that it cannot
 *  wrap (otherwise objects might be freed incorrectly after wrapping).  This
 *  means essentially that the refcount field must be as wide as data pointers.
 *  On 64-bit platforms this means that the refcount needs to be 64 bits even
 *  if an 'int' is 32 bits.  This is a bit unfortunate, and compromising on
 *  this might be reasonable in the future.
 *
 *  Heap header size on 32-bit platforms: 8 bytes without reference counting,
 *  16 bytes with reference counting.
 */

struct duk_heaphdr {
	duk_u32 h_flags;
#if defined(DUK_USE_REFERENCE_COUNTING)
	size_t h_refcount;
#endif
	duk_heaphdr *h_next;
#if defined(DUK_USE_DOUBLE_LINKED_HEAP)
	/* refcounting requires direct heap frees, which in turn requires a dual linked heap */
	duk_heaphdr *h_prev;
#endif
};

struct duk_heaphdr_string {
	duk_u32 h_flags;
#if defined(DUK_USE_REFERENCE_COUNTING)
	size_t h_refcount;
#endif
};

#define  DUK_HEAPHDR_FLAGS_TYPE_MASK      0x0000000fU
#define  DUK_HEAPHDR_FLAGS_FLAG_MASK      (~DUK_HEAPHDR_FLAGS_TYPE_MASK)

                                              /* 4 bits for heap type */
#define  DUK_HEAPHDR_FLAGS_HEAP_START     4   /* 6 heap flags */
#define  DUK_HEAPHDR_FLAGS_USER_START     10  /* 22 user flags */

#define  DUK_HEAPHDR_HEAP_FLAG_NUMBER(n)  (DUK_HEAPHDR_FLAGS_HEAP_START + (n))
#define  DUK_HEAPHDR_USER_FLAG_NUMBER(n)  (DUK_HEAPHDR_FLAGS_USER_START + (n))
#define  DUK_HEAPHDR_HEAP_FLAG(n)         (1 << (DUK_HEAPHDR_FLAGS_HEAP_START + (n)))
#define  DUK_HEAPHDR_USER_FLAG(n)         (1 << (DUK_HEAPHDR_FLAGS_USER_START + (n)))

#define  DUK_HEAPHDR_FLAG_REACHABLE       DUK_HEAPHDR_HEAP_FLAG(0)  /* mark-and-sweep: reachable */
#define  DUK_HEAPHDR_FLAG_TEMPROOT        DUK_HEAPHDR_HEAP_FLAG(1)  /* mark-and-sweep: children not processed */
#define  DUK_HEAPHDR_FLAG_FINALIZABLE     DUK_HEAPHDR_HEAP_FLAG(2)  /* mark-and-sweep: finalizable (on current pass) */
#define  DUK_HEAPHDR_FLAG_FINALIZED       DUK_HEAPHDR_HEAP_FLAG(3)  /* mark-and-sweep: finalized (on previous pass) */

#define  DUK_HTYPE_MIN                    1
#define  DUK_HTYPE_STRING                 1
#define  DUK_HTYPE_OBJECT                 2
#define  DUK_HTYPE_BUFFER                 3
#define  DUK_HTYPE_MAX                    3

#define  DUK_HEAPHDR_GET_NEXT(h)       ((h)->h_next)
#define  DUK_HEAPHDR_SET_NEXT(h,val)   do { \
		(h)->h_next = (val); \
	} while (0)

#if defined(DUK_USE_DOUBLE_LINKED_HEAP)
#define  DUK_HEAPHDR_GET_PREV(h)       ((h)->h_prev)
#define  DUK_HEAPHDR_SET_PREV(h,val)   do { \
		(h)->h_prev = (val); \
	} while (0)
#endif

#if defined(DUK_USE_REFERENCE_COUNTING)
#define  DUK_HEAPHDR_GET_REFCOUNT(h)   ((h)->h_refcount)
#define  DUK_HEAPHDR_SET_REFCOUNT(h,val)  do { \
		(h)->h_refcount = (val); \
	} while (0)
#else
/* refcount macros not defined without refcounting, caller must #ifdef now */
#endif  /* DUK_USE_REFERENCE_COUNTING */

/*
 *  Note: type is treated as a field separate from flags, so some masking is
 *  involved in the macros below.
 */

#define  DUK_HEAPHDR_GET_FLAGS(h)      ((h)->h_flags & DUK_HEAPHDR_FLAGS_FLAG_MASK)
#define  DUK_HEAPHDR_SET_FLAGS(h,val)  do { \
		(h)->h_flags = ((h)->h_flags & ~(DUK_HEAPHDR_FLAGS_FLAG_MASK)) | (val); \
	} while (0)

#define  DUK_HEAPHDR_GET_TYPE(h)       ((h)->h_flags & DUK_HEAPHDR_FLAGS_TYPE_MASK)
#define  DUK_HEAPHDR_SET_TYPE(h,val)   do { \
		(h)->h_flags = ((h)->h_flags & ~(DUK_HEAPHDR_FLAGS_TYPE_MASK)) | (val); \
	} while (0)

#define  DUK_HEAPHDR_HTYPE_VALID(h)    ( \
	DUK_HEAPHDR_GET_TYPE((h)) >= DUK_HTYPE_MIN && \
	DUK_HEAPHDR_GET_TYPE((h)) <= DUK_HTYPE_MAX \
	)

#define  DUK_HEAPHDR_SET_TYPE_AND_FLAGS(h,tval,fval)  do { \
		(h)->h_flags = ((tval) & DUK_HEAPHDR_FLAGS_TYPE_MASK) | \
		               ((fval) & DUK_HEAPHDR_FLAGS_FLAG_MASK); \
	} while (0)

#define  DUK_HEAPHDR_SET_FLAG_BITS(h,bits)  do { \
		DUK_ASSERT(((bits) & ~(DUK_HEAPHDR_FLAGS_FLAG_MASK)) == 0); \
		(h)->h_flags |= (bits); \
	} while (0)

#define  DUK_HEAPHDR_CLEAR_FLAG_BITS(h,bits)  do { \
		DUK_ASSERT(((bits) & ~(DUK_HEAPHDR_FLAGS_FLAG_MASK)) == 0); \
		(h)->h_flags &= ~((bits)); \
	} while (0)

#define  DUK_HEAPHDR_CHECK_FLAG_BITS(h,bits)  (((h)->h_flags & (bits)) != 0)

#define  DUK_HEAPHDR_SET_REACHABLE(h)      DUK_HEAPHDR_SET_FLAG_BITS((h),DUK_HEAPHDR_FLAG_REACHABLE)
#define  DUK_HEAPHDR_CLEAR_REACHABLE(h)    DUK_HEAPHDR_CLEAR_FLAG_BITS((h),DUK_HEAPHDR_FLAG_REACHABLE)
#define  DUK_HEAPHDR_HAS_REACHABLE(h)      DUK_HEAPHDR_CHECK_FLAG_BITS((h),DUK_HEAPHDR_FLAG_REACHABLE)

#define  DUK_HEAPHDR_SET_TEMPROOT(h)       DUK_HEAPHDR_SET_FLAG_BITS((h),DUK_HEAPHDR_FLAG_TEMPROOT)
#define  DUK_HEAPHDR_CLEAR_TEMPROOT(h)     DUK_HEAPHDR_CLEAR_FLAG_BITS((h),DUK_HEAPHDR_FLAG_TEMPROOT)
#define  DUK_HEAPHDR_HAS_TEMPROOT(h)       DUK_HEAPHDR_CHECK_FLAG_BITS((h),DUK_HEAPHDR_FLAG_TEMPROOT)

#define  DUK_HEAPHDR_SET_FINALIZABLE(h)    DUK_HEAPHDR_SET_FLAG_BITS((h),DUK_HEAPHDR_FLAG_FINALIZABLE)
#define  DUK_HEAPHDR_CLEAR_FINALIZABLE(h)  DUK_HEAPHDR_CLEAR_FLAG_BITS((h),DUK_HEAPHDR_FLAG_FINALIZABLE)
#define  DUK_HEAPHDR_HAS_FINALIZABLE(h)    DUK_HEAPHDR_CHECK_FLAG_BITS((h),DUK_HEAPHDR_FLAG_FINALIZABLE)

#define  DUK_HEAPHDR_SET_FINALIZED(h)      DUK_HEAPHDR_SET_FLAG_BITS((h),DUK_HEAPHDR_FLAG_FINALIZED)
#define  DUK_HEAPHDR_CLEAR_FINALIZED(h)    DUK_HEAPHDR_CLEAR_FLAG_BITS((h),DUK_HEAPHDR_FLAG_FINALIZED)
#define  DUK_HEAPHDR_HAS_FINALIZED(h)      DUK_HEAPHDR_CHECK_FLAG_BITS((h),DUK_HEAPHDR_FLAG_FINALIZED)

/* get or set a range of flags; m=first bit number, n=number of bits */
#define  DUK_HEAPHDR_GET_FLAG_RANGE(h,m,n)  (((h)->h_flags >> (m)) & ((1 << (n)) - 1))

#define  DUK_HEAPHDR_SET_FLAG_RANGE(h,m,n,v)  do { \
		(h)->h_flags = \
			((h)->h_flags & (~(((1 << (n)) - 1) << (m)))) \
			| ((v) << (m)); \
	} while (0)

/* init pointer fields to null */
#if defined(DUK_USE_DOUBLE_LINKED_HEAP)
#define  DUK_HEAPHDR_INIT_NULLS(h)       do { \
		(h)->h_next = NULL; \
	} while (0)
#else
#define  DUK_HEAPHDR_INIT_NULLS(h)       do { \
		(h)->h_next = NULL; \
		(h)->h_prev = NULL; \
	} while (0)
#endif

#define  DUK_HEAPHDR_STRING_INIT_NULLS(h)  /* currently nop */

/*
 *  Reference counting helper macros.  The macros take a thread argument
 *  and must thus always be executed in a specific thread context.  The
 *  thread argument is needed for features like finalization.  Currently
 *  it is not required for INCREF, but it is included just in case.
 *
 *  Note that 'raw' macros such as DUK_HEAPHDR_GET_REFCOUNT() are not
 *  defined without DUK_USE_REFERENCE_COUNTING, so caller must #ifdef
 *  around them.
 */

#if defined(DUK_USE_REFERENCE_COUNTING)

#define  DUK_TVAL_INCREF(thr,tv)                duk_heap_tval_incref((tv))
#define  DUK_TVAL_DECREF(thr,tv)                duk_heap_tval_decref((thr),(tv))
#define  _DUK_HEAPHDR_INCREF(thr,h)             duk_heap_heaphdr_incref((h))
#define  _DUK_HEAPHDR_DECREF(thr,h)             duk_heap_heaphdr_decref((thr),(h))
#define  DUK_HEAPHDR_INCREF(thr,h)              _DUK_HEAPHDR_INCREF((thr),(duk_heaphdr *) (h))
#define  DUK_HEAPHDR_DECREF(thr,h)              _DUK_HEAPHDR_DECREF((thr),(duk_heaphdr *) (h))
#define  DUK_HSTRING_INCREF(thr,h)              _DUK_HEAPHDR_INCREF((thr),(duk_heaphdr *) (h))
#define  DUK_HSTRING_DECREF(thr,h)              _DUK_HEAPHDR_DECREF((thr),(duk_heaphdr *) (h))
#define  DUK_HOBJECT_INCREF(thr,h)              _DUK_HEAPHDR_INCREF((thr),(duk_heaphdr *) (h))
#define  DUK_HOBJECT_DECREF(thr,h)              _DUK_HEAPHDR_DECREF((thr),(duk_heaphdr *) (h))
#define  DUK_HBUFFER_INCREF(thr,h)              _DUK_HEAPHDR_INCREF((thr),(duk_heaphdr *) (h))
#define  DUK_HBUFFER_DECREF(thr,h)              _DUK_HEAPHDR_DECREF((thr),(duk_heaphdr *) (h))
#define  DUK_HCOMPILEDFUNCTION_INCREF(thr,h)    _DUK_HEAPHDR_INCREF((thr),(duk_heaphdr *) &(h)->obj)
#define  DUK_HCOMPILEDFUNCTION_DECREF(thr,h)    _DUK_HEAPHDR_DECREF((thr),(duk_heaphdr *) &(h)->obj)
#define  DUK_HNATIVEFUNCTION_INCREF(thr,h)      _DUK_HEAPHDR_INCREF((thr),(duk_heaphdr *) &(h)->obj)
#define  DUK_HNATIVEFUNCTION_DECREF(thr,h)      _DUK_HEAPHDR_DECREF((thr),(duk_heaphdr *) &(h)->obj)
#define  DUK_HTHREAD_INCREF(thr,h)              _DUK_HEAPHDR_INCREF((thr),(duk_heaphdr *) &(h)->obj)
#define  DUK_HTHREAD_DECREF(thr,h)              _DUK_HEAPHDR_DECREF((thr),(duk_heaphdr *) &(h)->obj)

#else  /* DUK_USE_REFERENCE_COUNTING */

#define  DUK_TVAL_INCREF(thr,v)                 /* nop */
#define  DUK_TVAL_DECREF(thr,v)                 /* nop */
#define  DUK_HEAPHDR_INCREF(thr,h)              /* nop */
#define  DUK_HEAPHDR_DECREF(thr,h)              /* nop */
#define  DUK_HSTRING_INCREF(thr,h)              /* nop */
#define  DUK_HSTRING_DECREF(thr,h)              /* nop */
#define  DUK_HOBJECT_INCREF(thr,h)              /* nop */
#define  DUK_HOBJECT_DECREF(thr,h)              /* nop */
#define  DUK_HBUFFER_INCREF(thr,h)              /* nop */
#define  DUK_HBUFFER_DECREF(thr,h)              /* nop */
#define  DUK_HCOMPILEDFUNCTION_INCREF(thr,h)    /* nop */
#define  DUK_HCOMPILEDFUNCTION_DECREF(thr,h)    /* nop */
#define  DUK_HNATIVEFUNCTION_INCREF(thr,h)      /* nop */
#define  DUK_HNATIVEFUNCTION_DECREF(thr,h)      /* nop */
#define  DUK_HTHREAD_INCREF(thr,h)              /* nop */
#define  DUK_HTHREAD_DECREF(thr,h)              /* nop */

#endif  /* DUK_USE_REFERENCE_COUNTING */

#endif  /* DUK_HEAPHDR_H_INCLUDED */

#line 56 "duk_internal.h"
#include "duktape.h"
#line 1 "duk_api_internal.h"
/*
 *  Internal API calls which have (stack and other) semantics
 *  similar to the public API.
 */

#ifndef DUK_API_INTERNAL_H_INCLUDED
#define DUK_API_INTERNAL_H_INCLUDED

/* duk_push_sprintf constants */
#define  DUK_PUSH_SPRINTF_INITIAL_SIZE  256
#define  DUK_PUSH_SPRINTF_SANITY_LIMIT  (1*1024*1024*1024)

int duk_check_valstack_resize(duk_context *ctx, unsigned int min_new_size, int allow_shrink);
void duk_require_valstack_resize(duk_context *ctx, unsigned int min_new_size, int allow_shrink);

int duk_check_stack_raw(duk_context *ctx, unsigned int extra);
void duk_require_stack_raw(duk_context *ctx, unsigned int extra);

duk_tval *duk_get_tval(duk_context *ctx, int index);
duk_tval duk_get_tval_value(duk_context *ctx, int index);      /* FIXME: not implemented now */
duk_tval *duk_require_tval(duk_context *ctx, int index);
duk_tval duk_require_tval_value(duk_context *ctx, int index);  /* FIXME: not implemented now */
void duk_push_tval(duk_context *ctx, duk_tval *tv);
void duk_push_tval_value(duk_context *ctx, duk_tval tv);       /* FIXME: not implemented now */

void duk_push_this_check_object_coercible(duk_context *ctx);   /* push the current 'this' binding; throw TypeError
                                                                * if binding is not object coercible (CheckObjectCoercible).
                                                                */
void duk_push_this_coercible_to_object(duk_context *ctx);       /* duk_push_this() + CheckObjectCoercible() + duk_to_object() */
void duk_push_this_coercible_to_string(duk_context *ctx);       /* duk_push_this() + CheckObjectCoercible() + duk_to_string() */

duk_hstring *duk_get_hstring(duk_context *ctx, int index);
duk_hobject *duk_get_hobject(duk_context *ctx, int index);
duk_hobject *duk_get_hobject_with_class(duk_context *ctx, int index, int class);
duk_hbuffer *duk_get_hbuffer(duk_context *ctx, int index);
duk_hthread *duk_get_hthread(duk_context *ctx, int index);
duk_hcompiledfunction *duk_get_hcompiledfunction(duk_context *ctx, int index);
duk_hnativefunction *duk_get_hnativefunction(duk_context *ctx, int index);

/* FIXME: specific getters for e.g. thread; duk_get_hobject_with_flags()
 * could be the underlying primitive?
 */

duk_hstring *duk_to_hstring(duk_context *ctx, int index);
int duk_to_int_clamped_raw(duk_context *ctx, int index, int minval, int maxval, int *out_clamped);  /* out_clamped=NULL, RangeError if outside range */
int duk_to_int_clamped(duk_context *ctx, int index, int minval, int maxval);
int duk_to_int_check_range(duk_context *ctx, int index, int minval, int maxval);

duk_hstring *duk_require_hstring(duk_context *ctx, int index);
duk_hobject *duk_require_hobject(duk_context *ctx, int index);
duk_hobject *duk_require_hobject_with_class(duk_context *ctx, int index, int class);
duk_hbuffer *duk_require_hbuffer(duk_context *ctx, int index);
duk_hthread *duk_require_hthread(duk_context *ctx, int index);
duk_hcompiledfunction *duk_require_hcompiledfunction(duk_context *ctx, int index);
duk_hnativefunction *duk_require_hnativefunction(duk_context *ctx, int index);

void duk_push_unused(duk_context *ctx);
void duk_push_hstring(duk_context *ctx, duk_hstring *h);
void duk_push_hstring_stridx(duk_context *ctx, int stridx);
void duk_push_hobject(duk_context *ctx, duk_hobject *h);
void duk_push_hbuffer(duk_context *ctx, duk_hbuffer *h);
void duk_push_builtin(duk_context *ctx, int builtin_idx);
int duk_push_object_helper(duk_context *ctx, int hobject_flags_and_class, int prototype_bidx);
int duk_push_object_internal(duk_context *ctx);
int duk_push_compiledfunction(duk_context *ctx);

int duk_get_prop_stridx(duk_context *ctx, int obj_index, unsigned int stridx);     /* [] -> [val] */
int duk_put_prop_stridx(duk_context *ctx, int obj_index, unsigned int stridx);     /* [val] -> [] */
int duk_del_prop_stridx(duk_context *ctx, int obj_index, unsigned int stridx);     /* [] -> [] */
int duk_has_prop_stridx(duk_context *ctx, int obj_index, unsigned int stridx);     /* [] -> [] */

void duk_def_prop(duk_context *ctx, int obj_index, int desc_flags);  /* [key val] -> [] */
void duk_def_prop_stridx(duk_context *ctx, int obj_index, unsigned int stridx, int desc_flags);  /* [val] -> [] */
void duk_def_prop_stridx_builtin(duk_context *ctx, int obj_index, unsigned int stridx, unsigned int builtin_idx, int desc_flags);  /* [] -> [] */

void duk_def_prop_stridx_thrower(duk_context *ctx, int obj_index, unsigned int stridx, int desc_flags);  /* [] -> [] */

#endif  /* DUK_API_INTERNAL_H_INCLUDED */

#line 1 "duk_hstring.h"
/*
 *  Heap string representation.
 *
 *  Strings are byte sequences ordinarily stored in extended UTF-8 format,
 *  allowing values larger than the official UTF-8 range (used internally)
 *  and also allowing UTF-8 encoding of surrogate pairs (CESU-8 format).
 *  Strings may also be invalid UTF-8 altogether which is the case e.g. with
 *  strings used as internal property names and raw buffers converted to
 *  strings.  In such cases the 'clen' field contains an inaccurate value.
 *
 *  Ecmascript requires support for 32-bit long strings.  However, since each
 *  16-bit codepoint can take 3 bytes in CESU-8, this representation can only
 *  support about 1.4G codepoint long strings in extreme cases.  This is not
 *  really a practical issue.
 */

#ifndef DUK_HSTRING_H_INCLUDED
#define DUK_HSTRING_H_INCLUDED

/* already included: duk_heaphdr.h */
/* already included: duk_misc.h */

/* FIXME: flag for 'valid extended utf-8' (internal strings are not, regexp bytecode is)? */
/* FIXME: flag for 'contains non-bmp chars'? */

#define  DUK_HSTRING_FLAG_ARRIDX                     DUK_HEAPHDR_USER_FLAG(0)  /* string is a valid array index */
#define  DUK_HSTRING_FLAG_INTERNAL                   DUK_HEAPHDR_USER_FLAG(1)  /* string is internal */
#define  DUK_HSTRING_FLAG_RESERVED_WORD              DUK_HEAPHDR_USER_FLAG(2)  /* string is a reserved word (non-strict) */
#define  DUK_HSTRING_FLAG_STRICT_RESERVED_WORD       DUK_HEAPHDR_USER_FLAG(3)  /* string is a reserved word (strict) */
#define  DUK_HSTRING_FLAG_EVAL_OR_ARGUMENTS          DUK_HEAPHDR_USER_FLAG(4)  /* string is 'eval' or 'arguments' */

#define  DUK_HSTRING_HAS_ARRIDX(x)                   DUK_HEAPHDR_CHECK_FLAG_BITS(&(x)->hdr, DUK_HSTRING_FLAG_ARRIDX)
#define  DUK_HSTRING_HAS_INTERNAL(x)                 DUK_HEAPHDR_CHECK_FLAG_BITS(&(x)->hdr, DUK_HSTRING_FLAG_INTERNAL)
#define  DUK_HSTRING_HAS_RESERVED_WORD(x)            DUK_HEAPHDR_CHECK_FLAG_BITS(&(x)->hdr, DUK_HSTRING_FLAG_RESERVED_WORD)
#define  DUK_HSTRING_HAS_STRICT_RESERVED_WORD(x)     DUK_HEAPHDR_CHECK_FLAG_BITS(&(x)->hdr, DUK_HSTRING_FLAG_STRICT_RESERVED_WORD)
#define  DUK_HSTRING_HAS_EVAL_OR_ARGUMENTS(x)        DUK_HEAPHDR_CHECK_FLAG_BITS(&(x)->hdr, DUK_HSTRING_FLAG_EVAL_OR_ARGUMENTS)

#define  DUK_HSTRING_SET_ARRIDX(x)                   DUK_HEAPHDR_SET_FLAG_BITS(&(x)->hdr, DUK_HSTRING_FLAG_ARRIDX)
#define  DUK_HSTRING_SET_INTERNAL(x)                 DUK_HEAPHDR_SET_FLAG_BITS(&(x)->hdr, DUK_HSTRING_FLAG_INTERNAL)
#define  DUK_HSTRING_SET_RESERVED_WORD(x)            DUK_HEAPHDR_SET_FLAG_BITS(&(x)->hdr, DUK_HSTRING_FLAG_RESERVED_WORD)
#define  DUK_HSTRING_SET_STRICT_RESERVED_WORD(x)     DUK_HEAPHDR_SET_FLAG_BITS(&(x)->hdr, DUK_HSTRING_FLAG_STRICT_RESERVED_WORD)
#define  DUK_HSTRING_SET_EVAL_OR_ARGUMENTS(x)        DUK_HEAPHDR_SET_FLAG_BITS(&(x)->hdr, DUK_HSTRING_FLAG_EVAL_OR_ARGUMENTS)

#define  DUK_HSTRING_CLEAR_ARRIDX(x)                 DUK_HEAPHDR_CLEAR_FLAG_BITS(&(x)->hdr, DUK_HSTRING_FLAG_ARRIDX)
#define  DUK_HSTRING_CLEAR_INTERNAL(x)               DUK_HEAPHDR_CLEAR_FLAG_BITS(&(x)->hdr, DUK_HSTRING_FLAG_INTERNAL)
#define  DUK_HSTRING_CLEAR_RESERVED_WORD(x)          DUK_HEAPHDR_CLEAR_FLAG_BITS(&(x)->hdr, DUK_HSTRING_FLAG_RESERVED_WORD)
#define  DUK_HSTRING_CLEAR_STRICT_RESERVED_WORD(x)   DUK_HEAPHDR_CLEAR_FLAG_BITS(&(x)->hdr, DUK_HSTRING_FLAG_STRICT_RESERVED_WORD)
#define  DUK_HSTRING_CLEAR_EVAL_OR_ARGUMENTS(x)      DUK_HEAPHDR_CLEAR_FLAG_BITS(&(x)->hdr, DUK_HSTRING_FLAG_EVAL_OR_ARGUMENTS)

#define  DUK_HSTRING_IS_ASCII(x)                     ((x)->blen == (x)->clen)
#define  DUK_HSTRING_IS_EMPTY(x)                     ((x)->blen == 0)

#define  DUK_HSTRING_GET_HASH(x)                     ((x)->hash)
#define  DUK_HSTRING_GET_BYTELEN(x)                  ((x)->blen)
#define  DUK_HSTRING_GET_CHARLEN(x)                  ((x)->clen)
#define  DUK_HSTRING_GET_DATA(x)                     ((duk_u8 *) ((x) + 1))
#define  DUK_HSTRING_GET_DATA_END(x)                 (((duk_u8 *) ((x) + 1)) + ((x)->blen))

/* marker value; in E5 2^32-1 is not a valid array index (2^32-2 is highest valid) */
#define  DUK_HSTRING_NO_ARRAY_INDEX  (0xffffffffU)

/* get array index related to string (or return DUK_HSTRING_NO_ARRAY_INDEX);
 * avoids helper call if string has no array index value.
 */
#define  DUK_HSTRING_GET_ARRIDX_FAST(h)  \
	(DUK_HSTRING_HAS_ARRIDX((h)) ? duk_js_to_arrayindex_string_helper((h)) : DUK_HSTRING_NO_ARRAY_INDEX)

/* slower but more compact variant */
#define  DUK_HSTRING_GET_ARRIDX_SLOW(h)  \
	(duk_js_to_arrayindex_string_helper((h)))

/*
 *  Misc
 */

struct duk_hstring {
	/* smaller heaphdr than for other objects, because strings are held
	 * in string intern table which requires no link pointers.
	 */
	duk_heaphdr_string hdr;

	/* Note: we could try to stuff a partial hash (e.g. 16 bits) into the
	 * shared heap header.  Good hashing needs more hash bits though.
	 */

	duk_u32 hash;         /* string hash */
	duk_u32 blen;         /* length in bytes (not counting NUL term) */
	duk_u32 clen;         /* length in codepoints (must be E5 compatible) */

	/*
	 *  String value of 'blen+1' bytes follows (+1 for NUL termination
	 *  convenience for C API).
	 */
};

#endif  /* DUK_HSTRING_H_INCLUDED */

#line 1 "duk_hobject.h"
/*
 *  Heap object representation.
 *
 *  Heap objects are used for Ecmascript objects, arrays, and functions,
 *  but also for internal control like declarative and object environment
 *  records.  Compiled functions, native functions, and threads are also
 *  objects but with an extended C struct.
 *
 *  Objects provide the required Ecmascript semantics and special behaviors
 *  especially for property access.
 *
 *  Properties are stored in three conceptual parts:
 *
 *    1. A linear 'entry part' contains ordered key-value-attributes triples
 *       and is the main method of string properties.
 *
 *    2. An optional linear 'array part' is used for array objects to store a
 *       (dense) range of [0,N[ array indexed entries with default attributes
 *       (writable, enumerable, configurable).  If the array part would become
 *       sparse or non-default attributes are required, the array part is
 *       abandoned and moved to the 'entry part'.
 *
 *    3. An optional 'hash part' is used to optimize lookups of the entry
 *       part; it is used only for objects with sufficiently many properties 
 *       and can be abandoned without loss of information.
 *
 *  These three conceptual parts are stored in a single memory allocated area.
 *  This minimizes memory allocation overhead but also means that all three
 *  parts are resized together, and makes property access a bit complicated.
 */

#ifndef DUK_HOBJECT_H_INCLUDED
#define DUK_HOBJECT_H_INCLUDED

/* already included: duk_strings.h */

/* there are currently 22 flag bits available */
#define  DUK_HOBJECT_FLAG_EXTENSIBLE            DUK_HEAPHDR_USER_FLAG(0)   /* object is extensible */
#define  DUK_HOBJECT_FLAG_CONSTRUCTABLE         DUK_HEAPHDR_USER_FLAG(1)   /* object is constructable */
#define  DUK_HOBJECT_FLAG_BOUND                 DUK_HEAPHDR_USER_FLAG(2)   /* object established using Function.prototype.bind() */
#define  DUK_HOBJECT_FLAG_COMPILEDFUNCTION      DUK_HEAPHDR_USER_FLAG(4)   /* object is a compiled function (duk_hcompiledfunction) */
#define  DUK_HOBJECT_FLAG_NATIVEFUNCTION        DUK_HEAPHDR_USER_FLAG(5)   /* object is a native function (duk_hnativefunction) */
#define  DUK_HOBJECT_FLAG_THREAD                DUK_HEAPHDR_USER_FLAG(6)   /* object is a thread (duk_hthread) */
#define  DUK_HOBJECT_FLAG_ARRAY_PART            DUK_HEAPHDR_USER_FLAG(7)   /* object has an array part (a_size may still be 0) */
#define  DUK_HOBJECT_FLAG_STRICT                DUK_HEAPHDR_USER_FLAG(8)   /* function: function object is strict */
#define  DUK_HOBJECT_FLAG_NEWENV                DUK_HEAPHDR_USER_FLAG(9)   /* function: create new environment when called (see duk_hcompiledfunction) */
#define  DUK_HOBJECT_FLAG_NAMEBINDING           DUK_HEAPHDR_USER_FLAG(10)  /* function: create binding for func name (function templates only, used for named function expressions) */
#define  DUK_HOBJECT_FLAG_CREATEARGS            DUK_HEAPHDR_USER_FLAG(11)  /* function: create an arguments object on function call */
#define  DUK_HOBJECT_FLAG_ENVRECCLOSED          DUK_HEAPHDR_USER_FLAG(12)  /* envrec: (declarative) record is closed */
#define  DUK_HOBJECT_FLAG_SPECIAL_ARRAY         DUK_HEAPHDR_USER_FLAG(13)  /* 'Array' object, array length and index special behavior */
#define  DUK_HOBJECT_FLAG_SPECIAL_STRINGOBJ     DUK_HEAPHDR_USER_FLAG(14)  /* 'String' object, array index special behavior */
#define  DUK_HOBJECT_FLAG_SPECIAL_ARGUMENTS     DUK_HEAPHDR_USER_FLAG(15)  /* 'Arguments' object and has arguments special behavior (non-strict callee) */

#define  DUK_HOBJECT_FLAG_CLASS_BASE            DUK_HEAPHDR_USER_FLAG_NUMBER(18)
#define  DUK_HOBJECT_FLAG_CLASS_BITS            4

#define  DUK_HOBJECT_GET_CLASS_NUMBER(h)        \
	DUK_HEAPHDR_GET_FLAG_RANGE(&(h)->hdr, DUK_HOBJECT_FLAG_CLASS_BASE, DUK_HOBJECT_FLAG_CLASS_BITS)
#define  DUK_HOBJECT_SET_CLASS_NUMBER(h,v)      \
	DUK_HEAPHDR_SET_FLAG_RANGE(&(h)->hdr, DUK_HOBJECT_FLAG_CLASS_BASE, DUK_HOBJECT_FLAG_CLASS_BITS, (v))

/* for creating flag initializers */
#define  DUK_HOBJECT_CLASS_AS_FLAGS(v)          ((v) << DUK_HOBJECT_FLAG_CLASS_BASE)

/* E5 Section 8.6.2 + custom classes */
#define  DUK_HOBJECT_CLASS_UNUSED               0
#define  DUK_HOBJECT_CLASS_ARGUMENTS            1
#define  DUK_HOBJECT_CLASS_ARRAY                2
#define  DUK_HOBJECT_CLASS_BOOLEAN              3
#define  DUK_HOBJECT_CLASS_DATE                 4
#define  DUK_HOBJECT_CLASS_ERROR                5
#define  DUK_HOBJECT_CLASS_FUNCTION             6
#define  DUK_HOBJECT_CLASS_JSON                 7
#define  DUK_HOBJECT_CLASS_MATH                 8
#define  DUK_HOBJECT_CLASS_NUMBER               9
#define  DUK_HOBJECT_CLASS_OBJECT               10
#define  DUK_HOBJECT_CLASS_REGEXP               11
#define  DUK_HOBJECT_CLASS_STRING               12
#define  DUK_HOBJECT_CLASS_GLOBAL               13
#define  DUK_HOBJECT_CLASS_OBJENV               14  /* custom */
#define  DUK_HOBJECT_CLASS_DECENV               15  /* custom */

#define  DUK_HOBJECT_IS_OBJENV(h)               (DUK_HOBJECT_GET_CLASS_NUMBER((h)) == DUK_HOBJECT_CLASS_OBJENV)
#define  DUK_HOBJECT_IS_DECENV(h)               (DUK_HOBJECT_GET_CLASS_NUMBER((h)) == DUK_HOBJECT_CLASS_DECENV)
#define  DUK_HOBJECT_IS_ENV(h)                  (DUK_HOBJECT_IS_OBJENV((h)) || DUK_HOBJECT_IS_DECENV((h)))
#define  DUK_HOBJECT_IS_ARRAY(h)                (DUK_HOBJECT_GET_CLASS_NUMBER((h)) == DUK_HOBJECT_CLASS_ARRAY)
#define  DUK_HOBJECT_IS_COMPILEDFUNCTION(h)     DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_COMPILEDFUNCTION)
#define  DUK_HOBJECT_IS_NATIVEFUNCTION(h)       DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_NATIVEFUNCTION)
#define  DUK_HOBJECT_IS_THREAD(h)               DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_THREAD)

#define  DUK_HOBJECT_IS_NONBOUND_FUNCTION(h)    DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, \
                                                        DUK_HOBJECT_FLAG_COMPILEDFUNCTION | \
                                                        DUK_HOBJECT_FLAG_NATIVEFUNCTION)

#define  DUK_HOBJECT_IS_FUNCTION(h)             DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, \
                                                        DUK_HOBJECT_FLAG_BOUND | \
                                                        DUK_HOBJECT_FLAG_COMPILEDFUNCTION | \
                                                        DUK_HOBJECT_FLAG_NATIVEFUNCTION)

#define  DUK_HOBJECT_IS_CALLABLE(h)             DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, \
                                                        DUK_HOBJECT_FLAG_BOUND | \
                                                        DUK_HOBJECT_FLAG_COMPILEDFUNCTION | \
                                                        DUK_HOBJECT_FLAG_NATIVEFUNCTION)

/* object has any special behavior(s) */
#define  DUK_HOBJECT_SPECIAL_BEHAVIOR_FLAGS     (DUK_HOBJECT_FLAG_SPECIAL_ARRAY | \
                                                 DUK_HOBJECT_FLAG_SPECIAL_ARGUMENTS | \
                                                 DUK_HOBJECT_FLAG_SPECIAL_STRINGOBJ)

#define  DUK_HOBJECT_HAS_SPECIAL_BEHAVIOR(h)    DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_SPECIAL_BEHAVIOR_FLAGS)

#define  DUK_HOBJECT_HAS_EXTENSIBLE(h)          DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_EXTENSIBLE)
#define  DUK_HOBJECT_HAS_CONSTRUCTABLE(h)       DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_CONSTRUCTABLE)
#define  DUK_HOBJECT_HAS_BOUND(h)               DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_BOUND)
#define  DUK_HOBJECT_HAS_COMPILEDFUNCTION(h)    DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_COMPILEDFUNCTION)
#define  DUK_HOBJECT_HAS_NATIVEFUNCTION(h)      DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_NATIVEFUNCTION)
#define  DUK_HOBJECT_HAS_THREAD(h)              DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_THREAD)
#define  DUK_HOBJECT_HAS_ARRAY_PART(h)          DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_ARRAY_PART)
#define  DUK_HOBJECT_HAS_STRICT(h)              DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_STRICT)
#define  DUK_HOBJECT_HAS_NEWENV(h)              DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_NEWENV)
#define  DUK_HOBJECT_HAS_NAMEBINDING(h)         DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_NAMEBINDING)
#define  DUK_HOBJECT_HAS_CREATEARGS(h)          DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_CREATEARGS)
#define  DUK_HOBJECT_HAS_ENVRECCLOSED(h)        DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_ENVRECCLOSED)
#define  DUK_HOBJECT_HAS_SPECIAL_ARRAY(h)       DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_SPECIAL_ARRAY)
#define  DUK_HOBJECT_HAS_SPECIAL_STRINGOBJ(h)   DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_SPECIAL_STRINGOBJ)
#define  DUK_HOBJECT_HAS_SPECIAL_ARGUMENTS(h)   DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_SPECIAL_ARGUMENTS)

#define  DUK_HOBJECT_SET_EXTENSIBLE(h)          DUK_HEAPHDR_SET_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_EXTENSIBLE)
#define  DUK_HOBJECT_SET_CONSTRUCTABLE(h)       DUK_HEAPHDR_SET_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_CONSTRUCTABLE)
#define  DUK_HOBJECT_SET_BOUND(h)               DUK_HEAPHDR_SET_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_BOUND)
#define  DUK_HOBJECT_SET_COMPILEDFUNCTION(h)    DUK_HEAPHDR_SET_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_COMPILEDFUNCTION)
#define  DUK_HOBJECT_SET_NATIVEFUNCTION(h)      DUK_HEAPHDR_SET_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_NATIVEFUNCTION)
#define  DUK_HOBJECT_SET_THREAD(h)              DUK_HEAPHDR_SET_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_THREAD)
#define  DUK_HOBJECT_SET_ARRAY_PART(h)          DUK_HEAPHDR_SET_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_ARRAY_PART)
#define  DUK_HOBJECT_SET_STRICT(h)              DUK_HEAPHDR_SET_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_STRICT)
#define  DUK_HOBJECT_SET_NEWENV(h)              DUK_HEAPHDR_SET_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_NEWENV)
#define  DUK_HOBJECT_SET_NAMEBINDING(h)         DUK_HEAPHDR_SET_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_NAMEBINDING)
#define  DUK_HOBJECT_SET_CREATEARGS(h)          DUK_HEAPHDR_SET_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_CREATEARGS)
#define  DUK_HOBJECT_SET_ENVRECCLOSED(h)        DUK_HEAPHDR_SET_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_ENVRECCLOSED)
#define  DUK_HOBJECT_SET_SPECIAL_ARRAY(h)       DUK_HEAPHDR_SET_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_SPECIAL_ARRAY)
#define  DUK_HOBJECT_SET_SPECIAL_STRINGOBJ(h)   DUK_HEAPHDR_SET_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_SPECIAL_STRINGOBJ)
#define  DUK_HOBJECT_SET_SPECIAL_ARGUMENTS(h)   DUK_HEAPHDR_SET_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_SPECIAL_ARGUMENTS)

#define  DUK_HOBJECT_CLEAR_EXTENSIBLE(h)        DUK_HEAPHDR_CLEAR_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_EXTENSIBLE)
#define  DUK_HOBJECT_CLEAR_CONSTRUCTABLE(h)     DUK_HEAPHDR_CLEAR_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_CONSTRUCTABLE)
#define  DUK_HOBJECT_CLEAR_BOUND(h)             DUK_HEAPHDR_CLEAR_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_BOUND)
#define  DUK_HOBJECT_CLEAR_COMPILEDFUNCTION(h)  DUK_HEAPHDR_CLEAR_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_COMPILEDFUNCTION)
#define  DUK_HOBJECT_CLEAR_NATIVEFUNCTION(h)    DUK_HEAPHDR_CLEAR_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_NATIVEFUNCTION)
#define  DUK_HOBJECT_CLEAR_THREAD(h)            DUK_HEAPHDR_CLEAR_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_THREAD)
#define  DUK_HOBJECT_CLEAR_ARRAY_PART(h)        DUK_HEAPHDR_CLEAR_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_ARRAY_PART)
#define  DUK_HOBJECT_CLEAR_STRICT(h)            DUK_HEAPHDR_CLEAR_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_STRICT)
#define  DUK_HOBJECT_CLEAR_NEWENV(h)            DUK_HEAPHDR_CLEAR_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_NEWENV)
#define  DUK_HOBJECT_CLEAR_NAMEBINDING(h)       DUK_HEAPHDR_CLEAR_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_NAMEBINDING)
#define  DUK_HOBJECT_CLEAR_CREATEARGS(h)        DUK_HEAPHDR_CLEAR_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_CREATEARGS)
#define  DUK_HOBJECT_CLEAR_ENVRECCLOSED(h)      DUK_HEAPHDR_CLEAR_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_ENVRECCLOSED)
#define  DUK_HOBJECT_CLEAR_SPECIAL_ARRAY(h)     DUK_HEAPHDR_CLEAR_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_SPECIAL_ARRAY)
#define  DUK_HOBJECT_CLEAR_SPECIAL_STRINGOBJ(h) DUK_HEAPHDR_CLEAR_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_SPECIAL_STRINGOBJ)
#define  DUK_HOBJECT_CLEAR_SPECIAL_ARGUMENTS(h) DUK_HEAPHDR_CLEAR_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_SPECIAL_ARGUMENTS)

/* flags used for property attributes in duk_propdesc and packed flags */
#define  DUK_PROPDESC_FLAG_WRITABLE              (1 << 0)    /* E5 Section 8.6.1 */
#define  DUK_PROPDESC_FLAG_ENUMERABLE            (1 << 1)    /* E5 Section 8.6.1 */
#define  DUK_PROPDESC_FLAG_CONFIGURABLE          (1 << 2)    /* E5 Section 8.6.1 */
#define  DUK_PROPDESC_FLAG_ACCESSOR              (1 << 3)    /* accessor */
#define  DUK_PROPDESC_FLAGS_MASK                 (DUK_PROPDESC_FLAG_WRITABLE | \
                                                  DUK_PROPDESC_FLAG_ENUMERABLE | \
                                                  DUK_PROPDESC_FLAG_CONFIGURABLE | \
                                                  DUK_PROPDESC_FLAG_ACCESSOR)

/* convenience */
#define  DUK_PROPDESC_FLAGS_NONE                 0
#define  DUK_PROPDESC_FLAGS_W                    (DUK_PROPDESC_FLAG_WRITABLE)
#define  DUK_PROPDESC_FLAGS_E                    (DUK_PROPDESC_FLAG_ENUMERABLE)
#define  DUK_PROPDESC_FLAGS_C                    (DUK_PROPDESC_FLAG_CONFIGURABLE)
#define  DUK_PROPDESC_FLAGS_WE                   (DUK_PROPDESC_FLAG_WRITABLE | DUK_PROPDESC_FLAG_ENUMERABLE)
#define  DUK_PROPDESC_FLAGS_WC                   (DUK_PROPDESC_FLAG_WRITABLE | DUK_PROPDESC_FLAG_CONFIGURABLE)
#define  DUK_PROPDESC_FLAGS_EC                   (DUK_PROPDESC_FLAG_ENUMERABLE | DUK_PROPDESC_FLAG_CONFIGURABLE)
#define  DUK_PROPDESC_FLAGS_WEC                  (DUK_PROPDESC_FLAG_WRITABLE | \
                                                  DUK_PROPDESC_FLAG_ENUMERABLE | \
                                                  DUK_PROPDESC_FLAG_CONFIGURABLE)

/*
 *  Macros to access the 'p' allocation.
 */

#define  DUK_HOBJECT_E_GET_KEY_BASE(h)           \
	((duk_hstring **) ( \
		(h)->p \
	))
#define  DUK_HOBJECT_E_GET_VALUE_BASE(h)         \
	((duk_propvalue *) ( \
		(h)->p + \
			(h)->e_size * sizeof(duk_hstring *) \
	))
#define  DUK_HOBJECT_E_GET_FLAGS_BASE(h)         \
	((duk_u8 *) ( \
		(h)->p + (h)->e_size * (sizeof(duk_hstring *) + sizeof(duk_propvalue)) \
	))
#define  DUK_HOBJECT_A_GET_BASE(h)               \
	((duk_tval *) ( \
		(h)->p + \
			(h)->e_size * (sizeof(duk_hstring *) + sizeof(duk_propvalue) + sizeof(duk_u8)) \
	))
#define  DUK_HOBJECT_H_GET_BASE(h)               \
	((duk_u32 *) ( \
		(h)->p + \
			(h)->e_size * (sizeof(duk_hstring *) + sizeof(duk_propvalue) + sizeof(duk_u8)) + \
			(h)->a_size * sizeof(duk_tval) \
	))

#define  DUK_HOBJECT_P_COMPUTE_SIZE(n_ent,n_arr,n_hash) \
	( \
		(n_ent) * (sizeof(duk_hstring *) + sizeof(duk_propvalue) + sizeof(duk_u8)) + \
		(n_arr) * sizeof(duk_tval) + \
		(n_hash) * sizeof(duk_u32) \
	)

#define  DUK_HOBJECT_E_GET_KEY(h,i)              (DUK_HOBJECT_E_GET_KEY_BASE((h))[(i)])
#define  DUK_HOBJECT_E_GET_KEY_PTR(h,i)          (&DUK_HOBJECT_E_GET_KEY_BASE((h))[(i)])
#define  DUK_HOBJECT_E_GET_VALUE(h,i)            (DUK_HOBJECT_E_GET_VALUE_BASE((h))[(i)])
#define  DUK_HOBJECT_E_GET_VALUE_PTR(h,i)        (&DUK_HOBJECT_E_GET_VALUE_BASE((h))[(i)])
#define  DUK_HOBJECT_E_GET_VALUE_TVAL(h,i)       (DUK_HOBJECT_E_GET_VALUE((h),(i)).v)
#define  DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(h,i)   (&DUK_HOBJECT_E_GET_VALUE((h),(i)).v)
#define  DUK_HOBJECT_E_GET_VALUE_GETTER(h,i)     (DUK_HOBJECT_E_GET_VALUE((h),(i)).a.get)
#define  DUK_HOBJECT_E_GET_VALUE_GETTER_PTR(h,i) (&DUK_HOBJECT_E_GET_VALUE((h),(i)).a.get)
#define  DUK_HOBJECT_E_GET_VALUE_SETTER(h,i)     (DUK_HOBJECT_E_GET_VALUE((h),(i)).a.set)
#define  DUK_HOBJECT_E_GET_VALUE_SETTER_PTR(h,i) (&DUK_HOBJECT_E_GET_VALUE((h),(i)).a.set)
#define  DUK_HOBJECT_E_GET_FLAGS(h,i)            (DUK_HOBJECT_E_GET_FLAGS_BASE((h))[(i)])
#define  DUK_HOBJECT_E_GET_FLAGS_PTR(h,i)        (&DUK_HOBJECT_E_GET_FLAGS_BASE((h))[(i)])
#define  DUK_HOBJECT_A_GET_VALUE(h,i)            (DUK_HOBJECT_A_GET_BASE((h))[(i)])
#define  DUK_HOBJECT_A_GET_VALUE_PTR(h,i)        (&DUK_HOBJECT_A_GET_BASE((h))[(i)])
#define  DUK_HOBJECT_H_GET_INDEX(h,i)            (DUK_HOBJECT_H_GET_BASE((h))[(i)])
#define  DUK_HOBJECT_H_GET_INDEX_PTR(h,i)        (&DUK_HOBJECT_H_GET_BASE((h))[(i)])

#define  DUK_HOBJECT_E_SET_KEY(h,i,k)  do { \
		DUK_HOBJECT_E_GET_KEY((h),(i)) = (k); \
	} while (0)
#define  DUK_HOBJECT_E_SET_VALUE(h,i,v)  do { \
		DUK_HOBJECT_E_GET_VALUE((h),(i)) = (v); \
	} while (0)
#define  DUK_HOBJECT_E_SET_VALUE_TVAL(h,i,v)  do { \
		DUK_HOBJECT_E_GET_VALUE((h),(i)).v = (v); \
	} while (0)
#define  DUK_HOBJECT_E_SET_VALUE_GETTER(h,i,v)  do { \
		DUK_HOBJECT_E_GET_VALUE((h),(i)).a.get = (v); \
	} while (0)
#define  DUK_HOBJECT_E_SET_VALUE_SETTER(h,i,v)  do { \
		DUK_HOBJECT_E_GET_VALUE((h),(i)).a.set = (v); \
	} while (0)
#define  DUK_HOBJECT_E_SET_FLAGS(h,i,f)  do { \
		DUK_HOBJECT_E_GET_FLAGS((h),(i)) = (f); \
	} while (0)
#define  DUK_HOBJECT_A_SET_VALUE(h,i,v)  do { \
		DUK_HOBJECT_A_GET_VALUE((h),(i)) = (v); \
	} while (0)
#define  DUK_HOBJECT_A_SET_VALUE_TVAL(h,i,v)  DUK_HOBJECT_A_SET_VALUE((h),(i),(v))  /* alias for above */
#define  DUK_HOBJECT_H_SET_INDEX(h,i,v)  do { \
		DUK_HOBJECT_H_GET_INDEX((h),(i)) = (v); \
	} while (0)

#define  DUK_HOBJECT_E_SET_FLAG_BITS(h,i,mask)  do { \
		DUK_HOBJECT_E_GET_FLAGS_BASE((h))[(i)] |= (mask); \
	} while (0)

#define  DUK_HOBJECT_E_CLEAR_FLAG_BITS(h,i,mask)  do { \
		DUK_HOBJECT_E_GET_FLAGS_BASE((h))[(i)] &= ~(mask); \
	} while (0)

#define  DUK_HOBJECT_E_SLOT_IS_WRITABLE(h,i)     ((DUK_HOBJECT_E_GET_FLAGS((h),(i)) & DUK_PROPDESC_FLAG_WRITABLE) != 0)
#define  DUK_HOBJECT_E_SLOT_IS_ENUMERABLE(h,i)   ((DUK_HOBJECT_E_GET_FLAGS((h),(i)) & DUK_PROPDESC_FLAG_ENUMERABLE) != 0)
#define  DUK_HOBJECT_E_SLOT_IS_CONFIGURABLE(h,i) ((DUK_HOBJECT_E_GET_FLAGS((h),(i)) & DUK_PROPDESC_FLAG_CONFIGURABLE) != 0)
#define  DUK_HOBJECT_E_SLOT_IS_ACCESSOR(h,i)     ((DUK_HOBJECT_E_GET_FLAGS((h),(i)) & DUK_PROPDESC_FLAG_ACCESSOR) != 0)

#define  DUK_HOBJECT_E_SLOT_SET_WRITABLE(h,i)        DUK_HOBJECT_E_SET_FLAG_BITS((h),(i),DUK_PROPDESC_FLAG_WRITABLE)
#define  DUK_HOBJECT_E_SLOT_SET_ENUMERABLE(h,i)      DUK_HOBJECT_E_SET_FLAG_BITS((h),(i),DUK_PROPDESC_FLAG_ENUMERABLE)
#define  DUK_HOBJECT_E_SLOT_SET_CONFIGURABLE(h,i)    DUK_HOBJECT_E_SET_FLAG_BITS((h),(i),DUK_PROPDESC_FLAG_CONFIGURABLE)
#define  DUK_HOBJECT_E_SLOT_SET_ACCESSOR(h,i)        DUK_HOBJECT_E_SET_FLAG_BITS((h),(i),DUK_PROPDESC_FLAG_ACCESSOR)

#define  DUK_HOBJECT_E_SLOT_CLEAR_WRITABLE(h,i)      DUK_HOBJECT_E_CLEAR_FLAG_BITS((h),(i),DUK_PROPDESC_FLAG_WRITABLE)
#define  DUK_HOBJECT_E_SLOT_CLEAR_ENUMERABLE(h,i)    DUK_HOBJECT_E_CLEAR_FLAG_BITS((h),(i),DUK_PROPDESC_FLAG_ENUMERABLE)
#define  DUK_HOBJECT_E_SLOT_CLEAR_CONFIGURABLE(h,i)  DUK_HOBJECT_E_CLEAR_FLAG_BITS((h),(i),DUK_PROPDESC_FLAG_CONFIGURABLE)
#define  DUK_HOBJECT_E_SLOT_CLEAR_ACCESSOR(h,i)      DUK_HOBJECT_E_CLEAR_FLAG_BITS((h),(i),DUK_PROPDESC_FLAG_ACCESSOR)

#define  DUK_PROPDESC_IS_WRITABLE(p)             (((p)->flags & DUK_PROPDESC_FLAG_WRITABLE) != 0)
#define  DUK_PROPDESC_IS_ENUMERABLE(p)           (((p)->flags & DUK_PROPDESC_FLAG_ENUMERABLE) != 0)
#define  DUK_PROPDESC_IS_CONFIGURABLE(p)         (((p)->flags & DUK_PROPDESC_FLAG_CONFIGURABLE) != 0)
#define  DUK_PROPDESC_IS_ACCESSOR(p)             (((p)->flags & DUK_PROPDESC_FLAG_ACCESSOR) != 0)

#define  DUK_HOBJECT_HASHIDX_UNUSED              0xffffffffU
#define  DUK_HOBJECT_HASHIDX_DELETED             0xfffffffeU

/*
 *  Misc
 */

/* Maximum prototype traversal depth.  Sanity limit which handles e.g.
 * prototype loops (even complex ones like 1->2->3->4->2->3->4->2->3->4).
 */
#define  DUK_HOBJECT_PROTOTYPE_CHAIN_SANITY      10000

/* Maximum traversal depth for "bound function" chains. */
#define  DUK_HOBJECT_BOUND_CHAIN_SANITY          10000

/*
 *  Ecmascript [[Class]]
 */

/* range check not necessary because all 4-bit values are mapped */
#define  DUK_HOBJECT_CLASS_NUMBER_TO_STRIDX(n)  duk_class_number_to_stridx[(n)]

#define  DUK_HOBJECT_GET_CLASS_STRING(heap,h)          \
	DUK_HEAP_GET_STRING( \
		(heap), \
		DUK_HOBJECT_CLASS_NUMBER_TO_STRIDX(DUK_HOBJECT_GET_CLASS_NUMBER((h))) \
	)

/*
 *  Macros for property handling
 */		

/* note: this updates refcounts */
#define  DUK_HOBJECT_SET_PROTOTYPE(thr,h,p)              duk_hobject_set_prototype((thr),(h),(p))

/*
 *  Macros for Ecmascript built-in semantics
 */

#define  DUK_HOBJECT_OBJECT_SEAL(thr,obj)                duk_hobject_object_seal_freeze_helper((thr),(obj),0)
#define  DUK_HOBJECT_OBJECT_FREEZE(htr,obj)              duk_hobject_object_seal_freeze_helper((thr),(obj),1)
#define  DUK_HOBJECT_OBJECT_IS_SEALED(obj)               duk_hobject_object_is_sealed_frozen_helper((obj),0)
#define  DUK_HOBJECT_OBJECT_IS_FROZEN(obj)               duk_hobject_object_is_sealed_frozen_helper((obj),1)
#define  DUK_HOBJECT_OBJECT_PREVENT_EXTENSIONS(vm,obj)   DUK_HOBJECT_CLEAR_EXTENSIBLE((obj))
#define  DUK_HOBJECT_OBJECT_IS_EXTENSIBLE(vm,obj)        DUK_HOBJECT_HAS_EXTENSIBLE((obj))

/*
 *  Resizing and hash behavior
 */

/* Sanity limit on max number of properties (allocated, not necessarily used).
 * This is somewhat arbitrary, but if we're close to 2**32 properties some
 * algorithms will fail (e.g. hash size selection, next prime selection).
 * Also, we use negative array/entry table indices to indicate 'not found',
 * so anything above 0x80000000 will cause trouble now.
 */
#define  DUK_HOBJECT_MAX_PROPERTIES       0x7fffffffU   /* 2**31-1 ~= 2G properties */

/* higher value conserves memory; also note that linear scan is cache friendly */
#define  DUK_HOBJECT_E_USE_HASH_LIMIT     32

/* hash size relative to entries size: for value X, approx. hash_prime(e_size + e_size / X) */
#define  DUK_HOBJECT_H_SIZE_DIVISOR       4  /* hash size approx. 1.25 times entries size */

/* if new_size < L * old_size, resize without abandon check; L = 3-bit fixed point, e.g. 9 -> 9/8 = 112.5% */
#define  DUK_HOBJECT_A_FAST_RESIZE_LIMIT  9  /* 112.5%, i.e. new size less than 12.5% higher -> fast resize */

/* if density < L, abandon array part, L = 3-bit fixed point, e.g. 2 -> 2/8 = 25% */
/* limit is quite low: one array entry is 8 bytes, one normal entry is 4+1+8+4 = 17 bytes (with hash entry) */
#define  DUK_HOBJECT_A_ABANDON_LIMIT      2  /* 25%, i.e. less than 25% used -> abandon */

/* FIXME: where to get align target (now fixed to 4)? */
/* internal align target for props allocation, must be 2*n for some n */
#define  DUK_HOBJECT_ALIGN_TARGET         4

/* controls for minimum entry part growth */
#define  DUK_HOBJECT_E_MIN_GROW_ADD       16
#define  DUK_HOBJECT_E_MIN_GROW_DIVISOR   8  /* 2^3 -> 1/8 = 12.5% min growth */

/* controls for minimum array part growth */
#define  DUK_HOBJECT_A_MIN_GROW_ADD       16
#define  DUK_HOBJECT_A_MIN_GROW_DIVISOR   8  /* 2^3 -> 1/8 = 12.5% min growth */

/* probe sequence */
#define  DUK_HOBJECT_HASH_INITIAL(hash,h_size)  ((hash) % (h_size))
#define  DUK_HOBJECT_HASH_PROBE_STEP(hash)      DUK_UTIL_GET_HASH_PROBE_STEP((hash))

/*
 *  PC-to-line constants
 */

#define  DUK_PC2LINE_SKIP    64

/* maximum length for a SKIP-1 diffstream: 35 bits per entry, rounded up to bytes */
#define  DUK_PC2LINE_MAX_DIFF_LENGTH    (((DUK_PC2LINE_SKIP - 1) * 35 + 7) / 8)

/*
 *  Struct defs
 */

struct duk_propaccessor {
	duk_hobject *get;
	duk_hobject *set;
};

union duk_propvalue {
	duk_tval v;
	duk_propaccessor a;
};

struct duk_propdesc {
	/* read-only values 'lifted' for ease of use */
	int flags;
	duk_hobject *get;
	duk_hobject *set;

	/* for updating (all are set to < 0 for virtual properties) */
	int e_idx;	/* prop index in 'entry part', < 0 if not there */
	int h_idx;	/* prop index in 'hash part', < 0 if not there */
	int a_idx;	/* prop index in 'array part', < 0 if not there */
};

struct duk_hobject {
	duk_heaphdr hdr;

	/*
	 *  'p' contains {key,value,flags} entries, optional array entries, and an
	 *  optional hash lookup table for non-array entries in a single 'sliced'
	 *  allocation:
	 *
	 *    e_size * sizeof(duk_hstring *)   bytes of   entry keys (e_used gc reachable)
	 *    e_size * sizeof(duk_propvalue)   bytes of   entry values (e_used gc reachable)
	 *    e_size * sizeof(duk_u8)          bytes of   entry flags (e_used gc reachable)
	 *    a_size * sizeof(duk_tval)        bytes of   (opt) array values (plain only) (all gc reachable)
	 *    h_size * sizeof(duk_u32)         bytes of   (opt) hash indexes to entries (e_size),
	 *                                                0xffffffffU = unused, 0xfffffffeU = deleted
	 *
	 *  Objects with few keys don't have a hash index; keys are looked up linearly,
	 *  which is cache efficient because the keys are consecutive.  Larger objects
	 *  have a hash index part which contains integer indexes to the entries part.
	 *
	 *  A single allocation reduces memory allocation overhead but requires more
	 *  work when any part needs to be resized.  A sliced allocation for entries
	 *  makes linear key matching faster on most platforms (more locality) and
	 *  skimps on flags size (which would be followed by 3 bytes of padding in
	 *  most architectures if entries were placed in a struct).
	 *
	 *  'p' also contains internal properties distinguished with a non-BMP
	 *  prefix.  Often used properties should be placed early in 'p' whenever
	 *  possible to make accessing them as fast a possible.
	 */

	duk_u8 *p;
	duk_u32 e_size;
	duk_u32 e_used;
	duk_u32 a_size;
	duk_u32 h_size;

	/* prototype: the only internal property lifted outside 'e' as it is so central */
	duk_hobject *prototype;
};

/*
 *  Exposed data
 */

extern duk_u8 duk_class_number_to_stridx[16];

/*
 *  Prototypes
 */

/* alloc and init */
duk_hobject *duk_hobject_alloc(duk_heap *heap, int hobject_flags);
duk_hobject *duk_hobject_alloc_checked(duk_hthread *thr, int hobject_flags);
duk_hcompiledfunction *duk_hcompiledfunction_alloc(duk_heap *heap, int hobject_flags);
duk_hnativefunction *duk_hnativefunction_alloc(duk_heap *heap, int hobject_flags);
duk_hthread *duk_hthread_alloc(duk_heap *heap, int hobject_flags);

/* low-level property functions */
void duk_hobject_find_existing_entry(duk_hobject *obj, duk_hstring *key, int *e_idx, int *h_idx);
duk_tval *duk_hobject_find_existing_entry_tval_ptr(duk_hobject *obj, duk_hstring *key);
duk_tval *duk_hobject_find_existing_array_entry_tval_ptr(duk_hobject *obj, duk_u32 i);

/* core property functions */
int duk_hobject_getprop(duk_hthread *thr, duk_tval *tv_obj, duk_tval *tv_key);
int duk_hobject_putprop(duk_hthread *thr, duk_tval *tv_obj, duk_tval *tv_key, duk_tval *tv_val, int throw_flag);
int duk_hobject_delprop(duk_hthread *thr, duk_tval *tv_obj, duk_tval *tv_key, int throw_flag);
int duk_hobject_hasprop(duk_hthread *thr, duk_tval *tv_obj, duk_tval *tv_key);

/* internal property functions */
int duk_hobject_delprop_raw(duk_hthread *thr, duk_hobject *obj, duk_hstring *key, int throw_flag);
int duk_hobject_hasprop_raw(duk_hthread *thr, duk_hobject *obj, duk_hstring *key);
void duk_hobject_define_property_internal(duk_hthread *thr, duk_hobject *obj, duk_hstring *key, int propflags);
void duk_hobject_set_length(duk_hthread *thr, duk_hobject *obj, duk_u32 length);
void duk_hobject_set_length_zero(duk_hthread *thr, duk_hobject *obj);
duk_u32 duk_hobject_get_length(duk_hthread *thr, duk_hobject *obj);

/* Object built-in methods */
int duk_hobject_object_define_property(duk_context *ctx);
int duk_hobject_object_define_properties(duk_context *ctx);
int duk_hobject_object_get_own_property_descriptor(duk_context *ctx);
void duk_hobject_object_seal_freeze_helper(duk_hthread *thr, duk_hobject *obj, int freeze);
int duk_hobject_object_is_sealed_frozen_helper(duk_hobject *obj, int is_frozen);
int duk_hobject_object_ownprop_helper(duk_context *ctx, int required_desc_flags);

/* internal properties */
int duk_hobject_get_internal_value(duk_heap *heap, duk_hobject *obj, duk_tval *tv);
duk_hstring *duk_hobject_get_internal_value_string(duk_heap *heap, duk_hobject *obj);
	
/* hobject management functions */
void duk_hobject_compact_props(duk_hthread *thr, duk_hobject *obj);

/* enumeration */
void duk_hobject_enumerator_create(duk_context *ctx, int enum_flags);
int duk_hobject_get_enumerated_keys(duk_context *ctx, int enum_flags);
int duk_hobject_enumerator_next(duk_context *ctx, int get_value);

/* macros */
void duk_hobject_set_prototype(duk_hthread *thr, duk_hobject *h, duk_hobject *p);

/* finalization */
void duk_hobject_run_finalizer(duk_hthread *thr, duk_hobject *obj);

/* pc2line */
void duk_hobject_pc2line_pack(duk_hthread *thr, duk_compiler_instr *instrs, size_t length);
duk_u32 duk_hobject_pc2line_query(duk_hbuffer_fixed *buf, int pc);

/* misc */	
int duk_hobject_prototype_chain_contains(duk_hthread *thr, duk_hobject *h, duk_hobject *p);

#endif  /* DUK_HOBJECT_H_INCLUDED */

#line 1 "duk_hcompiledfunction.h"
/*
 *  Heap compiled function (Ecmascript function) representation.
 *
 *  There is a single data buffer containing the Ecmascript function's
 *  bytecode, constants, and inner functions.
 */

#ifndef DUK_HCOMPILEDFUNCTION_H_INCLUDED
#define DUK_HCOMPILEDFUNCTION_H_INCLUDED

/*
 *  Accessor macros for function specific data areas
 */

/* Note: assumes 'data' is always a fixed buffer */
#define  DUK_HCOMPILEDFUNCTION_GET_BUFFER_BASE(h)  \
	DUK_HBUFFER_FIXED_GET_DATA_PTR((duk_hbuffer_fixed *) (h)->data)

#define  DUK_HCOMPILEDFUNCTION_GET_CONSTS_BASE(h)  \
	((duk_tval *) DUK_HCOMPILEDFUNCTION_GET_BUFFER_BASE((h)))

#define  DUK_HCOMPILEDFUNCTION_GET_FUNCS_BASE(h)  \
	((h)->funcs)

#define  DUK_HCOMPILEDFUNCTION_GET_CODE_BASE(h)  \
	((h)->bytecode)

#define  DUK_HCOMPILEDFUNCTION_GET_CONSTS_END(h)  \
	((duk_tval *) DUK_HCOMPILEDFUNCTION_GET_FUNCS_BASE((h)))

#define  DUK_HCOMPILEDFUNCTION_GET_FUNCS_END(h)  \
	((duk_hobject **) DUK_HCOMPILEDFUNCTION_GET_CODE_BASE((h)))

#define  DUK_HCOMPILEDFUNCTION_GET_CODE_END(h)  \
	((duk_instr *) (DUK_HBUFFER_FIXED_GET_DATA_PTR((duk_hbuffer_fixed *) (h)->data) + \
	                DUK_HBUFFER_GET_SIZE((h)->data)))

#define  DUK_HCOMPILEDFUNCTION_GET_CONSTS_SIZE(h)  \
	( \
	 (size_t) \
	 ( \
	   ((duk_u8 *) DUK_HCOMPILEDFUNCTION_GET_CONSTS_END((h))) - \
	   ((duk_u8 *) DUK_HCOMPILEDFUNCTION_GET_CONSTS_BASE((h))) \
	 ) \
	)

#define  DUK_HCOMPILEDFUNCTION_GET_FUNCS_SIZE(h)  \
	( \
	 (size_t) \
	 ( \
	   ((duk_u8 *) DUK_HCOMPILEDFUNCTION_GET_FUNCS_END((h))) - \
	   ((duk_u8 *) DUK_HCOMPILEDFUNCTION_GET_FUNCS_BASE((h))) \
	 ) \
	)

#define  DUK_HCOMPILEDFUNCTION_GET_CODE_SIZE(h)  \
	( \
	 (size_t) \
	 ( \
	   ((duk_u8 *) DUK_HCOMPILEDFUNCTION_GET_CODE_END((h))) - \
	   ((duk_u8 *) DUK_HCOMPILEDFUNCTION_GET_CODE_BASE((h))) \
	 ) \
	)

#define  DUK_HCOMPILEDFUNCTION_GET_CONSTS_COUNT(h)  \
	((size_t) (DUK_HCOMPILEDFUNCTION_GET_CONSTS_SIZE((h)) / sizeof(duk_tval)))

#define  DUK_HCOMPILEDFUNCTION_GET_FUNCS_COUNT(h)  \
	((size_t) (DUK_HCOMPILEDFUNCTION_GET_FUNCS_SIZE((h)) / sizeof(duk_hobject *)))

#define  DUK_HCOMPILEDFUNCTION_GET_CODE_COUNT(h)  \
	((size_t) (DUK_HCOMPILEDFUNCTION_GET_CODE_SIZE((h)) / sizeof(duk_instr)))


/*
 *  Main struct
 */

struct duk_hcompiledfunction {
	/* shared object part */
	duk_hobject obj;

	/*
	 *  Pointers to function data area for faster access.  Function
	 *  data is a buffer shared between all closures of the same
	 *  "template" function.  The data buffer is always fixed (non-
	 *  dynamic, hence stable), with a layout as follows:
	 *
	 *    constants (duk_tval)
	 *    inner functions (duk_hobject *)
	 *    bytecode (duk_instr)
	 *
	 *  Note: bytecode end address can be computed from 'data' buffer
	 *  size.  It is not strictly necessary functionally, assuming
	 *  bytecode never jumps outside its allocated area.  However,
	 *  it's a safety/robustness feature for avoiding the chance of
	 *  executing random data as bytecode due to a compiler error.
	 *
	 *  Note: values in the data buffer must be incref'd (they will
	 *  be decref'd on release) for every compiledfunction referring
	 *  to the 'data' element.
	 */

	duk_hbuffer *data;    /* data area, fixed allocation, stable data ptrs */

	/* no need for constants pointer */
	duk_hobject **funcs;
	duk_instr *bytecode;

	/*
	 *  'nregs' registers are allocated on function entry, at most 'nargs'
	 *  are initialized to arguments, and the rest to undefined.  Arguments
	 *  above 'nregs' are not mapped to registers.  All registers in the
	 *  active stack range must be initialized because they are GC reachable.
	 *  'nargs' is needed so that if the function is given more than 'nargs'
	 *  arguments, the additional arguments do not 'clobber' registers
	 *  beyond 'nregs' which must be consistently initialized to undefined.
	 *
	 *  Usually there is no need to know which registers are mapped to
	 *  local variables.  Registers may be allocated to variable in any
	 *  way (even including gaps).  However, a register-variable mapping
	 *  must be the same for the duration of the function execution and
	 *  the register cannot be used for anything else.
	 *
	 *  When looking up variables by name, the '_varmap' map is used.
	 *  When an activation closes, registers mapped to arguments are
	 *  copied into the environment record based on the same map.  The
	 *  reverse map (from register to variable) is not currently needed
	 *  at run time, except for debugging, so it is not maintained.
	 */

	duk_u16 nregs;                /* regs to allocate */
	duk_u16 nargs;                /* number of arguments allocated to regs */

	/*
	 *  Additional control information is placed into the object itself
	 *  as internal properties to avoid unnecessary fields for the
	 *  majority of functions.  The compiler tries to omit internal
	 *  control fields when possible.
	 *
	 *  Function templates:
	 *
	 *    {
	 *      _varmap: { "arg1": 0, "arg2": 1, "varname": 2 },
	 *      _formals: [ "arg1", "arg2" ],
	 *      _name: "func",    // declaration, named function expressions
	 *      _source: "function func(arg1, arg2) { ... }",
	 *      _pc2line: <debug info for pc-to-line mapping>,
	 *      _filename: <debug info for creating nice errors>
	 *    }
	 *
	 *  Function instances:
	 *
	 *    {
	 *      length: 2,
	 *      prototype: { constructor: <func> },
	 *      caller: <thrower>,
	 *      arguments: <thrower>,
	 *      _varmap: { "arg1": 0, "arg2": 1, "varname": 2 },
	 *      _formals: [ "arg1", "arg2" ],
	 *      _name: "func",    // declaration, named function expressions
	 *      _source: "function func(arg1, arg2) { ... }",
	 *      _pc2line: <debug info for pc-to-line mapping>,
	 *      _filename: <debug info for creating nice errors>
	 *      _varenv: <variable environment of closure>,
	 *      _lexenv: <lexical environment of closure (if differs from _varenv)>
	 *    }
	 *
	 *  More detailed description of these properties can be found
	 *  in the documentation.
	 */
};

#endif  /* DUK_HCOMPILEDFUNCTION_H_INCLUDED */

#line 1 "duk_hnativefunction.h"
/*
 *  Heap native function representation.
 */

#ifndef DUK_HNATIVEFUNCTION_H_INCLUDED
#define DUK_HNATIVEFUNCTION_H_INCLUDED

#define  DUK_HNATIVEFUNCTION_NARGS_VARARGS  ((duk_i16) -1)
#define  DUK_HNATIVEFUNCTION_NARGS_MAX      ((duk_i16) 0x7fff)

struct duk_hnativefunction {
	/* shared object part */
	duk_hobject obj;

	duk_c_function func;
	duk_i16 nargs;

	/* XXX: there is a nice 16-bit space here.  What to put here?
	 *
	 * One alternative: put a 16-bit 'magic' (or 'salt') here, and allow
	 * C code to get the 'magic' value of their wrapping duk_hnativefunction.
	 * This would allow the same C functions to be used internally, while
	 * flags and small parameter fields could be given through the 'magic'
	 * value.  For instance, there are a bunch of setter/getter functions
	 * in the Date built-in which only differ in a few flags.
	 */

	/* Note: cannot place nargs/magic into the heaphdr flags, because
	 * duk_hobject takes almost all flags already (and needs the spare).
	 */
};

#endif  /* DUK_HNATIVEFUNCTION_H_INCLUDED */

#line 1 "duk_hthread.h"
/*
 *  Heap thread object representation.
 *
 *  duk_hthread is also the 'context' (duk_context) for exposed APIs
 *  which mostly operate on the topmost frame of the value stack.
 */

#ifndef DUK_HTHREAD_H_INCLUDED
#define DUK_HTHREAD_H_INCLUDED

/* already included: duk_bittypes.h */
/* already included: duk_forwdecl.h */
#line 1 "duk_builtins.h"
/*
 *  Automatically generated by genbuiltins.py, do not edit!
 */

#ifndef __DUK_BUILTINS_H
#define __DUK_BUILTINS_H 1

extern duk_c_function duk_builtin_native_functions[];

extern char duk_builtins_data[];

#define  DUK_BUILTINS_DATA_LENGTH                                      1163

#define  DUK_BIDX_GLOBAL                                               0
#define  DUK_BIDX_GLOBAL_ENV                                           1
#define  DUK_BIDX_OBJECT_CONSTRUCTOR                                   2
#define  DUK_BIDX_OBJECT_PROTOTYPE                                     3
#define  DUK_BIDX_FUNCTION_CONSTRUCTOR                                 4
#define  DUK_BIDX_FUNCTION_PROTOTYPE                                   5
#define  DUK_BIDX_ARRAY_CONSTRUCTOR                                    6
#define  DUK_BIDX_ARRAY_PROTOTYPE                                      7
#define  DUK_BIDX_STRING_CONSTRUCTOR                                   8
#define  DUK_BIDX_STRING_PROTOTYPE                                     9
#define  DUK_BIDX_BOOLEAN_CONSTRUCTOR                                  10
#define  DUK_BIDX_BOOLEAN_PROTOTYPE                                    11
#define  DUK_BIDX_NUMBER_CONSTRUCTOR                                   12
#define  DUK_BIDX_NUMBER_PROTOTYPE                                     13
#define  DUK_BIDX_DATE_CONSTRUCTOR                                     14
#define  DUK_BIDX_DATE_PROTOTYPE                                       15
#define  DUK_BIDX_REGEXP_CONSTRUCTOR                                   16
#define  DUK_BIDX_REGEXP_PROTOTYPE                                     17
#define  DUK_BIDX_ERROR_CONSTRUCTOR                                    18
#define  DUK_BIDX_ERROR_PROTOTYPE                                      19
#define  DUK_BIDX_EVAL_ERROR_CONSTRUCTOR                               20
#define  DUK_BIDX_EVAL_ERROR_PROTOTYPE                                 21
#define  DUK_BIDX_RANGE_ERROR_CONSTRUCTOR                              22
#define  DUK_BIDX_RANGE_ERROR_PROTOTYPE                                23
#define  DUK_BIDX_REFERENCE_ERROR_CONSTRUCTOR                          24
#define  DUK_BIDX_REFERENCE_ERROR_PROTOTYPE                            25
#define  DUK_BIDX_SYNTAX_ERROR_CONSTRUCTOR                             26
#define  DUK_BIDX_SYNTAX_ERROR_PROTOTYPE                               27
#define  DUK_BIDX_TYPE_ERROR_CONSTRUCTOR                               28
#define  DUK_BIDX_TYPE_ERROR_PROTOTYPE                                 29
#define  DUK_BIDX_URI_ERROR_CONSTRUCTOR                                30
#define  DUK_BIDX_URI_ERROR_PROTOTYPE                                  31
#define  DUK_BIDX_MATH                                                 32
#define  DUK_BIDX_JSON                                                 33
#define  DUK_BIDX_TYPE_ERROR_THROWER                                   34
#define  DUK_BIDX_DUK                                                  35
#define  DUK_BIDX_THREAD_PROTOTYPE                                     36
#define  DUK_BIDX_DOUBLE_ERROR                                         37

#define  DUK_NUM_BUILTINS                                              38

#endif  /* __DUK_BUILTINS_H */
#line 14 "duk_hthread.h"

/*
 *  Stack constants
 */

#define  DUK_VALSTACK_GROW_STEP          128     /* roughly 1 kiB */
#define  DUK_VALSTACK_SHRINK_THRESHOLD   256     /* roughly 2 kiB */
#define  DUK_VALSTACK_SHRINK_SPARE       64      /* roughly 0.5 kiB */
#define  DUK_VALSTACK_INITIAL_SIZE       64      /* roughly 0.5 kiB -> but rounds up to DUK_VALSTACK_GROW_STEP in practice */
#define  DUK_VALSTACK_INTERNAL_EXTRA     64      /* internal extra elements assumed on function entry,
                                                  * always added to user-defined 'extra' for e.g. the
                                                  * duk_check_stack() call.
                                                  */
#define  DUK_VALSTACK_DEFAULT_MAX        1000000

#define  DUK_CALLSTACK_GROW_STEP         8       /* roughly 256 bytes */
#define  DUK_CALLSTACK_SHRINK_THRESHOLD  16      /* roughly 512 bytes */
#define  DUK_CALLSTACK_SHRINK_SPARE      8       /* roughly 256 bytes */
#define  DUK_CALLSTACK_INITIAL_SIZE      8
#define  DUK_CALLSTACK_DEFAULT_MAX       10000

#define  DUK_CATCHSTACK_GROW_STEP         4      /* roughly 64 bytes */
#define  DUK_CATCHSTACK_SHRINK_THRESHOLD  8      /* roughly 128 bytes */
#define  DUK_CATCHSTACK_SHRINK_SPARE      4      /* roughly 64 bytes */
#define  DUK_CATCHSTACK_INITIAL_SIZE      4
#define  DUK_CATCHSTACK_DEFAULT_MAX       10000

/*
 *  Activation defines
 */

#define  DUK_ACT_FLAG_STRICT          (1 << 0)  /* function executes in strict mode */
#define  DUK_ACT_FLAG_TAILCALLED      (1 << 1)  /* activation has tailcalled one or more times */
#define  DUK_ACT_FLAG_CONSTRUCT       (1 << 2)  /* function executes as a constructor (called via "new") */
#define  DUK_ACT_FLAG_PREVENT_YIELD   (1 << 3)  /* activation prevents yield (native call or "new") */
#define  DUK_ACT_FLAG_DIRECT_EVAL     (1 << 4)  /* activation is a direct eval call */

/*
 *  Catcher defines
 */

/* flags field: LLLLLLFT, L = label (24 bits), F = flags (4 bits), T = type (4 bits) */
#define  DUK_CAT_TYPE_MASK            0x0000000f
#define  DUK_CAT_TYPE_BITS            4
#define  DUK_CAT_LABEL_MASK           0xffffff00
#define  DUK_CAT_LABEL_BITS           24
#define  DUK_CAT_LABEL_SHIFT          8

#define  DUK_CAT_FLAG_CATCH_ENABLED          (1 << 4)   /* catch part will catch */
#define  DUK_CAT_FLAG_FINALLY_ENABLED        (1 << 5)   /* finally part will catch */
#define  DUK_CAT_FLAG_CATCH_BINDING_ENABLED  (1 << 6)   /* request to create catch binding */
#define  DUK_CAT_FLAG_LEXENV_ACTIVE          (1 << 7)   /* catch or with binding is currently active */

#define  DUK_CAT_TYPE_UNKNOWN         0
#define  DUK_CAT_TYPE_TCF             1
#define  DUK_CAT_TYPE_LABEL           2

#define  DUK_CAT_GET_TYPE(c)          ((c)->flags & DUK_CAT_TYPE_MASK)
#define  DUK_CAT_GET_LABEL(c)         (((c)->flags & DUK_CAT_LABEL_MASK) >> DUK_CAT_LABEL_SHIFT)

#define  DUK_CAT_HAS_CATCH_ENABLED(c)           ((c)->flags & DUK_CAT_FLAG_CATCH_ENABLED)
#define  DUK_CAT_HAS_FINALLY_ENABLED(c)         ((c)->flags & DUK_CAT_FLAG_FINALLY_ENABLED)
#define  DUK_CAT_HAS_CATCH_BINDING_ENABLED(c)   ((c)->flags & DUK_CAT_FLAG_CATCH_BINDING_ENABLED)
#define  DUK_CAT_HAS_LEXENV_ACTIVE(c)           ((c)->flags & DUK_CAT_FLAG_LEXENV_ACTIVE)

#define  DUK_CAT_SET_CATCH_ENABLED(c)    do { \
		(c)->flags |= DUK_CAT_FLAG_CATCH_ENABLED; \
	} while (0)
#define  DUK_CAT_SET_FINALLY_ENABLED(c)  do { \
		(c)->flags |= DUK_CAT_FLAG_FINALLY_ENABLED; \
	} while (0)
#define  DUK_CAT_SET_CATCH_BINDING_ENABLED(c)    do { \
		(c)->flags |= DUK_CAT_FLAG_CATCH_BINDING_ENABLED; \
	} while (0)
#define  DUK_CAT_SET_LEXENV_ACTIVE(c)    do { \
		(c)->flags |= DUK_CAT_FLAG_LEXENV_ACTIVE; \
	} while (0)

#define  DUK_CAT_CLEAR_CATCH_ENABLED(c)    do { \
		(c)->flags &= ~DUK_CAT_FLAG_CATCH_ENABLED; \
	} while (0)
#define  DUK_CAT_CLEAR_FINALLY_ENABLED(c)  do { \
		(c)->flags &= ~DUK_CAT_FLAG_FINALLY_ENABLED; \
	} while (0)
#define  DUK_CAT_CLEAR_CATCH_BINDING_ENABLED(c)    do { \
		(c)->flags &= ~DUK_CAT_FLAG_CATCH_BINDING_ENABLED; \
	} while (0)
#define  DUK_CAT_CLEAR_LEXENV_ACTIVE(c)    do { \
		(c)->flags &= ~DUK_CAT_FLAG_LEXENV_ACTIVE; \
	} while (0)

/*
 *  Thread defines
 */

#define  DUK_HTHREAD_GET_STRING(thr,idx)          ((thr)->strs[(idx)])

#define  DUK_HTHREAD_GET_CURRENT_ACTIVATION(thr)  (&(thr)->callstack[(thr)->callstack_top - 1])

/* values for the state field */
#define  DUK_HTHREAD_STATE_INACTIVE     1   /* thread not currently running */
#define  DUK_HTHREAD_STATE_RUNNING      2   /* thread currently running (only one at a time) */
#define  DUK_HTHREAD_STATE_RESUMED      3   /* thread resumed another thread (active but not running) */
#define  DUK_HTHREAD_STATE_YIELDED      4   /* thread has yielded */
#define  DUK_HTHREAD_STATE_TERMINATED   5   /* thread has terminated */

/*
 *  Struct defines
 */

/* Note: it's nice if size is 2^N (now 32 bytes on 32 bit) */
struct duk_activation {
	int flags;
	duk_hobject *func;      /* function being executed; for bound function calls, this is the final, real function */
	duk_hobject *var_env;   /* current variable environment (may be NULL if delayed) */
	duk_hobject *lex_env;   /* current lexical environment (may be NULL if delayed) */
	int pc;                 /* next instruction to execute */

	/* Current 'this' binding is the value just below idx_bottom */

	/* These following are only used for book-keeping of Ecmascript-initiated
	 * calls, to allow returning to an Ecmascript function properly.
	 *
	 * Note: idx_bottom is always set, while idx_retval is only applicable for
	 * activations below the topmost one.  Currently idx_retval for the topmost
	 * activation is considered garbage (and it not initialized on entry or
	 * cleared on return; may contain previous or garbage values).
	 */

	int idx_bottom;         /* Bottom of valstack for this activation, used to reset
	                         * valstack_bottom on return; index is absolute.
	                         * Note: idx_top not needed because top is set to 'nregs'
	                         * always when returning to an Ecmascript activation.
	                         */
	int idx_retval;         /* Return value when returning to this activation
	                         * (points to caller reg, not callee reg); index is absolute
	                         * Note: only set if activation is -not topmost-.
	                         */

	/* Note: earlier, 'this' binding was handled with an index to the
	 * (calling) valstack.  This works for everything except tail
	 * calls, which must not "cumulate" valstack temps.
	 */

	int unused1;  /* pad to 2^N */
};

/* Note: it's nice if size is 2^N (not 4x4 = 16 bytes on 32 bit) */
struct duk_catcher {
	int flags;               /* type and control flags */
	int callstack_index;     /* callstack index of related activation */
	int pc_base;             /* resume execution from pc_base or pc_base+1 */
	int idx_base;            /* idx_base and idx_base+1 get completion value and type */
	duk_hstring *h_varname;  /* borrowed reference to catch variable name (or NULL if none) */
	                         /* (reference is valid as long activation exists) */
};

struct duk_hthread {
	/* shared object part */
	duk_hobject obj;

	/* backpointers */
	duk_heap *heap;

	/* current strictness flag: affects API calls */
	duk_u8 strict;
	duk_u8 state;
	duk_u8 unused1;
	duk_u8 unused2;

	/* sanity limits */
	int valstack_max;
	int callstack_max;
	int catchstack_max;

	/* XXX: valstack, callstack, and catchstack are currently assumed
	 * to have non-NULL pointers.  Relaxing this would not lead to big
	 * benefits (except perhaps for terminated threads).
	 */

	/* value stack: these are expressed as pointers for faster stack manipulation */
	duk_tval *valstack;			/* start of valstack allocation */
	duk_tval *valstack_end;			/* end of valstack allocation (exclusive) */
	duk_tval *valstack_bottom;		/* bottom of current frame */
	duk_tval *valstack_top;			/* top of current frame (exclusive) */

	/* call stack */
	duk_activation *callstack;
	unsigned int callstack_size;		/* allocation size */
	unsigned int callstack_top;		/* next to use, highest used is top - 1 */
	unsigned int callstack_preventcount;	/* number of activation records in callstack preventing a yield */

	/* catch stack */
	duk_catcher *catchstack;
	unsigned int catchstack_size;		/* allocation size */
	unsigned int catchstack_top;		/* next to use, highest used is top - 1 */

	/* yield/resume book-keeping */
	duk_hthread *resumer;			/* who resumed us (if any) */

	/* Builtin-objects; may be shared with other threads, copies of
	 * pointers in duk_heap.  This is rather expensive, currently
	 * 38x4 = 152 bytes.
	 */
	duk_hobject *builtins[DUK_NUM_BUILTINS];

	/* convenience copies from heap/vm for faster access */
	duk_hstring **strs;			/* (from duk_heap) */
};

/*
 *  Prototypes
 */

void duk_hthread_copy_builtin_objects(duk_hthread *thr_from, duk_hthread *thr_to);
void duk_hthread_create_builtin_objects(duk_hthread *thr);
int duk_hthread_init_stacks(duk_heap *heap, duk_hthread *thr);
void duk_hthread_terminate(duk_hthread *thr);

void duk_hthread_callstack_grow(duk_hthread *thr);
void duk_hthread_callstack_shrink_check(duk_hthread *thr);
void duk_hthread_callstack_unwind(duk_hthread *thr, int new_top);
void duk_hthread_catchstack_grow(duk_hthread *thr);
void duk_hthread_catchstack_shrink_check(duk_hthread *thr);
void duk_hthread_catchstack_unwind(duk_hthread *thr, int new_top);

duk_activation *duk_hthread_get_current_activation(duk_hthread *thr);

#endif  /* DUK_HTHREAD_H_INCLUDED */

#line 1 "duk_hbuffer.h"
/*
 *  Heap buffer representation.
 *
 *  Heap allocated user data buffer which is either:
 *
 *    1. A fixed size buffer (data follows header statically)
 *    2. A dynamic size buffer (data pointer follows header)
 *
 *  The data pointer for a variable size buffer of zero size may be NULL.
 */

#ifndef DUK_HBUFFER_H_INCLUDED
#define DUK_HBUFFER_H_INCLUDED

/* already included: duk_bittypes.h */
/* already included: duk_forwdecl.h */

#define  DUK_HBUFFER_FLAG_DYNAMIC        DUK_HEAPHDR_USER_FLAG(0)  /* buffer is resizable */

#define  DUK_HBUFFER_HAS_DYNAMIC(x)      DUK_HEAPHDR_CHECK_FLAG_BITS(&(x)->hdr, DUK_HBUFFER_FLAG_DYNAMIC)

#define  DUK_HBUFFER_SET_DYNAMIC(x)      DUK_HEAPHDR_SET_FLAG_BITS(&(x)->hdr, DUK_HBUFFER_FLAG_DYNAMIC)

#define  DUK_HBUFFER_CLEAR_DYNAMIC(x)    DUK_HEAPHDR_CLEAR_FLAG_BITS(&(x)->hdr, DUK_HBUFFER_FLAG_DYNAMIC)

#define  DUK_HBUFFER_FIXED_GET_DATA_PTR(x)         ((duk_u8 *) (&((x)->fixed_data[0])))

#define  DUK_HBUFFER_DYNAMIC_GET_ALLOC_SIZE(x)     ((x)->usable_size + 1)
#define  DUK_HBUFFER_DYNAMIC_GET_USABLE_SIZE(x)    ((x)->usable_size)
#define  DUK_HBUFFER_DYNAMIC_GET_SPARE_SIZE(x)     ((x)->usable_size - (x)->size)
#define  DUK_HBUFFER_DYNAMIC_GET_CURR_DATA_PTR(x)  ((x)->curr_alloc)

/* gets the actual buffer contents which matches the current allocation size
 * (may be NULL for zero size dynamic buffer)
 */
#define  DUK_HBUFFER_GET_DATA_PTR(x)  ( \
	DUK_HBUFFER_HAS_DYNAMIC((x)) ? \
		DUK_HBUFFER_DYNAMIC_GET_CURR_DATA_PTR((duk_hbuffer_dynamic *) (x)) : \
		DUK_HBUFFER_FIXED_GET_DATA_PTR((duk_hbuffer_fixed *) (x)) \
	)

/* gets the current user visible size, without accounting for a dynamic
 * buffer's "spare" (= usable size).
 */
#define  DUK_HBUFFER_GET_SIZE(x)         ((x)->size)

#define  DUK_HBUFFER_SET_SIZE(x,val)  do { \
		(x)->size = (val); \
	} while (0)

/* growth parameters */
#define  DUK_HBUFFER_SPARE_ADD      16
#define  DUK_HBUFFER_SPARE_DIVISOR  16   /* 2^4 -> 1/16 = 6.25% spare */

struct duk_hbuffer {
	duk_heaphdr hdr;

	/* it's not strictly necessary to track the current size, but
	 * it is useful for writing robust native code.
	 */

	size_t size;  /* current size (not counting a dynamic buffer's "spare") */

	/*
	 *  Data following the header depends on the DUK_HBUFFER_FLAG_DYNAMIC
	 *  flag.
	 *
	 *  If the flag is clear (the buffer is a fixed size one), the buffer
	 *  data follows the header directly, consisting of 'size' bytes,
	 *  followed by a zero byte for robustness.
	 *
	 *  If the flag is set, the actual buffer is allocated separately, and
	 *  a few control fields follow the header.  Specifically:
	 *
	 *    - a "void *" pointing to the current allocation
	 *    - a size_t indicating the full allocated size (always >= 'size')
	 */
};

struct duk_hbuffer_fixed {
	duk_heaphdr hdr;
	size_t size;

#ifdef DUK_USE_STRUCT_HACK
	duk_u8 fixed_data[0];
#else
	duk_u8 fixed_data[];
#endif
};

struct duk_hbuffer_dynamic {
	duk_heaphdr hdr;
	size_t size;

	void *curr_alloc;  /* may be NULL if usable_size == 0 */
	size_t usable_size;

	/* alloc size is usable_size + 1; a zero byte always follows the buffer */
};

/*
 *  Prototypes
 */

duk_hbuffer *duk_hbuffer_alloc(duk_heap *heap, size_t size, int dynamic);

/* dynamic buffer ops */
void duk_hbuffer_resize(duk_hthread *thr, duk_hbuffer_dynamic *buf, size_t new_size, size_t new_usable_size);
void duk_hbuffer_reset(duk_hthread *thr, duk_hbuffer_dynamic *buf);
void duk_hbuffer_compact(duk_hthread *thr, duk_hbuffer_dynamic *buf);
void duk_hbuffer_append_bytes(duk_hthread *thr, duk_hbuffer_dynamic *buf, duk_u8 *data, size_t length);
void duk_hbuffer_append_byte(duk_hthread *thr, duk_hbuffer_dynamic *buf, duk_u8 byte);
size_t duk_hbuffer_append_cstring(duk_hthread *thr, duk_hbuffer_dynamic *buf, const char *str);
size_t duk_hbuffer_append_hstring(duk_hthread *thr, duk_hbuffer_dynamic *buf, duk_hstring *str);
size_t duk_hbuffer_append_xutf8(duk_hthread *thr, duk_hbuffer_dynamic *buf, duk_u32 codepoint);
size_t duk_hbuffer_append_cesu8(duk_hthread *thr, duk_hbuffer_dynamic *buf, duk_u32 codepoint);
void duk_hbuffer_append_native_u32(duk_hthread *thr, duk_hbuffer_dynamic *buf, duk_u32 val);
void duk_hbuffer_insert_bytes(duk_hthread *thr, duk_hbuffer_dynamic *buf, size_t offset, duk_u8 *data, size_t length);
void duk_hbuffer_insert_byte(duk_hthread *thr, duk_hbuffer_dynamic *buf, size_t offset, duk_u8 byte);
size_t duk_hbuffer_insert_cstring(duk_hthread *thr, duk_hbuffer_dynamic *buf, size_t offset, const char *str);
size_t duk_hbuffer_insert_hstring(duk_hthread *thr, duk_hbuffer_dynamic *buf, size_t offset, duk_hstring *str);
size_t duk_hbuffer_insert_xutf8(duk_hthread *thr, duk_hbuffer_dynamic *buf, size_t offset, duk_u32 codepoint);
size_t duk_hbuffer_insert_cesu8(duk_hthread *thr, duk_hbuffer_dynamic *buf, size_t offset, duk_u32 codepoint);
void duk_hbuffer_remove_slice(duk_hthread *thr, duk_hbuffer_dynamic *buf, size_t offset, size_t length);
void duk_hbuffer_insert_slice(duk_hthread *thr, duk_hbuffer_dynamic *buf, size_t dst_offset, size_t src_offset, size_t length);
void duk_hbuffer_append_slice(duk_hthread *thr, duk_hbuffer_dynamic *buf, size_t src_offset, size_t length);

#endif  /* DUK_HBUFFER_H_INCLUDED */

#line 1 "duk_heap.h"
/*
 *  Heap structure.
 *
 *  Heap contains allocated heap objects, interned strings, and built-in
 *  strings for one or more threads.
 */

#ifndef DUK_HEAP_H_INCLUDED
#define DUK_HEAP_H_INCLUDED

/* already included: duk_bittypes.h */
/* already included: duk_forwdecl.h */
/* already included: duk_strings.h */

/* alloc function typedefs in duktape.h */

/*
 *  Heap flags
 */

#define  DUK_HEAP_FLAG_MARKANDSWEEP_RUNNING                     (1 << 0)  /* mark-and-sweep is currently running */
#define  DUK_HEAP_FLAG_MARKANDSWEEP_RECLIMIT_REACHED            (1 << 1)  /* mark-and-sweep marking reached a recursion limit and must use multi-pass marking */
#define  DUK_HEAP_FLAG_REFZERO_FREE_RUNNING                     (1 << 2)  /* refcount code is processing refzero list */

#define  _DUK_HEAP_HAS_FLAGS(heap,bits)               ((heap)->flags & (bits))
#define  _DUK_HEAP_SET_FLAGS(heap,bits)  do { \
		(heap)->flags |= (bits); \
	} while (0)
#define  _DUK_HEAP_CLEAR_FLAGS(heap,bits)  do { \
		(heap)->flags &= ~(bits); \
	} while (0)

#define  DUK_HEAP_HAS_MARKANDSWEEP_RUNNING(heap)            _DUK_HEAP_HAS_FLAGS((heap), DUK_HEAP_FLAG_MARKANDSWEEP_RUNNING)
#define  DUK_HEAP_HAS_MARKANDSWEEP_RECLIMIT_REACHED(heap)   _DUK_HEAP_HAS_FLAGS((heap), DUK_HEAP_FLAG_MARKANDSWEEP_RECLIMIT_REACHED)
#define  DUK_HEAP_HAS_REFZERO_FREE_RUNNING(heap)            _DUK_HEAP_HAS_FLAGS((heap), DUK_HEAP_FLAG_REFZERO_FREE_RUNNING)

#define  DUK_HEAP_SET_MARKANDSWEEP_RUNNING(heap)            _DUK_HEAP_SET_FLAGS((heap), DUK_HEAP_FLAG_MARKANDSWEEP_RUNNING)
#define  DUK_HEAP_SET_MARKANDSWEEP_RECLIMIT_REACHED(heap)   _DUK_HEAP_SET_FLAGS((heap), DUK_HEAP_FLAG_MARKANDSWEEP_RECLIMIT_REACHED)
#define  DUK_HEAP_SET_REFZERO_FREE_RUNNING(heap)            _DUK_HEAP_SET_FLAGS((heap), DUK_HEAP_FLAG_REFZERO_FREE_RUNNING)

#define  DUK_HEAP_CLEAR_MARKANDSWEEP_RUNNING(heap)          _DUK_HEAP_CLEAR_FLAGS((heap), DUK_HEAP_FLAG_MARKANDSWEEP_RUNNING)
#define  DUK_HEAP_CLEAR_MARKANDSWEEP_RECLIMIT_REACHED(heap) _DUK_HEAP_CLEAR_FLAGS((heap), DUK_HEAP_FLAG_MARKANDSWEEP_RECLIMIT_REACHED)
#define  DUK_HEAP_CLEAR_REFZERO_FREE_RUNNING(heap)          _DUK_HEAP_CLEAR_FLAGS((heap), DUK_HEAP_FLAG_REFZERO_FREE_RUNNING)

/*
 *  Longjmp types, also double as identifying continuation type for a rethrow (in 'finally')
 */

#define  DUK_LJ_TYPE_UNKNOWN      0    /* unused */
#define  DUK_LJ_TYPE_RETURN       1    /* value1 -> return value */
#define  DUK_LJ_TYPE_THROW        2    /* value1 -> error object */
#define  DUK_LJ_TYPE_BREAK        3    /* value1 -> label number */
#define  DUK_LJ_TYPE_CONTINUE     4    /* value1 -> label number */
#define  DUK_LJ_TYPE_YIELD        5    /* value1 -> yield value, iserror -> error / normal */
#define  DUK_LJ_TYPE_RESUME       6    /* value1 -> resume value, value2 -> resumee thread, iserror -> error/normal */
#define  DUK_LJ_TYPE_NORMAL       7    /* pseudo-type to indicate a normal continuation (for 'finally' rethrowing) */

/* dummy non-zero value to be used as an argument for longjmp(), see man longjmp */
#define  DUK_LONGJMP_DUMMY_VALUE  1

/*
 *  Mark-and-sweep flags
 *
 *  These are separate from heap level flags now but could be merged.
 *  The heap structure only contains a 'base mark-and-sweep flags'
 *  field and the GC caller can impose further flags.
 */

#define  DUK_MS_FLAG_EMERGENCY                (1 << 0)   /* emergency mode: try extra hard */
#define  DUK_MS_FLAG_NO_STRINGTABLE_RESIZE    (1 << 1)   /* don't resize stringtable (but may sweep it); needed during stringtable resize */
#define  DUK_MS_FLAG_NO_FINALIZERS            (1 << 2)   /* don't run finalizers (which may have arbitrary side effects) */
#define  DUK_MS_FLAG_NO_OBJECT_COMPACTION     (1 << 3)   /* don't compact objects; needed during object property allocation resize */

/*
 *  Other heap related defines
 */

/* Maximum duk_handle_call / duk_handle_safe_call depth.  Note that this
 * does not limit bytecode executor internal call depth at all (e.g.
 * for Ecmascript-to-Ecmascript calls, thread yields/resumes, etc).
 * There is a separate callstack depth limit for threads.
 */

#define  DUK_HEAP_DEFAULT_CALL_RECURSION_LIMIT             60  /* assuming 0.5 kB between calls, about 30kB of stack */ 

/* mark-and-sweep C recursion depth for marking phase; if reached,
 * mark object as a TEMPROOT and use multi-pass marking.
 */
#ifdef  DUK_USE_MARK_AND_SWEEP
#ifdef  DUK_USE_GC_TORTURE
#define  DUK_HEAP_DEFAULT_MARK_AND_SWEEP_RECURSION_LIMIT   3
#else
#define  DUK_HEAP_DEFAULT_MARK_AND_SWEEP_RECURSION_LIMIT   32
#endif
#endif

/* mark-and-sweep interval can be much lower with reference counting */
#ifdef  DUK_USE_MARK_AND_SWEEP
#ifdef  DUK_USE_REFERENCE_COUNTING
#define  DUK_HEAP_DEFAULT_MARK_AND_SWEEP_TRIGGER_LIMIT     10000
#else
#define  DUK_HEAP_DEFAULT_MARK_AND_SWEEP_TRIGGER_LIMIT     1000
#endif
#endif

/* stringcache is used for speeding up char-offset-to-byte-offset
 * translations for non-ASCII strings
 */
#define  DUK_HEAP_STRCACHE_SIZE                            4
#define  DUK_HEAP_STRINGCACHE_NOCACHE_LIMIT                16  /* strings up to the this length are not cached */

/* helper to insert a (non-string) heap object into heap allocated list */
#define  DUK_HEAP_INSERT_INTO_HEAP_ALLOCATED(heap,hdr)     duk_heap_insert_into_heap_allocated((heap),(hdr))

/*
 *  Stringtable
 */

/* initial stringtable size, must be prime and higher than DUK_UTIL_MIN_HASH_PRIME */
#define  DUK_STRTAB_INITIAL_SIZE            17

/* indicates a deleted string; any fixed non-NULL, non-hstring pointer works */
#define  DUK_STRTAB_DELETED_MARKER(heap)    ((duk_hstring *) heap)

/* resizing parameters */
#define  DUK_STRTAB_MIN_FREE_DIVISOR        4                /* load factor max 75% */
#define  DUK_STRTAB_MIN_USED_DIVISOR        4                /* load factor min 25% */
#define  DUK_STRTAB_GROW_ST_SIZE(n)         ((n) + (n))      /* used entries + approx 100% -> reset load to 50% */

#define  DUK_STRTAB_U32_MAX_STRLEN          10               /* 4'294'967'295 */
#define  DUK_STRTAB_HIGHEST_32BIT_PRIME     0xfffffffbU

/* probe sequence */
#define  DUK_STRTAB_HASH_INITIAL(hash,h_size)    ((hash) % (h_size))
#define  DUK_STRTAB_HASH_PROBE_STEP(hash)        DUK_UTIL_GET_HASH_PROBE_STEP((hash))

/*
 *  Built-in strings
 */

/* heap string indices are autogenerated in duk_strings.h */
#define  DUK_HEAP_GET_STRING(heap,idx)  ((heap)->strs[(idx)])

/*
 *  Raw memory calls: relative to heap, but no GC interaction
 */

#define  DUK_ALLOC_RAW(heap,size) \
	((heap)->alloc_func((heap)->alloc_udata, (size)))

#define  DUK_REALLOC_RAW(heap,ptr,newsize) \
	((heap)->realloc_func((heap)->alloc_udata, (ptr), (newsize)))

#define  DUK_FREE_RAW(heap,ptr) \
	((heap)->free_func((heap)->alloc_udata, (ptr)))

/*
 *  Memory calls: relative to heap, GC interaction, but no error throwing.
 *
 *  FIXME: currently a mark-and-sweep triggered by memory allocation will
 *  run using the heap->heap_thread.  This thread is also used for running
 *  mark-and-sweep finalization; this is not ideal because it breaks the
 *  isolation between multiple global environments.
 *
 *  Notes:
 *
 *    - DUK_FREE() is required to ignore NULL and any other possible return
 *      value of a zero-sized alloc/realloc (same as ANSI C free()).
 * 
 *    - There is no DUK_REALLOC_ZEROED (and checked variant) because we don't
 *      assume to know the old size.  Caller must zero the reallocated memory.
 *
 *    - DUK_REALLOC_INDIRECT() must be used when a mark-and-sweep triggered
 *      by an allocation failure might invalidate the original 'ptr', thus
 *      causing a realloc retry to use an invalid pointer.  Example: we're
 *      reallocating the value stack and a finalizer resizes the same value
 *      stack during mark-and-sweep.  The indirect variant knows the storage
 *      location of the pointer being reallocated and looks it up on every
 *      attempt; the storage location must of course be stable, which is
 *      always the case for heap objects now.
 *
 *      Note: the pointer in the storage location ('iptr') is read but is
 *      NOT updated; caller must do that.
 */

#define  DUK_ALLOC(heap,size)                            duk_heap_mem_alloc((heap), (size))
#define  DUK_ALLOC_ZEROED(heap,size)                     duk_heap_mem_alloc_zeroed((heap), (size))
#define  DUK_REALLOC(heap,ptr,newsize)                   duk_heap_mem_realloc((heap), (ptr), (newsize))
#define  DUK_REALLOC_INDIRECT(heap,iptr,newsize)         duk_heap_mem_realloc_indirect((heap), (iptr), (newsize))
#define  DUK_FREE(heap,ptr)                              duk_heap_mem_free((heap), (ptr))

/*
 *  Memory calls: relative to a thread, GC interaction, throw error on alloc failure
 */

/* XXX: add __func__ */

#ifdef DUK_USE_VERBOSE_ERRORS
#define  DUK_ALLOC_CHECKED(thr,size)                     duk_heap_mem_alloc_checked((thr), (size), __FILE__, __LINE__)
#define  DUK_ALLOC_CHECKED_ZEROED(thr,size)              duk_heap_mem_alloc_checked_zeroed((thr), (size), __FILE__, __LINE__)
#define  DUK_REALLOC_CHECKED(thr,ptr,newsize)            duk_heap_mem_realloc_checked((thr), (ptr), (newsize), __FILE__, __LINE__)
#define  DUK_REALLOC_INDIRECT_CHECKED(thr,iptr,newsize)  duk_heap_mem_realloc_indirect_checked((thr), (iptr), (newsize), __FILE__, __LINE__)
#define  DUK_FREE_CHECKED(thr,ptr)                       duk_heap_mem_free((thr)->heap, (ptr))  /* must not fail */
#else
#define  DUK_ALLOC_CHECKED(thr,size)                     duk_heap_mem_alloc_checked((thr), (size))
#define  DUK_ALLOC_CHECKED_ZEROED(thr,size)              duk_heap_mem_alloc_checked_zeroed((thr), (size))
#define  DUK_REALLOC_CHECKED(thr,ptr,newsize)            duk_heap_mem_realloc_checked((thr), (ptr), (newsize))
#define  DUK_REALLOC_INDIRECT_CHECKED(thr,iptr,newsize)  duk_heap_mem_realloc_indirect_checked((thr), (iptr), (newsize))
#define  DUK_FREE_CHECKED(thr,ptr)                       duk_heap_mem_free((thr)->heap, (ptr))  /* must not fail */
#endif

/*
 *  Memory constants
 */

#define  DUK_HEAP_ALLOC_FAIL_MARKANDSWEEP_LIMIT           5   /* Retry allocation after mark-and-sweep for this
                                                               * many times.  A single mark-and-sweep round is
                                                               * not guaranteed to free all unreferenced memory
                                                               * because of finalization (in fact, ANY number of
                                                               * rounds is strictly not enough).
                                                               */

#define  DUK_HEAP_ALLOC_FAIL_MARKANDSWEEP_EMERGENCY_LIMIT  3  /* Starting from this round, use emergency mode
                                                               * for mark-and-sweep.
                                                               */

/*
 *  String cache should ideally be at duk_hthread level, but that would
 *  cause string finalization to slow down relative to the number of
 *  threads; string finalization must check the string cache for "weak"
 *  references to the string being finalized to avoid dead pointers.
 *
 *  Thus, string caches are now at the heap level now.
 */

struct duk_strcache {
	duk_hstring *h;
	duk_u32 bidx;
	duk_u32 cidx;
};

/*
 *  Longjmp state, contains the information needed to perform a longjmp.
 *  Longjmp related values are written to value1, value2, and iserror.
 */

struct duk_ljstate {
	duk_jmpbuf *jmpbuf_ptr;   /* current setjmp() catchpoint */
	duk_hobject *errhandler;  /* function to invoke for errors before unwinding; may be NULL, -borrowed reference- (must be in valstack) */
	int type;                 /* longjmp type */
	duk_tval value1;          /* 1st related value (type specific) */
	duk_tval value2;          /* 2nd related value (type specific) */
	int iserror;              /* isError flag for yield */
};

/*
 *  Main heap structure
 */

struct duk_heap {
	int flags;

	/* allocator functions */
        duk_alloc_function alloc_func;
        duk_realloc_function realloc_func;
        duk_free_function free_func;
        void *alloc_udata;

	/* allocated heap objects */
	duk_heaphdr *heap_allocated;

	/* work list for objects whose refcounts are zero but which have not been
	 * "finalized"; avoids recursive C calls when refcounts go to zero in a
	 * chain of objects.
	 */
#ifdef DUK_USE_REFERENCE_COUNTING
	duk_heaphdr *refzero_list;
	duk_heaphdr *refzero_list_tail;
#endif

#ifdef DUK_USE_MARK_AND_SWEEP
	/* mark-and-sweep control */
	int mark_and_sweep_trigger_counter;
	int mark_and_sweep_trigger_limit;
	int mark_and_sweep_recursion_depth;
	int mark_and_sweep_recursion_limit;

	/* mark-and-sweep flags automatically active (used for critical sections) */
	int mark_and_sweep_base_flags;

	/* work list for objects to be finalized (by mark-and-sweep) */
	duk_heaphdr *finalize_list;
#endif

	/* fatal error handling, called e.g. when a longjmp() is needed but
	 * lj.jmpbuf_ptr is NULL.  fatal_func must never return.
	 */
	duk_fatal_function fatal_func;

	/* longjmp state */
	duk_ljstate lj;

	/* marker for detecting internal "double faults", see duk_error_throw.c */
	int handling_error;

	/* heap thread, used internally and for finalization */
	duk_hthread *heap_thread;

	/* current thread */
	duk_hthread *curr_thread;	/* currently running thread */

	/* heap level "stash" object (e.g., various reachability roots) */
	duk_hobject *heap_object;

	/* duk_handle_call / duk_handle_safe_call recursion depth limiting */
	int call_recursion_depth;
	int call_recursion_limit;

	/* mix-in value for computing string hashes; should be reasonably unpredictable */
        duk_u32 hash_seed;

	/* rnd_state for duk_util_tinyrandom.c */
	duk_u32 rnd_state;

	/* string intern table (weak refs) */
	duk_hstring **st;
	duk_u32 st_size;     /* alloc size in elements */
	duk_u32 st_used;     /* used elements (includes DELETED) */

	/* string access cache (codepoint offset -> byte offset) for fast string
	 * character looping; 'weak' reference which needs special handling in GC.
	 */
	duk_strcache strcache[DUK_HEAP_STRCACHE_SIZE];

	/* built-in strings */
	duk_hstring *strs[DUK_HEAP_NUM_STRINGS];
};

/*
 *  Prototypes
 */

duk_heap *duk_heap_alloc(duk_alloc_function alloc_func,
                         duk_realloc_function realloc_func,
                         duk_free_function free_func,
                         void *alloc_udata,
                         duk_fatal_function fatal_func);
void duk_heap_free(duk_heap *heap);
void duk_heap_free_heaphdr_raw(duk_heap *heap, duk_heaphdr *hdr);

void duk_heap_insert_into_heap_allocated(duk_heap *heap, duk_heaphdr *hdr);
#if defined(DUK_USE_DOUBLE_LINKED_HEAP) && defined(DUK_USE_REFERENCE_COUNTING)
void duk_heap_remove_any_from_heap_allocated(duk_heap *heap, duk_heaphdr *hdr);
#endif

duk_hstring *duk_heap_string_lookup(duk_heap *heap, duk_u8 *str, duk_u32 blen);
duk_hstring *duk_heap_string_intern(duk_heap *heap, duk_u8 *str, duk_u32 blen);
duk_hstring *duk_heap_string_intern_checked(duk_hthread *thr, duk_u8 *str, duk_u32 len);
duk_hstring *duk_heap_string_lookup_u32(duk_heap *heap, duk_u32 val);
duk_hstring *duk_heap_string_intern_u32(duk_heap *heap, duk_u32 val);
duk_hstring *duk_heap_string_intern_u32_checked(duk_hthread *thr, duk_u32 val);
void duk_heap_string_remove(duk_heap *heap, duk_hstring *h);
void duk_heap_force_stringtable_resize(duk_heap *heap);

void duk_heap_strcache_string_remove(duk_heap *heap, duk_hstring *h);
duk_u32 duk_heap_strcache_offset_char2byte(duk_hthread *thr, duk_hstring *h, duk_u32 char_offset);

#ifdef DUK_USE_PROVIDE_DEFAULT_ALLOC_FUNCTIONS
void *duk_default_alloc_function(void *udata, size_t size);
void *duk_default_realloc_function(void *udata, void *ptr, size_t newsize);
void duk_default_free_function(void *udata, void *ptr);
#endif

void *duk_heap_mem_alloc(duk_heap *heap, size_t size);
void *duk_heap_mem_alloc_zeroed(duk_heap *heap, size_t size);
void *duk_heap_mem_realloc(duk_heap *heap, void *ptr, size_t newsize);
void *duk_heap_mem_realloc_indirect(duk_heap *heap, void **iptr, size_t newsize);
void duk_heap_mem_free(duk_heap *heap, void *ptr);

#ifdef DUK_USE_VERBOSE_ERRORS
void *duk_heap_mem_alloc_checked(duk_hthread *thr, size_t size, const char *filename, int line);
void *duk_heap_mem_alloc_checked_zeroed(duk_hthread *thr, size_t size, const char *filename, int line);
void *duk_heap_mem_realloc_checked(duk_hthread *thr, void *ptr, size_t newsize, const char *filename, int line);
void *duk_heap_mem_realloc_indirect_checked(duk_hthread *thr, void **iptr, size_t newsize, const char *filename, int line);
#else
void *duk_heap_mem_alloc_checked(duk_hthread *thr, size_t size);
void *duk_heap_mem_alloc_checked_zeroed(duk_hthread *thr, size_t size);
void *duk_heap_mem_realloc_checked(duk_hthread *thr, void *ptr, size_t newsize);
void *duk_heap_mem_realloc_indirect_checked(duk_hthread *thr, void **iptr, size_t newsize);
#endif

#ifdef DUK_USE_REFERENCE_COUNTING
void duk_heap_tval_incref(duk_tval *tv);
void duk_heap_tval_decref(duk_hthread *thr, duk_tval *tv);
void duk_heap_heaphdr_incref(duk_heaphdr *h);
void duk_heap_heaphdr_decref(duk_hthread *thr, duk_heaphdr *h);
void duk_heap_refcount_finalize_heaphdr(duk_hthread *thr, duk_heaphdr *hdr);
#else
/* no refcounting */
#endif

#ifdef DUK_USE_MARK_AND_SWEEP
int duk_heap_mark_and_sweep(duk_heap *heap, int flags);
#endif

duk_u32 duk_heap_hashstring(duk_heap *heap, duk_u8 *str, duk_u32 len);

#endif  /* DUK_HEAP_H_INCLUDED */

#line 1 "duk_debug.h"
/*
 *  Debugging macros, DUK_DPRINT() and its variants in particular.
 *
 *  DUK_DPRINT() allows formatted debug prints, and supports standard
 *  and Duktape specific formatters.  See duk_debug_vsnprintf.c for details.
 */

#ifndef DUK_DEBUG_H_INCLUDED
#define DUK_DEBUG_H_INCLUDED

/* already included: duk_features.h */
/* already included: duk_bittypes.h */
/* already included: duk_forwdecl.h */

#ifdef DUK_USE_DEBUG

/*
 *  Exposed debug macros: debugging enabled
 */

#include <stdarg.h>

#define  DUK_LEVEL_DEBUG    1
#define  DUK_LEVEL_DDEBUG   2
#define  DUK_LEVEL_DDDEBUG  3

#ifdef DUK_USE_VARIADIC_MACROS

/* Note: combining __FILE__, __LINE__, and __func__ into fmt would be
 * possible compile time, but waste some space with shared function names.
 */
#define  _DUK_DEBUG_LOG(lev,...)  duk_debug_log((lev), __FILE__, (int) __LINE__, __func__, __VA_ARGS__);

#define  DUK_DPRINT(...)          _DUK_DEBUG_LOG(DUK_LEVEL_DEBUG, __VA_ARGS__)

#ifdef DUK_USE_DDEBUG
#define  DUK_DDPRINT(...)         _DUK_DEBUG_LOG(DUK_LEVEL_DDEBUG, __VA_ARGS__)
#else
#define  DUK_DDPRINT(...)
#endif

#ifdef DUK_USE_DDDEBUG
#define  DUK_DDDPRINT(...)        _DUK_DEBUG_LOG(DUK_LEVEL_DDDEBUG, __VA_ARGS__)
#else
#define  DUK_DDDPRINT(...)
#endif

#else  /* DUK_USE_VARIADIC_MACROS */

#define  _DUK_DEBUG_STASH(lev)    \
	(void) memset((void *) duk_debug_file_stash, 0, (size_t) DUK_DEBUG_STASH_SIZE), \
	(void) memset((void *) duk_debug_line_stash, 0, (size_t) DUK_DEBUG_STASH_SIZE), \
	(void) memset((void *) duk_debug_func_stash, 0, (size_t) DUK_DEBUG_STASH_SIZE), \
	(void) snprintf(duk_debug_file_stash, DUK_DEBUG_STASH_SIZE - 1, "%s", __FILE__), \
	(void) snprintf(duk_debug_line_stash, DUK_DEBUG_STASH_SIZE - 1, "%d", (int) __LINE__), \
	(void) snprintf(duk_debug_func_stash, DUK_DEBUG_STASH_SIZE - 1, "%s", __func__), \
	(void) (duk_debug_level_stash = (lev))

#ifdef DUK_USE_DEBUG
#define  DUK_DPRINT  _DUK_DEBUG_STASH(DUK_LEVEL_DEBUG), (void) duk_debug_log  /* args go here in parens */
#else
#define  DUK_DPRINT  0 && 
#endif

#ifdef DUK_USE_DDEBUG
#define  DUK_DDPRINT  _DUK_DEBUG_STASH(DUK_LEVEL_DDEBUG), (void) duk_debug_log  /* args go here in parens */
#else
#define  DUK_DDPRINT  0 && 
#endif

#ifdef DUK_USE_DDDEBUG
#define  DUK_DDDPRINT  _DUK_DEBUG_STASH(DUK_LEVEL_DDDEBUG), (void) duk_debug_log  /* args go here in parens */
#else
#define  DUK_DDDPRINT  0 && 
#endif

#endif  /* DUK_USE_VARIADIC_MACROS */

/* object dumpers */

#define  DUK_DEBUG_DUMP_HEAP(x)               duk_debug_dump_heap((x))
#define  DUK_DEBUG_DUMP_HSTRING(x)            /*FIXME*/
#define  DUK_DEBUG_DUMP_HOBJECT(x)            duk_debug_dump_hobject((x))
#define  DUK_DEBUG_DUMP_HCOMPILEDFUNCTION(x)  /*FIXME*/
#define  DUK_DEBUG_DUMP_HNATIVEFUNCTION(x)    /*FIXME*/
#define  DUK_DEBUG_DUMP_HTHREAD(thr)          duk_debug_dump_hobject((duk_hobject *) (thr))
#define  DUK_DEBUG_DUMP_CALLSTACK(thr)        duk_debug_dump_callstack((thr))
#define  DUK_DEBUG_DUMP_ACTIVATION(thr,act)   duk_debug_dump_activation((thr),(act))

/* summary macros */

#define  DUK_DEBUG_SUMMARY_INIT()  do { \
		memset(duk_debug_summary_buf, 0, sizeof(duk_debug_summary_buf)); \
		duk_debug_summary_idx = 0; \
	} while (0)

#define  DUK_DEBUG_SUMMARY_CHAR(ch)  do { \
		duk_debug_summary_buf[duk_debug_summary_idx++] = (ch); \
		if (duk_debug_summary_idx >= sizeof(duk_debug_summary_buf) - 1) { \
			duk_debug_summary_buf[duk_debug_summary_idx++] = (char) 0; \
			DUK_DPRINT("    %s", duk_debug_summary_buf); \
			DUK_DEBUG_SUMMARY_INIT(); \
		} \
	} while (0)

#define  DUK_DEBUG_SUMMARY_FINISH()  do { \
		if (duk_debug_summary_idx > 0) { \
			duk_debug_summary_buf[duk_debug_summary_idx++] = (char) 0; \
			DUK_DPRINT("    %s", duk_debug_summary_buf); \
			DUK_DEBUG_SUMMARY_INIT(); \
		} \
	} while (0)

#else  /* DUK_USE_DEBUG */

/*
 *  Exposed debug macros: debugging disabled
 */

#ifdef DUK_USE_VARIADIC_MACROS

#define  DUK_DPRINT(...)
#define  DUK_DDPRINT(...)
#define  DUK_DDDPRINT(...)

#else  /* DUK_USE_VARIADIC_MACROS */

#define  DUK_DPRINT    0 &&   /* args go here in parens */
#define  DUK_DDPRINT   0 && 
#define  DUK_DDDPRINT  0 && 

#endif  /* DUK_USE_VARIADIC_MACROS */

#define  DUK_DEBUG_DUMP_HEAP(x)
#define  DUK_DEBUG_DUMP_HSTRING(x)
#define  DUK_DEBUG_DUMP_HOBJECT(x)
#define  DUK_DEBUG_DUMP_HCOMPILEDFUNCTION(x)
#define  DUK_DEBUG_DUMP_HNATIVEFUNCTION(x)
#define  DUK_DEBUG_DUMP_HTHREAD(x)

#define  DUK_DEBUG_SUMMARY_INIT()
#define  DUK_DEBUG_SUMMARY_CHAR(ch)
#define  DUK_DEBUG_SUMMARY_FINISH()

#endif  /* DUK_DEBUG */

/*
 *  Structs
 */

#ifdef DUK_USE_DEBUG
struct duk_fixedbuffer {
	duk_u8 *buffer;
	duk_u32 length;
	duk_u32 offset;
	int truncated;
};
#endif

/*
 *  Prototypes
 */

#ifdef DUK_USE_DEBUG
int duk_debug_vsnprintf(char *str, size_t size, const char *format, va_list ap);
int duk_debug_snprintf(char *str, size_t size, const char *format, ...);

#ifdef DUK_USE_VARIADIC_MACROS
void duk_debug_log(int level, const char *file, int line, const char *func, char *fmt, ...);
#else
/* parameter passing, not thread safe */
#define  DUK_DEBUG_STASH_SIZE  256
extern char duk_debug_file_stash[DUK_DEBUG_STASH_SIZE];
extern char duk_debug_line_stash[DUK_DEBUG_STASH_SIZE];
extern char duk_debug_func_stash[DUK_DEBUG_STASH_SIZE];
extern int duk_debug_level_stash;
extern void duk_debug_log(char *fmt, ...);
#endif

void duk_fb_put_bytes(duk_fixedbuffer *fb, duk_u8 *buffer, duk_u32 length);
void duk_fb_put_byte(duk_fixedbuffer *fb, duk_u8 x);
void duk_fb_put_cstring(duk_fixedbuffer *fb, char *x);
void duk_fb_sprintf(duk_fixedbuffer *fb, const char *fmt, ...);
int duk_fb_is_full(duk_fixedbuffer *fb);

void duk_debug_dump_heap(duk_heap *heap);
void duk_debug_heap_graphviz(duk_heap *heap);
void duk_debug_dump_hobject(duk_hobject *obj);
void duk_debug_dump_hthread(duk_hthread *thr);
void duk_debug_dump_callstack(duk_hthread *thr);
void duk_debug_dump_activation(duk_hthread *thr, duk_activation *act);

#define  DUK_DEBUG_SUMMARY_BUF_SIZE  76
extern char duk_debug_summary_buf[DUK_DEBUG_SUMMARY_BUF_SIZE];
extern int duk_debug_summary_idx;

#endif  /* DUK_USE_DEBUG */

#endif  /* DUK_DEBUG_H_INCLUDED */

#line 1 "duk_error.h"
/*
 *  Error handling macros, assertion macro, error codes.
 */

#ifndef DUK_ERROR_H_INCLUDED
#define DUK_ERROR_H_INCLUDED

/* already included: duk_misc.h */

/*
 *  Error codes (defined in duktape.h)
 *
 *  Error codes are used as a shorthand to throw exceptions from inside
 *  the implementation.  The appropriate Ecmascript object is constructed
 *  based on the code.  Ecmascript code throws objects directly.
 *
 *  The error codes are now defined in the public API header because they
 *  are also used by calling code.
 */

/* for function return codes */
#define  DUK_ERR_OK                   0     /* call successful */
#define  DUK_ERR_FAIL                 1     /* call failed */

/* duk_executor results */
/* FIXME: these need to be in the public API too */
#define  DUK_ERR_EXEC_SUCCESS         0     /* thread returned to entry level with success */
#define  DUK_ERR_EXEC_ERROR           1     /* thread encountered a so far uncaught error */
#define  DUK_ERR_EXEC_TERM            2     /* thread was terminated due to an unrecoverable error */

/*
 *  Normal error is thrown with a longjmp() through the current setjmp()
 *  catchpoint record in the duk_heap.  The 'curr_thread' of the duk_heap
 *  identifies the throwing thread.
 *
 *  Panic is thrown without a heap/thread context and cannot be caught.
 *  All bets are off, and the default implementation exits the process.
 *
 *  FIXME: panic should map to the fatal error handler.
 *
 *  Error formatting is not always necessary but there are no separate calls
 *  (to minimize code size).  Error object creation will consume a considerable
 *  amount of time, compared to which formatting is probably trivial.  Note
 *  that special formatting (provided by DUK_DEBUG macros) is NOT available.
 *
 *  The _RAW variants allow the caller to specify file and line.  This makes
 *  it easier to write checked calls which want to use the call site of the
 *  checked function, not the error macro call inside the checked function.
 *
 *  We prefer the standard variadic macros; if they are not available, we
 *  fall back to awkward hacks.
 */

#ifdef DUK_USE_VERBOSE_ERRORS

#ifdef DUK_USE_VARIADIC_MACROS

/* __VA_ARGS__ has comma issues for empty lists, so we mandate at least 1 argument for '...' (format string) */
#define  DUK_ERROR(thr,err,...)                    duk_err_handle_error(__FILE__, (int) __LINE__, (thr), (err), __VA_ARGS__)
#define  DUK_ERROR_RAW(file,line,thr,err,...)      duk_err_handle_error((file), (line), (thr), (err), __VA_ARGS__)
#define  DUK_PANIC(err,...)                        duk_err_handle_panic(__FILE__, __LINE__, (err), __VA_ARGS__)
#define  DUK_PANIC_RAW(file,line,err,...)          duk_err_handle_panic((file), (line), (err), __VA_ARGS__)

#else  /* DUK_USE_VARIADIC_MACROS */

/* Parameter passing here is not thread safe.  We rely on the __FILE__
 * pointer being a constant which can be passed through a global.
 */

#define  DUK_ERROR  \
	duk_err_file_stash = (const char *) __FILE__, \
	duk_err_line_stash = (int) __LINE__, \
	(void) duk_err_handle_error_stash
#define  DUK_ERROR_RAW                             duk_err_handle_error
#define  DUK_PANIC  \
	duk_err_file_stash = (const char *) __FILE__, \
	duk_err_line_stash = (int) __LINE__, \
	(void) duk_err_handle_panic_stash
#define  DUK_PANIC_RAW                             duk_err_handle_panic

#endif  /* DUK_USE_VARIADIC_MACROS */

#else  /* DUK_USE_VERBOSE_ERRORS */

#ifdef DUK_USE_VARIADIC_MACROS

#define  DUK_ERROR(thr,err,...)                    duk_err_handle_error((thr), (err))
#define  DUK_ERROR_RAW(file,line,thr,err,...)      duk_err_handle_error((thr), (err))
#define  DUK_PANIC(err,...)                        duk_err_handle_panic((err))
#define  DUK_PANIC_RAW(err,...)                    duk_err_handle_panic((err))

#else  /* DUK_USE_VARIADIC_MACROS */

/* This is sub-optimal because arguments will be passed but ignored, and the strings
 * will go into the object file.  Can't think of how to do this portably and still
 * relatively conveniently.
 */
#define  DUK_ERROR                                 duk_err_handle_error_nonverbose1
#define  DUK_ERROR_RAW                             duk_err_handle_error_nonverbose2
#define  DUK_PANIC                                 duk_err_handle_panic_nonverbose1
#define  DUK_PANIC_RAW                             duk_err_handle_panic_nonverbose2

#endif  /* DUK_USE_VARIADIC_MACROS */

#endif  /* DUK_USE_VERBOSE_ERRORS */

/*
 *  Assert macro: failure cause DUK_PANIC().
 */

#ifdef DUK_USE_ASSERTIONS

/* the message should be a compile time constant without formatting (less risk);
 * we don't care about assertion text size because they're not used in production
 * builds.
 */
#define  DUK_ASSERT(x)  do { \
	if (!(x)) { \
		DUK_PANIC(DUK_ERR_ASSERTION_ERROR, \
			"assertion failed: " #x \
			" (" __FILE__ ":" DUK_MACRO_STRINGIFY(__LINE__) ")"); \
	} \
	} while (0)

#else  /* DUK_USE_ASSERTIONS */

#define  DUK_ASSERT(x)  do { /* assertion omitted */ } while(0)

#endif  /* DUK_USE_ASSERTIONS */

/*
 *  "Never here" macro
 */

#define DUK_NEVER_HERE()  DUK_PANIC(DUK_ERR_INTERNAL_ERROR, "should not be here")

/*
 *  Final panic handler macro (unless defined already)
 */

/* FIXME: Change this is so that if DUK_USER_PANIC_HANDLER defined, map
 * DUK_PANIC_HANDLER to it? Cleaner than allowing user to define directly.
 * In any case, panics should map do fatal error handler in the public API.
 */

#ifdef DUK_PANIC_HANDLER
/* already defined, good */
#else
#ifdef DUK_USE_GCC_PRAGMAS
#define  DUK_PANIC_HANDLER(code,msg)  do { \
		/* GCC pragmas to suppress: warning: the address of 'xxx' will always evaluate as 'true' [-Waddress]' */ \
		_Pragma("GCC diagnostic push"); \
		_Pragma("GCC diagnostic ignored \"-Waddress\""); \
		fprintf(stderr, "PANIC %d: %s\n", code, msg ? msg : "null"); \
		fflush(stderr); \
		exit(-1); \
		_Pragma("GCC diagnostic pop"); \
	} while (0)
#else
#define  DUK_PANIC_HANDLER(code,msg)  do { \
		/* No pragmas to suppress warning, causes unclean build */ \
		fprintf(stderr, "PANIC %d: %s\n", code, msg ? msg : "null"); \
		fflush(stderr); \
		exit(-1); \
	} while (0)
#endif
#endif

/*
 *  Assertion helpers
 */

#if defined(DUK_USE_ASSERTIONS) && defined(DUK_USE_REFERENCE_COUNTING)
#define  DUK_ASSERT_REFCOUNT_NONZERO_HEAPHDR(h)  do { \
		DUK_ASSERT((h) == NULL || DUK_HEAPHDR_GET_REFCOUNT((duk_heaphdr *) (h)) > 0); \
	} while (0)
#define  DUK_ASSERT_REFCOUNT_NONZERO_TVAL(tv)  do { \
		if ((tv) != NULL && DUK_TVAL_IS_HEAP_ALLOCATED((tv))) { \
			DUK_ASSERT(DUK_HEAPHDR_GET_REFCOUNT(DUK_TVAL_GET_HEAPHDR((tv))) > 0); \
		} \
	} while (0)
#else
#define  DUK_ASSERT_REFCOUNT_NONZERO_HEAPHDR(h)  /* no refcount check */
#define  DUK_ASSERT_REFCOUNT_NONZERO_TVAL(tv)    /* no refcount check */
#endif

#define  DUK_ASSERT_TOP(ctx,n)  DUK_ASSERT(duk_get_top((ctx)) == (n))

/*
 *  Helper for valstack space
 *
 *  Caller of ASSERT_VALSTACK_SPACE() estimates the number of free stack entries
 *  required for its own use, and any child calls which are not (a) Duktape API calls
 *  or (b) Duktape calls which involve extending the valstack (e.g. getter call).
 */

#define  DUK_VALSTACK_ASSERT_EXTRA  5  /* this is added to checks to allow for Duktape
                                        * API calls in addition to function's own use
                                        */
#if defined(DUK_USE_ASSERTIONS)
#define  ASSERT_VALSTACK_SPACE(thr,n)   do { \
		DUK_ASSERT((thr) != NULL); \
		DUK_ASSERT((thr)->valstack_end - (thr)->valstack_top >= (n) + DUK_VALSTACK_ASSERT_EXTRA); \
	} while (0)
#else
#define  ASSERT_VALSTACK_SPACE(thr,n)   /* no valstack space check */
#endif

/*
 *  Prototypes
 */

#ifdef DUK_USE_VERBOSE_ERRORS
#ifdef DUK_USE_VARIADIC_MACROS
void duk_err_handle_error(const char *filename, int line, duk_hthread *thr, int code, const char *fmt, ...);
void duk_err_handle_panic(const char *filename, int line, int code, const char *fmt, ...);
#else  /* DUK_USE_VARIADIC_MACROS */
extern const char *duk_err_file_stash;
extern int duk_err_line_stash;
void duk_err_handle_error(const char *filename, int line, duk_hthread *thr, int code, const char *fmt, ...);
void duk_err_handle_error_stash(duk_hthread *thr, int code, const char *fmt, ...);
void duk_err_handle_panic(const char *filename, int line, int code, const char *fmt, ...);
void duk_err_handle_panic_stash(int code, const char *fmt, ...);
#endif  /* DUK_USE_VARIADIC_MACROS */
#else  /* DUK_USE_VERBOSE_ERRORS */
#ifdef DUK_USE_VARIADIC_MACROS
void duk_err_handle_error(duk_hthread *thr, int code);
void duk_err_handle_panic(int code);
#else  /* DUK_USE_VARIADIC_MACROS */
void duk_err_handle_error_nonverbose1(duk_hthread *thr, int code, const char *fmt, ...);
void duk_err_handle_error_nonverbose2(const char *filename, int line, duk_hthread *thr, int code, const char *fmt, ...);
void duk_err_handle_panic_nonverbose1(int code, const char *fmt, ...);
void duk_err_handle_panic_nonverbose2(const char *filename, int line, int code, const char *fmt, ...);
#endif  /* DUK_USE_VARIADIC_MACROS */
#endif  /* DUK_USE_VERBOSE_ERRORS */

#ifdef DUK_USE_VERBOSE_ERRORS
void duk_err_create_and_throw(duk_hthread *thr, duk_u32 code, const char *msg, const char *filename, int line);
#else
void duk_err_create_and_throw(duk_hthread *thr, duk_u32 code);
#endif

void duk_error_throw_from_negative_rc(duk_hthread *thr, int rc);

#ifdef DUK_USE_AUGMENT_ERRORS
void duk_err_augment_error(duk_hthread *thr, duk_hthread *thr_callstack, int err_index);
#endif

void duk_err_longjmp(duk_hthread *thr);

void duk_default_fatal_handler(duk_context *ctx, int code);

void duk_err_setup_heap_ljstate(duk_hthread *thr, int lj_type);

duk_hobject *duk_error_prototype_from_code(duk_hthread *thr, int err_code);

#endif  /* DUK_ERROR_H_INCLUDED */

#line 1 "duk_util.h"
/*
 *  Utilities
 */

#ifndef DUK_UTIL_H_INCLUDED
#define DUK_UTIL_H_INCLUDED

/* already included: duk_bittypes.h */

#define  DUK_UTIL_MIN_HASH_PRIME  17  /* must match genhashsizes.py */

#define  DUK_UTIL_GET_HASH_PROBE_STEP(hash)  (duk_util_probe_steps[(hash) & 0x1f])

/*
 *  Bitstream decoder
 */

struct duk_bitdecoder_ctx {
	duk_u8 *data;
	duk_u32 offset;
	duk_u32 length;
	duk_u32 currval;
	duk_u32 currbits;
};

/*
 *  Bitstream encoder
 */

struct duk_bitencoder_ctx {
	duk_u8 *data;
	duk_u32 offset;
	duk_u32 length;
	duk_u32 currval;
	duk_u32 currbits;
	int truncated;
};

/*
 *  Externs and prototypes
 */

extern char duk_lc_digits[36];
extern char duk_uc_nybbles[16];

/* Note: assumes that duk_util_probe_steps size is 32 */
extern duk_u8 duk_util_probe_steps[32];

duk_u32 duk_util_hashbytes(duk_u8 *data, duk_u32 len, duk_u32 seed);

duk_u32 duk_util_get_hash_prime(duk_u32 size);

duk_u32 duk_bd_decode(duk_bitdecoder_ctx *ctx, int bits);
int duk_bd_decode_flag(duk_bitdecoder_ctx *ctx);

void duk_be_encode(duk_bitencoder_ctx *ctx, duk_u32 data, int bits);
void duk_be_finish(duk_bitencoder_ctx *ctx);

void duk_util_base64_encode(const unsigned char *src, unsigned char *dst, size_t len);

duk_u32 duk_util_tinyrandom_get_bits(duk_hthread *thr, int n);
double duk_util_tinyrandom_get_double(duk_hthread *thr);

#endif  /* DUK_UTIL_H_INCLUDED */

#line 1 "duk_unicode.h"
/*
 *  Unicode helpers
 */

#ifndef DUK_UNICODE_H_INCLUDED
#define DUK_UNICODE_H_INCLUDED

/* already included: duk_features.h */

#define  DUK_UNICODE_MAX_XUTF8_LENGTH   7   /* up to 36 bit codepoints */
#define  DUK_UNICODE_MAX_CESU8_LENGTH   6   /* all codepoints up to U+10FFFF */

#define  DUK_UNICODE_CP_ZWNJ                   0x200c  /* zero-width non-joiner */
#define  DUK_UNICODE_CP_ZWJ                    0x200d  /* zero-width joiner */
#define  DUK_UNICODE_CP_REPLACEMENT_CHARACTER  0xfffd  /* http://en.wikipedia.org/wiki/Replacement_character#Replacement_character */

#ifdef DUK_USE_SOURCE_NONBMP
/*
 *  Automatically generated by extract_chars.py, do not edit!
 */

extern char duk_unicode_identifier_start_noascii[797];
#else
/*
 *  Automatically generated by extract_chars.py, do not edit!
 */

extern char duk_unicode_identifier_start_noascii_bmponly[614];
#endif

#ifdef DUK_USE_SOURCE_NONBMP
/*
 *  Automatically generated by extract_chars.py, do not edit!
 */

extern char duk_unicode_identifier_part_minus_identifier_start_noascii[397];
#else
/*
 *  Automatically generated by extract_chars.py, do not edit!
 */

extern char duk_unicode_identifier_part_minus_identifier_start_noascii_bmponly[348];
#endif

/*
 *  Automatically generated by extract_caseconv.py, do not edit!
 */

extern char duk_unicode_caseconv_uc[1288];
extern char duk_unicode_caseconv_lc[616];

/*
 *  Extern
 */

/* duk_unicode_support.c */
extern duk_u8 duk_unicode_xutf8_markers[7];
extern duk_u16 duk_unicode_re_ranges_digit[2];
extern duk_u16 duk_unicode_re_ranges_white[22];
extern duk_u16 duk_unicode_re_ranges_wordchar[8];
extern duk_u16 duk_unicode_re_ranges_not_digit[4];
extern duk_u16 duk_unicode_re_ranges_not_white[24];
extern duk_u16 duk_unicode_re_ranges_not_wordchar[10];

/*
 *  Prototypes
 */

int duk_unicode_get_xutf8_length(duk_u32 x);
size_t duk_unicode_encode_xutf8(duk_u32 x, duk_u8 *out);
size_t duk_unicode_encode_cesu8(duk_u32 x, duk_u8 *out);
int duk_unicode_xutf8_get_u32(duk_hthread *thr, duk_u8 **ptr, duk_u8 *ptr_start, duk_u8 *ptr_end, duk_u32 *out_cp);
duk_u32 duk_unicode_xutf8_get_u32_checked(duk_hthread *thr, duk_u8 **ptr, duk_u8 *ptr_start, duk_u8 *ptr_end);
duk_u32 duk_unicode_unvalidated_utf8_length(duk_u8 *data, duk_u32 blen);
int duk_unicode_is_whitespace(int x);
int duk_unicode_is_line_terminator(int x);
int duk_unicode_is_identifier_start(int x);
int duk_unicode_is_identifier_part(int x);
void duk_unicode_case_convert_string(duk_hthread *thr, int uppercase);
int duk_unicode_re_canonicalize_char(duk_hthread *thr, int x);
int duk_unicode_re_is_wordchar(int x);

#endif  /* DUK_UNICODE_H_INCLUDED */

#line 1 "duk_json.h"
/*
 *  Defines for JSON, especially duk_builtin_json.c.
 */

#ifndef DUK_JSON_H_INCLUDED
#define DUK_JSON_H_INCLUDED

/* Object/array recursion limit (to protect C stack) */
#define  DUK_JSON_ENC_RECURSION_LIMIT          100
#define  DUK_JSON_DEC_RECURSION_LIMIT          100

/* Encoding flags */
#define  DUK_JSON_ENC_FLAG_ASCII_ONLY          (1 << 0)  /* escape any non-ASCII characters */
#define  DUK_JSON_ENC_FLAG_AVOID_KEY_QUOTES    (1 << 1)  /* avoid key quotes when key is an ASCII Identifier */
#define  DUK_JSON_ENC_FLAG_EXT_CUSTOM          (1 << 2)  /* extended types: custom encoding */
#define  DUK_JSON_ENC_FLAG_EXT_COMPATIBLE      (1 << 3)  /* extended types: compatible encoding */

/* How much stack to require on entry to object/array encode */
#define  DUK_JSON_ENC_REQSTACK                 32

/* How much stack to require on entry to object/array decode */
#define  DUK_JSON_DEC_REQSTACK                 32

/* Encoding state.  Heap object references are all borrowed. */
typedef struct {
	duk_hthread *thr;
	duk_hbuffer_dynamic *h_buf;
	duk_hobject *h_replacer;     /* replacer function */
	duk_hstring *h_gap;          /* gap (if empty string, NULL) */
	duk_hstring *h_indent;       /* current indent (if gap is NULL, this is NULL) */
	int idx_proplist;            /* explicit PropertyList */
	int idx_loop;                /* valstack index of loop detection object */
	int flags;
	int flag_ascii_only;
	int flag_avoid_key_quotes;
	int flag_ext_custom;
	int flag_ext_compatible;
	int recursion_depth;
	int recursion_limit;
	int mask_for_undefined;      /* type bit mask: types which certainly produce 'undefined' */
	int stridx_custom_undefined;
	int stridx_custom_nan;
	int stridx_custom_neginf;
	int stridx_custom_posinf;
} duk_json_enc_ctx;

typedef struct {
	duk_hthread *thr;
	duk_u8 *p;
	duk_u8 *p_end;
	int idx_reviver;
	int flags;
	int recursion_depth;
	int recursion_limit;
} duk_json_dec_ctx;

#endif  /* DUK_JSON_H_INCLUDED */

#line 1 "duk_js.h"
/*
 *  Ecmascript execution, support primitives.
 */

#ifndef DUK_JS_H_INCLUDED
#define DUK_JS_H_INCLUDED

/* call flags */
#define  DUK_CALL_FLAG_PROTECTED              (1 << 0)  /* duk_handle_call: call is protected */
#define  DUK_CALL_FLAG_IGNORE_RECLIMIT        (1 << 1)  /* duk_handle_call: call ignores C recursion limit (for errhandler calls) */
#define  DUK_CALL_FLAG_CONSTRUCTOR_CALL       (1 << 2)  /* duk_handle_call: constructor call (i.e. called as 'new Foo()') */
#define  DUK_CALL_FLAG_IS_RESUME              (1 << 3)  /* duk_handle_ecma_call_setup: setup for a resume() */
#define  DUK_CALL_FLAG_IS_TAILCALL            (1 << 4)  /* duk_handle_ecma_call_setup: setup for a tailcall */
#define  DUK_CALL_FLAG_DIRECT_EVAL            (1 << 5)  /* call is a direct eval call */

/* conversions, coercions, comparison, etc */
int duk_js_toboolean(duk_tval *tv);
double duk_js_tonumber(duk_hthread *thr, duk_tval *tv);
double duk_js_tointeger_number(double x);
double duk_js_tointeger(duk_hthread *thr, duk_tval *tv);
duk_u32 duk_js_touint32_number(double x);
duk_u32 duk_js_touint32(duk_hthread *thr, duk_tval *tv);
duk_i32 duk_js_toint32_number(double x);
duk_i32 duk_js_toint32(duk_hthread *thr, duk_tval *tv);
duk_u16 duk_js_touint16_number(double x);
duk_u16 duk_js_touint16(duk_hthread *thr, duk_tval *tv);
int duk_js_is_arrayindex_raw_string(duk_u8 *str, duk_u32 blen);
int duk_js_to_arrayindex_string(duk_hstring *h, duk_u32 *out_idx);
duk_u32 duk_js_to_arrayindex_string_helper(duk_hstring *h);
int duk_js_equals_number(double x, double y);
int duk_js_equals(duk_hthread *thr, duk_tval *tv_x, duk_tval *tv_y);
int duk_js_strict_equals(duk_tval *tv_x, duk_tval *tv_y);
int duk_js_samevalue_number(double x, double y);
int duk_js_samevalue(duk_tval *tv_x, duk_tval *tv_y);
int duk_js_string_compare(duk_hstring *h1, duk_hstring *h2);
int duk_js_compare_helper(duk_hthread *thr, duk_tval *tv_x, duk_tval *tv_y, int eval_left_first, int negate);
int duk_js_lessthan(duk_hthread *thr, duk_tval *tv_x, duk_tval *tv_y);
int duk_js_greaterthan(duk_hthread *thr, duk_tval *tv_x, duk_tval *tv_y);
int duk_js_lessthanorequal(duk_hthread *thr, duk_tval *tv_x, duk_tval *tv_y);
int duk_js_greaterthanorequal(duk_hthread *thr, duk_tval *tv_x, duk_tval *tv_y);
int duk_js_instanceof(duk_hthread *thr, duk_tval *tv_x, duk_tval *tv_y);
int duk_js_in(duk_hthread *thr, duk_tval *tv_x, duk_tval *tv_y);
duk_hstring *duk_js_typeof(duk_hthread *thr, duk_tval *tv_x);

/* identifiers and environment handling */
int duk_js_getvar_envrec(duk_hthread *thr, duk_hobject *env, duk_hstring *name, int throw);
int duk_js_getvar_activation(duk_hthread *thr, duk_activation *act, duk_hstring *name, int throw);
void duk_js_putvar_envrec(duk_hthread *thr, duk_hobject *env, duk_hstring *name, duk_tval *val, int strict);
void duk_js_putvar_activation(duk_hthread *thr, duk_activation *act, duk_hstring *name, duk_tval *val, int strict);
int duk_js_delvar_envrec(duk_hthread *thr, duk_hobject *env, duk_hstring *name);
int duk_js_delvar_activation(duk_hthread *thr, duk_activation *act, duk_hstring *name);
int duk_js_declvar_activation(duk_hthread *thr, duk_activation *act, duk_hstring *name, duk_tval *val, int prop_flags, int is_func_decl);
void duk_js_init_activation_environment_records_delayed(duk_hthread *thr, duk_activation *act);
void duk_js_close_environment_record(duk_hthread *thr, duk_hobject *env, duk_hobject *func, int regbase);
duk_hobject *duk_create_activation_environment_record(duk_hthread *thr, duk_hobject *func, duk_u32 reg_bottom);
void duk_js_push_closure(duk_hthread *thr,
                         duk_hcompiledfunction *fun_temp,
                         duk_hobject *outer_var_env,
                         duk_hobject *outer_lex_env);

/* call handling */
int duk_handle_call(duk_hthread *thr,
                    int num_stack_args,
                    int call_flags,
                    duk_hobject *errhandler);
int duk_handle_safe_call(duk_hthread *thr,
                         duk_safe_call_function func,
                         int num_stack_args,
                         int num_stack_res,
                         duk_hobject *errhandler);
void duk_handle_ecma_call_setup(duk_hthread *thr,
                                int num_stack_args,
                                int call_flags);

/* bytecode execution */
void duk_js_execute_bytecode(duk_hthread *entry_thread);

#endif  /* DUK_JS_H_INCLUDED */

#line 1 "duk_numconv.h"
#ifndef DUK_NUMCONV_H_INCLUDED
#define DUK_NUMCONV_H_INCLUDED

/*
 *  Number-to-string conversion.  The semantics of these is very tightly
 *  bound with the Ecmascript semantics required for call sites.
 */

/* Output a specified number of digits instead of using the shortest
 * form.  Used for toPrecision() and toFixed().
 */
#define  DUK_N2S_FLAG_FIXED_FORMAT         (1 << 0)

/* Force exponential format.  Used for toExponential(). */
#define  DUK_N2S_FLAG_FORCE_EXP            (1 << 1)

/* If number would need zero padding (for whole number part), use
 * exponential format instead.  E.g. if input number is 12300, 3
 * digits are generated ("123"), output "1.23e+4" instead of "12300".
 * Used for toPrecision().
 */
#define  DUK_N2S_FLAG_NO_ZERO_PAD          (1 << 2)

/* Digit count indicates number of fractions (i.e. an absolute
 * digit index instead of a relative one).  Used together with
 * DUK_N2S_FLAG_FIXED_FORMAT for toFixed().
 */
#define  DUK_N2S_FLAG_FRACTION_DIGITS      (1 << 3)

/*
 *  String-to-number conversion
 */

/* Maximum exponent value when parsing numbers.  This is not strictly
 * compliant as there should be no upper limit, but as we parse the
 * exponent without a bigint, impose some limit.
 */
#define  DUK_S2N_MAX_EXPONENT              1000000000

/* Trim white space (= allow leading and trailing whitespace) */
#define  DUK_S2N_FLAG_TRIM_WHITE           (1 << 0)

/* Allow exponent */
#define  DUK_S2N_FLAG_ALLOW_EXP            (1 << 1)

/* Allow trailing garbage (e.g. treat "123foo" as "123) */
#define  DUK_S2N_FLAG_ALLOW_GARBAGE        (1 << 2)

/* Allow leading plus sign */
#define  DUK_S2N_FLAG_ALLOW_PLUS           (1 << 3)

/* Allow leading minus sign */
#define  DUK_S2N_FLAG_ALLOW_MINUS          (1 << 4)

/* Allow 'Infinity' */
#define  DUK_S2N_FLAG_ALLOW_INF            (1 << 5)

/* Allow fraction part */
#define  DUK_S2N_FLAG_ALLOW_FRAC           (1 << 6)

/* Allow naked fraction (e.g. ".123") */
#define  DUK_S2N_FLAG_ALLOW_NAKED_FRAC     (1 << 7)

/* Allow empty fraction (e.g. "123.") */
#define  DUK_S2N_FLAG_ALLOW_EMPTY_FRAC     (1 << 8)

/* Allow empty string to be interpreted as 0 */
#define  DUK_S2N_FLAG_ALLOW_EMPTY_AS_ZERO  (1 << 9)

/* Allow leading zeroes (e.g. "0123" -> "123") */
#define  DUK_S2N_FLAG_ALLOW_LEADING_ZERO   (1 << 10)

/* Allow automatic detection of hex base ("0x" or "0X" prefix),
 * overrides radix argument and forces integer mode.
 */
#define  DUK_S2N_FLAG_ALLOW_AUTO_HEX_INT   (1 << 11)

/* Allow automatic detection of octal base, overrides radix
 * argument and forces integer mode.
 */
#define  DUK_S2N_FLAG_ALLOW_AUTO_OCT_INT   (1 << 12)

/*
 *  Prototypes
 */

void duk_numconv_stringify(duk_context *ctx, int radix, int digits, int flags);
void duk_numconv_parse(duk_context *ctx, int radix, int flags);

#endif  /* DUK_NUMCONV_H_INCLUDED */

#line 1 "duk_builtin_protos.h"
/*
 *  Prototypes for all built-in functions.
 */

#ifndef DUK_BUILTIN_PROTOS_H_INCLUDED
#define DUK_BUILTIN_PROTOS_H_INCLUDED

int duk_builtin_array_constructor(duk_context *ctx);
int duk_builtin_array_constructor_is_array(duk_context *ctx);
int duk_builtin_array_prototype_to_string(duk_context *ctx);
int duk_builtin_array_prototype_to_locale_string(duk_context *ctx);
int duk_builtin_array_prototype_concat(duk_context *ctx);
int duk_builtin_array_prototype_join(duk_context *ctx);
int duk_builtin_array_prototype_pop(duk_context *ctx);
int duk_builtin_array_prototype_push(duk_context *ctx);
int duk_builtin_array_prototype_reverse(duk_context *ctx);
int duk_builtin_array_prototype_shift(duk_context *ctx);
int duk_builtin_array_prototype_slice(duk_context *ctx);
int duk_builtin_array_prototype_sort(duk_context *ctx);
int duk_builtin_array_prototype_splice(duk_context *ctx);
int duk_builtin_array_prototype_unshift(duk_context *ctx);
int duk_builtin_array_prototype_index_of(duk_context *ctx);
int duk_builtin_array_prototype_last_index_of(duk_context *ctx);
int duk_builtin_array_prototype_every(duk_context *ctx);
int duk_builtin_array_prototype_some(duk_context *ctx);
int duk_builtin_array_prototype_for_each(duk_context *ctx);
int duk_builtin_array_prototype_map(duk_context *ctx);
int duk_builtin_array_prototype_filter(duk_context *ctx);
int duk_builtin_array_prototype_reduce(duk_context *ctx);
int duk_builtin_array_prototype_reduce_right(duk_context *ctx);

int duk_builtin_boolean_constructor(duk_context *ctx);
int duk_builtin_boolean_prototype_to_string(duk_context *ctx);
int duk_builtin_boolean_prototype_value_of(duk_context *ctx);

int duk_builtin_date_constructor(duk_context *ctx);
int duk_builtin_date_constructor_parse(duk_context *ctx);
int duk_builtin_date_constructor_utc(duk_context *ctx);
int duk_builtin_date_constructor_now(duk_context *ctx);
int duk_builtin_date_prototype_to_string(duk_context *ctx);
int duk_builtin_date_prototype_to_date_string(duk_context *ctx);
int duk_builtin_date_prototype_to_time_string(duk_context *ctx);
int duk_builtin_date_prototype_to_locale_string(duk_context *ctx);
int duk_builtin_date_prototype_to_locale_date_string(duk_context *ctx);
int duk_builtin_date_prototype_to_locale_time_string(duk_context *ctx);
int duk_builtin_date_prototype_value_of(duk_context *ctx);
int duk_builtin_date_prototype_get_time(duk_context *ctx);
int duk_builtin_date_prototype_get_full_year(duk_context *ctx);
int duk_builtin_date_prototype_get_utc_full_year(duk_context *ctx);
int duk_builtin_date_prototype_get_month(duk_context *ctx);
int duk_builtin_date_prototype_get_utc_month(duk_context *ctx);
int duk_builtin_date_prototype_get_date(duk_context *ctx);
int duk_builtin_date_prototype_get_utc_date(duk_context *ctx);
int duk_builtin_date_prototype_get_day(duk_context *ctx);
int duk_builtin_date_prototype_get_utc_day(duk_context *ctx);
int duk_builtin_date_prototype_get_hours(duk_context *ctx);
int duk_builtin_date_prototype_get_utc_hours(duk_context *ctx);
int duk_builtin_date_prototype_get_minutes(duk_context *ctx);
int duk_builtin_date_prototype_get_utc_minutes(duk_context *ctx);
int duk_builtin_date_prototype_get_seconds(duk_context *ctx);
int duk_builtin_date_prototype_get_utc_seconds(duk_context *ctx);
int duk_builtin_date_prototype_get_milliseconds(duk_context *ctx);
int duk_builtin_date_prototype_get_utc_milliseconds(duk_context *ctx);
int duk_builtin_date_prototype_get_timezone_offset(duk_context *ctx);
int duk_builtin_date_prototype_set_time(duk_context *ctx);
int duk_builtin_date_prototype_set_milliseconds(duk_context *ctx);
int duk_builtin_date_prototype_set_utc_milliseconds(duk_context *ctx);
int duk_builtin_date_prototype_set_seconds(duk_context *ctx);
int duk_builtin_date_prototype_set_utc_seconds(duk_context *ctx);
int duk_builtin_date_prototype_set_minutes(duk_context *ctx);
int duk_builtin_date_prototype_set_utc_minutes(duk_context *ctx);
int duk_builtin_date_prototype_set_hours(duk_context *ctx);
int duk_builtin_date_prototype_set_utc_hours(duk_context *ctx);
int duk_builtin_date_prototype_set_date(duk_context *ctx);
int duk_builtin_date_prototype_set_utc_date(duk_context *ctx);
int duk_builtin_date_prototype_set_month(duk_context *ctx);
int duk_builtin_date_prototype_set_utc_month(duk_context *ctx);
int duk_builtin_date_prototype_set_full_year(duk_context *ctx);
int duk_builtin_date_prototype_set_utc_full_year(duk_context *ctx);
int duk_builtin_date_prototype_to_utc_string(duk_context *ctx);
int duk_builtin_date_prototype_to_iso_string(duk_context *ctx);
int duk_builtin_date_prototype_to_json(duk_context *ctx);
#if 1	/* FIXME: Section B */
int duk_builtin_date_prototype_get_year(duk_context *ctx);
int duk_builtin_date_prototype_set_year(duk_context *ctx);
#endif

int duk_builtin_duk_object_addr(duk_context *ctx);
int duk_builtin_duk_object_refc(duk_context *ctx);
int duk_builtin_duk_object_gc(duk_context *ctx);
int duk_builtin_duk_object_get_finalizer(duk_context *ctx);
int duk_builtin_duk_object_set_finalizer(duk_context *ctx);
int duk_builtin_duk_object_spawn(duk_context *ctx);
int duk_builtin_duk_object_resume(duk_context *ctx);
int duk_builtin_duk_object_yield(duk_context *ctx);
int duk_builtin_duk_object_curr(duk_context *ctx);
int duk_builtin_duk_object_print(duk_context *ctx);
int duk_builtin_duk_object_time(duk_context *ctx);
int duk_builtin_duk_object_enc(duk_context *ctx);
int duk_builtin_duk_object_dec(duk_context *ctx);
int duk_builtin_duk_object_sleep(duk_context *ctx);

int duk_builtin_error_constructor(duk_context *ctx);
int duk_builtin_error_prototype_to_string(duk_context *ctx);
int duk_builtin_eval_error_constructor(duk_context *ctx);
int duk_builtin_range_error_constructor(duk_context *ctx);
int duk_builtin_reference_error_constructor(duk_context *ctx);
int duk_builtin_regexp_constructor(duk_context *ctx);
int duk_builtin_string_constructor(duk_context *ctx);
int duk_builtin_syntax_error_constructor(duk_context *ctx);
int duk_builtin_type_error_constructor(duk_context *ctx);
int duk_builtin_uri_error_constructor(duk_context *ctx);

int duk_builtin_function_constructor(duk_context *ctx);
int duk_builtin_function_prototype(duk_context *ctx);
int duk_builtin_function_prototype_to_string(duk_context *ctx);
int duk_builtin_function_prototype_apply(duk_context *ctx);
int duk_builtin_function_prototype_call(duk_context *ctx);
int duk_builtin_function_prototype_bind(duk_context *ctx);

int duk_builtin_global_object_eval(duk_context *ctx);
int duk_builtin_global_object_parse_int(duk_context *ctx);
int duk_builtin_global_object_parse_float(duk_context *ctx);
int duk_builtin_global_object_is_nan(duk_context *ctx);
int duk_builtin_global_object_is_finite(duk_context *ctx);
int duk_builtin_global_object_decode_uri(duk_context *ctx);
int duk_builtin_global_object_decode_uri_component(duk_context *ctx);
int duk_builtin_global_object_encode_uri(duk_context *ctx);
int duk_builtin_global_object_encode_uri_component(duk_context *ctx);
#if 1  /* FIXME: Section B */
int duk_builtin_global_object_escape(duk_context *ctx);
int duk_builtin_global_object_unescape(duk_context *ctx);
#endif
#if 1  /* FIXME: browser-like */
int duk_builtin_global_object_print(duk_context *ctx);
int duk_builtin_global_object_alert(duk_context *ctx);
#endif

void duk_builtin_json_parse_helper(duk_context *ctx,
                                   int idx_value,
                                   int idx_reviver,
                                   int flags);
void duk_builtin_json_stringify_helper(duk_context *ctx,
                                       int idx_value,
                                       int idx_replacer,
                                       int idx_space,
                                       int flags);
int duk_builtin_json_object_parse(duk_context *ctx);
int duk_builtin_json_object_stringify(duk_context *ctx);

int duk_builtin_math_object_abs(duk_context *ctx);
int duk_builtin_math_object_acos(duk_context *ctx);
int duk_builtin_math_object_asin(duk_context *ctx);
int duk_builtin_math_object_atan(duk_context *ctx);
int duk_builtin_math_object_atan2(duk_context *ctx);
int duk_builtin_math_object_ceil(duk_context *ctx);
int duk_builtin_math_object_cos(duk_context *ctx);
int duk_builtin_math_object_exp(duk_context *ctx);
int duk_builtin_math_object_floor(duk_context *ctx);
int duk_builtin_math_object_log(duk_context *ctx);
int duk_builtin_math_object_max(duk_context *ctx);
int duk_builtin_math_object_min(duk_context *ctx);
int duk_builtin_math_object_pow(duk_context *ctx);
int duk_builtin_math_object_random(duk_context *ctx);
int duk_builtin_math_object_round(duk_context *ctx);
int duk_builtin_math_object_sin(duk_context *ctx);
int duk_builtin_math_object_sqrt(duk_context *ctx);
int duk_builtin_math_object_tan(duk_context *ctx);

int duk_builtin_number_constructor(duk_context *ctx);
int duk_builtin_number_prototype_to_string(duk_context *ctx);
int duk_builtin_number_prototype_to_locale_string(duk_context *ctx);
int duk_builtin_number_prototype_value_of(duk_context *ctx);
int duk_builtin_number_prototype_to_fixed(duk_context *ctx);
int duk_builtin_number_prototype_to_exponential(duk_context *ctx);
int duk_builtin_number_prototype_to_precision(duk_context *ctx);

int duk_builtin_object_constructor(duk_context *ctx);
int duk_builtin_object_constructor_get_prototype_of(duk_context *ctx);
int duk_builtin_object_constructor_get_own_property_descriptor(duk_context *ctx);
int duk_builtin_object_constructor_get_own_property_names(duk_context *ctx);
int duk_builtin_object_constructor_create(duk_context *ctx);
int duk_builtin_object_constructor_define_property(duk_context *ctx);
int duk_builtin_object_constructor_define_properties(duk_context *ctx);
int duk_builtin_object_constructor_seal(duk_context *ctx);
int duk_builtin_object_constructor_freeze(duk_context *ctx);
int duk_builtin_object_constructor_prevent_extensions(duk_context *ctx);
int duk_builtin_object_constructor_is_sealed(duk_context *ctx);
int duk_builtin_object_constructor_is_frozen(duk_context *ctx);
int duk_builtin_object_constructor_is_extensible(duk_context *ctx);
int duk_builtin_object_constructor_keys(duk_context *ctx);
int duk_builtin_object_prototype_to_string(duk_context *ctx);
int duk_builtin_object_prototype_to_locale_string(duk_context *ctx);
int duk_builtin_object_prototype_value_of(duk_context *ctx);
int duk_builtin_object_prototype_has_own_property(duk_context *ctx);
int duk_builtin_object_prototype_is_prototype_of(duk_context *ctx);
int duk_builtin_object_prototype_property_is_enumerable(duk_context *ctx);

int duk_builtin_regexp_prototype_exec(duk_context *ctx);
int duk_builtin_regexp_prototype_test(duk_context *ctx);
int duk_builtin_regexp_prototype_to_string(duk_context *ctx);

int duk_builtin_string_constructor_from_char_code(duk_context *ctx);
int duk_builtin_string_prototype_to_string(duk_context *ctx);
int duk_builtin_string_prototype_value_of(duk_context *ctx);
int duk_builtin_string_prototype_char_at(duk_context *ctx);
int duk_builtin_string_prototype_char_code_at(duk_context *ctx);
int duk_builtin_string_prototype_concat(duk_context *ctx);
int duk_builtin_string_prototype_index_of(duk_context *ctx);
int duk_builtin_string_prototype_last_index_of(duk_context *ctx);
int duk_builtin_string_prototype_locale_compare(duk_context *ctx);
int duk_builtin_string_prototype_match(duk_context *ctx);
int duk_builtin_string_prototype_replace(duk_context *ctx);
int duk_builtin_string_prototype_search(duk_context *ctx);
int duk_builtin_string_prototype_slice(duk_context *ctx);
int duk_builtin_string_prototype_split(duk_context *ctx);
int duk_builtin_string_prototype_substring(duk_context *ctx);
int duk_builtin_string_prototype_to_lower_case(duk_context *ctx);
int duk_builtin_string_prototype_to_locale_lower_case(duk_context *ctx);
int duk_builtin_string_prototype_to_upper_case(duk_context *ctx);
int duk_builtin_string_prototype_to_locale_upper_case(duk_context *ctx);
int duk_builtin_string_prototype_trim(duk_context *ctx);
#if 1  /* FIXME: section B */
int duk_builtin_string_prototype_substr(duk_context *ctx);
#endif
int duk_builtin_thread_prototype_to_string(duk_context *ctx);

int duk_builtin_type_error_thrower(duk_context *ctx);

#endif  /* DUK_BUILTIN_PROTOS_H_INCLUDED */

/* already included: duk_strings.h */
/* already included: duk_builtins.h */
#line 75 "duk_internal.h"

#endif  /* DUK_INTERNAL_H_INCLUDED */

#line 1 "duk_alloc_default.c"
/*
 *  Default allocation functions.
 *
 *  Assumes behavior such as malloc allowing zero size, yielding
 *  a NULL or a unique pointer which is a no-op for free.
 */

/* include removed: duk_internal.h */

void *duk_default_alloc_function(void *udata, size_t size) {
	void *res;
	res = malloc(size);
	DUK_DDDPRINT("default alloc function: %d -> %p", size, res);
	return res;
}

void *duk_default_realloc_function(void *udata, void *ptr, size_t newsize) {
	void *res;
	res = realloc(ptr, newsize);
	DUK_DDDPRINT("default realloc function: %p %d -> %p", ptr, newsize, res);
	return res;
}

void duk_default_free_function(void *udata, void *ptr) {
	DUK_DDDPRINT("default free function: %p", ptr);
	free(ptr);
}


#line 1 "duk_alloc_torture.c"
/*
 *  Torture allocation functions.
 *
 *  Provides various debugging features:
 *
 *    - Wraps allocations with "buffer zones" which are checked on free
 *    - Overwrites freed memory with garbage (not zero)
 *    - Debug prints memory usage info after every alloc/realloc/free
 *
 *  Can be left out of a standard compilation.
 */

/* include removed: duk_internal.h */

/* FIXME: unimplemented */

void *duk_torture_alloc_function(void *udata, size_t size) {
	DUK_DDDPRINT("torture alloc function: %d", size);
	return malloc(size);
}

void *duk_torture_realloc_function(void *udata, void *ptr, size_t newsize) {
	DUK_DDDPRINT("torture realloc function: %p %d", ptr, newsize);
	return realloc(ptr, newsize);
}

void duk_torture_free_function(void *udata, void *ptr) {
	DUK_DDDPRINT("torture free function: %p", ptr);
	free(ptr);
}

#line 1 "duk_api.c"
/*
 *  API calls not falling into other categories.
 *
 *  Also contains internal functions (such as duk_get_tval()), defined
 *  in duk_api_internal.h, with semantics similar to the public API.
 */

/* FIXME: repetition of stack pre-checks -> helper or macro or inline */

/* include removed: duk_internal.h */

/*
 *  Helpers
 */

static int api_coerce_d2i(double d) {
	/*
	 *  Special cases like NaN and +/- Infinity are handled explicitly
	 *  because a plain C coercion from double to int handles these cases
	 *  in undesirable ways.  For instance, NaN may coerce to INT_MIN
	 *  (not zero), and INT_MAX + 1 may coerce to INT_MIN (not INT_MAX).
	 *
	 *  This double-to-int coercion differs from ToInteger() because it
	 *  has a finite range (ToInteger() allows e.g. +/- Infinity).  It
	 *  also differs from ToInt32() because the INT_MIN/INT_MAX clamping
	 *  depends on the size of the int type on the platform.  In particular,
	 *  on platforms with a 64-bit int type, the full range is allowed.
	 */

	if (fpclassify(d) == FP_NAN) {
		return 0;
	} else if (d < INT_MIN) {
		/* covers -Infinity */
		return INT_MIN;
	} else if (d > INT_MAX) {
		/* covers +Infinity */
		return INT_MAX;
	} else {
		/* coerce towards zero */
		return (int) d;
	}
}

/*
 *  Stack indexes and stack size management
 */

int duk_normalize_index(duk_context *ctx, int index) {
	duk_hthread *thr = (duk_hthread *) ctx;
	duk_tval *tv;

	DUK_ASSERT(ctx != NULL);
	DUK_ASSERT(DUK_INVALID_INDEX < 0);

	if (index < 0) {
		if (index == DUK_INVALID_INDEX) {
			goto fail;
		}
		tv = thr->valstack_top + index;
		DUK_ASSERT(tv < thr->valstack_top);
		if (tv < thr->valstack_bottom) {
			goto fail;
		}
	} else {
		tv = thr->valstack_bottom + index;
		DUK_ASSERT(tv >= thr->valstack_bottom);
		if (tv >= thr->valstack_top) {
			goto fail;
		}
	}

	DUK_ASSERT((int) (tv - thr->valstack_bottom) >= 0);
	return (int) (tv - thr->valstack_bottom);

 fail:
	return DUK_INVALID_INDEX;
}

int duk_require_normalize_index(duk_context *ctx, int index) {
	duk_hthread *thr = (duk_hthread *) ctx;
	int ret;

	DUK_ASSERT(ctx != NULL);

	ret = duk_normalize_index(ctx, index);
	if (ret < 0) {
		DUK_ERROR(thr, DUK_ERR_API_ERROR, "invalid index: %d", index);
	}
	return ret;
}

int duk_is_valid_index(duk_context *ctx, int index) {
	DUK_ASSERT(DUK_INVALID_INDEX < 0);
	return (duk_normalize_index(ctx, index) >= 0);
}

void duk_require_valid_index(duk_context *ctx, int index) {
	duk_hthread *thr = (duk_hthread *) ctx;

	DUK_ASSERT(ctx != NULL);
	DUK_ASSERT(DUK_INVALID_INDEX < 0);

	if (duk_normalize_index(ctx, index) < 0) {
		DUK_ERROR(thr, DUK_ERR_API_ERROR, "invalid index: %d", index);
	}
}

int duk_get_top(duk_context *ctx) {
	duk_hthread *thr = (duk_hthread *) ctx;

	DUK_ASSERT(ctx != NULL);

	return (int) (thr->valstack_top - thr->valstack_bottom);
}

/* set stack top within currently allocated range, but don't reallocate */
void duk_set_top(duk_context *ctx, int index) {
	duk_hthread *thr = (duk_hthread *) ctx;
	duk_tval *tv_new_top;

	DUK_ASSERT(ctx != NULL);
	DUK_ASSERT(DUK_INVALID_INDEX < 0);

	/* FIXME: the pointer arithmetic here is not safe on a 32-bit platform,
	 * as it may wrap.  For instance, with 8-byte values, the index 0x20000000
	 * will wrap and be equivalent to index 0; with 12-byte values, the index
	 * 0x15555556 will wrap to +8 bytes and does not even wrap evenly to a
	 * duk_tval boundary!  A correct check would first impose a min/max index
	 * which guarantees that there is only one "round" of wrapping at most,
	 * and then wrapping needs to be detected because we don't want the value
	 * stack to be wrapped around end-of-memory.
	 */

	if (index < 0) {
		if (index == DUK_INVALID_INDEX) {
			goto invalid_index;
		}
		tv_new_top = thr->valstack_top + index;
	} else {
		/* may be higher than valstack_top, but not higher than
		 * allocated stack
		 */
		tv_new_top = thr->valstack_bottom + index;
	}

	/* Check both ends: for extreme values the pointer arithmetic may wrap.
	 * The check doesn't detect wrapping so it's technically incorrect.
	 */
	if (tv_new_top < thr->valstack_bottom) {
		goto invalid_index;
	}
	if (tv_new_top > thr->valstack_end) {
		goto invalid_index;
	}

	if (tv_new_top >= thr->valstack_top) {
		/* no pointer stability issues when increasing stack size */
		while (thr->valstack_top < tv_new_top) {
			/* no need to decref previous or new value */
			DUK_ASSERT(DUK_TVAL_IS_UNDEFINED_UNUSED(thr->valstack_top));
			DUK_TVAL_SET_UNDEFINED_ACTUAL(thr->valstack_top);
			thr->valstack_top++;
		}
	} else {
		/* each DECREF potentially invalidates valstack pointers, careful */
		ptrdiff_t pdiff = ((char *) thr->valstack_top) - ((char *) tv_new_top);  /* byte diff (avoid shift/div) */

		/* FIXME: inlined DECREF macro would be nice here: no NULL check,
		 * refzero queueing but no refzero algorithm run (= no pointer
		 * instability), inline code.
		 */
	
		while (pdiff > 0) {
			duk_tval tv_tmp;
			duk_tval *tv;

			thr->valstack_top--;
			tv = thr->valstack_top;
			DUK_ASSERT(tv >= thr->valstack_bottom);
			DUK_TVAL_SET_TVAL(&tv_tmp, tv);
			DUK_TVAL_SET_UNDEFINED_UNUSED(tv);
			DUK_TVAL_DECREF(thr, &tv_tmp);  /* side effects */

			pdiff -= sizeof(duk_tval);
		}
	}
	return;

 invalid_index:
	DUK_ERROR(thr, DUK_ERR_API_ERROR, "invalid index: %d", index);
}

int duk_get_top_index(duk_context *ctx) {
	duk_hthread *thr = (duk_hthread *) ctx;
	int ret;

	DUK_ASSERT(ctx != NULL);

	ret = ((int) (thr->valstack_top - thr->valstack_bottom)) - 1;
	if (ret < 0) {
		/* Return invalid index; if caller uses this without checking
		 * in another API call, the index will never (practically)
		 * map to a valid stack entry.
		 */
		return DUK_INVALID_INDEX;
	}
	return ret;
}

int duk_require_top_index(duk_context *ctx) {
	duk_hthread *thr = (duk_hthread *) ctx;
	int ret;

	DUK_ASSERT(ctx != NULL);

	ret = ((int) (thr->valstack_top - thr->valstack_bottom)) - 1;
	if (ret < 0) {
		DUK_ERROR(thr, DUK_ERR_API_ERROR, "invalid index");
	}
	return ret;
}

/* FIXME: perhaps refactor this to allow caller to specify some parameters, or
 * at least a 'compact' flag which skips any spare or round-up .. useful for
 * emergency gc.
 */

/* Resize valstack, with careful recomputation of all pointers.
 * Must also work if ALL pointers are NULL.
 *
 * Note: this is very tricky because the valstack realloc may
 * cause a mark-and-sweep, which may run finalizers.  Running
 * finalizers may resize the valstack recursively.  So, after
 * realloc returns, we know that the valstack "top" should still
 * be the same (there should not be live values above the "top"),
 * but its underlying size may have changed.
 */
static int resize_valstack(duk_context *ctx, size_t new_size) {
	duk_hthread *thr = (duk_hthread *) ctx;
	ptrdiff_t old_bottom_offset;
	ptrdiff_t old_top_offset;
	ptrdiff_t old_end_offset_post;
#ifdef DUK_USE_DEBUG
	ptrdiff_t old_end_offset_pre;
	duk_tval *old_valstack_pre;
	duk_tval *old_valstack_post;
#endif
	duk_tval *new_valstack;
	duk_tval *p;
	size_t new_alloc_size;

	DUK_ASSERT(ctx != NULL);
	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(thr->valstack_bottom >= thr->valstack);
	DUK_ASSERT(thr->valstack_top >= thr->valstack_bottom);
	DUK_ASSERT(thr->valstack_end >= thr->valstack_top);
	DUK_ASSERT(thr->valstack_top - thr->valstack <= new_size);  /* can't resize below 'top' */

	/* get pointer offsets for tweaking below */
	old_bottom_offset = (((duk_u8 *) thr->valstack_bottom) - ((duk_u8 *) thr->valstack));
	old_top_offset = (((duk_u8 *) thr->valstack_top) - ((duk_u8 *) thr->valstack));
#ifdef DUK_USE_DEBUG
	old_end_offset_pre = (((duk_u8 *) thr->valstack_end) - ((duk_u8 *) thr->valstack));  /* not very useful, used for debugging */
	old_valstack_pre = thr->valstack;
#endif

	/* allocate a new valstack
	 *
	 * Note: cannot use a plain DUK_REALLOC() because a mark-and-sweep may
	 * invalidate the original thr->valstack base pointer inside the realloc
	 * process.  See doc/memory-management.txt.
	 */

	new_alloc_size = sizeof(duk_tval) * new_size;
	new_valstack = DUK_REALLOC_INDIRECT(thr->heap, (void **) &thr->valstack, new_alloc_size);
	if (!new_valstack) {
		DUK_DPRINT("failed to resize valstack to %d entries (%d bytes)",
		           new_size, new_alloc_size);
		return 0;
	}

	/* Note: the realloc may have triggered a mark-and-sweep which may
	 * have resized our valstack internally.  However, the mark-and-sweep
	 * MUST NOT leave the stack bottom/top in a different state.  Particular
	 * assumptions and facts:
	 *
	 *   - The thr->valstack pointer may be different after realloc,
	 *     and the offset between thr->valstack_end <-> thr->valstack
	 *     may have changed.
	 *   - The offset between thr->valstack_bottom <-> thr->valstack
	 *     and thr->valstack_top <-> thr->valstack MUST NOT have changed,
	 *     because mark-and-sweep must adhere to a strict stack policy.
	 *     In other words, logical bottom and top MUST NOT have changed.
	 *   - All values above the top are unreachable but are initialized
	 *     to UNDEFINED_UNUSED, up to the post-realloc valstack_end.
	 *   - 'old_end_offset' must be computed after realloc to be correct.
	 */

	DUK_ASSERT((((duk_u8 *) thr->valstack_bottom) - ((duk_u8 *) thr->valstack)) == old_bottom_offset);
	DUK_ASSERT((((duk_u8 *) thr->valstack_top) - ((duk_u8 *) thr->valstack)) == old_top_offset);

	/* success, fixup pointers */
	old_end_offset_post = (((duk_u8 *) thr->valstack_end) - ((duk_u8 *) thr->valstack));  /* must be computed after realloc */
#ifdef DUK_USE_DEBUG
	old_valstack_post = thr->valstack;
#endif
	thr->valstack = new_valstack;
	thr->valstack_end = new_valstack + new_size;
	thr->valstack_bottom = (duk_tval *) ((duk_u8 *) new_valstack + old_bottom_offset);
	thr->valstack_top = (duk_tval *) ((duk_u8 *) new_valstack + old_top_offset);

	DUK_ASSERT(thr->valstack_bottom >= thr->valstack);
	DUK_ASSERT(thr->valstack_top >= thr->valstack_bottom);
	DUK_ASSERT(thr->valstack_end >= thr->valstack_top);

	/* useful for debugging */
#ifdef DUK_USE_DEBUG
	if (old_end_offset_pre != old_end_offset_post) {
		DUK_DPRINT("valstack was resized during valstack_resize(), probably by mark-and-sweep; "
		           "end offset changed: %d -> %d",
		           old_end_offset_pre,
		           old_end_offset_post);
	}
	if (old_valstack_pre != old_valstack_post) {
		DUK_DPRINT("valstack pointer changed during valstack_resize(), probably by mark-and-sweep: %p -> %p",
		           (void *) old_valstack_pre,
		           (void *) old_valstack_post);
	}
#endif

	DUK_DPRINT("resized valstack to %d elements (%d bytes), bottom=%d, top=%d, "
	           "new pointers: start=%p end=%p bottom=%p top=%p",
	           (int) new_size, (int) new_alloc_size,
	           (int) (thr->valstack_bottom - thr->valstack),
	           (int) (thr->valstack_top - thr->valstack),
	           (void *) thr->valstack, (void *) thr->valstack_end,
	           (void *) thr->valstack_bottom, (void *) thr->valstack_top);

	/* init newly allocated slots (only) */
	p = (duk_tval *) ((duk_u8 *) thr->valstack + old_end_offset_post);
	while (p < thr->valstack_end) {
		/* never executed if new size is smaller */
		DUK_TVAL_SET_UNDEFINED_UNUSED(p);
		p++;
	}

	/* assertion check: we try to maintain elements above top in known state */
#ifdef DUK_USE_ASSERTIONS
	p = thr->valstack_top;
	while (p < thr->valstack_end) {
		/* everything above old valstack top should be preinitialized now */
		DUK_ASSERT(DUK_TVAL_IS_UNDEFINED_UNUSED(p));
		p++;
	}
#endif
	return 1;
}

static int check_valstack_resize_helper(duk_context *ctx,
                                        size_t min_new_size,
                                        int shrink_flag,
                                        int compact_flag,
                                        int throw_flag) {
	duk_hthread *thr = (duk_hthread *) ctx;
	size_t old_size;
	size_t new_size;
	int is_shrink = 0;

	DUK_DDDPRINT("check valstack resize: min_new_size=%d, curr_size=%d, curr_top=%d, "
	             "curr_bottom=%d, shrink=%d, compact=%d, throw=%d",
	             (int) min_new_size,
	             (int) (thr->valstack_end - thr->valstack),
	             (int) (thr->valstack_top - thr->valstack),
	             (int) (thr->valstack_bottom - thr->valstack),
	             shrink_flag, compact_flag, throw_flag);

	DUK_ASSERT(ctx != NULL);
	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(thr->valstack_bottom >= thr->valstack);
	DUK_ASSERT(thr->valstack_top >= thr->valstack_bottom);
	DUK_ASSERT(thr->valstack_end >= thr->valstack_top);

	old_size = (unsigned int) (thr->valstack_end - thr->valstack);

	if (min_new_size <= old_size) {
		is_shrink = 1;
		if (!shrink_flag ||
		    old_size - min_new_size < DUK_VALSTACK_SHRINK_THRESHOLD) {
			DUK_DDDPRINT("no need to grow or shrink valstack");
			return 1;
		}
	}

	new_size = min_new_size;
	if (!compact_flag) {
		if (is_shrink) {
			/* shrink case; leave some spare */
			new_size += DUK_VALSTACK_SHRINK_SPARE;
		}

		/* round up roughly to next 'grow step' */
		new_size = (new_size / DUK_VALSTACK_GROW_STEP + 1) * DUK_VALSTACK_GROW_STEP;
	}

	DUK_DDPRINT("want to %s valstack: %d -> %d elements (min_new_size %d)",
	            (new_size > old_size ? "grow" : "shrink"),
	            old_size, new_size, min_new_size);

	if (new_size >= thr->valstack_max) {
		/* Note: may be triggered even if minimal new_size would not reach the limit,
		 * plan limit accordingly (taking DUK_VALSTACK_GROW_STEP into account.
		 */
		if (throw_flag) {
			DUK_ERROR(thr, DUK_ERR_INTERNAL_ERROR, "valstack limit reached");
		} else {
			return 0;
		}
	}

	/*
	 *  When resizing the valstack, a mark-and-sweep may be triggered for
	 *  the allocation of the new valstack.  If the mark-and-sweep needs
	 *  to use our thread for something, it may cause *the same valstack*
	 *  to be resized recursively.  This happens e.g. when mark-and-sweep
	 *  finalizers are called.
	 *
	 *  This is taken into account carefully in resize_valstack().
	 */

	if (!resize_valstack(ctx, new_size)) {
		if (is_shrink) {
			DUK_DDPRINT("valstack resize failed, but is a shrink, ignore");
			return 1;
		}

		DUK_DDPRINT("valstack resize failed");

		if (throw_flag) {
			DUK_ERROR(ctx, DUK_ERR_ALLOC_ERROR, "failed to extend valstack");
		} else {
			return 0;
		}
	}

	DUK_DDDPRINT("valstack resize successful");
	return 1;
}

/* FIXME: unused now */
int duk_check_valstack_resize(duk_context *ctx, unsigned int min_new_size, int allow_shrink) {
	return check_valstack_resize_helper(ctx,
	                                    min_new_size,  /* min_new_size */
	                                    allow_shrink,  /* shrink_flag */
	                                    0,             /* compact flag */
	                                    0);            /* throw flag */
}

/* FIXME: unused now */
void duk_require_valstack_resize(duk_context *ctx, unsigned int min_new_size, int allow_shrink) {
	(void) check_valstack_resize_helper(ctx,
	                                    min_new_size,  /* min_new_size */
	                                    allow_shrink,  /* shrink_flag */
	                                    0,             /* compact flag */
	                                    1);            /* throw flag */
}

int duk_check_stack(duk_context *ctx, unsigned int extra) {
	duk_hthread *thr = (duk_hthread *) ctx;
	unsigned int min_new_size;

	DUK_ASSERT(ctx != NULL);
	DUK_ASSERT(thr != NULL);

	min_new_size = (thr->valstack_top - thr->valstack) + extra + DUK_VALSTACK_INTERNAL_EXTRA;
	return check_valstack_resize_helper(ctx,
	                                    min_new_size,  /* min_new_size */
	                                    0,             /* shrink_flag */
	                                    0,             /* compact flag */
	                                    0);            /* throw flag */
}

void duk_require_stack(duk_context *ctx, unsigned int extra) {
	duk_hthread *thr = (duk_hthread *) ctx;
	unsigned int min_new_size;

	DUK_ASSERT(ctx != NULL);
	DUK_ASSERT(thr != NULL);

	min_new_size = (thr->valstack_top - thr->valstack) + extra + DUK_VALSTACK_INTERNAL_EXTRA;
	(void) check_valstack_resize_helper(ctx,
	                                    min_new_size,  /* min_new_size */
	                                    0,             /* shrink_flag */
	                                    0,             /* compact flag */
	                                    1);            /* throw flag */
}

int duk_check_stack_top(duk_context *ctx, unsigned int top) {
	unsigned int min_new_size;

	DUK_ASSERT(ctx != NULL);

	min_new_size = top + DUK_VALSTACK_INTERNAL_EXTRA;
	return check_valstack_resize_helper(ctx,
	                                    min_new_size,  /* min_new_size */
	                                    0,             /* shrink_flag */
	                                    0,             /* compact flag */
	                                    0);            /* throw flag */
}

void duk_require_stack_top(duk_context *ctx, unsigned int top) {
	unsigned int min_new_size;

	DUK_ASSERT(ctx != NULL);

	min_new_size = top + DUK_VALSTACK_INTERNAL_EXTRA;
	(void) check_valstack_resize_helper(ctx,
	                                    min_new_size,  /* min_new_size */
	                                    0,             /* shrink_flag */
	                                    0,             /* compact flag */
	                                    1);            /* throw flag */
}

/*
 *  Stack manipulation
 */

void duk_swap(duk_context *ctx, int index1, int index2) {
	duk_tval *tv1;
	duk_tval *tv2;
	duk_tval tv;  /* temp */

	DUK_ASSERT(ctx != NULL);

	tv1 = duk_require_tval(ctx, index1);
	DUK_ASSERT(tv1 != NULL);
	tv2 = duk_require_tval(ctx, index2);
	DUK_ASSERT(tv2 != NULL);

	DUK_TVAL_SET_TVAL(&tv, tv1);
	DUK_TVAL_SET_TVAL(tv1, tv2);
	DUK_TVAL_SET_TVAL(tv2, &tv);	
}

void duk_swap_top(duk_context *ctx, int index) {
	DUK_ASSERT(ctx != NULL);

	duk_swap(ctx, index, -1);
}

void duk_dup(duk_context *ctx, int from_index) {
	duk_tval *tv;

	DUK_ASSERT(ctx != NULL);

	tv = duk_require_tval(ctx, from_index);
	DUK_ASSERT(tv != NULL);

	duk_push_tval(ctx, tv);
}

void duk_dup_top(duk_context *ctx) {
	DUK_ASSERT(ctx != NULL);

	duk_dup(ctx, -1);
}

void duk_insert(duk_context *ctx, int to_index) {
	duk_tval *p;
	duk_tval *q;
	duk_tval tv;
	size_t nbytes;

	DUK_ASSERT(ctx != NULL);

	p = duk_require_tval(ctx, to_index);
	DUK_ASSERT(p != NULL);
	q = duk_require_tval(ctx, -1);
	DUK_ASSERT(q != NULL);

	DUK_ASSERT(q >= p);

	/*              nbytes
	 *           <--------->
	 *    [ ... | p | x | x | q ]
	 * => [ ... | q | p | x | x ]
	 */

	nbytes = (size_t) (((duk_u8 *) q) - ((duk_u8 *) p));  /* Note: 'q' is top-1 */

	DUK_DDDPRINT("duk_insert: to_index=%p, p=%p, q=%p, nbytes=%d", to_index, p, q, nbytes);
	if (nbytes > 0) {
		DUK_TVAL_SET_TVAL(&tv, q);
		memmove((void *) (p + 1), (void *) p, nbytes);
		DUK_TVAL_SET_TVAL(p, &tv);
	} else {
		/* nop: insert top to top */
		DUK_ASSERT(nbytes == 0);
		DUK_ASSERT(p == q);
	}
}

void duk_replace(duk_context *ctx, int to_index) {
	duk_hthread *thr = (duk_hthread *) ctx;
	duk_tval *tv1;
	duk_tval *tv2;
	duk_tval tv;  /* temp */

	DUK_ASSERT(ctx != NULL);

	tv1 = duk_require_tval(ctx, -1);
	DUK_ASSERT(tv1 != NULL);
	tv2 = duk_require_tval(ctx, to_index);
	DUK_ASSERT(tv2 != NULL);

	/* For tv1 == tv2, both pointing to stack top, the end result
	 * is same as duk_pop(ctx).
	 */

	DUK_TVAL_SET_TVAL(&tv, tv2);
	DUK_TVAL_SET_TVAL(tv2, tv1);
	DUK_TVAL_SET_UNDEFINED_UNUSED(tv1);
	thr->valstack_top--;
	DUK_TVAL_DECREF(thr, &tv);
}

void duk_remove(duk_context *ctx, int index) {
	duk_hthread *thr = (duk_hthread *) ctx;
	duk_tval *p;
	duk_tval *q;
#ifdef DUK_USE_REFERENCE_COUNTING
	duk_tval tv;
#endif
	size_t nbytes;

	DUK_ASSERT(ctx != NULL);

	p = duk_require_tval(ctx, index);
	DUK_ASSERT(p != NULL);
	q = duk_require_tval(ctx, -1);
	DUK_ASSERT(q != NULL);

	DUK_ASSERT(q >= p);

	/*              nbytes
	 *           <--------->
	 *    [ ... | p | x | x | q ]
	 * => [ ... | x | x | q ]
	 */

#ifdef DUK_USE_REFERENCE_COUNTING
	/* use a temp: decref only when valstack reachable values are correct */
	DUK_TVAL_SET_TVAL(&tv, p);
#endif

	nbytes = (size_t) (((duk_u8 *) q) - ((duk_u8 *) p));  /* Note: 'q' is top-1 */
	if (nbytes > 0) {
		memmove(p, p + 1, nbytes);
	}
	DUK_TVAL_SET_UNDEFINED_UNUSED(q);
	thr->valstack_top--;

#ifdef DUK_USE_REFERENCE_COUNTING
	DUK_TVAL_DECREF(thr, &tv);
#endif
}

void duk_xmove(duk_context *ctx, duk_context *from_ctx, unsigned int count) {
	duk_hthread *thr = (duk_hthread *) ctx;
	duk_hthread *from_thr = (duk_hthread *) from_ctx;
	void *src;
	size_t nbytes;
	duk_tval *p;

	DUK_ASSERT(ctx != NULL);
	DUK_ASSERT(from_ctx != NULL);

	nbytes = sizeof(duk_tval) * count;
	if (nbytes == 0) {
		return;
	}
	if (((duk_u8 *) thr->valstack_end) - ((duk_u8 *) thr->valstack_top) < nbytes) {
		DUK_ERROR(thr, DUK_ERR_API_ERROR, "attempt to push beyond currently allocated stack");
	}
	src = (void *) (((duk_u8 *) from_thr->valstack_top) - nbytes);
	if (src < (void *) thr->valstack_bottom) {
		DUK_ERROR(thr, DUK_ERR_API_ERROR, "source stack does not contain enough elements");
	}

	/* copy values (no overlap even if ctx == from_ctx) */
	memcpy((void *) thr->valstack_top, src, nbytes);

	/* incref them */
	p = thr->valstack_top;
	thr->valstack_top = (duk_tval *) (((duk_u8 *) thr->valstack_top) + nbytes);
	while (p < thr->valstack_top) {
		DUK_TVAL_INCREF(thr, p);
		p++;
	}
}

/*
 *  Get/require
 */

/* internal */
duk_tval *duk_get_tval(duk_context *ctx, int index) {
	duk_hthread *thr = (duk_hthread *) ctx;
	duk_tval *tv;

	DUK_ASSERT(ctx != NULL);
	DUK_ASSERT(DUK_INVALID_INDEX < 0);

	if (index < 0) {
		if (index == DUK_INVALID_INDEX) {
			return NULL;
		}
		tv = thr->valstack_top + index;
		DUK_ASSERT(tv < thr->valstack_top);
		if (tv < thr->valstack_bottom) {
			return NULL;
		}
	} else {
		tv = thr->valstack_bottom + index;
		DUK_ASSERT(tv >= thr->valstack_bottom);
		if (tv >= thr->valstack_top) {
			return NULL;
		}
	}
	return tv;
}

duk_tval *duk_require_tval(duk_context *ctx, int index) {
	duk_hthread *thr = (duk_hthread *) ctx;
	duk_tval *tv;

	DUK_ASSERT(ctx != NULL);
	DUK_ASSERT(DUK_INVALID_INDEX < 0);

	if (index < 0) {
		if (index == DUK_INVALID_INDEX) {
			/* XXX: this check may not be necessary
			 * on some architectures but be careful
			 * of wrapping.
			 */
			goto fail;
		}
		tv = thr->valstack_top + index;
		DUK_ASSERT(tv < thr->valstack_top);
		if (tv < thr->valstack_bottom) {
			goto fail;
		}
	} else {
		tv = thr->valstack_bottom + index;
		DUK_ASSERT(tv >= thr->valstack_bottom);
		if (tv >= thr->valstack_top) {
			goto fail;
		}
	}
	return tv;

 fail:
	DUK_ERROR(thr, DUK_ERR_API_ERROR, "index out of bounds");
	return NULL;  /* not reachable */
}

void duk_require_undefined(duk_context *ctx, int index) {
	duk_hthread *thr = (duk_hthread *) ctx;
	duk_tval *tv;

	DUK_ASSERT(ctx != NULL);

	tv = duk_get_tval(ctx, index);
	if (tv && DUK_TVAL_IS_UNDEFINED(tv)) {
		/* Note: accept both 'actual' and 'unused' undefined */
		return;
	}
	DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, "not undefined");
}

void duk_require_null(duk_context *ctx, int index) {
	duk_hthread *thr = (duk_hthread *) ctx;
	duk_tval *tv;

	DUK_ASSERT(ctx != NULL);

	tv = duk_get_tval(ctx, index);
	if (tv && DUK_TVAL_IS_NULL(tv)) {
		return;
	}
	DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, "not null");
}

int duk_get_boolean(duk_context *ctx, int index) {
	int ret = 0;  /* default: false */
	duk_tval *tv;

	DUK_ASSERT(ctx != NULL);

	tv = duk_get_tval(ctx, index);
	if (tv && DUK_TVAL_IS_BOOLEAN(tv)) {
		ret = DUK_TVAL_GET_BOOLEAN(tv);
	}

	DUK_ASSERT(ret == 0 || ret == 1);
	return ret;
}

int duk_require_boolean(duk_context *ctx, int index) {
	duk_hthread *thr = (duk_hthread *) ctx;
	duk_tval *tv;

	DUK_ASSERT(ctx != NULL);

	tv = duk_get_tval(ctx, index);
	if (tv && DUK_TVAL_IS_BOOLEAN(tv)) {
		int ret = DUK_TVAL_GET_BOOLEAN(tv);
		DUK_ASSERT(ret == 0 || ret == 1);
		return ret;
	}

	DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, "not boolean");
	return 0;  /* not reachable */
}

double duk_get_number(duk_context *ctx, int index) {
	double ret = NAN;  /* default: NaN */
	duk_tval *tv;

	DUK_ASSERT(ctx != NULL);

	tv = duk_get_tval(ctx, index);
	if (tv && DUK_TVAL_IS_NUMBER(tv)) {
		ret = DUK_TVAL_GET_NUMBER(tv);

	}

	/*
	 *  Number should already be in NAN-normalized form,
	 *  but let's normalize anyway.
	 *
	 *  XXX: NAN normalization for external API might be
	 *  different from internal normalization?
	 */

	DUK_DOUBLE_NORMALIZE_NAN_CHECK(&ret);

	return ret;
}

double duk_require_number(duk_context *ctx, int index) {
	duk_hthread *thr = (duk_hthread *) ctx;
	duk_tval *tv;

	DUK_ASSERT(ctx != NULL);

	tv = duk_get_tval(ctx, index);
	if (tv && DUK_TVAL_IS_NUMBER(tv)) {
		double ret = DUK_TVAL_GET_NUMBER(tv);

		/*
		 *  Number should already be in NAN-normalized form,
		 *  but let's normalize anyway.
		 *
		 *  XXX: NAN normalization for external API might be
		 *  different from internal normalization?
		 */
		DUK_DOUBLE_NORMALIZE_NAN_CHECK(&ret);
		return ret;
	}

	DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, "not number");
	return NAN;  /* not reachable */
}

int duk_get_int(duk_context *ctx, int index) {
	/* Custom coercion for API */
	return api_coerce_d2i(duk_get_number(ctx, index));
}

int duk_require_int(duk_context *ctx, int index) {
	/* Custom coercion for API */
	return api_coerce_d2i(duk_require_number(ctx, index));
}

const char *duk_get_lstring(duk_context *ctx, int index, size_t *out_len) {
	const char *ret;
	duk_tval *tv;

	DUK_ASSERT(ctx != NULL);

	/* default: NULL, length 0 */
	ret = NULL;
	if (out_len) {
		*out_len = 0;
	}

	tv = duk_get_tval(ctx, index);
	if (tv && DUK_TVAL_IS_STRING(tv)) {
		/* Here we rely on duk_hstring instances always being zero
		 * terminated even if the actual string is not.
		 */
		duk_hstring *h = DUK_TVAL_GET_STRING(tv);
		DUK_ASSERT(h != NULL);
		ret = (const char *) DUK_HSTRING_GET_DATA(h);
		if (out_len) {
			*out_len = DUK_HSTRING_GET_BYTELEN(h);
		}
	}

	return ret;
}

const char *duk_require_lstring(duk_context *ctx, int index, size_t *out_len) {
	duk_hthread *thr = (duk_hthread *) ctx;
	const char *ret;

	DUK_ASSERT(ctx != NULL);

	/* Note: this check relies on the fact that even a zero-size string
	 * has a non-NULL pointer.
	 */
	ret = duk_get_lstring(ctx, index, out_len);
	if (ret) {
		return ret;
	}

	DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, "not string");
	return NULL;  /* not reachable */
}

const char *duk_get_string(duk_context *ctx, int index) {
	DUK_ASSERT(ctx != NULL);

	return duk_get_lstring(ctx, index, NULL);
}

const char *duk_require_string(duk_context *ctx, int index) {
	DUK_ASSERT(ctx != NULL);

	return duk_require_lstring(ctx, index, NULL);
}

void *duk_get_pointer(duk_context *ctx, int index) {
	duk_tval *tv;

	DUK_ASSERT(ctx != NULL);

	tv = duk_get_tval(ctx, index);
	if (tv && DUK_TVAL_IS_POINTER(tv)) {
		void *p = DUK_TVAL_GET_POINTER(tv);  /* may be NULL */
		return (void *) p;
	}

	return NULL;
}

void *duk_require_pointer(duk_context *ctx, int index) {
	duk_hthread *thr = (duk_hthread *) ctx;
	duk_tval *tv;

	DUK_ASSERT(ctx != NULL);

	/* Note: here we must be wary of the fact that a pointer may be
	 * valid and be a NULL.
	 */
	tv = duk_get_tval(ctx, index);
	if (tv && DUK_TVAL_IS_POINTER(tv)) {
		void *p = DUK_TVAL_GET_POINTER(tv);  /* may be NULL */
		return (void *) p;
	}

	DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, "not pointer");
	return NULL;  /* not reachable */
}

void *duk_get_voidptr(duk_context *ctx, int index) {
	duk_tval *tv;

	DUK_ASSERT(ctx != NULL);

	tv = duk_get_tval(ctx, index);
	if (tv && DUK_TVAL_IS_HEAP_ALLOCATED(tv)) {
		duk_heaphdr *h = DUK_TVAL_GET_HEAPHDR(tv);
		DUK_ASSERT(h != NULL);
		return (void *) h;
	}

	return NULL;
}

void *duk_get_buffer(duk_context *ctx, int index, size_t *out_size) {
	duk_tval *tv;

	DUK_ASSERT(ctx != NULL);

	if (out_size != NULL) {
		*out_size = 0;
	}

	tv = duk_get_tval(ctx, index);
	if (tv && DUK_TVAL_IS_BUFFER(tv)) {
		duk_hbuffer *h = DUK_TVAL_GET_BUFFER(tv);
		DUK_ASSERT(h != NULL);
		if (out_size) {
			*out_size = DUK_HBUFFER_GET_SIZE(h);
		}
		return (void *) DUK_HBUFFER_GET_DATA_PTR(h);  /* may be NULL (but only if size is 0) */
	}

	return NULL;
}

void *duk_require_buffer(duk_context *ctx, int index, size_t *out_size) {
	duk_hthread *thr = (duk_hthread *) ctx;
	duk_tval *tv;

	DUK_ASSERT(ctx != NULL);

	if (out_size != NULL) {
		*out_size = 0;
	}

	/* Note: here we must be wary of the fact that a data pointer may
	 * be a NULL for a zero-size buffer.
	 */
	
	tv = duk_get_tval(ctx, index);
	if (tv && DUK_TVAL_IS_BUFFER(tv)) {
		duk_hbuffer *h = DUK_TVAL_GET_BUFFER(tv);
		DUK_ASSERT(h != NULL);
		if (out_size) {
			*out_size = DUK_HBUFFER_GET_SIZE(h);
		}
		return (void *) DUK_HBUFFER_GET_DATA_PTR(h);  /* may be NULL (but only if size is 0) */
	}

	DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, "not buffer");
	return NULL;  /* not reachable */
}

/* internal */
/* FIXME: allow_null can be baked into 'tag' */
static duk_heaphdr *get_tagged_heaphdr(duk_context *ctx, int index, int tag, int allow_null) {
	duk_hthread *thr = (duk_hthread *) ctx;
	duk_tval *tv;

	DUK_ASSERT(ctx != NULL);

	tv = duk_get_tval(ctx, index);
	if (tv && DUK_TVAL_GET_TAG(tv) == tag) {
		duk_heaphdr *ret;

		/* Note: tag comparison in general doesn't work for numbers,
		 * but it does work for everything else (heap objects here).
		 */
		ret = DUK_TVAL_GET_HEAPHDR(tv);
		DUK_ASSERT(ret != NULL);  /* tagged null pointers should never occur */
		return ret;
	}
	if (allow_null) {
		return (duk_heaphdr *) NULL;
	}

	DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, "incorrect type, expected tag %d", tag);
	return NULL;  /* not reachable */
}

/* internal */
duk_hstring *duk_get_hstring(duk_context *ctx, int index) {
	return (duk_hstring *) get_tagged_heaphdr(ctx, index, DUK_TAG_STRING, 1);
}

/* internal */
duk_hstring *duk_require_hstring(duk_context *ctx, int index) {
	return (duk_hstring *) get_tagged_heaphdr(ctx, index, DUK_TAG_STRING, 0);
}

/* internal */
duk_hobject *duk_get_hobject(duk_context *ctx, int index) {
	return (duk_hobject *) get_tagged_heaphdr(ctx, index, DUK_TAG_OBJECT, 1);
}

/* internal */
duk_hobject *duk_get_hobject_with_class(duk_context *ctx, int index, int class) {
	duk_hobject *h = (duk_hobject *) get_tagged_heaphdr(ctx, index, DUK_TAG_OBJECT, 1);
	if (h != NULL && DUK_HOBJECT_GET_CLASS_NUMBER(h) != class) {
		h = NULL;
	}
	return h;
}

/* internal */
duk_hobject *duk_require_hobject(duk_context *ctx, int index) {
	return (duk_hobject *) get_tagged_heaphdr(ctx, index, DUK_TAG_OBJECT, 0);
}

/* internal */
duk_hobject *duk_require_hobject_with_class(duk_context *ctx, int index, int class) {
	duk_hthread *thr = (duk_hthread *) ctx;
	duk_hobject *h = (duk_hobject *) get_tagged_heaphdr(ctx, index, DUK_TAG_OBJECT, 0);
	DUK_ASSERT(h != NULL);
	if (DUK_HOBJECT_GET_CLASS_NUMBER(h) != class) {
		DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, "expected object with class number %d", class);
	}
	return h;
}

/* internal */
duk_hbuffer *duk_get_hbuffer(duk_context *ctx, int index) {
	return (duk_hbuffer *) get_tagged_heaphdr(ctx, index, DUK_TAG_BUFFER, 1);
}

/* internal */
duk_hbuffer *duk_require_hbuffer(duk_context *ctx, int index) {
	return (duk_hbuffer *) get_tagged_heaphdr(ctx, index, DUK_TAG_BUFFER, 0);
}

/* internal */
duk_hthread *duk_get_hthread(duk_context *ctx, int index) {
	duk_hobject *h = (duk_hobject *) get_tagged_heaphdr(ctx, index, DUK_TAG_OBJECT, 0);
	if (!DUK_HOBJECT_IS_THREAD(h)) {
		return NULL;
	}
	return (duk_hthread *) h;
}

/* internal */
duk_hthread *duk_require_hthread(duk_context *ctx, int index) {
	duk_hthread *thr = (duk_hthread *) ctx;
	duk_hobject *h = (duk_hobject *) get_tagged_heaphdr(ctx, index, DUK_TAG_OBJECT, 0);
	if (!DUK_HOBJECT_IS_THREAD(h)) {
		DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, "incorrect type, expected thread");
	}
	return (duk_hthread *) h;
}

/* internal */
duk_hcompiledfunction *duk_get_hcompiledfunction(duk_context *ctx, int index) {
	duk_hobject *h = (duk_hobject *) get_tagged_heaphdr(ctx, index, DUK_TAG_OBJECT, 0);
	if (!DUK_HOBJECT_IS_COMPILEDFUNCTION(h)) {
		return NULL;
	}
	return (duk_hcompiledfunction *) h;
}

/* internal */
duk_hcompiledfunction *duk_require_hcompiledfunction(duk_context *ctx, int index) {
	duk_hthread *thr = (duk_hthread *) ctx;
	duk_hobject *h = (duk_hobject *) get_tagged_heaphdr(ctx, index, DUK_TAG_OBJECT, 0);
	if (!DUK_HOBJECT_IS_COMPILEDFUNCTION(h)) {
		DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, "incorrect type, expected compiledfunction");
	}
	return (duk_hcompiledfunction *) h;
}

/* internal */
duk_hnativefunction *duk_get_hnativefunction(duk_context *ctx, int index) {
	duk_hobject *h = (duk_hobject *) get_tagged_heaphdr(ctx, index, DUK_TAG_OBJECT, 0);
	if (!DUK_HOBJECT_IS_NATIVEFUNCTION(h)) {
		return NULL;
	}
	return (duk_hnativefunction *) h;
}

/* internal */
duk_hnativefunction *duk_require_hnativefunction(duk_context *ctx, int index) {
	duk_hthread *thr = (duk_hthread *) ctx;
	duk_hobject *h = (duk_hobject *) get_tagged_heaphdr(ctx, index, DUK_TAG_OBJECT, 0);
	if (!DUK_HOBJECT_IS_NATIVEFUNCTION(h)) {
		DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, "incorrect type, expected nativefunction");
	}
	return (duk_hnativefunction *) h;
}

/* about 300 bytes, worth it? */
void duk_get_multiple(duk_context *ctx, int start_index, const char *types, ...) {
	va_list ap;
	duk_hthread *thr;
	const char *p;
	int index;

	DUK_ASSERT(ctx != NULL);
	DUK_ASSERT(types != NULL);

	thr = (duk_hthread *) ctx;
	index = duk_require_normalize_index(ctx, start_index);

	va_start(ap, types);

	p = types;
	for (;;) {
		unsigned int ch = (unsigned int) (*p++);
		switch (ch) {
		case 'u': {
			/* no effect */
			break;
		}
		case 'n': {
			/* no effect */
			break;
		}
		case 'b': {
			int *out = va_arg(ap, int *);
			DUK_ASSERT(out);
			*out = duk_get_boolean(ctx, index);
			break;
		}
		case 'd': {
			double *out = va_arg(ap, double *);
			DUK_ASSERT(out);
			*out = duk_get_number(ctx, index);
			break;
		}
		case 'i': {
			int *out = va_arg(ap, int *);
			DUK_ASSERT(out);
			*out = duk_get_int(ctx, index);
			break;
		}
		case 's': {
			const char **out = va_arg(ap, const char **);
			DUK_ASSERT(out);
			*out = duk_get_string(ctx, index);
			break;
		}
		case 'l': {
			const char **out1 = va_arg(ap, const char **);
			size_t *out2 = va_arg(ap, size_t *);
			DUK_ASSERT(out1);
			DUK_ASSERT(out2);
			*out1 = duk_get_lstring(ctx, index, out2);
			break;
		}
		case 'p': {
			void **out = va_arg(ap, void **);
			DUK_ASSERT(out);
			*out = duk_get_pointer(ctx, index);
			break;
		}
		case '-': {
			break;
		}
		case 0: {
			goto done;
		}
		default: {
			DUK_ERROR(thr, DUK_ERR_API_ERROR, "invalid type char: %d", ch);
		}
		}

		index++;
	}
 done:

	va_end(ap);
}

duk_c_function duk_get_c_function(duk_context *ctx, int index) {
	duk_tval *tv;
	duk_hobject *h;
	duk_hnativefunction *f;

	DUK_ASSERT(ctx != NULL);

	tv = duk_get_tval(ctx, index);
	if (!tv) {
		return NULL;
	}
	if (!DUK_TVAL_IS_OBJECT(tv)) {
		return NULL;
	}
	h = DUK_TVAL_GET_OBJECT(tv);
	DUK_ASSERT(h != NULL);
	
	if (!DUK_HOBJECT_IS_NATIVEFUNCTION(h)) {
		return NULL;
	}
	DUK_ASSERT(DUK_HOBJECT_HAS_NATIVEFUNCTION(h));
	f = (duk_hnativefunction *) h;

	return f->func;
}

duk_c_function duk_require_c_function(duk_context *ctx, int index) {
	duk_hthread *thr = (duk_hthread *) ctx;
	duk_c_function ret;

	ret = duk_get_c_function(ctx, index);
	if (!ret) {
		DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, "incorrect type, expected c function");
	}
	return ret;
}

duk_context *duk_get_context(duk_context *ctx, int index) {
	duk_hobject *h;

	h = duk_get_hobject(ctx, index);
	if (!h) {
		return NULL;
	}
	if (!DUK_HOBJECT_HAS_THREAD(h)) {
		return NULL;
	}
	return (duk_context *) h;
}

duk_context *duk_require_context(duk_context *ctx, int index) {
	return (duk_context *) duk_require_hthread(ctx, index);
}

size_t duk_get_length(duk_context *ctx, int index) {
	duk_tval *tv;

	DUK_ASSERT(ctx != NULL);

	tv = duk_get_tval(ctx, index);
	if (!tv) {
		return 0;
	}

	switch (DUK_TVAL_GET_TAG(tv)) {
	case DUK_TAG_UNDEFINED:
	case DUK_TAG_NULL:
	case DUK_TAG_BOOLEAN:
	case DUK_TAG_POINTER:
		return 0;
	case DUK_TAG_STRING: {
		duk_hstring *h = DUK_TVAL_GET_STRING(tv);
		DUK_ASSERT(h != NULL);
		return (size_t) DUK_HSTRING_GET_CHARLEN(h);
	}
	case DUK_TAG_OBJECT: {
		duk_hobject *h = DUK_TVAL_GET_OBJECT(tv);
		DUK_ASSERT(h != NULL);
		return (size_t) duk_hobject_get_length((duk_hthread *) ctx, h);
	}
	case DUK_TAG_BUFFER: {
		duk_hbuffer *h = DUK_TVAL_GET_BUFFER(tv);
		DUK_ASSERT(h != NULL);
		return (size_t) DUK_HBUFFER_GET_SIZE(h);
	}
	default:
		/* number */
		DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv));
		return 0;
	}

	return 0;
}

/*
 *  Conversions and coercions
 *
 *  The conversion/coercions are in-place operations on the value stack.
 *  Some operations are implemented here directly, while others call a
 *  helper in duk_js_ops.c after validating arguments.
 */

/* E5 Section 8.12.8 */

static int defaultvalue_coerce_attempt(duk_context *ctx, int index, int func_stridx) {
	if (duk_get_prop_stridx(ctx, index, func_stridx)) {
		/* [ ... func ] */
		if (duk_is_callable(ctx, -1)) {
			duk_dup(ctx, index);         /* -> [ ... func this ] */
			duk_call_method(ctx, 0);     /* -> [ ... retval ] */
			if (duk_is_primitive(ctx, -1)) {
				duk_replace(ctx, index);
				return 1;
			}
			/* [ ... retval ]; popped below */
		}
	}
	duk_pop(ctx);  /* [ ... func/retval ] -> [ ... ] */
	return 0;
}

void duk_to_defaultvalue(duk_context *ctx, int index, int hint) {
	duk_hthread *thr = (duk_hthread *) ctx;
	duk_hobject *obj;
	int coercers[] = { DUK_STRIDX_VALUE_OF, DUK_STRIDX_TO_STRING };

	DUK_ASSERT(ctx != NULL);
	DUK_ASSERT(thr != NULL);

	index = duk_require_normalize_index(ctx, index);

	if (!duk_is_object(ctx, index)) {
		DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, "not object");
	}
	obj = duk_get_hobject(ctx, index);
	DUK_ASSERT(obj != NULL);

	if (hint == DUK_HINT_NONE) {
		if (DUK_HOBJECT_GET_CLASS_NUMBER(obj) == DUK_HOBJECT_CLASS_DATE) {
			hint = DUK_HINT_STRING;
		} else {
			hint = DUK_HINT_NUMBER;
		}
	}

	if (hint == DUK_HINT_STRING) {
		coercers[0] = DUK_STRIDX_TO_STRING;
		coercers[1] = DUK_STRIDX_VALUE_OF;
	}

	if (defaultvalue_coerce_attempt(ctx, index, coercers[0])) {
		return;
	}

	if (defaultvalue_coerce_attempt(ctx, index, coercers[1])) {
		return;
	}

	DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, "failed to coerce with [[DefaultValue]]");
}

void duk_to_undefined(duk_context *ctx, int index) {
	duk_hthread *thr = (duk_hthread *) ctx;
	duk_tval *tv;
	duk_tval tv_temp;

	DUK_ASSERT(ctx != NULL);
	DUK_UNREF(thr);

	tv = duk_require_tval(ctx, index);
	DUK_ASSERT(tv != NULL);
	DUK_TVAL_SET_TVAL(&tv_temp, tv);
	DUK_TVAL_SET_UNDEFINED_ACTUAL(tv);
	DUK_TVAL_DECREF(thr, &tv_temp);
}

void duk_to_null(duk_context *ctx, int index) {
	duk_hthread *thr = (duk_hthread *) ctx;
	duk_tval *tv;
	duk_tval tv_temp;

	DUK_ASSERT(ctx != NULL);
	DUK_UNREF(thr);

	tv = duk_require_tval(ctx, index);
	DUK_ASSERT(tv != NULL);
	DUK_TVAL_SET_TVAL(&tv_temp, tv);
	DUK_TVAL_SET_NULL(tv);
	DUK_TVAL_DECREF(thr, &tv_temp);
}

/* E5 Section 9.1 */
void duk_to_primitive(duk_context *ctx, int index, int hint) {
	duk_tval *tv;

	DUK_ASSERT(ctx != NULL);
	DUK_ASSERT(hint == DUK_HINT_NONE || hint == DUK_HINT_NUMBER || hint == DUK_HINT_STRING);

	index = duk_require_normalize_index(ctx, index);

	tv = duk_require_tval(ctx, index);
	DUK_ASSERT(tv != NULL);

	if (DUK_TVAL_GET_TAG(tv) != DUK_TAG_OBJECT) {
		/* everything except object stay as is */
		return;
	}
	DUK_ASSERT(DUK_TVAL_IS_OBJECT(tv));

	duk_to_defaultvalue(ctx, index, hint);
}

/* E5 Section 9.2 */
int duk_to_boolean(duk_context *ctx, int index) {
	duk_hthread *thr = (duk_hthread *) ctx;
	duk_tval *tv;
	duk_tval tv_temp;
	int val;

	DUK_ASSERT(ctx != NULL);
	DUK_UNREF(thr);

	index = duk_require_normalize_index(ctx, index);

	tv = duk_require_tval(ctx, index);
	DUK_ASSERT(tv != NULL);

	val = duk_js_toboolean(tv);

	/* Note: no need to re-lookup tv, conversion is side effect free */
	DUK_ASSERT(tv != NULL);
	DUK_TVAL_SET_TVAL(&tv_temp, tv);
	DUK_TVAL_SET_BOOLEAN(tv, val);
	DUK_TVAL_DECREF(thr, &tv_temp);
	return val;
}

double duk_to_number(duk_context *ctx, int index) {
	duk_hthread *thr = (duk_hthread *) ctx;
	duk_tval *tv;
	duk_tval tv_temp;
	double d;

	DUK_ASSERT(ctx != NULL);

	tv = duk_require_tval(ctx, index);
	DUK_ASSERT(tv != NULL);
	d = duk_js_tonumber(thr, tv);

	/* Note: need to re-lookup because ToNumber() may have side effects */
	tv = duk_require_tval(ctx, index);
	DUK_TVAL_SET_TVAL(&tv_temp, tv);
	DUK_TVAL_SET_NUMBER(tv, d);  /* no need to incref */
	DUK_TVAL_DECREF(thr, &tv_temp);

	return d;
}

/* FIXME: combine all the integer conversions: they share everything
 * but the helper function for coercion.
 */

int duk_to_int(duk_context *ctx, int index) {
	duk_hthread *thr = (duk_hthread *) ctx;
	duk_tval *tv;
	duk_tval tv_temp;
	double d;

	DUK_ASSERT(ctx != NULL);

	tv = duk_require_tval(ctx, index);
	DUK_ASSERT(tv != NULL);
	d = duk_js_tointeger(thr, tv);  /* E5 Section 9.4, ToInteger() */

	/* relookup in case duk_js_tointeger() ends up e.g. coercing an object */
	tv = duk_require_tval(ctx, index);
	DUK_TVAL_SET_TVAL(&tv_temp, tv);
	DUK_TVAL_SET_NUMBER(tv, d);  /* no need to incref */
	DUK_TVAL_DECREF(thr, &tv_temp);

	/* Custom coercion for API */
	return api_coerce_d2i(d);
}

int duk_to_int32(duk_context *ctx, int index) {
	duk_hthread *thr = (duk_hthread *) ctx;
	duk_tval *tv;
	duk_tval tv_temp;
	double d;

	DUK_ASSERT(ctx != NULL);

	tv = duk_require_tval(ctx, index);
	DUK_ASSERT(tv != NULL);
	d = (double) duk_js_toint32(thr, tv);

	/* must relookup */
	tv = duk_require_tval(ctx, index);
	DUK_TVAL_SET_TVAL(&tv_temp, tv);
	DUK_TVAL_SET_NUMBER(tv, d);  /* no need to incref */
	DUK_TVAL_DECREF(thr, &tv_temp);

	/* ToInt32() should already have restricted the result to
	 * an acceptable range (unless 'int' is less than 32 bits).
	 */
	return (int) d;
}

unsigned int duk_to_uint32(duk_context *ctx, int index) {
	duk_hthread *thr = (duk_hthread *) ctx;
	duk_tval *tv;
	duk_tval tv_temp;
	double d;

	DUK_ASSERT(ctx != NULL);

	tv = duk_require_tval(ctx, index);
	DUK_ASSERT(tv != NULL);
	d = (double) duk_js_touint32(thr, tv);

	/* must relookup */
	tv = duk_require_tval(ctx, index);
	DUK_TVAL_SET_TVAL(&tv_temp, tv);
	DUK_TVAL_SET_NUMBER(tv, d);  /* no need to incref */
	DUK_TVAL_DECREF(thr, &tv_temp);

	/* ToUint32() should already have restricted the result to
	 * an acceptable range (unless 'unsigned int' is less than 32 bits).
	 */
	return (unsigned int) d;
}

unsigned int duk_to_uint16(duk_context *ctx, int index) {
	duk_hthread *thr = (duk_hthread *) ctx;
	duk_tval *tv;
	duk_tval tv_temp;
	double d;

	DUK_ASSERT(ctx != NULL);

	tv = duk_require_tval(ctx, index);
	DUK_ASSERT(tv != NULL);
	d = (double) duk_js_touint16(thr, tv);

	/* must relookup */
	tv = duk_require_tval(ctx, index);
	DUK_TVAL_SET_TVAL(&tv_temp, tv);
	DUK_TVAL_SET_NUMBER(tv, d);  /* no need to incref */
	DUK_TVAL_DECREF(thr, &tv_temp);

	/* ToUint32() should already have restricted the result to
	 * an acceptable range (unless 'unsigned int' is less than 32 bits).
	 */
	return (unsigned int) d;
}

const char *duk_to_lstring(duk_context *ctx, int index, size_t *out_len) {
	duk_to_string(ctx, index);
	return duk_require_lstring(ctx, index, out_len);
}

/* FIXME: other variants like uint, u32 etc */
int duk_to_int_clamped_raw(duk_context *ctx, int index, int minval, int maxval, int *out_clamped) {
	duk_hthread *thr = (duk_hthread *) ctx;
	duk_tval *tv;
	duk_tval tv_temp;
	double d;
	int clamped = 0;

	DUK_ASSERT(ctx != NULL);

	tv = duk_require_tval(ctx, index);
	DUK_ASSERT(tv != NULL);
	d = duk_js_tointeger(thr, tv);  /* E5 Section 9.4, ToInteger() */

	if (d < (double) minval) {
		clamped = 1;
		d = (double) minval;
	} else if (d > (double) maxval) {
		clamped = 1;
		d = (double) maxval;
	}

	/* relookup in case duk_js_tointeger() ends up e.g. coercing an object */
	tv = duk_require_tval(ctx, index);
	DUK_TVAL_SET_TVAL(&tv_temp, tv);
	DUK_TVAL_SET_NUMBER(tv, d);  /* no need to incref */
	DUK_TVAL_DECREF(thr, &tv_temp);

	if (out_clamped) {
		*out_clamped = clamped;
	} else {
		/* coerced value is updated to value stack even when RangeError thrown */
		if (clamped) {
			DUK_ERROR(thr, DUK_ERR_RANGE_ERROR, "number outside range");
		}
	}

	return (int) d;
}

int duk_to_int_clamped(duk_context *ctx, int index, int minval, int maxval) {
	int dummy;
	return duk_to_int_clamped_raw(ctx, index, minval, maxval, &dummy);
}

int duk_to_int_check_range(duk_context *ctx, int index, int minval, int maxval) {
	return duk_to_int_clamped_raw(ctx, index, minval, maxval, NULL);  /* NULL -> RangeError */
}

const char *duk_to_string(duk_context *ctx, int index) {
	duk_tval *tv;

	DUK_ASSERT(ctx != NULL);

	index = duk_require_normalize_index(ctx, index);

	tv = duk_require_tval(ctx, index);
	DUK_ASSERT(tv != NULL);

	switch (DUK_TVAL_GET_TAG(tv)) {
	case DUK_TAG_UNDEFINED: {
		duk_push_hstring_stridx(ctx, DUK_STRIDX_UNDEFINED);
		break;
	}
	case DUK_TAG_NULL: {
		duk_push_hstring_stridx(ctx, DUK_STRIDX_NULL);
		break;
	}
	case DUK_TAG_BOOLEAN: {
		if (DUK_TVAL_GET_BOOLEAN(tv)) {
			duk_push_hstring_stridx(ctx, DUK_STRIDX_TRUE);
		} else {
			duk_push_hstring_stridx(ctx, DUK_STRIDX_FALSE);
		}
		break;
	}
	case DUK_TAG_STRING: {
		/* nop */
		goto skip_replace;
	}
	case DUK_TAG_OBJECT: {
		duk_to_primitive(ctx, index, DUK_HINT_STRING);
		return duk_to_string(ctx, index);  /* Note: recursive call */
	}
	case DUK_TAG_BUFFER: {
		duk_hbuffer *h = DUK_TVAL_GET_BUFFER(tv);

		/* Note: this currently allows creation of internal strings. */

		DUK_ASSERT(h != NULL);
		duk_push_lstring(ctx,
		                 (const char *) DUK_HBUFFER_GET_DATA_PTR(h),
		                 (unsigned int) DUK_HBUFFER_GET_SIZE(h));
		break;
	}
	case DUK_TAG_POINTER: {
		duk_push_sprintf(ctx, "%p", (void *) DUK_TVAL_GET_POINTER(tv));
		break;
	}
	default: {
		/* number */
		DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv));
		duk_push_tval(ctx, tv);
		duk_numconv_stringify(ctx,
		                      10 /*radix*/,
		                      0 /*precision:shortest*/,
		                      0 /*force_exponential*/);
		break;
	}
	}

	duk_replace(ctx, index);

 skip_replace:
	return duk_require_string(ctx, index);
}

/* internal */
duk_hstring *duk_to_hstring(duk_context *ctx, int index) {
	duk_hstring *ret;
	DUK_ASSERT(ctx != NULL);
	duk_to_string(ctx, index);
	ret = duk_get_hstring(ctx, index);
	DUK_ASSERT(ret != NULL);
	return ret;
}

void *duk_to_buffer(duk_context *ctx, int index, size_t *out_size) {
	duk_hbuffer *h_buf;

	index = duk_require_normalize_index(ctx, index);

	if (duk_is_buffer(ctx, index)) {
		/* Buffer is kept as is: note that fixed/dynamic nature of
		 * the buffer is not changed.
		 */
	} else {
		/* Non-buffer value is first ToString() coerced, then converted to
		 * a fixed size buffer.
		 */
		duk_hstring *h_str;
		void *buf;

		duk_to_string(ctx, index);
		h_str = duk_get_hstring(ctx, index);
		DUK_ASSERT(h_str != NULL);

		buf = duk_push_fixed_buffer(ctx, DUK_HSTRING_GET_BYTELEN(h_str));
		DUK_ASSERT(buf != NULL);
		memcpy(buf, DUK_HSTRING_GET_DATA(h_str), DUK_HSTRING_GET_BYTELEN(h_str));
		duk_replace(ctx, index);
	}

	h_buf = duk_get_hbuffer(ctx, index);
	DUK_ASSERT(h_buf != NULL);

	if (out_size) {
		*out_size = DUK_HBUFFER_GET_SIZE(h_buf);
	}
	return DUK_HBUFFER_GET_DATA_PTR(h_buf);
}

void *duk_to_pointer(duk_context *ctx, int index) {
	duk_tval *tv;
	void *res;

	DUK_ASSERT(ctx != NULL);

	index = duk_require_normalize_index(ctx, index);

	tv = duk_require_tval(ctx, index);
	DUK_ASSERT(tv != NULL);

	switch (DUK_TVAL_GET_TAG(tv)) {
	case DUK_TAG_UNDEFINED:
	case DUK_TAG_NULL:
	case DUK_TAG_BOOLEAN:
		res = NULL;
		break;
	case DUK_TAG_POINTER:
		res = DUK_TVAL_GET_POINTER(tv);
		break;
	case DUK_TAG_STRING:
	case DUK_TAG_OBJECT:
	case DUK_TAG_BUFFER:
		/* heap allocated: return heap pointer which is NOT useful
		 * for the caller, except for debugging.
		 */
		res = (void *) DUK_TVAL_GET_HEAPHDR(tv);
		break;
	default:
		/* number */
		res = NULL;
		break;
	}

	duk_push_pointer(ctx, res);
	duk_replace(ctx, index);
	return res;
}

void duk_to_object(duk_context *ctx, int index) {
	duk_hthread *thr = (duk_hthread *) ctx;
	duk_tval *tv;
	duk_hobject *res;

	DUK_ASSERT(ctx != NULL);

	index = duk_require_normalize_index(ctx, index);

	tv = duk_require_tval(ctx, index);
	DUK_ASSERT(tv != NULL);

	switch (DUK_TVAL_GET_TAG(tv)) {
	case DUK_TAG_UNDEFINED:
	case DUK_TAG_NULL:
	case DUK_TAG_BUFFER:
	case DUK_TAG_POINTER: {
		DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, "attempt to coerce incompatible value to object");
		break;
	}
	case DUK_TAG_BOOLEAN: {
		int val = DUK_TVAL_GET_BOOLEAN(tv);

		(void) duk_push_object_helper(ctx,
		                              DUK_HOBJECT_FLAG_EXTENSIBLE |
		                              DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_BOOLEAN),
		                              DUK_BIDX_BOOLEAN_PROTOTYPE);
		res = duk_require_hobject(ctx, -1);
		DUK_ASSERT(res != NULL);

		/* Note: Boolean prototype's internal value property is not writable,
		 * but duk_def_prop_stridx() disregards the write protection.  Boolean
		 * instances are immutable.
		 */
		duk_push_boolean(ctx, val);
		duk_def_prop_stridx(ctx, -2, DUK_STRIDX_INT_VALUE, DUK_PROPDESC_FLAGS_NONE);

		duk_replace(ctx, index);
		break;
	}
	case DUK_TAG_STRING: {
		(void) duk_push_object_helper(ctx,
		                              DUK_HOBJECT_FLAG_EXTENSIBLE |
		                              DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_STRING),
		                              DUK_BIDX_STRING_PROTOTYPE);
		res = duk_require_hobject(ctx, -1);
		DUK_ASSERT(res != NULL);

		/* Note: String prototype's internal value property is not writable,
		 * but duk_def_prop_stridx() disregards the write protection.  String
		 * instances are immutable.
		 */
		duk_dup(ctx, index);
		duk_def_prop_stridx(ctx, -2, DUK_STRIDX_INT_VALUE, DUK_PROPDESC_FLAGS_NONE);

		/* Enable special string behavior only after internal value has been set */
		DUK_HOBJECT_SET_SPECIAL_STRINGOBJ(res);

		duk_replace(ctx, index);
		break;
	}
	case DUK_TAG_OBJECT: {
		/* nop */
		break;
	}
	default: {
		/* number */
		double val;

		DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv));
		val = DUK_TVAL_GET_NUMBER(tv);

		(void) duk_push_object_helper(ctx,
		                              DUK_HOBJECT_FLAG_EXTENSIBLE |
		                              DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_NUMBER),
		                              DUK_BIDX_NUMBER_PROTOTYPE);
		res = duk_require_hobject(ctx, -1);
		DUK_ASSERT(res != NULL);

		/* Note: Number prototype's internal value property is not writable,
		 * but duk_def_prop_stridx() disregards the write protection.  Number
		 * instances are immutable.
		 */
		duk_push_number(ctx, val);
		duk_def_prop_stridx(ctx, -2, DUK_STRIDX_INT_VALUE, DUK_PROPDESC_FLAGS_NONE);

		duk_replace(ctx, index);
		break;
	}
	}
}

/*
 *  Type checking
 */

static int _tag_check(duk_context *ctx, int index, int tag) {
	duk_tval *tv;

	tv = duk_get_tval(ctx, index);
	if (!tv) {
		return 0;
	}
	return (DUK_TVAL_GET_TAG(tv) == tag);
}

static int _obj_flag_any_default_false(duk_context *ctx, int index, int flag_mask) {
	duk_hobject *obj;

	DUK_ASSERT(ctx != NULL);

	obj = duk_get_hobject(ctx, index);
	if (obj) {
		return (DUK_HEAPHDR_CHECK_FLAG_BITS((duk_heaphdr *) obj, flag_mask) ? 1 : 0);
	}
	return 0;
}

int duk_get_type(duk_context *ctx, int index) {
	duk_tval *tv;

	tv = duk_get_tval(ctx, index);
	if (!tv) {
		return DUK_TYPE_NONE;
	}
	switch (DUK_TVAL_GET_TAG(tv)) {
	case DUK_TAG_UNDEFINED:
		return DUK_TYPE_UNDEFINED;
	case DUK_TAG_NULL:
		return DUK_TYPE_NULL;
	case DUK_TAG_BOOLEAN:
		return DUK_TYPE_BOOLEAN;
	case DUK_TAG_STRING:
		return DUK_TYPE_STRING;
	case DUK_TAG_OBJECT:
		return DUK_TYPE_OBJECT;
	case DUK_TAG_BUFFER:
		return DUK_TYPE_BUFFER;
	case DUK_TAG_POINTER:
		return DUK_TYPE_POINTER;
	default:
		/* Note: number has no explicit tag (in 8-byte representation) */
		DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv));
		return DUK_TYPE_NUMBER;
	}
	DUK_NEVER_HERE();
}

int duk_get_type_mask(duk_context *ctx, int index) {
	duk_tval *tv;

	tv = duk_get_tval(ctx, index);
	if (!tv) {
		return DUK_TYPE_MASK_NONE;
	}
	switch (DUK_TVAL_GET_TAG(tv)) {
	case DUK_TAG_UNDEFINED:
		return DUK_TYPE_MASK_UNDEFINED;
	case DUK_TAG_NULL:
		return DUK_TYPE_MASK_NULL;
	case DUK_TAG_BOOLEAN:
		return DUK_TYPE_MASK_BOOLEAN;
	case DUK_TAG_STRING:
		return DUK_TYPE_MASK_STRING;
	case DUK_TAG_OBJECT:
		return DUK_TYPE_MASK_OBJECT;
	case DUK_TAG_BUFFER:
		return DUK_TYPE_MASK_BUFFER;
	case DUK_TAG_POINTER:
		return DUK_TYPE_MASK_POINTER;
	default:
		/* Note: number has no explicit tag (in 8-byte representation) */
		DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv));
		return DUK_TYPE_MASK_NUMBER;
	}
	DUK_NEVER_HERE();
}

int duk_is_undefined(duk_context *ctx, int index) {
	DUK_ASSERT(ctx != NULL);
	return _tag_check(ctx, index, DUK_TAG_UNDEFINED);
}

int duk_is_null(duk_context *ctx, int index) {
	DUK_ASSERT(ctx != NULL);
	return _tag_check(ctx, index, DUK_TAG_NULL);
}

int duk_is_null_or_undefined(duk_context *ctx, int index) {
	duk_tval *tv;

	tv = duk_get_tval(ctx, index);
	if (!tv) {
		return 0;
	}
	return (DUK_TVAL_GET_TAG(tv) == DUK_TAG_UNDEFINED) ||
	       (DUK_TVAL_GET_TAG(tv) == DUK_TAG_NULL);
}

int duk_is_boolean(duk_context *ctx, int index) {
	DUK_ASSERT(ctx != NULL);
	return _tag_check(ctx, index, DUK_TAG_BOOLEAN);
}

int duk_is_number(duk_context *ctx, int index) {
	duk_tval *tv;

	DUK_ASSERT(ctx != NULL);

	/*
	 *  Number is special because it doesn't have a specific
	 *  tag in the 8-byte representation.
	 */

	/* XXX: shorter version for 12-byte representation? */

	tv = duk_get_tval(ctx, index);
	if (!tv) {
		return 0;
	}
	return DUK_TVAL_IS_NUMBER(tv);
}

int duk_is_nan(duk_context *ctx, int index) {
	/* FIXME: make more compact */
	/* FIXME: this will now return false for non-numbers, even though they would
	 * coerce to NaN.  In particular, duk_get_number() returns a NaN for
	 * non-numbers, so should this also return true for non-numbers?
	 */
	return duk_is_number(ctx, index) && isnan(duk_get_number(ctx, index));
}

int duk_is_string(duk_context *ctx, int index) {
	DUK_ASSERT(ctx != NULL);
	return _tag_check(ctx, index, DUK_TAG_STRING);
}

int duk_is_object(duk_context *ctx, int index) {
	DUK_ASSERT(ctx != NULL);
	return _tag_check(ctx, index, DUK_TAG_OBJECT);
}

int duk_is_buffer(duk_context *ctx, int index) {
	DUK_ASSERT(ctx != NULL);
	return _tag_check(ctx, index, DUK_TAG_BUFFER);
}

int duk_is_pointer(duk_context *ctx, int index) {
	DUK_ASSERT(ctx != NULL);
	return _tag_check(ctx, index, DUK_TAG_POINTER);
}

int duk_is_array(duk_context *ctx, int index) {
	duk_hobject *obj;

	DUK_ASSERT(ctx != NULL);

	obj = duk_get_hobject(ctx, index);
	if (obj) {
		return (DUK_HOBJECT_GET_CLASS_NUMBER(obj) == DUK_HOBJECT_CLASS_ARRAY ? 1 : 0);
	}
	return 0;
}

int duk_is_function(duk_context *ctx, int index) {
	return _obj_flag_any_default_false(ctx,
	                                   index,
	                                   DUK_HOBJECT_FLAG_COMPILEDFUNCTION |
	                                   DUK_HOBJECT_FLAG_NATIVEFUNCTION |
	                                   DUK_HOBJECT_FLAG_BOUND);
}

int duk_is_c_function(duk_context *ctx, int index) {
	return _obj_flag_any_default_false(ctx,
	                                   index,
	                                   DUK_HOBJECT_FLAG_NATIVEFUNCTION);
}

int duk_is_ecmascript_function(duk_context *ctx, int index) {
	return _obj_flag_any_default_false(ctx,
	                                   index,
	                                   DUK_HOBJECT_FLAG_COMPILEDFUNCTION);
}

int duk_is_bound_function(duk_context *ctx, int index) {
	return _obj_flag_any_default_false(ctx,
	                                   index,
	                                   DUK_HOBJECT_FLAG_BOUND);
}

int duk_is_thread(duk_context *ctx, int index) {
	return _obj_flag_any_default_false(ctx,
	                                   index,
	                                   DUK_HOBJECT_FLAG_THREAD);
}

int duk_is_callable(duk_context *ctx, int index) {
	/* XXX: currently same as duk_is_function() */
	return _obj_flag_any_default_false(ctx,
	                                   index,
	                                   DUK_HOBJECT_FLAG_COMPILEDFUNCTION |
	                                   DUK_HOBJECT_FLAG_NATIVEFUNCTION |
	                                   DUK_HOBJECT_FLAG_BOUND);
}

int duk_is_dynamic(duk_context *ctx, int index) {
	duk_tval *tv;

	DUK_ASSERT(ctx != NULL);

	tv = duk_get_tval(ctx, index);
	if (DUK_TVAL_IS_BUFFER(tv)) {
		duk_hbuffer *h = DUK_TVAL_GET_BUFFER(tv);
		DUK_ASSERT(h != NULL);
		return (DUK_HBUFFER_HAS_DYNAMIC(h) ? 1 : 0);
	}
	return 0;
}

int duk_is_fixed(duk_context *ctx, int index) {
	duk_tval *tv;

	DUK_ASSERT(ctx != NULL);

	tv = duk_get_tval(ctx, index);
	if (DUK_TVAL_IS_BUFFER(tv)) {
		duk_hbuffer *h = DUK_TVAL_GET_BUFFER(tv);
		DUK_ASSERT(h != NULL);
		return (DUK_HBUFFER_HAS_DYNAMIC(h) ? 0 : 1);
	}
	return 0;
}

int duk_is_primitive(duk_context *ctx, int index) {
	return !duk_is_object(ctx, index);
}

int duk_is_object_coercible(duk_context *ctx, int index) {
	int mask = DUK_TYPE_MASK_BOOLEAN |
	           DUK_TYPE_MASK_NUMBER |
	           DUK_TYPE_MASK_STRING |
	           DUK_TYPE_MASK_OBJECT;
	/* FIXME: what about buffer and pointer? */

	return (duk_get_type_mask(ctx, index) & mask ? 1 : 0);
}

/*
 *  Pushers
 */

/* internal */
void duk_push_tval(duk_context *ctx, duk_tval *tv) {
	duk_hthread *thr = (duk_hthread *) ctx;
	duk_tval *tv_slot;

	DUK_ASSERT(ctx != NULL);
	DUK_ASSERT(tv != NULL);

	if (thr->valstack_top >= thr->valstack_end) {
		DUK_ERROR(thr, DUK_ERR_API_ERROR, "attempt to push beyond currently allocated stack");
	}

	tv_slot = thr->valstack_top;
	DUK_TVAL_SET_TVAL(tv_slot, tv);
	DUK_TVAL_INCREF(thr, tv);
	thr->valstack_top++;
}

/* internal */
void duk_push_unused(duk_context *ctx) {
	duk_tval tv;
	DUK_ASSERT(ctx != NULL);
	DUK_TVAL_SET_UNDEFINED_ACTUAL(&tv);
	duk_push_tval(ctx, &tv);
}

void duk_push_undefined(duk_context *ctx) {
	duk_tval tv;
	DUK_ASSERT(ctx != NULL);
	DUK_TVAL_SET_UNDEFINED_ACTUAL(&tv);  /* XXX: heap constant would be nice */
	duk_push_tval(ctx, &tv);
}

void duk_push_null(duk_context *ctx) {
	duk_tval tv;
	DUK_ASSERT(ctx != NULL);
	DUK_TVAL_SET_NULL(&tv);  /* XXX: heap constant would be nice */
	duk_push_tval(ctx, &tv);
}

void duk_push_boolean(duk_context *ctx, int val) {
	duk_tval tv;
	int b = (val ? 1 : 0);
	DUK_ASSERT(ctx != NULL);
	DUK_TVAL_SET_BOOLEAN(&tv, b);
	duk_push_tval(ctx, &tv);
}

void duk_push_true(duk_context *ctx) {
	duk_push_boolean(ctx, 1);
}

void duk_push_false(duk_context *ctx) {
	duk_push_boolean(ctx, 0);
}

void duk_push_number(duk_context *ctx, double val) {
	duk_tval tv;
	DUK_ASSERT(ctx != NULL);

	/* normalize NaN which may not match our canonical internal NaN */
	DUK_DOUBLE_NORMALIZE_NAN_CHECK(&val);

	DUK_TVAL_SET_NUMBER(&tv, val);
	duk_push_tval(ctx, &tv);
}

void duk_push_int(duk_context *ctx, int val) {
	duk_push_number(ctx, (double) val);
}

void duk_push_nan(duk_context *ctx) {
	duk_push_number(ctx, NAN);
}

const char *duk_push_lstring(duk_context *ctx, const char *str, size_t len) {
	duk_hthread *thr = (duk_hthread *) ctx;
	duk_hstring *h;
	duk_tval *tv_slot;

	DUK_ASSERT(ctx != NULL);

	/* check stack before interning (avoid hanging temp) */
	if (thr->valstack_top >= thr->valstack_end) {
		DUK_ERROR(thr, DUK_ERR_API_ERROR, "attempt to push beyond currently allocated stack");
	}

	/* NULL with zero length represents an empty string; NULL with higher
	 * length is also now trated like an empty string although it is
	 * a bit dubious.  This is unlike duk_push_string() which pushes a
	 * 'null' if the input string is a NULL.
	 */
	if (!str) {
		len = 0;
	}

	h = duk_heap_string_intern_checked(thr, (duk_u8 *) str, (duk_u32) len);
	DUK_ASSERT(h != NULL);

	tv_slot = thr->valstack_top;
	DUK_TVAL_SET_STRING(tv_slot, h);
	DUK_HSTRING_INCREF(thr, h);
	thr->valstack_top++;

	return (const char *) DUK_HSTRING_GET_DATA(h);
}

const char *duk_push_string(duk_context *ctx, const char *str) {
	DUK_ASSERT(ctx != NULL);

	if (str) {
		return duk_push_lstring(ctx, str, (unsigned int) strlen(str));
	} else {
		duk_push_null(ctx);
		return NULL;
	}
}

/* This is a bit clunky because it is ANSI C portable.  Should perhaps
 * relocate to another file because this is potentially platform
 * dependent.
 */
const char *duk_push_string_file(duk_context *ctx, const char *path) {
	duk_hthread *thr = (duk_hthread *) ctx;
	FILE *f = NULL;
	char *buf;
	long sz;

	DUK_ASSERT(ctx != NULL);
	if (!path) {
		goto fail;
	}
	f = fopen(path, "rb");
	if (!f) {
		goto fail;
	}
	if (fseek(f, 0, SEEK_END) < 0) {
		goto fail;
	}
	sz = ftell(f);
	if (sz < 0) {
		goto fail;
	}
	if (fseek(f, 0, SEEK_SET) < 0) {
		goto fail;
	}
	buf = duk_push_fixed_buffer(ctx, sz);
	DUK_ASSERT(buf != NULL);
	if (fread(buf, 1, sz, f) != sz) {
		goto fail;
	}
	(void) fclose(f);  /* ignore fclose() error */
	f = NULL;
	return duk_to_string(ctx, -1);

 fail:
	if (f) {
		fclose(f);
	}
	DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, "failed to read file");
	return NULL;
}

void duk_push_pointer(duk_context *ctx, void *val) {
	duk_tval tv;
	DUK_ASSERT(ctx != NULL);

	DUK_TVAL_SET_POINTER(&tv, val);
	duk_push_tval(ctx, &tv);
}

void duk_push_multiple(duk_context *ctx, const char *types, ...) {
	va_list ap;
	duk_hthread *thr;
	const char *p;

	DUK_ASSERT(ctx != NULL);
	DUK_ASSERT(types != NULL);

	thr = (duk_hthread *) ctx;

	va_start(ap, types);

	p = types;
	for (;;) {
		unsigned int ch = (unsigned int) (*p++);
		switch (ch) {
		case 'u': {
			(void) duk_push_undefined(ctx);
			break;
		}
		case 'n': {
			(void) duk_push_null(ctx);
			break;
		}
		case 'b': {
			int val = va_arg(ap, int);
			(void) duk_push_boolean(ctx, val);
			break;
		}
		case 'd': {
			double val = va_arg(ap, double);
			(void) duk_push_number(ctx, val);
			break;
		}
		case 'i': {
			int val = va_arg(ap, int);
			(void) duk_push_int(ctx, val);
			break;
		}
		case 's': {
			const char *val = va_arg(ap, const char *);
			(void) duk_push_string(ctx, val);
			break;
		}
		case 'l': {
			const char *val = va_arg(ap, const char *);
			size_t len = va_arg(ap, size_t);
			(void) duk_push_lstring(ctx, val, len);
			break;
		}
		case 'p': {
			void *val = va_arg(ap, void *);
			(void) duk_push_pointer(ctx, val);
			break;
		}
		case 0: {
			goto done;
		}
		default: {
			DUK_ERROR(thr, DUK_ERR_API_ERROR, "invalid type char: %d", ch);
		}
		}
	}
 done:

	va_end(ap);
}

#define  PUSH_THIS_FLAG_CHECK_COERC  (1 << 0)
#define  PUSH_THIS_FLAG_TO_OBJECT    (1 << 1)
#define  PUSH_THIS_FLAG_TO_STRING    (1 << 2)

static void push_this_helper(duk_context *ctx, int flags) {
	duk_hthread *thr = (duk_hthread *) ctx;

	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(ctx != NULL);
	DUK_ASSERT(thr->callstack_top >= 0 && thr->callstack_top <= thr->callstack_size);

	if (thr->callstack_top == 0) {
		if (flags & PUSH_THIS_FLAG_CHECK_COERC) {
			goto type_error;
		}
		duk_push_undefined(ctx);
	} else {
		duk_tval tv_tmp;
		duk_tval *tv;

		/* 'this' binding is just before current activation's bottom */
		DUK_ASSERT(thr->valstack_bottom > thr->valstack);
		tv = thr->valstack_bottom - 1;
		if (flags & PUSH_THIS_FLAG_CHECK_COERC) {
			if (DUK_TVAL_IS_UNDEFINED(tv) || DUK_TVAL_IS_NULL(tv)) {
				goto type_error;
			}
		}

		DUK_TVAL_SET_TVAL(&tv_tmp, tv);
		duk_push_tval(ctx, &tv_tmp);
	}

	if (flags & PUSH_THIS_FLAG_TO_OBJECT) {
		duk_to_object(ctx, -1);
	} else if (flags & PUSH_THIS_FLAG_TO_STRING) {
		duk_to_string(ctx, -1);
	}

	return;

 type_error:
	DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, "not object coercible");
}

void duk_push_this(duk_context *ctx) {
	push_this_helper(ctx, 0 /*flags*/);
}

void duk_push_this_check_object_coercible(duk_context *ctx) {
	push_this_helper(ctx, PUSH_THIS_FLAG_CHECK_COERC /*flags*/);
}

void duk_push_this_coercible_to_object(duk_context *ctx) {
	push_this_helper(ctx, PUSH_THIS_FLAG_CHECK_COERC |
	                      PUSH_THIS_FLAG_TO_OBJECT /*flags*/);
}

void duk_push_this_coercible_to_string(duk_context *ctx) {
	push_this_helper(ctx, PUSH_THIS_FLAG_CHECK_COERC |
	                      PUSH_THIS_FLAG_TO_STRING /*flags*/);
}

void duk_push_current_function(duk_context *ctx) {
	duk_hthread *thr = (duk_hthread *) ctx;

	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(ctx != NULL);
	DUK_ASSERT(thr->callstack_top >= 0 && thr->callstack_top <= thr->callstack_size);

	if (thr->callstack_top == 0) {
		duk_push_undefined(ctx);
	} else {
		duk_activation *act = thr->callstack + thr->callstack_top - 1;
		DUK_ASSERT(act->func != NULL);
		duk_push_hobject(ctx, act->func);
	}
}

void duk_push_current_thread(duk_context *ctx) {
	duk_hthread *thr = (duk_hthread *) ctx;

	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(ctx != NULL);

	if (thr->heap->curr_thread) {
		duk_push_hobject(ctx, (duk_hobject *) thr->heap->curr_thread);
	} else {
		duk_push_undefined(ctx);
	}
}

void duk_push_global_object(duk_context *ctx) {
	duk_hthread *thr = (duk_hthread *) ctx;

	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(ctx != NULL);

	duk_push_hobject(ctx, thr->builtins[DUK_BIDX_GLOBAL]);
}

static int try_push_vsprintf(duk_context *ctx, void *buf, size_t sz, const char *fmt, va_list ap) {
	int len;

	len = vsnprintf((char *) buf, sz, fmt, ap);
	if (len < sz) {
		return len;
	}
	return -1;
}

const char *duk_push_vsprintf(duk_context *ctx, const char *fmt, va_list ap) {
	duk_hthread *thr = (duk_hthread *) ctx;
	size_t sz = DUK_PUSH_SPRINTF_INITIAL_SIZE;
	void *buf;
	int len;
	const char *res;

	DUK_ASSERT(ctx != NULL);

	/* special handling of fmt==NULL */
	if (!fmt) {
		duk_hstring *h_str;
		duk_push_hstring_stridx(ctx, DUK_STRIDX_EMPTY_STRING);
		h_str = DUK_HTHREAD_STRING_EMPTY_STRING(thr);  /* rely on interning, must be this string */
		return (const char *) DUK_HSTRING_GET_DATA(h_str);
	}

	/* initial estimate based on format string */
	sz = strlen(fmt) + 10;  /* XXX: plus something to avoid just missing */
	if (sz < DUK_PUSH_SPRINTF_INITIAL_SIZE) {
		sz = DUK_PUSH_SPRINTF_INITIAL_SIZE;
	}
	DUK_ASSERT(sz > 0);

	buf = duk_push_dynamic_buffer(ctx, sz);

	for(;;) {
		len = try_push_vsprintf(ctx, buf, sz, fmt, ap);
		if (len >= 0) {
			break;
		}

		/* failed, resize and try again */
		sz = sz * 2;
		if (sz >= DUK_PUSH_SPRINTF_SANITY_LIMIT) {
			DUK_ERROR(thr, DUK_ERR_API_ERROR, "cannot sprintf, required buffer insanely long");
		}

		buf = duk_resize_buffer(ctx, -1, sz);
		DUK_ASSERT(buf != NULL);
	}

	/* FIXME: buffer to string */
	res = duk_push_lstring(ctx, buf, len);  /* [buf res] */
	duk_remove(ctx, -2);
	return res;
}

const char *duk_push_sprintf(duk_context *ctx, const char *fmt, ...) {
	va_list ap;
	const char *retval;

	/* allow fmt==NULL */
	va_start(ap, fmt);
	retval = duk_push_vsprintf(ctx, fmt, ap);
	va_end(ap);

	return retval;
}

/* internal */
int duk_push_object_helper(duk_context *ctx, int hobject_flags_and_class, int prototype_bidx) {
	duk_hthread *thr = (duk_hthread *) ctx;
	duk_tval *tv_slot;
	duk_hobject *h;
	int ret;

	DUK_ASSERT(ctx != NULL);
	DUK_ASSERT(prototype_bidx == -1 ||
	           (prototype_bidx >= 0 && prototype_bidx < DUK_NUM_BUILTINS));

	/* check stack before interning (avoid hanging temp) */
	if (thr->valstack_top >= thr->valstack_end) {
		DUK_ERROR(thr, DUK_ERR_API_ERROR, "attempt to push beyond currently allocated stack");
	}

	h = duk_hobject_alloc(thr->heap, hobject_flags_and_class);
	if (!h) {
		DUK_ERROR(thr, DUK_ERR_ALLOC_ERROR, "failed to allocate an object");
	}

	DUK_DDDPRINT("created object with flags: 0x%08x", h->hdr.h_flags);

	tv_slot = thr->valstack_top;
	DUK_TVAL_SET_OBJECT(tv_slot, h);
	DUK_HOBJECT_INCREF(thr, h);
	ret = (int) (thr->valstack_top - thr->valstack_bottom);
	thr->valstack_top++;

	/* object is now reachable */

	if (prototype_bidx >= 0) {
		DUK_HOBJECT_SET_PROTOTYPE(thr, h, thr->builtins[prototype_bidx]);
	} else {
		DUK_ASSERT(prototype_bidx == -1);
		DUK_ASSERT(h->prototype == NULL);
	}

	return ret;
}

int duk_push_object(duk_context *ctx) {
	return duk_push_object_helper(ctx,
	                              DUK_HOBJECT_FLAG_EXTENSIBLE |
	                              DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_OBJECT),
	                              DUK_BIDX_OBJECT_PROTOTYPE);
}

int duk_push_array(duk_context *ctx) {
	duk_hthread *thr = (duk_hthread *) ctx;
	duk_hobject *obj;
	int ret;

	ret = duk_push_object_helper(ctx,
	                             DUK_HOBJECT_FLAG_EXTENSIBLE |
	                             DUK_HOBJECT_FLAG_ARRAY_PART |
	                             DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_ARRAY),
	                             DUK_BIDX_ARRAY_PROTOTYPE);

	obj = duk_require_hobject(ctx, ret);

	/*
	 *  An array must have a 'length' property (E5 Section 15.4.5.2).
	 *  The special array behavior flag must only be enabled once the
	 *  length property has been added.
	 */

	duk_push_number(ctx, 0.0);
	duk_hobject_define_property_internal(thr,
	                                     obj,
	                                     DUK_HTHREAD_STRING_LENGTH(thr),
	                                     DUK_PROPDESC_FLAGS_W);
	DUK_HOBJECT_SET_SPECIAL_ARRAY(obj);

	return ret;
}

int duk_push_thread(duk_context *ctx) {
	duk_hthread *thr = (duk_hthread *) ctx;
	duk_hthread *obj;
	int ret;
	duk_tval *tv_slot;

	DUK_ASSERT(ctx != NULL);

	/* check stack before interning (avoid hanging temp) */
	if (thr->valstack_top >= thr->valstack_end) {
		DUK_ERROR(thr, DUK_ERR_API_ERROR, "attempt to push beyond currently allocated stack");
	}

	obj = duk_hthread_alloc(thr->heap,
	                        DUK_HOBJECT_FLAG_EXTENSIBLE |
	                        DUK_HOBJECT_FLAG_THREAD |
	                        DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_OBJECT));
	if (!obj) {
		DUK_ERROR(thr, DUK_ERR_ALLOC_ERROR, "failed to allocate a thread object");
	}
	obj->state = DUK_HTHREAD_STATE_INACTIVE;
	obj->strs = thr->strs;
	duk_hthread_copy_builtin_objects(thr, obj);
	DUK_DDDPRINT("created thread object with flags: 0x%08x", obj->obj.hdr.h_flags);

	tv_slot = thr->valstack_top;
	DUK_TVAL_SET_OBJECT(tv_slot, (duk_hobject *) obj);
	DUK_HOBJECT_INCREF(thr, obj);
	ret = (int) (thr->valstack_top - thr->valstack_bottom);
	thr->valstack_top++;

	/* default prototype (Note: 'obj' must be reachable) */
	DUK_HOBJECT_SET_PROTOTYPE(thr, (duk_hobject *) obj, thr->builtins[DUK_BIDX_THREAD_PROTOTYPE]);

	/* important to do this *after* pushing, to make the thread reachable for gc */

	if (!duk_hthread_init_stacks(thr->heap, obj)) {
		DUK_ERROR(thr, DUK_ERR_ALLOC_ERROR, "failed to allocate thread");
	}

	/* FIXME: initial size should satisfy this -> just assert for it */
	/* extend initial stack so that it matches common requirements */

	duk_require_stack((duk_context *) obj, 0);

	return ret;
}

int duk_push_compiledfunction(duk_context *ctx) {
	duk_hthread *thr = (duk_hthread *) ctx;
	duk_hcompiledfunction *obj;
	int ret;
	duk_tval *tv_slot;

	DUK_ASSERT(ctx != NULL);

	/* check stack before interning (avoid hanging temp) */
	if (thr->valstack_top >= thr->valstack_end) {
		DUK_ERROR(thr, DUK_ERR_API_ERROR, "attempt to push beyond currently allocated stack");
	}

	/* Template functions are not strictly constructable (they don't
	 * have a "prototype" property for instance), so leave the
	 * DUK_HOBJECT_FLAG_CONSRUCTABLE flag cleared here.
	 */

	obj = duk_hcompiledfunction_alloc(thr->heap,
	                                  DUK_HOBJECT_FLAG_EXTENSIBLE |
	                                  DUK_HOBJECT_FLAG_COMPILEDFUNCTION |
	                                  DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_FUNCTION));
	if (!obj) {
		DUK_ERROR(thr, DUK_ERR_ALLOC_ERROR, "failed to allocate a function object");
	}

	DUK_DDDPRINT("created compiled function object with flags: 0x%08x", obj->obj.hdr.h_flags);

	tv_slot = thr->valstack_top;
	DUK_TVAL_SET_OBJECT(tv_slot, (duk_hobject *) obj);
	DUK_HOBJECT_INCREF(thr, obj);
	ret = (int) (thr->valstack_top - thr->valstack_bottom);
	thr->valstack_top++;

	/* default prototype (Note: 'obj' must be reachable) */
	DUK_HOBJECT_SET_PROTOTYPE(thr, (duk_hobject *) obj, thr->builtins[DUK_BIDX_FUNCTION_PROTOTYPE]);

	return ret;
}

int duk_push_c_function(duk_context *ctx, duk_c_function func, int nargs) {
	duk_hthread *thr = (duk_hthread *) ctx;
	duk_hnativefunction *obj;
	int ret;
	duk_tval *tv_slot;
	duk_u16 func_nargs;

	DUK_ASSERT(ctx != NULL);

	/* check stack before interning (avoid hanging temp) */
	if (thr->valstack_top >= thr->valstack_end) {
		DUK_ERROR(thr, DUK_ERR_API_ERROR, "attempt to push beyond currently allocated stack");
	}
	if (func == NULL) {
		goto api_error;
	}
	if (nargs >= 0 && nargs < DUK_HNATIVEFUNCTION_NARGS_MAX) {
		func_nargs = (duk_u16) nargs;
	} else if (nargs == DUK_VARARGS) {
		func_nargs = DUK_HNATIVEFUNCTION_NARGS_VARARGS;
	} else {
		goto api_error;
	}

	/* DUK_HOBJECT_FLAG_CONSTRUCTABLE is not currently set; native functions
	 * are not constructable unless explicitly defined as such.
	 *
	 * DUK_HOBJECT_FLAG_NEWENV is currently always set; native functions
	 * cannot e.g. declare variables to caller's scope.
	 */

	obj = duk_hnativefunction_alloc(thr->heap, DUK_HOBJECT_FLAG_EXTENSIBLE |
	                                           DUK_HOBJECT_FLAG_CONSTRUCTABLE |
	                                           DUK_HOBJECT_FLAG_NATIVEFUNCTION |
	                                           DUK_HOBJECT_FLAG_NEWENV |
	                                           DUK_HOBJECT_FLAG_STRICT |
	                                           DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_FUNCTION));
	if (!obj) {
		DUK_ERROR(thr, DUK_ERR_ALLOC_ERROR, "failed to allocate a function object");
	}

	obj->func = func;
	obj->nargs = func_nargs;

	DUK_DDDPRINT("created native function object with flags: 0x%08x, nargs=%d", obj->obj.hdr.h_flags, obj->nargs);

	tv_slot = thr->valstack_top;
	DUK_TVAL_SET_OBJECT(tv_slot, (duk_hobject *) obj);
	DUK_HOBJECT_INCREF(thr, obj);
	ret = (int) (thr->valstack_top - thr->valstack_bottom);
	thr->valstack_top++;

	/* default prototype (Note: 'obj' must be reachable) */
	DUK_HOBJECT_SET_PROTOTYPE(thr, (duk_hobject *) obj, thr->builtins[DUK_BIDX_FUNCTION_PROTOTYPE]);

	return ret;

 api_error:
	DUK_ERROR(thr, DUK_ERR_API_ERROR, "invalid argument(s)");
	return 0;  /* not reached */
}

int duk_push_error_object(duk_context *ctx, int err_code, const char *fmt, ...) {
	duk_hthread *thr = (duk_hthread *) ctx;
	int retval;
	va_list ap;
	duk_hobject *errobj;
	duk_hobject *proto;

	DUK_ASSERT(ctx != NULL);
	DUK_ASSERT(thr != NULL);

	va_start(ap, fmt);

	retval = duk_push_object_helper(ctx,
	                                DUK_HOBJECT_FLAG_EXTENSIBLE |
	                                DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_ERROR),
	                                DUK_BIDX_ERROR_PROTOTYPE);  /* prototype updated below */

	errobj = duk_require_hobject(ctx, -1);

	/* error gets its 'name' from the prototype */
	proto = duk_error_prototype_from_code(thr, err_code);
	DUK_HOBJECT_SET_PROTOTYPE(thr, errobj, proto);  /* adjusts refcount */

	/* ... and its 'message' from an instance property */
	if (fmt) {
		duk_push_vsprintf(ctx, fmt, ap);
		duk_def_prop_stridx(ctx, -2, DUK_STRIDX_MESSAGE, DUK_PROPDESC_FLAGS_WC);
	}

	/* 'code' property is custom */
	duk_push_int(ctx, err_code);
	duk_def_prop_stridx(ctx, -2, DUK_STRIDX_CODE, DUK_PROPDESC_FLAGS_WC);

	va_end(ap);

	/* Note: errors should be augmented when they are created, not when
	 * they are thrown or rethrown.  The caller should augment the newly
	 * pushed error if it is relevant.
	 */

#ifdef DUK_USE_AUGMENT_ERRORS
	duk_err_augment_error(thr, thr, -1);  /* may throw an error */
#endif

	return retval;
}

/* FIXME: repetition, see duk_push_object */
void *duk_push_buffer(duk_context *ctx, size_t size, int dynamic) {
	duk_hthread *thr = (duk_hthread *) ctx;
	duk_tval *tv_slot;
	duk_hbuffer *h;

	DUK_ASSERT(ctx != NULL);

	/* check stack before interning (avoid hanging temp) */
	if (thr->valstack_top >= thr->valstack_end) {
		DUK_ERROR(thr, DUK_ERR_API_ERROR, "attempt to push beyond currently allocated stack");
	}

	h = duk_hbuffer_alloc(thr->heap, size, dynamic);
	if (!h) {
		DUK_ERROR(thr, DUK_ERR_ALLOC_ERROR, "failed to allocate buffer");
	}

	tv_slot = thr->valstack_top;
	DUK_TVAL_SET_BUFFER(tv_slot, h);
	DUK_HBUFFER_INCREF(thr, h);
	thr->valstack_top++;

	return DUK_HBUFFER_GET_DATA_PTR(h);
}

void *duk_push_fixed_buffer(duk_context *ctx, size_t size) {
	return duk_push_buffer(ctx, size, 0);
}

void *duk_push_dynamic_buffer(duk_context *ctx, size_t size) {
	return duk_push_buffer(ctx, size, 1);
}

int duk_push_object_internal(duk_context *ctx) {
	return duk_push_object_helper(ctx,
	                              DUK_HOBJECT_FLAG_EXTENSIBLE |
	                              DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_OBJECT),
	                              -1);  /* no prototype */
}

/* internal */
void duk_push_hstring(duk_context *ctx, duk_hstring *h) {
	duk_tval tv;
	DUK_ASSERT(ctx != NULL);
	DUK_ASSERT(h != NULL);
	DUK_TVAL_SET_STRING(&tv, h);
	duk_push_tval(ctx, &tv);
}

void duk_push_hstring_stridx(duk_context *ctx, int stridx) {
	duk_hthread *thr = (duk_hthread *) ctx;
	DUK_ASSERT(stridx >= 0 && stridx < DUK_HEAP_NUM_STRINGS);
	duk_push_hstring(ctx, thr->strs[stridx]);
}

/* internal */
void duk_push_hobject(duk_context *ctx, duk_hobject *h) {
	duk_tval tv;
	DUK_ASSERT(ctx != NULL);
	DUK_ASSERT(h != NULL);
	DUK_TVAL_SET_OBJECT(&tv, h);
	duk_push_tval(ctx, &tv);
}

/* internal */
void duk_push_hbuffer(duk_context *ctx, duk_hbuffer *h) {
	duk_tval tv;
	DUK_ASSERT(ctx != NULL);
	DUK_ASSERT(h != NULL);
	DUK_TVAL_SET_BUFFER(&tv, h);
	duk_push_tval(ctx, &tv);
}

/* internal */
void duk_push_builtin(duk_context *ctx, int builtin_idx) {
	duk_hthread *thr = (duk_hthread *) ctx;
	DUK_ASSERT(ctx != NULL);
	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(builtin_idx >= 0 && builtin_idx < DUK_NUM_BUILTINS);
	DUK_ASSERT(thr->builtins[builtin_idx] != NULL);  /* FIXME: this assumption may need be to relaxed! */
	duk_push_hobject(ctx, thr->builtins[builtin_idx]);
}

/*
 *  Poppers
 */

void duk_pop_n(duk_context *ctx, unsigned int count) {
	duk_hthread *thr = (duk_hthread *) ctx;
	DUK_ASSERT(ctx != NULL);

	if (thr->valstack_top - thr->valstack_bottom < count) {
		DUK_ERROR(thr, DUK_ERR_API_ERROR, "attempt to pop too many entries");
	}

	/*
	 *  Must be very careful here, every DECREF may cause reallocation
	 *  of our valstack.
	 */

	/* FIXME: inlined DECREF macro would be nice here: no NULL check,
	 * refzero queueing but no refzero algorithm run (= no pointer
	 * instability), inline code.
	 */
	
#ifdef DUK_USE_REFERENCE_COUNTING
	while (count > 0) {
		duk_tval tv_tmp;
		duk_tval *tv;

		thr->valstack_top--;
		tv = thr->valstack_top;
		DUK_ASSERT(tv >= thr->valstack_bottom);
		DUK_TVAL_SET_TVAL(&tv_tmp, tv);
		DUK_TVAL_SET_UNDEFINED_UNUSED(tv);
		DUK_TVAL_DECREF(thr, &tv_tmp);  /* side effects */
		count--;
	}
#else
	while (count > 0) {
		duk_tval *tv;

		thr->valstack_top--;
		tv = thr->valstack_top;
		DUK_ASSERT(tv >= thr->valstack_bottom);
		DUK_TVAL_SET_UNDEFINED_UNUSED(tv);
		count--;
	}

#endif

	DUK_ASSERT(thr->valstack_top >= thr->valstack_bottom);
}

void duk_pop(duk_context *ctx) {
	duk_pop_n(ctx, 1);
}

void duk_pop_2(duk_context *ctx) {
	duk_pop_n(ctx, 2);
}

void duk_pop_3(duk_context *ctx) {
	duk_pop_n(ctx, 3);
}

/*
 *  Error throwing
 */

void duk_throw(duk_context *ctx) {
	duk_hthread *thr = (duk_hthread *) ctx;

	DUK_ASSERT(thr->valstack_bottom >= thr->valstack);
	DUK_ASSERT(thr->valstack_top >= thr->valstack_bottom);
	DUK_ASSERT(thr->valstack_end >= thr->valstack_top);

	if (!thr->heap->lj.jmpbuf_ptr) {
		/*
		 *  No jmpbuf_ptr, so cannot longjmp.  Because we can't return
		 *  either (that would violate caller expectations), there's no
		 *  other option than to panic.  Caller should ensure this never
		 *  happens.
		 */
		duk_fatal(ctx, DUK_ERR_UNCAUGHT_ERROR);
		DUK_NEVER_HERE();
	}

	if (thr->valstack_top == thr->valstack_bottom) {
		DUK_ERROR(thr, DUK_ERR_API_ERROR, "no value to throw");
	}

	/* Note: errors are augmented when they are created, not when they are
	 * thrown or re-thrown.
	 */

	DUK_DDDPRINT("THROW ERROR (API): %!dT", duk_get_tval(ctx, -1));

	duk_err_setup_heap_ljstate(thr, DUK_LJ_TYPE_THROW);

	duk_err_longjmp(thr);
	DUK_NEVER_HERE();
}

void duk_fatal(duk_context *ctx, int err_code) {
	duk_hthread *thr = (duk_hthread *) ctx;

	DUK_ASSERT(ctx != NULL);
	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(thr->heap != NULL);
	DUK_ASSERT(thr->heap->fatal_func != NULL);

	DUK_DPRINT("fatal error occurred, code %d", err_code);

	thr->heap->fatal_func(ctx, err_code);
	DUK_NEVER_HERE();
}

void duk_error(duk_context *ctx, int err_code, const char *fmt, ...) {
	/* FIXME: push_error_object_vsprintf? */
	va_list ap;

	va_start(ap, fmt);
	duk_push_vsprintf(ctx, fmt, ap);
	va_end(ap);
	duk_push_error_object(ctx, err_code, "%s", duk_get_string(ctx, -1));
	duk_throw(ctx);
}

int duk_equals(duk_context *ctx, int index1, int index2) {
	duk_hthread *thr = (duk_hthread *) ctx;
	duk_tval *tv1, *tv2;

	tv1 = duk_get_tval(ctx, index1);
	if (!tv1) {
		return 0;
	}
	tv2 = duk_get_tval(ctx, index2);
	if (!tv2) {
		return 0;
	}

	/* Coercion may be needed, the helper handles that by pushing the
	 * tagged values to the stack.
	 */
	return duk_js_equals(thr, tv1, tv2);

}

int duk_strict_equals(duk_context *ctx, int index1, int index2) {
	duk_tval *tv1, *tv2;

	tv1 = duk_get_tval(ctx, index1);
	if (!tv1) {
		return 0;
	}
	tv2 = duk_get_tval(ctx, index2);
	if (!tv2) {
		return 0;
	}

	/* No coercions or other side effects, so safe */
	return duk_js_strict_equals(tv1, tv2);
}

/*
 *  Heap creation
 */

duk_context *duk_create_heap(duk_alloc_function alloc_func,
                             duk_realloc_function realloc_func,
                             duk_free_function free_func,
                             void *alloc_udata,
                             duk_fatal_function fatal_handler) {
	duk_heap *heap = NULL;
	duk_context *ctx;

	/* Assume that either all memory funcs are NULL or non-NULL, mixed
	 * cases will now be unsafe.
	 */

	/* FIXME: just assert non-NULL values here and make caller arguments
	 * do the defaulting to the default implementations (smaller code)?
	 */

	if (!alloc_func) {
		DUK_ASSERT(realloc_func == NULL);
		DUK_ASSERT(free_func == NULL);
		alloc_func = duk_default_alloc_function;
		realloc_func = duk_default_realloc_function;
		free_func = duk_default_free_function;
	} else {
		DUK_ASSERT(realloc_func != NULL);
		DUK_ASSERT(free_func != NULL);
	}

	if (!fatal_handler) {
		fatal_handler = duk_default_fatal_handler;
	}

	DUK_ASSERT(alloc_func != NULL);
	DUK_ASSERT(realloc_func != NULL);
	DUK_ASSERT(free_func != NULL);
	DUK_ASSERT(fatal_handler != NULL);

	heap = duk_heap_alloc(alloc_func, realloc_func, free_func, alloc_udata, fatal_handler);
	if (!heap) {
		return NULL;
	}
	ctx = (duk_context *) heap->heap_thread;
	DUK_ASSERT(ctx != NULL);
	DUK_ASSERT(((duk_hthread *) ctx)->heap != NULL);
	return ctx;
}

void duk_destroy_heap(duk_context *ctx) {
	duk_hthread *thr = (duk_hthread *) ctx;
	duk_heap *heap;

	if (!ctx) {
		return;
	}
	heap = thr->heap;
	DUK_ASSERT(heap != NULL);

	duk_heap_free(heap);
}

#line 1 "duk_api_buffer.c"
/*
 *  Buffer
 */

/* include removed: duk_internal.h */

void *duk_resize_buffer(duk_context *ctx, int index, size_t new_size) {
	duk_hthread *thr = (duk_hthread *) ctx;
	duk_hbuffer_dynamic *h;

	DUK_ASSERT(ctx != NULL);

	h = (duk_hbuffer_dynamic *) duk_require_hbuffer(ctx, index);
	DUK_ASSERT(h != NULL);

	if (!DUK_HBUFFER_HAS_DYNAMIC(h)) {
		DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, "buffer is not dynamic");
	}

	duk_hbuffer_resize(thr, h, new_size, new_size);  /* snug */

	return DUK_HBUFFER_DYNAMIC_GET_CURR_DATA_PTR(h);
}

void duk_to_fixed_buffer(duk_context *ctx, int index) {
	duk_hbuffer_dynamic *h_src;
	char *data;
	size_t size;

	index = duk_require_normalize_index(ctx, index);

	h_src = (duk_hbuffer_dynamic *) duk_require_hbuffer(ctx, index);
	DUK_ASSERT(h_src != NULL);
	if (!DUK_HBUFFER_HAS_DYNAMIC(h_src)) {
		return;
	}

	size = DUK_HBUFFER_GET_SIZE(h_src);
	data = duk_push_fixed_buffer(ctx, size);
	if (size > 0) {
		DUK_ASSERT(data != NULL);
		memcpy(data, DUK_HBUFFER_DYNAMIC_GET_CURR_DATA_PTR(h_src), size);
	}

	duk_replace(ctx, index);
}

#line 1 "duk_api_call.c"
/*
 *  Calls.
 *
 *  Protected variants should avoid ever throwing an error.
 */

/* include removed: duk_internal.h */

static int resolve_errhandler(duk_context *ctx, int pop_count, int errhandler_index, duk_hobject **out_ptr) {
	duk_hthread *thr = (duk_hthread *) ctx;

	DUK_ASSERT(out_ptr != NULL);

	if (errhandler_index == DUK_INVALID_INDEX) {
		/* use existing, if any */
		*out_ptr = thr->heap->lj.errhandler;
		return 1;
	}

	if (duk_is_valid_index(ctx, errhandler_index)) {
		duk_tval *tv = duk_require_tval(ctx, errhandler_index);

		if (DUK_TVAL_IS_OBJECT(tv)) {
			duk_hobject *h = DUK_TVAL_GET_OBJECT(tv);
			DUK_ASSERT(h != NULL);
			if (DUK_HOBJECT_IS_CALLABLE(h)) {
				*out_ptr = h;
				return 1;
			}
		} else if (DUK_TVAL_IS_UNDEFINED(tv) || DUK_TVAL_IS_NULL(tv)) {
			/* explicitly force NULL handler */
			*out_ptr = NULL;
			return 1;
		}

		/* fall through to error */
	}

	/*
	 *  Error: don't throw anything here as we're part of the 'pcall' process.
	 *  Instead, simulate an error by pushing an error object on the top of
	 *  stack.  The current error handler will not be called for this particular
	 *  error though.
	 *
	 *  FIXME: this is naturally not reliable as pushing an error object may
	 *  result in out-of-memory.  The API call semantics may need to be changed,
	 *  or perhaps we need to push a pre-built object here to avoid any error
	 *  potential (or instead of a pre-built object a plain value like undefined).
	 */

	DUK_ASSERT(pop_count <= duk_get_top(ctx));  /* caller ensures */

	duk_pop_n(ctx, pop_count);
	(void) duk_push_error_object(ctx, DUK_ERR_API_ERROR, "invalid errhandler");
	return 0;
}

/* Prepare value stack for a method call through an object property.
 * May currently throw an error e.g. when getting the property.
 */
static void call_prop_prep_stack(duk_context *ctx, int normalized_obj_index, int nargs) {
	DUK_DDDPRINT("call_prop_prep_stack, normalized_obj_index=%d, nargs=%d, stacktop=%d",
	             normalized_obj_index, nargs, duk_get_top(ctx));

	/* [... key arg1 ... argN] */

	/* duplicate key */
	duk_dup(ctx, -nargs - 1);  /* Note: -nargs alone would fail for nargs == 0, this is OK */
	duk_get_prop(ctx, normalized_obj_index);

	DUK_DDDPRINT("func: %!T", duk_get_tval(ctx, -1));

	/* [... key arg1 ... argN func] */

	duk_replace(ctx, -nargs - 2);

	/* [... func arg1 ... argN] */

	duk_dup(ctx, normalized_obj_index);
	duk_insert(ctx, -nargs - 1);

	/* [... func this arg1 ... argN] */
}

void duk_call(duk_context *ctx, int nargs) {
	duk_hthread *thr = (duk_hthread *) ctx;
	duk_hobject *errhandler;
	int call_flags;
	int idx_func;
	int rc;

	DUK_ASSERT(ctx != NULL);
	DUK_ASSERT(thr != NULL);

	idx_func = duk_get_top(ctx) - nargs - 1;  /* must work for nargs <= 0 */
	if (idx_func < 0 || nargs < 0) {
		/* note that we can't reliably pop anything here */
		DUK_ERROR(ctx, DUK_ERR_API_ERROR, "invalid call args");
	}

	/* awkward; we assume there is space for this */
	duk_push_undefined(ctx);
	duk_insert(ctx, idx_func + 1);

	errhandler = thr->heap->lj.errhandler;  /* use existing one (if any) */

	call_flags = 0;  /* not protected, respect reclimit, not constructor */

	rc = duk_handle_call(thr,           /* thread */
	                     nargs,         /* num_stack_args */
	                     call_flags,    /* call_flags */
	                     errhandler);   /* errhandler */
	DUK_UNREF(rc);
}

void duk_call_method(duk_context *ctx, int nargs) {
	duk_hthread *thr = (duk_hthread *) ctx;
	duk_hobject *errhandler;
	int call_flags;
	int idx_func;
	int rc;

	DUK_ASSERT(ctx != NULL);
	DUK_ASSERT(thr != NULL);

	idx_func = duk_get_top(ctx) - nargs - 2;  /* must work for nargs <= 0 */
	if (idx_func < 0 || nargs < 0) {
		/* note that we can't reliably pop anything here */
		DUK_ERROR(ctx, DUK_ERR_API_ERROR, "invalid call args");
	}

	errhandler = thr->heap->lj.errhandler;  /* use existing one (if any) */

	call_flags = 0;  /* not protected, respect reclimit, not constructor */

	rc = duk_handle_call(thr,           /* thread */
	                     nargs,         /* num_stack_args */
	                     call_flags,    /* call_flags */
	                     errhandler);   /* errhandler */
	DUK_UNREF(rc);
}

void duk_call_prop(duk_context *ctx, int obj_index, int nargs) {
	/*
	 *  XXX: if duk_handle_call() took values through indices, this could be
	 *  made much more sensible.  However, duk_handle_call() needs to fudge
	 *  the 'this' and 'func' values to handle bound function chains, which
	 *  is now done "in-place", so this is not a trivial change.
	 */

	obj_index = duk_require_normalize_index(ctx, obj_index);  /* make absolute */

	call_prop_prep_stack(ctx, obj_index, nargs);

	duk_call_method(ctx, nargs);
}

int duk_pcall(duk_context *ctx, int nargs, int errhandler_index) {
	duk_hthread *thr = (duk_hthread *) ctx;
	duk_hobject *errhandler = NULL;
	int call_flags;
	int idx_func;
	int rc;

	DUK_ASSERT(ctx != NULL);
	DUK_ASSERT(thr != NULL);

	idx_func = duk_get_top(ctx) - nargs - 1;  /* must work for nargs <= 0 */
	if (idx_func < 0 || nargs < 0) {
		/* note that we can't reliably pop anything here */
		DUK_ERROR(ctx, DUK_ERR_API_ERROR, "invalid call args");
		/* FIXME: actually terminate thread? */
		return DUK_ERR_EXEC_TERM;
	}

	if (!resolve_errhandler(ctx, nargs + 1, errhandler_index, &errhandler)) {
		/* error on top of stack */
		return DUK_ERR_EXEC_ERROR;
	}

	/* awkward; we assume there is space for this */
	duk_push_undefined(ctx);
	duk_insert(ctx, idx_func + 1);

	call_flags = DUK_CALL_FLAG_PROTECTED;  /* protected, respect reclimit, not constructor */

	rc = duk_handle_call(thr,           /* thread */
	                     nargs,         /* num_stack_args */
	                     call_flags,    /* call_flags */
	                     errhandler);   /* errhandler */

	return rc;
}

int duk_pcall_method(duk_context *ctx, int nargs, int errhandler_index) {
	duk_hthread *thr = (duk_hthread *) ctx;
	duk_hobject *errhandler = NULL;
	int call_flags;
	int idx_func;
	int rc;

	DUK_ASSERT(ctx != NULL);
	DUK_ASSERT(thr != NULL);

	idx_func = duk_get_top(ctx) - nargs - 2;  /* must work for nargs <= 0 */
	if (idx_func < 0 || nargs < 0) {
		/* note that we can't reliably pop anything here */
		/* FIXME: actually terminate thread? */
		return DUK_ERR_EXEC_TERM;
	}

	if (!resolve_errhandler(ctx, nargs + 2, errhandler_index, &errhandler)) {
		/* error on top of stack */
		return DUK_ERR_EXEC_ERROR;
	}

	call_flags = DUK_CALL_FLAG_PROTECTED;  /* protected, respect reclimit, not constructor */

	rc = duk_handle_call(thr,           /* thread */
	                     nargs,         /* num_stack_args */
	                     call_flags,    /* call_flags */
	                     errhandler);   /* errhandler */

	return rc;
}

int duk_pcall_prop(duk_context *ctx, int obj_index, int nargs, int errhandler_index) {
	/* FIXME: these will throw errors now, so this is a bad idea */
	obj_index = duk_require_normalize_index(ctx, obj_index);  /* make absolute */
	call_prop_prep_stack(ctx, obj_index, nargs);

	return duk_pcall_method(ctx, nargs, errhandler_index);
}

int duk_safe_call(duk_context *ctx, duk_safe_call_function func, int nargs, int nrets, int errhandler_index) {
	duk_hthread *thr = (duk_hthread *) ctx;
	duk_hobject *errhandler = NULL;
	int rc;

	DUK_ASSERT(ctx != NULL);
	DUK_ASSERT(thr != NULL);

	if (duk_get_top(ctx) < nargs || nrets < 0) {
		/* also covers sanity for negative 'nargs'; note that we can't
		 * reliably pop anything here
		 */
		/* FIXME: actually terminate thread? */
		return DUK_ERR_EXEC_TERM;
	}

	if (!resolve_errhandler(ctx, nargs, errhandler_index, &errhandler)) {
		/* error on top of stack (args popped) */
		return DUK_ERR_EXEC_ERROR;
	}

	rc = duk_handle_safe_call(thr,           /* thread */
	                          func,          /* func */
	                          nargs,         /* num_stack_args */
	                          nrets,         /* num_stack_res */
	                          errhandler);   /* errhandler */

	return rc;
}

void duk_new(duk_context *ctx, int nargs) {
	/*
	 *  There are two [[Construct]] operations in the specification:
	 *
	 *    - E5 Section 13.2.2: for Function objects
	 *    - E5 Section 15.3.4.5.2: for "bound" Function objects
	 *
	 *  The chain of bound functions is resolved in Section 15.3.4.5.2,
	 *  with arguments "piling up" until the [[Construct]] internal
	 *  method is called on the final, actual Function object.  Note
	 *  that the "prototype" property is looked up *only* from the
	 *  final object, *before* calling the constructor.
	 *
	 *  Currently we follow the bound function chain here to get the
	 *  "prototype" property value from the final, non-bound function.
	 *  However, we let duk_handle_call() handle the argument "piling"
	 *  when the constructor is called.  The bound function chain is
	 *  thus now processed twice.
	 *
	 *  When constructing new Array instances, an unnecessary object is
	 *  created and discarded now: the standard [[Construct]] creates an
	 *  object, and calls the Array constructor.  The Array constructor
	 *  returns an Array instance, which is used as the result value for
	 *  the "new" operation; the object created before the Array constructor
	 *  call is discarded.
	 *
	 *  This would be easy to fix, e.g. by knowing that the Array constructor
	 *  will always create a replacement object and skip creating the fallback
	 *  object in that case.  FIXME.
	 *
	 *  Note: functions called via "new" need to know they are called as a
	 *  constructor.  For instance, built-in constructors behave differently
	 *  depending on how they are called.
	 */

	/* FIXME: should this go to duk_js_call.c? it implements core semantics. */

	duk_hthread *thr = (duk_hthread *) ctx;
	duk_hobject *proto;
	duk_hobject *cons;
	duk_hobject *fallback;
	int idx_cons;
	duk_hobject *errhandler = NULL;
	int call_flags;
	int rc;

	/* [... constructor arg1 ... argN] */

	idx_cons = duk_require_normalize_index(ctx, -nargs - 1);

	DUK_DDDPRINT("top=%d, nargs=%d, idx_cons=%d", duk_get_top(ctx), nargs, idx_cons);

	/* FIXME: code duplication */

	/*
	 *  Figure out the final, non-bound constructor, to get "prototype"
	 *  property.
	 */

	duk_dup(ctx, idx_cons);
	for (;;) {
		cons = duk_get_hobject(ctx, -1);
		if (cons == NULL || !DUK_HOBJECT_HAS_CONSTRUCTABLE(cons)) {
			/* Checking constructability from anything else than the
			 * initial constructor is not strictly necessary, but a
			 * nice sanity check.
			 */
			goto not_constructable;
		}
		if (!DUK_HOBJECT_HAS_BOUND(cons)) {
			break;
		}
		duk_get_prop_stridx(ctx, -1, DUK_STRIDX_INT_TARGET);  /* -> [... cons target] */
		duk_remove(ctx, -2);                                  /* -> [... target] */
	}
	DUK_ASSERT(cons != NULL && !DUK_HOBJECT_HAS_BOUND(cons));

	/* [... constructor arg1 ... argN final_cons] */

	/*
	 *  Create "fallback" object to be used as the object instance,
	 *  unless the constructor returns a replacement value.
	 *  Its internal prototype needs to be set based on "prototype"
	 *  property of the constructor.
	 */

	duk_push_object(ctx);  /* class Object, extensible */

	/* [... constructor arg1 ... argN final_cons fallback] */

	duk_get_prop_stridx(ctx, -2, DUK_STRIDX_PROTOTYPE);
	proto = duk_get_hobject(ctx, -1);
	if (!proto) {
		DUK_DDDPRINT("constructor has no 'prototype' property, or value not an object "
		             "-> leave standard Object prototype as fallback prototype");
	} else {
		DUK_DDDPRINT("constructor has 'prototype' property with object value "
		             "-> set fallback prototype to that value: %!iO", proto);
		fallback = duk_get_hobject(ctx, -2);
		DUK_ASSERT(fallback != NULL);
		DUK_HOBJECT_SET_PROTOTYPE(thr, fallback, proto);
	}
	duk_pop(ctx);

	/* [... constructor arg1 ... argN final_cons fallback] */

	/*
	 *  Manipulate callstack for the call.
	 */

	duk_dup_top(ctx);
	duk_insert(ctx, idx_cons + 1);  /* use fallback as 'this' value */
	duk_insert(ctx, idx_cons);      /* also stash it before constructor,
	                                 * in case we need it (as the fallback value)
	                                 */
	duk_pop(ctx);                   /* pop final_cons */


	/* [... fallback constructor fallback(this) arg1 ... argN];
	 * Note: idx_cons points to first 'fallback', not 'constructor'.
	 */

	DUK_DDDPRINT("before call, idx_cons+1 (constructor) -> %!T, idx_cons+2 (fallback/this) -> %!T, "
	             "nargs=%d, top=%d",
	             duk_get_tval(ctx, idx_cons + 1), duk_get_tval(ctx, idx_cons + 2),
	             nargs, duk_get_top(ctx));

	/*
	 *  Call the constructor function (called in "constructor mode").
	 */

	errhandler = thr->heap->lj.errhandler;  /* use existing one (if any) */

	call_flags = DUK_CALL_FLAG_CONSTRUCTOR_CALL;  /* not protected, respect reclimit, is a constructor call */

	rc = duk_handle_call(thr,           /* thread */
	                     nargs,         /* num_stack_args */
	                     call_flags,    /* call_flags */
	                     errhandler);   /* errhandler */
	DUK_UNREF(rc);

	/* [... fallback retval] */

	DUK_DDDPRINT("constructor call finished, rc=%d, fallback=%!iT, retval=%!iT",
	             rc, duk_get_tval(ctx, -2), duk_get_tval(ctx, -1));

	/*
	 *  Determine whether to use the constructor return value as the created
	 *  object instance or not.
	 */

	if (duk_is_object(ctx, -1)) {
		duk_remove(ctx, -2);
	} else {
		duk_pop(ctx);
	}

	/*
	 *  Augment created errors upon creation.
	 *
	 * Note: errors should be augmented when they are created, not when
	 * they are thrown or rethrown.
	 */

	duk_err_augment_error(thr, thr, -1);

	/* [... retval] */

	return;

 not_constructable:
	DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, "not constructable");
}

int duk_is_constructor_call(duk_context *ctx) {
	duk_hthread *thr = (duk_hthread *) ctx;
	duk_activation *act;

	DUK_ASSERT(ctx != NULL);
	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(thr->callstack_top >= 0);

	if (thr->callstack_top <= 0) {
		return 0;
	}

	act = thr->callstack + thr->callstack_top - 1;
	return ((act->flags & DUK_ACT_FLAG_CONSTRUCT) != 0 ? 1 : 0);
}

int duk_is_strict_call(duk_context *ctx) {
	duk_hthread *thr = (duk_hthread *) ctx;
	duk_activation *act;

	DUK_ASSERT(ctx != NULL);
	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(thr->callstack_top >= 0);

	if (thr->callstack_top <= 0) {
		return 0;
	}

	act = thr->callstack + thr->callstack_top - 1;
	return ((act->flags & DUK_ACT_FLAG_STRICT) != 0 ? 1 : 0);
}

#line 1 "duk_api_codec.c"
/*
 *  Encoding and decoding basic formats: hex, base64.
 *
 *  These are in-place operations which may allow an optimized implementation.
 */

/* include removed: duk_internal.h */

/* dst length must be exactly ceil(len/3)*4 */
static void base64_encode_helper(const unsigned char *src, const unsigned char *src_end,
                                 unsigned char *dst, unsigned char *dst_end) {
	unsigned int i, snip;
	unsigned int x, y, t;

	while (src < src_end) {
		/* read 3 bytes into 't', padded by zero */
		snip = 4;
		t = 0;
		for (i = 0; i < 3; i++) {
			t = t << 8;
			if (src >= src_end) {
				snip--;
			} else {
				t += (unsigned int) (*src++);
			}
		}

		/*
		 *  Missing bytes    snip     base64 example
		 *    0               4         XXXX
		 *    1               3         XXX=
		 *    2               2         XX==
		 */

		DUK_ASSERT(snip >= 2 && snip <= 4);

		for (i = 0; i < 4; i++) {
			x = (t >> 18) & 0x3f;
			t = t << 6;

			/* A straightforward 64-byte lookup would be faster
			 * and cleaner, but this is shorter.
			 */
			if (i >= snip) {
				y = '=';
			} else if (x <= 25) {
				y = x + 'A';
			} else if (x <= 51) {
				y = x - 26 + 'a';
			} else if (x <= 61) {
				y = x - 52 + '0';
			} else if (x == 62) {
				y = '+';
			} else {
				y = '/';
			}

			DUK_ASSERT(dst < dst_end);
			*dst++ = (unsigned char) y;
		}
	}
}

static int base64_decode_helper(const unsigned char *src, const unsigned char *src_end,
                                unsigned char *dst, unsigned char *dst_end, unsigned char **out_dst_final) {
	unsigned int t;
	unsigned int x, y;
	int group_idx;

	t = 0;
	group_idx = 0;

	while (src < src_end) {
		x = *src++;

		if (x >= 'A' && x <= 'Z') {
			y = x - 'A' + 0;
		} else if (x >= 'a' && x <= 'z') {
			y = x - 'a' + 26;
		} else if (x >= '0' && x <= '9') {
			y = x - '0' + 52;
		} else if (x == '+') {
			y = 62;
		} else if (x == '/') {
			y = 63;
		} else if (x == '=') {
			/* We don't check the zero padding bytes here right now.
			 * This seems to be common behavior for base-64 decoders.
			 */

			if (group_idx == 2) {
				/* xx== -> 1 byte, t contains 12 bits, 4 on right are zero */
				t = t >> 4;
				DUK_ASSERT(dst < dst_end);
				*dst++ = (unsigned char) t;

				if (src >= src_end) {
					goto error;
				}
				x = *src++;
				if (x != '=') {
					goto error;
				}
			} else if (group_idx == 3) {
				/* xxx= -> 2 bytes, t contains 18 bits, 2 on right are zero */
				t = t >> 2;
				DUK_ASSERT(dst < dst_end);
				*dst++ = (unsigned char) ((t >> 8) & 0xff);
				DUK_ASSERT(dst < dst_end);
				*dst++ = (unsigned char) (t & 0xff);
			} else {
				goto error;
			}

			/* Here we can choose either to end parsing and ignore
			 * whatever follows, or to continue parsing in case
			 * multiple (possibly padded) base64 strings have been
			 * concatenated.  Currently, keep on parsing.
			 */
			t = 0;
			group_idx = 0;
			continue;
		} else if (x == 0x09 || x == 0x0a || x == 0x0d || x == 0x20) {
			/* allow basic ASCII whitespace */
			continue;
		} else {
			goto error;
		}

		t = (t << 6) + y;

		if (group_idx == 3) {
			/* output 3 bytes from 't' */
			DUK_ASSERT(dst < dst_end);
			*dst++ = (unsigned char) ((t >> 16) & 0xff);
			DUK_ASSERT(dst < dst_end);
			*dst++ = (unsigned char) ((t >> 8) & 0xff);
			DUK_ASSERT(dst < dst_end);
			*dst++ = (unsigned char) (t & 0xff);
			t = 0;
			group_idx = 0;
		} else {
			group_idx++;
		}
	}

	if (group_idx != 0) {
		/* Here we'd have the option of decoding unpadded base64
		 * (e.g. "xxxxyy" instead of "xxxxyy==".  Currently not
		 * accepted.
		 */
		goto error;
	}

	*out_dst_final = dst;
	return 1;

 error:
	return 0;
}

const char *duk_base64_encode(duk_context *ctx, int index) {
	duk_hthread *thr = (duk_hthread *) ctx;
	unsigned char *src;
	size_t srclen;
	size_t dstlen;
	unsigned char *dst;
	const char *ret;

	/* FIXME: optimize for string inputs: no need to coerce to a buffer
	 * which makes a copy of the input.
	 */

	index = duk_require_normalize_index(ctx, index);
	src = (unsigned char *) duk_to_buffer(ctx, index, &srclen);
	/* Note: for srclen=0, src may be NULL */

	/* Computation must not wrap; this limit works for 32-bit size_t:
	 * >>> srclen = 3221225469
	 * >>> '%x' % ((srclen + 2) / 3 * 4)
	 * 'fffffffc'
	 */
	if (srclen > 3221225469U) {
		goto type_error;
	}
	dstlen = (srclen + 2) / 3 * 4;
	dst = duk_push_fixed_buffer(ctx, dstlen);

	base64_encode_helper((const unsigned char *) src, (const unsigned char *) (src + srclen),
	                     (unsigned char *) dst, (unsigned char *) (dst + dstlen));

	ret = duk_to_string(ctx, -1);
	duk_replace(ctx, index);
	return ret;

 type_error:
	DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, "base64 encode failed");
	return NULL;  /* never here */
}

void duk_base64_decode(duk_context *ctx, int index) {
	duk_hthread *thr = (duk_hthread *) ctx;
	const char *src;
	size_t srclen;
	size_t dstlen;
	unsigned char *dst;
	unsigned char *dst_final;
	int retval;

	/* FIXME: optimize for buffer inputs: no need to coerce to a string
	 * which causes an unnecessary interning.
	 */

	index = duk_require_normalize_index(ctx, index);
	src = duk_to_lstring(ctx, index, &srclen);

	/* Computation must not wrap, only srclen + 3 is at risk of
	 * wrapping because after that the number gets smaller.
	 * This limit works for 32-bit size_t:
	 * 0x100000000 - 3 - 1 = 4294967292
	 */
	if (srclen > 4294967292U) {
		goto type_error;
	}
	dstlen = (srclen + 3) / 4 * 3;  /* upper limit */
	dst = duk_push_dynamic_buffer(ctx, dstlen);
	/* Note: for dstlen=0, dst may be NULL */

	retval = base64_decode_helper((unsigned char *) src, (unsigned char *) (src + srclen),
	                              dst, dst + dstlen, &dst_final);
	if (!retval) {
		goto type_error;
	}

	/* XXX: convert to fixed buffer? */
	(void) duk_resize_buffer(ctx, -1, (size_t) (dst_final - dst));
	duk_replace(ctx, index);
	return;

 type_error:
	DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, "base64 decode failed");
}

const char *duk_hex_encode(duk_context *ctx, int index) {
	unsigned char *data;
	size_t len;
	size_t i;
	int t;
	unsigned char *buf;
	const char *ret;

	/* FIXME: special case for input string, no need to coerce to buffer */

	index = duk_require_normalize_index(ctx, index);
	data = duk_to_buffer(ctx, index, &len);
	DUK_ASSERT(data != NULL);

	buf = (unsigned char *) duk_push_fixed_buffer(ctx, len * 2);
	DUK_ASSERT(buf != NULL);
	/* buf is always zeroed */

	for (i = 0; i < len; i++) {
		t = data[i];
		buf[i*2 + 0] = duk_lc_digits[t >> 4];
		buf[i*2 + 1] = duk_lc_digits[t & 0x0f];
	}

	ret = duk_to_string(ctx, -1);
	duk_replace(ctx, index);
	return ret;
}

void duk_hex_decode(duk_context *ctx, int index) {
	duk_hthread *thr = (duk_hthread *) ctx;
	const char *str;
	size_t len;
	size_t i;
	int t;
	unsigned char *buf;

	/* FIXME: optimize for buffer inputs: no need to coerce to a string
	 * which causes an unnecessary interning.
	 */

	index = duk_require_normalize_index(ctx, index);
	str = duk_to_lstring(ctx, index, &len);
	DUK_ASSERT(str != NULL);

	if (len & 0x01) {
		goto type_error;
	}

	buf = (unsigned char *) duk_push_fixed_buffer(ctx, len / 2);
	DUK_ASSERT(buf != NULL);
	/* buf is always zeroed */

	for (i = 0; i < len; i++) {
		t = str[i];
		if (t >= '0' && t <= '9') {
			t = t - '0' + 0x00;
		} else if (t >= 'a' && t <= 'f') {
			t = t - 'a' + 0x0a;
		} else if (t >= 'A' && t <= 'F') {
			t = t - 'A' + 0x0a;
		} else {
			goto type_error;
		}

		if (i & 0x01) {
			buf[i >> 1] += (unsigned char) t;
		} else {
			buf[i >> 1] = (unsigned char) (t << 4);
		}
	}

	duk_replace(ctx, index);
	return;

 type_error:
	DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, "hex decode failed");
}

const char *duk_json_encode(duk_context *ctx, int index) {
#ifdef DUK_USE_ASSERTIONS
	int top_at_entry = duk_get_top(ctx);
#endif
	const char *ret;

	index = duk_require_normalize_index(ctx, index);
	duk_builtin_json_stringify_helper(ctx,
	                                  index /*idx_value*/,
	                                  DUK_INVALID_INDEX /*idx_replacer*/,
	                                  DUK_INVALID_INDEX /*idx_space*/,
	                                  0 /*flags*/);
	DUK_ASSERT(duk_is_string(ctx, -1));
	duk_replace(ctx, index);
	ret = duk_get_string(ctx, index);

	DUK_ASSERT(duk_get_top(ctx) == top_at_entry);

	return ret;
}

void duk_json_decode(duk_context *ctx, int index) {
#ifdef DUK_USE_ASSERTIONS
	int top_at_entry = duk_get_top(ctx);
#endif

	index = duk_require_normalize_index(ctx, index);
	duk_builtin_json_parse_helper(ctx,
	                              index /*idx_value*/,
	                              DUK_INVALID_INDEX /*idx_reviver*/,
	                              0 /*flags*/);
	duk_replace(ctx, index);

	DUK_ASSERT(duk_get_top(ctx) == top_at_entry);
}

#line 1 "duk_api_compile.c"
/*
 *  Compilation and evaluation
 */

/* include removed: duk_internal.h */

/* FIXME: placeholder */

/* Eval is just a wrapper now. */
void duk_eval(duk_context *ctx) {
	int comp_flags;

	/* [ ... source ] */

	comp_flags = DUK_COMPILE_EVAL;
	if (duk_is_strict_call(ctx)) {
		comp_flags |= DUK_COMPILE_STRICT;
	}
	duk_compile(ctx, comp_flags);

	/* [ ... closure ] */

	duk_call(ctx, 0);

	/* [ ... result ] */
}

void duk_compile(duk_context *ctx, int flags) {
	duk_hthread *thr = (duk_hthread *) ctx;
	int comp_flags;
	duk_hcompiledfunction *h_templ;

	/* [ ... source ] */

	/* FIXME: flags */
	/* FIXME: unnecessary translation of flags */

	comp_flags = 0;
	if (flags & DUK_COMPILE_EVAL) {
		comp_flags = DUK_JS_COMPILE_FLAG_EVAL;
	}
	if (flags & DUK_COMPILE_FUNCTION) {
		duk_error(ctx, DUK_ERR_UNIMPLEMENTED_ERROR, "unimplemented");
	}
	if (flags & DUK_COMPILE_STRICT) {
		comp_flags = DUK_JS_COMPILE_FLAG_STRICT;
	}

	duk_js_compile(thr, comp_flags);
	h_templ = (duk_hcompiledfunction *) duk_get_hobject(ctx, -1);

	/* [ ... func_template ] */

        duk_js_push_closure(thr,
	                   h_templ,
	                   thr->builtins[DUK_BIDX_GLOBAL_ENV],
	                   thr->builtins[DUK_BIDX_GLOBAL_ENV]);

	/* [ ... func_template closure ] */

	duk_remove(ctx, -2);  /* -> [ ... closure ] */

	/* [ ... closure ] */
}

#line 1 "duk_api_memory.c"
/*
 *  Memory calls.
 */

/* include removed: duk_internal.h */

void *duk_alloc_raw(duk_context *ctx, size_t size) {
	duk_hthread *thr = (duk_hthread *) ctx;

	DUK_ASSERT(ctx != NULL);

	return DUK_ALLOC_RAW(thr->heap, size);
}

void duk_free_raw(duk_context *ctx, void *ptr) {
	duk_hthread *thr = (duk_hthread *) ctx;

	DUK_ASSERT(ctx != NULL);

	DUK_FREE_RAW(thr->heap, ptr);
}

void *duk_realloc_raw(duk_context *ctx, void *ptr, size_t size) {
	duk_hthread *thr = (duk_hthread *) ctx;

	DUK_ASSERT(ctx != NULL);

	return DUK_REALLOC_RAW(thr->heap, ptr, size);
}

void *duk_alloc(duk_context *ctx, size_t size) {
	duk_hthread *thr = (duk_hthread *) ctx;

	DUK_ASSERT(ctx != NULL);

	return DUK_ALLOC(thr->heap, size);
}

void duk_free(duk_context *ctx, void *ptr) {
	duk_hthread *thr = (duk_hthread *) ctx;

	DUK_ASSERT(ctx != NULL);

	DUK_FREE(thr->heap, ptr);
}

void *duk_realloc(duk_context *ctx, void *ptr, size_t size) {
	duk_hthread *thr = (duk_hthread *) ctx;

	DUK_ASSERT(ctx != NULL);

	/*
	 *  Note: since this is an exposed API call, there should be
	 *  no way a mark-and-sweep could have a side effect on the
	 *  memory allocation behind 'ptr'; the pointer should never
	 *  be something that Duktape wants to change.
	 *
	 *  Thus, no need to use DUK_REALLOC_INDIRECT (and we don't
	 *  have the storage location here anyway).
	 */

	return DUK_REALLOC(thr->heap, ptr, size);
}

void duk_get_memory_functions(duk_context *ctx, duk_memory_functions *out_funcs) {
	duk_hthread *thr = (duk_hthread *) ctx;
	duk_heap *heap;

	DUK_ASSERT(ctx != NULL);
	DUK_ASSERT(out_funcs != NULL);
	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(thr->heap != NULL);

	heap = thr->heap;
	out_funcs->alloc = heap->alloc_func;
	out_funcs->realloc = heap->realloc_func;
	out_funcs->free = heap->free_func;
	out_funcs->udata = heap->alloc_udata;
}

#line 1 "duk_api_object.c"
/*
 *  Object handling: property access and other support functions.
 */

/* include removed: duk_internal.h */

/*
 *  Property handling
 *
 *  The API exposes only the most common property handling functions.
 *  The caller can invoke Ecmascript built-ins for full control (e.g.
 *  defineProperty, getOwnPropertyDescriptor).
 */

int duk_get_prop(duk_context *ctx, int obj_index) {
	duk_hthread *thr = (duk_hthread *) ctx;
	duk_tval *tv_obj;
	duk_tval *tv_key;
	int rc;

	DUK_ASSERT(ctx != NULL);

	/* Note: copying tv_obj and tv_key to locals to shield against a valstack
	 * resize is not necessary for a property get right now.
	 */

	tv_obj = duk_require_tval(ctx, obj_index);
	tv_key = duk_require_tval(ctx, -1);

	rc = duk_hobject_getprop(thr, tv_obj, tv_key);
	/* a value is left on stack regardless of rc */

	duk_remove(ctx, -2);  /* remove key */
	return rc;  /* 1 if property found, 0 otherwise */
}

int duk_get_prop_string(duk_context *ctx, int obj_index, const char *key) {
	DUK_ASSERT(ctx != NULL);
	DUK_ASSERT(key != NULL);

	obj_index = duk_require_normalize_index(ctx, obj_index);
	duk_push_string(ctx, key);
	return duk_get_prop(ctx, obj_index);
}

int duk_get_prop_index(duk_context *ctx, int obj_index, unsigned int arr_index) {
	DUK_ASSERT(ctx != NULL);

	obj_index = duk_require_normalize_index(ctx, obj_index);
	duk_push_number(ctx, (double) arr_index);
	return duk_get_prop(ctx, obj_index);
}

int duk_get_prop_stridx(duk_context *ctx, int obj_index, unsigned int stridx) {
	duk_hthread *thr = (duk_hthread *) ctx;

	DUK_ASSERT(ctx != NULL);
	DUK_ASSERT(stridx >= 0 && stridx < DUK_HEAP_NUM_STRINGS);

	obj_index = duk_require_normalize_index(ctx, obj_index);
	duk_push_hstring(ctx, thr->strs[stridx]);
	return duk_get_prop(ctx, obj_index);
}

int duk_put_prop(duk_context *ctx, int obj_index) {
	duk_hthread *thr = (duk_hthread *) ctx;
	duk_tval *tv_obj;
	duk_tval *tv_key;
	duk_tval *tv_val;
	int throw_flag;
	int rc;

	DUK_ASSERT(ctx != NULL);

	/* Note: copying tv_obj and tv_key to locals to shield against a valstack
	 * resize is not necessary for a property put right now (putprop protects
	 * against it internally).
	 */

	tv_obj = duk_require_tval(ctx, obj_index);
	tv_key = duk_require_tval(ctx, -2);
	tv_val = duk_require_tval(ctx, -1);
	throw_flag = duk_is_strict_call(ctx);  /* FIXME */

	rc = duk_hobject_putprop(thr, tv_obj, tv_key, tv_val, throw_flag);

	duk_pop_2(ctx);  /* remove key and value */
	return rc;  /* 1 if property found, 0 otherwise */
}

int duk_put_prop_string(duk_context *ctx, int obj_index, const char *key) {
	DUK_ASSERT(ctx != NULL);
	DUK_ASSERT(key != NULL);

	obj_index = duk_require_normalize_index(ctx, obj_index);
	duk_push_string(ctx, key);
	duk_swap_top(ctx, -2);  /* [val key] -> [key val] */
	return duk_put_prop(ctx, obj_index);
}

int duk_put_prop_index(duk_context *ctx, int obj_index, unsigned int arr_index) {
	DUK_ASSERT(ctx != NULL);

	obj_index = duk_require_normalize_index(ctx, obj_index);
	duk_push_number(ctx, (double) arr_index);
	duk_swap_top(ctx, -2);  /* [val key] -> [key val] */
	return duk_put_prop(ctx, obj_index);
}

int duk_put_prop_stridx(duk_context *ctx, int obj_index, unsigned int stridx) {
	duk_hthread *thr = (duk_hthread *) ctx;

	DUK_ASSERT(ctx != NULL);
	DUK_ASSERT(stridx >= 0 && stridx < DUK_HEAP_NUM_STRINGS);

	obj_index = duk_require_normalize_index(ctx, obj_index);
	duk_push_hstring(ctx, thr->strs[stridx]);
	duk_swap_top(ctx, -2);  /* [val key] -> [key val] */
	return duk_put_prop(ctx, obj_index);
}

int duk_del_prop(duk_context *ctx, int obj_index) {
	duk_hthread *thr = (duk_hthread *) ctx;
	duk_tval *tv_obj;
	duk_tval *tv_key;
	int throw_flag;
	int rc;

	DUK_ASSERT(ctx != NULL);

	/* Note: copying tv_obj and tv_key to locals to shield against a valstack
	 * resize is not necessary for a property delete right now.
	 */

	tv_obj = duk_require_tval(ctx, obj_index);
	tv_key = duk_require_tval(ctx, -1);
	throw_flag = duk_is_strict_call(ctx);  /* FIXME */

	rc = duk_hobject_delprop(thr, tv_obj, tv_key, throw_flag);

	duk_pop(ctx);  /* remove key */
	return rc;
}

int duk_del_prop_string(duk_context *ctx, int obj_index, const char *key) {
	DUK_ASSERT(ctx != NULL);
	DUK_ASSERT(key != NULL);

	obj_index = duk_require_normalize_index(ctx, obj_index);
	duk_push_string(ctx, key);
	return duk_del_prop(ctx, obj_index);
}

int duk_del_prop_index(duk_context *ctx, int obj_index, unsigned int arr_index) {
	DUK_ASSERT(ctx != NULL);

	obj_index = duk_require_normalize_index(ctx, obj_index);
	duk_push_number(ctx, (double) arr_index);
	return duk_del_prop(ctx, obj_index);
}

int duk_del_prop_stridx(duk_context *ctx, int obj_index, unsigned int stridx) {
	duk_hthread *thr = (duk_hthread *) ctx;

	DUK_ASSERT(ctx != NULL);
	DUK_ASSERT(stridx >= 0 && stridx < DUK_HEAP_NUM_STRINGS);

	obj_index = duk_require_normalize_index(ctx, obj_index);
	duk_push_hstring(ctx, thr->strs[stridx]);
	return duk_del_prop(ctx, obj_index);
}

int duk_has_prop(duk_context *ctx, int obj_index) {
	duk_hthread *thr = (duk_hthread *) ctx;
	duk_tval *tv_obj;
	duk_tval *tv_key;
	int rc;

	DUK_ASSERT(ctx != NULL);

	/* Note: copying tv_obj and tv_key to locals to shield against a valstack
	 * resize is not necessary for a property existence check right now.
	 */

	tv_obj = duk_require_tval(ctx, obj_index);
	tv_key = duk_require_tval(ctx, -1);

	rc = duk_hobject_hasprop(thr, tv_obj, tv_key);

	duk_pop(ctx);  /* remove key */
	return rc;  /* 1 if property found, 0 otherwise */
}

int duk_has_prop_string(duk_context *ctx, int obj_index, const char *key) {
	DUK_ASSERT(ctx != NULL);
	DUK_ASSERT(key != NULL);

	obj_index = duk_require_normalize_index(ctx, obj_index);
	duk_push_string(ctx, key);
	return duk_has_prop(ctx, obj_index);
}

int duk_has_prop_index(duk_context *ctx, int obj_index, unsigned int arr_index) {
	DUK_ASSERT(ctx != NULL);

	obj_index = duk_require_normalize_index(ctx, obj_index);
	duk_push_number(ctx, (double) arr_index);
	return duk_has_prop(ctx, obj_index);
}

int duk_has_prop_stridx(duk_context *ctx, int obj_index, unsigned int stridx) {
	duk_hthread *thr = (duk_hthread *) ctx;

	DUK_ASSERT(ctx != NULL);
	DUK_ASSERT(stridx >= 0 && stridx < DUK_HEAP_NUM_STRINGS);

	obj_index = duk_require_normalize_index(ctx, obj_index);
	duk_push_hstring(ctx, thr->strs[stridx]);
	return duk_has_prop(ctx, obj_index);
}

void duk_def_prop(duk_context *ctx, int obj_index, int desc_flags) {
	duk_hthread *thr = (duk_hthread *) ctx;
	duk_hobject *obj;
	duk_hstring *key;

	DUK_ASSERT(ctx != NULL);

	obj = duk_require_hobject(ctx, obj_index);
	DUK_ASSERT(obj != NULL);
	key = duk_to_hstring(ctx, -2);
	DUK_ASSERT(key != NULL);
	DUK_ASSERT(duk_require_tval(ctx, -1) != NULL);

	duk_hobject_define_property_internal(thr, obj, key, desc_flags);

	duk_pop(ctx);  /* pop key */
}

void duk_def_prop_stridx(duk_context *ctx, int obj_index, unsigned int stridx, int desc_flags) {
	duk_hthread *thr = (duk_hthread *) ctx;
	duk_hobject *obj;
	duk_hstring *key;

	DUK_ASSERT(ctx != NULL);
	DUK_ASSERT(stridx >= 0 && stridx < DUK_HEAP_NUM_STRINGS);

	obj = duk_require_hobject(ctx, obj_index);
	DUK_ASSERT(obj != NULL);
	key = thr->strs[stridx];
	DUK_ASSERT(key != NULL);
	DUK_ASSERT(duk_require_tval(ctx, -1) != NULL);

	duk_hobject_define_property_internal(thr, obj, key, desc_flags);

	/* value already popped */
}

void duk_def_prop_stridx_builtin(duk_context *ctx, int obj_index, unsigned int stridx, unsigned int builtin_idx, int desc_flags) {
	duk_hthread *thr = (duk_hthread *) ctx;
	duk_hobject *obj;
	duk_hstring *key;

	DUK_ASSERT(ctx != NULL);
	DUK_ASSERT(stridx >= 0 && stridx < DUK_HEAP_NUM_STRINGS);
	DUK_ASSERT(builtin_idx >= 0 && builtin_idx < DUK_NUM_BUILTINS);

	obj = duk_require_hobject(ctx, obj_index);
	DUK_ASSERT(obj != NULL);
	key = thr->strs[stridx];
	DUK_ASSERT(key != NULL);

	duk_push_hobject(ctx, thr->builtins[builtin_idx]);
	duk_hobject_define_property_internal(thr, obj, key, desc_flags);
	/* value popped by call */
}

/* This is a rare property helper; it sets the global thrower (E5 Section 13.2.3)
 * setter/getter into an object property.  Since there are so few places where
 * accessor properties are created (by the implementation), there are almost no
 * other API calls for creating such properties (except calling Object.defineProperty()
 * properly).
 *
 * This is needed by the 'arguments' object creation code and by function instance
 * creation code.
 */

void duk_def_prop_stridx_thrower(duk_context *ctx, int obj_index, unsigned int stridx, int desc_flags) {
	duk_hthread *thr = (duk_hthread *) ctx;
	duk_hobject *obj;
	duk_hobject *thrower;
	int e_idx;
	int h_idx;

	DUK_ASSERT(ctx != NULL);
	DUK_ASSERT(stridx >= 0 && stridx < DUK_HEAP_NUM_STRINGS);

	/*
	 *  Since we don't have an internal define function for creating
	 *  accessor values, first set the value to 'undefined', then get
	 *  the storage slot and update in-place to an accessor.
	 */

	obj_index = duk_require_normalize_index(ctx, obj_index);

	duk_push_undefined(ctx);
	duk_def_prop_stridx(ctx, obj_index, stridx, desc_flags);

	obj = duk_require_hobject(ctx, obj_index);
	duk_hobject_find_existing_entry(obj, DUK_HTHREAD_GET_STRING(thr, stridx), &e_idx, &h_idx);
	DUK_ASSERT(e_idx >= 0 && e_idx < obj->e_used);

	/* no need to decref, as previous value is 'undefined' */
	thrower = thr->builtins[DUK_BIDX_TYPE_ERROR_THROWER];
	DUK_HOBJECT_E_SLOT_SET_ACCESSOR(obj, e_idx);
	DUK_HOBJECT_E_SET_VALUE_GETTER(obj, e_idx, thrower);
	DUK_HOBJECT_E_SET_VALUE_SETTER(obj, e_idx, thrower);
	DUK_HOBJECT_INCREF(thr, thrower);
	DUK_HOBJECT_INCREF(thr, thrower);  /* XXX: macro to increment a count directly */
}

/*
 *  Object related
 *
 *  Note: seal() and freeze() are accessible through Ecmascript bindings,
 *  and are not exposed through the API.
 */

void duk_compact(duk_context *ctx, int obj_index) {
	duk_hthread *thr = (duk_hthread *) ctx;
	duk_hobject *obj;

	DUK_ASSERT(ctx != NULL);

	obj = duk_get_hobject(ctx, obj_index);
	if (obj) {
		/* Note: this may fail, caller should protect the call if necessary */
		duk_hobject_compact_props(thr, obj);
	}
}

/* FIXME: the duk_hobject_enum.c stack APIs should be reworked */

void duk_enum(duk_context *ctx, int obj_index, int enum_flags) {
	DUK_ASSERT(ctx != NULL);

	duk_require_hobject(ctx, obj_index);
	duk_dup(ctx, obj_index);
	duk_hobject_enumerator_create(ctx, enum_flags);   /* [target] -> [enum] */
}

int duk_next(duk_context *ctx, int enum_index, int get_value) {
	duk_require_hobject(ctx, enum_index);
	duk_dup(ctx, enum_index);
	return duk_hobject_enumerator_next(ctx, get_value);
}


#line 1 "duk_api_string.c"
/*
 *  String manipulation
 */

/* include removed: duk_internal.h */

static void concat_and_join_helper(duk_context *ctx, int count, int is_join) {
	unsigned int i;
	unsigned int len;
	unsigned int idx;
	duk_hstring *h;
	duk_u8 *buf;

	DUK_ASSERT(ctx != NULL);

	if (count <= 0) {
		duk_push_hstring_stridx(ctx, DUK_STRIDX_EMPTY_STRING);
		return;
	}

	if (is_join) {
		h = duk_to_hstring(ctx, -count-1);
		DUK_ASSERT(h != NULL);
		len = DUK_HSTRING_GET_BYTELEN(h) * (count - 1);
	} else {
		len = 0;
	}

	for (i = count; i >= 1; i--) {
		duk_to_string(ctx, -i);
		h = duk_require_hstring(ctx, -i);
		len += DUK_HSTRING_GET_BYTELEN(h);
	}

	DUK_DDDPRINT("join/concat %d strings, total length %d bytes", count, len);

	/* use stack allocated buffer to ensure reachability in errors (e.g. intern error) */
	buf = duk_push_fixed_buffer(ctx, len);
	DUK_ASSERT(buf != NULL);

	/* [... (sep) str1 str2 ... strN buf] */

	idx = 0;
	for (i = count; i >= 1; i--) {
		if (is_join && i != count) {
			h = duk_require_hstring(ctx, -count-2);  /* extra -1 for buffer */
			memcpy(buf + idx, DUK_HSTRING_GET_DATA(h), DUK_HSTRING_GET_BYTELEN(h));
			idx += DUK_HSTRING_GET_BYTELEN(h);
		}
		h = duk_require_hstring(ctx, -i-1);  /* extra -1 for buffer */
		memcpy(buf + idx, DUK_HSTRING_GET_DATA(h), DUK_HSTRING_GET_BYTELEN(h));
		idx += DUK_HSTRING_GET_BYTELEN(h);
	}

	DUK_ASSERT(idx == len);

	/* [... (sep) str1 str2 ... strN buf] */

	/* get rid of the strings early to minimize memory use before intern */

	if (is_join) {
		duk_replace(ctx, -count-2);  /* overwrite sep */
		duk_pop_n(ctx, count);
	} else {
		duk_replace(ctx, -count-1);  /* overwrite str1 */
		duk_pop_n(ctx, count-1);
	}

	/* [... buf] */

	/*
	 *  FIXME: just allow C code to call duk_to_string() on buffers.
	 *  This allows C code to manufacture internal keys, but since we
	 *  trust C code anyway, this is not a big issue.
	 */

	duk_push_lstring(ctx, (const char *) buf, len);

	/* [... buf res] */

	duk_remove(ctx, -2);

	/* [... res] */
}

void duk_concat(duk_context *ctx, unsigned int count) {
	concat_and_join_helper(ctx, count, 0 /*is_join*/);
}

void duk_join(duk_context *ctx, unsigned int count) {
	concat_and_join_helper(ctx, count, 1 /*is_join*/);
}

void duk_decode_string(duk_context *ctx, int index, duk_decode_char_function callback, void *udata) {
	DUK_ERROR((duk_hthread *) ctx, DUK_ERR_UNIMPLEMENTED_ERROR, "FIXME");
}

void duk_map_string(duk_context *ctx, int index, duk_map_char_function callback, void *udata) {
	DUK_ERROR((duk_hthread *) ctx, DUK_ERR_UNIMPLEMENTED_ERROR, "FIXME");
}

void duk_substring(duk_context *ctx, int index, size_t start_offset, size_t end_offset) {
	duk_hthread *thr = (duk_hthread *) ctx;
	duk_hstring *h;
	duk_hstring *res;
	size_t start_byte_offset;
	size_t end_byte_offset;

	DUK_ASSERT(ctx != NULL);

	index = duk_require_normalize_index(ctx, index);
	h = duk_require_hstring(ctx, index);
	DUK_ASSERT(h != NULL);

	if (end_offset >= DUK_HSTRING_GET_CHARLEN(h)) {
		end_offset = DUK_HSTRING_GET_CHARLEN(h);
	}
	if (start_offset > end_offset) {
		start_offset = end_offset;
	}

	DUK_ASSERT(start_offset >= 0 && start_offset <= end_offset && start_offset <= DUK_HSTRING_GET_CHARLEN(h));
	DUK_ASSERT(end_offset >= 0 && end_offset >= start_offset && end_offset <= DUK_HSTRING_GET_CHARLEN(h));

	start_byte_offset = (size_t) duk_heap_strcache_offset_char2byte(thr, h, start_offset);
	end_byte_offset = (size_t) duk_heap_strcache_offset_char2byte(thr, h, end_offset);

	DUK_ASSERT(end_byte_offset >= start_byte_offset);

	res = duk_heap_string_intern_checked(thr,
	                                     DUK_HSTRING_GET_DATA(h) + start_byte_offset,
	                                     end_byte_offset - start_byte_offset);

	duk_push_hstring(ctx, res);
	duk_replace(ctx, index);
}

/* FIXME: this is quite clunky.  Add Unicode helpers to scan backwards and
 * forwards with a callback to process codepoints?
 */
void duk_trim(duk_context *ctx, int index) {
	duk_hthread *thr = (duk_hthread *) ctx;
	duk_hstring *h;
	duk_u8 *p, *p_start, *p_end, *p_tmp1, *p_tmp2;  /* pointers for scanning */
	duk_u8 *q_start, *q_end;  /* start (incl) and end (excl) of trimmed part */
	duk_u32 cp;

	index = duk_require_normalize_index(ctx, index);
	h = duk_require_hstring(ctx, index);
	DUK_ASSERT(h != NULL);

	p_start = DUK_HSTRING_GET_DATA(h);
	p_end = p_start + DUK_HSTRING_GET_BYTELEN(h);

	p = p_start;
	while (p < p_end) {
		p_tmp1 = p;
		cp = duk_unicode_xutf8_get_u32_checked(thr, &p_tmp1, p_start, p_end);
		if (!(duk_unicode_is_whitespace(cp) || duk_unicode_is_line_terminator(cp))) {
			break;
		}
		p = p_tmp1;
	}
	q_start = p;
	if (p == p_end) {
		/* entire string is whitespace */
		q_end = p;
		goto scan_done;
	}

	p = p_end;
	while (p > p_start) {
		p_tmp1 = p;
		while (p > p_start) {
			p--;
			if (((*p) & 0xc0) != 0x80) {
				break;
			}
		}
		p_tmp2 = p;

		cp = duk_unicode_xutf8_get_u32_checked(thr, &p_tmp2, p_start, p_end);
		if (!(duk_unicode_is_whitespace(cp) || duk_unicode_is_line_terminator(cp))) {
			p = p_tmp1;
			break;
		}
	}
	q_end = p;

 scan_done:
	/* This may happen when forward and backward scanning disagree.
	 * This may happen for non-extended-UTF-8 strings.
	 */
	if (q_end < q_start) {
		q_end = q_start;
	}

	DUK_ASSERT(q_start >= p_start && q_start <= p_end);
	DUK_ASSERT(q_end >= p_start && q_end <= p_end);
	DUK_ASSERT(q_end >= q_start);

	DUK_DDDPRINT("trim: p_start=%p, p_end=%p, q_start=%p, q_end=%p",
	             (void *) p_start, (void *) p_end, (void *) q_start, (void *) q_end);

	if (q_start == p_start && q_end == p_end) {
		DUK_DDDPRINT("nothing was trimmed: avoid interning (hashing etc)");
		return;
	}

	duk_push_lstring(ctx, (const char *) q_start, (size_t) (q_end - q_start));
	duk_replace(ctx, index);
}

#line 1 "duk_api_thread.c"
/*
 *  Thread handling
 */

/* include removed: duk_internal.h */

/* FIXME */

#line 1 "duk_api_var.c"
/*
 *  Variable access
 */

/* include removed: duk_internal.h */

void duk_get_var(duk_context *ctx) {
	duk_hthread *thr = (duk_hthread *) ctx;
	duk_activation *act;
	duk_hstring *h_varname;
	int throw_flag = 1;  /* always throw ReferenceError for unresolvable */

	DUK_ASSERT(ctx != NULL);

	h_varname = duk_require_hstring(ctx, -1);  /* XXX: tostring? */
	DUK_ASSERT(h_varname != NULL);

	act = duk_hthread_get_current_activation(thr);
	if (act) {
		(void) duk_js_getvar_activation(thr, act, h_varname, throw_flag);  /* -> [ ... varname val this ] */
	} else {
		/* Outside any activation -> look up from global. */
		DUK_ASSERT(thr->builtins[DUK_BIDX_GLOBAL_ENV] != NULL);
		(void) duk_js_getvar_envrec(thr, thr->builtins[DUK_BIDX_GLOBAL_ENV], h_varname, throw_flag);
	}

	/* [ ... varname val this ]  (because throw_flag == 1, always resolved) */

	duk_pop(ctx);
	duk_remove(ctx, -2);

	/* [ ... val ] */

	/* Return value would be pointless: because throw_flag==1, we always
	 * throw if the identifier doesn't resolve.
	 */
	return;
}

void duk_put_var(duk_context *ctx) {
	duk_hthread *thr = (duk_hthread *) ctx;
	duk_activation *act;
	duk_hstring *h_varname;
	duk_tval *tv_val;
	int throw_flag;

	DUK_ASSERT(ctx != NULL);

	h_varname = duk_require_hstring(ctx, -2);  /* XXX: tostring? */
	DUK_ASSERT(h_varname != NULL);

	tv_val = duk_require_tval(ctx, -1);

	throw_flag = duk_is_strict_call(ctx);

	act = duk_hthread_get_current_activation(thr);
	if (act) {
		duk_js_putvar_activation(thr, act, h_varname, tv_val, throw_flag);  /* -> [ ... varname val this ] */
	} else {
		/* Outside any activation -> put to global. */
		DUK_ASSERT(thr->builtins[DUK_BIDX_GLOBAL_ENV] != NULL);
		duk_js_putvar_envrec(thr, thr->builtins[DUK_BIDX_GLOBAL_ENV], h_varname, tv_val, throw_flag);
	}

	/* [ ... varname val ] */

	duk_pop_2(ctx);

	/* [ ... ] */

	return;
}

int duk_del_var(duk_context *ctx) {
	DUK_ERROR((duk_hthread *) ctx, DUK_ERR_UNIMPLEMENTED_ERROR, "unimplemented");
	return 0;
}

int duk_has_var(duk_context *ctx) {
	DUK_ERROR((duk_hthread *) ctx, DUK_ERR_UNIMPLEMENTED_ERROR, "unimplemented");
	return 0;
}


#line 1 "duk_builtin_array.c"
/*
 *  Array built-ins
 *
 *  Note that most Array built-ins are intentionally generic and work even
 *  when the 'this' binding is not an Array instance.  To ensure this,
 *  Array algorithms do not assume "magical" Array behavior for the "length"
 *  property, for instance.
 *
 *  FIXME: the "Throw" flag should be set for (almost?) all [[Put]] and
 *  [[Delete]] operations, but it's currently false throughout.  C typing
 *  is incorrect in several places, and array lengths above 2G won't work
 *  reliably.  Further, some valid array length values may be above 2**32-1,
 *  and this is not always correctly handled.
 */

/* include removed: duk_internal.h */

/* Shared entry code for many Array built-ins.  Note that length is left
 * on stack (it could be popped, but that's not necessary).
 */
static unsigned int push_this_obj_len_u32(duk_context *ctx) {
	unsigned int len;

	duk_push_this_coercible_to_object(ctx);
	duk_get_prop_stridx(ctx, -1, DUK_STRIDX_LENGTH);
	len = duk_to_uint32(ctx, -1);

	/* -> [ ... ToObject(this) ToUint32(length) ] */
	return len;
}

/*
 *  Constructor
 */

int duk_builtin_array_constructor(duk_context *ctx) {
	int nargs;
	double d;
	duk_u32 len;
	int i;

	nargs = duk_get_top(ctx);
	duk_push_array(ctx);

	if (nargs == 1 && duk_is_number(ctx, 0)) {
		/* FIXME: expensive check */
		d = duk_get_number(ctx, 0);
		len = duk_to_uint32(ctx, 0);
		if (((double) len) != d) {
			return DUK_RET_RANGE_ERROR;
		}

		/* FIXME: if 'len' is low, may want to ensure array part is kept:
		 * the caller is likely to want a dense array.
		 */
		duk_dup(ctx, 0);
		duk_put_prop_stridx(ctx, -2, DUK_STRIDX_LENGTH);  /* [ ToUint32(len) array ToUint32(len) ] -> [ ToUint32(len) array ] */
		return 1;
	}

	/* FIXME: optimize by creating array into correct size directly, and
	 * operating on the array part directly; values can be memcpy()'d from
	 * value stack directly as long as refcounts are increased.
	 */
	for (i = 0; i < nargs; i++) {
		duk_dup(ctx, i);
		duk_put_prop_index(ctx, -2, i);
	}
	return 1;
}

/*
 *  isArray()
 */

int duk_builtin_array_constructor_is_array(duk_context *ctx) {
	duk_hobject *h;

	h = duk_get_hobject_with_class(ctx, 0, DUK_HOBJECT_CLASS_ARRAY);
	duk_push_boolean(ctx, (h != NULL));
	return 1;
}

/*
 *  toString()
 */

int duk_builtin_array_prototype_to_string(duk_context *ctx) {
	duk_push_this_coercible_to_object(ctx);
	duk_get_prop_stridx(ctx, -1, DUK_STRIDX_JOIN);

	/* [ ... this func ] */
	if (!duk_is_callable(ctx, -1)) {
		/* Fall back to the initial (original) Object.toString().  We don't
		 * currently have pointers to the built-in functions, only the top
		 * level global objects (like "Array") so this is now done in a bit
		 * of a hacky manner.  It would be cleaner to push the (original)
		 * function and use duk_call_method().
		 */

		/* FIXME: 'this' will be ToObject() coerced twice, which is incorrect
		 * but should have no visible side effects.
		 */
		DUK_DDDPRINT("this.join is not callable, fall back to (original) Object.toString");
		duk_set_top(ctx, 0);
		return duk_builtin_object_prototype_to_string(ctx);
	}

	/* [ ... this func ] */

	duk_insert(ctx, -2);

	/* [ ... func this ] */

	DUK_DDDPRINT("calling: func=%!iT, this=%!iT", duk_get_tval(ctx, -2), duk_get_tval(ctx, -1));
	duk_call_method(ctx, 0);

	return 1;
}

/*
 *  concat()
 */

int duk_builtin_array_prototype_concat(duk_context *ctx) {
	int i, n;
	int j, len;
	int idx;
	duk_hobject *h;

	/* FIXME: the insert here is a bit expensive if there are a lot of items.
	 * It could also be special cased in the outermost for loop quite easily
	 * (as the element is dup()'d anyway).
	 */

	duk_push_this_coercible_to_object(ctx);
	duk_insert(ctx, 0);
	n = duk_get_top(ctx);
	duk_push_array(ctx);  /* -> [ ToObject(this) item1 ... itemN arr ] */

	idx = 0;
	for (i = 0; i < n; i++) {
		DUK_ASSERT_TOP(ctx, n + 1);

		/* [ ToObject(this) item1 ... itemN arr ] */

		duk_dup(ctx, i);
		h = duk_get_hobject_with_class(ctx, -1, DUK_HOBJECT_CLASS_ARRAY);
		if (!h) {
			duk_put_prop_index(ctx, -2, idx++);
			continue;
		}

		/* [ ToObject(this) item1 ... itemN arr item(i) ] */

		/* FIXME: an array can have length higher than 32 bits; this is not handled
		 * correctly now (also len is signed so length above 2**31-1 will have trouble.
		 */
		len = duk_get_length(ctx, -1);
		for (j = 0; j < len; j++) {
			if (duk_get_prop_index(ctx, -1, j)) {
				/* [ ToObject(this) item1 ... itemN arr item(i) item(i)[j] ] */
				duk_put_prop_index(ctx, -3, idx++);
			} else {
				idx++;
				duk_pop(ctx);
			}
		}
		duk_pop(ctx);
	}

	DUK_ASSERT_TOP(ctx, n + 1);
	return 1;
}

/*
 *  join(), toLocaleString()
 */

/* Note: checking valstack is necessary, but only in the per-element loop */

/* FIXME: This placeholder does not work well for a large number of elements.
 * Provide proper hierarchical concat/join primitives in the API and use
 * them here.
 */

static int array_join_helper(duk_context *ctx, int to_locale_string) {
	duk_u32 len;
	duk_u32 i;

	DUK_ASSERT_TOP(ctx, 1);
	if (duk_is_undefined(ctx, 0)) {
		duk_pop(ctx);
		duk_push_hstring_stridx(ctx, DUK_STRIDX_COMMA);
	} else {
		duk_to_string(ctx, 0);
	}

	len = push_this_obj_len_u32(ctx);

	/* [ sep ToObject(this) len ] */

	DUK_DDDPRINT("sep=%!T, this=%!T, len=%d", duk_get_tval(ctx, 0), duk_get_tval(ctx, 1), len);

	duk_require_stack(ctx, len + 1);
	duk_dup(ctx, 0);

	/* [ sep ToObject(this) len sep ] */

	for (i = 0; i < len; i++) {
		duk_get_prop_index(ctx, 1, i);
		if (duk_is_null_or_undefined(ctx, -1)) {
			duk_pop(ctx);
			duk_push_hstring_stridx(ctx, DUK_STRIDX_EMPTY_STRING);
		} else {
			if (to_locale_string) {
				duk_to_object(ctx, -1);
				duk_get_prop_stridx(ctx, -1, DUK_STRIDX_TO_LOCALE_STRING);
				duk_insert(ctx, -2);  /* -> [ ... toLocaleString ToObject(val) ] */
				duk_call_method(ctx, 0);
				duk_to_string(ctx, -1);
			} else {
				duk_to_string(ctx, -1);
			}
		}
	}

	/* [ sep ToObject(this) len sep str0 ... str(len-1) ] */

	duk_join(ctx, len);
	return 1;
}

int duk_builtin_array_prototype_join(duk_context *ctx) {
	DUK_ASSERT_TOP(ctx, 1);  /* nargs is 1 */
	return array_join_helper(ctx, 0 /*to_locale_string*/);
}

int duk_builtin_array_prototype_to_locale_string(duk_context *ctx) {
	DUK_ASSERT_TOP(ctx, 0);  /* nargs is 0 */
	duk_push_undefined(ctx);  /* array_join_helper() will default to a comma */
	return array_join_helper(ctx, 1 /*to_locale_string*/);
}

/*
 *  pop(), push()
 */

int duk_builtin_array_prototype_pop(duk_context *ctx) {
	unsigned int len;
	unsigned int idx;

	DUK_ASSERT_TOP(ctx, 0);
	len = push_this_obj_len_u32(ctx);
	if (len == 0) {
		duk_push_int(ctx, 0);
		duk_put_prop_stridx(ctx, 0, DUK_STRIDX_LENGTH);  /* FIXME: Throw */
		return 0;
	}
	idx = len - 1;

	duk_get_prop_index(ctx, 0, idx);
	duk_del_prop_index(ctx, 0, idx);  /* FIXME: Throw */
	duk_push_int(ctx, idx);  /* FIXME: unsigned */
	duk_put_prop_stridx(ctx, 0, DUK_STRIDX_LENGTH);  /* FIXME: Throw */
	return 1;
}

int duk_builtin_array_prototype_push(duk_context *ctx) {
	/* Note: 'this' is not necessarily an Array object.  The push()
	 * algorithm is supposed to work for other kinds of objects too,
	 * so the algorithm has e.g. an explicit update for the 'length'
	 * property which is normally "magical" in arrays.
	 */

	double len;
	int i, n;

	n = duk_get_top(ctx);
	len = (double) push_this_obj_len_u32(ctx);

	/* [ arg1 ... argN obj length ] */

	/* Note: we keep track of length with a double instead of a 32-bit
	 * (unsigned) int because the length can go beyond 32 bits and the
	 * final length value is NOT wrapped to 32 bits on this call.
	 */

	for (i = 0; i < n; i++) {
		duk_push_number(ctx, len);
		duk_dup(ctx, i);
		duk_put_prop(ctx, -4);  /* FIXME: "Throw" */
		len += 1.0;
	}

	duk_push_number(ctx, len);
	duk_dup_top(ctx);
	duk_put_prop_stridx(ctx, -4, DUK_STRIDX_LENGTH);

	/* [ arg1 ... argN obj length new_length ] */
	return 1;
}

/*
 *  sort()
 *
 *  Currently qsort with random pivot.  This is now really, really slow,
 *  because there is no fast path for array parts.
 */

static int array_sort_compare(duk_context *ctx, int idx1, int idx2) {
	int have1, have2;
	int undef1, undef2;
	int ret;
	int idx_obj = 1, idx_fn = 0;  /* fixed offsets in valstack */
	duk_hstring *h1, *h2;

	/* Fast exit if indices are identical.  This is valid for a non-existent property,
	 * for an undefined value, and almost always for ToString() coerced comparison of
	 * arbitrary values (corner cases where this is not the case include e.g. a an
	 * object with varying ToString() coercion).
	 *
	 * The specification does not prohibit "caching" of values read from the array, so
	 * assuming equality for comparing an index with itself falls into the category of
	 * "caching".
	 *
	 * Also, compareFn may be inconsistent, so skipping a call to compareFn here may
	 * have an effect on the final result.  The specification does not require any
	 * specific behavior for inconsistent compare functions, so again, this fast path
	 * is OK.
	 */

	if (idx1 == idx2) {
		DUK_DDDPRINT("array_sort_compare: idx1=%d, idx2=%d -> indices identical, quick exit", idx1, idx2);
		return 0;
	}

	have1 = duk_get_prop_index(ctx, idx_obj, idx1);
	have2 = duk_get_prop_index(ctx, idx_obj, idx2);

	DUK_DDDPRINT("array_sort_compare: idx1=%d, idx2=%d, have1=%d, have2=%d, val1=%!T, val2=%!T",
	             idx1, idx2, have1, have2, duk_get_tval(ctx, -2), duk_get_tval(ctx, -1));

	if (have1) {
		if (have2) {
			;
		} else {
			ret = -1;
			goto pop_ret;
		}
	} else {
		if (have2) {
			ret = 1;
			goto pop_ret;
		} else {
			ret = 0;
			goto pop_ret;
		}
	}

	undef1 = duk_is_undefined(ctx, -2);
	undef2 = duk_is_undefined(ctx, -1);
	if (undef1) {
		if (undef2) {
			ret = 0;
			goto pop_ret;
		} else {
			ret = 1;
			goto pop_ret;
		}
	} else {
		if (undef2) {
			ret = -1;
			goto pop_ret;
		} else {
			;
		}
	}

	if (!duk_is_undefined(ctx, idx_fn)) {
		double d;

		/* no need to check callable; duk_call() will do that */
		duk_dup(ctx, idx_fn);    /* -> [ ... x y fn ] */
		duk_insert(ctx, -3);     /* -> [ ... fn x y ] */
		duk_call(ctx, 2);        /* -> [ ... res ] */

		/* The specification is a bit vague what to do if the return
		 * value is not a number.  Other implementations seem to
		 * tolerate non-numbers but e.g. V8 won't apparently do a
		 * ToNumber().
		 */

		/* FIXME: best behavior for real world compatibility? */

		d = duk_to_number(ctx, -1);
		if (d < 0.0) {
			ret = -1;
		} else if (d > 0.0) {
			ret = 1;
		} else {
			ret = 0;
		}

		duk_pop(ctx);
		DUK_DDDPRINT("-> result %d (from comparefn, after coercion)", ret);
		return ret;
	}

	/* string compare is the default (a bit oddly) */

	h1 = duk_to_hstring(ctx, -2);
	h2 = duk_to_hstring(ctx, -1);
	DUK_ASSERT(h1 != NULL);
	DUK_ASSERT(h2 != NULL);

	ret = duk_js_string_compare(h1, h2);  /* retval is directly usable */
	goto pop_ret;

 pop_ret:
	duk_pop_2(ctx);
	DUK_DDDPRINT("-> result %d", ret);
	return ret;
}

static void array_sort_swap(duk_context *ctx, int l, int r) {
	int have_l, have_r;
	int idx_obj = 1;  /* fixed offsets in valstack */

	if (l == r) {
		return;
	}

	/* swap elements; deal with non-existent elements correctly */
	have_l = duk_get_prop_index(ctx, idx_obj, l);
	have_r = duk_get_prop_index(ctx, idx_obj, r);

	if (have_r) {
		/* right exists, [[Put]] regardless whether or not left exists */
		duk_put_prop_index(ctx, idx_obj, l);
	} else {
		duk_del_prop_index(ctx, idx_obj, l);
		duk_pop(ctx);
	}

	if (have_l) {
		duk_put_prop_index(ctx, idx_obj, r);
	} else {
		duk_del_prop_index(ctx, idx_obj, r);
		duk_pop(ctx);
	}
}

#ifdef DUK_USE_DDDEBUG
/* Debug print which visualizes the qsort partitioning process. */
static void debuglog_qsort_state(duk_context *ctx, int lo, int hi, int pivot) {
	char buf[4096];
	char *ptr = buf;
	int i, n;
	n = duk_get_length(ctx, 1);
	if (n > 4000) {
		n = 4000;
	}
	*ptr++ = '[';
	for (i = 0; i < n; i++) {
		if (i == pivot) {
			*ptr++ = '|';
		} else if (i == lo) {
			*ptr++ = '<';
		} else if (i == hi) {
			*ptr++ = '>';
		} else if (i >= lo && i <= hi) {
			*ptr++ = '-';
		} else {
			*ptr++ = ' ';
		}
	}
	*ptr++ = ']';
	*ptr++ = '\0';

	DUK_DDDPRINT("%s   (lo=%d, hi=%d, pivot=%d)", buf, lo, hi, pivot);
}
#endif

static void array_qsort(duk_context *ctx, int lo, int hi) {
	duk_hthread *thr = (duk_hthread *) ctx;
	int p, l, r;

	DUK_DDDPRINT("array_qsort: lo=%d, hi=%d, obj=%!T", lo, hi, duk_get_tval(ctx, 1));

	DUK_ASSERT_TOP(ctx, 3);

	/* In some cases it may be that lo > hi, or hi < 0; these
	 * degenerate cases happen e.g. for empty arrays, and in
	 * recursion leaves.
	 */

	/* trivial cases */
	if (hi - lo < 1) {
		DUK_DDDPRINT("degenerate case, return immediately");
		return;
	}
	DUK_ASSERT(hi > lo);
	DUK_ASSERT(hi - lo + 1 >= 2);

	/* randomized pivot selection */
	p = lo + (duk_util_tinyrandom_get_bits(thr, 30) % (hi - lo + 1));  /* rnd in [lo,hi] */
	DUK_ASSERT(p >= lo && p <= hi);
	DUK_DDDPRINT("lo=%d, hi=%d, chose pivot p=%d", lo, hi, p);

	/* move pivot out of the way */
	array_sort_swap(ctx, p, lo);
	p = lo;
	DUK_DDDPRINT("pivot moved out of the way: %!T", duk_get_tval(ctx, 1));

	l = lo + 1;
	r = hi;
	for (;;) {
		/* find elements to swap */
		for (;;) {
			DUK_DDDPRINT("left scan: l=%d, r=%d, p=%d", l, r, p);
			if (l >= hi) {
				break;
			}
			if (array_sort_compare(ctx, l, p) >= 0) {  /* !(l < p) */
				break;
			}
			l++;
		}
		for (;;) {
			DUK_DDDPRINT("right scan: l=%d, r=%d, p=%d", l, r, p);
			if (r <= lo) {
				break;
			}
			if (array_sort_compare(ctx, p, r) >= 0) {  /* !(p < r) */
				break;
			}
			r--;
		}
		if (l >= r) {
			goto done;
		}
		DUK_ASSERT(l < r);

		DUK_DDDPRINT("swap %d and %d", l, r);

		array_sort_swap(ctx, l, r);

		DUK_DDDPRINT("after swap: %!T", duk_get_tval(ctx, 1));
		l++;
		r--;
	}
 done:
	/* Note that 'l' and 'r' may cross, i.e. r < l */
	DUK_ASSERT(l >= lo && l <= hi);
	DUK_ASSERT(r >= lo && r <= hi);

	/* FIXME: there's no explicit recursion bound here now.  For the average
	 * qsort recursion depth O(log n) that's not really necessary: e.g. for
	 * 2**32 recursion depth would be about 32 which is OK.  However, qsort
	 * worst case recursion depth is O(n) which may be a problem.
	 */

	/* move pivot to its final place */
	DUK_DDDPRINT("before final pivot swap: %!T", duk_get_tval(ctx, 1));
	array_sort_swap(ctx, lo, r);	

#ifdef DUK_USE_DDDEBUG
	debuglog_qsort_state(ctx, lo, hi, r);
#endif

	DUK_DDDPRINT("recurse: pivot=%d, obj=%!T", r, duk_get_tval(ctx, 1));
	array_qsort(ctx, lo, r - 1);
	array_qsort(ctx, r + 1, hi);
}

int duk_builtin_array_prototype_sort(duk_context *ctx) {
	unsigned int len;

	len = push_this_obj_len_u32(ctx);

	/* stack[0] = compareFn
	 * stack[1] = ToObject(this)
	 * stack[2] = ToUint32(length)
	 */

	array_qsort(ctx, 0, len - 1);

	DUK_ASSERT_TOP(ctx, 3);
	duk_pop(ctx);
	return 1;  /* return ToObject(this) */
}

/*
 *  splice()
 */

/* FIXME: this compiles to over 500 bytes now, even without special handling
 * for an array part.  Uses signed ints so does not handle full array range correctly.
 */

/* FIXME: can shift() / unshift() use the same helper?
 *   shift() is (close to?) <--> splice(0, 1)
 *   unshift is (close to?) <--> splice(0, 0, [items])?
 */

int duk_builtin_array_prototype_splice(duk_context *ctx) {
	int nargs;
	int item_count;
	int len;
	int rel_start;
	int del_count;
	int i;

	nargs = duk_get_top(ctx);
	if (nargs < 2) {
		duk_set_top(ctx, 2);
		nargs = 2;
	}

	len = push_this_obj_len_u32(ctx);

	rel_start = duk_to_int_clamped(ctx, 0, -len, len);
	if (rel_start < 0) {
		rel_start = len + rel_start;
	}
	DUK_ASSERT(rel_start >= 0 && rel_start <= len);

	del_count = duk_to_int_clamped(ctx, 1, 0, len - rel_start);
	DUK_ASSERT(del_count >= 0 && del_count <= len - rel_start);
	DUK_ASSERT(del_count + rel_start <= len);

	duk_push_array(ctx);

	/* stack[0] = start
	 * stack[1] = deleteCount
	 * stack[2...nargs-1] = items
	 * stack[nargs] = ToObject(this)               -3
	 * stack[nargs+1] = ToUint32(length)           -2
	 * stack[nargs+2] = result array               -1
	 */

	DUK_ASSERT_TOP(ctx, nargs + 3);

	/* Step 9: copy elements-to-be-deleted into the result array */

	for (i = 0; i < del_count; i++) {
		if (duk_get_prop_index(ctx, -3, rel_start + i)) {
			duk_put_prop_index(ctx, -2, i);  /* throw flag irrelevant (false in std alg) */
		} else {
			duk_pop(ctx);
		}
	}

	/* Steps 12 and 13: reorganize elements to make room for itemCount elements */

	DUK_ASSERT(nargs >= 2);
	item_count = nargs - 2;
	if (item_count < del_count) {
		/*    [ A B C D E F G H ]    rel_index = 2, del_count 3, item count 1
		 * -> [ A B F G H ]          (conceptual intermediate step)
		 * -> [ A B . F G H ]        (placeholder marked)
		 *    [ A B C F G H ]        (actual result at this point, C will be replaced)
		 */

		DUK_ASSERT_TOP(ctx, nargs + 3);

		for (i = rel_start; i < len - del_count; i++) {
			if (duk_get_prop_index(ctx, -3, i + del_count)) {
				duk_put_prop_index(ctx, -4, i + item_count);  /* FIXME: Throw */
			} else {
				duk_pop(ctx);
				duk_del_prop_index(ctx, -3, i + item_count);  /* FIXME: Throw */
			}
		}

		DUK_ASSERT_TOP(ctx, nargs + 3);

		/* loop iterator init and limit changed from standard algorithm */
		for (i = len - 1; i >= len - del_count + item_count; i--) {
			duk_del_prop_index(ctx, -3, i);  /* FIXME: Throw */
		}

		DUK_ASSERT_TOP(ctx, nargs + 3);
	} else if (item_count > del_count) {
		/*    [ A B C D E F G H ]    rel_index = 2, del_count 3, item count 4
		 * -> [ A B F G H ]          (conceptual intermediate step)
		 * -> [ A B . . . . F G H ]  (placeholder marked)
		 *    [ A B C D E F F G H ]  (actual result at this point)
		 */

		DUK_ASSERT_TOP(ctx, nargs + 3);

		/* loop iterator init and limit changed from standard algorithm */
		for (i = len - del_count - 1; i >= rel_start; i--) {
			if (duk_get_prop_index(ctx, -3, i + del_count)) {
				duk_put_prop_index(ctx, -4, i + item_count);  /* FIXME: Throw */
			} else {
				duk_pop(ctx);
				duk_del_prop_index(ctx, -3, i + item_count);  /* FIXME: Throw */
			}
		}

		DUK_ASSERT_TOP(ctx, nargs + 3);
	} else {
		/*    [ A B C D E F G H ]    rel_index = 2, del_count 3, item count 3
		 * -> [ A B F G H ]          (conceptual intermediate step)
		 * -> [ A B . . . F G H ]    (placeholder marked)
		 *    [ A B C D E F G H ]    (actual result at this point)
		 */
	}
	DUK_ASSERT_TOP(ctx, nargs + 3);

	/* Step 15: insert itemCount elements into the hole made above */

	for (i = 0; i < item_count; i++) {
		duk_dup(ctx, i + 2);  /* args start at index 2 */
		duk_put_prop_index(ctx, -4, rel_start + i);  /* FIXME: Throw */
	}

	/* Step 16: update length; note that the final length may be above 32 bit range */

	duk_push_number(ctx, ((double) len) - ((double) del_count) + ((double) item_count));
	duk_put_prop_stridx(ctx, -4, DUK_STRIDX_LENGTH);

	/* result array is already at the top of stack */
	DUK_ASSERT_TOP(ctx, nargs + 3);
	return 1;
}

/*
 *  reverse()
 */

int duk_builtin_array_prototype_reverse(duk_context *ctx) {
	unsigned int len;
	unsigned int middle;
	unsigned int lower, upper;
	int have_lower, have_upper;

	len = push_this_obj_len_u32(ctx);
	middle = len / 2;

	for (lower = 0; lower < middle; lower++) {
		DUK_ASSERT_TOP(ctx, 2);

		upper = len - lower - 1;

		have_lower = duk_get_prop_index(ctx, -2, lower);
		have_upper = duk_get_prop_index(ctx, -3, upper);

		/* [ ToObject(this) ToUint32(length) lowerValue upperValue ] */

		if (have_upper) {
			duk_put_prop_index(ctx, -4, lower);  /* FIXME: Throw */
		} else {
			duk_del_prop_index(ctx, -4, lower);
			duk_pop(ctx);
		}

		if (have_lower) {
			duk_put_prop_index(ctx, -3, upper);
		} else {
			duk_del_prop_index(ctx, -3, upper);
			duk_pop(ctx);
		}

		DUK_ASSERT_TOP(ctx, 2);
	}

	DUK_ASSERT_TOP(ctx, 2);
	duk_pop(ctx);  /* -> [ ToObject(this) ] */
	return 1;
}

/*
 *  slice()
 */

int duk_builtin_array_prototype_slice(duk_context *ctx) {
	unsigned int len;
	int start, end;
	int idx;
	int i;

	len = push_this_obj_len_u32(ctx);
	duk_push_array(ctx);

	/* stack[0] = start
	 * stack[1] = end
	 * stack[2] = ToObject(this)
	 * stack[3] = ToUint32(length)
	 * stack[4] = result array
	 */

	start = duk_to_int_clamped(ctx, 0, -len, len);  /* FIXME: does not support full 32-bit range */
	if (start < 0) {
		start = len + start;
	}
	/* FIXME: could duk_is_undefined() provide defaulting undefined to 'len'
	 * (the upper limit)?
	 */
	if (duk_is_undefined(ctx, 1)) {
		end = len;
	} else {
		end = duk_to_int_clamped(ctx, 1, -len, len);
		if (end < 0) {
			end = len + end;
		}
	}
	DUK_ASSERT(start >= 0 && start <= len);
	DUK_ASSERT(end >= 0 && end <= len);

	idx = 0;
	for (i = start; i < end; i++) {
		DUK_ASSERT_TOP(ctx, 5);
		if (duk_get_prop_index(ctx, 2, i)) {
			duk_put_prop_index(ctx, 4, idx);
		} else {
			duk_pop(ctx);
		}
		idx++;
		DUK_ASSERT_TOP(ctx, 5);
	}

	DUK_ASSERT_TOP(ctx, 5);
	return 1;
}

/*
 *  shift()
 */

int duk_builtin_array_prototype_shift(duk_context *ctx) {
	unsigned int len;
	unsigned int i;

	len = push_this_obj_len_u32(ctx);
	if (len == 0) {
		duk_push_int(ctx, 0);
		duk_put_prop_stridx(ctx, 0, DUK_STRIDX_LENGTH);  /* FIXME: Throw */
		return 0;
	}

	duk_get_prop_index(ctx, 0, 0);

	/* stack[0] = object (this)
	 * stack[1] = ToUint32(length)
	 * stack[2] = elem at index 0 (retval)
	 */

	for (i = 1; i < len; i++) {
		DUK_ASSERT_TOP(ctx, 3);
		if (duk_get_prop_index(ctx, 0, i)) {
			/* fromPresent = true */
			duk_put_prop_index(ctx, 0, i - 1);  /* FIXME: Throw */
		} else {
			/* fromPresent = false */
			duk_del_prop_index(ctx, 0, i - 1);
			duk_pop(ctx);
		}
	}
	duk_del_prop_index(ctx, 0, len - 1);  /* FIXME: Throw */

	duk_push_number(ctx, (double) (len - 1));  /* FIXME: push uint */
	duk_put_prop_stridx(ctx, 0, DUK_STRIDX_LENGTH);

	DUK_ASSERT_TOP(ctx, 3);
	return 1;
}

/*
 *  unshift()
 */

int duk_builtin_array_prototype_unshift(duk_context *ctx) {
	int nargs;
	unsigned int len;
	unsigned int i;
	double final_len;

	nargs = duk_get_top(ctx);
	len = push_this_obj_len_u32(ctx);

	/* stack[0...nargs-1] = unshift args (vararg)
	 * stack[nargs] = ToObject(this)
	 * stack[nargs+1] = ToUint32(length)
	 */

	DUK_ASSERT_TOP(ctx, nargs + 2);

	/* Note: unshift() may operate on indices above unsigned 32-bit range
	 * and the final length may be >= 2**32.  Hence we use 'double' vars
	 * here, when appropriate.
	 */

	i = len;
	while (i > 0) {
		DUK_ASSERT_TOP(ctx, nargs + 2);
		i--;
		duk_push_number(ctx, ((double) i) + ((double) nargs));  /* k+argCount-1; note that may be above 32-bit range */
		if (duk_get_prop_index(ctx, -3, i)) {
			/* fromPresent = true */
			/* [ ... ToObject(this) ToUint32(length) to val ] */
			duk_put_prop(ctx, -4);  /* -> [ ... ToObject(this) ToUint32(length) ] */  /* FIXME: Throw */
		} else {
			/* fromPresent = false */
			/* [ ... ToObject(this) ToUint32(length) to val ] */
			duk_pop(ctx);
			duk_del_prop(ctx, -3);  /* -> [ ... ToObject(this) ToUint32(length) ] */  /* FIXME: Throw */
		}
		DUK_ASSERT_TOP(ctx, nargs + 2);
	}

	for (i = 0; i < nargs; i++) {
		DUK_ASSERT_TOP(ctx, nargs + 2);
		duk_dup(ctx, i);  /* -> [ ... ToObject(this) ToUint32(length) arg[i] ] */
		duk_put_prop_index(ctx, -3, i);  /* FIXME: Throw */
		DUK_ASSERT_TOP(ctx, nargs + 2);
	}

	DUK_ASSERT_TOP(ctx, nargs + 2);
	final_len = ((double) len) + ((double) nargs);
	duk_push_number(ctx, final_len);
	duk_dup_top(ctx);  /* -> [ ... ToObject(this) ToUint32(length) final_len final_len ] */
	duk_put_prop_stridx(ctx, -4, DUK_STRIDX_LENGTH);  /* FIXME: Throw */
	return 1;
}

/*
 *  indexOf(), lastIndexOf()
 */

static int array_indexof_helper(duk_context *ctx, int idx_step) {
	/* FIXME: types, ensure loop below works when fixed (i must be able to go negative right now) */
	int nargs;
	int i, len;
	int fromIndex;

	/* lastIndexOf() needs to be a vararg function because we must distinguish
	 * between an undefined fromIndex and a "not given" fromIndex; indexOf() is
	 * made vararg for symmetry although it doesn't strictly need to be.
	 */

	nargs = duk_get_top(ctx);
	duk_set_top(ctx, 2);

	len = push_this_obj_len_u32(ctx);
	if (len == 0) {
		goto not_found;
	}

	/* Index clamping is a bit tricky, we must ensure that we'll only iterate
	 * through elements that exist and that the specific requirements from E5.1
	 * Sections 15.4.4.14 and 15.4.4.15 are fulfilled; especially:
	 *
	 *   - indexOf: clamp to [-len,len], negative handling -> [0,len],
	 *     if clamped result is len, for-loop bails out immediately
	 *
	 *   - lastIndexOf: clamp to [-len-1, len-1], negative handling -> [-1, len-1],
	 *     if clamped result is -1, for-loop bails out immediately
	 *
	 * If fromIndex is not given, ToInteger(undefined) = 0, which is correct
	 * for indexOf() but incorrect for lastIndexOf().  Hence special handling,
	 * and why lastIndexOf() needs to be a vararg function.
	 */

	if (nargs >= 2) {
		fromIndex = duk_to_int_clamped(ctx,
		                               1,
		                               (idx_step > 0 ? -len : -len - 1),
		                               (idx_step > 0 ? len : len - 1));
		if (fromIndex < 0) {
			fromIndex = len + fromIndex;
		}
	} else {
		/* for indexOf, ToInteger(undefined) would be 0, i.e. correct, but
		 * handle both indexOf and lastIndexOf specially here.
		 */
		if (idx_step > 0) {
			fromIndex = 0;
		} else {
			fromIndex = len - 1;
		}
	}

	/* stack[0] = searchElement
	 * stack[1] = fromIndex
	 * stack[2] = object
	 * stack[3] = length (not needed, but not popped above)
	 */

	for (i = fromIndex;
	     i >= 0 && i < len;
	     i += idx_step) {
		DUK_ASSERT_TOP(ctx, 4);

		if (duk_get_prop_index(ctx, 2, i)) {
			DUK_ASSERT_TOP(ctx, 5);
			if (duk_strict_equals(ctx, 0, 4)) {
				duk_push_int(ctx, i);
				return 1;
			}
		}

		duk_pop(ctx);
	}

 not_found:
	duk_push_int(ctx, -1);
	return 1;
}

int duk_builtin_array_prototype_index_of(duk_context *ctx) {
	return array_indexof_helper(ctx, 1 /*idx_step*/);
}

int duk_builtin_array_prototype_last_index_of(duk_context *ctx) {
	return array_indexof_helper(ctx, -1 /*idx_step*/);
}

/*
 *  every(), some(), forEach(), map(), filter()
 */

#define  ITER_EVERY    0
#define  ITER_SOME     1
#define  ITER_FOREACH  2
#define  ITER_MAP      3
#define  ITER_FILTER   4

/* FIXME: This helper is a bit awkward because the handling for the different iteration
 * callers is quite different.  This now compiles to a bit less than 500 bytes, so with
 * 5 callers the net result is about 100 bytes / caller.
 */

static int iter_helper(duk_context *ctx, int iter_type) {
	int len;
	int i;
	int k;
	int bval;

	/* each call this helper serves has nargs==2 */
	DUK_ASSERT_TOP(ctx, 2);

	len = push_this_obj_len_u32(ctx);
	if (!duk_is_callable(ctx, 0)) {
		goto type_error;
	}
	/* if thisArg not supplied, behave as if undefined was supplied */

	if (iter_type == ITER_MAP || iter_type == ITER_FILTER) {
		duk_push_array(ctx);
	} else {
		duk_push_undefined(ctx);
	}

	/* stack[0] = callback
	 * stack[1] = thisArg
	 * stack[2] = object
	 * stack[3] = ToUint32(length)  (unused, but avoid unnecessary pop)
	 * stack[4] = result array (or undefined)
	 */

	k = 0;  /* result index for filter() */
	for (i = 0; i < len; i++) {
		DUK_ASSERT_TOP(ctx, 5);

		if (!duk_get_prop_index(ctx, 2, i)) {
			duk_pop(ctx);
			continue;
		}

		/* The original value needs to be preserved for filter(), hence
		 * this funny order.  We can't re-get the value because of side
		 * effects.
		 */

		duk_dup(ctx, 0);
		duk_dup(ctx, 1);
		duk_dup(ctx, -3);
		duk_push_int(ctx, i);
		duk_dup(ctx, 2);  /* [ ... val callback thisArg val i obj ] */
		duk_call_method(ctx, 3); /* -> [ ... val retval ] */

		switch (iter_type) {
		case ITER_EVERY:
			bval = duk_to_boolean(ctx, -1);
			if (!bval) {
				return 1;
			}
			break;
		case ITER_SOME:
			bval = duk_to_boolean(ctx, -1);
			if (bval) {
				return 1;
			}
			break;
		case ITER_FOREACH:
			/* nop */
			break;
		case ITER_MAP:
			duk_dup(ctx, -1);
			duk_put_prop_index(ctx, 4, i);  /* retval to result[i] */
			break;
		case ITER_FILTER:
			bval = duk_to_boolean(ctx, -1);
			if (bval) {
				duk_dup(ctx, -2);  /* orig value */
				duk_put_prop_index(ctx, 4, k);
				k++;
			}
			break;
		default:
			DUK_NEVER_HERE();
			break;
		}
		duk_pop_2(ctx);

		DUK_ASSERT_TOP(ctx, 5);
	}

	switch (iter_type) {
	case ITER_EVERY:
		duk_push_true(ctx);
		break;
	case ITER_SOME:
		duk_push_false(ctx);
		break;
	case ITER_FOREACH:
		duk_push_undefined(ctx);
		break;
	case ITER_MAP:
	case ITER_FILTER:
		DUK_ASSERT_TOP(ctx, 5);
		DUK_ASSERT(duk_is_array(ctx, -1));  /* topmost element is the result array already */
		break;
	default:
		DUK_NEVER_HERE();
		break;
	}

	return 1;

 type_error:
	return DUK_RET_TYPE_ERROR;
}

int duk_builtin_array_prototype_every(duk_context *ctx) {
	return iter_helper(ctx, ITER_EVERY);
}

int duk_builtin_array_prototype_some(duk_context *ctx) {
	return iter_helper(ctx, ITER_SOME);
}

int duk_builtin_array_prototype_for_each(duk_context *ctx) {
	return iter_helper(ctx, ITER_FOREACH);
}

int duk_builtin_array_prototype_map(duk_context *ctx) {
	return iter_helper(ctx, ITER_MAP);
}

int duk_builtin_array_prototype_filter(duk_context *ctx) {
	return iter_helper(ctx, ITER_FILTER);
}

/*
 *  reduce(), reduceRight()
 */

static int reduce_helper(duk_context *ctx, int idx_step) {
	int nargs;
	int have_acc;
	int i, len;

	/* idx_step is +1 for reduce, -1 for reduceRight */

	/* We're a varargs function because we need to detect whether
	 * initialValue was given or not.
	 */
	nargs = duk_get_top(ctx);
	DUK_DPRINT("nargs=%d", nargs);

	duk_set_top(ctx, 2);
	len = push_this_obj_len_u32(ctx);
	if (!duk_is_callable(ctx, 0)) {
		goto type_error;
	}

	/* stack[0] = callback fn
	 * stack[1] = initialValue
	 * stack[2] = object (coerced this)
	 * stack[3] = length (not needed, but not popped above)
	 * stack[4] = accumulator
	 */

	have_acc = 0;
	if (nargs >= 2) {
		duk_dup(ctx, 1);
		have_acc = 1;
	}
	DUK_DPRINT("have_acc=%d, acc=%!T", have_acc, duk_get_tval(ctx, 3));

	for (i = (idx_step >= 0 ? 0 : len - 1);
	     i >= 0 && i < len;
	     i += idx_step) {
		DUK_DPRINT("i=%d, len=%d, have_acc=%d, top=%d, acc=%!T",
		           i, len, have_acc, duk_get_top(ctx), duk_get_tval(ctx, 4));

		DUK_ASSERT((have_acc && duk_get_top(ctx) == 5) ||
		           (!have_acc && duk_get_top(ctx) == 4));

		if (!duk_has_prop_index(ctx, 2, i)) {
			continue;
		}

		if (!have_acc) {
			DUK_ASSERT_TOP(ctx, 4);
			duk_get_prop_index(ctx, 2, i);
			have_acc = 1;
			DUK_ASSERT_TOP(ctx, 5);
		} else {
			DUK_ASSERT_TOP(ctx, 5);
			duk_dup(ctx, 0);
			duk_dup(ctx, 4);
			duk_get_prop_index(ctx, 2, i);
			duk_push_int(ctx, i);  /* FIXME: type */
			duk_dup(ctx, 2);
			DUK_DPRINT("calling reduce function: func=%!T, prev=%!T, curr=%!T, idx=%!T, obj=%!T",
			           duk_get_tval(ctx, -5), duk_get_tval(ctx, -4), duk_get_tval(ctx, -3),
			           duk_get_tval(ctx, -2), duk_get_tval(ctx, -1));
			duk_call(ctx, 4);
			DUK_DPRINT("-> result: %!T", duk_get_tval(ctx, -1));
			duk_replace(ctx, 4);
			DUK_ASSERT_TOP(ctx, 5);
		}
	}

	if (!have_acc) {
		goto type_error;
	}

	DUK_ASSERT_TOP(ctx, 5);
	return 1;

 type_error:
	return DUK_RET_TYPE_ERROR;
}

int duk_builtin_array_prototype_reduce(duk_context *ctx) {
	return reduce_helper(ctx, 1 /*idx_step*/);
}

int duk_builtin_array_prototype_reduce_right(duk_context *ctx) {
	return reduce_helper(ctx, -1 /*idx_step*/);
}

#line 1 "duk_builtin_boolean.c"
/*
 *  Boolean built-ins
 */

/* include removed: duk_internal.h */

/* Helper to check 'this', get the primitive value to stack top, and
 * optionally coerce with ToString().
 */
static int tostring_valueof_helper(duk_context *ctx, int coerce_tostring) {
	duk_tval *tv;
	duk_hobject *h;

	/* FIXME: there is room to use a shared helper here, many built-ins
	 * check the 'this' type, and if it's an object, check its class,
	 * then get its internal value, etc.
	 */

	duk_push_this(ctx);
	tv = duk_get_tval(ctx, -1);
	DUK_ASSERT(tv != NULL);

	if (DUK_TVAL_IS_BOOLEAN(tv)) {
		goto type_ok;
	} else if (DUK_TVAL_IS_OBJECT(tv)) {
		h = DUK_TVAL_GET_OBJECT(tv);
		DUK_ASSERT(h != NULL);

		if (DUK_HOBJECT_GET_CLASS_NUMBER(h) == DUK_HOBJECT_CLASS_BOOLEAN) {
			duk_get_prop_stridx(ctx, -1, DUK_STRIDX_INT_VALUE);
			DUK_ASSERT(duk_is_boolean(ctx, -1));
			goto type_ok;
		}
	}

	return DUK_RET_TYPE_ERROR;

 type_ok:
	if (coerce_tostring) {
		duk_to_string(ctx, -1);
	}
	return 1;
}

int duk_builtin_boolean_constructor(duk_context *ctx) {
	duk_hobject *this;

	duk_to_boolean(ctx, 0);

	if (duk_is_constructor_call(ctx)) {
		/* FIXME: helper; rely on Boolean.prototype as being non-writable, non-configurable */
		duk_push_this(ctx);
		this = duk_get_hobject(ctx, -1);
		DUK_ASSERT(this != NULL);
		DUK_ASSERT(this->prototype == ((duk_hthread *) ctx)->builtins[DUK_BIDX_BOOLEAN_PROTOTYPE]);

		DUK_HOBJECT_SET_CLASS_NUMBER(this, DUK_HOBJECT_CLASS_BOOLEAN);

		duk_dup(ctx, 0);  /* -> [ val obj val ] */
		duk_def_prop_stridx(ctx, -2, DUK_STRIDX_INT_VALUE, DUK_PROPDESC_FLAGS_NONE);  /* FIXME: proper flags? */
	}  /* unbalanced stack */

	return 1;
}

int duk_builtin_boolean_prototype_to_string(duk_context *ctx) {
	return tostring_valueof_helper(ctx, 1 /*coerce_tostring*/);
}

int duk_builtin_boolean_prototype_value_of(duk_context *ctx) {
	return tostring_valueof_helper(ctx, 0 /*coerce_tostring*/);
}

#line 1 "duk_builtin_date.c"
/*
 *  Date built-ins
 *
 *  Unlike most built-ins, Date has a lot of platform dependencies for
 *  getting UTC time, converting between UTC and local time, and parsing
 *  and formatting time values.
 *
 *  See doc/datetime.txt.
 */

/* include removed: duk_internal.h */

/*
 *  Platform specific includes and defines
 *
 *  Note that necessary system headers (like <sys/time.h>) are included
 *  by duk_internal.h (or duk_features.h, which is included by duk_internal.h)
 *  because the header locations vary between systems and we don't want
 *  that clutter here.
 */

#if defined(DUK_USE_DATE_NOW_GETTIMEOFDAY)
#define  GET_NOW_TIMEVAL      get_now_timeval_gettimeofday
#else
#error no function to get current time
#endif

#if defined(DUK_USE_DATE_TZO_GMTIME)
#define  GET_LOCAL_TZOFFSET   get_local_tzoffset_gmtime
#else
#error no function to get local tzoffset
#endif

/* Buffer sizes for some UNIX calls.  Larger than strictly necessary
 * to avoid Valgrind errors.
 */
#define  STRPTIME_BUF_SIZE  64
#define  STRFTIME_BUF_SIZE  64

/*
 *  Other file level defines
 */

/* Forward declarations. */
static double push_this_and_get_timeval_tzoffset(duk_context *ctx, int flags, int *out_tzoffset);
static double push_this_and_get_timeval(duk_context *ctx, int flags);
static void timeval_to_parts(double d, int *parts, double *dparts, int flags);
static double get_timeval_from_dparts(double *dparts, int flags);
static void twodigit_year_fixup(duk_context *ctx, int idx_val);

/* Millisecond count constants. */
#define  MS_SECOND          1000
#define  MS_MINUTE          (60 * 1000)
#define  MS_HOUR            (60 * 60 * 1000)
#define  MS_DAY             (24 * 60 * 60 * 1000)

/* Part indices for internal breakdowns.  Part order from IDX_YEAR to
 * IDX_MILLISECOND matches argument ordering of Ecmascript API calls
 * (like Date constructor call).  A few functions in this file depend
 * on the specific ordering, so change with care.
 */
#define  IDX_YEAR           0  /* year */
#define  IDX_MONTH          1  /* month: 0 to 11 */
#define  IDX_DAY            2  /* day within month: 0 to 30 */
#define  IDX_HOUR           3
#define  IDX_MINUTE         4
#define  IDX_SECOND         5
#define  IDX_MILLISECOND    6
#define  IDX_WEEKDAY        7  /* weekday: 0 to 6, 0=sunday, 1=monday, etc */
#define  NUM_PARTS          8

/* Internal API call flags, used for various functions in this file.
 * Certain flags are used by only certain functions, but since the flags
 * don't overlap, a single flags value can be passed around to multiple
 * functions.
 *
 * The unused top bits of the flags field are also used to pass values
 * to helpers (get_part_helper() and set_part_helper()).
 */
#define  FLAG_NAN_TO_ZERO          (1 << 0)  /* timeval breakdown: internal time value NaN -> zero */
#define  FLAG_NAN_TO_RANGE_ERROR   (1 << 1)  /* timeval breakdown: internal time value NaN -> RangeError (toISOString) */
#define  FLAG_ONEBASED             (1 << 2)  /* timeval breakdown: convert month and day-of-month parts to one-based (default is zero-based) */
#define  FLAG_LOCALTIME            (1 << 3)  /* convert time value to local time */
#define  FLAG_SUB1900              (1 << 4)  /* getter: subtract 1900 from year when getting year part */
#define  FLAG_TOSTRING_DATE        (1 << 5)  /* include date part in string conversion result */
#define  FLAG_TOSTRING_TIME        (1 << 6)  /* include time part in string conversion result */
#define  FLAG_TOSTRING_LOCALE      (1 << 7)  /* use locale specific formatting if available */
#define  FLAG_TIMESETTER           (1 << 8)  /* setter: call is a time setter (affects hour, min, sec, ms); otherwise date setter (affects year, month, day-in-month) */
#define  FLAG_YEAR_FIXUP           (1 << 9)  /* setter: perform 2-digit year fixup (00...99 -> 1900...1999) */

/*
 *  Platform specific helpers
 */

#ifdef DUK_USE_DATE_NOW_GETTIMEOFDAY
/* Get current Ecmascript time (= UNIX/Posix time, but in milliseconds). */
static double get_now_timeval_gettimeofday(duk_context *ctx) {
	duk_hthread *thr = (duk_hthread *) ctx;
	struct timeval tv;
	double d;

	if (gettimeofday(&tv, NULL) != 0) {
		DUK_ERROR(thr, DUK_ERR_INTERNAL_ERROR, "gettimeofday failed");
	}

	d = ((double) tv.tv_sec) * 1000.0 +
	    ((double) (tv.tv_usec / 1000));
	DUK_ASSERT(floor(d) == d);  /* no fractions */

	return d;
}
#endif  /* DUK_USE_DATE_NOW_GETTIMEOFDAY */

#ifdef DUK_USE_DATE_TZO_GMTIME
/* Get local time offset (in seconds) for a certain (UTC) instant 'd'. */
static int get_local_tzoffset_gmtime(double d) {
	time_t t, t1, t2;
	int parts[NUM_PARTS];
	double dparts[NUM_PARTS];
	struct tm tms[2];

	/* For NaN/inf, the return value doesn't matter. */
	if (!isfinite(d)) {
		return 0;
	}

	/*
	 *  This is a bit tricky to implement portably.  The result depends
	 *  on the timestamp (specifically, DST depends on the timestamp).
	 *  If e.g. UNIX APIs are used, they'll have portability issues with
	 *  very small and very large years.
	 *
	 *  Current approach:
	 *
	 *  - Clamp year to stay within portable UNIX limits.  Avoid 2038 as
	 *    some conversions start to fail.  Avoid 1970, as some conversions
	 *    in January 1970 start to fail (verified in practice).
	 *
	 *  - Create a UTC time breakdown from 't', and then pretend it is a
	 *    local time breakdown and build a UTC time from it.  The timestamp
	 *    will effectively shift backwards by time the time offset (e.g. -2h
	 *    or -3h for EET/EEST).  Convert with mktime() twice to get the DST
	 *    flag for the final conversion.
	 *
	 *  FIXME: this is probably not entirely correct nor clear, but is
	 *  good enough for now.
	 */

	timeval_to_parts(d, parts, dparts, 0 /*flags*/);

	/*
	 *  FIXME: must choose 'equivalent year', E5 Section 15.9.1.8, instead
	 *  of just clamping.
	 */
	if (parts[IDX_YEAR] < 1971) {
		dparts[IDX_YEAR] = 1971.0;
	} else if (parts[IDX_YEAR] > 2037) {
		dparts[IDX_YEAR] = 2037.0;
	}

	d = get_timeval_from_dparts(dparts, 0 /*flags*/);
	DUK_ASSERT(d >= 0 && d < 2147483648.0 * 1000.0);  /* unsigned 31-bit range */
	t = (size_t) (d / 1000.0);
	DUK_DDDPRINT("timeval: %lf -> time_t %d", d, (int) t);

	t1 = t;

	memset((void *) tms, 0, sizeof(struct tm) * 2);

	(void) gmtime_r(&t, &tms[0]);
	memcpy((void *) &tms[1], &tms[0], sizeof(struct tm));
	DUK_DDDPRINT("before mktime: tm={sec:%d,min:%d,hour:%d,mday:%d,mon:%d,year:%d,"
	             "wday:%d,yday:%d,isdst:%d}",
	             (int) tms[0].tm_sec, (int) tms[0].tm_min, (int) tms[0].tm_hour,
	             (int) tms[0].tm_mday, (int) tms[0].tm_mon, (int) tms[0].tm_year,
	             (int) tms[0].tm_wday, (int) tms[0].tm_yday, (int) tms[0].tm_isdst);

	(void) mktime(&tms[0]);
	tms[1].tm_isdst = tms[0].tm_isdst;
	t2 = mktime(&tms[1]);
	DUK_ASSERT(t2 >= 0);
	if (t2 < 0) {
		goto error;
	}

	DUK_DDDPRINT("after mktime: tm={sec:%d,min:%d,hour:%d,mday:%d,mon:%d,year:%d,"
	             "wday:%d,yday:%d,isdst:%d}",
	             (int) tms[1].tm_sec, (int) tms[1].tm_min, (int) tms[1].tm_hour,
	             (int) tms[1].tm_mday, (int) tms[1].tm_mon, (int) tms[1].tm_year,
	             (int) tms[1].tm_wday, (int) tms[1].tm_yday, (int) tms[1].tm_isdst);
	DUK_DDDPRINT("t2=%d", (int) t2);

	/* Positive if local time ahead of UTC. */

	/* difftime() returns a double, so coercion to int generates quite
	 * a lot of code.  Direct subtraction is not portable, however.
	 *
	 * FIXME: allow direct subtraction on known platforms.
	 */
#if 0
	return t1 - t2;
#endif
	return (int) difftime(t1, t2);

 error:
	/* FIXME: return something more useful, so that caller can throw? */
	DUK_DPRINT("mktime() failed, d=%lf", d);
	return 0;
}
#endif  /* DUK_USE_DATE_TZO_GMTIME */

#ifdef DUK_USE_DATE_PRS_STRPTIME
static int parse_string_strptime(duk_context *ctx, const char *str) {
	struct tm tm;
	time_t t;
	char buf[STRPTIME_BUF_SIZE];

	/* copy to buffer with spare to avoid Valgrind gripes from strptime */
	memset(buf, 0, sizeof(buf));
	snprintf(buf, sizeof(buf) - 1, "%s", str);

	DUK_DDDPRINT("parsing: '%s'", buf);

	memset(&tm, 0, sizeof(tm));
	if (strptime((const char *) buf, "%c", &tm) != NULL) {
		DUK_DDDPRINT("before mktime: tm={sec:%d,min:%d,hour:%d,mday:%d,mon:%d,year:%d,"
		             "wday:%d,yday:%d,isdst:%d}",
		             (int) tm.tm_sec, (int) tm.tm_min, (int) tm.tm_hour,
		             (int) tm.tm_mday, (int) tm.tm_mon, (int) tm.tm_year,
		             (int) tm.tm_wday, (int) tm.tm_yday, (int) tm.tm_isdst);
		tm.tm_isdst = -1;  /* negative: dst info not available */

		t = mktime(&tm);
		DUK_DDDPRINT("mktime() -> %d", (int) t);
		if (t >= 0) {
			duk_push_number(ctx, ((double) t) * 1000.0);
			return 1;
		}
	}

	return 0;
}
#endif  /* DUK_USE_DATE_PRS_STRPTIME */

#ifdef DUK_USE_DATE_PRS_GETDATE
static int parse_string_getdate(duk_context *ctx, const char *str) {
	struct tm tm;
	int rc;
	time_t t;

	/* For this to work, DATEMSK must be set, to this is not very
	 * convenient for an embeddable interpreter.
	 */

	memset(&tm, 0, sizeof(struct tm));
	rc = getdate_r(str, &tm);
	DUK_DDDPRINT("getdate_r() -> %d", rc);

	if (rc == 0) {
		t = mktime(&tm);
		DUK_DDDPRINT("mktime() -> %d", (int) t);
		if (t >= 0) {
			duk_push_number(ctx, (double) t);
			return 1;
		}
	}

	duk_push_nan(ctx);
	return 1;
}
#endif  /* DUK_USE_DATE_PRS_GETDATE */

#ifdef DUK_USE_DATE_FMT_STRFTIME
static int format_parts_strftime(duk_context *ctx, int *parts, int tzoffset, int flags) {
	char buf[STRFTIME_BUF_SIZE];
	struct tm tm;
	const char *fmt;

	/* If platform doesn't support the entire Ecmascript range, we need to
	 * return 0 so that the caller can fall back to the default formatter.
	 *
	 * FIXME: how to detect this more correctly?  add a feature define?
	 * The size of time_t is probably not an accurate guarantee of strftime
	 * supporting or not supporting a large time range.
	 */
	if (sizeof(time_t) < 8 &&
	   (parts[IDX_YEAR] < 1970 || parts[IDX_YEAR] > 2037)) {
		/* be paranoid for 32-bit time values (even avoiding negative ones) */
		return 0;
	}

	memset(&tm, 0, sizeof(tm));
	tm.tm_sec = parts[IDX_SECOND];
	tm.tm_min = parts[IDX_MINUTE];
	tm.tm_hour = parts[IDX_HOUR];
	tm.tm_mday = parts[IDX_DAY];       /* already one-based */
	tm.tm_mon = parts[IDX_MONTH] - 1;  /* one-based -> zero-based */
	tm.tm_year = parts[IDX_YEAR] - 1900;
	tm.tm_wday = parts[IDX_WEEKDAY];
	tm.tm_isdst = 0;

	memset(buf, 0, sizeof(buf));
	if ((flags & FLAG_TOSTRING_DATE) && (flags & FLAG_TOSTRING_TIME)) {
		fmt = "%c";
	} else if (flags & FLAG_TOSTRING_DATE) {
		fmt = "%x";
	} else {
		DUK_ASSERT(flags & FLAG_TOSTRING_TIME);
		fmt = "%X";
	}
	(void) strftime(buf, sizeof(buf) - 1, fmt, &tm);
	DUK_ASSERT(buf[sizeof(buf) - 1] == 0);

	duk_push_string(ctx, buf);
	return 1;
}
#endif  /* DUK_USE_DATE_FMT_STRFTIME */

/*
 *  ISO 8601 subset parser.
 */

/* Parser part count. */
#define  NUM_ISO8601_PARSER_PARTS  9

/* Parser part indices. */
#define  PI_YEAR         0
#define  PI_MONTH        1
#define  PI_DAY          2
#define  PI_HOUR         3
#define  PI_MINUTE       4
#define  PI_SECOND       5
#define  PI_MILLISECOND  6
#define  PI_TZHOUR       7
#define  PI_TZMINUTE     8

/* Parser part masks. */
#define  PM_YEAR         (1 << PI_YEAR)
#define  PM_MONTH        (1 << PI_MONTH)
#define  PM_DAY          (1 << PI_DAY)
#define  PM_HOUR         (1 << PI_HOUR)
#define  PM_MINUTE       (1 << PI_MINUTE)
#define  PM_SECOND       (1 << PI_SECOND)
#define  PM_MILLISECOND  (1 << PI_MILLISECOND)
#define  PM_TZHOUR       (1 << PI_TZHOUR)
#define  PM_TZMINUTE     (1 << PI_TZMINUTE)

/* Parser separator indices. */
#define  SI_PLUS         0
#define  SI_MINUS        1
#define  SI_T            2
#define  SI_SPACE        3
#define  SI_COLON        4
#define  SI_PERIOD       5
#define  SI_Z            6
#define  SI_NUL          7

/* Parser separator masks. */
#define  SM_PLUS         (1 << SI_PLUS)
#define  SM_MINUS        (1 << SI_MINUS)
#define  SM_T            (1 << SI_T)
#define  SM_SPACE        (1 << SI_SPACE)
#define  SM_COLON        (1 << SI_COLON)
#define  SM_PERIOD       (1 << SI_PERIOD)
#define  SM_Z            (1 << SI_Z)
#define  SM_NUL          (1 << SI_NUL)

/* Rule control flags. */
#define  CF_NEG          (1 << 0)  /* continue matching, set neg_tzoffset flag */
#define  CF_ACCEPT       (1 << 1)  /* accept string */
#define  CF_ACCEPT_NUL   (1 << 2)  /* accept string if next char is NUL (otherwise reject) */

#define  PACK_RULE(partmask,sepmask,nextpart,flags)  \
	((partmask) + ((sepmask) << 9) + ((nextpart) << 17) + ((flags) << 21))

#define  UNPACK_RULE(rule,var_nextidx,var_flags)  do { \
		(var_nextidx) = ((rule) >> 17) & 0x0f; \
		(var_flags) = (rule) >> 21; \
	} while (0)

#define  RULE_MASK_PART_SEP  0x1ffff

/* Matching separator index is used in the control table */
static const char parse_iso8601_seps[] = {
	'+' /*0*/, '-' /*1*/, 'T' /*2*/, ' ' /*3*/,
	':' /*4*/, '.' /*5*/, 'Z' /*6*/, (char) 0 /*7*/
};

/* Rule table: first matching rule is used to determine what to do next. */
static const int parse_iso8601_control[] = {
	PACK_RULE(PM_YEAR, SM_MINUS, PI_MONTH, 0),
	PACK_RULE(PM_MONTH, SM_MINUS, PI_DAY, 0),
	PACK_RULE(PM_YEAR | PM_MONTH | PM_DAY, SM_T | SM_SPACE, PI_HOUR, 0),
	PACK_RULE(PM_HOUR, SM_COLON, PI_MINUTE, 0),
	PACK_RULE(PM_MINUTE, SM_COLON, PI_SECOND, 0),
	PACK_RULE(PM_SECOND, SM_PERIOD, PI_MILLISECOND, 0),
	PACK_RULE(PM_TZHOUR, SM_COLON, PI_TZMINUTE, 0),
	PACK_RULE(PM_YEAR | PM_MONTH | PM_DAY | PM_HOUR /*Note1*/ | PM_MINUTE | PM_SECOND | PM_MILLISECOND, SM_PLUS, PI_TZHOUR, 0),
	PACK_RULE(PM_YEAR | PM_MONTH | PM_DAY | PM_HOUR /*Note1*/ | PM_MINUTE | PM_SECOND | PM_MILLISECOND, SM_MINUS, PI_TZHOUR, CF_NEG),
	PACK_RULE(PM_YEAR | PM_MONTH | PM_DAY | PM_HOUR /*Note1*/ | PM_MINUTE | PM_SECOND | PM_MILLISECOND, SM_Z, 0, CF_ACCEPT_NUL),
	PACK_RULE(PM_YEAR | PM_MONTH | PM_DAY | PM_HOUR /*Note1*/ | PM_MINUTE | PM_SECOND | PM_MILLISECOND | PM_TZHOUR /*Note2*/ | PM_TZMINUTE, SM_NUL, 0, CF_ACCEPT)

	/* Note1: the specification doesn't require matching a time form with
	 *        just hours ("HH"), but we accept it here, e.g. "2012-01-02T12Z".
	 *
	 * Note2: the specification doesn't require matching a timezone offset
	 *        with just hours ("HH"), but accept it here, e.g. "2012-01-02T03:04:05+02"
	 */
};

static int parse_string_iso8601_subset(duk_context *ctx, const char *str) {
	int parts[NUM_ISO8601_PARSER_PARTS];
	double dparts[NUM_PARTS];
	double d;
	const char *p;
	int part_idx = 0;
	int accum = 0;
	int neg_year = 0;
	int neg_tzoffset = 0;
	int ndigits = 0;
	char ch;
	int i;

	/* During parsing, month and day are one-based; set defaults here. */
	memset(parts, 0, sizeof(parts));
	DUK_ASSERT(parts[IDX_YEAR] == 0);  /* don't care value, year is mandatory */
	parts[IDX_MONTH] = 1;
	parts[IDX_DAY] = 1;

	/* Special handling for year sign. */
	p = str;
	ch = p[0];
	if (ch == '+') {
		p++;
	} else if (ch == '-') {
		neg_year = 1;
		p++;
	}

	for (;;) {
		ch = *p++;
		DUK_DDDPRINT("parsing, part_idx=%d, char=%d ('%c')", part_idx, (int) ch,
		             (ch >= 0x20 && ch <= 0x7e) ? ch : '?');

		if (ch >= '0' && ch <= '9') {
			if (ndigits >= 9) {
				DUK_DDDPRINT("too many digits -> reject");
				goto reject;
			}
			if (part_idx == PI_MILLISECOND /*msec*/ && ndigits >= 3) {
				/* ignore millisecond fractions after 3 */
			} else {
				accum = accum * 10 + ((int) ch) - ((int) '0') + 0x00;
				ndigits++;
			}
		} else {
			int match_val;
			int sep_idx;

			if (ndigits <= 0) {
				goto reject;
			}
			if (part_idx == PI_MILLISECOND) {
				/* complete the millisecond field */
				while (ndigits < 3) {
					accum *= 10;
					ndigits++;
				}
			}
			parts[part_idx] = accum;
			DUK_DDDPRINT("wrote part %d -> value %d", part_idx, accum);

			accum = 0;
			ndigits = 0;

			for (i = 0; i < sizeof(parse_iso8601_seps); i++) {
				if (parse_iso8601_seps[i] == ch) {
					break;
				}
			}
			if (i == sizeof(parse_iso8601_seps)) {
				DUK_DDDPRINT("separator character doesn't match -> reject");
				goto reject;
			}

			sep_idx = i;
			match_val = (1 << part_idx) + (1 << (sep_idx + 9));  /* match against rule part/sep bits */

			for (i = 0; i < sizeof(parse_iso8601_control) / sizeof(int); i++) {
				int rule = parse_iso8601_control[i];
				int nextpart;
				int cflags;

				DUK_DDDPRINT("part_idx=%d, sep_idx=%d, match_val=0x%08x, considering rule=0x%08x",
				           part_idx, sep_idx, match_val, rule);

				if ((rule & match_val) != match_val) {
					continue;
				}

				UNPACK_RULE(rule, nextpart, cflags);

				DUK_DDDPRINT("rule match -> part_idx=%d, sep_idx=%d, match_val=0x%08x, rule=0x%08x -> nextpart=%d, cflags=0x%02x",
				             part_idx, sep_idx, match_val, rule, nextpart, cflags);

				if (cflags & CF_NEG) {
					neg_tzoffset = 1;
				}

				if (cflags & CF_ACCEPT) {
					goto accept;
				}

				if (cflags & CF_ACCEPT_NUL) {
					DUK_ASSERT(*(p-1) != (char) 0);
					if (*p == '\0') {
						goto accept;
					}
					goto reject;
				}

				part_idx = nextpart;
				break;
			}  /* rule match */

			if (i == sizeof(parse_iso8601_control) / sizeof(int)) {
				DUK_DDDPRINT("no rule matches -> reject");
				goto reject;
			}

			if (ch == (char) 0) {
				/* This shouldn't be necessary, but check just in case
				 * to avoid any chance of overruns.
				 */
				DUK_DDDPRINT("NUL after rule matching (should not happen) -> reject");
				goto reject;
			}
		}  /* if-digit-else-ctrl */
	}  /* char loop */

	/* We should never exit the loop above, but if we do, reject
	 * by falling through.
	 */
	DUK_DDDPRINT("fell out of char loop without explicit accept/reject -> reject");

 reject:
	DUK_DDDPRINT("reject");
	return 0;

 accept:
	DUK_DDDPRINT("accept");

	/* Apply timezone offset to get the main parts in UTC */
	if (neg_year) {
		parts[PI_YEAR] = -parts[PI_YEAR];
	}
	if (neg_tzoffset) {
		parts[PI_HOUR] += parts[PI_TZHOUR];
		parts[PI_MINUTE] += parts[PI_TZMINUTE];
	} else {
		parts[PI_HOUR] -= parts[PI_TZHOUR];
		parts[PI_MINUTE] -= parts[PI_TZMINUTE];
	}
	parts[PI_MONTH] -= 1;  /* zero-based month */
	parts[PI_DAY] -= 1;  /* zero-based day */

	/* Use double parts, they tolerate unnormalized time.
	 *
	 * Note: IDX_WEEKDAY is initialized with a bogus value (PI_TZHOUR)
	 * on purpose.  It won't be actually used by get_timeval_from_dparts(),
	 * but will make the value initialized just in case, and avoid any
	 * potential for Valgrind issues.
	 */
	for (i = 0; i < NUM_PARTS; i++) {
		DUK_DDDPRINT("part[%d] = %d", i, parts[i]);
		dparts[i] = parts[i];
	}

	d = get_timeval_from_dparts(dparts, 0 /*flags*/);
	duk_push_number(ctx, d);
	return 1;
}

/*
 *  Date/time parsing helper.
 *
 *  Parse a datetime string into a time value.  We must first try to parse
 *  the input according to the standard format in E5.1 Section 15.9.1.15.
 *  If that fails, we can try to parse using custom parsing, which can
 *  either be platform neutral (custom code) or platform specific (using
 *  existing platform API calls).
 *
 *  Note in particular that we must parse whatever toString(), toUTCString(),
 *  and toISOString() can produce; see E5.1 Section 15.9.4.2.
 */

/*
 *  FIXME: check standard behavior and also usual behavior in other
 *  implementations.  For instance, V8 parses '2012-01-01' as UTC and
 *  '2012/01/01' as local time.
 */

static int parse_string(duk_context *ctx, const char *str) {
	/* XXX: there is a small risk here: because the ISO 8601 parser is
	 * very loose, it may end up parsing some datetime values which
	 * would be better parsed with a platform specific parser.
	 */

	DUK_DDDPRINT("parse datetime from string '%s'", str);

	if (parse_string_iso8601_subset(ctx, str) > 0) {
		return 1;
	}

#if defined(DUK_USE_DATE_PRS_STRPTIME)
	if (parse_string_strptime(ctx, str) > 0) {
		return 1;
	}
#elif defined(DUK_USE_DATE_PRS_GETDATE)
	if (parse_string_getdate(ctx, str) > 0) {
		return 1;
	}
#else
	/* No platform-specific parsing, this is not an error. */
#endif

	duk_push_nan(ctx);
	return 1;
}

/*
 *  Calendar helpers
 *
 *  Some helpers are used for getters and can operate on normalized values
 *  which can be represented with 32-bit signed integers.  Other helpers are
 *  needed by setters and operate on un-normalized double values, must watch
 *  out for non-finite numbers etc.
 */

static unsigned char days_in_month[12] = {
	(unsigned char) 31, (unsigned char) 28, (unsigned char) 31, (unsigned char) 30,
	(unsigned char) 31, (unsigned char) 30, (unsigned char) 31, (unsigned char) 31,
	(unsigned char) 30, (unsigned char) 31, (unsigned char) 30, (unsigned char) 31
};

static int is_leap_year(int year) {
	if ((year % 4) != 0) {
		return 0;
	}
	if ((year % 100) != 0) {
		return 1;
	}
	if ((year % 400) != 0) {
		return 0;
	}
	return 1;
}

static double timeclip(double x) {
	if (!isfinite(x)) {
		return NAN;
	}

	if (x > 8.64e15 || x < -8.64e15) {
		return NAN;
	}

	x = duk_js_tointeger_number(x);

	/* Here we'd have the option to normalize -0 to +0. */
	return x;
}

/* Integer division which floors also negative values correctly. */
static int div_floor(int a, int b) {
	DUK_ASSERT(b > 0);
	if (a >= 0) {
		return a / b;
	} else {
		/* e.g. a = -4, b = 5  -->  -4 - 5 + 1 / 5  -->  -8 / 5  -->  -1
		 *      a = -5, b = 5  -->  -5 - 5 + 1 / 5  -->  -9 / 5  -->  -1
		 *      a = -6, b = 5  -->  -6 - 5 + 1 / 5  -->  -10 / 5  -->  -2
		 */
		return (a - b + 1) / b;
	}
}

/* Compute day number of the first day of a given year. */
static int day_from_year(int year) {
	/* Note: in integer arithmetic, (x / 4) is same as floor(x / 4) for non-negative
	 * values, but is incorrect for negative ones.
	 */
	return 365 * (year - 1970) + div_floor(year - 1969, 4) - div_floor(year - 1901, 100) + div_floor(year - 1601, 400);
}

/* Given a day number, determine year and day-within-year. */
static int year_from_day(int day, int *out_day_within_year) {
	int year;
	int diff_days;

	/* estimate year upwards (towards positive infinity), then back down;
	 * two iterations should be enough
	 */

	if (day >= 0) {
		year = 1970 + day / 365;
	} else {
		year = 1970 + day / 366;
	}

	for (;;) {
		diff_days = day_from_year(year) - day;
		DUK_DDDPRINT("year=%d day=%d, diff_days=%d", year, day, diff_days);
		if (diff_days <= 0) {
			*out_day_within_year = -diff_days;
			DUK_DDDPRINT("--> year=%d, day-within-year=%d",
			             year, *out_day_within_year);
			DUK_ASSERT(*out_day_within_year >= 0);
			DUK_ASSERT(*out_day_within_year <= (is_leap_year(year) ? 366 : 365));
			return year;
		}

		/* Note: this is very tricky; we must never 'overshoot' the
		 * correction downwards.
		 */
		year -= 1 + (diff_days - 1) / 366;  /* conservative */
	}
}

/* Given a (year, month, day-within-month) triple, compute day number.
 * The input triple is un-normalized and may contain non-finite values.
 */
static double make_day(double year, double month, double day) {
	int day_num;
	int is_leap;
	int i;

	/* Assume that year, month, day are all coerced to whole numbers.
	 * They may also be NaN or infinity, in which case this function
	 * must return NaN or infinity to ensure time value becomes NaN.
	 */

	if (!isfinite(year) || !isfinite(month)) {
		return NAN;
	}
	
	year += floor(month / 12);

	month = fmod(month, 12);
	if (month < 0) {
		/* handle negative values */
		month += 12;
	}

	/* The algorithm in E5.1 Section 15.9.1.12 normalizes month, but
	 * does not normalize the day-of-month (nor check whether or not
	 * it is finite) because it's not necessary for finding the day
	 * number which matches the (year,month) pair.
	 *
	 * We assume that day_from_year() is exact here.
	 *
	 * Without an explicit infinity / NaN check in the beginning,
	 * day_num would be a bogus integer here.
	 */

	day_num = day_from_year((int) year);
	is_leap = is_leap_year((int) year);
	for (i = 0; i < (int) month; i++) {
		day_num += days_in_month[i];
		if (i == 1 && is_leap) {
			day_num++;
		}
	}

	return (double) day_num + day;
}

/* Split time value into parts.  The time value is assumed to be an internal
 * one, i.e. finite, no fractions.  Possible local time adjustment has already
 * been applied when reading the time value.
 */
static void timeval_to_parts(double d, int *parts, double *dparts, int flags) {
	double d1, d2;
	int t1, t2;
	int year, month, day;
	int dim;
	int i;
	int is_leap;

	DUK_ASSERT(isfinite(d));    /* caller checks */
	DUK_ASSERT(floor(d) == d);  /* no fractions in internal time */

	/* these computations are guaranteed to be exact for the valid
	 * E5 time value range, assuming milliseconds without fractions.
	 */
	d1 = fmod(d, (double) MS_DAY);
	if (d1 < 0.0) {
		/* deal with negative values */
		d1 += (double) MS_DAY;
	}
	d2 = floor(d / (double) MS_DAY);
	DUK_ASSERT(d2 * ((double) MS_DAY) + d1 == d);

	/* now expected to fit into a 32-bit integer */
	t1 = (int) d1;
	t2 = (int) d2;
	DUK_ASSERT((double) t1 == d1);
	DUK_ASSERT((double) t2 == d2);

	/* t1 = milliseconds within day, t2 = day number */

	parts[IDX_MILLISECOND] = t1 % 1000; t1 /= 1000;
	parts[IDX_SECOND] = t1 % 60; t1 /= 60;
	parts[IDX_MINUTE] = t1 % 60; t1 /= 60;
	parts[IDX_HOUR] = t1;
	DUK_ASSERT(parts[IDX_MILLISECOND] >= 0 && parts[IDX_MILLISECOND] <= 999);
	DUK_ASSERT(parts[IDX_SECOND] >= 0 && parts[IDX_SECOND] <= 59);
	DUK_ASSERT(parts[IDX_MINUTE] >= 0 && parts[IDX_MINUTE] <= 59);
	DUK_ASSERT(parts[IDX_HOUR] >= 0 && parts[IDX_HOUR] <= 23);

	parts[IDX_WEEKDAY] = (t2 + 4) % 7;  /* E5.1 Section 15.9.1.6 */
	if (parts[IDX_WEEKDAY] < 0) {
		/* deal with negative values */
		parts[IDX_WEEKDAY] += 7;
	}

	year = year_from_day(t2, &day);
	is_leap = is_leap_year(year);
	for (month = 0; month < 12; month++) {
		dim = days_in_month[month];
		if (month == 1 && is_leap) {
			dim++;
		}
		DUK_DDDPRINT("month=%d, dim=%d, day=%d", month, dim, day);
		if (day < dim) {
			break;
		}
		day -= dim;
	}
	DUK_DDDPRINT("final month=%d", month);
	DUK_ASSERT(month >= 0 && month <= 11);
	DUK_ASSERT(day >= 0 && day <= 31);

	parts[IDX_YEAR] = year;
	parts[IDX_MONTH] = month;
	parts[IDX_DAY] = day;

	if (flags & FLAG_ONEBASED) {
		parts[IDX_MONTH]++;  /* zero-based -> one-based */
		parts[IDX_DAY]++;    /* -""- */
	}

	if (dparts != NULL) {
		for (i = 0; i < NUM_PARTS; i++) {
			dparts[i] = (double) parts[i];
		}
	}
}

/* Compute time value from (double) parts. */
static double get_timeval_from_dparts(double *dparts, int flags) {
	double tmp_time;
	double tmp_day;
	double d;
	int i;

	/* Expects 'this' at top of stack on entry. */

	/* Coerce all finite parts with ToInteger().  ToInteger() must not
	 * be called for NaN/Infinity because it will convert e.g. NaN to
	 * zero.  If ToInteger() has already been called, this has no side
	 * effects and is idempotent.
	 *
	 * Don't read dparts[IDX_WEEKDAY]; it will cause Valgrind issues
	 * if the value is uninitialized.
	 */
	for (i = 0; i <= IDX_MILLISECOND; i++) {
		d = dparts[i];
		if (isfinite(d)) {
			dparts[i] = duk_js_tointeger_number(d);
		}
	}

	/* Use explicit steps in computation to try to ensure that
	 * computation happens with intermediate results coerced to
	 * double values (instead of using something more accurate).
	 * E.g. E5.1 Section 15.9.1.11 requires use of IEEE 754
	 * rules (= Ecmascript '+' and '*' operators).
	 */
	
	/* MakeTime */
	tmp_time = 0;
	tmp_time += dparts[IDX_HOUR] * ((double) MS_HOUR);
	tmp_time += dparts[IDX_MINUTE] * ((double) MS_MINUTE);
	tmp_time += dparts[IDX_SECOND] * ((double) MS_SECOND);
	tmp_time += dparts[IDX_MILLISECOND];

	/* MakeDay */
	tmp_day = make_day(dparts[IDX_YEAR], dparts[IDX_MONTH], dparts[IDX_DAY]);

	/* MakeDate */
	d = tmp_day * ((double) MS_DAY) + tmp_time;

	DUK_DDDPRINT("time=%lf day=%lf --> timeval=%lf", tmp_time, tmp_day, d);

	/* Optional UTC conversion followed by TimeClip().
	 * Note that this also handles Infinity -> NaN conversion.
	 */
	if (flags & FLAG_LOCALTIME) {
		/* FIXME: this is now incorrect.  'd' is local time here (as
		 * we're converting to UTC), but GET_LOCAL_TZOFFSET() should
		 * be called with UTC time.  This needs to be reworked to avoid
		 * the chicken-and-egg problem.
		 *
		 * See E5.1 Section 15.9.1.9:
		 * UTC(t) = t - LocalTZA - DaylightSavingTA(t - LocalTZA)
		 *
		 * For NaN/inf, GET_LOCAL_TZOFFSET() returns 0.
		 */

		d -= GET_LOCAL_TZOFFSET(d) * 1000;
	}
	d = timeclip(d);

	return d;
}

/*
 *  API oriented helpers
 */

/* Push 'this' binding, check that it is a Date object; then push the
 * internal time value.  At the end, stack is: [ ... this timeval ].
 * Returns the time value.  Local time adjustment is done if requested.
 */
static double push_this_and_get_timeval_tzoffset(duk_context *ctx, int flags, int *out_tzoffset) {
	duk_hthread *thr = (duk_hthread *) ctx;
	duk_hobject *h;
	double d;
	int tzoffset = 0;

	duk_push_this(ctx);
	h = duk_get_hobject(ctx, -1);  /* FIXME: getter with class check, useful in built-ins */
	if (h == NULL || DUK_HOBJECT_GET_CLASS_NUMBER(h) != DUK_HOBJECT_CLASS_DATE) {
		DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, "expected Date");
	}

	duk_get_prop_stridx(ctx, -1, DUK_STRIDX_INT_VALUE);
	d = duk_to_number(ctx, -1);
	duk_pop(ctx);

	if (isnan(d)) {
		if (flags & FLAG_NAN_TO_ZERO) {
			d = 0.0;
		}
		if (flags & FLAG_NAN_TO_RANGE_ERROR) {
			DUK_ERROR(thr, DUK_ERR_RANGE_ERROR, "Invalid Date");
		}
	}
	/* if no NaN handling flag, may still be NaN here, but not Inf */
	DUK_ASSERT(!isinf(d));

	if (flags & FLAG_LOCALTIME) {
		/* Note: DST adjustment is determined using UTC time.
		 * If 'd' is NaN, tzoffset will be 0.
		 */
		tzoffset = GET_LOCAL_TZOFFSET(d);  /* seconds */
		d += tzoffset * 1000;
	}
	if (out_tzoffset) {
		*out_tzoffset = tzoffset;
	}

	/* [ ... this ] */
	return d;
}

static double push_this_and_get_timeval(duk_context *ctx, int flags) {
	return push_this_and_get_timeval_tzoffset(ctx, flags, NULL);
}

/* Set timeval to 'this' from dparts, push the new time value onto the
 * value stack and return 1 (caller can then tailcall us).  Expects
 * the value stack to contain 'this' on the stack top.
 */
static int set_this_timeval_from_dparts(duk_context *ctx, double *dparts, int flags) {
	double d;

	/* [ ... this ] */

	d = get_timeval_from_dparts(dparts, flags);
	duk_push_number(ctx, d);  /* -> [ ... this timeval_new ] */
	duk_dup_top(ctx);         /* -> [ ... this timeval_new timeval_new ] */
	duk_put_prop_stridx(ctx, -3, DUK_STRIDX_INT_VALUE);

	/* stack top: new time value */
	return 1;
}

static int format_parts_iso8601(duk_context *ctx, int *parts, int tzoffset, int flags_and_sep) {
	char yearstr[8];   /* "-123456\0" */
	char tzstr[8];     /* "+11:22\0" */
	char sep = (char) (flags_and_sep >> 16);

	DUK_ASSERT(parts[IDX_MONTH] >= 1 && parts[IDX_MONTH] <= 12);
	DUK_ASSERT(parts[IDX_DAY] >= 1 && parts[IDX_DAY] <= 31);

	/* Note: %06d for positive value, %07d for negative value to include sign and
	 * 6 digits.
	 */
	sprintf(yearstr,
	        (parts[IDX_YEAR] >= 0 && parts[IDX_YEAR] <= 9999) ? "%04d" :
		        ((parts[IDX_YEAR] >= 0) ? "+%06d" : "%07d"),
	        parts[IDX_YEAR]);

	if (flags_and_sep & FLAG_LOCALTIME) {
		/* tzoffset seconds are dropped */
		if (tzoffset >= 0) {
			int tmp = tzoffset / 60;
			sprintf(tzstr, "+%02d:%02d", tmp / 60, tmp % 60);
		} else {
			int tmp = -tzoffset / 60;
			sprintf(tzstr, "-%02d:%02d", tmp / 60, tmp % 60);
		}
	} else {
		tzstr[0] = 'Z';
		tzstr[1] = (char) 0;
	}

	if ((flags_and_sep & FLAG_TOSTRING_DATE) && (flags_and_sep & FLAG_TOSTRING_TIME)) {
		duk_push_sprintf(ctx, "%s-%02d-%02d%c%02d:%02d:%02d.%03d%s",
		                 yearstr, parts[IDX_MONTH], parts[IDX_DAY], sep,
		                 parts[IDX_HOUR], parts[IDX_MINUTE], parts[IDX_SECOND],
		                 parts[IDX_MILLISECOND], tzstr);
	} else if (flags_and_sep & FLAG_TOSTRING_DATE) {
		duk_push_sprintf(ctx, "%s-%02d-%02d", yearstr, parts[IDX_MONTH], parts[IDX_DAY]);
	} else {
		DUK_ASSERT(flags_and_sep & FLAG_TOSTRING_TIME);
		duk_push_sprintf(ctx, "%02d:%02d:%02d.%03d%s", parts[IDX_HOUR], parts[IDX_MINUTE],
		                 parts[IDX_SECOND], parts[IDX_MILLISECOND], tzstr);
	}

	return 1;
}

/* Helper for string conversion calls: check 'this' binding, get the
 * internal time value, and format date and/or time in a few formats.
 */
static int to_string_helper(duk_context *ctx, int flags_and_sep) {
	double d;
	int parts[NUM_PARTS];
	int tzoffset;  /* seconds */
	int rc;

	d = push_this_and_get_timeval_tzoffset(ctx, flags_and_sep, &tzoffset);
	if (isnan(d)) {
		duk_push_hstring_stridx(ctx, DUK_STRIDX_INVALID_DATE);
		return 1;
	}
	DUK_ASSERT(isfinite(d));

	/* formatters always get one-based month/day-of-month */
	timeval_to_parts(d, parts, NULL, FLAG_ONEBASED);
	DUK_ASSERT(parts[IDX_MONTH] >= 1 && parts[IDX_MONTH] <= 12);
	DUK_ASSERT(parts[IDX_DAY] >= 1 && parts[IDX_DAY] <= 31);

	if (flags_and_sep & FLAG_TOSTRING_LOCALE) {
		/* try locale specific formatter; if it refuses to format the
		 * string, fall back to an ISO 8601 formatted value in local
		 * time.
		 */
#ifdef DUK_USE_DATE_FMT_STRFTIME
		rc = format_parts_strftime(ctx, parts, tzoffset, flags_and_sep);
		if (rc == 1) {
			return rc;
		}
#else
		/* No locale specific formatter; this is OK, we fall back
		 * to ISO 8601.
		 */
#endif
	}

	rc = format_parts_iso8601(ctx, parts, tzoffset, flags_and_sep);
	return rc;
}

/* Helper for component getter calls: check 'this' binding, get the
 * internal time value, split it into parts (either as UTC time or
 * local time), push a specified component as a return value to the
 * value stack and return 1 (caller can then tailcall us).
 */
static int get_part_helper(duk_context *ctx, int flags_and_idx) {
	double d;
	int parts[NUM_PARTS];
	int idx_part = flags_and_idx >> 16;

	DUK_ASSERT(idx_part >= 0 && idx_part < NUM_PARTS);

	d = push_this_and_get_timeval(ctx, flags_and_idx);
	if (isnan(d)) {
		duk_push_nan(ctx);
		return 1;
	}
	DUK_ASSERT(isfinite(d));

	timeval_to_parts(d, parts, NULL, flags_and_idx);

	/* Setter APIs detect special year numbers (0...99) and apply a +1900
	 * only in certain cases.  The legacy getYear() getter applies -1900
	 * unconditionally.
	 */
	duk_push_int(ctx, (flags_and_idx & FLAG_SUB1900) ? parts[idx_part] - 1900 : parts[idx_part]);
	return 1;
}

/* Helper for component setter calls: check 'this' binding, get the
 * internal time value, split it into parts (either as UTC time or
 * local time), modify one or more components as specified, recompute
 * the time value, set it as the internal value.  Finally, push the
 * new time value as a return value to the value stack and return 1
 * (caller can then tailcall us).
 */
static int set_part_helper(duk_context *ctx, int flags_and_maxnargs) {
	double d;
	int parts[NUM_PARTS];
	double dparts[NUM_PARTS];
	int nargs;
	int maxnargs = flags_and_maxnargs >> 16;
	int idx_first, idx;
	int i;

	nargs = duk_get_top(ctx);
	d = push_this_and_get_timeval(ctx, flags_and_maxnargs);
	DUK_ASSERT(isfinite(d) || isnan(d));

	if (isfinite(d)) {
		timeval_to_parts(d, parts, dparts, flags_and_maxnargs);
	} else {
		/* NaN timevalue: we need to coerce the arguments, but
		 * the resulting internal timestamp needs to remain NaN.
		 * This works but is not pretty: parts and dparts will
		 * be partially uninitialized, but we only write to it.
		 */
	}

	/*
	 *  Determining which datetime components to overwrite based on
	 *  stack arguments is a bit complicated, but important to factor
	 *  out from setters themselves for compactness.
	 *
	 *  If FLAG_TIMESETTER, maxnargs indicates setter type:
	 *
	 *   1 -> millisecond
	 *   2 -> second, [millisecond]
	 *   3 -> minute, [second], [millisecond]
	 *   4 -> hour, [minute], [second], [millisecond]
	 *
	 *  Else:
	 *
	 *   1 -> date
	 *   2 -> month, [date]
	 *   3 -> year, [month], [date]
	 *
	 *  By comparing nargs and maxnargs (and flags) we know which
	 *  components to override.  We rely on part index ordering.
	 */

	if (flags_and_maxnargs & FLAG_TIMESETTER) {
		DUK_ASSERT(maxnargs >= 1 && maxnargs <= 4);
		idx_first = IDX_MILLISECOND - (maxnargs - 1);
	} else {
		DUK_ASSERT(maxnargs >= 1 && maxnargs <= 3);
		idx_first = IDX_DAY - (maxnargs - 1);
	}
	DUK_ASSERT(idx_first >= 0 && idx_first < NUM_PARTS);

	for (i = 0; i < maxnargs; i++) {
		if (i >= nargs) {
			/* no argument given -> leave components untouched */
			break;
		}
		idx = idx_first + i;
		DUK_ASSERT(idx >= 0 && idx < NUM_PARTS);

		if (idx == IDX_YEAR && (flags_and_maxnargs & FLAG_YEAR_FIXUP)) {
			twodigit_year_fixup(ctx, i);
		}

		dparts[idx] = duk_to_number(ctx, i);

		if (idx == IDX_DAY) {
			/* Day-of-month is one-based in the API, but zero-based
			 * internally, so fix here.  Note that month is zero-based
			 * both in the API and internally.
			 */
			dparts[idx] -= 1.0;
		}
	}

	/* Leaves new timevalue on stack top and returns 1, which is correct
	 * for part setters.
	 */
	if (isfinite(d)) {
		return set_this_timeval_from_dparts(ctx, dparts, flags_and_maxnargs);
	} else {
		/* Internal timevalue is already NaN, so don't touch it. */
		duk_push_nan(ctx);
		return 1;
	}
}

/* Apply ToNumber() to specified index; if ToInteger(val) in [0,99], add
 * 1900 and replace value at idx_val.
 */
static void twodigit_year_fixup(duk_context *ctx, int idx_val) {
	double d;

	/* E5 Sections 15.9.3.1, B.2.4, B.2.5 */
	duk_to_number(ctx, idx_val);
	if (duk_is_nan(ctx, idx_val)) {
		return;
	}
	duk_dup(ctx, idx_val);
	duk_to_int(ctx, -1);
	d = duk_get_number(ctx, -1);  /* get as double to handle huge numbers correctly */
	if (d >= 0.0 && d <= 99.0) {
		d += 1900.0;
		duk_push_number(ctx, d);
		duk_replace(ctx, idx_val);
	}
	duk_pop(ctx);
}

/* Set datetime parts from stack arguments, defaulting any missing values.
 * Day-of-week is not set; it is not required when setting the time value.
 */
static void set_parts_from_args(duk_context *ctx, double *dparts, int nargs) {
	double d;
	int i;
	int idx;

	/* Causes a ToNumber() coercion, but doesn't break coercion order since
	 * year is coerced first anyway.
	 */
	twodigit_year_fixup(ctx, 0);

	/* There are at most 7 args, but we use 8 here so that also
	 * IDX_WEEKDAY gets initialized (to zero) to avoid the potential
	 * for any Valgrind gripes later.
	 */
	for (i = 0; i < 8; i++) {
		/* Note: rely on index ordering */
		idx = IDX_YEAR + i;
		if (i < nargs) {
			d = duk_to_number(ctx, i);
			if (idx == IDX_DAY) {
				/* Convert day from one-based to zero-based (internal).  This may
				 * cause the day part to be negative, which is OK.
				 */
				d -= 1.0;
			}
		} else {
			/* All components default to 0 except day-of-month which defaults
			 * to 1.  However, because our internal day-of-month is zero-based,
			 * it also defaults to zero here.
			 */
			d = 0.0;
		}
		dparts[idx] = d;
	}

	DUK_DDDPRINT("parts from args -> %lf %lf %lf %lf %lf %lf %lf %lf",
	             dparts[0], dparts[1], dparts[2], dparts[3],
	             dparts[4], dparts[5], dparts[6], dparts[7]);
}

/*
 *  Constructor calls
 */

int duk_builtin_date_constructor(duk_context *ctx) {
	int nargs = duk_get_top(ctx);
	int is_cons = duk_is_constructor_call(ctx);
	double dparts[NUM_PARTS];
	double d;

	DUK_DDDPRINT("Date constructor, nargs=%d, is_cons=%d", nargs, is_cons);

	duk_push_object_helper(ctx,
	                       DUK_HOBJECT_FLAG_EXTENSIBLE |
	                       DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_DATE),
	                       DUK_BIDX_DATE_PROTOTYPE);

	/* Unlike most built-ins, the internal [[PrimitiveValue]] of a Date
	 * is mutable.
	 */

	if (nargs == 0 || !is_cons) {
		d = timeclip(GET_NOW_TIMEVAL(ctx));
		duk_push_number(ctx, d);
		duk_def_prop_stridx(ctx, -2, DUK_STRIDX_INT_VALUE, DUK_PROPDESC_FLAGS_W);
		if (!is_cons) {
			/* called as a normal function: return new Date().toString() */
			duk_to_string(ctx, -1);
		}
		return 1;
	} else if (nargs == 1) {
		duk_to_primitive(ctx, 0, DUK_HINT_NONE);
		if (duk_is_string(ctx, 0)) {
			parse_string(ctx, duk_to_string(ctx, 0));
			duk_replace(ctx, 0);  /* may be NaN */
		}
		d = timeclip(duk_to_number(ctx, 0));
		duk_push_number(ctx, d);
		duk_def_prop_stridx(ctx, -2, DUK_STRIDX_INT_VALUE, DUK_PROPDESC_FLAGS_W);
		return 1;
	}

	set_parts_from_args(ctx, dparts, nargs);

	/* Parts are in local time, convert when setting. */

	set_this_timeval_from_dparts(ctx, dparts, FLAG_LOCALTIME /*flags*/);  /* -> [ ... this timeval ] */
	duk_pop(ctx);  /* -> [ ... this ] */
	return 1;
}

int duk_builtin_date_constructor_parse(duk_context *ctx) {
	return parse_string(ctx, duk_to_string(ctx, 0));
}

int duk_builtin_date_constructor_utc(duk_context *ctx) {
	int nargs = duk_get_top(ctx);
	double dparts[NUM_PARTS];
	double d;

	/* Behavior for nargs < 2 is implementation dependent: currently we'll
	 * set a NaN time value (matching V8 behavior) in this case.
	 */

	if (nargs < 2) {
		duk_push_nan(ctx);
	} else {
		set_parts_from_args(ctx, dparts, nargs);
		d = get_timeval_from_dparts(dparts, 0 /*flags*/);
		duk_push_number(ctx, d);
	}
	return 1;
}

int duk_builtin_date_constructor_now(duk_context *ctx) {
	double d;

	d = GET_NOW_TIMEVAL(ctx);
	DUK_ASSERT(timeclip(d) == d);  /* TimeClip() should never be necessary */
	duk_push_number(ctx, d);
	return 1;
}

/*
 *  Conversions
 *
 *  Human readable conversions are now basically ISO 8601 with a space
 *  (instead of 'T') as the date/time separator.  This is a good baseline
 *  and is platform independent.
 */

#define  TOSTRING(ctx,flags,sep)  \
	to_string_helper((ctx),(flags) | (((int) (sep)) << 16))

int duk_builtin_date_prototype_to_string(duk_context *ctx) {
	return TOSTRING(ctx, FLAG_TOSTRING_DATE |
	                     FLAG_TOSTRING_TIME |
	                     FLAG_LOCALTIME, ' ');
}

int duk_builtin_date_prototype_to_date_string(duk_context *ctx) {
	return TOSTRING(ctx, FLAG_TOSTRING_DATE |
	                     FLAG_LOCALTIME, ' ');
}

int duk_builtin_date_prototype_to_time_string(duk_context *ctx) {
	return TOSTRING(ctx, FLAG_TOSTRING_TIME |
	                     FLAG_LOCALTIME, ' ');
}

int duk_builtin_date_prototype_to_locale_string(duk_context *ctx) {
	return TOSTRING(ctx, FLAG_TOSTRING_DATE |
	                     FLAG_TOSTRING_TIME |
	                     FLAG_TOSTRING_LOCALE |
	                     FLAG_LOCALTIME, ' ');
}

int duk_builtin_date_prototype_to_locale_date_string(duk_context *ctx) {
	return TOSTRING(ctx, FLAG_TOSTRING_DATE |
	                     FLAG_TOSTRING_LOCALE |
	                     FLAG_LOCALTIME, ' ');
}

int duk_builtin_date_prototype_to_locale_time_string(duk_context *ctx) {
	return TOSTRING(ctx, FLAG_TOSTRING_TIME |
	                     FLAG_TOSTRING_LOCALE |
	                     FLAG_LOCALTIME, ' ');
}

int duk_builtin_date_prototype_value_of(duk_context *ctx) {
	/* This native function is also used for Date.prototype.getTime()
	 * as their behavior is identical.
	 */

	double d = push_this_and_get_timeval(ctx, 0 /*flags*/);  /* -> [ this ] */
	DUK_ASSERT(isfinite(d) || isnan(d));
	duk_push_number(ctx, d);
	return 1;
}

int duk_builtin_date_prototype_to_utc_string(duk_context *ctx) {
	/* E5.1 specification does not require a specific format, but
	 * result should be human readable.  The specification suggests
	 * using ISO 8601 format with a space (instead of 'T') separator
	 * if a more human readable format is not available.
	 */
	return TOSTRING(ctx, FLAG_TOSTRING_DATE |
	                     FLAG_TOSTRING_TIME, ' ');
}

int duk_builtin_date_prototype_to_iso_string(duk_context *ctx) {
	/* Unlike other conversion functions, toISOString() requires a
	 * RangeError for invalid date values.
	 */
	return TOSTRING(ctx, FLAG_NAN_TO_RANGE_ERROR |
	                     FLAG_TOSTRING_DATE |
	                     FLAG_TOSTRING_TIME, 'T');
}

int duk_builtin_date_prototype_to_json(duk_context *ctx) {
	/* Note: toJSON() is a generic function which works even if 'this'
	 * is not a Date.  The sole argument is ignored.
	 */

	duk_push_this(ctx);
	duk_to_object(ctx, -1);

	duk_dup_top(ctx);
	duk_to_primitive(ctx, -1, DUK_HINT_NUMBER);
	if (duk_is_number(ctx, -1)) {
		double d = duk_get_number(ctx, -1);
		if (!isfinite(d)) {
			duk_push_null(ctx);
			return 1;
		}
	}
	duk_pop(ctx);

	duk_get_prop_stridx(ctx, -1, DUK_STRIDX_TO_ISO_STRING);
	duk_dup(ctx, -2);  /* -> [ O toIsoString O ] */
	duk_call_method(ctx, 0);
	return 1;
}

/*
 *  Getters not related to component access.
 */

int duk_builtin_date_prototype_get_timezone_offset(duk_context *ctx) {
	/*
	 *  Return (t - LocalTime(t)) in minutes:
	 *
	 *    t - LocalTime(t) = t - (t + LocalTZA + DaylightSavingTA(t))
	 *                     = -(LocalTZA + DaylightSavingTA(t))
	 *
	 *  where DaylightSavingTA() is checked for time 't'.
	 *
	 *  Note that the sign of the result is opposite to common usage,
	 *  e.g. for EE(S)T which normally is +2h or +3h from UTC, this
	 *  function returns -120 or -180.
	 *
	 */

	double d;
	int tzoffset;

	/* Note: DST adjustment is determined using UTC time. */
	d = push_this_and_get_timeval(ctx, 0 /*flags*/);
	DUK_ASSERT(isfinite(d) || isnan(d));
	if (isnan(d)) {
		duk_push_nan(ctx);
	} else {
		DUK_ASSERT(isfinite(d));
		tzoffset = GET_LOCAL_TZOFFSET(d);
		duk_push_int(ctx, -tzoffset / 60);
	}
	return 1;
}

/* Date.prototype.getTime() and Date.prototype.valueOf() have identical
 * behavior.  They have separate function objects, but share the same C
 * function (duk_builtin_date_prototype_value_of).
 */

/*
 *  Getters.
 *
 *  Implementing getters is quite easy.  The internal time value is either
 *  NaN, or represents milliseconds (without fractions) from Jan 1, 1970.
 *  The internal time value can be converted to integer parts, and each
 *  part will be normalized and will fit into a 32-bit signed integer.
 */

/* part index is encoded into flags field to reduce argument count */
#define  GET_PART(ctx,flags,partidx)  \
	get_part_helper((ctx), (flags) | ((partidx) << 16))

int duk_builtin_date_prototype_get_full_year(duk_context *ctx) {
	return GET_PART(ctx, FLAG_LOCALTIME, IDX_YEAR);
}

int duk_builtin_date_prototype_get_utc_full_year(duk_context *ctx) {
	return GET_PART(ctx, 0, IDX_YEAR);
}

int duk_builtin_date_prototype_get_month(duk_context *ctx) {
	return GET_PART(ctx, FLAG_LOCALTIME, IDX_MONTH);
}

int duk_builtin_date_prototype_get_utc_month(duk_context *ctx) {
	return GET_PART(ctx, 0, IDX_MONTH);
}

int duk_builtin_date_prototype_get_date(duk_context *ctx) {
	/* Note: 'date' means day-of-month, and is zero-based in internal
	 * calculations but public API expects it to be one-based.
	 */
	return GET_PART(ctx, FLAG_ONEBASED | FLAG_LOCALTIME, IDX_DAY);
}

int duk_builtin_date_prototype_get_utc_date(duk_context *ctx) {
	/* Note: 'date' means day-of-month.  Result should be one-based. */
	return GET_PART(ctx, FLAG_ONEBASED, IDX_DAY);
}

int duk_builtin_date_prototype_get_day(duk_context *ctx) {
	/* Note: 'day' means day-of-week */
	return GET_PART(ctx, FLAG_LOCALTIME, IDX_WEEKDAY);
}

int duk_builtin_date_prototype_get_utc_day(duk_context *ctx) {
	/* Note: 'day' means day-of-week */
	return GET_PART(ctx, 0, IDX_WEEKDAY);
}

int duk_builtin_date_prototype_get_hours(duk_context *ctx) {
	return GET_PART(ctx, FLAG_LOCALTIME, IDX_HOUR);
}

int duk_builtin_date_prototype_get_utc_hours(duk_context *ctx) {
	return GET_PART(ctx, 0, IDX_HOUR);
}

int duk_builtin_date_prototype_get_minutes(duk_context *ctx) {
	return GET_PART(ctx, FLAG_LOCALTIME, IDX_MINUTE);
}

int duk_builtin_date_prototype_get_utc_minutes(duk_context *ctx) {
	return GET_PART(ctx, 0, IDX_MINUTE);
}

int duk_builtin_date_prototype_get_seconds(duk_context *ctx) {
	return GET_PART(ctx, FLAG_LOCALTIME, IDX_SECOND);
}

int duk_builtin_date_prototype_get_utc_seconds(duk_context *ctx) {
	return GET_PART(ctx, 0, IDX_SECOND);
}

int duk_builtin_date_prototype_get_milliseconds(duk_context *ctx) {
	return GET_PART(ctx, FLAG_LOCALTIME, IDX_MILLISECOND);
}

int duk_builtin_date_prototype_get_utc_milliseconds(duk_context *ctx) {
	return GET_PART(ctx, 0, IDX_MILLISECOND);
}

#ifdef DUK_USE_SECTION_B
int duk_builtin_date_prototype_get_year(duk_context *ctx) {
	return GET_PART(ctx, FLAG_LOCALTIME | FLAG_SUB1900, IDX_YEAR);
}
#endif  /* DUK_USE_SECTION_B */

/*
 *  Setters.
 *
 *  Setters are a bit more complicated than getters.  Component setters
 *  break down the current time value into its (normalized) component
 *  parts, replace one or more components with -unnormalized- new values,
 *  and the components are then converted back into a time value.  As an
 *  example of using unnormalized values:
 *
 *    var d = new Date(1234567890);
 *
 *  is equivalent to:
 *
 *    var d = new Date(0);
 *    d.setUTCMilliseconds(1234567890);
 */

#define  SET_PART(ctx,flags,maxnargs) \
	set_part_helper((ctx), (flags) | ((maxnargs) << 16))

int duk_builtin_date_prototype_set_time(duk_context *ctx) {
	double d;

	(void) push_this_and_get_timeval(ctx, 0 /*flags*/); /* -> [ timeval this ] */
	d = timeclip(duk_to_number(ctx, 0));
	duk_push_number(ctx, d);
	duk_dup_top(ctx);
	duk_put_prop_stridx(ctx, -3, DUK_STRIDX_INT_VALUE); /* -> [ timeval this timeval ] */

	return 1;
}

int duk_builtin_date_prototype_set_milliseconds(duk_context *ctx) {
	return SET_PART(ctx, FLAG_TIMESETTER | FLAG_LOCALTIME, 1);
}

int duk_builtin_date_prototype_set_utc_milliseconds(duk_context *ctx) {
	return SET_PART(ctx, FLAG_TIMESETTER, 1);
}

int duk_builtin_date_prototype_set_seconds(duk_context *ctx) {
	return SET_PART(ctx, FLAG_TIMESETTER | FLAG_LOCALTIME, 2);
}

int duk_builtin_date_prototype_set_utc_seconds(duk_context *ctx) {
	return SET_PART(ctx, FLAG_TIMESETTER, 2);
}

int duk_builtin_date_prototype_set_minutes(duk_context *ctx) {
	return SET_PART(ctx, FLAG_TIMESETTER | FLAG_LOCALTIME, 3);
}

int duk_builtin_date_prototype_set_utc_minutes(duk_context *ctx) {
	return SET_PART(ctx, FLAG_TIMESETTER, 3);
}

int duk_builtin_date_prototype_set_hours(duk_context *ctx) {
	return SET_PART(ctx, FLAG_TIMESETTER | FLAG_LOCALTIME, 4);
}

int duk_builtin_date_prototype_set_utc_hours(duk_context *ctx) {
	return SET_PART(ctx, FLAG_TIMESETTER, 4);
}

int duk_builtin_date_prototype_set_date(duk_context *ctx) {
	return SET_PART(ctx, FLAG_LOCALTIME, 1);
}

int duk_builtin_date_prototype_set_utc_date(duk_context *ctx) {
	return SET_PART(ctx, 0, 1);
}

int duk_builtin_date_prototype_set_month(duk_context *ctx) {
	return SET_PART(ctx, FLAG_LOCALTIME, 2);
}

int duk_builtin_date_prototype_set_utc_month(duk_context *ctx) {
	return SET_PART(ctx, 0, 2);
}

int duk_builtin_date_prototype_set_full_year(duk_context *ctx) {
	return SET_PART(ctx, FLAG_NAN_TO_ZERO | FLAG_LOCALTIME, 3);
}

int duk_builtin_date_prototype_set_utc_full_year(duk_context *ctx) {
	return SET_PART(ctx, FLAG_NAN_TO_ZERO, 3);
}

#ifdef DUK_USE_SECTION_B
int duk_builtin_date_prototype_set_year(duk_context *ctx) {
	/* Special year check is omitted.  NaN / Infinity will just flow
	 * through and ultimately result in a NaN internal time value.
	 */

	/* setYear() does not have optional arguments for setting month and
	 * day-in-month, but we indicate 'maxnargs' to be 3 to get the year
	 * written to the correct component index in set_part_helper().
	 * Because there are never optional arguments here (this is not a
	 * varargs function) only the year will be set.
	 */
	 
	DUK_ASSERT_TOP(ctx, 1);
	return SET_PART(ctx, FLAG_NAN_TO_ZERO | FLAG_YEAR_FIXUP, 3);
}
#endif  /* DUK_USE_SECTION_B */

/* Date.prototype.toGMTString() and Date.prototype.toUTCString() are
 * required to be the same Ecmascript function object (!), so it is omitted
 * from here.
 */

#line 1 "duk_builtin_duk.c"
/*
 *  __duk__ built-ins
 */

#include <sys/time.h>

/* include removed: duk_internal.h */

int duk_builtin_duk_object_addr(duk_context *ctx) {
	duk_tval *tv;
	void *p;

	tv = duk_get_tval(ctx, 0);
	if (!tv || !DUK_TVAL_IS_HEAP_ALLOCATED(tv)) {
		return 0;  /* undefined */
	}
	p = (void *) DUK_TVAL_GET_HEAPHDR(tv);

	/* any heap allocated value (string, object, buffer) has a stable pointer */
	duk_push_sprintf(ctx, "%p", p);
	return 1;
}

int duk_builtin_duk_object_refc(duk_context *ctx) {
#ifdef DUK_USE_REFERENCE_COUNTING
	duk_tval *tv = duk_get_tval(ctx, 0);
	duk_heaphdr *h;
	if (!tv) {
		return 0;
	}
	if (!DUK_TVAL_IS_HEAP_ALLOCATED(tv)) {
		return 0;
	}
	h = DUK_TVAL_GET_HEAPHDR(tv);
	duk_push_int(ctx, DUK_HEAPHDR_GET_REFCOUNT(h));
	return 1;
#else
	return 0;
#endif
}

int duk_builtin_duk_object_gc(duk_context *ctx) {
#ifdef DUK_USE_MARK_AND_SWEEP
	duk_hthread *thr = (duk_hthread *) ctx;
	int flags;
	int rc;

	flags = duk_get_int(ctx, 0);
	rc = duk_heap_mark_and_sweep(thr->heap, flags);
	duk_push_int(ctx, rc);
	return 1;
#else
	return 0;
#endif
}

int duk_builtin_duk_object_get_finalizer(duk_context *ctx) {
	(void) duk_require_hobject(ctx, 0);
	duk_get_prop_stridx(ctx, 0, DUK_STRIDX_INT_FINALIZER);
	return 1;
}

int duk_builtin_duk_object_set_finalizer(duk_context *ctx) {
	DUK_ASSERT_TOP(ctx, 2);
	(void) duk_put_prop_stridx(ctx, 0, DUK_STRIDX_INT_FINALIZER);  /* XXX: check value? */
	return 0;
}

/*
 *  Spawn a thread.
 */

int duk_builtin_duk_object_spawn(duk_context *ctx) {
	duk_hthread *new_thr;
	duk_hobject *func;

	if (!duk_is_callable(ctx, 0)) {
		return DUK_RET_TYPE_ERROR;
	}
	func = duk_get_hobject(ctx, 0);
	DUK_ASSERT(func != NULL);

	duk_push_thread(ctx);
	new_thr = (duk_hthread *) duk_get_hobject(ctx, -1);
	DUK_ASSERT(new_thr != NULL);
	new_thr->state = DUK_HTHREAD_STATE_INACTIVE;

	/* push initial function call to new thread stack; this is
	 * picked up by resume().
	 */
	duk_push_hobject((duk_context *) new_thr, func);

	return 1;  /* return thread */
}

/*
 *  Resume a thread.
 *
 *  The thread must be in resumable state, either (a) new thread which hasn't
 *  yet started, or (b) a thread which has previously yielded.  This method
 *  must be called from an Ecmascript function.
 *
 *  Args:
 *    - thread
 *    - value
 *    - isError (defaults to false)
 *
 *  Note: yield and resume handling is currently asymmetric.
 */

int duk_builtin_duk_object_resume(duk_context *ctx) {
	duk_hthread *thr = (duk_hthread *) ctx;
	duk_hthread *thr_resume;
	duk_tval tv_tmp;
	duk_tval *tv;
	duk_hobject *func;
	int is_error;

	DUK_DDDPRINT("__duk__.resume(): thread=%!T, value=%!T, is_error=%!T",
	             duk_get_tval(ctx, 0),
	             duk_get_tval(ctx, 1),
	             duk_get_tval(ctx, 2));

	DUK_ASSERT(thr->state == DUK_HTHREAD_STATE_RUNNING);
	DUK_ASSERT(thr->heap->curr_thread == thr);

	thr_resume = duk_require_hthread(ctx, 0);
	is_error = duk_to_boolean(ctx, 2);

	/*
	 *  Thread state and calling context checks
	 */

	if (thr->callstack_top < 2) {
		DUK_DDPRINT("resume state invalid: callstack should contain at least 2 entries (caller and __duk__.resume)");
		goto state_error;
	}
	DUK_ASSERT((thr->callstack + thr->callstack_top - 1)->func != NULL);  /* us */
	DUK_ASSERT(DUK_HOBJECT_IS_NATIVEFUNCTION((thr->callstack + thr->callstack_top - 1)->func));
	DUK_ASSERT((thr->callstack + thr->callstack_top - 2)->func != NULL);  /* caller */

	if (!DUK_HOBJECT_IS_COMPILEDFUNCTION((thr->callstack + thr->callstack_top - 2)->func)) {
		DUK_DDPRINT("resume state invalid: caller must be Ecmascript code");
		goto state_error;
	}

	/* Note: there is no requirement that: 'thr->callstack_preventcount == 1'
	 * like for yield.
	 */

	if (thr_resume->state != DUK_HTHREAD_STATE_INACTIVE &&
	    thr_resume->state != DUK_HTHREAD_STATE_YIELDED) {
		DUK_DDPRINT("resume state invalid: target thread must be INACTIVE or YIELDED");
		goto state_error;
	}

	DUK_ASSERT(thr_resume->state == DUK_HTHREAD_STATE_INACTIVE ||
	           thr_resume->state == DUK_HTHREAD_STATE_YIELDED);

	/* Further state-dependent pre-checks */

	if (thr_resume->state == DUK_HTHREAD_STATE_YIELDED) {
		/* no pre-checks now, assume a previous yield() has left things in
		 * tip-top shape (longjmp handler will assert for these).
		 */
	} else {
		DUK_ASSERT(thr_resume->state == DUK_HTHREAD_STATE_INACTIVE);

		if ((thr_resume->callstack_top != 0) ||
		    (thr_resume->valstack_top - thr_resume->valstack != 1)) {
			goto state_invalid_initial;
		}
		tv = &thr_resume->valstack_top[-1];
		DUK_ASSERT(tv >= thr_resume->valstack && tv < thr_resume->valstack_top);
		if (!DUK_TVAL_IS_OBJECT(tv)) {
			goto state_invalid_initial;
		}
		func = DUK_TVAL_GET_OBJECT(tv);
		DUK_ASSERT(func != NULL);
		if (!DUK_HOBJECT_IS_COMPILEDFUNCTION(func)) {
			/* Note: cannot be a bound function either right now,
			 * this would be easy to relax though.
			 */
			goto state_invalid_initial;
		}

	}

	/*
	 *  The error object has been augmented with a traceback and other
	 *  info from its creation point -- usually another thread.  It might
	 *  be nice to get a traceback from the resumee but this is not the
	 *  case now.
	 */

#ifdef DUK_USE_DEBUG  /* debug logging */
	if (is_error) {
		DUK_DDDPRINT("RESUME ERROR: thread=%!T, value=%!T",
		             duk_get_tval(ctx, 0),
		             duk_get_tval(ctx, 1));
	} else if (thr_resume->state == DUK_HTHREAD_STATE_YIELDED) {
		DUK_DDDPRINT("RESUME NORMAL: thread=%!T, value=%!T",
		             duk_get_tval(ctx, 0),
		             duk_get_tval(ctx, 1));
	} else {
		DUK_DDDPRINT("RESUME INITIAL: thread=%!T, value=%!T",
		             duk_get_tval(ctx, 0),
		             duk_get_tval(ctx, 1));
	}
#endif

	thr->heap->lj.type = DUK_LJ_TYPE_RESUME;

	/* lj value2: thread */
	DUK_ASSERT(thr->valstack_bottom < thr->valstack_top);
	DUK_TVAL_SET_TVAL(&tv_tmp, &thr->heap->lj.value2);
	DUK_TVAL_SET_TVAL(&thr->heap->lj.value2, &thr->valstack_bottom[0]);
	DUK_TVAL_INCREF(thr, &thr->heap->lj.value2);
	DUK_TVAL_DECREF(thr, &tv_tmp);

	/* lj value1: value */
	DUK_ASSERT(thr->valstack_bottom + 1 < thr->valstack_top);
	DUK_TVAL_SET_TVAL(&tv_tmp, &thr->heap->lj.value1);
	DUK_TVAL_SET_TVAL(&thr->heap->lj.value1, &thr->valstack_bottom[1]);
	DUK_TVAL_INCREF(thr, &thr->heap->lj.value1);
	DUK_TVAL_DECREF(thr, &tv_tmp);

	thr->heap->lj.iserror = is_error;

	DUK_ASSERT(thr->heap->lj.jmpbuf_ptr != NULL);  /* call is from executor, so we know we have a jmpbuf */
	duk_err_longjmp(thr);  /* execution resumes in bytecode executor */
	return 0;  /* never here */

 state_invalid_initial:
	DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, "invalid initial thread state/stack");
	return 0;  /* never here */

 state_error:
	DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, "invalid state for resume");
	return 0;  /* never here */
}

/*
 *  Yield the current thread.
 *
 *  The thread must be in yieldable state: it must have a resumer, and there
 *  must not be any yield-preventing calls (native calls and constructor calls,
 *  currently) in the thread's call stack (otherwise a resume would not be
 *  possible later).  This method must be called from an Ecmascript function.
 *
 *  Args:
 *    - value
 *    - isError (defaults to false)
 *
 *  Note: yield and resume handling is currently asymmetric.
 */

int duk_builtin_duk_object_yield(duk_context *ctx) {
	duk_hthread *thr = (duk_hthread *) ctx;
	duk_tval tv_tmp;
	int is_error;

	DUK_DDDPRINT("__duk__.yield(): value=%!T, is_error=%!T",
	             duk_get_tval(ctx, 0),
	             duk_get_tval(ctx, 1));

	DUK_ASSERT(thr->state == DUK_HTHREAD_STATE_RUNNING);
	DUK_ASSERT(thr->heap->curr_thread == thr);

	is_error = duk_to_boolean(ctx, 1);

	/*
	 *  Thread state and calling context checks
	 */

	if (!thr->resumer) {
		DUK_DDPRINT("yield state invalid: current thread must have a resumer");
		goto state_error;
	}
	DUK_ASSERT(thr->resumer->state == DUK_HTHREAD_STATE_RESUMED);

	if (thr->callstack_top < 2) {
		DUK_DDPRINT("yield state invalid: callstack should contain at least 2 entries (caller and __duk__.yield)");
		goto state_error;
	}
	DUK_ASSERT((thr->callstack + thr->callstack_top - 1)->func != NULL);  /* us */
	DUK_ASSERT(DUK_HOBJECT_IS_NATIVEFUNCTION((thr->callstack + thr->callstack_top - 1)->func));
	DUK_ASSERT((thr->callstack + thr->callstack_top - 2)->func != NULL);  /* caller */

	if (!DUK_HOBJECT_IS_COMPILEDFUNCTION((thr->callstack + thr->callstack_top - 2)->func)) {
		DUK_DDPRINT("yield state invalid: caller must be Ecmascript code");
		goto state_error;
	}

	DUK_ASSERT(thr->callstack_preventcount >= 1);  /* should never be zero, because we (__duk__.yield) are on the stack */
	if (thr->callstack_preventcount != 1) {
		/* Note: the only yield-preventing call is __duk__.yield(), hence check for 1, not 0 */
		DUK_DDPRINT("yield state invalid: there must be no yield-preventing calls in current thread callstack (preventcount is %d)",
		            thr->callstack_preventcount);
		goto state_error;
	}

	/*
	 *  The error object has been augmented with a traceback and other
	 *  info from its creation point -- usually the current thread.
	 */

#ifdef DUK_USE_DEBUG
	if (is_error) {
		DUK_DDDPRINT("YIELD ERROR: value=%!T",
		             duk_get_tval(ctx, 0));
	} else {
		DUK_DDDPRINT("YIELD NORMAL: value=%!T",
		             duk_get_tval(ctx, 0));
	}
#endif

	/*
	 *  Process yield
	 *
	 *  After longjmp(), processing continues in bytecode executor longjmp
	 *  handler, which will e.g. update thr->resumer to NULL.
	 */

	thr->heap->lj.type = DUK_LJ_TYPE_YIELD;

	/* lj value1: value */
	DUK_ASSERT(thr->valstack_bottom < thr->valstack_top);
	DUK_TVAL_SET_TVAL(&tv_tmp, &thr->heap->lj.value1);
	DUK_TVAL_SET_TVAL(&thr->heap->lj.value1, &thr->valstack_bottom[0]);
	DUK_TVAL_INCREF(thr, &thr->heap->lj.value1);
	DUK_TVAL_DECREF(thr, &tv_tmp);

	thr->heap->lj.iserror = is_error;

	DUK_ASSERT(thr->heap->lj.jmpbuf_ptr != NULL);  /* call is from executor, so we know we have a jmpbuf */
	duk_err_longjmp(thr);  /* execution resumes in bytecode executor */
	return 0;  /* never here */

 state_error:
	DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, "invalid state for yield");
	return 0;  /* never here */
}

int duk_builtin_duk_object_curr(duk_context *ctx) {
	duk_push_current_thread(ctx);
	return 1;
}

int duk_builtin_duk_object_print(duk_context *ctx) {
	return DUK_RET_UNIMPLEMENTED_ERROR;	/*FIXME*/
}

int duk_builtin_duk_object_time(duk_context *ctx) {
	struct timeval tv;

	memset(&tv, 0, sizeof(tv));
	if (gettimeofday(&tv, NULL) != 0) {
		return DUK_RET_ERROR;
	}
	duk_push_number(ctx, (double) tv.tv_sec + ((double) tv.tv_usec) / 1000000.0);
	return 1;
}

int duk_builtin_duk_object_enc(duk_context *ctx) {
	duk_hthread *thr = (duk_hthread *) ctx;
	duk_hstring *h_str;

	h_str = duk_to_hstring(ctx, 0);
	if (h_str == DUK_HTHREAD_STRING_HEX(thr)) {
		duk_hex_encode(ctx, 1);
		DUK_ASSERT_TOP(ctx, 2);
		return 1;
	} else if (h_str == DUK_HTHREAD_STRING_BASE64(thr)) {
		duk_base64_encode(ctx, 1);
		DUK_ASSERT_TOP(ctx, 2);
		return 1;
	} else {
		return DUK_RET_TYPE_ERROR;
	}
}

int duk_builtin_duk_object_dec(duk_context *ctx) {
	duk_hthread *thr = (duk_hthread *) ctx;
	duk_hstring *h_str;

	h_str = duk_to_hstring(ctx, 0);
	if (h_str == DUK_HTHREAD_STRING_HEX(thr)) {
		duk_hex_decode(ctx, 1);
		DUK_ASSERT_TOP(ctx, 2);
		return 1;
	} else if (h_str == DUK_HTHREAD_STRING_BASE64(thr)) {
		duk_base64_decode(ctx, 1);
		DUK_ASSERT_TOP(ctx, 2);
		return 1;
	} else {
		return DUK_RET_TYPE_ERROR;
	}
}

int duk_builtin_duk_object_sleep(duk_context *ctx) {
	struct timeval tv;
	int msec;

	/* FIXME: signal handling */
	msec = duk_to_int(ctx, 0);
	tv.tv_sec = msec / 1000;
	tv.tv_usec = (msec % 1000) * 1000;
	(void) select(0, NULL, NULL, NULL, &tv);
	return 0;
}

#line 1 "duk_builtin_error.c"
/*
 *  Error built-ins
 */

/* include removed: duk_internal.h */

int duk_builtin_error_constructor(duk_context *ctx) {
	/* Behavior for constructor and non-constructor call is
	 * exactly the same.
	 */

	duk_push_object_helper(ctx,
	                       DUK_HOBJECT_FLAG_EXTENSIBLE |
	                       DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_ERROR),
	                       DUK_BIDX_ERROR_PROTOTYPE);

	if (!duk_is_undefined(ctx, 0)) {
		duk_to_string(ctx, 0);
		duk_dup(ctx, 0);  /* [ message error message ] */
		duk_def_prop_stridx(ctx, -2, DUK_STRIDX_MESSAGE, DUK_PROPDESC_FLAGS_WC);
	}

	return 1;
}

int duk_builtin_eval_error_constructor(duk_context *ctx) {
	return DUK_RET_UNIMPLEMENTED_ERROR;
}

int duk_builtin_range_error_constructor(duk_context *ctx) {
	return DUK_RET_UNIMPLEMENTED_ERROR;
}

int duk_builtin_reference_error_constructor(duk_context *ctx) {
	return DUK_RET_UNIMPLEMENTED_ERROR;
}

int duk_builtin_syntax_error_constructor(duk_context *ctx) {
	return DUK_RET_UNIMPLEMENTED_ERROR;
}

int duk_builtin_type_error_constructor(duk_context *ctx) {
	return DUK_RET_UNIMPLEMENTED_ERROR;
}

int duk_builtin_uri_error_constructor(duk_context *ctx) {
	return DUK_RET_UNIMPLEMENTED_ERROR;
}

int duk_builtin_error_prototype_to_string(duk_context *ctx) {
	/* FIXME: optimize with more direct internal access */

	duk_push_this(ctx);
	if (!duk_is_object(ctx, -1)) {
		goto type_error;
	}

	/* [ ... this ] */

	duk_get_prop_stridx(ctx, -1, DUK_STRIDX_NAME);
	if (duk_is_undefined(ctx, -1)) {
		duk_pop(ctx);
		duk_push_string(ctx, "Error");
	} else {
		duk_to_string(ctx, -1);
	}

	/* [ ... this name ] */

	/* FIXME: Are steps 6 and 7 in E5 Section 15.11.4.4 duplicated by
	 * accident or are they actually needed?  The first ToString()
	 * could conceivably return 'undefined'.
	 */
	duk_get_prop_stridx(ctx, -2, DUK_STRIDX_MESSAGE);
	if (duk_is_undefined(ctx, -1)) {
		duk_pop(ctx);
		duk_push_string(ctx, "");
	} else {
		duk_to_string(ctx, -1);
	}

	/* [ ... this name message ] */

	if (duk_get_length(ctx, -2) == 0) {
		/* name is empty -> return message */
		return 1;
	}
	if (duk_get_length(ctx, -1) == 0) {
		/* message is empty -> return name */
		duk_pop(ctx);
		return 1;
	}
	duk_push_string(ctx, ": ");
	duk_insert(ctx, -2);  /* ... name ': ' message */
	duk_concat(ctx, 3);

	return 1;

 type_error:
	return DUK_RET_TYPE_ERROR;
}

#line 1 "duk_builtin_function.c"
/*
 *  Function built-ins
 */

/* include removed: duk_internal.h */

int duk_builtin_function_constructor(duk_context *ctx) {
	duk_hthread *thr = (duk_hthread *) ctx;
	int num_args;
	int i;
	int comp_flags;
	duk_hcompiledfunction *func;
	duk_hobject *outer_lex_env;
	duk_hobject *outer_var_env;

	/* normal and constructor calls have identical semantics */

	num_args = duk_get_top(ctx);

	for (i = 0; i < num_args; i++) {
		duk_to_string(ctx, i);
	}

	if (num_args == 0) {
		duk_push_string(ctx, "");
		duk_push_string(ctx, "");
	} else if (num_args == 1) {
		duk_push_string(ctx, "");
	} else {
		duk_insert(ctx, 0);   /* [ arg1 ... argN-1 body] -> [body arg1 ... argN-1] */
		duk_push_string(ctx, ",");
		duk_insert(ctx, 1);
		duk_join(ctx, num_args - 1);
	}

	/* [ body formals ], formals is comma separated list that needs to be parsed */

	DUK_ASSERT_TOP(ctx, 2);

	/* FIXME: this placeholder is not always correct, but use for now.
	 * It will fail in corner cases; see test-dev-func-cons-args.js.
	 */
	duk_push_string(ctx, "function(");
	duk_dup(ctx, 1);
	duk_push_string(ctx, "){");
	duk_dup(ctx, 0);
	duk_push_string(ctx, "}");
	duk_concat(ctx, 5);

	DUK_ASSERT_TOP(ctx, 3);

	/* FIXME: uses internal API */

	/* strictness is not inherited, intentional */
	comp_flags = DUK_JS_COMPILE_FLAG_FUNCEXPR;

	duk_js_compile(thr, comp_flags);
	func = (duk_hcompiledfunction *) duk_get_hobject(ctx, -1);
	DUK_ASSERT(func != NULL);
	DUK_ASSERT(DUK_HOBJECT_IS_COMPILEDFUNCTION((duk_hobject *) func));

	/* only outer_lex_env matters, as functions always get a new
	 * variable declaration environment.
	 */

	outer_lex_env = thr->builtins[DUK_BIDX_GLOBAL_ENV];
	outer_var_env = thr->builtins[DUK_BIDX_GLOBAL_ENV];

	duk_js_push_closure(thr, func, outer_var_env, outer_lex_env);

	return 1;
}

int duk_builtin_function_prototype(duk_context *ctx) {
	/* ignore arguments, return undefined (E5 Section 15.3.4) */
	return 0;
}

int duk_builtin_function_prototype_to_string(duk_context *ctx) {
	duk_tval *tv;

	/*
	 *  E5 Section 15.3.4.2 places few requirements on the output of
	 *  this function:
	 *
	 *    - The result is an implementation dependent representation
	 *      of the function; in particular
	 *
	 *    - The result must follow the syntax of a FunctionDeclaration.
	 *      In particular, the function must have a name (even in the
	 *      case of an anonymous function or a function with an empty
	 *      name).
	 *
	 *    - Note in particular that the output does NOT need to compile
	 *      into anything useful.
	 */


	/* FIXME: faster internal way to get this */
	duk_push_this(ctx);
	tv = duk_get_tval(ctx, -1);
	DUK_ASSERT(tv != NULL);

	if (DUK_TVAL_IS_OBJECT(tv)) {
		duk_hobject *obj = DUK_TVAL_GET_OBJECT(tv);
		const char *func_name = "anonymous";

		/* FIXME: rework, it would be nice to avoid C formatting functions to
		 * ensure there are no Unicode issues.
		 */

		duk_get_prop_stridx(ctx, -1, DUK_STRIDX_NAME);
		if (!duk_is_undefined(ctx, -1)) {
			func_name = duk_to_string(ctx, -1);
			DUK_ASSERT(func_name != NULL);

			if (func_name[0] == (char) 0) {
				func_name = "empty";
			}
		}

		if (DUK_HOBJECT_HAS_COMPILEDFUNCTION(obj)) {
			/* FIXME: actual source, if available */
			duk_push_sprintf(ctx, "function %s() {/* source code */}", func_name);
		} else if (DUK_HOBJECT_HAS_NATIVEFUNCTION(obj)) {
			duk_push_sprintf(ctx, "function %s() {/* native code */}", func_name);
		} else if (DUK_HOBJECT_HAS_BOUND(obj)) {
			duk_push_sprintf(ctx, "function %s() {/* bound */}", func_name);
		} else {
			goto type_error;
		}
	} else {
		goto type_error;
	}

	return 1;

 type_error:
	return DUK_RET_TYPE_ERROR;
}

int duk_builtin_function_prototype_apply(duk_context *ctx) {
	unsigned int len;
	unsigned int i;

	/* FIXME: stack checks */

	DUK_ASSERT_TOP(ctx, 2);  /* not a vararg function */

	duk_push_this(ctx);
	if (!duk_is_callable(ctx, -1)) {
		DUK_DDDPRINT("func is not callable");
		goto type_error;
	}
	duk_insert(ctx, 0);
	DUK_ASSERT_TOP(ctx, 3);

	DUK_DDDPRINT("func=%!iT, thisArg=%!iT, argArray=%!iT",
	             duk_get_tval(ctx, 0), duk_get_tval(ctx, 1), duk_get_tval(ctx, 2));

	/* [ func thisArg argArray ] */

	if (duk_is_null_or_undefined(ctx, 2)) {
		DUK_DDDPRINT("argArray is null/undefined, no args");
		len = 0;
	} else if (!duk_is_object(ctx, 2)) {
		goto type_error;
	} else {
		DUK_DDDPRINT("argArray is an object");

		/* FIXME: make this an internal helper */
		duk_get_prop_stridx(ctx, 2, DUK_STRIDX_LENGTH);
		len = duk_to_uint32(ctx, -1);
		duk_pop(ctx);

		duk_require_stack(ctx, len);  /* FIXME: more? */

		DUK_DDDPRINT("argArray length is %d", len);
		for (i = 0; i < len; i++) {
			duk_get_prop_index(ctx, 2, i);
		}
	}
	duk_remove(ctx, 2);
	DUK_ASSERT_TOP(ctx, 2 + len);

	/* [ func thisArg arg1 ... argN ] */
	
	DUK_DDDPRINT("apply, func=%!iT, thisArg=%!iT, len=%d",
	             duk_get_tval(ctx, 0), duk_get_tval(ctx, 1), len);
	duk_call_method(ctx, len);
	return 1;

 type_error:
	return DUK_RET_TYPE_ERROR;
}

int duk_builtin_function_prototype_call(duk_context *ctx) {
	int nargs;

	/* Step 1 is not necessary because duk_call_method() will take
	 * care of it.
	 */

	/* vararg function, thisArg needs special handling */
	nargs = duk_get_top(ctx);  /* = 1 + arg count */
	if (nargs == 0) {
		duk_push_undefined(ctx);
		nargs++;
	}
	DUK_ASSERT(nargs >= 1);

	/* [ thisArg arg1 ... argN ] */

	duk_push_this(ctx);  /* 'func' in the algorithm */
	duk_insert(ctx, 0);

	/* [ func thisArg arg1 ... argN ] */

	DUK_DDDPRINT("func=%!iT, thisArg=%!iT, argcount=%d, top=%d",
	             duk_get_tval(ctx, 0), duk_get_tval(ctx, 1), nargs - 1, duk_get_top(ctx));
	duk_call_method(ctx, nargs - 1);	
	return 1;
}

/* FIXME: the implementation now assumes "chained" bound functions,
 * whereas "collapsed" bound functions (where there is ever only
 * one bound function which directly points to a non-bound, final
 * function) would require a "collapsing" implementation which
 * merges argument lists etc here.
 */
int duk_builtin_function_prototype_bind(duk_context *ctx) {
	duk_hobject *h_target;
	int nargs;
	int i;

	/* FIXME: stack checks */

	/* vararg function, careful arg handling (e.g. thisArg may not be present) */
	nargs = duk_get_top(ctx);  /* = 1 + arg count */
	if (nargs == 0) {
		duk_push_undefined(ctx);
		nargs++;
	}
	DUK_ASSERT(nargs >= 1);

	duk_push_this(ctx);
	if (!duk_is_callable(ctx, -1)) {
		DUK_DDDPRINT("func is not callable");
		goto type_error;
	}

	/* [ thisArg arg1 ... argN func ]  (thisArg+args == nargs total) */
	DUK_ASSERT_TOP(ctx, nargs + 1);

	/* create bound function object */
	duk_push_object_helper(ctx,
	                       DUK_HOBJECT_FLAG_EXTENSIBLE |
	                       DUK_HOBJECT_FLAG_BOUND |
	                       DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_FUNCTION),
	                       DUK_BIDX_FUNCTION_PROTOTYPE);

	/* FIXME: check hobject flags (e.g. strict) */

	/* [ thisArg arg1 ... argN func boundFunc ] */
	duk_dup(ctx, -2);  /* func */
	duk_def_prop_stridx(ctx, -2, DUK_STRIDX_INT_TARGET, DUK_PROPDESC_FLAGS_NONE);

	duk_dup(ctx, 0);   /* thisArg */
	duk_def_prop_stridx(ctx, -2, DUK_STRIDX_INT_THIS, DUK_PROPDESC_FLAGS_NONE);

	duk_push_array(ctx);

	/* [ thisArg arg1 ... argN func boundFunc argArray ] */

	for (i = 0; i < nargs - 1; i++) {
		duk_dup(ctx, 1 + i);
		duk_put_prop_index(ctx, -2, i);
	}
	duk_def_prop_stridx(ctx, -2, DUK_STRIDX_INT_ARGS, DUK_PROPDESC_FLAGS_NONE);

	/* [ thisArg arg1 ... argN func boundFunc ] */

	/* bound function 'length' property is interesting */
	h_target = duk_get_hobject(ctx, -2);
	DUK_ASSERT(h_target != NULL);
	if (DUK_HOBJECT_GET_CLASS_NUMBER(h_target) == DUK_HOBJECT_CLASS_FUNCTION) {
		int tmp;
		duk_get_prop_stridx(ctx, -2, DUK_STRIDX_LENGTH);
		tmp = duk_to_int(ctx, -1) - (nargs - 1);  /* step 15.a */
		duk_pop(ctx);
		duk_push_int(ctx, (tmp < 0 ? 0 : tmp));
	} else {
		duk_push_int(ctx, 0);
	}
	duk_def_prop_stridx(ctx, -2, DUK_STRIDX_LENGTH, DUK_PROPDESC_FLAGS_NONE);  /* attrs in E5 Section 15.3.5.1 */

	DUK_DDDPRINT("created bound function: %!iT", duk_get_tval(ctx, -1));

	return 1;

 type_error:
	return DUK_RET_TYPE_ERROR;
}

#line 1 "duk_builtin_global.c"
/*
 *  Global object built-ins
 */

/* include removed: duk_internal.h */

/*
 *  Encoding/decoding helpers
 */

/* Macros for creating and checking bitmasks for character encoding.
 * Bit number is a bit counterintuitive, but minimizes code size.
 */
#define  MKBITS(a,b,c,d,e,f,g,h)  ((unsigned char) ( \
	((a) << 0) | ((b) << 1) | ((c) << 2) | ((d) << 3) | \
	((e) << 4) | ((f) << 5) | ((g) << 6) | ((h) << 7) \
	))
#define  CHECK_BITMASK(table,cp)  ((table)[(cp) >> 3] & (1 << ((cp) & 0x07)))

/* E5.1 Section 15.1.3.3: uriReserved + uriUnescaped + '#' */
static unsigned char encode_uri_unescaped_table[16] = {
	MKBITS(0, 0, 0, 0, 0, 0, 0, 0), MKBITS(0, 0, 0, 0, 0, 0, 0, 0),  /* 0x00-0x0f */
	MKBITS(0, 0, 0, 0, 0, 0, 0, 0), MKBITS(0, 0, 0, 0, 0, 0, 0, 0),  /* 0x10-0x1f */
	MKBITS(0, 1, 0, 1, 1, 0, 1, 1), MKBITS(1, 1, 1, 1, 1, 1, 1, 1),  /* 0x20-0x2f */
	MKBITS(1, 1, 1, 1, 1, 1, 1, 1), MKBITS(1, 1, 1, 1, 0, 1, 0, 1),  /* 0x30-0x3f */
	MKBITS(1, 1, 1, 1, 1, 1, 1, 1), MKBITS(1, 1, 1, 1, 1, 1, 1, 1),  /* 0x40-0x4f */
	MKBITS(1, 1, 1, 1, 1, 1, 1, 1), MKBITS(1, 1, 1, 0, 0, 0, 0, 1),  /* 0x50-0x5f */
	MKBITS(0, 1, 1, 1, 1, 1, 1, 1), MKBITS(1, 1, 1, 1, 1, 1, 1, 1),  /* 0x60-0x6f */
	MKBITS(1, 1, 1, 1, 1, 1, 1, 1), MKBITS(1, 1, 1, 0, 0, 0, 1, 0),  /* 0x70-0x7f */
};

/* E5.1 Section 15.1.3.4: uriUnescaped */
static unsigned char encode_uri_component_unescaped_table[16] = {
	MKBITS(0, 0, 0, 0, 0, 0, 0, 0), MKBITS(0, 0, 0, 0, 0, 0, 0, 0),  /* 0x00-0x0f */
	MKBITS(0, 0, 0, 0, 0, 0, 0, 0), MKBITS(0, 0, 0, 0, 0, 0, 0, 0),  /* 0x10-0x1f */
	MKBITS(0, 1, 0, 0, 0, 0, 0, 1), MKBITS(1, 1, 1, 0, 0, 1, 1, 0),  /* 0x20-0x2f */
	MKBITS(1, 1, 1, 1, 1, 1, 1, 1), MKBITS(1, 1, 0, 0, 0, 0, 0, 0),  /* 0x30-0x3f */
	MKBITS(0, 1, 1, 1, 1, 1, 1, 1), MKBITS(1, 1, 1, 1, 1, 1, 1, 1),  /* 0x40-0x4f */
	MKBITS(1, 1, 1, 1, 1, 1, 1, 1), MKBITS(1, 1, 1, 0, 0, 0, 0, 1),  /* 0x50-0x5f */
	MKBITS(0, 1, 1, 1, 1, 1, 1, 1), MKBITS(1, 1, 1, 1, 1, 1, 1, 1),  /* 0x60-0x6f */
	MKBITS(1, 1, 1, 1, 1, 1, 1, 1), MKBITS(1, 1, 1, 0, 0, 0, 1, 0),  /* 0x70-0x7f */
};

/* E5.1 Section 15.1.3.1: uriReserved + '#' */
static unsigned char decode_uri_reserved_table[16] = {
	MKBITS(0, 0, 0, 0, 0, 0, 0, 0), MKBITS(0, 0, 0, 0, 0, 0, 0, 0),  /* 0x00-0x0f */
	MKBITS(0, 0, 0, 0, 0, 0, 0, 0), MKBITS(0, 0, 0, 0, 0, 0, 0, 0),  /* 0x10-0x1f */
	MKBITS(0, 0, 0, 1, 1, 0, 1, 0), MKBITS(0, 0, 0, 1, 1, 0, 0, 1),  /* 0x20-0x2f */
	MKBITS(0, 0, 0, 0, 0, 0, 0, 0), MKBITS(0, 0, 1, 1, 0, 1, 0, 1),  /* 0x30-0x3f */
	MKBITS(1, 0, 0, 0, 0, 0, 0, 0), MKBITS(0, 0, 0, 0, 0, 0, 0, 0),  /* 0x40-0x4f */
	MKBITS(0, 0, 0, 0, 0, 0, 0, 0), MKBITS(0, 0, 0, 0, 0, 0, 0, 0),  /* 0x50-0x5f */
	MKBITS(0, 0, 0, 0, 0, 0, 0, 0), MKBITS(0, 0, 0, 0, 0, 0, 0, 0),  /* 0x60-0x6f */
	MKBITS(0, 0, 0, 0, 0, 0, 0, 0), MKBITS(0, 0, 0, 0, 0, 0, 0, 0),  /* 0x70-0x7f */
};

/* E5.1 Section 15.1.3.2: empty */
static unsigned char decode_uri_component_reserved_table[16] = {
	MKBITS(0, 0, 0, 0, 0, 0, 0, 0), MKBITS(0, 0, 0, 0, 0, 0, 0, 0),  /* 0x00-0x0f */
	MKBITS(0, 0, 0, 0, 0, 0, 0, 0), MKBITS(0, 0, 0, 0, 0, 0, 0, 0),  /* 0x10-0x1f */
	MKBITS(0, 0, 0, 0, 0, 0, 0, 0), MKBITS(0, 0, 0, 0, 0, 0, 0, 0),  /* 0x20-0x2f */
	MKBITS(0, 0, 0, 0, 0, 0, 0, 0), MKBITS(0, 0, 0, 0, 0, 0, 0, 0),  /* 0x30-0x3f */
	MKBITS(0, 0, 0, 0, 0, 0, 0, 0), MKBITS(0, 0, 0, 0, 0, 0, 0, 0),  /* 0x40-0x4f */
	MKBITS(0, 0, 0, 0, 0, 0, 0, 0), MKBITS(0, 0, 0, 0, 0, 0, 0, 0),  /* 0x50-0x5f */
	MKBITS(0, 0, 0, 0, 0, 0, 0, 0), MKBITS(0, 0, 0, 0, 0, 0, 0, 0),  /* 0x60-0x6f */
	MKBITS(0, 0, 0, 0, 0, 0, 0, 0), MKBITS(0, 0, 0, 0, 0, 0, 0, 0),  /* 0x70-0x7f */
};

#ifdef DUK_USE_SECTION_B
/* E5.1 Section B.2.2, step 7. */
static unsigned char escape_unescaped_table[16] = {
	MKBITS(0, 0, 0, 0, 0, 0, 0, 0), MKBITS(0, 0, 0, 0, 0, 0, 0, 0),  /* 0x00-0x0f */
	MKBITS(0, 0, 0, 0, 0, 0, 0, 0), MKBITS(0, 0, 0, 0, 0, 0, 0, 0),  /* 0x10-0x1f */
	MKBITS(0, 0, 0, 0, 0, 0, 0, 0), MKBITS(0, 0, 1, 1, 0, 1, 1, 1),  /* 0x20-0x2f */
	MKBITS(1, 1, 1, 1, 1, 1, 1, 1), MKBITS(1, 1, 0, 0, 0, 0, 0, 0),  /* 0x30-0x3f */
	MKBITS(1, 1, 1, 1, 1, 1, 1, 1), MKBITS(1, 1, 1, 1, 1, 1, 1, 1),  /* 0x40-0x4f */
	MKBITS(1, 1, 1, 1, 1, 1, 1, 1), MKBITS(1, 1, 1, 0, 0, 0, 0, 1),  /* 0x50-0x5f */
	MKBITS(0, 1, 1, 1, 1, 1, 1, 1), MKBITS(1, 1, 1, 1, 1, 1, 1, 1),  /* 0x60-0x6f */
	MKBITS(1, 1, 1, 1, 1, 1, 1, 1), MKBITS(1, 1, 1, 0, 0, 0, 0, 0)   /* 0x70-0x7f */
};
#endif  /* DUK_USE_SECTION_B */

typedef struct {
	duk_hthread *thr;
	duk_hstring *h_str;
	duk_hbuffer_dynamic *h_buf;
	duk_u8 *p;
	duk_u8 *p_start;
	duk_u8 *p_end;
} duk_transform_context;

typedef void (*transform_callback)(duk_transform_context *tfm_ctx, void *udata, duk_u32 cp);

/* FIXME: refactor and share with other code */
static int decode_hex_escape(duk_u8 *p, int n) {
	int ch;
	int t = 0;

	while (n > 0) {
		t = t * 16;
		ch = (int) (*p++);
		if (ch >= (int) '0' && ch <= (int) '9') {
			t += ch - ((int) '0');
		} else if (ch >= (int) 'a' && ch <= (int) 'f') {
			t += ch - ((int) 'a') + 0x0a;
		} else if (ch >= (int) 'A' && ch <= (int) 'F') {
			t += ch - ((int) 'A') + 0x0a;
		} else {
			return -1;
		}
		n--;
	}
	return t;
}

static int transform_helper(duk_context *ctx, transform_callback callback, void *udata) {
	duk_hthread *thr = (duk_hthread *) ctx;
	duk_transform_context tfm_ctx_alloc;
	duk_transform_context *tfm_ctx = &tfm_ctx_alloc;
	duk_u32 cp;

	tfm_ctx->thr = thr;

	tfm_ctx->h_str = duk_to_hstring(ctx, 0);
	DUK_ASSERT(tfm_ctx->h_str != NULL);

	(void) duk_push_dynamic_buffer(ctx, 0);
	tfm_ctx->h_buf = (duk_hbuffer_dynamic *) duk_get_hbuffer(ctx, -1);
	DUK_ASSERT(tfm_ctx->h_buf != NULL);
	DUK_ASSERT(DUK_HBUFFER_HAS_DYNAMIC(tfm_ctx->h_buf));

	tfm_ctx->p_start = DUK_HSTRING_GET_DATA(tfm_ctx->h_str);
	tfm_ctx->p_end = tfm_ctx->p_start + DUK_HSTRING_GET_BYTELEN(tfm_ctx->h_str);
	tfm_ctx->p = tfm_ctx->p_start;

	while (tfm_ctx->p < tfm_ctx->p_end) {
		cp = duk_unicode_xutf8_get_u32_checked(thr, &tfm_ctx->p, tfm_ctx->p_start, tfm_ctx->p_end);
		callback(tfm_ctx, udata, cp);
	}

	duk_to_string(ctx, -1);
	return 1;
}

static void transform_callback_encode_uri(duk_transform_context *tfm_ctx, void *udata, duk_u32 cp) {
	duk_u8 xutf8_buf[DUK_UNICODE_MAX_XUTF8_LENGTH];
	duk_u8 buf[3];
	size_t len;
	duk_u32 cp1, cp2;
	int i, t;
	duk_u8 *unescaped_table = (duk_u8 *) udata;

	if ((cp < 128) && CHECK_BITMASK(unescaped_table, cp)) {
		duk_hbuffer_append_byte(tfm_ctx->thr, tfm_ctx->h_buf, (duk_u8) cp);
		return;
	} else if (cp >= 0xdc00 && cp <= 0xdfff) {
		goto uri_error;
	} else if (cp >= 0xd800 && cp <= 0xdbff) {
		/* Needs lookahead */
		if (duk_unicode_xutf8_get_u32(tfm_ctx->thr, &tfm_ctx->p, tfm_ctx->p_start, tfm_ctx->p_end, &cp2) == 0) {
			goto uri_error;
		}
		if (!(cp2 >= 0xdc00 && cp2 <= 0xdfff)) {
			goto uri_error;
		}
		cp1 = cp;
		cp = ((cp1 - 0xd800) << 10) + (cp2 - 0xdc00) + 0x10000;
	} else if (cp > 0x10ffff) {
		/* Although we can allow non-BMP characters (they'll decode
		 * back into surrogate pairs), we don't allow extended UTF-8
		 * characters; they would encode to URIs which won't decode
		 * back because of strict UTF-8 checks in URI decoding.
		 * (However, we could just as well allow them here.)
		 */
		goto uri_error;
	} else {
		/* Non-BMP characters within valid UTF-8 range: encode as is.
		 * They'll decode back into surrogate pairs.
		 */
		;
	}

	len = duk_unicode_encode_xutf8(cp, xutf8_buf);
	buf[0] = (duk_u8) '%';
	for (i = 0; i < len; i++) {
		t = (int) xutf8_buf[i];
		buf[1] = (duk_u8) duk_uc_nybbles[t >> 4];
		buf[2] = (duk_u8) duk_uc_nybbles[t & 0x0f];
		duk_hbuffer_append_bytes(tfm_ctx->thr, tfm_ctx->h_buf, buf, 3);
	}
	return;

 uri_error:
	DUK_ERROR(tfm_ctx->thr, DUK_ERR_URI_ERROR, "invalid input");
}

static void transform_callback_decode_uri(duk_transform_context *tfm_ctx, void *udata, duk_u32 cp) {
	duk_u8 *reserved_table = (duk_u8 *) udata;
	int utf8_blen;
	int min_cp;
	int t;
	int i;

	if (cp == (duk_u32) '%') {
		duk_u8 *p = tfm_ctx->p;
		size_t left = (size_t) (tfm_ctx->p_end - p);  /* bytes left */

		DUK_DDDPRINT("percent encoding, left=%d", (int) left);

		if (left < 2) {
			goto uri_error;
		}

		t = decode_hex_escape(p, 2);
		DUK_DDDPRINT("first byte: %d", t);
		if (t < 0) {
			goto uri_error;
		}

		if (t < 128) {
			if (CHECK_BITMASK(reserved_table, t)) {
				/* decode '%xx' to '%xx' if decoded char in reserved set */
				DUK_ASSERT(tfm_ctx->p - 1 >= tfm_ctx->p_start);
				duk_hbuffer_append_bytes(tfm_ctx->thr, tfm_ctx->h_buf, (duk_u8 *) (p - 1), 3);
			} else {
				duk_hbuffer_append_byte(tfm_ctx->thr, tfm_ctx->h_buf, (duk_u8) t);
			}
			tfm_ctx->p += 2;
			return;
		}

		/* Decode UTF-8 codepoint from a sequence of hex escapes.  The
		 * first byte of the sequence has been decoded to 't'.
		 *
		 * Note that UTF-8 validation must be strict according to the
		 * specification: E5.1 Section 15.1.3, decode algorithm step
		 * 4.d.vii.8.  URIError from non-shortest encodings is also
		 * specifically noted in the spec.
		 */

		DUK_ASSERT(t >= 0x80);
		if (t < 0xc0) {
			/* continuation byte */
			goto uri_error;
		} else if (t < 0xe0) {
			/* 110x xxxx; 2 bytes */
			utf8_blen = 2;
			min_cp = 0x80;
			cp = t & 0x1f;
		} else if (t < 0xf0) {
			/* 1110 xxxx; 3 bytes */
			utf8_blen = 3;
			min_cp = 0x800;
			cp = t & 0x0f;
		} else if (t < 0xf8) {
			/* 1111 0xxx; 4 bytes */
			utf8_blen = 4;
			min_cp = 0x10000;
			cp = t & 0x07;
		} else {
			/* extended utf-8 not allowed for URIs */
			goto uri_error;
		}

		if (left < utf8_blen * 3 - 1) {
			/* '%xx%xx...%xx', p points to char after first '%' */
			goto uri_error;
		}

		p += 3;
		for (i = 1; i < utf8_blen; i++) {
			/* p points to digit part ('%xy', p points to 'x') */
			t = decode_hex_escape(p, 2);
			DUK_DDDPRINT("i=%d utf8_blen=%d cp=%d t=0x%02x", i, utf8_blen, cp,t);
			if (t < 0) {
				goto uri_error;
			}
			if ((t & 0xc0) != 0x80) {
				goto uri_error;
			}
			cp = (cp << 6) + (t & 0x3f);
			p += 3;
		}
		p--;  /* p overshoots */
		tfm_ctx->p = p;

		DUK_DDDPRINT("final cp=%d, min_cp=%d", cp, min_cp);

		if (cp < min_cp || cp > 0x10ffff || (cp >= 0xd800 && cp <= 0xdfff)) {
			goto uri_error;
		}

		/* The E5.1 algorithm checks whether or not a decoded codepoint
		 * is below 0x80 and perhaps may be in the "reserved" set.
		 * This seems pointless because the single byte UTF-8 case is
		 * handled separately, and non-shortest encodings are rejected.
		 * So, 'cp' cannot be below 0x80 here, and thus cannot be in
		 * the reserved set.
		 */

		/* utf-8 validation ensures these */
		DUK_ASSERT(cp >= 0x80 && cp <= 0x10ffff);

		if (cp >= 0x10000) {
			cp -= 0x10000;
			DUK_ASSERT(cp < 0x100000);
			duk_hbuffer_append_xutf8(tfm_ctx->thr, tfm_ctx->h_buf, (cp >> 10) + 0xd800);
			duk_hbuffer_append_xutf8(tfm_ctx->thr, tfm_ctx->h_buf, (cp & 0x03ff) + 0xdc00);
		} else {
			duk_hbuffer_append_xutf8(tfm_ctx->thr, tfm_ctx->h_buf, cp);
		}
	} else {
		duk_hbuffer_append_xutf8(tfm_ctx->thr, tfm_ctx->h_buf, cp);
	}
	return;

 uri_error:
	DUK_ERROR(tfm_ctx->thr, DUK_ERR_URI_ERROR, "invalid input");
}

#ifdef DUK_USE_SECTION_B
static void transform_callback_escape(duk_transform_context *tfm_ctx, void *udata, duk_u32 cp) {
	duk_u8 buf[6];
	size_t len;

	if ((cp < 128) && CHECK_BITMASK(escape_unescaped_table, cp)) {
		buf[0] = (duk_u8) cp;
		len = 1;
	} else if (cp < 256) {
		buf[0] = (duk_u8) '%';
		buf[1] = (duk_u8) duk_uc_nybbles[cp >> 4];
		buf[2] = (duk_u8) duk_uc_nybbles[cp & 0x0f];
		len = 3;
	} else if (cp < 65536) {
		buf[0] = (duk_u8) '%';
		buf[1] = (duk_u8) 'u';
		buf[2] = (duk_u8) duk_uc_nybbles[cp >> 12];
		buf[3] = (duk_u8) duk_uc_nybbles[(cp >> 8) & 0x0f];
		buf[4] = (duk_u8) duk_uc_nybbles[(cp >> 4) & 0x0f];
		buf[5] = (duk_u8) duk_uc_nybbles[cp & 0x0f];
		len = 6;
	} else {
		/* Characters outside BMP cannot be escape()'d.  We could
		 * encode them as surrogate pairs (for codepoints inside
		 * valid UTF-8 range, but not extended UTF-8).  Because
		 * escape() and unescape() are legacy functions, we don't.
		 */
		goto esc_error;
	}

	duk_hbuffer_append_bytes(tfm_ctx->thr, tfm_ctx->h_buf, buf, len);
	return;

 esc_error:
	DUK_ERROR(tfm_ctx->thr, DUK_ERR_TYPE_ERROR, "invalid input");
}

static void transform_callback_unescape(duk_transform_context *tfm_ctx, void *udata, duk_u32 cp) {
	int t;

	if (cp == (duk_u32) '%') {
		duk_u8 *p = tfm_ctx->p;
		size_t left = (size_t) (tfm_ctx->p_end - p);  /* bytes left */

		if (left >= 5 && p[0] == 'u' &&
		    ((t = decode_hex_escape(p + 1, 4)) >= 0)) {
			cp = (duk_u32) t;
			tfm_ctx->p += 5;
		} else if (left >= 2 &&
		    ((t = decode_hex_escape(p, 2)) >= 0)) {
			cp = (duk_u32) t;
			tfm_ctx->p += 2;
		}
	}

	duk_hbuffer_append_xutf8(tfm_ctx->thr, tfm_ctx->h_buf, cp);
}

#endif  /* DUK_USE_SECTION_B */

/*
 *  Eval
 */

int duk_builtin_global_object_eval(duk_context *ctx) {
	duk_hthread *thr = (duk_hthread *) ctx;
	duk_hstring *h;
	duk_activation *act;
	duk_hcompiledfunction *func;
	duk_hobject *outer_lex_env;
	duk_hobject *outer_var_env;
	int this_to_global = 1;
	int comp_flags;

	DUK_ASSERT_TOP(ctx, 1);

	if (thr->callstack_top < 2) {
		/* callstack_top - 1 --> this function
		 * callstack_top - 2 --> caller
		 *
		 * If called directly from C, callstack_top might be 1.
		 * We don't support that now.
		 */
		return DUK_RET_TYPE_ERROR;
	}
	DUK_ASSERT(thr->callstack_top >= 2);  /* caller and this function */

	h = duk_get_hstring(ctx, 0);
	if (!h) {
		return 1;  /* return arg as-is */
	}

	/* FIXME: uses internal API */

	comp_flags = DUK_JS_COMPILE_FLAG_EVAL;
	act = thr->callstack + thr->callstack_top - 2;  /* caller */
	if (act->flags & DUK_ACT_FLAG_STRICT) {
		comp_flags |= DUK_JS_COMPILE_FLAG_STRICT;
	}

	duk_js_compile(thr, comp_flags);
	func = (duk_hcompiledfunction *) duk_get_hobject(ctx, -1);
	DUK_ASSERT(func != NULL);
	DUK_ASSERT(DUK_HOBJECT_IS_COMPILEDFUNCTION((duk_hobject *) func));

	/* E5 Section 10.4.2 */
	DUK_ASSERT(thr->callstack_top >= 2);
	act = thr->callstack + thr->callstack_top - 1;  /* this function */
	if (act->flags & DUK_ACT_FLAG_DIRECT_EVAL) {	
		act = thr->callstack + thr->callstack_top - 2;  /* caller */
		if (act->lex_env == NULL) {
			DUK_DDDPRINT("delayed environment initialization");

			/* this may have side effects, so re-lookup act */
			duk_js_init_activation_environment_records_delayed(thr, act);
			act = thr->callstack + thr->callstack_top - 2;
		}
		DUK_ASSERT(act->lex_env != NULL);
		DUK_ASSERT(act->var_env != NULL);

		this_to_global = 0;

		if (DUK_HOBJECT_HAS_STRICT((duk_hobject *) func)) {
			duk_hobject *new_env;

			DUK_DDDPRINT("direct eval call to a strict function -> "
			             "var_env and lex_env to a fresh env, "
			             "this_binding to caller's this_binding");

			(void) duk_push_object_helper(ctx,
			                              DUK_HOBJECT_FLAG_EXTENSIBLE |
			                              DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_DECENV),
			                              -1);  /* no prototype, updated below */
			new_env = duk_require_hobject(ctx, -1);
			DUK_ASSERT(new_env != NULL);
			DUK_DDDPRINT("new_env allocated: %!iO", new_env);

			act = thr->callstack + thr->callstack_top - 2;  /* caller */
			DUK_HOBJECT_SET_PROTOTYPE(thr, new_env, act->lex_env);  /* updates refcounts */
			act = NULL;  /* invalidated */

			outer_lex_env = new_env;
			outer_var_env = new_env;

			duk_insert(ctx, 0);  /* stash to bottom of value stack to keep new_env reachable */

			/* compiler's responsibility */
			DUK_ASSERT(DUK_HOBJECT_HAS_NEWENV((duk_hobject *) func));
		} else {
			DUK_DDDPRINT("direct eval call to a non-strict function -> "
			             "var_env and lex_env to caller's envs, "
			             "this_binding to caller's this_binding");

			outer_lex_env = act->lex_env;
			outer_var_env = act->var_env;

			/* compiler's responsibility */
			DUK_ASSERT(!DUK_HOBJECT_HAS_NEWENV((duk_hobject *) func));
		}
	} else {
		DUK_DDDPRINT("indirect eval call -> var_env and lex_env to "
		             "global object, this_binding to global object");

		this_to_global = 1;
		outer_lex_env = thr->builtins[DUK_BIDX_GLOBAL_ENV];
		outer_var_env = thr->builtins[DUK_BIDX_GLOBAL_ENV];
	}
	act = NULL;

	duk_js_push_closure(thr, func, outer_var_env, outer_lex_env);

	if (this_to_global) {
		DUK_ASSERT(thr->builtins[DUK_BIDX_GLOBAL] != NULL);
		duk_push_hobject(ctx, thr->builtins[DUK_BIDX_GLOBAL]);
	} else {
		duk_tval *tv;
		DUK_ASSERT(thr->callstack_top >= 2);
		act = thr->callstack + thr->callstack_top - 2;  /* caller */
		tv = thr->valstack + act->idx_bottom - 1;  /* this is just beneath bottom */
		DUK_ASSERT(tv >= thr->valstack);
		duk_push_tval(ctx, tv);
	}

	DUK_DDDPRINT("eval -> lex_env=%!iO, var_env=%!iO, this_binding=%!T",
	             outer_lex_env, outer_var_env, duk_get_tval(ctx, -1));

	duk_call_method(ctx, 0);

	return 1;
}

/*
 *  Parsing of ints and floats
 */

int duk_builtin_global_object_parse_int(duk_context *ctx) {
	int strip_prefix;
	duk_i32 radix;
	int s2n_flags;

	DUK_ASSERT_TOP(ctx, 2);
	duk_to_string(ctx, 0);

	strip_prefix = 1;
	radix = duk_to_int32(ctx, 1);
	if (radix != 0) {
		if (radix < 2 || radix > 36) {
			goto ret_nan;
		}
		/* FIXME: how should octal behave here? */
		if (radix != 16) {
			strip_prefix = 0;
		}
	} else {
		radix = 10;
	}

	s2n_flags = DUK_S2N_FLAG_TRIM_WHITE |
	            DUK_S2N_FLAG_ALLOW_GARBAGE |
	            DUK_S2N_FLAG_ALLOW_PLUS |
	            DUK_S2N_FLAG_ALLOW_MINUS |
	            DUK_S2N_FLAG_ALLOW_LEADING_ZERO |
#ifdef DUK_USE_OCTAL_SUPPORT
	            (strip_prefix ? DUK_S2N_FLAG_ALLOW_AUTO_OCT_INT : 0) |
#endif
	            (strip_prefix ? DUK_S2N_FLAG_ALLOW_AUTO_HEX_INT : 0);

	duk_dup(ctx, 0);
	duk_numconv_parse(ctx, radix, s2n_flags);
	return 1;

 ret_nan:
	duk_push_nan(ctx);
	return 1;
}

int duk_builtin_global_object_parse_float(duk_context *ctx) {
	int s2n_flags;

	DUK_ASSERT_TOP(ctx, 1);
	duk_to_string(ctx, 0);

	/* FIXME: flags */
	s2n_flags = DUK_S2N_FLAG_TRIM_WHITE |
	            DUK_S2N_FLAG_ALLOW_EXP |
	            DUK_S2N_FLAG_ALLOW_GARBAGE |
	            DUK_S2N_FLAG_ALLOW_PLUS |
	            DUK_S2N_FLAG_ALLOW_MINUS |
	            DUK_S2N_FLAG_ALLOW_INF |
	            DUK_S2N_FLAG_ALLOW_FRAC |
	            DUK_S2N_FLAG_ALLOW_NAKED_FRAC |
	            DUK_S2N_FLAG_ALLOW_EMPTY_FRAC |
	            DUK_S2N_FLAG_ALLOW_LEADING_ZERO;

	duk_numconv_parse(ctx, 10 /*radix*/, s2n_flags);
	return 1;
}

/*
 *  Number checkers
 */
int duk_builtin_global_object_is_nan(duk_context *ctx) {
	double d = duk_to_number(ctx, 0);
	duk_push_boolean(ctx, isnan(d));
	return 1;
}

int duk_builtin_global_object_is_finite(duk_context *ctx) {
	double d = duk_to_number(ctx, 0);
	duk_push_boolean(ctx, isfinite(d));
	return 1;
}

/*
 *  URI handling
 */

int duk_builtin_global_object_decode_uri(duk_context *ctx) {
	return transform_helper(ctx, transform_callback_decode_uri, (void *) decode_uri_reserved_table);
}

int duk_builtin_global_object_decode_uri_component(duk_context *ctx) {
	return transform_helper(ctx, transform_callback_decode_uri, (void *) decode_uri_component_reserved_table);
}

int duk_builtin_global_object_encode_uri(duk_context *ctx) {
	return transform_helper(ctx, transform_callback_encode_uri, (void *) encode_uri_unescaped_table);
}

int duk_builtin_global_object_encode_uri_component(duk_context *ctx) {
	return transform_helper(ctx, transform_callback_encode_uri, (void *) encode_uri_component_unescaped_table);
}

#ifdef DUK_USE_SECTION_B
int duk_builtin_global_object_escape(duk_context *ctx) {
	return transform_helper(ctx, transform_callback_escape, (void *) NULL);
}

int duk_builtin_global_object_unescape(duk_context *ctx) {
	return transform_helper(ctx, transform_callback_unescape, (void *) NULL);
}
#endif

#ifdef DUK_USE_BROWSER_LIKE
static int print_alert_helper(duk_context *ctx, FILE *f_out) {
	int nargs;
	int i;
	const char *str;
	size_t len;
	char nl = '\n';

	/* If argument count is 1 and first argument is a buffer, write the buffer
	 * as raw data into the file without a newline; this allows exact control
	 * over stdout/stderr without an additional entrypoint (useful for now).
	 */

	nargs = duk_get_top(ctx);
	if (nargs == 1 && duk_is_buffer(ctx, 0)) {
		const char *buf = NULL;
		size_t sz = 0;
		buf = duk_get_buffer(ctx, 0, &sz);
		if (buf && sz > 0) {
			fwrite(buf, 1, sz, f_out);
		}
		goto flush;
	}

	/* FIXME: best semantics link?  Now apply ToString to args, join with ' ' */
	/* FIXME: ToString() coerce inplace instead? */

	if (nargs > 0) {
		for (i = 0; i < nargs; i++) {
			if (i != 0) {
				duk_push_hstring_stridx(ctx, DUK_STRIDX_SPACE);
			}
			duk_dup(ctx, i);
			duk_to_string(ctx, -1);
		}

		duk_concat(ctx, 2*nargs - 1);

		str = duk_get_lstring(ctx, -1, &len);
		if (str) {
			fwrite(str, 1, len, f_out);
		}
	}

	fwrite(&nl, 1, 1, f_out);

 flush:
	fflush(f_out);
	return 0;

}

int duk_builtin_global_object_print(duk_context *ctx) {
	return print_alert_helper(ctx, stdout);
}

int duk_builtin_global_object_alert(duk_context *ctx) {
	return print_alert_helper(ctx, stderr);
}
#endif  /* DUK_USE_BROWSER_LIKE */


#line 1 "duk_builtin_json.c"
/*
 *  JSON built-ins.
 *
 *  See doc/json.txt.
 */

/* include removed: duk_internal.h */

/*
 *  Local defines and forward declarations.
 */

static void json_dec_syntax_error(duk_json_dec_ctx *js_ctx);
static void json_dec_eat_white(duk_json_dec_ctx *js_ctx);
static int json_dec_peek(duk_json_dec_ctx *js_ctx);
static int json_dec_get(duk_json_dec_ctx *js_ctx);
static int json_dec_get_nonwhite(duk_json_dec_ctx *js_ctx);
static duk_u32 json_dec_decode_hex_escape(duk_json_dec_ctx *js_ctx, int n);
static void json_dec_req_stridx(duk_json_dec_ctx *js_ctx, int stridx);
static void json_dec_string(duk_json_dec_ctx *js_ctx);
static void json_dec_number(duk_json_dec_ctx *js_ctx);
static void json_dec_objarr_shared_entry(duk_json_dec_ctx *js_ctx);
static void json_dec_objarr_shared_exit(duk_json_dec_ctx *js_ctx);
static void json_dec_object(duk_json_dec_ctx *js_ctx);
static void json_dec_array(duk_json_dec_ctx *js_ctx);
static void json_dec_value(duk_json_dec_ctx *js_ctx);
static void json_dec_reviver_walk(duk_json_dec_ctx *js_ctx);

static void json_emit_1(duk_json_enc_ctx *js_ctx, char ch);
static void json_emit_2(duk_json_enc_ctx *js_ctx, int chars);
static void json_emit_esc(duk_json_enc_ctx *js_ctx, duk_u32 cp, char *esc_str, int digits);
static void json_emit_esc16(duk_json_enc_ctx *js_ctx, duk_u32 cp);
static void json_emit_esc32(duk_json_enc_ctx *js_ctx, duk_u32 cp);
static void json_emit_xutf8(duk_json_enc_ctx *js_ctx, duk_u32 cp);
static void json_emit_hstring(duk_json_enc_ctx *js_ctx, duk_hstring *h);
static void json_emit_cstring(duk_json_enc_ctx *js_ctx, const char *p);
static int json_enc_key_quotes_needed(duk_hstring *h_key);
static void json_enc_quote_string(duk_json_enc_ctx *js_ctx, duk_hstring *h_str);
static void json_enc_objarr_shared_entry(duk_json_enc_ctx *js_ctx, duk_hstring **h_stepback, duk_hstring **h_indent, int *entry_top);
static void json_enc_objarr_shared_exit(duk_json_enc_ctx *js_ctx, duk_hstring **h_stepback, duk_hstring **h_indent, int *entry_top);
static void json_enc_object(duk_json_enc_ctx *js_ctx);
static void json_enc_array(duk_json_enc_ctx *js_ctx);
static int json_enc_value1(duk_json_enc_ctx *js_ctx, int idx_holder);
static void json_enc_value2(duk_json_enc_ctx *js_ctx);
static int json_enc_allow_into_proplist(duk_tval *tv);

/*
 *  Parsing implementation.
 *
 *  JSON lexer is now separate from duk_lexer.c because there are numerous
 *  small differences making it difficult to share the lexer.
 *
 *  The parser here works with raw bytes directly; this works because all
 *  JSON delimiters are ASCII characters.  Invalid xUTF-8 encoded values
 *  inside strings will be passed on without normalization; this is not a
 *  compliance concern because compliant inputs will always be valid
 *  CESU-8 encodings.
 */

static void json_dec_syntax_error(duk_json_dec_ctx *js_ctx) {
	/* Shared handler to minimize parser size.  Cause will be
	 * hidden, unfortunately.
	 */
	DUK_ERROR(js_ctx->thr, DUK_ERR_SYNTAX_ERROR, "invalid json");
}

static void json_dec_eat_white(duk_json_dec_ctx *js_ctx) {
	int t;
	for (;;) {
		if (js_ctx->p >= js_ctx->p_end) {
			break;
		}
		t = (int) (*js_ctx->p);
		if (!(t == 0x20 || t == 0x0a || t == 0x0d || t == 0x09)) {
			break;
		}
		js_ctx->p++;
	}
}

static int json_dec_peek(duk_json_dec_ctx *js_ctx) {
	if (js_ctx->p >= js_ctx->p_end) {
		return -1;
	} else {
		return (int) (*js_ctx->p);
	}
}

static int json_dec_get(duk_json_dec_ctx *js_ctx) {
	/* FIXME: multiple EOFs will now be supplied to the caller.  This could also
	 * be changed so that reading the second EOF would cause an error automatically.
	 */
	if (js_ctx->p >= js_ctx->p_end) {
		return -1;
	} else {
		return (int) (*js_ctx->p++);
	}
}

static int json_dec_get_nonwhite(duk_json_dec_ctx *js_ctx) {
	json_dec_eat_white(js_ctx);
	return json_dec_get(js_ctx);
}

static duk_u32 json_dec_decode_hex_escape(duk_json_dec_ctx *js_ctx, int n) {
	int i;
	duk_u32 res = 0;
	int x;

	for (i = 0; i < n; i++) {
		/* FIXME: share helper from lexer; duk_lexer.c / hexval(). */

		x = json_dec_get(js_ctx);

		DUK_DDDPRINT("decode_hex_escape: i=%d, n=%d, res=%d, x=%d",
		             i, n, (int) res, x);

		res *= 16;
		if (x >= (int) '0' && x <= (int) '9') {
			res += x - (int) '0';
		} else if (x >= 'a' && x <= 'f') {
			res += x - (int) 'a' + 0x0a;
		} else if (x >= 'A' && x <= 'F') {
			res += x - (int) 'A' + 0x0a;
		} else {
			/* catches EOF */
			goto syntax_error;
		}
	}

	DUK_DDDPRINT("final hex decoded value: %d", (int) res);
	return res;

 syntax_error:
	json_dec_syntax_error(js_ctx);
	DUK_NEVER_HERE();
	return 0;
}

static void json_dec_req_stridx(duk_json_dec_ctx *js_ctx, int stridx) {
	duk_hstring *h;
	duk_u8 *p;
	duk_u8 *p_end;
	int x;

	/* First character has already been eaten and checked by the
	 * caller.
	 */

	DUK_ASSERT(stridx >= 0 && stridx < DUK_HEAP_NUM_STRINGS);
	h = DUK_HTHREAD_GET_STRING(js_ctx->thr, stridx);
	DUK_ASSERT(h != NULL);

	p = (duk_u8 *) DUK_HSTRING_GET_DATA(h);
	p_end = ((duk_u8 *) DUK_HSTRING_GET_DATA(h)) +
	        DUK_HSTRING_GET_BYTELEN(h);

	DUK_ASSERT((int) *(js_ctx->p - 1) == (int) *p);
	p++;  /* first char */

	while (p < p_end) {
		x = json_dec_get(js_ctx);
		if ((int) (*p) != x) {
			/* catches EOF */
			goto syntax_error;
		}
		p++;
	}

	return;

 syntax_error:
	json_dec_syntax_error(js_ctx);
	DUK_NEVER_HERE();
}

static void json_dec_string(duk_json_dec_ctx *js_ctx) {
	duk_hthread *thr = js_ctx->thr;
	duk_context *ctx = (duk_context *) thr;
	duk_hbuffer_dynamic *h_buf;
	int x;

	/* '"' was eaten by caller */

	/* Note that we currently parse -bytes-, not codepoints.
	 * All non-ASCII extended UTF-8 will encode to bytes >= 0x80,
	 * so they'll simply pass through (valid UTF-8 or not).
	 */

	duk_push_dynamic_buffer(ctx, 0);
	h_buf = (duk_hbuffer_dynamic *) duk_get_hbuffer(ctx, -1);
	DUK_ASSERT(h_buf != NULL);
	DUK_ASSERT(DUK_HBUFFER_HAS_DYNAMIC(h_buf));

	for (;;) {
		x = json_dec_get(js_ctx);
		if (x == (int) '"') {
			break;
		} else if (x == (int) '\\') {
			x = json_dec_get(js_ctx);
			switch (x) {
			case '\\': break;
			case '"': break;
			case '/': break;
			case 't': x = 0x09; break;
			case 'n': x = 0x0a; break;
			case 'r': x = 0x0d; break;
			case 'f': x = 0x0c; break;
			case 'b': x = 0x08; break;
			case 'u': {
				x = json_dec_decode_hex_escape(js_ctx, 4);
				break;
			}
#if 0  /* FIXME: custom formats */
			case 'U': {
				if (0) {
					x = json_dec_decode_hex_escape(js_ctx, 8);
				} else {
					goto syntax_error;
				}
				break;
			}
			case 'x': {
				if (0) {
					x = json_dec_decode_hex_escape(js_ctx, 2);
				} else {
					goto syntax_error;
				}
				break;
			}
#endif
			default:
				goto syntax_error;
			}
			duk_hbuffer_append_xutf8(thr, h_buf, (duk_u32) x);
		} else if (x < 0x20) {
			/* catches EOF (-1) */
			goto syntax_error;
		} else {
			duk_hbuffer_append_byte(thr, h_buf, (duk_u8) x);
		}
	}

	duk_to_string(ctx, -1);

	/* [ ... str ] */

	return;

 syntax_error:
	json_dec_syntax_error(js_ctx);
	DUK_NEVER_HERE();
}

static void json_dec_number(duk_json_dec_ctx *js_ctx) {
	duk_context *ctx = (duk_context *) js_ctx->thr;
	duk_u8 *p_start;
	int x;
	int s2n_flags;

	DUK_DDDPRINT("parse_number");

	/* Caller has already eaten the first character so backtrack one
	 * byte.  This is correct because the first character is either
	 * '-' or a digit (i.e. an ASCII character).
	 */

	js_ctx->p--;  /* safe */
	p_start = js_ctx->p;

	/* FIXME: this is an approximate parses and way too lenient
	 * (will e.g. parse "1.2.3").  Fix when actual number parsing is
	 * added, and ensure that the number parse can be made to obey
	 * the JSON restrictions.
	 */

	for (;;) {
		x = json_dec_peek(js_ctx);

		DUK_DDDPRINT("parse_number: p_start=%p, p=%p, p_end=%p, x=%d",
		             (void *) p_start, (void *) js_ctx->p,
		             (void *) js_ctx->p_end, x);

		if (!((x >= (int) '0' && x <= (int) '9') ||
		      (x == '.' || x == 'e' || x == 'E' || x == '-'))) {
			break;
		}

		js_ctx->p++;  /* safe, because matched char */
	}

	DUK_ASSERT(js_ctx->p > p_start);
	duk_push_lstring(ctx, (const char *) p_start, (size_t) (js_ctx->p - p_start));

	s2n_flags = DUK_S2N_FLAG_ALLOW_EXP |
	            DUK_S2N_FLAG_ALLOW_MINUS |  /* but don't allow leading plus */
	            DUK_S2N_FLAG_ALLOW_FRAC;

	DUK_DDDPRINT("parse_number: string before parsing: %!T", duk_get_tval(ctx, -1));
	duk_numconv_parse(ctx, 10 /*radix*/, s2n_flags);
	if (duk_is_nan(ctx, -1)) {
		/* FIXME: retcode parse error indicator? */
		DUK_ERROR(js_ctx->thr, DUK_ERR_SYNTAX_ERROR, "invalid number");
	}
	DUK_ASSERT(duk_is_number(ctx, -1));
	DUK_DDDPRINT("parse_number: final number: %!T", duk_get_tval(ctx, -1));

	/* [ ... num ] */
}

static void json_dec_objarr_shared_entry(duk_json_dec_ctx *js_ctx) {
	duk_context *ctx = (duk_context *) js_ctx->thr;
	duk_require_stack(ctx, DUK_JSON_DEC_REQSTACK);

	/* c recursion check */

	DUK_ASSERT(js_ctx->recursion_depth >= 0);
	DUK_ASSERT(js_ctx->recursion_depth <= js_ctx->recursion_limit);
	if (js_ctx->recursion_depth >= js_ctx->recursion_limit) {
		DUK_ERROR((duk_hthread *) ctx, DUK_ERR_INTERNAL_ERROR, "recursion limit");
	}
	js_ctx->recursion_depth++;
}

static void json_dec_objarr_shared_exit(duk_json_dec_ctx *js_ctx) {
	/* c recursion check */

	DUK_ASSERT(js_ctx->recursion_depth > 0);
	DUK_ASSERT(js_ctx->recursion_depth <= js_ctx->recursion_limit);
	js_ctx->recursion_depth--;
}

static void json_dec_object(duk_json_dec_ctx *js_ctx) {
	duk_context *ctx = (duk_context *) js_ctx->thr;
	int key_count;
	int x;

	DUK_DDDPRINT("parse_object");

	json_dec_objarr_shared_entry(js_ctx);

	duk_push_object(ctx);

	/* Initial '{' has been checked and eaten by caller. */

	key_count = 0;
	for (;;) {
		x = json_dec_get_nonwhite(js_ctx);

		DUK_DDDPRINT("parse_object: obj=%!T, x=%d, key_count=%d",
		           duk_get_tval(ctx, -1), x, key_count);

		/* handle comma and closing brace */

		if (x == (int) ',' && key_count > 0) {
			/* accept comma, expect new value */
			x = json_dec_get_nonwhite(js_ctx);
		} else if (x == (int) '}') {
			/* eat closing brace */
			break;
		} else if (key_count == 0) {
			/* accept anything, expect first value (EOF will be
			 * caught by key parsing below.
			 */
			;
		} else {
			/* catches EOF (and initial comma) */
			goto syntax_error;
		}

		/* parse key and value */

		if (x == (int) '"') {
			json_dec_string(js_ctx);
#if 0  /* FIXME: custom format */
		} else if (0 && ((x >= (int) 'a' && x <= (int) 'z') ||
		                 (x >= (int) 'A' && x <= (int) 'Z') ||
		                 (x == (int) '$' && x == (int) '_'))) {
			/* plain key */
			goto syntax_error;  /* FIXME */
#endif
		} else {
			goto syntax_error;
		}

		/* [ ... obj key ] */

		x = json_dec_get_nonwhite(js_ctx);
		if (x != (int) ':') {
			goto syntax_error;
		}

		json_dec_value(js_ctx);

		/* [ ... obj key val ] */

		duk_put_prop(ctx, -3);

		/* [ ... obj ] */

		key_count++;
	}

	/* [ ... obj ] */

	DUK_DDDPRINT("parse_object: final object is %!T", duk_get_tval(ctx, -1));

	json_dec_objarr_shared_exit(js_ctx);
	return;

 syntax_error:
	json_dec_syntax_error(js_ctx);
	DUK_NEVER_HERE();
}

static void json_dec_array(duk_json_dec_ctx *js_ctx) {
	duk_context *ctx = (duk_context *) js_ctx->thr;
	int arr_idx;
	int x;

	DUK_DDDPRINT("parse_array");

	json_dec_objarr_shared_entry(js_ctx);

	duk_push_array(ctx);

	/* Initial '[' has been checked and eaten by caller. */

	arr_idx = 0;
	for (;;) {
		x = json_dec_get_nonwhite(js_ctx);

		DUK_DDDPRINT("parse_array: arr=%!T, x=%d, arr_idx=%d",
		             duk_get_tval(ctx, -1), x, arr_idx);

		/* handle comma and closing bracket */

		if ((x == ',') && (arr_idx != 0)) {
			/* accept comma, expect new value */
			;
		} else if (x == (int) ']') {
			/* eat closing bracket */
			break;
		} else if (arr_idx == 0) {
			/* accept anything, expect first value (EOF will be
			 * caught by json_dec_value() below.
			 */
			js_ctx->p--;  /* backtrack (safe) */
		} else {
			/* catches EOF (and initial comma) */
			goto syntax_error;
		}

		/* parse value */

		json_dec_value(js_ctx);

		/* [ ... arr val ] */

		duk_put_prop_index(ctx, -2, arr_idx);
		arr_idx++;
	}

	/* [ ... arr ] */

	DUK_DDDPRINT("parse_array: final array is %!T", duk_get_tval(ctx, -1));

	json_dec_objarr_shared_exit(js_ctx);
	return;

 syntax_error:
	json_dec_syntax_error(js_ctx);
	DUK_NEVER_HERE();
}

static void json_dec_value(duk_json_dec_ctx *js_ctx) {
	duk_context *ctx = (duk_context *) js_ctx->thr;
	int x;

	x = json_dec_get_nonwhite(js_ctx);

	DUK_DDDPRINT("parse_value: initial x=%d", x);

	if (x == (int) '"') {
		json_dec_string(js_ctx);
	} else if ((x >= (int) '0' && x <= (int) '9') || (x == (int) '-')) {
#if 0  /* FIXME: custom format */
		if (XXX && json_dec_peek(js_ctx) == 'I') {
			/* parse -Infinity */
		} else {
			/* parse number */
		}
#endif
		/* We already ate 'x', so json_dec_number() will back up one byte. */
		json_dec_number(js_ctx);
	} else if (x == 't') {
		json_dec_req_stridx(js_ctx, DUK_STRIDX_TRUE);
		duk_push_true(ctx);
	} else if (x == 'f') {
		json_dec_req_stridx(js_ctx, DUK_STRIDX_FALSE);
		duk_push_false(ctx);
	} else if (x == 'n') {
		json_dec_req_stridx(js_ctx, DUK_STRIDX_NULL);
		duk_push_null(ctx);
#if 0  /* FIXME: custom format */
	} else if (XXX && x == 'u') {
		/* parse undefined */
	} else if (XXX && x == 'N') {
		/* parse NaN */
	} else if (XXX && x == 'I') {
		/* parse Infinity */
#endif
	} else if (x == '{') {
		json_dec_object(js_ctx);
	} else if (x == '[') {
		json_dec_array(js_ctx);
	} else {
		/* catches EOF */
		goto syntax_error;
	}

	json_dec_eat_white(js_ctx);

	/* [ ... val ] */
	return;

 syntax_error:
	json_dec_syntax_error(js_ctx);
	DUK_NEVER_HERE();
}

/* Recursive value reviver, implements the Walk() algorithm.  No C recursion
 * check is done here because the initial parsing step will already ensure
 * there is a reasonable limit on C recursion depth and hence object depth.
 */
static void json_dec_reviver_walk(duk_json_dec_ctx *js_ctx) {
	duk_context *ctx = (duk_context *) js_ctx->thr;
	duk_hobject *h;
	unsigned int i;  /* FIXME: type */
	unsigned int arr_len;  /* FIXME: type */

	DUK_DDDPRINT("walk: top=%d, holder=%!T, name=%!T",
	             duk_get_top(ctx), duk_get_tval(ctx, -2), duk_get_tval(ctx, -1));

	duk_dup_top(ctx);
	duk_get_prop(ctx, -3);  /* -> [ ... holder name val ] */

	h = duk_get_hobject(ctx, -1);
	if (h != NULL) {
		if (DUK_HOBJECT_GET_CLASS_NUMBER(h) == DUK_HOBJECT_CLASS_ARRAY) {
			arr_len = duk_get_length(ctx, -1);
			for (i = 0; i < arr_len; i++) {
				/* [ ... holder name val ] */

				DUK_DDDPRINT("walk: array, top=%d, i=%d, arr_len=%d, holder=%!T, name=%!T, val=%!T",
				             duk_get_top(ctx), i, arr_len, duk_get_tval(ctx, -3),
				             duk_get_tval(ctx, -2), duk_get_tval(ctx, -1));

				/* FIXME: push_uint_string / push_u32_string */
				duk_dup_top(ctx);
				duk_push_number(ctx, (double) i);
				duk_to_string(ctx, -1);  /* -> [ ... holder name val val ToString(i) ] */
				json_dec_reviver_walk(js_ctx);  /* -> [ ... holder name val new_elem ] */

				if (duk_is_undefined(ctx, -1)) {
					duk_pop(ctx);
					duk_del_prop_index(ctx, -1, i);
				} else {
					duk_put_prop_index(ctx, -2, i);
				}
			}
		} else {
			/* [ ... holder name val ] */
			duk_enum(ctx, -1, DUK_ENUM_OWN_PROPERTIES_ONLY /*flags*/);
			while (duk_next(ctx, -1 /*enum_index*/, 0 /*get_value*/)) {
				DUK_DDDPRINT("walk: object, top=%d, holder=%!T, name=%!T, val=%!T, enum=%!iT, obj_key=%!T",
				             duk_get_top(ctx), duk_get_tval(ctx, -5),
				             duk_get_tval(ctx, -4), duk_get_tval(ctx, -3),
				             duk_get_tval(ctx, -2), duk_get_tval(ctx, -1));

				/* [ ... holder name val enum obj_key ] */
				duk_dup(ctx, -3);
				duk_dup(ctx, -2);

				/* [ ... holder name val enum obj_key val obj_key ] */
				json_dec_reviver_walk(js_ctx);

				/* [ ... holder name val enum obj_key new_elem ] */
				if (duk_is_undefined(ctx, -1)) {
					duk_pop(ctx);
					duk_del_prop(ctx, -3);
				} else {
					duk_put_prop(ctx, -4);
				}
			}
			duk_pop(ctx);  /* pop enum */
		}
	}

	/* [ ... holder name val ] */

	duk_dup(ctx, js_ctx->idx_reviver);
	duk_insert(ctx, -4);  /* -> [ ... reviver holder name val ] */
	duk_call_method(ctx, 2);  /* -> [ ... res ] */

	DUK_DDDPRINT("walk: top=%d, result=%!T", duk_get_top(ctx), duk_get_tval(ctx, -1));
}

/*
 *  Stringify implementation.
 */

#define  EMIT_1(js_ctx,ch)       json_emit_1((js_ctx),(ch))
#define  EMIT_2(js_ctx,ch1,ch2)  json_emit_2((js_ctx),(((int)(ch1)) << 8) + (int)(ch2))
#define  EMIT_ESC16(js_ctx,cp)   json_emit_esc16((js_ctx),(cp))
#define  EMIT_ESC32(js_ctx,cp)   json_emit_esc32((js_ctx),(cp))
#define  EMIT_XUTF8(js_ctx,cp)   json_emit_xutf8((js_ctx),(cp))
#define  EMIT_HSTR(js_ctx,h)     json_emit_hstring((js_ctx),(h))
#define  EMIT_CSTR(js_ctx,p)     json_emit_cstring((js_ctx),(p))
#define  EMIT_STRIDX(js_ctx,i)   json_emit_stridx((js_ctx),(i))

static void json_emit_1(duk_json_enc_ctx *js_ctx, char ch) {
	duk_hbuffer_append_byte(js_ctx->thr, js_ctx->h_buf, (duk_u8) ch);
}

static void json_emit_2(duk_json_enc_ctx *js_ctx, int chars) {
	duk_u8 buf[2];
	buf[0] = (chars >> 8);
	buf[1] = chars & 0xff;
	duk_hbuffer_append_bytes(js_ctx->thr, js_ctx->h_buf, (duk_u8 *) buf, 2);
}

static void json_emit_esc(duk_json_enc_ctx *js_ctx, duk_u32 cp, char *esc_str, int digits) {
	int dig;

	duk_hbuffer_append_cstring(js_ctx->thr, js_ctx->h_buf, esc_str);

	while (digits > 0) {
		digits--;
		dig = (cp >> (4 * digits)) & 0x0f;
		duk_hbuffer_append_byte(js_ctx->thr, js_ctx->h_buf, duk_lc_digits[dig]);
	}
}

static void json_emit_esc16(duk_json_enc_ctx *js_ctx, duk_u32 cp) {
	json_emit_esc(js_ctx, cp, "\\u", 4);
}

static void json_emit_esc32(duk_json_enc_ctx *js_ctx, duk_u32 cp) {
	/* custom format */
	json_emit_esc(js_ctx, cp, "\\U", 8);
}

static void json_emit_xutf8(duk_json_enc_ctx *js_ctx, duk_u32 cp) {
	(void) duk_hbuffer_append_xutf8(js_ctx->thr, js_ctx->h_buf, cp);
}

static void json_emit_hstring(duk_json_enc_ctx *js_ctx, duk_hstring *h) {
	DUK_ASSERT(h != NULL);
	duk_hbuffer_append_bytes(js_ctx->thr,
	                         js_ctx->h_buf,
	                         (duk_u8 *) DUK_HSTRING_GET_DATA(h),
	                         (size_t) DUK_HSTRING_GET_BYTELEN(h));
}

static void json_emit_cstring(duk_json_enc_ctx *js_ctx, const char *p) {
	DUK_ASSERT(p != NULL);
	(void) duk_hbuffer_append_cstring(js_ctx->thr, js_ctx->h_buf, p);
}

static void json_emit_stridx(duk_json_enc_ctx *js_ctx, int stridx) {
	DUK_ASSERT(stridx >= 0 && stridx < DUK_HEAP_NUM_STRINGS);
	json_emit_hstring(js_ctx, DUK_HTHREAD_GET_STRING(js_ctx->thr, stridx));
}

/* Check whether key quotes would be needed (custom encoding). */
static int json_enc_key_quotes_needed(duk_hstring *h_key) {
	duk_u8 *p, *p_start, *p_end;
	int ch;

	DUK_ASSERT(h_key != NULL);
	p_start = DUK_HSTRING_GET_DATA(h_key);
	p_end = p_start + DUK_HSTRING_GET_BYTELEN(h_key);
	p = p_start;

	DUK_DDDPRINT("json_enc_key_quotes_needed: h_key=%!O, p_start=%p, p_end=%p, p=%p",
	             (duk_heaphdr *) h_key, (void *) p_start, (void *) p_end, (void *) p);

	/* Since we only accept ASCII characters, there is no need for
	 * actual decoding.  A non-ASCII character will be >= 0x80 which
	 * causes a false return value immediately.
	 */

	while (p < p_end) {
		ch = (int) (*p);

		/* accept ASCII IdentifierStart and IdentifierPart if not first char */
		if ((ch >= (int) 'a' && ch <= (int) 'z') ||
		    (ch >= (int) 'A' && ch <= (int) 'Z') ||
		    (ch == (int) '$' || ch == (int) '_') ||
		    ((p > p_start) && (ch >= (int) '0' && ch <= (int) '9'))) {
			p++;
			continue;
		}

		/* all non-ASCII characters also come here (first byte >= 0x80) */
		return 1;
	}

	return 0;
}

/* The Quote(value) operation: quote a string.
 *
 * Stack policy: [ ] -> [ ].
 */

static char quote_esc[14] = {
	'\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0',
	'b',  't',  'n',  '\0', 'f',  'r'
};

static void json_enc_quote_string(duk_json_enc_ctx *js_ctx, duk_hstring *h_str) {
	duk_hthread *thr = js_ctx->thr;
	duk_u8 *p, *p_start, *p_end;
	duk_u32 cp;

	DUK_DDDPRINT("json_enc_quote_string: h_str=%!O", h_str);

	DUK_ASSERT(h_str != NULL);
	p_start = DUK_HSTRING_GET_DATA(h_str);
	p_end = p_start + DUK_HSTRING_GET_BYTELEN(h_str);
	p = p_start;

	EMIT_1(js_ctx, '"');

	while (p < p_end) {
		cp = *p;

		if (cp <= 0x7f) {
			/* ascii fast path: avoid decoding utf-8 */
			p++;
			if (cp == 0x22 || cp == 0x5c) {
				/* double quote or backslash */
				EMIT_2(js_ctx, '\\', (char) cp);
			} else if (cp < 0x20) {
				char esc_char;

				/* This approach is a bit shorter than a straight
				 * if-else-ladder and also a bit faster.
				 */
				if (cp < sizeof(quote_esc) &&
				    (esc_char = quote_esc[cp]) != (char) 0) {
					EMIT_2(js_ctx, '\\', esc_char);
				} else {
					EMIT_ESC16(js_ctx, cp);
				}
			} else if (cp == 0x7f && js_ctx->flag_ascii_only) {
				EMIT_ESC16(js_ctx, cp);
			} else {
				/* any other printable -> as is */
				EMIT_1(js_ctx, (char) cp);
			}
		} else {
			/* slow path decode */

			/* FIXME: this may currently fail, we'd prefer it never do that */
			cp = duk_unicode_xutf8_get_u32_checked(thr, &p, p_start, p_end);

			if (js_ctx->flag_ascii_only) {
				if (cp > 0xffff) {
					EMIT_ESC32(js_ctx, cp);
				} else {
					EMIT_ESC16(js_ctx, cp);
				}
			} else {
				/* as is */
				EMIT_XUTF8(js_ctx, cp);
			}
		}
	}

	EMIT_1(js_ctx, '"');
}

/* Shared entry handling for object/array serialization: indent/stepback,
 * loop detection.
 */
static void json_enc_objarr_shared_entry(duk_json_enc_ctx *js_ctx, duk_hstring **h_stepback, duk_hstring **h_indent, int *entry_top) {
	duk_context *ctx = (duk_context *) js_ctx->thr;
	duk_hobject *h_target;

	*entry_top = duk_get_top(ctx);

	duk_require_stack(ctx, DUK_JSON_ENC_REQSTACK);

	/* loop check */

	h_target = duk_get_hobject(ctx, -1);  /* object or array */
	DUK_ASSERT(h_target != NULL);
	duk_push_sprintf(ctx, "%p", (void *) h_target);

	duk_dup_top(ctx);  /* -> [ ... voidp voidp ] */
	if (duk_has_prop(ctx, js_ctx->idx_loop)) {
		DUK_ERROR((duk_hthread *) ctx, DUK_ERR_TYPE_ERROR, "cyclic input");
	}
	duk_push_true(ctx);  /* -> [ ... voidp true ] */
	duk_put_prop(ctx, js_ctx->idx_loop);  /* -> [ ... ] */

	/* c recursion check */

	DUK_ASSERT(js_ctx->recursion_depth >= 0);
	DUK_ASSERT(js_ctx->recursion_depth <= js_ctx->recursion_limit);
	if (js_ctx->recursion_depth >= js_ctx->recursion_limit) {
		DUK_ERROR((duk_hthread *) ctx, DUK_ERR_INTERNAL_ERROR, "recursion limit");
	}
	js_ctx->recursion_depth++;

	/* figure out indent and stepback */

	*h_indent = NULL;
	*h_stepback = NULL;
	if (js_ctx->h_gap != NULL) {
		DUK_ASSERT(js_ctx->h_indent != NULL);

		*h_stepback = js_ctx->h_indent;
		duk_push_hstring(ctx, js_ctx->h_indent);
		duk_push_hstring(ctx, js_ctx->h_gap);
		duk_concat(ctx, 2);
		js_ctx->h_indent = duk_get_hstring(ctx, -1);
		*h_indent = js_ctx->h_indent;
		DUK_ASSERT(js_ctx->h_indent != NULL);

		/* The new indent string is left at value stack top, and will
		 * be popped by the shared exit handler.
	 	 */
	} else {
		DUK_ASSERT(js_ctx->h_indent == NULL);
	}

	DUK_DDDPRINT("shared entry finished: top=%d, loop=%!T",
	             duk_get_top(ctx), duk_get_tval(ctx, js_ctx->idx_loop));
}

/* Shared exit handling for object/array serialization. */
static void json_enc_objarr_shared_exit(duk_json_enc_ctx *js_ctx, duk_hstring **h_stepback, duk_hstring **h_indent, int *entry_top) {
	duk_context *ctx = (duk_context *) js_ctx->thr;
	duk_hobject *h_target;

	if (js_ctx->h_gap != NULL) {
		DUK_ASSERT(js_ctx->h_indent != NULL);
		DUK_ASSERT(*h_stepback != NULL);
		DUK_ASSERT(*h_indent != NULL);

		js_ctx->h_indent = *h_stepback;  /* previous js_ctx->h_indent */

		/* Note: we don't need to pop anything because the duk_set_top()
		 * at the end will take care of it.
		 */
	} else {
		DUK_ASSERT(js_ctx->h_indent == NULL);
		DUK_ASSERT(*h_stepback == NULL);
		DUK_ASSERT(*h_indent == NULL);
	}

	/* c recursion check */

	DUK_ASSERT(js_ctx->recursion_depth > 0);
	DUK_ASSERT(js_ctx->recursion_depth <= js_ctx->recursion_limit);
	js_ctx->recursion_depth--;

	/* loop check */

	h_target = duk_get_hobject(ctx, *entry_top - 1);  /* original target at entry_top - 1 */
	DUK_ASSERT(h_target != NULL);
	duk_push_sprintf(ctx, "%p", (void *) h_target);

	duk_del_prop(ctx, js_ctx->idx_loop);  /* -> [ ... ] */

	/* restore stack top after unbalanced code paths */
	duk_set_top(ctx, *entry_top);

	DUK_DDDPRINT("shared entry finished: top=%d, loop=%!T",
	             duk_get_top(ctx), duk_get_tval(ctx, js_ctx->idx_loop));
}

/* The JO(value) operation: encode object.
 *
 * Stack policy: [ object ] -> [ object ].
 */
static void json_enc_object(duk_json_enc_ctx *js_ctx) {
	duk_context *ctx = (duk_context *) js_ctx->thr;
	duk_hstring *h_stepback;
	duk_hstring *h_indent;
	duk_hstring *h_key;
	int entry_top;
	int idx_obj;
	int idx_keys;
	int first;
	int undef;
	int arr_len;
	int i;

	DUK_DDDPRINT("json_enc_object: obj=%!T", duk_get_tval(ctx, -1));

	json_enc_objarr_shared_entry(js_ctx, &h_stepback, &h_indent, &entry_top);

	idx_obj = entry_top - 1;

	if (js_ctx->idx_proplist >= 0) {
		idx_keys = js_ctx->idx_proplist;
	} else {
		/* FIXME: would be nice to enumerate an object at specified index */
		duk_dup(ctx, idx_obj);
		(void) duk_hobject_get_enumerated_keys(ctx, DUK_ENUM_OWN_PROPERTIES_ONLY /*flags*/);  /* [ ... target ] -> [ ... target keys ] */
		idx_keys = duk_require_normalize_index(ctx, -1);
		/* leave stack unbalanced on purpose */
	}

	DUK_DDDPRINT("idx_keys=%d, h_keys=%!T", idx_keys, duk_get_tval(ctx, idx_keys));

	/* Steps 8-10 have been merged to avoid a "partial" variable. */

	EMIT_1(js_ctx, '{');

	/* FIXME: keys is an internal object with all keys to be processed
	 * in its (gapless) array part.  Because nobody can touch the keys
	 * object, we could iterate its array part directly (keeping in mind
	 * that it can be reallocated).
	 */

	arr_len = duk_get_length(ctx, idx_keys);
	first = 1;
	for (i = 0; i < arr_len; i++) {
		duk_get_prop_index(ctx, idx_keys, i);  /* -> [ ... key ] */

		DUK_DDDPRINT("object property loop: holder=%!T, key=%!T",
		             duk_get_tval(ctx, idx_obj), duk_get_tval(ctx, -1));

		undef = json_enc_value1(js_ctx, idx_obj);
		if (undef) {
			/* Value would yield 'undefined', so skip key altogether.
			 * Side effects have already happened.
			 */
			continue;
		}

		/* [ ... key val ] */

		if (first) {
			first = 0;
		} else {
			EMIT_1(js_ctx, (char) ',');
		}
		if (h_indent != NULL) {
			EMIT_1(js_ctx, (char) 0x0a);
			EMIT_HSTR(js_ctx, h_indent);
		}

		h_key = duk_get_hstring(ctx, -2);
		DUK_ASSERT(h_key != NULL);
		if (js_ctx->flag_avoid_key_quotes && !json_enc_key_quotes_needed(h_key)) {
			/* emit key as is */
			EMIT_HSTR(js_ctx, h_key);
		} else {
			json_enc_quote_string(js_ctx, h_key);
		}

		if (h_indent != NULL) {
			EMIT_2(js_ctx, ':', ' ');
		} else {
			EMIT_1(js_ctx, ':');
		}

		/* [ ... key val ] */

		json_enc_value2(js_ctx);  /* -> [ ... ] */
	}

	if (!first) {
		if (h_stepback != NULL) {
			DUK_ASSERT(h_indent != NULL);
			EMIT_1(js_ctx, (char) 0x0a);
			EMIT_HSTR(js_ctx, h_stepback);
		}
	}
	EMIT_1(js_ctx, '}');

	json_enc_objarr_shared_exit(js_ctx, &h_stepback, &h_indent, &entry_top);

	DUK_ASSERT_TOP(ctx, entry_top);
}

/* The JA(value) operation: encode array.
 *
 * Stack policy: [ array ] -> [ array ].
 */
static void json_enc_array(duk_json_enc_ctx *js_ctx) {
	duk_context *ctx = (duk_context *) js_ctx->thr;
	duk_hstring *h_stepback;
	duk_hstring *h_indent;
	int entry_top;
	int idx_arr;
	int undef;
	unsigned int i;
	unsigned int arr_len;  /* FIXME: type */

	DUK_DDDPRINT("json_enc_array: array=%!T", duk_get_tval(ctx, -1));

	json_enc_objarr_shared_entry(js_ctx, &h_stepback, &h_indent, &entry_top);

	idx_arr = entry_top - 1;

	/* Steps 8-10 have been merged to avoid a "partial" variable. */

	EMIT_1(js_ctx, '[');

	arr_len = duk_get_length(ctx, idx_arr);
	for (i = 0; i < arr_len; i++) {
		DUK_DDDPRINT("array entry loop: array=%!T, h_indent=%!O, h_stepback=%!O, index=%d, arr_len=%d",
		             duk_get_tval(ctx, idx_arr), h_indent, h_stepback, i, arr_len);

		if (i > 0) {
			EMIT_1(js_ctx, ',');
		}
		if (h_indent != NULL) {
			EMIT_1(js_ctx, (char) 0x0a);
			EMIT_HSTR(js_ctx, h_indent);
		}

		/* FIXME: duk_push_uint_string() */
		duk_push_number(ctx, (double) i);
		duk_to_string(ctx, -1);  /* -> [ ... key ] */
		undef = json_enc_value1(js_ctx, idx_arr);

		if (undef) {
			EMIT_STRIDX(js_ctx, DUK_STRIDX_NULL);
		} else {
			/* [ ... key val ] */
			json_enc_value2(js_ctx);
		}
	}

	if (arr_len > 0) {
		if (h_stepback != NULL) {
			DUK_ASSERT(h_indent != NULL);
			EMIT_1(js_ctx, (char) 0x0a);
			EMIT_HSTR(js_ctx, h_stepback);
		}
	}
	EMIT_1(js_ctx, ']');

	json_enc_objarr_shared_exit(js_ctx, &h_stepback, &h_indent, &entry_top);

	DUK_ASSERT_TOP(ctx, entry_top);
}

/* The Str(key, holder) operation: encode value, steps 1-4.
 *
 * Returns non-zero if the value between steps 4 and 5 would yield an
 * 'undefined' final result.  This is useful in JO() because we need to
 * get the side effects out, but need to know whether or not a key will
 * be omitted from the serialization.
 *
 * Stack policy: [ ... key ] -> [ ... key val ]  if retval == 0.
 *                           -> [ ... ]          if retval != 0.
 */
static int json_enc_value1(duk_json_enc_ctx *js_ctx, int idx_holder) {
	duk_context *ctx = (duk_context *) js_ctx->thr;
	duk_hobject *h;
	duk_tval *tv;
	int c;

	DUK_DDDPRINT("json_enc_value1: idx_holder=%d, holder=%!T, key=%!T",
	             idx_holder, duk_get_tval(ctx, idx_holder), duk_get_tval(ctx, -1));

	duk_dup_top(ctx);               /* -> [ ... key key ] */
	duk_get_prop(ctx, idx_holder);  /* -> [ ... key val ] */

	DUK_DDDPRINT("value=%!T", duk_get_tval(ctx, -1));

	h = duk_get_hobject(ctx, -1);
	if (h != NULL) {
		duk_get_prop_stridx(ctx, -1, DUK_STRIDX_TO_JSON);
		h = duk_get_hobject(ctx, -1);  /* FIXME: duk_get_hobject_callable */
		if (h != NULL && DUK_HOBJECT_IS_CALLABLE(h)) {
			DUK_DDDPRINT("value is object, has callable toJSON() -> call it");
			duk_dup(ctx, -2);         /* -> [ ... key val toJSON val ] */
			duk_dup(ctx, -4);         /* -> [ ... key val toJSON val key ] */
			duk_call_method(ctx, 1);  /* -> [ ... key val val' ] */
			duk_remove(ctx, -2);      /* -> [ ... key val' ] */
		} else {
			duk_pop(ctx);
		}
	}

	/* [ ... key val ] */

	DUK_DDDPRINT("value=%!T", duk_get_tval(ctx, -1));

	if (js_ctx->h_replacer) {
		/* FIXME: here a "slice copy" would be useful */
		DUK_DDDPRINT("replacer is set, call replacer");
		duk_push_hobject(ctx, js_ctx->h_replacer);  /* -> [ ... key val replacer ] */
		duk_dup(ctx, idx_holder);                   /* -> [ ... key val replacer holder ] */
		duk_dup(ctx, -4);                           /* -> [ ... key val replacer holder key ] */
		duk_dup(ctx, -4);                           /* -> [ ... key val replacer holder key val ] */
		duk_call_method(ctx, 2);                    /* -> [ ... key val val' ] */
		duk_remove(ctx, -2);                        /* -> [ ... key val' ] */
	}

	/* [ ... key val ] */

	DUK_DDDPRINT("value=%!T", duk_get_tval(ctx, -1));

	tv = duk_get_tval(ctx, -1);
	DUK_ASSERT(tv != NULL);
	if (DUK_TVAL_IS_OBJECT(tv)) {
		h = DUK_TVAL_GET_OBJECT(tv);
		DUK_ASSERT(h != NULL);

		c = DUK_HOBJECT_GET_CLASS_NUMBER(h);
		if (c == DUK_HOBJECT_CLASS_NUMBER) {
			DUK_DDDPRINT("value is a Number object -> coerce with ToNumber()");
			duk_to_number(ctx, -1);
		} else if (c == DUK_HOBJECT_CLASS_STRING) {
			DUK_DDDPRINT("value is a String object -> coerce with ToString()");
			duk_to_string(ctx, -1);
		} else if (c == DUK_HOBJECT_CLASS_BOOLEAN) {
			DUK_DDDPRINT("value is a Boolean object -> get internal value");
			duk_get_prop_stridx(ctx, -1, DUK_STRIDX_INT_VALUE);
			DUK_ASSERT(DUK_TVAL_IS_BOOLEAN(duk_get_tval(ctx, -1)));
			duk_remove(ctx, -2);
		}
	}

	/* [ ... key val ] */

	DUK_DDDPRINT("value=%!T", duk_get_tval(ctx, -1));

	tv = duk_get_tval(ctx, -1);
	DUK_ASSERT(tv != NULL);

	if (duk_get_type_mask(ctx, -1) & js_ctx->mask_for_undefined) {
		/* will result in undefined */
		DUK_DDDPRINT("-> will result in undefined (type mask check)");
		goto undef;
	}

	h = duk_get_hobject(ctx, -1);
	if (h != NULL && DUK_HOBJECT_IS_CALLABLE(h)) {
		if (js_ctx->flags & (DUK_JSON_ENC_FLAG_EXT_CUSTOM |
		                     DUK_JSON_ENC_FLAG_EXT_COMPATIBLE)) {
			/* function will be serialized to custom format */
		} else {
			/* functions are not serialized, results in undefined */
			DUK_DDDPRINT("-> will result in undefined (function)");
			goto undef;
		}
	}

	DUK_DDDPRINT("-> will not result in undefined");
	return 0;

 undef:
	duk_pop_2(ctx);
	return 1;
}

/* The Str(key, holder) operation: encode value, steps 5-10.
 *
 * This must not be called unless json_enc_value1() returns non-zero.
 * If so, this is guaranteed to produce a non-undefined result.
 * Non-standard encodings (e.g. for undefined) are only used if
 * json_enc_value1() indicates they are accepted; they're not
 * checked or asserted here again.
 *
 * Stack policy: [ ... key val ] -> [ ... ].
 */
static void json_enc_value2(duk_json_enc_ctx *js_ctx) {
	duk_context *ctx = (duk_context *) js_ctx->thr;
	duk_tval *tv;

	DUK_DDDPRINT("json_enc_value2: key=%!T, val=%!T",
	             duk_get_tval(ctx, -2), duk_get_tval(ctx, -1));

	/* [ ... key val ] */

	tv = duk_get_tval(ctx, -1);
	DUK_ASSERT(tv != NULL);

	switch (DUK_TVAL_GET_TAG(tv)) {
	case DUK_TAG_UNDEFINED: {
		EMIT_STRIDX(js_ctx, js_ctx->stridx_custom_undefined);
		break;
	}
	case DUK_TAG_NULL: {
		EMIT_STRIDX(js_ctx, DUK_STRIDX_NULL);
		break;
	}
	case DUK_TAG_BOOLEAN: {
		EMIT_STRIDX(js_ctx, DUK_TVAL_GET_BOOLEAN(tv) ?
		            DUK_STRIDX_TRUE : DUK_STRIDX_FALSE);
		break;
	}
	case DUK_TAG_POINTER: {
		/* FIXME: custom format unfinished */
		char buf[40];  /* FIXME: how to figure correct size? */
		const char *fmt;

		/* FIXME: NULL results in '((nil))' now */
		memset(buf, 0, sizeof(buf));
		if (js_ctx->flag_ext_custom) {
			fmt = "(%p)";
		} else {
			DUK_ASSERT(js_ctx->flag_ext_compatible);
			fmt = "{\"_ptr\":\"(%p)\"}";
		}
		snprintf(buf, sizeof(buf) - 1, fmt, (void *) DUK_TVAL_GET_POINTER(tv));
		EMIT_CSTR(js_ctx, buf);
		break;
	}
	case DUK_TAG_STRING: {
		duk_hstring *h = DUK_TVAL_GET_STRING(tv);
		DUK_ASSERT(h != NULL);

		json_enc_quote_string(js_ctx, h);
		break;
	}
	case DUK_TAG_OBJECT: {
		duk_hobject *h = DUK_TVAL_GET_OBJECT(tv);
		DUK_ASSERT(h != NULL);

		if (DUK_HOBJECT_IS_CALLABLE(h)) {
			if (js_ctx->flag_ext_custom) {
				/* FIXME: just ToString() now */
			} else {
				DUK_ASSERT(js_ctx->flag_ext_compatible);
				/* FIXME: just ToString() now */
			}
			duk_to_string(ctx, -1);
			json_enc_quote_string(js_ctx, duk_require_hstring(ctx, -1));
		} else if (DUK_HOBJECT_GET_CLASS_NUMBER(h) == DUK_HOBJECT_CLASS_ARRAY) {
			json_enc_array(js_ctx);
		} else {
			json_enc_object(js_ctx);
		}
		break;
	}
	case DUK_TAG_BUFFER: {
		duk_hbuffer *h = DUK_TVAL_GET_BUFFER(tv);
		DUK_ASSERT(h != NULL);

		/* FIXME: custom format unfinished */

		/* FIXME: other alternatives for binary include base64, base85,
		 * encoding as Unicode 8-bit codepoints, etc.
		 */

		if (js_ctx->flag_ext_custom) {
			duk_u8 *p, *p_end;
			int x;
			p = (duk_u8 *) DUK_HBUFFER_GET_DATA_PTR(h);
			p_end = p + DUK_HBUFFER_GET_SIZE(h);
			EMIT_1(js_ctx, '|');
			while (p < p_end) {
				x = (int) *p++;
				duk_hbuffer_append_byte(js_ctx->thr, js_ctx->h_buf, duk_lc_digits[(x >> 4) & 0x0f]);
				duk_hbuffer_append_byte(js_ctx->thr, js_ctx->h_buf, duk_lc_digits[x & 0x0f]);
			}
			EMIT_1(js_ctx, '|');
		} else {
			DUK_ASSERT(js_ctx->flag_ext_compatible);
			duk_base64_encode(ctx, -1);
			EMIT_CSTR(js_ctx, "{\"_base64\":");  /* FIXME: stridx */
			json_enc_quote_string(js_ctx, duk_require_hstring(ctx, -1));
			EMIT_1(js_ctx, '}');
		}
		break;
	}
	default: {
		/* number */
		double d;
		int c;
		int s;
		int stridx;
		int n2s_flags;
		duk_hstring *h_str;
		DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv));

		d = DUK_TVAL_GET_NUMBER(tv);
		c = fpclassify(d);
		s = signbit(d);

		if (!(c == FP_INFINITE || c == FP_NAN)) {
			DUK_ASSERT(isfinite(d));
			n2s_flags = 0;
			/* [ ... number ] -> [ ... string ] */
			duk_numconv_stringify(ctx, 10 /*radix*/, 0 /*digits*/, n2s_flags);
			h_str = duk_to_hstring(ctx, -1);
			DUK_ASSERT(h_str != NULL);
			EMIT_HSTR(js_ctx, h_str);
			break;
		}

		/* FIXME: awkward check */

		if (!(js_ctx->flags & (DUK_JSON_ENC_FLAG_EXT_CUSTOM |
		                       DUK_JSON_ENC_FLAG_EXT_COMPATIBLE))) {
			stridx = DUK_STRIDX_NULL;
		} else if (c == FP_NAN) {
			stridx = js_ctx->stridx_custom_nan;
		} else if (s == 0) {
			stridx = js_ctx->stridx_custom_neginf;
		} else {
			stridx = js_ctx->stridx_custom_posinf;
		}
		EMIT_STRIDX(js_ctx, stridx);
		break;
	}
	}

	/* [ ... key val ] -> [ ... ] */

	duk_pop_2(ctx);
}

/* E5 Section 15.12.3, main algorithm, step 4.b.ii steps 1-4. */
static int json_enc_allow_into_proplist(duk_tval *tv) {
	duk_hobject *h;
	int c;

	DUK_ASSERT(tv != NULL);
	if (DUK_TVAL_IS_STRING(tv) || DUK_TVAL_IS_NUMBER(tv)) {
		return 1;
	} else if (DUK_TVAL_IS_OBJECT(tv)) {
		h = DUK_TVAL_GET_OBJECT(tv);
		DUK_ASSERT(h != NULL);
		c = DUK_HOBJECT_GET_CLASS_NUMBER(h);
		if (c == DUK_HOBJECT_CLASS_STRING || c == DUK_HOBJECT_CLASS_NUMBER) {
			return 1;
		}
	}

	return 0;
}

/*
 *  Top level wrappers
 */

void duk_builtin_json_parse_helper(duk_context *ctx,
                                   int idx_value,
                                   int idx_reviver,
                                   int flags) {
	duk_hthread *thr = (duk_hthread *) ctx;
	duk_json_dec_ctx js_ctx_alloc;
	duk_json_dec_ctx *js_ctx = &js_ctx_alloc;
	duk_hstring *h_text;
#ifdef DUK_USE_ASSERTIONS
	int top_at_entry = duk_get_top(ctx);
#endif

	DUK_DDDPRINT("JSON parse start: text=%!T, reviver=%!T, flags=0x%08x, stack_top=%d",
	             duk_get_tval(ctx, idx_value), duk_get_tval(ctx, idx_reviver),
	             flags, duk_get_top(ctx));

	memset(&js_ctx_alloc, 0, sizeof(js_ctx_alloc));
	js_ctx->thr = thr;
#ifdef DUK_USE_EXPLICIT_NULL_INIT
	/* nothing now */
#endif
	js_ctx->recursion_limit = DUK_JSON_DEC_RECURSION_LIMIT;

	/* FIXME: flags for custom parsing */

	h_text = duk_to_hstring(ctx, idx_value);  /* coerce in-place */
	DUK_ASSERT(h_text != NULL);

	js_ctx->p = (duk_u8 *) DUK_HSTRING_GET_DATA(h_text);
	js_ctx->p_end = ((duk_u8 *) DUK_HSTRING_GET_DATA(h_text)) +
	                DUK_HSTRING_GET_BYTELEN(h_text);

	json_dec_value(js_ctx);  /* -> [ ... value ] */

	/* Trailing whitespace has been eaten by json_dec_value(), so if
	 * we're not at end of input here, it's a SyntaxError.
	 */

	if (js_ctx->p != js_ctx->p_end) {
		DUK_ERROR(thr, DUK_ERR_SYNTAX_ERROR, "invalid json");
	}

	if (duk_is_callable(ctx, idx_reviver)) {
		DUK_DDDPRINT("applying reviver: %!T", duk_get_tval(ctx, idx_reviver));

		js_ctx->idx_reviver = idx_reviver;

		DUK_ASSERT_TOP(ctx, 3);

		duk_push_object(ctx);
		duk_dup(ctx, -2);  /* -> [ ... val root val ] */
		duk_put_prop_stridx(ctx, -2, DUK_STRIDX_EMPTY_STRING);  /* default attrs ok */
		duk_push_hstring_stridx(ctx, DUK_STRIDX_EMPTY_STRING);  /* -> [ ... val root "" ] */

		DUK_DDDPRINT("start reviver walk, root=%!T, name=%!T",
		             duk_get_tval(ctx, -2), duk_get_tval(ctx, -1));

		json_dec_reviver_walk(js_ctx);  /* [ ... val root "" ] -> [ ... val val' ] */
		duk_remove(ctx, -2);            /* -> [ ... val' ] */
	} else {
		DUK_DDDPRINT("reviver does not exist or is not callable: %!T",
		             duk_get_tval(ctx, idx_reviver));
	}

	/* Final result is at stack top. */

	DUK_DDDPRINT("JSON parse end: text=%!T, reviver=%!T, flags=0x%08x, result=%!T, stack_top=%d",
	             duk_get_tval(ctx, idx_value), duk_get_tval(ctx, idx_reviver),
	             flags, duk_get_tval(ctx, -1), duk_get_top(ctx));

	DUK_ASSERT(duk_get_top(ctx) == top_at_entry + 1);
}

void duk_builtin_json_stringify_helper(duk_context *ctx,
                                       int idx_value,
                                       int idx_replacer,
                                       int idx_space,
                                       int flags) {
	duk_hthread *thr = (duk_hthread *) ctx;
	duk_json_enc_ctx js_ctx_alloc;
	duk_json_enc_ctx *js_ctx = &js_ctx_alloc;
	duk_hobject *h;
	int undef;
	int idx_holder;
	int top_at_entry;

	DUK_DDDPRINT("JSON stringify start: value=%!T, replacer=%!T, space=%!T, flags=0x%08x, stack_top=%d",
	             duk_get_tval(ctx, idx_value), duk_get_tval(ctx, idx_replacer),
	             duk_get_tval(ctx, idx_space), flags, duk_get_top(ctx));

	top_at_entry = duk_get_top(ctx);

	/*
	 *  Context init
	 */

	memset(&js_ctx_alloc, 0, sizeof(js_ctx_alloc));
	js_ctx->thr = thr;
#ifdef DUK_USE_EXPLICIT_NULL_INIT
	js_ctx->h_replacer = NULL;
	js_ctx->h_gap = NULL;
	js_ctx->h_indent = NULL;
#endif
	js_ctx->idx_proplist = -1;
	js_ctx->recursion_limit = DUK_JSON_ENC_RECURSION_LIMIT;

#if 0  /* FIXME: custom formats; some implemented, some not */
	flags |= DUK_JSON_ENC_FLAG_ASCII_ONLY;
	flags |= DUK_JSON_ENC_FLAG_AVOID_KEY_QUOTES;
	flags |= DUK_JSON_ENC_FLAG_EXT_COMPATIBLE;
#endif
	js_ctx->flags = flags;
	js_ctx->flag_ascii_only = flags & DUK_JSON_ENC_FLAG_ASCII_ONLY;
	js_ctx->flag_avoid_key_quotes = flags & DUK_JSON_ENC_FLAG_AVOID_KEY_QUOTES;
	js_ctx->flag_ext_custom = flags & DUK_JSON_ENC_FLAG_EXT_CUSTOM;
	js_ctx->flag_ext_compatible = flags & DUK_JSON_ENC_FLAG_EXT_COMPATIBLE;

	if (flags & DUK_JSON_ENC_FLAG_EXT_CUSTOM) {
		js_ctx->stridx_custom_undefined = DUK_STRIDX_UNDEFINED;
		js_ctx->stridx_custom_nan = DUK_STRIDX_NAN;
		js_ctx->stridx_custom_neginf = DUK_STRIDX_MINUS_INFINITY;
		js_ctx->stridx_custom_posinf = DUK_STRIDX_INFINITY;
	} else if (js_ctx->flags & DUK_JSON_ENC_FLAG_EXT_COMPATIBLE) {
		js_ctx->stridx_custom_undefined = DUK_STRIDX_JSON_EXT_UNDEFINED;
		js_ctx->stridx_custom_nan = DUK_STRIDX_JSON_EXT_NAN;
		js_ctx->stridx_custom_neginf = DUK_STRIDX_JSON_EXT_NEGINF;
		js_ctx->stridx_custom_posinf = DUK_STRIDX_JSON_EXT_POSINF;
	}

	if (js_ctx->flags & (DUK_JSON_ENC_FLAG_EXT_CUSTOM |
	                     DUK_JSON_ENC_FLAG_EXT_COMPATIBLE)) {
		DUK_ASSERT(js_ctx->mask_for_undefined == 0);  /* already zero */
	} else {
		js_ctx->mask_for_undefined = DUK_TYPE_MASK_UNDEFINED |
		                             DUK_TYPE_MASK_POINTER |
		                             DUK_TYPE_MASK_BUFFER;
	}

	(void) duk_push_dynamic_buffer(ctx, 0);
	js_ctx->h_buf = (duk_hbuffer_dynamic *) duk_get_hbuffer(ctx, -1);
	DUK_ASSERT(js_ctx->h_buf != NULL);
	DUK_ASSERT(DUK_HBUFFER_HAS_DYNAMIC(js_ctx->h_buf));

	js_ctx->idx_loop = duk_push_object_internal(ctx);
	DUK_ASSERT(js_ctx->idx_loop >= 0);

	/* [ ... buf loop ] */

	/*
	 *  Process replacer/proplist (2nd argument to JSON.stringify)
	 */

	h = duk_get_hobject(ctx, idx_replacer);
	if (h != NULL) {
		if (DUK_HOBJECT_IS_CALLABLE(h)) {
			js_ctx->h_replacer = h;
		} else if (DUK_HOBJECT_GET_CLASS_NUMBER(h) == DUK_HOBJECT_CLASS_ARRAY) {
			/* Here the specification requires correct array index enumeration
			 * which is a bit tricky for sparse arrays (it is handled by the
			 * enum setup code).  We now enumerate ancestors too, although the
			 * specification is not very clear on whether that is required.
			 */

			int plist_idx = 0;
			int enum_flags;

			js_ctx->idx_proplist = duk_push_array(ctx);  /* FIXME: array internal? */

			enum_flags = DUK_ENUM_ARRAY_INDICES_ONLY |
			             DUK_ENUM_SORT_ARRAY_INDICES;  /* expensive flag */
			duk_enum(ctx, idx_replacer, enum_flags);
			while (duk_next(ctx, -1 /*enum_index*/, 1 /*get_value*/)) {
				/* [ ... proplist enum_obj key val ] */
				if (json_enc_allow_into_proplist(duk_get_tval(ctx, -1))) {
					/* FIXME: duplicates should be eliminated here */
					DUK_DDDPRINT("proplist enum: key=%!T, val=%!T --> accept", duk_get_tval(ctx, -2), duk_get_tval(ctx, -1));
					duk_to_string(ctx, -1);  /* extra coercion of strings is OK */
					duk_put_prop_index(ctx, -4, plist_idx);  /* -> [ ... proplist enum_obj key ] */
					plist_idx++;
					duk_pop(ctx);
				} else {
					DUK_DDDPRINT("proplist enum: key=%!T, val=%!T --> reject", duk_get_tval(ctx, -2), duk_get_tval(ctx, -1));
					duk_pop_2(ctx);
				}
                        }
                        duk_pop(ctx);  /* pop enum */

			/* [ ... proplist ] */
		}
	}

	/* [ ... buf loop (proplist) ] */

	/*
	 *  Process space (3rd argument to JSON.stringify)
	 */

	h = duk_get_hobject(ctx, idx_space);
	if (h != NULL) {
		int c = DUK_HOBJECT_GET_CLASS_NUMBER(h);
		if (c == DUK_HOBJECT_CLASS_NUMBER) {
			duk_to_number(ctx, idx_space);
		} else if (c == DUK_HOBJECT_CLASS_STRING) {
			duk_to_string(ctx, idx_space);
		}
	}

	if (duk_is_number(ctx, idx_space)) {
		double d;
		int nspace;
		char spaces[10] = { ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ' };  /* FIXME:helper */

		/* ToInteger() coercion; NaN -> 0, infinities are clamped to 0 and 10 */
		/* FIXME: get_clamped_int; double arithmetic is expensive */
		(void) duk_to_int(ctx, idx_space);
		d = duk_get_number(ctx, idx_space);
		if (d > 10) {
			nspace = 10;
		} else if (d < 1) {
			nspace = 0;
		} else {
			nspace = (int) d;
		}
		DUK_ASSERT(nspace >= 0 && nspace <= 10);

		duk_push_lstring(ctx, spaces, nspace);
		js_ctx->h_gap = duk_get_hstring(ctx, -1);
		DUK_ASSERT(js_ctx->h_gap != NULL);
	} else if (duk_is_string(ctx, idx_space)) {
		/* FIXME: substring in-place at idx_place? */
		duk_dup(ctx, idx_space);
		duk_substring(ctx, -1, 0, 10);  /* clamp to 10 chars */
		js_ctx->h_gap = duk_get_hstring(ctx, -1);
		DUK_ASSERT(js_ctx->h_gap != NULL);
	} else {
		/* nop */
	}

	if (js_ctx->h_gap != NULL) {
		/* if gap is empty, behave as if not given at all */
		if (DUK_HSTRING_GET_CHARLEN(js_ctx->h_gap) == 0) {
			js_ctx->h_gap = NULL;
		} else {
			/* set 'indent' only if it will actually increase */
			js_ctx->h_indent = DUK_HTHREAD_STRING_EMPTY_STRING(thr);
		}
	}

	DUK_ASSERT((js_ctx->h_gap == NULL && js_ctx->h_indent == NULL) ||
	           (js_ctx->h_gap != NULL && js_ctx->h_indent != NULL));

	/* [ ... buf loop (proplist) (gap) ] */

	/*
	 *  Create wrapper object and serialize
	 */

	idx_holder = duk_push_object(ctx);
	duk_dup(ctx, idx_value);
	duk_put_prop_stridx(ctx, -2, DUK_STRIDX_EMPTY_STRING);

	DUK_DDDPRINT("before: flags=0x%08x, buf=%!O, loop=%!T, replacer=%!O, proplist=%!T, gap=%!O, indent=%!O, holder=%!T",
	             js_ctx->flags,
	             js_ctx->h_buf,
	             duk_get_tval(ctx, js_ctx->idx_loop),
	             js_ctx->h_replacer,
	             js_ctx->idx_proplist >= 0 ? duk_get_tval(ctx, js_ctx->idx_proplist) : NULL,
	             js_ctx->h_gap,
	             js_ctx->h_indent,
	             duk_get_tval(ctx, -1));
	
	/* serialize the wrapper with empty string key */

	duk_push_hstring_stridx(ctx, DUK_STRIDX_EMPTY_STRING);

	/* [ ... buf loop (proplist) (gap) holder "" ] */

	undef = json_enc_value1(js_ctx, idx_holder);  /* [ ... holder key ] -> [ ... holder key val ] */

	DUK_DDDPRINT("after: flags=0x%08x, buf=%!O, loop=%!T, replacer=%!O, proplist=%!T, gap=%!O, indent=%!O, holder=%!T",
	             js_ctx->flags,
	             js_ctx->h_buf,
	             duk_get_tval(ctx, js_ctx->idx_loop),
	             js_ctx->h_replacer,
	             js_ctx->idx_proplist >= 0 ? duk_get_tval(ctx, js_ctx->idx_proplist) : NULL,
	             js_ctx->h_gap,
	             js_ctx->h_indent,
	             duk_get_tval(ctx, -3));

	if (undef) {
		/*
		 *  Result is undefined
		 */

		duk_push_undefined(ctx);
	} else {
		/*
		 *  Finish and convert buffer to result string
		 */

		json_enc_value2(js_ctx);  /* [ ... key val ] -> [ ... ] */
		DUK_ASSERT(js_ctx->h_buf != NULL);
		duk_push_hbuffer(ctx, (duk_hbuffer *) js_ctx->h_buf);
		duk_to_string(ctx, -1);
	}

	/* The stack has a variable shape here, so force it to the
	 * desired one explicitly.
	 */

	duk_replace(ctx, top_at_entry);
	duk_set_top(ctx, top_at_entry + 1);

	DUK_DDDPRINT("JSON stringify end: value=%!T, replacer=%!T, space=%!T, flags=0x%08x, result=%!T, stack_top=%d",
	             duk_get_tval(ctx, idx_value), duk_get_tval(ctx, idx_replacer),
	             duk_get_tval(ctx, idx_space), flags, duk_get_tval(ctx, -1), duk_get_top(ctx));

	DUK_ASSERT(duk_get_top(ctx) == top_at_entry + 1);
}

/*
 *  Entry points
 */

int duk_builtin_json_object_parse(duk_context *ctx) {
	duk_builtin_json_parse_helper(ctx,
	                              0 /*idx_value*/,
	                              1 /*idx_replacer*/,
	                              0 /*flags*/);
	return 1;
}

int duk_builtin_json_object_stringify(duk_context *ctx) {
	duk_builtin_json_stringify_helper(ctx,
	                                  0 /*idx_value*/,
	                                  1 /*idx_replacer*/,
	                                  2 /*idx_space*/,
	                                  0 /*flags*/);
	return 1;
}

#line 1 "duk_builtin_math.c"
/*
 *  Math built-ins
 */

/* include removed: duk_internal.h */

/*
 *  Use static helpers which can work with math.h functions matching
 *  the following signatures. This is not portable if any of these math
 *  functions is actually a macro.
 */

typedef double (*one_arg_func)(double);
typedef double (*two_arg_func)(double, double);

static int math_one_arg(duk_context *ctx, one_arg_func fun) {
	duk_push_number(ctx, fun(duk_to_number(ctx, 0)));
	return 1;
}

static int math_two_arg(duk_context *ctx, two_arg_func fun) {
	duk_push_number(ctx, fun(duk_to_number(ctx, 0), duk_to_number(ctx, 1)));
	return 1;
}

static int math_minmax(duk_context *ctx, double initial, two_arg_func min_max) {
	int n = duk_get_top(ctx);
	int i;
	double res = initial;
	double t;

	/*
	 *  Note: fmax() does not match the E5 semantics.  E5 requires
	 *  that if -any- input to Math.max() is a NAN, the result is a
	 *  NaN.  fmax() will return a NAN only if -both- inputs are NaN.
	 *  Same applies to fmin().
	 *
	 *  Note: every input value must be coerced with ToNumber(), even
	 *  if we know the result will be a NAN anyway: ToNumber() may have
	 *  side effects for which even order of evaluation matters.
	 */

	for (i = 0; i < n; i++) {
		t = duk_to_number(ctx, i);
		if (fpclassify(t) == FP_NAN || fpclassify(res) == FP_NAN) {
			/* Note: not normalized, but duk_push_number() will normalize */
			/* FIXME: best constant for NAN? */
			res = NAN;
		} else {
			res = min_max(res, t);
		}
	}

	duk_push_number(ctx, res);
	return 1;
}

static double fmin_fixed(double x, double y) {
	/* fmin() with args -0 and +0 is not guaranteed to return
	 * -0 as Ecmascript requires.
	 */
	if (x == 0 && y == 0) {
		/* XXX: what's the safest way of creating a negative zero? */
		if (signbit(x) != 0 || signbit(y) != 0) {
			return -0.0;
		} else {
			return +0.0;
		}
	}
#ifdef DUK_USE_MATH_FMIN
	return fmin(x, y);
#else
	return (x < y ? x : y);
#endif
}

static double fmax_fixed(double x, double y) {
	/* fmax() with args -0 and +0 is not guaranteed to return
	 * +0 as Ecmascript requires.
	 */
	if (x == 0 && y == 0) {
		if (signbit(x) == 0 || signbit(y) == 0) {
			return +0.0;
		} else {
			return -0.0;
		}
	}
#ifdef DUK_USE_MATH_FMAX
	return fmax(x, y);
#else
	return (x > y ? x : y);
#endif
}

static double round_fixed(double x) {
	/* Numbers half-way between integers must be rounded towards +Infinity,
	 * e.g. -3.5 must be rounded to -3 (not -4).  When rounded to zero, zero
	 * sign must be set appropriately.  E5.1 Section 15.8.2.15.
	 *
	 * Note that ANSI C round() is "round to nearest integer, away from zero",
	 * which is incorrect for negative values.  Here we make do with floor().
	 */

	int c = fpclassify(x);
	if (c == FP_NAN || c == FP_INFINITE || c == FP_ZERO) {
		return x;
	}

	/*
	 *  x is finite and non-zero
	 *
	 *  -1.6 -> floor(-1.1) -> -2
	 *  -1.5 -> floor(-1.0) -> -1  (towards +Inf)
	 *  -1.4 -> floor(-0.9) -> -1
	 *  -0.5 -> -0.0               (special case)
	 *  -0.1 -> -0.0               (special case)
	 *  +0.1 -> +0.0               (special case)
	 *  +0.5 -> floor(+1.0) -> 1   (towards +Inf)
	 *  +1.4 -> floor(+1.9) -> 1
	 *  +1.5 -> floor(+2.0) -> 2   (towards +Inf)
	 *  +1.6 -> floor(+2.1) -> 2
	 */

	if (x >= -0.5 && x < 0.5) {
		/* +0.5 is handled by floor, this is on purpose */
		if (x < 0.0) {
			return -0.0;
		} else {
			return +0.0;
		}
	}

	return floor(x + 0.5);
}

static double pow_fixed(double x, double y) {
	/* The ANSI C pow() semantics differ from Ecmascript.
	 *
	 * E.g. when x==1 and y is +/- infinite, the Ecmascript required
	 * result is NaN, while at least Linux pow() returns 1.
	 */

	int cy;

	cy = fpclassify(y);

	if (cy == FP_NAN) {
		goto ret_nan;
	}
	if (fabs(x) == 1.0 && cy == FP_INFINITE) {
		goto ret_nan;
	}

	return pow(x, y);

 ret_nan:
	return NAN;
}

int duk_builtin_math_object_abs(duk_context *ctx) {
	return math_one_arg(ctx, fabs);
}

int duk_builtin_math_object_acos(duk_context *ctx) {
	return math_one_arg(ctx, acos);
}

int duk_builtin_math_object_asin(duk_context *ctx) {
	return math_one_arg(ctx, asin);
}

int duk_builtin_math_object_atan(duk_context *ctx) {
	return math_one_arg(ctx, atan);
}

int duk_builtin_math_object_atan2(duk_context *ctx) {
	return math_two_arg(ctx, atan2);
}

int duk_builtin_math_object_ceil(duk_context *ctx) {
	return math_one_arg(ctx, ceil);
}

int duk_builtin_math_object_cos(duk_context *ctx) {
	return math_one_arg(ctx, cos);
}

int duk_builtin_math_object_exp(duk_context *ctx) {
	return math_one_arg(ctx, exp);
}

int duk_builtin_math_object_floor(duk_context *ctx) {
	return math_one_arg(ctx, floor);
}

int duk_builtin_math_object_log(duk_context *ctx) {
	return math_one_arg(ctx, log);
}

int duk_builtin_math_object_max(duk_context *ctx) {
	/* FIXME: compile warning here on gcc-4.0, floating constant exceeds range of 'float' */
	return math_minmax(ctx, -((double) INFINITY), fmax_fixed);
}

int duk_builtin_math_object_min(duk_context *ctx) {
	/* FIXME: compile warning here on gcc-4.0, floating constant exceeds range of 'float' */
	return math_minmax(ctx, (double) INFINITY, fmin_fixed);
}

int duk_builtin_math_object_pow(duk_context *ctx) {
	return math_two_arg(ctx, pow_fixed);
}

int duk_builtin_math_object_random(duk_context *ctx) {
	duk_push_number(ctx, duk_util_tinyrandom_get_double((duk_hthread *) ctx));
	return 1;
}

int duk_builtin_math_object_round(duk_context *ctx) {
	return math_one_arg(ctx, round_fixed);
}

int duk_builtin_math_object_sin(duk_context *ctx) {
	return math_one_arg(ctx, sin);
}

int duk_builtin_math_object_sqrt(duk_context *ctx) {
	return math_one_arg(ctx, sqrt);
}

int duk_builtin_math_object_tan(duk_context *ctx) {
	return math_one_arg(ctx, tan);
}

#line 1 "duk_builtin_number.c"
/*
 *  Number built-ins
 */

/* FIXME: needs to be refactored when exact parsing / string conversion
 * primitives are implemented.
 */

/* include removed: duk_internal.h */

static double push_this_number_plain(duk_context *ctx) {
	duk_hobject *h;

	/* Number built-in accepts a plain number or a Number object (whose
	 * internal value is operated on).  Other types cause TypeError.
	 */

	duk_push_this(ctx);
	if (duk_is_number(ctx, -1)) {
		DUK_DDDPRINT("plain number value: %!T", duk_get_tval(ctx, -1));
		goto done;
	}
	h = duk_get_hobject(ctx, -1);
	if (!h || 
	    (DUK_HOBJECT_GET_CLASS_NUMBER(h) != DUK_HOBJECT_CLASS_NUMBER)) {
		DUK_DDDPRINT("unacceptable this value: %!T", duk_get_tval(ctx, -1));
		DUK_ERROR((duk_hthread *) ctx, DUK_ERR_TYPE_ERROR, "expected a number");
	}
	duk_get_prop_stridx(ctx, -1, DUK_STRIDX_INT_VALUE);
	DUK_ASSERT(duk_is_number(ctx, -1));
	DUK_DDDPRINT("number object: %!T, internal value: %!T", duk_get_tval(ctx, -2), duk_get_tval(ctx, -1));
	duk_remove(ctx, -2);

 done:
	return duk_get_number(ctx, -1);
}

int duk_builtin_number_constructor(duk_context *ctx) {
	int nargs;
	duk_hobject *h_this;

	/*
	 *  The Number constructor uses ToNumber(arg) for number coercion
	 *  (coercing an undefined argument to NaN).  However, if the
	 *  argument is not given at all, +0 must be used instead.  To do
	 *  this, a vararg function is used.
	 */

	nargs = duk_get_top(ctx);
	if (nargs == 0) {
		duk_push_int(ctx, 0);
	}
	duk_to_number(ctx, 0);
	duk_set_top(ctx, 1);
	DUK_ASSERT_TOP(ctx, 1);

	if (!duk_is_constructor_call(ctx)) {
		return 1;
	}

	/*
	 *  E5 Section 15.7.2.1 requires that the constructed object
	 *  must have the original Number.prototype as its internal
	 *  prototype.  However, since Number.prototype is non-writable
	 *  and non-configurable, this doesn't have to be enforced here:
	 *  The default object (bound to 'this') is OK, though we have
	 *  to change its class.
	 *
	 *  Internal value set to ToNumber(arg) or +0; if no arg given,
	 *  ToNumber(undefined) = NaN, so special treatment is needed
	 *  (above).  String internal value is immutable.
	 */

	/* FIXME: helper */
	duk_push_this(ctx);
	h_this = duk_get_hobject(ctx, -1);
	DUK_ASSERT(h_this != NULL);
	DUK_HOBJECT_SET_CLASS_NUMBER(h_this, DUK_HOBJECT_CLASS_NUMBER);

	DUK_ASSERT(h_this->prototype == ((duk_hthread *) ctx)->builtins[DUK_BIDX_NUMBER_PROTOTYPE]);
	DUK_ASSERT(DUK_HOBJECT_GET_CLASS_NUMBER(h_this) == DUK_HOBJECT_CLASS_NUMBER);
	DUK_ASSERT(DUK_HOBJECT_HAS_EXTENSIBLE(h_this));

	duk_dup(ctx, 0);  /* -> [ val obj val ] */
	duk_def_prop_stridx(ctx, -2, DUK_STRIDX_INT_VALUE, DUK_PROPDESC_FLAGS_NONE);
	return 0;  /* no return value -> don't replace created value */
}

int duk_builtin_number_prototype_value_of(duk_context *ctx) {
	(void) push_this_number_plain(ctx);
	return 1;
}

int duk_builtin_number_prototype_to_string(duk_context *ctx) {
	int radix;
	int n2s_flags;

	(void) push_this_number_plain(ctx);
	if (duk_is_undefined(ctx, 0)) {
		radix = 10;
	} else {
		radix = duk_to_int_check_range(ctx, 0, 2, 36);
	}
	DUK_DDDPRINT("radix=%d", radix);

	n2s_flags = 0;

	duk_numconv_stringify(ctx,
	                      radix /*radix*/,
	                      0 /*digits*/,
	                      n2s_flags /*flags*/);
	return 1;
}

int duk_builtin_number_prototype_to_locale_string(duk_context *ctx) {
	/* FIXME: just use toString() for now; permitted although not recommended.
	 * nargs==1, so radix is passed to toString().
	 */
	return duk_builtin_number_prototype_to_string(ctx);
}

/*
 *  toFixed(), toExponential(), toPrecision()
 */

/* FIXME: shared helper for toFixed(), toExponential(), toPrecision()? */

int duk_builtin_number_prototype_to_fixed(duk_context *ctx) {
	int frac_digits;
	double d;
	int c;
	int n2s_flags;

	frac_digits = duk_to_int_check_range(ctx, 0, 0, 20);
	d = push_this_number_plain(ctx);

	c = fpclassify(d);
	if (c == FP_NAN || c == FP_INFINITE) {
		goto use_to_string;
	}

	if (d >= 1.0e21 || d <= -1.0e21) {
		goto use_to_string;
	}

	n2s_flags = DUK_N2S_FLAG_FIXED_FORMAT |
	            DUK_N2S_FLAG_FRACTION_DIGITS;

	duk_numconv_stringify(ctx,
	                      10 /*radix*/,
	                      frac_digits /*digits*/,
	                      n2s_flags /*flags*/);
	return 1;

 use_to_string:
	DUK_ASSERT_TOP(ctx, 2);
	duk_to_string(ctx, -1);
	return 1;
}

int duk_builtin_number_prototype_to_exponential(duk_context *ctx) {
	int frac_undefined;
	int frac_digits;
	double d;
	int c;
	int n2s_flags;

	d = push_this_number_plain(ctx);

	frac_undefined = duk_is_undefined(ctx, 0);
	duk_to_int(ctx, 0);  /* for side effects */

	c = fpclassify(d);
	if (c == FP_NAN || c == FP_INFINITE) {
		goto use_to_string;
	}

	frac_digits = duk_to_int_check_range(ctx, 0, 0, 20);

	n2s_flags = DUK_N2S_FLAG_FORCE_EXP |
	           (frac_undefined ? 0 : DUK_N2S_FLAG_FIXED_FORMAT);

	duk_numconv_stringify(ctx,
	                      10 /*radix*/,
	                      frac_digits + 1 /*leading digit + fractions*/,
	                      n2s_flags /*flags*/);
	return 1;

 use_to_string:
	DUK_ASSERT_TOP(ctx, 2);
	duk_to_string(ctx, -1);
	return 1;
}

int duk_builtin_number_prototype_to_precision(duk_context *ctx) {
	/* The specification has quite awkward order of coercion and
	 * checks for toPrecision().  The operations below are a bit
	 * reordered, within constraints of observable side effects.
	 */

	double d;
	int prec;
	int c;
	int n2s_flags;

	DUK_ASSERT_TOP(ctx, 1);

	d = push_this_number_plain(ctx);
	if (duk_is_undefined(ctx, 0)) {
		goto use_to_string;
	}
	DUK_ASSERT_TOP(ctx, 2);

	duk_to_int(ctx, 0);  /* for side effects */

	c = fpclassify(d);
	if (c == FP_NAN || c == FP_INFINITE) {
		goto use_to_string;
	}

	prec = duk_to_int_check_range(ctx, 0, 1, 21);

	n2s_flags = DUK_N2S_FLAG_FIXED_FORMAT |
	            DUK_N2S_FLAG_NO_ZERO_PAD;

	duk_numconv_stringify(ctx,
	                      10 /*radix*/,
	                      prec /*digits*/,
	                      n2s_flags /*flags*/);
	return 1;

 use_to_string:
	/* Used when precision is undefined; also used for NaN (-> "NaN"),
	 * and +/- infinity (-> "Infinity", "-Infinity").
	 */

	DUK_ASSERT_TOP(ctx, 2);
	duk_to_string(ctx, -1);
	return 1;
}

#line 1 "duk_builtin_object.c"
/*
 *  Object built-ins
 */

/* include removed: duk_internal.h */

int duk_builtin_object_constructor(duk_context *ctx) {
	if (!duk_is_constructor_call(ctx) &&
	    !duk_is_null_or_undefined(ctx, 0)) {
		duk_to_object(ctx, 0);
		return 1;
	}

	if (duk_is_object(ctx, 0)) {
		return 1;
	}

	if (duk_get_type_mask(ctx, 0) &
	    (DUK_TYPE_MASK_STRING | DUK_TYPE_MASK_BOOLEAN | DUK_TYPE_MASK_NUMBER)) {
		duk_to_object(ctx, 0);
		return 1;
	}

	/* FIXME: handling for POINTER and BUFFER */

	duk_push_object_helper(ctx,
	                       DUK_HOBJECT_FLAG_EXTENSIBLE |
	                       DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_OBJECT),
	                       DUK_BIDX_OBJECT_PROTOTYPE);
	return 1;
}

int duk_builtin_object_constructor_get_prototype_of(duk_context *ctx) {
	duk_hobject *h;

	h = duk_require_hobject(ctx, 0);
	DUK_ASSERT(h != NULL);

	/* FIXME: should the API call handle this directly, i.e. attempt
	 * to duk_push_hobject(ctx, null) would push a null instead?
	 */

	if (h->prototype) {
		duk_push_hobject(ctx, h->prototype);
	} else {
		duk_push_null(ctx);
	}
	return 1;
}

int duk_builtin_object_constructor_get_own_property_descriptor(duk_context *ctx) {
	/* FIXME: no need for indirect call */
	return duk_hobject_object_get_own_property_descriptor(ctx);
}

int duk_builtin_object_constructor_get_own_property_names(duk_context *ctx) {
	DUK_ASSERT_TOP(ctx, 1);
	(void) duk_require_hobject(ctx, 0);
	return duk_hobject_get_enumerated_keys(ctx, DUK_ENUM_INCLUDE_NONENUMERABLE |
	                                            DUK_ENUM_OWN_PROPERTIES_ONLY);
}

int duk_builtin_object_constructor_create(duk_context *ctx) {
	duk_hthread *thr = (duk_hthread *) ctx;
	duk_tval *tv;
	duk_hobject *proto = NULL;
	duk_hobject *h;

	DUK_ASSERT_TOP(ctx, 2);

	tv = duk_get_tval(ctx, 0);
	DUK_ASSERT(tv != NULL);
	if (DUK_TVAL_IS_NULL(tv)) {
		;
	} else if (DUK_TVAL_IS_OBJECT(tv)) {
		proto = DUK_TVAL_GET_OBJECT(tv);
		DUK_ASSERT(proto != NULL);
	} else {
		return DUK_RET_TYPE_ERROR;
	}

	/* FIXME: direct helper to create with specific prototype */
	(void) duk_push_object_helper(ctx,
	                              DUK_HOBJECT_FLAG_EXTENSIBLE |
	                              DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_OBJECT),
	                              -1);
	h = duk_get_hobject(ctx, -1);
	DUK_ASSERT(h != NULL);
	DUK_ASSERT(h->prototype == NULL);
	DUK_HOBJECT_SET_PROTOTYPE(thr, h, proto);

	if (!duk_is_undefined(ctx, 1)) {
		/* [ O Properties obj ] */

		/* Use original function.  No need to get it explicitly,
		 * just call the helper.
		 */

		duk_replace(ctx, 0);

		/* [ obj Properties ] */

		return duk_hobject_object_define_properties(ctx);
	}

	/* [ O Properties obj ] */

	return 1;
}

int duk_builtin_object_constructor_define_property(duk_context *ctx) {
	/* FIXME: no need for indirect call */
	return duk_hobject_object_define_property(ctx);
}

int duk_builtin_object_constructor_define_properties(duk_context *ctx) {
	/* FIXME: no need for indirect call */
	return duk_hobject_object_define_properties(ctx);
}

static int seal_freeze_helper(duk_context *ctx, int is_freeze) {
	duk_hthread *thr = (duk_hthread *) ctx;
	duk_hobject *h;

	h = duk_require_hobject(ctx, 0);
	DUK_ASSERT(h != NULL);

	duk_hobject_object_seal_freeze_helper(thr, h, is_freeze /*freeze*/);

	/* Sealed and frozen objects cannot gain any more properties,
	 * so this is a good time to compact them.
	 */
	duk_hobject_compact_props(thr, h);

	return 1;
}

int duk_builtin_object_constructor_seal(duk_context *ctx) {
	return seal_freeze_helper(ctx, 0);
}

int duk_builtin_object_constructor_freeze(duk_context *ctx) {
	return seal_freeze_helper(ctx, 1);
}

int duk_builtin_object_constructor_prevent_extensions(duk_context *ctx) {
	duk_hthread *thr = (duk_hthread *) ctx;
	duk_hobject *h;

	h = duk_require_hobject(ctx, 0);
	DUK_ASSERT(h != NULL);

	DUK_HOBJECT_CLEAR_EXTENSIBLE(h);

	/* A non-extensible object cannot gain any more properties,
	 * so this is a good time to compact.
	 */
	duk_hobject_compact_props(thr, h);
	
	return 1;
}

int duk_builtin_object_constructor_is_sealed(duk_context *ctx) {
	duk_hobject *h;
	int rc;

	h = duk_require_hobject(ctx, 0);
	DUK_ASSERT(h != NULL);

	rc = duk_hobject_object_is_sealed_frozen_helper(h, 0 /*is_frozen*/);
	duk_push_boolean(ctx ,rc);
	return 1;
}

int duk_builtin_object_constructor_is_frozen(duk_context *ctx) {
	duk_hobject *h;
	int rc;

	h = duk_require_hobject(ctx, 0);
	DUK_ASSERT(h != NULL);

	rc = duk_hobject_object_is_sealed_frozen_helper(h, 1 /*is_frozen*/);
	duk_push_boolean(ctx, rc);
	return 1;
}

int duk_builtin_object_constructor_is_extensible(duk_context *ctx) {
	duk_hobject *h;

	h = duk_require_hobject(ctx, 0);
	DUK_ASSERT(h != NULL);

	duk_push_boolean(ctx, DUK_HOBJECT_HAS_EXTENSIBLE(h));
	return 1;
}

int duk_builtin_object_constructor_keys(duk_context *ctx) {
	DUK_ASSERT_TOP(ctx, 1);
	(void) duk_require_hobject(ctx, 0);
	return duk_hobject_get_enumerated_keys(ctx, DUK_ENUM_OWN_PROPERTIES_ONLY);
}

int duk_builtin_object_prototype_to_string(duk_context *ctx) {
	duk_hthread *thr = (duk_hthread *) ctx;

	duk_push_this(ctx);
	duk_push_string(ctx, "[object ");

	if (duk_is_undefined(ctx, -2)) {
		duk_push_string(ctx, "Undefined");
	} else if (duk_is_null(ctx, -2)) {
		duk_push_string(ctx, "Null");
	} else {
		duk_hobject *h_this;
		duk_hstring *h_classname;

		duk_to_object(ctx, -2);
		h_this = duk_get_hobject(ctx, -2);
		DUK_ASSERT(h_this != NULL);

		h_classname = DUK_HOBJECT_GET_CLASS_STRING(thr->heap, h_this);
		DUK_ASSERT(h_classname != NULL);

		duk_push_hstring(ctx, h_classname);
	}

	duk_push_string(ctx, "]");
	duk_concat(ctx, 3);
	return 1;
}

int duk_builtin_object_prototype_to_locale_string(duk_context *ctx) {
	DUK_ASSERT_TOP(ctx, 0);
	duk_push_this_coercible_to_object(ctx);
	duk_get_prop_stridx(ctx, 0, DUK_STRIDX_TO_STRING);
	if (!duk_is_callable(ctx, 1)) {
		return DUK_RET_TYPE_ERROR;
	}
	duk_dup(ctx, 0);  /* -> [ O toString O ] */
	duk_call_method(ctx, 0);  /* FIXME: call method tailcall? */
	return 1;
}

int duk_builtin_object_prototype_value_of(duk_context *ctx) {
	duk_push_this_coercible_to_object(ctx);
	return 1;
}

int duk_builtin_object_prototype_is_prototype_of(duk_context *ctx) {
	duk_hthread *thr = (duk_hthread *) ctx;
	duk_hobject *h_v;
	duk_hobject *h_obj;

	DUK_ASSERT_TOP(ctx, 1);

	h_v = duk_get_hobject(ctx, 0);
	if (!h_v) {
		duk_push_false(ctx);  /* FIXME: tail call: return duk_push_false(ctx) */
		return 1;
	}

	duk_push_this_coercible_to_object(ctx);
	h_obj = duk_get_hobject(ctx, 1);
	DUK_ASSERT(h_obj != NULL);

	/* E5.1 Section 15.2.4.6, step 3.a, lookup proto once before compare */
	duk_push_boolean(ctx, duk_hobject_prototype_chain_contains(thr, h_v->prototype, h_obj));
	return 1;
}

int duk_builtin_object_prototype_has_own_property(duk_context *ctx) {
	return duk_hobject_object_ownprop_helper(ctx, 0 /*required_desc_flags*/);
}

int duk_builtin_object_prototype_property_is_enumerable(duk_context *ctx) {
	return duk_hobject_object_ownprop_helper(ctx, DUK_PROPDESC_FLAG_ENUMERABLE /*required_desc_flags*/);
}

#line 1 "duk_builtin_regexp.c"
/*
 *  RegExp built-ins
 */

/* include removed: duk_internal.h */

#ifdef DUK_USE_REGEXP_SUPPORT

static void get_this_regexp(duk_context *ctx) {
	duk_hobject *h;

	duk_push_this(ctx);
	h = duk_require_hobject_with_class(ctx, -1, DUK_HOBJECT_CLASS_REGEXP);
	DUK_ASSERT(h != NULL);
	DUK_UNREF(h);
	duk_insert(ctx, 0);  /* prepend regexp to valstack 0 index */
}

/* FIXME: much to improve (code size) */
int duk_builtin_regexp_constructor(duk_context *ctx) {
	duk_hthread *thr = (duk_hthread *) ctx;
	duk_hobject *h_pattern;

	DUK_ASSERT_TOP(ctx, 2);
	h_pattern = duk_get_hobject(ctx, 0);

	if (!duk_is_constructor_call(ctx) &&
	    h_pattern != NULL &&
	    DUK_HOBJECT_GET_CLASS_NUMBER(h_pattern) == DUK_HOBJECT_CLASS_REGEXP &&
	    duk_is_undefined(ctx, 1)) {
		/* Called as a function, pattern has [[Class]] "RegExp" and
		 * flags is undefined -> return object as is.
		 */
		duk_dup(ctx, 0);
		return 1;
	}

	/* Else functionality is identical for function call and constructor
	 * call.
	 */

	if (h_pattern != NULL &&
	    DUK_HOBJECT_GET_CLASS_NUMBER(h_pattern) == DUK_HOBJECT_CLASS_REGEXP) {
		if (duk_is_undefined(ctx, 1)) {
			int flag_g, flag_i, flag_m;
			duk_get_prop_stridx(ctx, 0, DUK_STRIDX_SOURCE);

			/* FIXME: very awkward handling of flags */
			duk_get_prop_stridx(ctx, 0, DUK_STRIDX_GLOBAL);
			flag_g = duk_to_boolean(ctx, -1);
			duk_get_prop_stridx(ctx, 0, DUK_STRIDX_IGNORE_CASE);
			flag_i = duk_to_boolean(ctx, -1);
			duk_get_prop_stridx(ctx, 0, DUK_STRIDX_MULTILINE);
			flag_m = duk_to_boolean(ctx, -1);
			duk_pop_n(ctx, 3);

			duk_push_sprintf(ctx, "%s%s%s",
			                 (flag_g ? "g" : ""),
			                 (flag_i ? "i" : ""),
			                 (flag_m ? "m" : ""));

			/* [ ... pattern flags ] */
		} else {
			return DUK_RET_TYPE_ERROR;
		}
	} else {
		if (duk_is_undefined(ctx, 0)) {
			duk_push_string(ctx, "");
		} else {
			duk_dup(ctx, 0);
			duk_to_string(ctx, -1);
		}
		if (duk_is_undefined(ctx, 1)) {
			duk_push_string(ctx, "");
		} else {
			duk_dup(ctx, 1);
			duk_to_string(ctx, -1);
		}

		/* [ ... pattern flags ] */
	}

	DUK_DDDPRINT("RegExp constructor/function call, pattern=%!T, flags=%!T",
	             duk_get_tval(ctx, -2), duk_get_tval(ctx, -1));

	/* [ ... pattern flags ] */

	duk_regexp_compile(thr);

	/* [ ... bytecode escaped_source ] */

	duk_regexp_create_instance(thr);

	/* [ ... RegExp ] */

	return 1;
}

int duk_builtin_regexp_prototype_exec(duk_context *ctx) {
	get_this_regexp(ctx);

	/* [ regexp input ] */

	duk_regexp_match((duk_hthread *) ctx);

	/* [ result ] */

	return 1;
}

int duk_builtin_regexp_prototype_test(duk_context *ctx) {
	get_this_regexp(ctx);

	/* [ regexp input ] */

	/* result object is created and discarded; wasteful but saves code space */
	duk_regexp_match((duk_hthread *) ctx);

	/* [ result ] */

	duk_push_boolean(ctx, (duk_is_null(ctx, -1) ? 0 : 1));

	return 1;
}

int duk_builtin_regexp_prototype_to_string(duk_context *ctx) {
	duk_hstring *h_bc;
	int re_flags;

	get_this_regexp(ctx);

	/* [ regexp ] */

	duk_get_prop_stridx(ctx, 0, DUK_STRIDX_SOURCE);
	duk_get_prop_stridx(ctx, 0, DUK_STRIDX_INT_BYTECODE);
	h_bc = duk_get_hstring(ctx, -1);
	DUK_ASSERT(h_bc != NULL);
	DUK_ASSERT(DUK_HSTRING_GET_BYTELEN(h_bc) >= 1);
	DUK_ASSERT(DUK_HSTRING_GET_CHARLEN(h_bc) >= 1);
	DUK_ASSERT(DUK_HSTRING_GET_DATA(h_bc)[0] < 0x80);
	re_flags = (int) DUK_HSTRING_GET_DATA(h_bc)[0];

	/* [ regexp source bytecode ] */

	duk_push_sprintf(ctx, "/%s/%s%s%s",
	                 duk_get_string(ctx, -2),
	                 (re_flags & DUK_RE_FLAG_GLOBAL) ? "g" : "",
	                 (re_flags & DUK_RE_FLAG_IGNORE_CASE) ? "i" : "",
	                 (re_flags & DUK_RE_FLAG_MULTILINE) ? "m" : "");

	return 1;
}

#else  /* DUK_USE_REGEXP_SUPPORT */

int duk_builtin_regexp_prototype_exec(duk_context *ctx) {
	return DUK_RET_UNSUPPORTED_ERROR;
}

int duk_builtin_regexp_prototype_test(duk_context *ctx) {
	return DUK_RET_UNSUPPORTED_ERROR;
}

int duk_builtin_regexp_prototype_to_string(duk_context *ctx) {
	return DUK_RET_UNSUPPORTED_ERROR;
}

#endif  /* DUK_USE_REGEXP_SUPPORT */

/*

could also map flag values as follows:

"gim\0gi\0gm\0g\0"

flags	desc		offset in above string
0	(none)		3
1	g		11	
2	i		5
3	gi		4
4	m		2
5	gm		7
6	im		1
7	gim		0

*/

#line 1 "duk_builtin_string.c"
/*
 *  String built-ins
 */

/* include removed: duk_internal.h */

/*
 *  Constructor
 */

int duk_builtin_string_constructor(duk_context *ctx) {
	/* String constructor needs to distinguish between an argument not given at all
	 * vs. given as 'undefined'.  We're a vararg function to handle this properly.
	 */

	if (duk_get_top(ctx) == 0) {
		duk_push_hstring_stridx(ctx, DUK_STRIDX_EMPTY_STRING);
	} else {
		duk_to_string(ctx, 0);
	}
	DUK_ASSERT(duk_is_string(ctx, 0));
	duk_set_top(ctx, 1);

	if (duk_is_constructor_call(ctx)) {
		duk_push_object_helper(ctx,
		                       DUK_HOBJECT_FLAG_EXTENSIBLE |
		                       DUK_HOBJECT_FLAG_SPECIAL_STRINGOBJ |
		                       DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_STRING),
		                       DUK_BIDX_STRING_PROTOTYPE);

		/* String object internal value is immutable */
		duk_dup(ctx, 0);
		duk_def_prop_stridx(ctx, -2, DUK_STRIDX_INT_VALUE, DUK_PROPDESC_FLAGS_NONE);
	}
	/* Note: unbalanced stack on purpose */

	return 1;
}

int duk_builtin_string_constructor_from_char_code(duk_context *ctx) {
	duk_hthread *thr = (duk_hthread *) ctx;
	duk_hbuffer_dynamic *h;
	size_t i, n;
	unsigned int cp;

	/* FIXME: create a helper to create a UTF-8 string from multiple
	 * codepoints in one go.  Expose through C API?
	 */

	n = duk_get_top(ctx);
	duk_push_dynamic_buffer(ctx, 0);  /* FIXME: initial size estimate from 'n' */
	h = (duk_hbuffer_dynamic *) duk_get_hbuffer(ctx, -1);

	for (i = 0; i < n; i++) {
		cp = duk_to_uint16(ctx, i);
		duk_hbuffer_append_cesu8(thr, h, cp);
	}

	duk_to_string(ctx, -1);
	return 1;
}

/*
 *  toString(), valueOf()
 */

int duk_builtin_string_prototype_to_string(duk_context *ctx) {
	duk_tval *tv;

	duk_push_this(ctx);
	tv = duk_require_tval(ctx, -1);
	DUK_ASSERT(tv != NULL);

	if (DUK_TVAL_IS_STRING(tv)) {
		/* return as is */
		return 1;
	} else if (DUK_TVAL_IS_OBJECT(tv)) {
		duk_hobject *h = DUK_TVAL_GET_OBJECT(tv);
		DUK_ASSERT(h != NULL);

		/* Must be a "string object", i.e. class "String" */
		if (DUK_HOBJECT_GET_CLASS_NUMBER(h) != DUK_HOBJECT_CLASS_STRING) {
			goto type_error;
		}

		duk_get_prop_stridx(ctx, -1, DUK_STRIDX_INT_VALUE);
		DUK_ASSERT(duk_is_string(ctx, -1));

		return 1;
	} else {
		goto type_error;
	}

	/* never here, but fall through */

 type_error:
	return DUK_RET_TYPE_ERROR;
}

int duk_builtin_string_prototype_value_of(duk_context *ctx) {
	/* FIXME: can use same native function */
	return duk_builtin_string_prototype_to_string(ctx);
}

/*
 *  Character and charcode access
 */

/* FIXME: charAt() and charCodeAt() could probably use a shared helper. */

int duk_builtin_string_prototype_char_at(duk_context *ctx) {
	int pos;

	/* FIXME: faster implementation */
	/* FIXME: handling int values outside C int range, currently
	 * duk_to_int() coerces to min/max int, so this works passably.
	 */

	duk_push_this_coercible_to_string(ctx);
	pos = duk_to_int(ctx, 0);
	duk_substring(ctx, -1, pos, pos + 1);
	return 1;
}

int duk_builtin_string_prototype_char_code_at(duk_context *ctx) {
	duk_hthread *thr = (duk_hthread *) ctx;
	int pos;
	duk_u32 boff;
	duk_hstring *h;
	duk_u8 *p, *p_start, *p_end;
	duk_u32 cp;
	int clamped;

	/* FIXME: faster implementation */

	DUK_DDDPRINT("arg=%!T", duk_get_tval(ctx, 0));

	duk_push_this_coercible_to_string(ctx);
	h = duk_get_hstring(ctx, -1);
	DUK_ASSERT(h != NULL);

	pos = duk_to_int_clamped_raw(ctx,
	                             0 /*index*/,
	                             0 /*min(incl)*/,
	                             DUK_HSTRING_GET_CHARLEN(h) - 1 /*max(incl*/,
	                             &clamped /*clamped*/);
	if (clamped) {
		duk_push_number(ctx, NAN);  /* FIXME: best constant for NAN? */
		return 1;
	}

	boff = duk_heap_strcache_offset_char2byte(thr, h, (duk_u32) pos);
	DUK_DDDPRINT("charCodeAt: pos=%d -> boff=%d, str=%!O", pos, boff, h);
	DUK_ASSERT(boff >= 0 && boff < DUK_HSTRING_GET_BYTELEN(h));
	p_start = DUK_HSTRING_GET_DATA(h);
	p_end = p_start + DUK_HSTRING_GET_BYTELEN(h);
	p = p_start + boff;
	DUK_DDDPRINT("p_start=%p, p_end=%p, p=%p", (void *) p_start, (void *) p_end, (void *) p);

	/* FIXME: this may throw an error, though not for valid E5 strings - is this OK here? */
	cp = duk_unicode_xutf8_get_u32_checked(thr, &p, p_start, p_end);

	/* FIXME: push_uint or push_u32 */
	duk_push_number(ctx, (double) cp);
	return 1;
}

/*
 *  substring(), substr(), slice()
 */

/* FIXME: any chance of merging these three similar algorithms? */

int duk_builtin_string_prototype_substring(duk_context *ctx) {
	duk_hstring *h;
	int start_pos;
	int end_pos;
	int len;

	duk_push_this_coercible_to_string(ctx);
	h = duk_get_hstring(ctx, -1);
	DUK_ASSERT(h != NULL);
	len = DUK_HSTRING_GET_CHARLEN(h);

	/* [ start end str ] */

	/* FIXME: int clamping does not support full string range,
	 * needs type fixing.
	 */

	start_pos = duk_to_int_clamped(ctx, 0, 0, len);
	if (duk_is_undefined(ctx, 1)) {
		end_pos = len;
	} else {
		end_pos = duk_to_int_clamped(ctx, 1, 0, len);
	}
	DUK_ASSERT(start_pos >= 0 && start_pos <= len);
	DUK_ASSERT(end_pos >= 0 && end_pos <= len);

	if (start_pos > end_pos) {
		int tmp = start_pos;
		start_pos = end_pos;
		end_pos = tmp;
	}

	DUK_ASSERT(end_pos >= start_pos);

	duk_substring(ctx, -1, (size_t) start_pos, (size_t) end_pos);
	return 1;
}

#ifdef DUK_USE_SECTION_B
int duk_builtin_string_prototype_substr(duk_context *ctx) {
	duk_hstring *h;
	int start_pos;
	int end_pos;
	int len;

	/* Unlike non-obsolete String calls, substr() algorithm in E5.1
	 * specification will happily coerce undefined and null to strings
	 * ("undefined" and "null").
	 */
	duk_push_this(ctx);
	duk_to_string(ctx, -1);
	h = duk_get_hstring(ctx, -1);
	DUK_ASSERT(h != NULL);
	len = DUK_HSTRING_GET_CHARLEN(h);

	/* [ start length str ] */

	/* FIXME: int clamping does not support full string range,
	 * needs type fixing.
	 */

	/* The implementation for computing of start_pos and end_pos differs
	 * from the standard algorithm, but is intended to result in the exactly
	 * same behavior.  This is not always obvious.
	 */

	/* combines steps 2 and 5; -len ensures max() not needed for step 5 */
	start_pos = duk_to_int_clamped(ctx, 0, -len, len);
	if (start_pos < 0) {
		start_pos = len + start_pos;
	}
	DUK_ASSERT(start_pos >= 0 && start_pos <= len);

	/* combines steps 3, 6; step 7 is not needed */
	if (duk_is_undefined(ctx, 1)) {
		end_pos = len;
	} else {
		DUK_ASSERT(start_pos <= len);
		end_pos = start_pos + duk_to_int_clamped(ctx, 1, 0, len - start_pos);
	}
	DUK_ASSERT(start_pos >= 0 && start_pos <= len);
	DUK_ASSERT(end_pos >= 0 && end_pos <= len);
	DUK_ASSERT(end_pos >= start_pos);

	duk_substring(ctx, -1, (size_t) start_pos, (size_t) end_pos);
	return 1;
}
#endif  /* DUK_USE_SECTION_B */

int duk_builtin_string_prototype_slice(duk_context *ctx) {
	duk_hstring *h;
	int start_pos;
	int end_pos;
	int len;

	duk_push_this_coercible_to_string(ctx);
	h = duk_get_hstring(ctx, -1);
	DUK_ASSERT(h != NULL);
	len = DUK_HSTRING_GET_CHARLEN(h);

	/* [ start end str ] */

	/* FIXME: int clamping does not support full string range,
	 * needs type fixing.
	 */

	start_pos = duk_to_int_clamped(ctx, 0, -len, len);
	if (start_pos < 0) {
		start_pos = len + start_pos;
	}
	if (duk_is_undefined(ctx, 1)) {
		end_pos = len;
	} else {
		end_pos = duk_to_int_clamped(ctx, 1, -len, len);
		if (end_pos < 0) {
			end_pos = len + end_pos;
		}
	}
	DUK_ASSERT(start_pos >= 0 && start_pos <= len);
	DUK_ASSERT(end_pos >= 0 && end_pos <= len);

	if (end_pos < start_pos) {
		end_pos = start_pos;
	}

	DUK_ASSERT(end_pos >= start_pos);

	duk_substring(ctx, -1, (size_t) start_pos, (size_t) end_pos);
	return 1;
}

/*
 *  Case conversion
 */

static int caseconv_helper(duk_context *ctx, int uppercase) {
	duk_hthread *thr = (duk_hthread *) ctx;

	duk_push_this_coercible_to_string(ctx);
	duk_unicode_case_convert_string(thr, uppercase);
	return 1;
}

int duk_builtin_string_prototype_to_lower_case(duk_context *ctx) {
	return caseconv_helper(ctx, 0 /*uppercase*/);
}

int duk_builtin_string_prototype_to_upper_case(duk_context *ctx) {
	return caseconv_helper(ctx, 1 /*uppercase*/);
}

int duk_builtin_string_prototype_to_locale_lower_case(duk_context *ctx) {
	/* Currently no locale specific case conversion */
	/* FIXME: use same native function */
	return duk_builtin_string_prototype_to_lower_case(ctx);
}

int duk_builtin_string_prototype_to_locale_upper_case(duk_context *ctx) {
	/* Currently no locale specific case conversion */
	/* FIXME: use same native function */
	return duk_builtin_string_prototype_to_upper_case(ctx);
}

/*
 *  indexOf() and lastIndexOf()
 */

static int string_indexof_helper(duk_context *ctx, int is_lastindexof) {
	duk_hthread *thr = (duk_hthread *) ctx;
	duk_hstring *h_this;
	duk_hstring *h_search;
	int clen_this;
	int cpos;
	int bpos;
	duk_u8 *p_start, *p_end, *p;
	duk_u8 *q_start;
	size_t q_blen;
	duk_u8 firstbyte;
	duk_u8 t;

	duk_push_this_coercible_to_string(ctx);
	h_this = duk_get_hstring(ctx, -1);
	DUK_ASSERT(h_this != NULL);
	clen_this = DUK_HSTRING_GET_CHARLEN(h_this);

	h_search = duk_to_hstring(ctx, 0);
	DUK_ASSERT(h_search != NULL);
	q_start = DUK_HSTRING_GET_DATA(h_search);
	q_blen = (size_t) DUK_HSTRING_GET_BYTELEN(h_search);

	duk_to_number(ctx, 1);
	if (duk_is_nan(ctx, 1) && is_lastindexof) {
		/* indexOf: NaN should cause pos to be zero.
		 * lastIndexOf: NaN should cause pos to be +Infinity
	 	 * (and later be clamped to len).
		 */
		cpos = clen_this;
	} else {
		cpos = duk_to_int_clamped(ctx, 1, 0, clen_this);
	}

	/* Empty searchstring always matches; cpos must be clamped here. */
	if (q_blen == 0) {
		duk_push_int(ctx, cpos);
		return 1;
	}

	bpos = (int) duk_heap_strcache_offset_char2byte(thr, h_this, (duk_u32) cpos);

	p_start = DUK_HSTRING_GET_DATA(h_this);
	p_end = p_start + DUK_HSTRING_GET_BYTELEN(h_this);
	p = p_start + bpos;

	/* This loop is optimized for size.  For speed, there should be
	 * two separate loops, and we should ensure that memcmp() can be
	 * used without an extra "will searchstring fit" check.  Doing
	 * the preconditioning for 'p' and 'p_end' is easy but cpos
	 * must be updated if 'p' is wound back (backward scanning).
	 */

	firstbyte = q_start[0];
	while (p <= p_end && p >= p_start) {
		t = *p;

		/* For Ecmascript strings, this check can only match for
		 * initial UTF-8 bytes (not continuation bytes).
		 */

		if ((t == firstbyte) && ((p_end - p) >= q_blen)) {
			DUK_ASSERT(q_blen > 0);  /* no issues with memcmp() zero size, even if broken */
			if (memcmp(p, q_start, q_blen) == 0) {
				duk_push_int(ctx, cpos);
				return 1;
			}
		}

		/* track cpos while scanning */
		if (is_lastindexof) {
			/* when going backwards, we decrement cpos 'early';
			 * 'p' may point to a continuation byte of the char
			 * at offset 'cpos', but that's OK because we'll
			 * backtrack all the way to the initial byte.
			 */
			if ((t & 0xc0) != 0x80) {
				cpos--;
			}
			p--;
		} else {
			if ((t & 0xc0) != 0x80) {
				cpos++;
			}
			p++;
		}
	}

	/* Not found.  Empty string case is handled specially above. */
	duk_push_int(ctx, -1);
	return 1;
}

int duk_builtin_string_prototype_index_of(duk_context *ctx) {
	return string_indexof_helper(ctx, 0 /*is_lastindexof*/);
}

int duk_builtin_string_prototype_last_index_of(duk_context *ctx) {
	/* -1 is used because the generated x86 load is shorter than for 1 */
	return string_indexof_helper(ctx, -1 /*is_lastindexof*/);
}

/*
 *  replace()
 */

/* FIXME: the current implementation works but is quite clunky; it compiles
 * to almost 1,5kB of x86 code so it needs to be simplified (better approach,
 * shared helpers, etc).
 */

/* FIXME: some ideas for refactoring:
 * - a primitive to convert a string into a regexp matcher (reduces matching
 *   code at the cost of making matching much slower)
 * - use replace() as a basic helper for match() and split(), which are both
 *   much simpler
 */

int duk_builtin_string_prototype_replace(duk_context *ctx) {
	duk_hthread *thr = (duk_hthread *) ctx;
	duk_hstring *h_input;
	duk_hstring *h_repl;
	duk_hstring *h_match;
	duk_hstring *h_search;
	duk_hobject *h_re;
	duk_hbuffer_dynamic *h_buf;
	int is_regexp;
	int is_global;
	int is_repl_func;
	duk_u32 match_start_coff, match_start_boff;
	int match_caps;
	duk_u32 prev_match_end_boff;
	duk_u8 *r_start, *r_end, *r;   /* repl string scan */

	DUK_ASSERT_TOP(ctx, 2);
	duk_push_this_coercible_to_string(ctx);
	h_input = duk_get_hstring(ctx, -1);
	DUK_ASSERT(h_input != NULL);
	duk_push_dynamic_buffer(ctx, 0);
	h_buf = (duk_hbuffer_dynamic *) duk_get_hbuffer(ctx, -1);
	DUK_ASSERT(h_buf != NULL);
	DUK_ASSERT_TOP(ctx, 4);

	/* stack[0] = search value
	 * stack[1] = replace value
	 * stack[2] = input string
	 * stack[3] = result buffer
	 */

	h_re = duk_get_hobject_with_class(ctx, 0, DUK_HOBJECT_CLASS_REGEXP);
	if (h_re) {
		is_regexp = 1;
		duk_get_prop_stridx(ctx, 0, DUK_STRIDX_GLOBAL);
		DUK_ASSERT(duk_is_boolean(ctx, -1));
		is_global = duk_to_boolean(ctx, -1);
		duk_pop(ctx);

		if (is_global) {
			/* start match from beginning */
			duk_push_int(ctx, 0);
			duk_put_prop_stridx(ctx, 0, DUK_STRIDX_LAST_INDEX);
		}
	} else {
		duk_to_string(ctx, 0);
		is_regexp = 0;
		is_global = 0;
	}

	if (duk_is_function(ctx, 1)) {
		is_repl_func = 1;
		r_start = NULL;
		r_end = NULL;
	} else {
		is_repl_func = 0;
		h_repl = duk_to_hstring(ctx, 1);
		DUK_ASSERT(h_repl != NULL);
		r_start = DUK_HSTRING_GET_DATA(h_repl);
		r_end = r_start + DUK_HSTRING_GET_BYTELEN(h_repl);
	}

	prev_match_end_boff = 0;

	for (;;) {
		/*
		 *  If matching with a regexp:
		 *    - non-global RegExp: lastIndex not touched on a match, zeroed
		 *      on a non-match
		 *    - global RegExp: on match, lastIndex will be updated by regexp
		 *      executor to point to next char after the matching part (so that
		 *      characters in the matching part are not matched again)
		 *
		 *  If matching with a string:
		 *    - always non-global match, find first occurrence
		 *
		 *  We need:
		 *    - The character offset of start-of-match for the replacer function
		 *    - The byte offsets for start-of-match and end-of-match to implement
		 *      the replacement values $&, $`, and $', and to copy non-matching
		 *      input string portions (including header and trailer) verbatim.
		 *
		 *  NOTE: the E5.1 specification is a bit vague how the RegExp should
		 *  behave in the replacement process; e.g. is matching done first for
		 *  all matches (in the global RegExp case) before any replacer calls
		 *  are made?  See: test-builtin-string-proto-replace.js for discussion.
		 */

		DUK_ASSERT_TOP(ctx, 4);

		if (is_regexp) {
			duk_dup(ctx, 0);
			duk_dup(ctx, 2);
			duk_regexp_match(thr);  /* [ ... regexp input ] -> [ res_obj ] */
			if (!duk_is_object(ctx, -1)) {
				duk_pop(ctx);
				break;
			}

			duk_get_prop_stridx(ctx, -1, DUK_STRIDX_INDEX);
			DUK_ASSERT(duk_is_number(ctx, -1));
			match_start_coff = duk_get_int(ctx, -1);
			duk_pop(ctx);

			duk_get_prop_index(ctx, -1, 0);
			DUK_ASSERT(duk_is_string(ctx, -1));
			h_match = duk_get_hstring(ctx, -1);
			DUK_ASSERT(h_match != NULL);
			duk_pop(ctx);  /* h_match is borrowed, remains reachable through match_obj */

			if (DUK_HSTRING_GET_BYTELEN(h_match) == 0) {
				/* This should be equivalent to match() algorithm step 8.f.iii.2:
				 * detect an empty match and allow it, but don't allow it twice.
				 */
				duk_u32 last_index;

				duk_get_prop_stridx(ctx, 0, DUK_STRIDX_LAST_INDEX);
				last_index = duk_get_int(ctx, -1);  /* FIXME: duk_get_uint32() */
				DUK_DDDPRINT("empty match, bump lastIndex: %d -> %d", last_index, last_index + 1);
				duk_pop(ctx);
				duk_push_int(ctx, last_index + 1);
				duk_put_prop_stridx(ctx, 0, DUK_STRIDX_LAST_INDEX);
			}

			match_caps = duk_get_length(ctx, -1);
		} else {
			duk_u8 *p_start, *p_end, *p;   /* input string scan */
			duk_u8 *q_start;               /* match string */
			size_t q_blen;

			DUK_ASSERT(!is_global);  /* single match always */

			p_start = DUK_HSTRING_GET_DATA(h_input);
			p_end = p_start + DUK_HSTRING_GET_BYTELEN(h_input);
			p = p_start;

			h_search = duk_get_hstring(ctx, 0);
			DUK_ASSERT(h_search != NULL);
			q_start = DUK_HSTRING_GET_DATA(h_search);
			q_blen = DUK_HSTRING_GET_BYTELEN(h_search);

			p_end -= q_blen;  /* ensure full memcmp() fits in while */

			match_start_coff = 0;

			while (p <= p_end) {
				/* FIXME: wrapped utility memcmp() which is guaranteed to work
				 * even if byte count is zero?
				 */
				DUK_ASSERT(p + q_blen <= DUK_HSTRING_GET_DATA(h_input) + DUK_HSTRING_GET_BYTELEN(h_input));
				if (memcmp((void *) p, (void *) q_start, (size_t) q_blen) == 0) {
					duk_dup(ctx, 0);
					h_match = duk_get_hstring(ctx, -1);
					DUK_ASSERT(h_match != NULL);
					match_caps = 0;
					goto found;
				}

				/* track utf-8 non-continuation bytes */
				if ((p[0] & 0xc0) != 0x80) {
					match_start_coff++;
				}
				p++;
			}

			/* not found */
			break;
		}
	 found:

		/* stack[0] = search value
		 * stack[1] = replace value
		 * stack[2] = input string
		 * stack[3] = result buffer
		 * stack[4] = regexp match OR match string
		 */

		match_start_boff = duk_heap_strcache_offset_char2byte(thr, h_input, match_start_coff);

		duk_hbuffer_append_bytes(thr,
		                         h_buf,
		                         DUK_HSTRING_GET_DATA(h_input) + prev_match_end_boff,
		                         (size_t) (match_start_boff - prev_match_end_boff));

		prev_match_end_boff = match_start_boff + DUK_HSTRING_GET_BYTELEN(h_match);

		if (is_repl_func) {
			int idx_args;
			duk_hstring *h_repl;
			duk_u32 idx;

			/* regexp res_obj is at index 4 */

			duk_dup(ctx, 1);
			idx_args = duk_get_top(ctx);

			if (is_regexp) {
				duk_require_stack(ctx, match_caps + 2);
				for (idx = 0; idx < match_caps; idx++) {
					/* match followed by capture(s) */
					duk_get_prop_index(ctx, 4, idx);
				}
			} else {
				/* match == search string, by definition */
				duk_dup(ctx, 0);
			}
			duk_push_int(ctx, match_start_coff);
			duk_dup(ctx, 2);

			/* [ ... replacer match [captures] match_char_offset input ] */

			duk_call(ctx, duk_get_top(ctx) - idx_args);
			duk_to_string(ctx, -1);  /* -> [ ... repl_value ] */
			h_repl = duk_get_hstring(ctx, -1);
			DUK_ASSERT(h_repl != NULL);
			duk_hbuffer_append_hstring(thr, h_buf, h_repl);
			duk_pop(ctx);  /* repl_value */
		} else {
			r = r_start;

			while (r < r_end) {
				int ch1, ch2, ch3;
				int capnum, captmp, capadv;
				size_t left;

				ch1 = *r++;
				if (ch1 != (int) '$') {
					goto repl_write;
				}
				left = r_end - r;

				if (left <= 0) {
					goto repl_write;
				}

				ch2 = r[0];
				switch (ch2) {
				case (int) '$': {
					ch1 = (1 << 8) + (int) '$';
					goto repl_write;
				}
				case '&': {
					duk_hbuffer_append_hstring(thr, h_buf, h_match);
					r++;
					continue;
				}
				case '`': {
					duk_hbuffer_append_bytes(thr,
					                         h_buf,
					                         DUK_HSTRING_GET_DATA(h_input),
					                         match_start_boff);
					r++;
					continue;
				}
				case '\'': {
					duk_u32 match_end_boff;

					/* Use match charlen instead of bytelen, just in case the input and
					 * match codepoint encodings would have different lengths.
					 */
					match_end_boff = duk_heap_strcache_offset_char2byte(thr,
					                                                    h_input,
					                                                    match_start_coff + DUK_HSTRING_GET_CHARLEN(h_match));

					duk_hbuffer_append_bytes(thr,
					                         h_buf,
					                         DUK_HSTRING_GET_DATA(h_input) + match_end_boff,
					                         DUK_HSTRING_GET_BYTELEN(h_input) - match_end_boff);
					r++;
					continue;
				}
				default: {
					/* FIXME: optional check, match_caps is zero if no regexp,
					 * so dollar will be interpreted literally anyway.
					 */
					if (!is_regexp) {
						goto repl_write;
					}

					if (!(ch2 >= '0' && ch2 <= '9')) {
						goto repl_write;
					}
					capnum = ch2 - (int) '0';
					capadv = 1;

					if (left >= 2) {
						ch3 = r[1];
						if (ch3 >= '0' && ch3 <= '9') {
							captmp = capnum * 10 + (ch3 - (int) '0');
							if (captmp < match_caps) {
								capnum = captmp;
								capadv = 2;
							}
						}
					}

					if (capnum > 0 && capnum < match_caps) {
						DUK_ASSERT(is_regexp != 0);  /* match_caps == 0 without regexps */

						/* regexp res_obj is at offset 4 */
						duk_get_prop_index(ctx, 4, capnum);
						if (duk_is_string(ctx, -1)) {
							DUK_ASSERT(duk_get_hstring(ctx, -1) != NULL);
							duk_hbuffer_append_hstring(thr, h_buf, duk_get_hstring(ctx, -1));
						} else {
							/* undefined -> skip (replaced with empty) */
						}
						duk_pop(ctx);
						r += capadv;
						continue;
					} else {
						goto repl_write;
					}
				}  /* default case */
				}  /* switch (ch2) */

			 repl_write:
				/* ch1 = (r_increment << 8) + byte */
				duk_hbuffer_append_byte(thr, h_buf, (duk_u8) (ch1 & 0xff));
				r += ch1 >> 8;
			}  /* while repl */
		}

		duk_pop(ctx);  /* pop regexp res_obj or match string */

		if (!is_global) {
			break;
		}
	}

	/* trailer */
	duk_hbuffer_append_bytes(thr,
	                         h_buf,
	                         DUK_HSTRING_GET_DATA(h_input) + prev_match_end_boff,
	                         (size_t) (DUK_HSTRING_GET_BYTELEN(h_input) - prev_match_end_boff));

	DUK_ASSERT_TOP(ctx, 4);
	duk_to_string(ctx, -1);
	return 1;
}

/*
 *  split()
 */

/* FIXME: very messy now, but works */
/* FIXME: remove unused variables (they are nominally used so compiled doesn't complain) */
/* FIXME: general cleanup */

int duk_builtin_string_prototype_split(duk_context *ctx) {
	duk_hthread *thr = (duk_hthread *) ctx;
	duk_hstring *h_input;
	duk_hstring *h_sep;
	duk_u32 limit;
	duk_u32 arr_idx;
	int is_regexp;
	int matched;  /* set to 1 if any match exists (needed for empty input special case) */
	duk_u32 prev_match_end_coff, prev_match_end_boff;
	duk_u32 match_start_boff, match_start_coff;
	duk_u32 match_end_boff, match_end_coff;

	duk_push_this_coercible_to_string(ctx);
	h_input = duk_get_hstring(ctx, -1);
	DUK_ASSERT(h_input != NULL);

	duk_push_array(ctx);

	if (duk_is_undefined(ctx, 1)) {
		limit = 0xffffffffU;
	} else {
		limit = duk_to_uint32(ctx, 1);
	}

	if (limit == 0) {
		return 1;
	}

	/* If the separator is a RegExp, make a "clone" of it.  The specification
	 * algorithm calls [[Match]] directly for specific indices; we emulate this
	 * by tweaking lastIndex and using a "force global" variant of duk_regexp_match()
	 * which will use global-style matching even when the RegExp itself is non-global.
	 */

	if (duk_is_undefined(ctx, 0)) {
		/* The spec algorithm first does "R = ToString(separator)" before checking
		 * whether separator is undefined.  Since this is side effect free, we can
		 * skip the ToString() here.
		 */
		duk_dup(ctx, 2);
		duk_put_prop_index(ctx, 3, 0);
		return 1;
	} else if (duk_get_hobject_with_class(ctx, 0, DUK_HOBJECT_CLASS_REGEXP) != NULL) {
		duk_push_hobject(ctx, thr->builtins[DUK_BIDX_REGEXP_CONSTRUCTOR]);
		duk_dup(ctx, 0);
		duk_new(ctx, 1);  /* [ ... RegExp val ] -> [ ... res ] */
		duk_replace(ctx, 0);
		/* lastIndex is initialized to zero by new RegExp() */
		is_regexp = 1;
	} else {
		duk_to_string(ctx, 0);
		is_regexp = 0;
	}

	/* stack[0] = separator (string or regexp)
	 * stack[1] = limit
	 * stack[2] = input string
	 * stack[3] = result array
	 */

	prev_match_end_boff = 0;
	prev_match_end_coff = 0;
	arr_idx = 0;
	matched = 0;

	for (;;) {
		/*
		 *  The specification uses RegExp [[Match]] to attempt match at specific
		 *  offsets.  We don't have such a primitive, so we use an actual RegExp
		 *  and tweak lastIndex.  Since the RegExp may be non-global, we use a
		 *  special variant which forces global-like behavior for matching.
		 */

		DUK_ASSERT_TOP(ctx, 4);

		if (is_regexp) {
			duk_dup(ctx, 0);
			duk_dup(ctx, 2);
			duk_regexp_match_force_global(ctx);  /* [ ... regexp input ] -> [ res_obj ] */
			if (!duk_is_object(ctx, -1)) {
				duk_pop(ctx);
				break;
			}
			matched = 1;

			duk_get_prop_stridx(ctx, -1, DUK_STRIDX_INDEX);
			DUK_ASSERT(duk_is_number(ctx, -1));
			match_start_coff = duk_get_int(ctx, -1);
			match_start_boff = duk_heap_strcache_offset_char2byte(thr, h_input, match_start_coff);
			duk_pop(ctx);

			if (match_start_coff == DUK_HSTRING_GET_CHARLEN(h_input)) {
				/* don't allow an empty match at the end of the string */
				duk_pop(ctx);
				break;
			}

			duk_get_prop_stridx(ctx, 0, DUK_STRIDX_LAST_INDEX);
			DUK_ASSERT(duk_is_number(ctx, -1));
			match_end_coff = duk_get_int(ctx, -1);
			match_end_boff = duk_heap_strcache_offset_char2byte(thr, h_input, match_end_coff);
			duk_pop(ctx);

			/* empty match -> bump and continue */
			if (prev_match_end_boff == match_end_boff) {
				duk_push_int(ctx, match_end_coff + 1);
				duk_put_prop_stridx(ctx, 0, DUK_STRIDX_LAST_INDEX);
				duk_pop(ctx);
				continue;
			}
		} else {
			duk_u8 *p_start, *p_end, *p;   /* input string scan */
			duk_u8 *q_start;               /* match string */
			size_t q_blen, q_clen;

			p_start = DUK_HSTRING_GET_DATA(h_input);
			p_end = p_start + DUK_HSTRING_GET_BYTELEN(h_input);
			p = p_start + prev_match_end_boff;

			h_sep = duk_get_hstring(ctx, 0);
			DUK_ASSERT(h_sep != NULL);
			q_start = DUK_HSTRING_GET_DATA(h_sep);
			q_blen = DUK_HSTRING_GET_BYTELEN(h_sep);
			q_clen = DUK_HSTRING_GET_CHARLEN(h_sep);

			p_end -= q_blen;  /* ensure full memcmp() fits in while */

			match_start_coff = prev_match_end_coff;

			if (q_blen == 0) {
				/* Handle empty separator case: it will always match, and always
				 * triggers the check in step 13.c.iii initially.  Note that we
				 * must skip to either end of string or start of first codepoint,
				 * skipping over any continuation bytes!
				 *
				 * Don't allow an empty string to match at the end of the input.
				 */

				matched = 1;  /* empty separator can always match */

				match_start_coff++;
				p++;
				while (p < p_end) {
					if ((p[0] & 0xc0) != 0x80) {
						goto found;
					}
					p++;
				}
				goto not_found;
			}

			DUK_ASSERT(q_blen > 0 && q_clen > 0);
			while (p <= p_end) {
				DUK_ASSERT(p + q_blen <= DUK_HSTRING_GET_DATA(h_input) + DUK_HSTRING_GET_BYTELEN(h_input));
				DUK_ASSERT(q_blen > 0);  /* no issues with empty memcmp() */
				if (memcmp((void *) p, (void *) q_start, (size_t) q_blen) == 0) {
					/* never an empty match, so step 13.c.iii can't be triggered */
					goto found;
				}

				/* track utf-8 non-continuation bytes */
				if ((p[0] & 0xc0) != 0x80) {
					match_start_coff++;
				}
				p++;
			}

		 not_found:
			/* not found */
			break;

		 found:
			matched = 1;
			match_start_boff = (duk_u32) (p - p_start);
			match_end_coff = match_start_coff + q_clen;
			match_end_boff = match_start_boff + q_blen;

			/* empty match (may happen with empty separator) -> bump and continue */
			if (prev_match_end_boff == match_end_boff) {
				prev_match_end_boff++;
				prev_match_end_coff++;
				continue;
			}
		}

		/* stack[0] = separator (string or regexp)
		 * stack[1] = limit
		 * stack[2] = input string
		 * stack[3] = result array
		 * stack[4] = regexp res_obj (if is_regexp)
		 */

		DUK_DDDPRINT("split; match_start b=%d,c=%d, match_end b=%d,c=%d, prev_end b=%d,c=%d",
		             (int) match_start_boff, (int) match_start_coff,
		             (int) match_end_boff, (int) match_end_coff,
		             (int) prev_match_end_boff, (int) prev_match_end_coff);

		duk_push_lstring(ctx,
		                 (const char *) (DUK_HSTRING_GET_DATA(h_input) + prev_match_end_boff),
		                 (size_t) (match_start_boff - prev_match_end_boff));
		duk_put_prop_index(ctx, 3, arr_idx);
		arr_idx++;
		if (arr_idx >= limit) {
			goto hit_limit;
		}

		if (is_regexp) {
			size_t i, len;

			len = duk_get_length(ctx, 4);
			for (i = 1; i < len; i++) {
				duk_get_prop_index(ctx, 4, i);
				duk_put_prop_index(ctx, 3, arr_idx);
				arr_idx++;
				if (arr_idx >= limit) {
					goto hit_limit;
				}
			}

			duk_pop(ctx);
			/* lastIndex already set up for next match */
		} else {
			/* no action */
		}

		prev_match_end_boff = match_end_boff;
		prev_match_end_coff = match_end_coff;
		continue;
	}

	/* Combined step 11 (empty string special case) and 14-15. */

	DUK_DDDPRINT("split trailer; match_start b=%d,c=%d, match_end b=%d,c=%d, prev_end b=%d,c=%d",
	             (int) match_start_boff, (int) match_start_coff,
	             (int) match_end_boff, (int) match_end_coff,
	             (int) prev_match_end_boff, (int) prev_match_end_coff);

	if (DUK_HSTRING_GET_CHARLEN(h_input) > 0 || !matched) {
		/* Add trailer if:
		 *   a) non-empty input
		 *   b) empty input and no (zero size) match found (step 11)
		 */

		duk_push_lstring(ctx,
		                 (const char *) DUK_HSTRING_GET_DATA(h_input) + prev_match_end_boff,
		                 (size_t) (DUK_HSTRING_GET_BYTELEN(h_input) - prev_match_end_boff));
		duk_put_prop_index(ctx, 3, arr_idx);
		/* No arr_idx update or limit check */
	}

	return 1;

 hit_limit:
	if (is_regexp) {
		duk_pop(ctx);
	}

	return 1;
}

/*
 *  Various
 */

static void to_regexp_helper(duk_context *ctx, int index, int force_new) {
	duk_hthread *thr = (duk_hthread *) ctx;
	duk_hobject *h;

	/* Shared helper for match() steps 3-4, search() steps 3-4. */

	DUK_ASSERT(index >= 0);

	if (force_new) {
		goto do_new;
	}

	h = duk_get_hobject_with_class(ctx, index, DUK_HOBJECT_CLASS_REGEXP);
	if (!h) {
		goto do_new;
	}
	return;

 do_new:
	duk_push_hobject(ctx, thr->builtins[DUK_BIDX_REGEXP_CONSTRUCTOR]);
	duk_dup(ctx, index);
	duk_new(ctx, 1);  /* [ ... RegExp val ] -> [ ... res ] */
	duk_replace(ctx, index);
}

int duk_builtin_string_prototype_search(duk_context *ctx) {
	duk_hthread *thr = (duk_hthread *) ctx;

	/* Easiest way to implement the search required by the specification
	 * is to do a RegExp test() with lastIndex forced to zero.  To avoid
	 * side effects on the argument, "clone" the RegExp if a RegExp was
	 * given as input.
	 *
	 * The global flag of the RegExp should be ignored; setting lastIndex
	 * to zero (which happens when "cloning" the RegExp) should have an
	 * equivalent effect.
	 */

	DUK_ASSERT_TOP(ctx, 1);
	duk_push_this_coercible_to_string(ctx);  /* at index 1 */
	to_regexp_helper(ctx, 0 /*index*/, 1 /*force_new*/);

	/* stack[0] = regexp
	 * stack[1] = string
	 */

	/* Avoid using RegExp.prototype methods, as they're writable and
	 * configurable.
	 */

	duk_dup(ctx, 0);
	duk_dup(ctx, 1);  /* [ ... re_obj input ] */
	duk_regexp_match(thr);  /* -> [ ... res_obj ] */

	if (!duk_is_object(ctx, -1)) {
		duk_push_int(ctx, -1);
		return 1;
	}

	duk_get_prop_stridx(ctx, -1, DUK_STRIDX_INDEX);
	DUK_ASSERT(duk_is_number(ctx, -1));
	return 1;
}

int duk_builtin_string_prototype_match(duk_context *ctx) {
	duk_hthread *thr = (duk_hthread *) ctx;
	int global;
	int prev_last_index;
	int this_index;
	int arr_idx;

	DUK_ASSERT_TOP(ctx, 1);
	duk_push_this_coercible_to_string(ctx);
	to_regexp_helper(ctx, 0 /*index*/, 0 /*force_new*/);
	duk_get_prop_stridx(ctx, 0, DUK_STRIDX_GLOBAL);
	DUK_ASSERT(duk_is_boolean(ctx, -1));  /* 'global' is non-configurable and non-writable */
	global = duk_get_boolean(ctx, -1);
	duk_pop(ctx);
	DUK_ASSERT_TOP(ctx, 2);

	/* stack[0] = regexp
	 * stack[1] = string
	 */

	if (!global) {
		duk_regexp_match(thr);  /* -> [ res_obj ] */
		return 1;  /* return 'res_obj' */
	}

	/* Global case is more complex. */

	/* [ regexp string ] */

	duk_push_int(ctx, 0);
	duk_put_prop_stridx(ctx, 0, DUK_STRIDX_LAST_INDEX);
	duk_push_array(ctx);

	/* [ regexp string res_arr ] */

	prev_last_index = 0;
	arr_idx = 0;

	for (;;) {
		DUK_ASSERT_TOP(ctx, 3);

		duk_dup(ctx, 0);
		duk_dup(ctx, 1);
		duk_regexp_match(thr);  /* -> [ ... regexp string ] -> [ ... res_obj ] */

		if (!duk_is_object(ctx, -1)) {
			duk_pop(ctx);
			break;
		}

		duk_get_prop_stridx(ctx, 0, DUK_STRIDX_LAST_INDEX);
		DUK_ASSERT(duk_is_number(ctx, -1));
		this_index = duk_get_int(ctx, -1);
		duk_pop(ctx);

		if (this_index == prev_last_index) {
			this_index++;
			duk_push_int(ctx, this_index);
			duk_put_prop_stridx(ctx, 0, DUK_STRIDX_LAST_INDEX);
		}
		prev_last_index = this_index;

		duk_get_prop_index(ctx, -1, 0);  /* match string */
		duk_put_prop_index(ctx, 2, arr_idx);
		arr_idx++;
		duk_pop(ctx);  /* res_obj */
	}

	if (arr_idx == 0) {
		duk_push_null(ctx);
	}

	return 1;  /* return 'res_arr' or 'null' */
}

int duk_builtin_string_prototype_concat(duk_context *ctx) {
	/* duk_concat() coerces arguments with ToString() in correct order */
	duk_push_this_coercible_to_string(ctx);
	duk_insert(ctx, 0);  /* FIXME: this is relatively expensive */
	duk_concat(ctx, duk_get_top(ctx));
	return 1;
}

int duk_builtin_string_prototype_trim(duk_context *ctx) {
	DUK_ASSERT_TOP(ctx, 0);
	duk_push_this_coercible_to_string(ctx);
	duk_trim(ctx, 0);
	DUK_ASSERT_TOP(ctx, 1);
	return 1;
}

int duk_builtin_string_prototype_locale_compare(duk_context *ctx) {
	duk_hstring *h1;
	duk_hstring *h2;
	size_t h1_len, h2_len, prefix_len;
	int rc;
	int ret = 0;

	/* The current implementation of localeCompare() is simply a codepoint
	 * by codepoint comparison, implemented with a simple string compare
	 * because UTF-8 should preserve codepoint ordering (assuming valid
	 * shortest UTF-8 encoding).
	 *
	 * The specification requires that the return value must be related
	 * to the sort order: e.g. negative means that 'this' comes before
	 * 'that' in sort order.  We assume an ascending sort order.
	 */

	/* FIXME: could share code with duk_js_ops.c, duk_js_compare_helper */

	duk_push_this_coercible_to_string(ctx);
	h1 = duk_get_hstring(ctx, -1);
	DUK_ASSERT(h1 != NULL);

	h2 = duk_to_hstring(ctx, 0);
	DUK_ASSERT(h2 != NULL);

	h1_len = DUK_HSTRING_GET_BYTELEN(h1);
	h2_len = DUK_HSTRING_GET_BYTELEN(h2);
	prefix_len = (h1_len <= h2_len ? h1_len : h2_len);

	/* memcmp() should return zero (equal) for zero length, but avoid
	 * it because there are some platform specific bugs.  Don't use
	 * strncmp() because it stops comparing at a NUL.
	 */

	if (prefix_len == 0) {
		rc = 0;
		goto skip_memcmp;
	}
	rc = memcmp((const char *) DUK_HSTRING_GET_DATA(h1),
	            (const char *) DUK_HSTRING_GET_DATA(h2),
	            prefix_len);
 skip_memcmp:

	if (rc < 0) {
		ret = -1;
		goto done;
	} else if (rc > 0) {
		ret = 1;
		goto done;
	}

	/* prefix matches, lengths matter now */
	if (h1_len > h2_len) {
		ret = 1;
		goto done;
	} else if (h1_len == h2_len) {
		DUK_ASSERT(ret == 0);
		goto done;
	}
	ret = -1;
	goto done;

 done:
	duk_push_int(ctx, ret);
	return 1;
}

#line 1 "duk_builtin_thread.c"
/*
 *  Thread builtins
 */

/* include removed: duk_internal.h */

int duk_builtin_thread_prototype_to_string(duk_context *ctx) {
	/* FIXME: what to print, something special or just [object Object]? */
	/* FIXME: state */
	duk_push_sprintf(ctx, "[object Thread]");
	return 1;
}

#line 1 "duk_builtin_thrower.c"
/*
 *  Type error thrower, E5 Section 13.2.3.
 */

/* include removed: duk_internal.h */

int duk_builtin_type_error_thrower(duk_context *ctx) {
	return DUK_RET_TYPE_ERROR;
}
#line 1 "duk_builtins.c"
/*
 *  Automatically generated by genbuiltins.py, do not edit!
 */

/* include removed: duk_internal.h */

/* native functions: 190 */
duk_c_function duk_builtin_native_functions[] = {
	(duk_c_function) duk_builtin_array_constructor,
	(duk_c_function) duk_builtin_array_constructor_is_array,
	(duk_c_function) duk_builtin_array_prototype_concat,
	(duk_c_function) duk_builtin_array_prototype_every,
	(duk_c_function) duk_builtin_array_prototype_filter,
	(duk_c_function) duk_builtin_array_prototype_for_each,
	(duk_c_function) duk_builtin_array_prototype_index_of,
	(duk_c_function) duk_builtin_array_prototype_join,
	(duk_c_function) duk_builtin_array_prototype_last_index_of,
	(duk_c_function) duk_builtin_array_prototype_map,
	(duk_c_function) duk_builtin_array_prototype_pop,
	(duk_c_function) duk_builtin_array_prototype_push,
	(duk_c_function) duk_builtin_array_prototype_reduce,
	(duk_c_function) duk_builtin_array_prototype_reduce_right,
	(duk_c_function) duk_builtin_array_prototype_reverse,
	(duk_c_function) duk_builtin_array_prototype_shift,
	(duk_c_function) duk_builtin_array_prototype_slice,
	(duk_c_function) duk_builtin_array_prototype_some,
	(duk_c_function) duk_builtin_array_prototype_sort,
	(duk_c_function) duk_builtin_array_prototype_splice,
	(duk_c_function) duk_builtin_array_prototype_to_locale_string,
	(duk_c_function) duk_builtin_array_prototype_to_string,
	(duk_c_function) duk_builtin_array_prototype_unshift,
	(duk_c_function) duk_builtin_boolean_constructor,
	(duk_c_function) duk_builtin_boolean_prototype_to_string,
	(duk_c_function) duk_builtin_boolean_prototype_value_of,
	(duk_c_function) duk_builtin_date_constructor,
	(duk_c_function) duk_builtin_date_constructor_now,
	(duk_c_function) duk_builtin_date_constructor_parse,
	(duk_c_function) duk_builtin_date_constructor_utc,
	(duk_c_function) duk_builtin_date_prototype_get_date,
	(duk_c_function) duk_builtin_date_prototype_get_day,
	(duk_c_function) duk_builtin_date_prototype_get_full_year,
	(duk_c_function) duk_builtin_date_prototype_get_hours,
	(duk_c_function) duk_builtin_date_prototype_get_milliseconds,
	(duk_c_function) duk_builtin_date_prototype_get_minutes,
	(duk_c_function) duk_builtin_date_prototype_get_month,
	(duk_c_function) duk_builtin_date_prototype_get_seconds,
	(duk_c_function) duk_builtin_date_prototype_get_timezone_offset,
	(duk_c_function) duk_builtin_date_prototype_get_utc_date,
	(duk_c_function) duk_builtin_date_prototype_get_utc_day,
	(duk_c_function) duk_builtin_date_prototype_get_utc_full_year,
	(duk_c_function) duk_builtin_date_prototype_get_utc_hours,
	(duk_c_function) duk_builtin_date_prototype_get_utc_milliseconds,
	(duk_c_function) duk_builtin_date_prototype_get_utc_minutes,
	(duk_c_function) duk_builtin_date_prototype_get_utc_month,
	(duk_c_function) duk_builtin_date_prototype_get_utc_seconds,
	(duk_c_function) duk_builtin_date_prototype_get_year,
	(duk_c_function) duk_builtin_date_prototype_set_date,
	(duk_c_function) duk_builtin_date_prototype_set_full_year,
	(duk_c_function) duk_builtin_date_prototype_set_hours,
	(duk_c_function) duk_builtin_date_prototype_set_milliseconds,
	(duk_c_function) duk_builtin_date_prototype_set_minutes,
	(duk_c_function) duk_builtin_date_prototype_set_month,
	(duk_c_function) duk_builtin_date_prototype_set_seconds,
	(duk_c_function) duk_builtin_date_prototype_set_time,
	(duk_c_function) duk_builtin_date_prototype_set_utc_date,
	(duk_c_function) duk_builtin_date_prototype_set_utc_full_year,
	(duk_c_function) duk_builtin_date_prototype_set_utc_hours,
	(duk_c_function) duk_builtin_date_prototype_set_utc_milliseconds,
	(duk_c_function) duk_builtin_date_prototype_set_utc_minutes,
	(duk_c_function) duk_builtin_date_prototype_set_utc_month,
	(duk_c_function) duk_builtin_date_prototype_set_utc_seconds,
	(duk_c_function) duk_builtin_date_prototype_set_year,
	(duk_c_function) duk_builtin_date_prototype_to_date_string,
	(duk_c_function) duk_builtin_date_prototype_to_iso_string,
	(duk_c_function) duk_builtin_date_prototype_to_json,
	(duk_c_function) duk_builtin_date_prototype_to_locale_date_string,
	(duk_c_function) duk_builtin_date_prototype_to_locale_string,
	(duk_c_function) duk_builtin_date_prototype_to_locale_time_string,
	(duk_c_function) duk_builtin_date_prototype_to_string,
	(duk_c_function) duk_builtin_date_prototype_to_time_string,
	(duk_c_function) duk_builtin_date_prototype_to_utc_string,
	(duk_c_function) duk_builtin_date_prototype_value_of,
	(duk_c_function) duk_builtin_duk_object_addr,
	(duk_c_function) duk_builtin_duk_object_curr,
	(duk_c_function) duk_builtin_duk_object_dec,
	(duk_c_function) duk_builtin_duk_object_enc,
	(duk_c_function) duk_builtin_duk_object_gc,
	(duk_c_function) duk_builtin_duk_object_get_finalizer,
	(duk_c_function) duk_builtin_duk_object_print,
	(duk_c_function) duk_builtin_duk_object_refc,
	(duk_c_function) duk_builtin_duk_object_resume,
	(duk_c_function) duk_builtin_duk_object_set_finalizer,
	(duk_c_function) duk_builtin_duk_object_sleep,
	(duk_c_function) duk_builtin_duk_object_spawn,
	(duk_c_function) duk_builtin_duk_object_time,
	(duk_c_function) duk_builtin_duk_object_yield,
	(duk_c_function) duk_builtin_error_constructor,
	(duk_c_function) duk_builtin_error_prototype_to_string,
	(duk_c_function) duk_builtin_eval_error_constructor,
	(duk_c_function) duk_builtin_function_constructor,
	(duk_c_function) duk_builtin_function_prototype,
	(duk_c_function) duk_builtin_function_prototype_apply,
	(duk_c_function) duk_builtin_function_prototype_bind,
	(duk_c_function) duk_builtin_function_prototype_call,
	(duk_c_function) duk_builtin_function_prototype_to_string,
	(duk_c_function) duk_builtin_global_object_alert,
	(duk_c_function) duk_builtin_global_object_decode_uri,
	(duk_c_function) duk_builtin_global_object_decode_uri_component,
	(duk_c_function) duk_builtin_global_object_encode_uri,
	(duk_c_function) duk_builtin_global_object_encode_uri_component,
	(duk_c_function) duk_builtin_global_object_escape,
	(duk_c_function) duk_builtin_global_object_eval,
	(duk_c_function) duk_builtin_global_object_is_finite,
	(duk_c_function) duk_builtin_global_object_is_nan,
	(duk_c_function) duk_builtin_global_object_parse_float,
	(duk_c_function) duk_builtin_global_object_parse_int,
	(duk_c_function) duk_builtin_global_object_print,
	(duk_c_function) duk_builtin_global_object_unescape,
	(duk_c_function) duk_builtin_json_object_parse,
	(duk_c_function) duk_builtin_json_object_stringify,
	(duk_c_function) duk_builtin_math_object_abs,
	(duk_c_function) duk_builtin_math_object_acos,
	(duk_c_function) duk_builtin_math_object_asin,
	(duk_c_function) duk_builtin_math_object_atan,
	(duk_c_function) duk_builtin_math_object_atan2,
	(duk_c_function) duk_builtin_math_object_ceil,
	(duk_c_function) duk_builtin_math_object_cos,
	(duk_c_function) duk_builtin_math_object_exp,
	(duk_c_function) duk_builtin_math_object_floor,
	(duk_c_function) duk_builtin_math_object_log,
	(duk_c_function) duk_builtin_math_object_max,
	(duk_c_function) duk_builtin_math_object_min,
	(duk_c_function) duk_builtin_math_object_pow,
	(duk_c_function) duk_builtin_math_object_random,
	(duk_c_function) duk_builtin_math_object_round,
	(duk_c_function) duk_builtin_math_object_sin,
	(duk_c_function) duk_builtin_math_object_sqrt,
	(duk_c_function) duk_builtin_math_object_tan,
	(duk_c_function) duk_builtin_number_constructor,
	(duk_c_function) duk_builtin_number_prototype_to_exponential,
	(duk_c_function) duk_builtin_number_prototype_to_fixed,
	(duk_c_function) duk_builtin_number_prototype_to_locale_string,
	(duk_c_function) duk_builtin_number_prototype_to_precision,
	(duk_c_function) duk_builtin_number_prototype_to_string,
	(duk_c_function) duk_builtin_number_prototype_value_of,
	(duk_c_function) duk_builtin_object_constructor,
	(duk_c_function) duk_builtin_object_constructor_create,
	(duk_c_function) duk_builtin_object_constructor_define_properties,
	(duk_c_function) duk_builtin_object_constructor_define_property,
	(duk_c_function) duk_builtin_object_constructor_freeze,
	(duk_c_function) duk_builtin_object_constructor_get_own_property_descriptor,
	(duk_c_function) duk_builtin_object_constructor_get_own_property_names,
	(duk_c_function) duk_builtin_object_constructor_get_prototype_of,
	(duk_c_function) duk_builtin_object_constructor_is_extensible,
	(duk_c_function) duk_builtin_object_constructor_is_frozen,
	(duk_c_function) duk_builtin_object_constructor_is_sealed,
	(duk_c_function) duk_builtin_object_constructor_keys,
	(duk_c_function) duk_builtin_object_constructor_prevent_extensions,
	(duk_c_function) duk_builtin_object_constructor_seal,
	(duk_c_function) duk_builtin_object_prototype_has_own_property,
	(duk_c_function) duk_builtin_object_prototype_is_prototype_of,
	(duk_c_function) duk_builtin_object_prototype_property_is_enumerable,
	(duk_c_function) duk_builtin_object_prototype_to_locale_string,
	(duk_c_function) duk_builtin_object_prototype_to_string,
	(duk_c_function) duk_builtin_object_prototype_value_of,
	(duk_c_function) duk_builtin_range_error_constructor,
	(duk_c_function) duk_builtin_reference_error_constructor,
	(duk_c_function) duk_builtin_regexp_constructor,
	(duk_c_function) duk_builtin_regexp_prototype_exec,
	(duk_c_function) duk_builtin_regexp_prototype_test,
	(duk_c_function) duk_builtin_regexp_prototype_to_string,
	(duk_c_function) duk_builtin_string_constructor,
	(duk_c_function) duk_builtin_string_constructor_from_char_code,
	(duk_c_function) duk_builtin_string_prototype_char_at,
	(duk_c_function) duk_builtin_string_prototype_char_code_at,
	(duk_c_function) duk_builtin_string_prototype_concat,
	(duk_c_function) duk_builtin_string_prototype_index_of,
	(duk_c_function) duk_builtin_string_prototype_last_index_of,
	(duk_c_function) duk_builtin_string_prototype_locale_compare,
	(duk_c_function) duk_builtin_string_prototype_match,
	(duk_c_function) duk_builtin_string_prototype_replace,
	(duk_c_function) duk_builtin_string_prototype_search,
	(duk_c_function) duk_builtin_string_prototype_slice,
	(duk_c_function) duk_builtin_string_prototype_split,
	(duk_c_function) duk_builtin_string_prototype_substr,
	(duk_c_function) duk_builtin_string_prototype_substring,
	(duk_c_function) duk_builtin_string_prototype_to_locale_lower_case,
	(duk_c_function) duk_builtin_string_prototype_to_locale_upper_case,
	(duk_c_function) duk_builtin_string_prototype_to_lower_case,
	(duk_c_function) duk_builtin_string_prototype_to_string,
	(duk_c_function) duk_builtin_string_prototype_to_upper_case,
	(duk_c_function) duk_builtin_string_prototype_trim,
	(duk_c_function) duk_builtin_string_prototype_value_of,
	(duk_c_function) duk_builtin_syntax_error_constructor,
	(duk_c_function) duk_builtin_thread_prototype_to_string,
	(duk_c_function) duk_builtin_type_error_constructor,
	(duk_c_function) duk_builtin_type_error_thrower,
	(duk_c_function) duk_builtin_uri_error_constructor,
};

char duk_builtins_data[] = {
(char)'\xd7', (char)'\x1a', (char)'\x62', (char)'\x41', (char)'\xad', 
(char)'\x1a', (char)'\x56', (char)'\xc1', (char)'\x9f', (char)'\x68', 
(char)'\x5c', (char)'\x00', (char)'\x0d', (char)'\x20', (char)'\x00', 
(char)'\xbf', (char)'\x94', (char)'\x34', (char)'\xd1', (char)'\x82', 
(char)'\xbf', (char)'\x86', (char)'\x91', (char)'\x70', (char)'\x4a', 
(char)'\x66', (char)'\x98', (char)'\x20', (char)'\x47', (char)'\xe4', 
(char)'\xde', (char)'\x34', (char)'\x07', (char)'\xfa', (char)'\x1a', 
(char)'\xa7', (char)'\xc0', (char)'\xcd', (char)'\x9a', (char)'\x56', 
(char)'\x00', (char)'\xaa', (char)'\x9a', (char)'\x56', (char)'\xa0', 
(char)'\x0a', (char)'\x9a', (char)'\x67', (char)'\x5f', (char)'\xea', 
(char)'\x9a', (char)'\x67', (char)'\x9f', (char)'\xca', (char)'\x9a', 
(char)'\x6e', (char)'\x5f', (char)'\xaa', (char)'\x9a', (char)'\x6e', 
(char)'\xdf', (char)'\x8a', (char)'\x9a', (char)'\x6f', (char)'\x5f', 
(char)'\x6a', (char)'\xa0', (char)'\xe6', (char)'\x8b', (char)'\xc2', 
(char)'\x01', (char)'\x4a', (char)'\x28', (char)'\x3f', (char)'\xff', 
(char)'\x55', (char)'\x53', (char)'\x00', (char)'\x00', (char)'\x00', 
(char)'\x00', (char)'\x00', (char)'\x00', (char)'\x03', (char)'\xe1', 
(char)'\xfc', (char)'\x95', (char)'\x00', (char)'\x00', (char)'\x00', 
(char)'\x00', (char)'\x00', (char)'\x00', (char)'\x03', (char)'\xc1', 
(char)'\xfc', (char)'\x9f', (char)'\x10', (char)'\x1a', (char)'\x61', 
(char)'\x03', (char)'\x0c', (char)'\x40', (char)'\x59', (char)'\x8c', 
(char)'\x0a', (char)'\x32', (char)'\x01', (char)'\x26', (char)'\x50', 
(char)'\x20', (char)'\xcc', (char)'\x03', (char)'\x99', (char)'\xc0', 
(char)'\x63', (char)'\x40', (char)'\x0a', (char)'\x69', (char)'\x40', 
(char)'\x0d', (char)'\x47', (char)'\xf9', (char)'\xac', (char)'\xfe', 
(char)'\x36', (char)'\x1f', (char)'\xa6', (char)'\xd3', (char)'\xf0', 
(char)'\xdc', (char)'\x7d', (char)'\x9b', (char)'\xc0', (char)'\x43', 
(char)'\x80', (char)'\x06', (char)'\x70', (char)'\x88', (char)'\x8e', 
(char)'\x33', (char)'\x5f', (char)'\x4c', (char)'\xe4', (char)'\xf9', 
(char)'\x6b', (char)'\x47', (char)'\xc3', (char)'\x51', (char)'\x3d', 
(char)'\xda', (char)'\x49', (char)'\xec', (char)'\xd0', (char)'\x4f', 
(char)'\x56', (char)'\x22', (char)'\x7a', (char)'\x31', (char)'\x93', 
(char)'\xcd', (char)'\x90', (char)'\x9e', (char)'\x4c', (char)'\xa4', 
(char)'\xf1', (char)'\x66', (char)'\x27', (char)'\x83', (char)'\x69', 
(char)'\x3b', (char)'\xdb', (char)'\x07', (char)'\xbb', (char)'\x98', 
(char)'\x47', (char)'\xff', (char)'\xff', (char)'\xe0', (char)'\x8d', 
(char)'\x21', (char)'\x80', (char)'\x00', (char)'\xa1', (char)'\xfe', 
(char)'\x01', (char)'\xae', (char)'\xb9', (char)'\x02', (char)'\x75', 
(char)'\x47', (char)'\x23', (char)'\xa6', (char)'\x3c', (char)'\x9d', 
(char)'\x11', (char)'\x48', (char)'\xe7', (char)'\x8c', (char)'\x67', 
(char)'\x34', (char)'\x5a', (char)'\x39', (char)'\x65', (char)'\x89', 
(char)'\xc9', (char)'\x1a', (char)'\x4e', (char)'\x39', (char)'\x52', 
(char)'\x71', (char)'\x49', (char)'\x93', (char)'\x86', (char)'\x48', 
(char)'\x9c', (char)'\x12', (char)'\x24', (char)'\xdf', (char)'\x94', 
(char)'\x2f', (char)'\xff', (char)'\x08', (char)'\x01', (char)'\x9b', 
(char)'\x33', (char)'\x60', (char)'\xd8', (char)'\x9a', (char)'\x06', 
(char)'\xbc', (char)'\xe0', (char)'\x35', (char)'\xa5', (char)'\xc9', 
(char)'\xab', (char)'\x30', (char)'\x4d', (char)'\x49', (char)'\x92', 
(char)'\x14', (char)'\x5f', (char)'\xc0', (char)'\x00', (char)'\x3f', 
(char)'\xc4', (char)'\x00', (char)'\x46', (char)'\xcb', (char)'\x01', 
(char)'\x34', (char)'\xd7', (char)'\x51', (char)'\xa4', (char)'\xbe', 
(char)'\x7d', (char)'\xa2', (char)'\xbc', (char)'\x7c', (char)'\x51', 
(char)'\xff', (char)'\x00', (char)'\x16', (char)'\x70', (char)'\x09', 
(char)'\x07', (char)'\xf8', (char)'\xc0', (char)'\x2a', (char)'\xd9', 
(char)'\x15', (char)'\x06', (char)'\xc0', (char)'\xa0', (char)'\x33', 
(char)'\x40', (char)'\x8f', (char)'\xb3', (char)'\x01', (char)'\xc9', 
(char)'\x96', (char)'\x14', (char)'\x0c', (char)'\xa0', (char)'\xb3', 
(char)'\xec', (char)'\x90', (char)'\xe0', (char)'\x64', (char)'\x07', 
(char)'\x83', (char)'\x1c', (char)'\x41', (char)'\x18', (char)'\xc2', 
(char)'\x44', (char)'\xc5', (char)'\x13', (char)'\x5e', (char)'\xc4', 
(char)'\x16', (char)'\x3e', (char)'\xc3', (char)'\x06', (char)'\x3e', 
(char)'\xc2', (char)'\x08', (char)'\x3e', (char)'\xc1', (char)'\x03', 
(char)'\x34', (char)'\xc0', (char)'\x11', (char)'\x34', (char)'\xbf', 
(char)'\x05', (char)'\x34', (char)'\xbe', (char)'\x09', (char)'\x34', 
(char)'\xbd', (char)'\x04', (char)'\x34', (char)'\xbc', (char)'\x0c', 
(char)'\x3e', (char)'\xbb', (char)'\x0d', (char)'\x3e', (char)'\x29', 
(char)'\x3f', (char)'\x80', (char)'\x0a', (char)'\xea', (char)'\x90', 
(char)'\xf8', (char)'\x7f', (char)'\x90', (char)'\x08', (char)'\xd6', 
(char)'\x10', (char)'\x01', (char)'\x46', (char)'\xcd', (char)'\xa8', 
(char)'\x35', (char)'\xee', (char)'\x01', (char)'\x73', (char)'\x4a', 
(char)'\x4b', (char)'\x8a', (char)'\x62', (char)'\x66', (char)'\xd3', 
(char)'\x9f', (char)'\x61', (char)'\xd4', (char)'\x1a', (char)'\x61', 
(char)'\x54', (char)'\x9a', (char)'\x5b', (char)'\xd5', (char)'\x12', 
(char)'\xda', (char)'\xac', (char)'\x96', (char)'\xb5', (char)'\x88', 
(char)'\xb4', (char)'\xad', (char)'\x26', (char)'\x3d', (char)'\x72', 
(char)'\x2c', (char)'\xeb', (char)'\xd1', (char)'\x65', (char)'\x62', 
(char)'\x8b', (char)'\x1b', (char)'\x40', (char)'\x58', (char)'\x59', 
(char)'\x02', (char)'\xbe', (char)'\xd8', (char)'\x15', (char)'\xd6', 
(char)'\x60', (char)'\xad', (char)'\xb7', (char)'\x05', (char)'\x65', 
(char)'\x82', (char)'\x0a', (char)'\x5f', (char)'\xe0', (char)'\x00', 
(char)'\x1f', (char)'\xe5', (char)'\x02', (char)'\x35', (char)'\x8c', 
(char)'\x13', (char)'\x64', (char)'\x60', (char)'\x1a', (char)'\xe3', 
(char)'\x20', (char)'\x29', (char)'\xbf', (char)'\x8a', (char)'\xab', 
(char)'\x81', (char)'\xff', (char)'\xff', (char)'\xff', (char)'\xff', 
(char)'\xff', (char)'\xff', (char)'\xde', (char)'\xfe', (char)'\xaa', 
(char)'\x80', (char)'\x02', (char)'\x00', (char)'\x00', (char)'\x00', 
(char)'\x00', (char)'\x00', (char)'\x00', (char)'\x00', (char)'\xa9', 
(char)'\x80', (char)'\x00', (char)'\x00', (char)'\x00', (char)'\x00', 
(char)'\x00', (char)'\x01', (char)'\xf0', (char)'\xfe', (char)'\xa7', 
(char)'\x80', (char)'\x00', (char)'\x00', (char)'\x00', (char)'\x00', 
(char)'\x00', (char)'\x01', (char)'\xe0', (char)'\xfe', (char)'\xa8', 
(char)'\x80', (char)'\x00', (char)'\x00', (char)'\x00', (char)'\x00', 
(char)'\x00', (char)'\x01', (char)'\xe1', (char)'\xfe', (char)'\x00', 
(char)'\x7f', (char)'\x98', (char)'\x08', (char)'\xd6', (char)'\x00', 
(char)'\x00', (char)'\x00', (char)'\x00', (char)'\x00', (char)'\x00', 
(char)'\x00', (char)'\x00', (char)'\x00', (char)'\xcd', (char)'\x98', 
(char)'\x72', (char)'\x6c', (char)'\x42', (char)'\x93', (char)'\x5e', 
(char)'\x20', (char)'\x14', (char)'\xd0', (char)'\x84', (char)'\xa5', 
(char)'\x83', (char)'\x25', (char)'\x24', (char)'\x31', (char)'\x0a', 
(char)'\x7f', (char)'\xe0', (char)'\x06', (char)'\xa3', (char)'\x1c', 
(char)'\x25', (char)'\x10', (char)'\xef', (char)'\xf5', (char)'\x08', 
(char)'\xd8', (char)'\x07', (char)'\xf9', (char)'\xc0', (char)'\x8d', 
(char)'\x64', (char)'\x00', (char)'\x00', (char)'\x00', (char)'\x00', 
(char)'\x00', (char)'\x00', (char)'\x7c', (char)'\x3f', (char)'\xda', 
(char)'\xd9', (char)'\x46', (char)'\x05', (char)'\x02', (char)'\x00', 
(char)'\x27', (char)'\xd1', (char)'\xc1', (char)'\xb0', (char)'\x88', 
(char)'\x09', (char)'\xe4', (char)'\x30', (char)'\x4e', (char)'\xa2', 
(char)'\x83', (char)'\x5d', (char)'\x24', (char)'\x13', (char)'\x89', 
(char)'\x20', (char)'\x9b', (char)'\x20', (char)'\x04', (char)'\xd1', 
(char)'\x48', (char)'\x26', (char)'\x49', (char)'\x01', (char)'\x30', 
(char)'\x5a', (char)'\x09', (char)'\x71', (char)'\xe0', (char)'\x4b', 
(char)'\x13', (char)'\x82', (char)'\x54', (char)'\x7c', (char)'\x12', 
(char)'\x85', (char)'\x00', (char)'\x93', (char)'\x21', (char)'\x04', 
(char)'\x91', (char)'\x50', (char)'\x24', (char)'\x48', (char)'\xc1', 
(char)'\x20', (char)'\x58', (char)'\x08', (char)'\xf2', (char)'\x50', 
(char)'\x47', (char)'\x17', (char)'\x02', (char)'\x34', (char)'\x88', 
(char)'\x11', (char)'\x85', (char)'\x60', (char)'\x8b', (char)'\x26', 
(char)'\x04', (char)'\x51', (char)'\xb9', (char)'\x22', (char)'\x4c', 
(char)'\xc9', (char)'\x10', (char)'\x76', (char)'\x48', (char)'\x73', 
(char)'\x65', (char)'\xe8', (char)'\x63', (char)'\xe5', (char)'\xe8', 
(char)'\x53', (char)'\x47', (char)'\xe8', (char)'\x43', (char)'\xc7', 
(char)'\xe8', (char)'\x33', (char)'\x29', (char)'\xe8', (char)'\x23', 
(char)'\xa9', (char)'\xe8', (char)'\x13', (char)'\x02', (char)'\x40', 
(char)'\x1c', (char)'\x11', (char)'\xfc', (char)'\xd5', (char)'\x79', 
(char)'\xf8', (char)'\xf5', (char)'\x79', (char)'\xf4', (char)'\xc5', 
(char)'\xf9', (char)'\xf0', (char)'\xe5', (char)'\xf9', (char)'\xed', 
(char)'\x20', (char)'\x0f', (char)'\x48', (char)'\x20', (char)'\x79', 
(char)'\x42', (char)'\x23', (char)'\xc1', (char)'\x78', (char)'\x1d', 
(char)'\xcf', (char)'\xc8', (char)'\x54', (char)'\x7f', (char)'\x00', 
(char)'\x00', (char)'\xff', (char)'\x40', (char)'\x61', (char)'\x94', 
(char)'\x10', (char)'\x30', (char)'\x00', (char)'\x80', (char)'\x9c', 
(char)'\xe1', (char)'\x1b', (char)'\xc0', (char)'\x31', (char)'\x8e', 
(char)'\x51', (char)'\x8e', (char)'\x31', (char)'\x8e', (char)'\x12', 
(char)'\x00', (char)'\x00', (char)'\x00', (char)'\x00', (char)'\x00', 
(char)'\x00', (char)'\x00', (char)'\x00', (char)'\x03', (char)'\x3a', 
(char)'\xd0', (char)'\x11', (char)'\xd2', (char)'\x84', (char)'\x9b', 
(char)'\x34', (char)'\x40', (char)'\x2a', (char)'\x7f', (char)'\x80', 
(char)'\x00', (char)'\x7f', (char)'\xa4', (char)'\x11', (char)'\xb0', 
(char)'\x80', (char)'\xa6', (char)'\xb2', (char)'\x00', (char)'\x02', 
(char)'\xd9', (char)'\x59', (char)'\x01', (char)'\x55', (char)'\xfc', 
(char)'\x00', (char)'\x13', (char)'\xfd', (char)'\x40', (char)'\x8d', 
(char)'\x85', (char)'\x00', (char)'\x35', (char)'\x90', (char)'\x00', 
(char)'\x01', (char)'\x57', (char)'\xfc', (char)'\x00', (char)'\x13', 
(char)'\xfd', (char)'\x60', (char)'\x8d', (char)'\x84', (char)'\xff', 
(char)'\x35', (char)'\x90', (char)'\x00', (char)'\x01', (char)'\x59', 
(char)'\xfc', (char)'\x00', (char)'\x13', (char)'\xfd', (char)'\x80', 
(char)'\x8d', (char)'\x84', (char)'\xfe', (char)'\x35', (char)'\x90', 
(char)'\x00', (char)'\x01', (char)'\x5b', (char)'\xfc', (char)'\x00', 
(char)'\x13', (char)'\xfd', (char)'\xa0', (char)'\x8d', (char)'\x84', 
(char)'\xfd', (char)'\x35', (char)'\x90', (char)'\x00', (char)'\x01', 
(char)'\x5d', (char)'\xfc', (char)'\x00', (char)'\x13', (char)'\xfd', 
(char)'\xc0', (char)'\x8d', (char)'\x84', (char)'\xfc', (char)'\x35', 
(char)'\x90', (char)'\x00', (char)'\x01', (char)'\x5f', (char)'\xfc', 
(char)'\x00', (char)'\x13', (char)'\xfd', (char)'\xe0', (char)'\x8d', 
(char)'\x84', (char)'\xfb', (char)'\x35', (char)'\x90', (char)'\x00', 
(char)'\x00', (char)'\xff', (char)'\xfc', (char)'\x83', (char)'\x54', 
(char)'\x06', (char)'\x95', (char)'\x71', (char)'\x48', (char)'\xb0', 
(char)'\xab', (char)'\xf0', (char)'\x54', (char)'\x03', (char)'\x4c', 
(char)'\x01', (char)'\x65', (char)'\x5b', (char)'\x5b', (char)'\xbb', 
(char)'\x16', (char)'\xb0', (char)'\x24', (char)'\x03', (char)'\x44', 
(char)'\x0e', (char)'\xf3', (char)'\x9f', (char)'\xaf', (char)'\xe4', 
(char)'\x22', (char)'\xee', (char)'\x63', (char)'\xf3', (char)'\x3c', 
(char)'\x0f', (char)'\xe8', (char)'\x22', (char)'\xb6', (char)'\x54', 
(char)'\x71', (char)'\x5f', (char)'\x73', (char)'\xf3', (char)'\x34', 
(char)'\x00', (char)'\xee', (char)'\x52', (char)'\x61', (char)'\x57', 
(char)'\xbc', (char)'\xbd', (char)'\xb3', (char)'\xf3', (char)'\x2c', 
(char)'\x01', (char)'\x82', (char)'\xd4', (char)'\x45', (char)'\x4f', 
(char)'\xb2', (char)'\x10', (char)'\x94', (char)'\x03', (char)'\x24', 
(char)'\x0c', (char)'\xd3', (char)'\xb7', (char)'\xf6', (char)'\x69', 
(char)'\xea', (char)'\x0e', (char)'\x63', (char)'\xf3', (char)'\x1c', 
(char)'\x0c', (char)'\xd3', (char)'\xb7', (char)'\xf6', (char)'\x69', 
(char)'\xea', (char)'\x0f', (char)'\x63', (char)'\xf4', (char)'\x8c', 
(char)'\x4e', (char)'\x04', (char)'\x61', (char)'\x71', (char)'\x23', 
(char)'\x03', (char)'\x91', (char)'\x17', (char)'\xdc', (char)'\xc8', 
(char)'\xbc', (char)'\xe8', (char)'\x85', (char)'\xd7', (char)'\x52', 
(char)'\x2e', (char)'\x3b', (char)'\x11', (char)'\x6d', (char)'\xdc', 
(char)'\x8b', (char)'\x4f', (char)'\x04', (char)'\x59', (char)'\x79', 
(char)'\x22', (char)'\xc3', (char)'\xd2', (char)'\xf2', (char)'\xbb', 
(char)'\xda', (char)'\xf2', (char)'\xb3', (char)'\xe2', (char)'\x15', 
(char)'\x5f', (char)'\x40', (char)'\xa8', (char)'\xfc', (char)'\x45', 
(char)'\x37', (char)'\xf2', (char)'\x29', (char)'\x40', (char)'\x11', 
(char)'\x46', (char)'\x04', (char)'\x83', (char)'\xff', (char)'\xf0', 
(char)'\x02', (char)'\x51', (char)'\xb7', (char)'\x21', (char)'\x41', 
(char)'\xbd', (char)'\x85', (char)'\xff', (char)'\xf0', (char)'\x00', 
(char)'\x0f', (char)'\xff', (char)'\xc2', (char)'\x10', (char)'\xc1', 
(char)'\x01', (char)'\x62', (char)'\x20', (char)'\x83', (char)'\x54', 
(char)'\xc9', (char)'\x83', (char)'\x16', (char)'\x6b', (char)'\x58', 
(char)'\x39', (char)'\x5a', (char)'\xc9', (char)'\x8b', (char)'\xb4', 
(char)'\x13', (char)'\x34', (char)'\xee', (char)'\xeb', (char)'\xe1', 
(char)'\x07', (char)'\x0c', (char)'\x3c', (char)'\x38', (char)'\x61', 
(char)'\xe9', (char)'\xd7', (char)'\x77', (char)'\x4e', (char)'\x78', 
(char)'\x50', (char)'\x33', (char)'\x5c', (char)'\xd1', (char)'\x73', 
(char)'\x05', (char)'\xac', (char)'\x19', (char)'\xb0', (char)'\x68', 
(char)'\xc1', (char)'\x82', (char)'\xdc', (char)'\xf9', (char)'\x77', 
(char)'\x65', (char)'\xe5', (char)'\xa7', (char)'\x1a', (char)'\x04', 
(char)'\x6c', (char)'\x98', (char)'\x31', (char)'\x64', (char)'\xc1', 
(char)'\xab', (char)'\x26', (char)'\x2c', (char)'\x1a', (char)'\xb2', 
(char)'\x62', (char)'\x82', (char)'\x9c', (char)'\xda', (char)'\x08', 
(char)'\x26', (char)'\xef', (char)'\xdc', (char)'\x82', (char)'\x6e', 
(char)'\x1f', (char)'\x28', (char)'\x19', (char)'\x31', (char)'\x40', 
(char)'\xc1', (char)'\xcb', (char)'\xa6', (char)'\x4c', (char)'\x9d', 
(char)'\x30', (char)'\x64', (char)'\x82', (char)'\xad', (char)'\x48', 
(char)'\x68', (char)'\x19', (char)'\x30', (char)'\x62', (char)'\xc9', 
(char)'\x07', (char)'\x87', (char)'\x0d', (char)'\xaf', (char)'\xb6', 
(char)'\x68', (char)'\x83', (char)'\xc3', (char)'\x86', (char)'\xd7', 
(char)'\xdb', (char)'\x34', (char)'\x41', (char)'\xe1', (char)'\xc3', 
(char)'\x6b', (char)'\xed', (char)'\x9a', (char)'\x20', (char)'\x8f', 
(char)'\x3a', (char)'\xaa', (char)'\xf9', (char)'\x9a', (char)'\x77', 
(char)'\x75', (char)'\xf0', (char)'\xed', (char)'\x06', (char)'\x16', 
(char)'\x4d', (char)'\x1c', (char)'\xb4', (char)'\x6a', (char)'\xc3', 
(char)'\x16', (char)'\x46', (char)'\xb9', (char)'\x18', (char)'\x63', 
(char)'\xc8', (char)'\xd5', (char)'\xc3', (char)'\x37', (char)'\x0c', 
(char)'\xb3', (char)'\x32', (char)'\xcc', (char)'\xc1', (char)'\xcb', 
(char)'\x3c', (char)'\x4e', (char)'\x70', (char)'\xb3', (char)'\xc2', 
(char)'\xe7', (char)'\x26', (char)'\x56', (char)'\x6d', (char)'\xd9', 
(char)'\x37', (char)'\xc2', (char)'\xc9', (char)'\x89', (char)'\xce', 
(char)'\xf5', (char)'\x03', (char)'\xe1', (char)'\xf4', (char)'\xa2', 
(char)'\x0f', (char)'\x28', (char)'\x90', (char)'\x69', (char)'\x38', 
(char)'\x82', (char)'\xe9', (char)'\xe4', (char)'\x18', (char)'\x53', 
(char)'\x40', (char)'\xea', (char)'\xa9', (char)'\x4b', (char)'\xd5', 
(char)'\xd0', (char)'\x38', (char)'\xa4', (char)'\xc1', (char)'\xb4', 
(char)'\xb0', (char)'\x0c', (char)'\xab', (char)'\x00', (char)'\x59', 
(char)'\x35', (char)'\x02', (char)'\xa9', (char)'\x88', (char)'\x12', 
(char)'\x54', (char)'\x20', (char)'\xff', (char)'\xfc', (char)'\x00', 
(char)'\x5b', (char)'\x37', (char)'\x40', (char)'\x9f', (char)'\xff', 
(char)'\x84', (char)'\x6c', (char)'\x82', (char)'\x17', (char)'\x13', 
(char)'\x7f', (char)'\x5c', (char)'\x5b', (char)'\x32', (char)'\xc5', 
(char)'\xe5', (char)'\xcb', (char)'\x7f', (char)'\x23', (char)'\x5c', 
(char)'\x11', (char)'\x7c', (char)'\xbc', (char)'\xb9', (char)'\x6f', 
(char)'\xe4', (char)'\x83', (char)'\x4e', (char)'\xe4', (char)'\x19', 
(char)'\x79', (char)'\x72', (char)'\xdf', (char)'\xc9', (char)'\x06', 
(char)'\x8c', (char)'\x3b', (char)'\xb2', (char)'\x6c', (char)'\xd3', 
(char)'\xbb', (char)'\x38', (char)'\x00', 
};
#line 1 "duk_debug_fixedbuffer.c"
/*
 *  Fixed buffer helper useful for debugging, requires no allocation
 *  which is critical for debugging.
 */

/* include removed: duk_internal.h */

#ifdef DUK_USE_DEBUG

void duk_fb_put_bytes(duk_fixedbuffer *fb, duk_u8 *buffer, duk_u32 length) {
	duk_u32 avail;

	avail = (fb->offset >= fb->length ? (duk_u32) 0 : (duk_u32) (fb->length - fb->offset));
	if (length > avail) {
		memcpy(fb->buffer + fb->offset, buffer, avail);
		fb->offset += avail;
		fb->truncated = 1;
	} else {
		memcpy(fb->buffer + fb->offset, buffer, length);
		fb->offset += length;
	}
}

void duk_fb_put_byte(duk_fixedbuffer *fb, duk_u8 x) {
	duk_fb_put_bytes(fb, &x, 1);
}

void duk_fb_put_cstring(duk_fixedbuffer *fb, char *x) {
	duk_fb_put_bytes(fb, (duk_u8 *) x, (duk_u32) strlen(x));
}

void duk_fb_sprintf(duk_fixedbuffer *fb, const char *fmt, ...) {
	duk_u32 avail;
	va_list ap;

	va_start(ap, fmt);
	avail = (fb->offset >= fb->length ? (duk_u32) 0 : (duk_u32) (fb->length - fb->offset));
	if (avail > 0) {
		int res = vsnprintf((char *) (fb->buffer + fb->offset), avail, fmt, ap);
		if (res < 0) {
			/* error */
		} else if (res >= avail) {
			/* truncated */
			fb->offset += avail;
			if (res > avail) {
				/* actual chars dropped (not just null term) */
				fb->truncated = 1;
			}
		} else {
			/* normal */
			fb->offset += res;
		}
	}
	va_end(ap);
}

int duk_fb_is_full(duk_fixedbuffer *fb) {
	return (fb->offset >= fb->length);
}

#endif  /* DUK_USE_DEBUG */

#line 1 "duk_debug_heap.c"
/*
 *  Debug dumping of duk_heap.
 */

/* include removed: duk_internal.h */

#ifdef DUK_USE_DEBUG

static void sanitize_snippet(char *buf, int buf_size, duk_hstring *str) {
	int i;
	int nchars;
	int maxchars;
	duk_u8 *data;

	memset(buf, 0, buf_size);

	maxchars = buf_size - 1;
	data = DUK_HSTRING_GET_DATA(str);
	nchars = (str->blen < maxchars ? str->blen : maxchars);
	for (i = 0; i < nchars; i++) {
		char c = (char) data[i];
		if (c < 0x20 || c > 0x7e) {
			c = '.';
		}
		buf[i] = c;
	}
}

static void format_func_ptr(char *buf, int buf_size, unsigned char *fptr, int fptr_size) {
	int i;
	char *p = buf;
	char *p_end = buf + buf_size - 1;

	memset(buf, 0, buf_size);

	for (i = 0; i < fptr_size; i++) {
		int left = p_end - p;
		if (left <= 0) {
			break;
		}
		p += snprintf(p, left, "%02x", (int) fptr[i]);
	}	
}

static const char *get_heap_type_string(duk_heaphdr *hdr) {
	switch (DUK_HEAPHDR_GET_TYPE(hdr)) {
	case DUK_HTYPE_STRING:
		return "string";
	case DUK_HTYPE_OBJECT:
		return "object";
	case DUK_HTYPE_BUFFER:
		return "buffer";
	default:
		return "???";
	}
}

static void dump_indented(duk_heaphdr *obj, int index) {
#ifdef DUK_USE_REFERENCE_COUNTING
	DUK_DPRINT("  [%d]: %p %s (flags: 0x%08x, ref: %d) -> %!O",
	           index,
	           (void *) obj,
	           get_heap_type_string(obj),
	           (int) DUK_HEAPHDR_GET_FLAGS(obj),
	           DUK_HEAPHDR_GET_REFCOUNT(obj),
	           obj);
#else
	DUK_DPRINT("  [%d]: %p %s (flags: 0x%08x) -> %!O",
	           index,
	           (void *) obj,
	           get_heap_type_string(obj),
	           (int) DUK_HEAPHDR_GET_FLAGS(obj),
	           obj);
#endif
}

static void dump_heaphdr_list(duk_heap *heap, duk_heaphdr *root, const char *name) {
	int count;
	duk_heaphdr *curr;

	count = 0;
	curr = root;
	while (curr) {
		count++;
		curr = DUK_HEAPHDR_GET_NEXT(curr);
	}

	DUK_DPRINT("%s, %d objects", name, count);

	count = 0;
	curr = root;
	while (curr) {
		count++;
		dump_indented(curr, count);
		curr = DUK_HEAPHDR_GET_NEXT(curr);
	}
}

static void dump_stringtable(duk_heap *heap) {
	duk_u32 i;
	char buf[64+1];

	DUK_DPRINT("stringtable %p, used %d, size %d, load %d%%",
	           (void *) heap->st,
	           (int) heap->st_used,
	           (int) heap->st_size,
	           (int) (((double) heap->st_used) / ((double) heap->st_size) * 100.0));

	for (i = 0; i < heap->st_size; i++) {
		duk_hstring *e = heap->st[i];

		if (!e) {
			DUK_DPRINT("  [%d]: NULL", i);
		} else if (e == DUK_STRTAB_DELETED_MARKER(heap)) {
			DUK_DPRINT("  [%d]: DELETED", i);
		} else {
			sanitize_snippet(buf, sizeof(buf), e);

			/* FIXME: all string flags not printed now */

#ifdef DUK_USE_REFERENCE_COUNTING
			DUK_DPRINT("  [%d]: %p (flags: 0x%08x, ref: %d) '%s', strhash=0x%08x, blen=%d, clen=%d, arridx=%d, internal=%d",
			           i,
			           (void *) e,
			           (int) DUK_HEAPHDR_GET_FLAGS((duk_heaphdr *) e),
			           (int) DUK_HEAPHDR_GET_REFCOUNT((duk_heaphdr *) e),
			           buf,
			           (int) e->hash,
			           (int) e->blen,
			           (int) e->clen,
			           DUK_HSTRING_HAS_ARRIDX(e) ? 1 : 0,
			           DUK_HSTRING_HAS_INTERNAL(e) ? 1 : 0);
#else
			DUK_DPRINT("  [%d]: %p (flags: 0x%08x) '%s', strhash=0x%08x, blen=%d, clen=%d, arridx=%d, internal=%d",
			           i,
			           (void *) e,
			           (int) DUK_HEAPHDR_GET_FLAGS((duk_heaphdr *) e),
			           buf,
			           (int) e->hash,
			           (int) e->blen,
			           (int) e->clen,
			           DUK_HSTRING_HAS_ARRIDX(e) ? 1 : 0,
			           DUK_HSTRING_HAS_INTERNAL(e) ? 1 : 0);
#endif
		}
	}
}

static void dump_strcache(duk_heap *heap) {
	duk_u32 i;
	char buf[64+1];

	DUK_DPRINT("stringcache");

	for (i = 0; i < DUK_HEAP_STRCACHE_SIZE; i++) {
		duk_strcache *c = &heap->strcache[i];
		if (!c->h) {
			DUK_DPRINT("  [%d]: bidx=%d, cidx=%d, str=NULL",
			           i, c->bidx, c->cidx);
		} else {
			sanitize_snippet(buf, sizeof(buf), c->h);
			DUK_DPRINT("  [%d]: bidx=%d cidx=%d str=%s",
			           i, c->bidx, c->cidx, buf);
		}
	} 
}

void duk_debug_dump_heap(duk_heap *heap) {
	char buf[64+1];

	DUK_DPRINT("=== heap %p ===", (void *) heap);
	DUK_DPRINT("  flags: 0x%08x", (int) heap->flags);

	/* Note: there is no standard formatter for function pointers */
#ifdef DUK_USE_GCC_PRAGMAS
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-pedantic"
#endif
	format_func_ptr(buf, sizeof(buf), (unsigned char *) &heap->alloc_func, sizeof(heap->alloc_func));
	DUK_DPRINT("  alloc_func: %s (func ptrs may be little endian)", buf);
	format_func_ptr(buf, sizeof(buf), (unsigned char *) &heap->realloc_func, sizeof(heap->realloc_func));
	DUK_DPRINT("  realloc_func: %s", buf);
	format_func_ptr(buf, sizeof(buf), (unsigned char *) &heap->free_func, sizeof(heap->free_func));
	DUK_DPRINT("  free_func: %s", buf);
#ifdef DUK_USE_GCC_PRAGMAS
#pragma GCC diagnostic pop
#endif

	DUK_DPRINT("  alloc_udata: %p", (void *) heap->alloc_udata);

#ifdef DUK_USE_MARK_AND_SWEEP
	DUK_DPRINT("  mark-and-sweep trig counter: %d", heap->mark_and_sweep_trigger_counter);
	DUK_DPRINT("  mark-and-sweep trig limit: %d", heap->mark_and_sweep_trigger_limit);
	DUK_DPRINT("  mark-and-sweep rec depth: %d", heap->mark_and_sweep_recursion_depth);
	DUK_DPRINT("  mark-and-sweep rec limit: %d", heap->mark_and_sweep_recursion_limit);
	DUK_DPRINT("  mark-and-sweep base flags: 0x%08x", heap->mark_and_sweep_base_flags);
#endif

	/* FIXME: heap->fatal_func */

	DUK_DPRINT("  lj.jmpbuf_ptr: %p", (void *) heap->lj.jmpbuf_ptr);
	DUK_DPRINT("  lj.errhandler: %!@O", (duk_heaphdr *) heap->lj.errhandler);
	DUK_DPRINT("  lj.type: %d", heap->lj.type);
	DUK_DPRINT("  lj.value1: %!T", &heap->lj.value1);
	DUK_DPRINT("  lj.value2: %!T", &heap->lj.value2);
	DUK_DPRINT("  lj.iserror: %d", heap->lj.iserror);

	DUK_DPRINT("  handling_error: %d", heap->handling_error);

	DUK_DPRINT("  heap_thread: %!@O", (duk_heaphdr *) heap->heap_thread);
	DUK_DPRINT("  curr_thread: %!@O", (duk_heaphdr *) heap->curr_thread);
	DUK_DPRINT("  heap_object: %!@O", (duk_heaphdr *) heap->heap_object);

	DUK_DPRINT("  call_recursion_depth: %d", heap->call_recursion_depth);
	DUK_DPRINT("  call_recursion_limit: %d", heap->call_recursion_limit);

	DUK_DPRINT("  hash_seed: 0x%08x", (int) heap->hash_seed);
	DUK_DPRINT("  rnd_state: 0x%08x", (int) heap->rnd_state);

	dump_strcache(heap);

	dump_heaphdr_list(heap, heap->heap_allocated, "heap allocated");

#ifdef DUK_USE_REFERENCE_COUNTING
	dump_heaphdr_list(heap, heap->refzero_list, "refcounting refzero list");
#endif

#ifdef DUK_USE_MARK_AND_SWEEP
	dump_heaphdr_list(heap, heap->finalize_list, "mark-and-sweep finalize list");
#endif

	dump_stringtable(heap);

	/* heap->strs: not worth dumping */
}

#endif  /* DUK_USE_DEBUG */

#line 1 "duk_debug_hobject.c"
/*
 *  Debug dumping of duk_hobject.
 */

/* include removed: duk_internal.h */

#ifdef DUK_USE_DEBUG

/* must match duk_hobject.h */
static const char *class_names[16] = {
	"unused",
	"Arguments",
	"Array",
	"Boolean",
	"Date",
	"Error",
	"Function",
	"JSON",
	"Math",
	"Number",
	"Object",
	"RegExp",
	"String",
	"global",
	"objenv",
	"decenv",
};

/* for thread dumping */
static char get_activation_summary_char(duk_activation *act) {
	if (act->func) {
		if (DUK_HOBJECT_IS_COMPILEDFUNCTION(act->func)) {
			return 'c';
		} else if (DUK_HOBJECT_IS_NATIVEFUNCTION(act->func)) {
			return 'n';
		} else {
			/* should not happen */
			return '?';
		}
	} else {
		/* should not happen */
		return '?';
	}
}

/* for thread dumping */
static char get_tval_summary_char(duk_tval *tv) {
	switch (DUK_TVAL_GET_TAG(tv)) {
	case DUK_TAG_UNDEFINED:
		if (DUK_TVAL_IS_UNDEFINED_UNUSED(tv)) {
			return '.';
		}
		return 'u';
	case DUK_TAG_NULL:
		return 'n';
	case DUK_TAG_BOOLEAN:
		return 'b';
	case DUK_TAG_STRING:
		return 's';
	case DUK_TAG_OBJECT: {
		duk_hobject *h = DUK_TVAL_GET_OBJECT(tv);

		if (DUK_HOBJECT_IS_ARRAY(h)) {
			return 'A';
		} else if (DUK_HOBJECT_IS_COMPILEDFUNCTION(h)) {
			return 'C';
		} else if (DUK_HOBJECT_IS_NATIVEFUNCTION(h)) {
			return 'N';
		} else if (DUK_HOBJECT_IS_THREAD(h)) {
			return 'T';
		}
		return 'O';
	}
	case DUK_TAG_BUFFER: {
		return 'B';
	}
	case DUK_TAG_POINTER: {
		return 'P';
	}
	default:
		DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv));
		return 'd';
	}
	return '?';
}

/* for thread dumping */
static char get_catcher_summary_char(duk_catcher *catcher) {
	switch (DUK_CAT_GET_TYPE(catcher)) {
	case DUK_CAT_TYPE_TCF:
		if (DUK_CAT_HAS_CATCH_ENABLED(catcher)) {
			if (DUK_CAT_HAS_FINALLY_ENABLED(catcher)) {
				return 'C';  /* catch and finally active */
			} else {
				return 'c';  /* only catch active */
			}
		} else {
			if (DUK_CAT_HAS_FINALLY_ENABLED(catcher)) {
				return 'f';  /* only finally active */
			} else {
				return 'w';  /* neither active (usually 'with') */
			}
		}
	case DUK_CAT_TYPE_LABEL:
		return 'l';
	case DUK_CAT_TYPE_UNKNOWN:
	default:
		return '?';
	}
	return '?';
}

void duk_debug_dump_hobject(duk_hobject *obj) {
	int i;

	DUK_DPRINT("=== hobject %p ===", (void *) obj);
	if (!obj) {
		return;
	}

	DUK_DPRINT("  %sextensible", DUK_HOBJECT_HAS_EXTENSIBLE(obj) ? "" : "!");
	DUK_DPRINT("  %sconstructable", DUK_HOBJECT_HAS_CONSTRUCTABLE(obj) ? "" : "!");
	DUK_DPRINT("  %sbound", DUK_HOBJECT_HAS_BOUND(obj) ? "" : "!");
	DUK_DPRINT("  %scompiledfunction", DUK_HOBJECT_HAS_COMPILEDFUNCTION(obj) ? "" : "!");
	DUK_DPRINT("  %snativefunction", DUK_HOBJECT_HAS_NATIVEFUNCTION(obj) ? "" : "!");
	DUK_DPRINT("  %sthread", DUK_HOBJECT_HAS_THREAD(obj) ? "" : "!");
	DUK_DPRINT("  %sarray_part", DUK_HOBJECT_HAS_ARRAY_PART(obj) ? "" : "!");
	DUK_DPRINT("  %sstrict", DUK_HOBJECT_HAS_STRICT(obj) ? "" : "!");
	DUK_DPRINT("  %snewenv", DUK_HOBJECT_HAS_NEWENV(obj) ? "" : "!");
	DUK_DPRINT("  %snamebinding", DUK_HOBJECT_HAS_NAMEBINDING(obj) ? "" : "!");
	DUK_DPRINT("  %screateargs", DUK_HOBJECT_HAS_CREATEARGS(obj) ? "" : "!");
	DUK_DPRINT("  %senvrecclosed", DUK_HOBJECT_HAS_ENVRECCLOSED(obj) ? "" : "!");
	DUK_DPRINT("  %sspecial_array", DUK_HOBJECT_HAS_SPECIAL_ARRAY(obj) ? "" : "!");
	DUK_DPRINT("  %sspecial_stringobj", DUK_HOBJECT_HAS_SPECIAL_STRINGOBJ(obj) ? "" : "!");
	DUK_DPRINT("  %sspecial_arguments", DUK_HOBJECT_HAS_SPECIAL_ARGUMENTS(obj) ? "" : "!");

	DUK_DPRINT("  class: number %d -> %s",
	           (int) DUK_HOBJECT_GET_CLASS_NUMBER(obj),
	           class_names[(DUK_HOBJECT_GET_CLASS_NUMBER(obj)) & 0x0f]);

	DUK_DPRINT("  prototype: %p -> %!O",
	           (void *) obj->prototype,
	           (duk_heaphdr *) obj->prototype);

	DUK_DPRINT("  props: p=%p, e_size=%d, e_used=%d, a_size=%d, h_size=%d",
	           (void *) obj->p,
	           (int) obj->e_size,
	           (int) obj->e_used,
	           (int) obj->a_size,
	           (int) obj->h_size);

	/*
	 *  Object (struct layout) specific dumping.  Inline code here
	 *  instead of helpers, to ensure debug line prefix is identical.
	 */

	if (DUK_HOBJECT_IS_COMPILEDFUNCTION(obj)) {
		duk_hcompiledfunction *h = (duk_hcompiledfunction *) obj;

		DUK_DPRINT("  hcompiledfunction");
		DUK_DPRINT("  data: %!O", h->data);
		DUK_DPRINT("  nregs: %d", (int) h->nregs);
		DUK_DPRINT("  nargs: %d", (int) h->nargs);

		if (h->data && DUK_HBUFFER_HAS_DYNAMIC(h->data) && DUK_HBUFFER_GET_DATA_PTR(h->data)) {
			DUK_DPRINT("  consts: %p (%d, %d bytes)",
			           (void *) DUK_HCOMPILEDFUNCTION_GET_CONSTS_BASE(h),
			           (int) DUK_HCOMPILEDFUNCTION_GET_CONSTS_COUNT(h),
			           (int) DUK_HCOMPILEDFUNCTION_GET_CONSTS_SIZE(h));
			DUK_DPRINT("  funcs: %p (%d, %d bytes)",
			           (void *) DUK_HCOMPILEDFUNCTION_GET_FUNCS_BASE(h),
			           (int) DUK_HCOMPILEDFUNCTION_GET_FUNCS_COUNT(h),
			           (int) DUK_HCOMPILEDFUNCTION_GET_FUNCS_SIZE(h));
			DUK_DPRINT("  bytecode: %p (%d, %d bytes)",
			           (void *) DUK_HCOMPILEDFUNCTION_GET_CODE_BASE(h),
			           (int) DUK_HCOMPILEDFUNCTION_GET_CODE_COUNT(h),
			           (int) DUK_HCOMPILEDFUNCTION_GET_CODE_SIZE(h));
		} else {
			DUK_DPRINT("  consts: ???");
			DUK_DPRINT("  funcs: ???");
			DUK_DPRINT("  bytecode: ???");
		}
	} else if (DUK_HOBJECT_IS_NATIVEFUNCTION(obj)) {
		duk_hnativefunction *h = (duk_hnativefunction *) obj;

		DUK_DPRINT("  hnativefunction");
		/* XXX: h->func, cannot print function pointers portably */
		DUK_DPRINT("  nargs: %d", (int) h->nargs);
	} else if (DUK_HOBJECT_IS_THREAD(obj)) {
		duk_hthread *thr = (duk_hthread *) obj;
		duk_tval *p;

		DUK_DPRINT("  hthread");
		DUK_DPRINT("  strict: %d", (int) thr->strict);
		DUK_DPRINT("  state: %d", (int) thr->state);

		DUK_DPRINT("  valstack_max: %d, callstack_max:%d, catchstack_max: %d",
		           thr->valstack_max, thr->callstack_max, thr->catchstack_max);

		DUK_DPRINT("  callstack: ptr %p, size %d, top %d, preventcount %d, used size %d entries (%d bytes), alloc size %d entries (%d bytes)",
		           (void *) thr->callstack,
		           thr->callstack_size,
		           thr->callstack_top,
		           thr->callstack_preventcount,
		           thr->callstack_top,
		           thr->callstack_top * sizeof(duk_activation),
		           thr->callstack_size,
		           thr->callstack_size * sizeof(duk_activation));

		DUK_DEBUG_SUMMARY_INIT();
		DUK_DEBUG_SUMMARY_CHAR('[');
		for (i = 0; i <= thr->callstack_size; i++) {
			if (i == thr->callstack_top) {
				DUK_DEBUG_SUMMARY_CHAR('|');
			}
			if (!thr->callstack) {
				DUK_DEBUG_SUMMARY_CHAR('@');
			} else if (i < thr->callstack_size) {
				if (i < thr->callstack_top) {
					/* tailcalling is nice to see immediately; other flags (e.g. strict)
					 * not that important.
					 */
					if (thr->callstack[i].flags & DUK_ACT_FLAG_TAILCALLED) {
						DUK_DEBUG_SUMMARY_CHAR('/');
					}
					DUK_DEBUG_SUMMARY_CHAR(get_activation_summary_char(&thr->callstack[i]));
				} else {
					DUK_DEBUG_SUMMARY_CHAR('.');
				}
			}
		}
		DUK_DEBUG_SUMMARY_CHAR(']');
		DUK_DEBUG_SUMMARY_FINISH();

		DUK_DPRINT("  valstack: ptr %p, end %p (%d), bottom %p (%d), top %p (%d), used size %d entries (%d bytes), alloc size %d entries (%d bytes)",
		           (void *) thr->valstack,
		           (void *) thr->valstack_end,
		           (int) (thr->valstack_end - thr->valstack),
		           (void *) thr->valstack_bottom,
		           (int) (thr->valstack_bottom - thr->valstack),
		           (void *) thr->valstack_top,
		           (int) (thr->valstack_top - thr->valstack),
		           (int) (thr->valstack_top - thr->valstack),
		           (int) (thr->valstack_top - thr->valstack) * sizeof(duk_tval),
		           (int) (thr->valstack_end - thr->valstack),
		           (int) (thr->valstack_end - thr->valstack) * sizeof(duk_tval));

		DUK_DEBUG_SUMMARY_INIT();
		DUK_DEBUG_SUMMARY_CHAR('[');
		p = thr->valstack;
		while (p <= thr->valstack_end) {
			i = (int) (p - thr->valstack);
			if (thr->callstack &&
			    thr->callstack_top > 0 &&
			    i == (thr->callstack + thr->callstack_top - 1)->idx_bottom) {
				DUK_DEBUG_SUMMARY_CHAR('>');
			}
			if (p == thr->valstack_top) {
				DUK_DEBUG_SUMMARY_CHAR('|');
			}
			if (p < thr->valstack_end) {
				if (p < thr->valstack_top) {
					DUK_DEBUG_SUMMARY_CHAR(get_tval_summary_char(p));
				} else {
					/* XXX: safe printer for these?  would be nice, because
					 * we could visualize whether the values are in proper
					 * state.
					 */
					DUK_DEBUG_SUMMARY_CHAR('.');
				}
			}
			p++;
		}
		DUK_DEBUG_SUMMARY_CHAR(']');
		DUK_DEBUG_SUMMARY_FINISH();

		DUK_DPRINT("  catchstack: ptr %p, size %d, top %d, used size %d entries (%d bytes), alloc size %d entries (%d bytes)",
		           (void *) thr->catchstack,
		           thr->catchstack_size,
		           thr->catchstack_top,
		           thr->catchstack_top,
		           thr->catchstack_top * sizeof(duk_catcher),
		           thr->catchstack_size,
		           thr->catchstack_size * sizeof(duk_catcher));

		DUK_DEBUG_SUMMARY_INIT();
		DUK_DEBUG_SUMMARY_CHAR('[');
		for (i = 0; i <= thr->catchstack_size; i++) {
			if (i == thr->catchstack_top) {
				DUK_DEBUG_SUMMARY_CHAR('|');
			}
			if (!thr->catchstack) {
				DUK_DEBUG_SUMMARY_CHAR('@');
			} else if (i < thr->catchstack_size) {
				if (i < thr->catchstack_top) {
					DUK_DEBUG_SUMMARY_CHAR(get_catcher_summary_char(&thr->catchstack[i]));
				} else {
					DUK_DEBUG_SUMMARY_CHAR('.');
				}
			}
		}
		DUK_DEBUG_SUMMARY_CHAR(']');
		DUK_DEBUG_SUMMARY_FINISH();

		DUK_DPRINT("  resumer: ptr %p",
		           (void *) thr->resumer);

#if 0  /* worth dumping? */
		for (i = 0; i < DUK_NUM_BUILTINS; i++) {
			DUK_DPRINT("  builtins[%d] -> %!@O", i, thr->builtins[i]);
		}
#endif
	}

	if (obj->p) {
		DUK_DPRINT("  props alloc size: %d",
		           (int) DUK_HOBJECT_P_COMPUTE_SIZE(obj->e_size, obj->a_size, obj->h_size));
	} else {
		DUK_DPRINT("  props alloc size: n/a");
	}

	DUK_DPRINT("  prop entries:");
	for (i = 0; i < obj->e_size; i++) {
		duk_hstring *k;
		duk_propvalue *v;

		k = DUK_HOBJECT_E_GET_KEY(obj, i);
		v = DUK_HOBJECT_E_GET_VALUE_PTR(obj, i);

		if (i >= obj->e_used) {
			DUK_DPRINT("    [%d]: UNUSED", i);
			continue;
		}

		if (!k) {
			DUK_DPRINT("    [%d]: NULL", i);
			continue;
		}

		if (DUK_HOBJECT_E_SLOT_IS_ACCESSOR(obj, i)) {
			DUK_DPRINT("    [%d]: [w=%d e=%d c=%d a=%d] %!O -> get:%p set:%p; get %!O; set %!O",
			           i,
			           DUK_HOBJECT_E_SLOT_IS_WRITABLE(obj, i),
			           DUK_HOBJECT_E_SLOT_IS_ENUMERABLE(obj, i),
			           DUK_HOBJECT_E_SLOT_IS_CONFIGURABLE(obj, i),
			           DUK_HOBJECT_E_SLOT_IS_ACCESSOR(obj, i),
			           k,
			           (void *) v->a.get,
			           (void *) v->a.set,
			           (duk_heaphdr *) v->a.get,
			           (duk_heaphdr *) v->a.set);
		} else {
			DUK_DPRINT("    [%d]: [w=%d e=%d c=%d a=%d] %!O -> %!T",
			           i,
			           DUK_HOBJECT_E_SLOT_IS_WRITABLE(obj, i),
			           DUK_HOBJECT_E_SLOT_IS_ENUMERABLE(obj, i),
			           DUK_HOBJECT_E_SLOT_IS_CONFIGURABLE(obj, i),
			           DUK_HOBJECT_E_SLOT_IS_ACCESSOR(obj, i),
			           k,
			           &v->v);
		}
	}

	DUK_DPRINT("  array entries:");
	for (i = 0; i < obj->a_size; i++) {
		DUK_DPRINT("    [%d]: [w=%d e=%d c=%d a=%d] %d -> %!T",
		           i,
		           1,  /* implicit attributes */
		           1,
		           1,
		           0,
		           i,
		           DUK_HOBJECT_A_GET_VALUE_PTR(obj, i));
	}

	DUK_DPRINT("  hash entries:");
	for (i = 0; i < obj->h_size; i++) {
		duk_u32 t = DUK_HOBJECT_H_GET_INDEX(obj, i);
		if (t == DUK_HOBJECT_HASHIDX_UNUSED) {
			DUK_DPRINT("    [%d]: unused", i);
		} else if (t == DUK_HOBJECT_HASHIDX_DELETED) {
			DUK_DPRINT("    [%d]: deleted", i);
		} else {
			DUK_DPRINT("    [%d]: %d",
			           i,
			           (int) t);
		}
	}
}

void duk_debug_dump_callstack(duk_hthread *thr) {
	int i;

	DUK_DPRINT("=== hthread %p callstack: %d entries ===",
	           (void *) thr,
	           (thr == NULL ? 0 : thr->callstack_top));
	if (!thr) {
		return;
	}

	for (i = 0; i < thr->callstack_top; i++) {
		duk_activation *act = &thr->callstack[i];
		duk_tval *this_binding = NULL;

		this_binding = thr->valstack + act->idx_bottom - 1;
		if (this_binding < thr->valstack || this_binding >= thr->valstack_top) {
			this_binding = NULL;
		}

		DUK_DPRINT("  [%d] -> flags=0x%08x, func=%!O, var_env=%!iO, lex_env=%!iO, pc=%d, idx_bottom=%d, idx_retval=%d, this_binding=%!T",
		           i,
		           act->flags,
		           (duk_heaphdr *) act->func,
		           (duk_heaphdr *) act->var_env,
		           (duk_heaphdr *) act->lex_env,
		           act->pc,
		           act->idx_bottom,
		           act->idx_retval,
		           this_binding);
	}
}

void duk_debug_dump_activation(duk_hthread *thr, duk_activation *act) {
	if (!act) {
		DUK_DPRINT("duk_activation: NULL");
	} else {
		duk_tval *this_binding = NULL;

		this_binding = thr->valstack + act->idx_bottom - 1;
		if (this_binding < thr->valstack || this_binding >= thr->valstack_top) {
			this_binding = NULL;
		}

		DUK_DPRINT("duk_activation: %p -> flags=0x%08x, func=%!O, var_env=%!O, lex_env=%!O, pc=%d, idx_bottom=%d, idx_retval=%d, this_binding=%!T",
		           (void *) act,
		           act->flags,
		           (duk_heaphdr *) act->func,
		           (duk_heaphdr *) act->var_env,
		           (duk_heaphdr *) act->lex_env,
		           act->pc,
		           act->idx_bottom,
		           act->idx_retval,
		           this_binding);
	}
}

#endif  /* DUK_USE_DEBUG */

#line 1 "duk_debug_macros.c"
/*
 *  Debugging macro calls.
 */

/* include removed: duk_internal.h */

#ifdef DUK_USE_DEBUG

/*
 *  Debugging enabled
 */

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>

/* for one-char summaries (usable for e.g. valstack) */
char duk_debug_summary_buf[DUK_DEBUG_SUMMARY_BUF_SIZE];
int duk_debug_summary_idx;

#define  DUK_DEBUG_BUFSIZE  65536
static char buf[DUK_DEBUG_BUFSIZE];

static const char *get_level_string(int level) {
	switch (level) {
	case DUK_LEVEL_DEBUG:
		return "D";
	case DUK_LEVEL_DDEBUG:
		return "DD";
	case DUK_LEVEL_DDDEBUG:
		return "DDD";
	}
	return "???";
}

#ifdef DUK_USE_DPRINT_COLORS

/* http://en.wikipedia.org/wiki/ANSI_escape_code */
#define  TERM_REVERSE  "\x1b[7m"
#define  TERM_BRIGHT   "\x1b[1m"
#define  TERM_RESET    "\x1b[0m"
#define  TERM_BLUE     "\x1b[34m"
#define  TERM_RED      "\x1b[31m"

static const char *get_term_1(int level) {
	return (const char *) TERM_RED;
}

static const char *get_term_2(int level) {
	switch (level) {
	case DUK_LEVEL_DEBUG:
		return (const char *) (TERM_RESET TERM_BRIGHT);
	case DUK_LEVEL_DDEBUG:
		return (const char *) (TERM_RESET);
	case DUK_LEVEL_DDDEBUG:
		return (const char *) (TERM_RESET TERM_BLUE);
	}
	return (const char *) TERM_RESET;
}

static const char *get_term_3(int level) {
	return (const char *) TERM_RESET;
}

#else

static const char *get_term_1(int level) {
	return (const char *) "";
}

static const char *get_term_2(int level) {
	return (const char *) "";
}

static const char *get_term_3(int level) {
	return (const char *) "";
}

#endif  /* DUK_USE_DPRINT_COLORS */

#ifdef DUK_USE_VARIADIC_MACROS

void duk_debug_log(int level, const char *file, int line, const char *func, char *fmt, ...) {
	va_list ap;

	va_start(ap, fmt);

	memset((void *) buf, 0, (size_t) DUK_DEBUG_BUFSIZE);
	duk_debug_vsnprintf(buf, DUK_DEBUG_BUFSIZE - 1, fmt, ap);

#ifdef DUK_USE_DPRINT_RDTSC
	fprintf(stderr, "%s[%s] <%llu> %s:%d (%s):%s %s%s\n",
		get_term_1(level),
		get_level_string(level),
		duk_rdtsc(),
		file,
		line,
		func,
		get_term_2(level),
		buf,
		get_term_3(level));
#else
	fprintf(stderr, "%s[%s] %s:%d (%s):%s %s%s\n",
		get_term_1(level),
		get_level_string(level),
		file,
		line,
		func,
		get_term_2(level),
		buf,
		get_term_3(level));
#endif
	fflush(stderr);

	va_end(ap);
}

#else  /* DUK_USE_VARIADIC_MACROS */

char duk_debug_file_stash[DUK_DEBUG_STASH_SIZE];
char duk_debug_line_stash[DUK_DEBUG_STASH_SIZE];
char duk_debug_func_stash[DUK_DEBUG_STASH_SIZE];
int duk_debug_level_stash;

void duk_debug_log(char *fmt, ...) {
	va_list ap;

	va_start(ap, fmt);

	memset((void *) buf, 0, (size_t) DUK_DEBUG_BUFSIZE);
	duk_debug_vsnprintf(buf, DUK_DEBUG_BUFSIZE - 1, fmt, ap);

#ifdef DUK_USE_DPRINT_RDTSC
	fprintf(stderr, "%s[%s] <%llu> %s:%s (%s):%s %s%s\n",
		get_term_1(level),
		get_level_string(duk_debug_level_stash),
		duk_rdtsc(),
	        duk_debug_file_stash,
	        duk_debug_line_stash,
	        duk_debug_func_stash,
		get_term_2(level),
		buf,
		get_term_3(level));
#else
	fprintf(stderr, "%s[%s] %s:%s (%s):%s %s%s\n",
		get_term_1(level),
		get_level_string(duk_debug_level_stash),
	        duk_debug_file_stash,
	        duk_debug_line_stash,
	        duk_debug_func_stash,
		get_term_2(level),
		buf,
		get_term_3(level));
#endif
	fflush(stderr);

	va_end(ap);
}

#endif  /* DUK_USE_VARIADIC_MACROS */

#else  /* DUK_USE_DEBUG */

/*
 *  Debugging disabled
 */

#endif  /* DUK_USE_DEBUG */

#line 1 "duk_debug_vsnprintf.c"
/*
 *  Custom formatter for debug printing, allowing Duktape specific data
 *  structures (such as tagged values and heap objects) to be printed with
 *  a nice format string.  Because debug printing should not affect execution
 *  state, formatting here must be independent of execution (see implications
 *  below) and must not allocate memory.
 *
 *  Custom format tags begin with a '%!' to safely distinguish them from
 *  standard format tags.  The following conversions are supported:
 *
 *     %!T    tagged value (duk_tval *)
 *     %!O    heap object (duk_heaphdr *)
 *     %!I    decoded bytecode instruction
 *     %!C    bytecode instruction opcode name
 *
 *  Everything is serialized in a JSON-like manner.  The default depth is one
 *  level, internal prototype is not followed, and internal properties are not
 *  serialized.  The following modifiers change this behavior:
 *
 *     @      print pointers
 *     #      print binary representations (where applicable)
 *     d      deep traversal of own properties (not prototype)
 *     p      follow prototype chain (useless without 'd')
 *     i      include internal properties (other than prototype)
 *     x      hexdump buffers
 *     h      heavy formatting
 *
 *  For instance, the following serializes objects recursively, but does not
 *  follow the prototype chain nor print internal properties: "%!dO".
 *
 *  Notes:
 *
 *    * Standard snprintf return value semantics seem to vary.  This
 *      implementation returns the number of bytes it actually wrote
 *      (excluding the null terminator).  If retval == buffer size,
 *      output was truncated (except for corner cases).
 *
 *    * Output format is intentionally different from Ecmascript
 *      formatting requirements, as formatting here serves debugging
 *      of internals.
 *
 *    * Depth checking (and updating) is done in each type printer
 *      separately, to allow them to call each other freely.
 *
 *    * Some pathological structures might take ages to print (e.g.
 *      self recursion with 100 properties pointing to the object
 *      itself).  To guard against these, each printer also checks
 *      whether the output buffer is full; if so, early exit.
 *
 *    * Reference loops are detected using a loop stack.
 */

/* include removed: duk_internal.h */

#ifdef DUK_USE_DEBUG

#include <stdio.h>
#include <stdarg.h>
#include <string.h>

/* list of conversion specifiers that terminate a format tag;
 * this is unfortunately guesswork.
 */
#define  ALLOWED_STANDARD_CONVERSION_SPECIFIERS  "diouxXeEfFgGaAcsCSpnm"

/* maximum length of standard format tag that we support */
#define  MAX_FORMAT_TAG_LENGTH  32

/* heapobj recursion depth when deep printing is selected */
#define  DEEP_DEPTH_LIMIT  8

/* maximum recursion depth for loop detection stacks */
#define  LOOP_STACK_DEPTH  256

/* must match bytecode defines now; build autogenerate? */
static char *bc_optab[] = {
	"LDREG", "STREG", "LDCONST", "LDINT", "LDINTX", "MPUTOBJ", "MPUTARR", "NEW", "REGEXP", "CSREG",
	"GETVAR", "PUTVAR", "DECLVAR", "DELVAR", "CSVAR", "CLOSURE", "GETPROP", "PUTPROP", "DELPROP", "CSPROP",
	"ADD", "SUB", "MUL", "DIV", "MOD", "UNM", "UNP", "INC", "DEC", "BAND",
	"BOR", "BXOR", "BNOT", "BASL", "BLSR", "BASR", "LNOT", "EQ", "NEQ", "SEQ",
	"SNEQ", "GT", "GE", "LT", "LE", "IF", "INSTOF", "IN", "JUMP", "RETURN",

	"CALL", "LABEL", "ENDLABEL", "BREAK", "CONTINUE", "TRYCATCH", "OP56", "OP57", "OP58", "OP59",
	"OP60", "EXTRA", "DEBUG", "INVALID",
};

static char *bc_extraoptab[] = {
	"NOP", "LDTHIS", "LDUNDEF", "LDNULL", "LDBOOL", "NEWOBJ", "NEWARR", "SETALEN", "TYPEOF", "TYPEOFID",
	"TONUM", "INITENUM", "NEXTENUM", "INITSET", "INITGET", "ENDTRY", "ENDCATCH", "ENDFIN", "THROW", "INVLHS",
	"XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX",
	"XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX",
	"XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX",

	"XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX",
	"XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX",
	"XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX",
	"XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX",
	"XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX",

	"XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX",
	"XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX",
	"XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX",
	"XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX",
	"XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX",

	"XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX",
	"XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX",
	"XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX",
	"XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX",
	"XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX",

	"XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX",
	"XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX",
	"XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX",
	"XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX",
	"XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX",

	"XXX", "XXX", "XXX", "XXX", "XXX", "XXX",
};

typedef struct duk_dprint_state duk_dprint_state;
struct duk_dprint_state {
	duk_fixedbuffer *fb;

	/* loop_stack_index could be perhaps be replaced by 'depth', but it's nice
	 * to not couple these two mechanisms unnecessarily.
	 */
	duk_hobject *loop_stack[LOOP_STACK_DEPTH];
	int loop_stack_index;
	int loop_stack_limit;

	int depth;
	int depth_limit;

	int pointer;
	int heavy;
	int binary;
	int follow_proto;
	int internal;
	int hexdump;
};

/* helpers */
static void print_hstring(duk_dprint_state *st, duk_hstring *k, int quotes);
static void print_hobject(duk_dprint_state *st, duk_hobject *h);
static void print_hbuffer(duk_dprint_state *st, duk_hbuffer *h);
static void print_tval(duk_dprint_state *st, duk_tval *tv);
static void print_instr(duk_dprint_state *st, duk_instr ins);
static void print_heaphdr(duk_dprint_state *st, duk_heaphdr *h);
static void print_shared_heaphdr(duk_dprint_state *st, duk_heaphdr *h);
static void print_shared_heaphdr_string(duk_dprint_state *st, duk_heaphdr_string *h);

static void print_shared_heaphdr(duk_dprint_state *st, duk_heaphdr *h) {
	duk_fixedbuffer *fb = st->fb;

	if (st->heavy) {
		duk_fb_sprintf(fb, "(%p)", (void *) h);
	}

	if (!h) {
		return;
	}

	if (st->binary) {
		int i;
		duk_fb_put_byte(fb, (duk_u8) '[');
		for (i = 0; i < sizeof(*h); i++) {
			duk_fb_sprintf(fb, "%02x", (int) ((unsigned char *)h)[i]);
		}
		duk_fb_put_byte(fb, (duk_u8) ']');
	}

#ifdef DUK_USE_REFERENCE_COUNTING  /* currently implicitly also DUK_USE_DOUBLE_LINKED_HEAP */
	if (st->heavy) {
		duk_fb_sprintf(fb, "[h_next=%p,h_prev=%p,h_refcount=%u,h_flags=%08x,type=%d,reachable=%d,temproot=%d,finalizable=%d,finalized=%d]",
		               DUK_HEAPHDR_GET_NEXT(h),
		               DUK_HEAPHDR_GET_PREV(h),
		               DUK_HEAPHDR_GET_REFCOUNT(h),
		               DUK_HEAPHDR_GET_FLAGS(h),
		               DUK_HEAPHDR_GET_TYPE(h),
		               DUK_HEAPHDR_HAS_REACHABLE(h),
		               DUK_HEAPHDR_HAS_TEMPROOT(h),
		               DUK_HEAPHDR_HAS_FINALIZABLE(h),
		               DUK_HEAPHDR_HAS_FINALIZED(h));
	}
#else
	if (st->heavy) {
		duk_fb_sprintf(fb, "[h_next=%p,h_flags=%08x,type=%d,reachable=%d,temproot=%d,finalizable=%d,finalized=%d]",
		               DUK_HEAPHDR_GET_NEXT(h),
	        	       DUK_HEAPHDR_GET_FLAGS(h),
		               DUK_HEAPHDR_GET_TYPE(h),
		               DUK_HEAPHDR_HAS_REACHABLE(h),
	        	       DUK_HEAPHDR_HAS_TEMPROOT(h),
		               DUK_HEAPHDR_HAS_FINALIZABLE(h),
		               DUK_HEAPHDR_HAS_FINALIZED(h));
	}
#endif
}

static void print_shared_heaphdr_string(duk_dprint_state *st, duk_heaphdr_string *h) {
	duk_fixedbuffer *fb = st->fb;

	if (st->heavy) {
		duk_fb_sprintf(fb, "(%p)", (void *) h);
	}

	if (!h) {
		return;
	}

	if (st->binary) {
		int i;
		duk_fb_put_byte(fb, (duk_u8) '[');
		for (i = 0; i < sizeof(*h); i++) {
			duk_fb_sprintf(fb, "%02x", (int) ((unsigned char *)h)[i]);
		}
		duk_fb_put_byte(fb, (duk_u8) ']');
	}

#ifdef DUK_USE_REFERENCE_COUNTING
	if (st->heavy) {
		duk_fb_sprintf(fb, "[h_refcount=%u,h_flags=%08x,type=%d,reachable=%d,temproot=%d,finalizable=%d,finalized=%d]",
		               DUK_HEAPHDR_GET_REFCOUNT((duk_heaphdr *) h),
		               DUK_HEAPHDR_GET_FLAGS((duk_heaphdr *) h),
		               DUK_HEAPHDR_GET_TYPE((duk_heaphdr *) h),
		               DUK_HEAPHDR_HAS_REACHABLE((duk_heaphdr *) h),
		               DUK_HEAPHDR_HAS_TEMPROOT((duk_heaphdr *) h),
		               DUK_HEAPHDR_HAS_FINALIZABLE((duk_heaphdr *) h),
		               DUK_HEAPHDR_HAS_FINALIZED((duk_heaphdr *) h));
	}
#else
	if (st->heavy) {
		duk_fb_sprintf(fb, "[h_flags=%08x,type=%d,reachable=%d,temproot=%d,finalizable=%d,finalized=%d]",
	        	       DUK_HEAPHDR_GET_FLAGS((duk_heaphdr *) h),
		               DUK_HEAPHDR_GET_TYPE((duk_heaphdr *) h),
		               DUK_HEAPHDR_HAS_REACHABLE((duk_heaphdr *) h),
	        	       DUK_HEAPHDR_HAS_TEMPROOT((duk_heaphdr *) h),
		               DUK_HEAPHDR_HAS_FINALIZABLE((duk_heaphdr *) h),
		               DUK_HEAPHDR_HAS_FINALIZED((duk_heaphdr *) h));
	}
#endif
}

static void print_hstring(duk_dprint_state *st, duk_hstring *h, int quotes) {
	duk_fixedbuffer *fb = st->fb;
	duk_u8 *p;
	duk_u8 *p_end;

	/* terminal type: no depth check */

	if (duk_fb_is_full(fb)) {
		return;
	}

	print_shared_heaphdr_string(st, &h->hdr);

	if (!h) {
		duk_fb_put_cstring(fb, "NULL");
		return;
	}

	p = DUK_HSTRING_GET_DATA(h);
	p_end = p + DUK_HSTRING_GET_BYTELEN(h);

	if (p_end > p && p[0] == '_') {
		/* if property key begins with underscore, encode it with
		 * forced quotes (e.g. "_foo") to distinguish it from encoded
		 * internal properties (e.g. \xffbar -> _bar).
		 */
		quotes = 1;
	}

	if (quotes) {
		duk_fb_put_byte(fb, (duk_u8) '"');
	}
	while (p < p_end) {
		duk_u8 ch = *p++;

		/* two special escapes: '\' and '"', other printables as is */
		if (ch == '\\') {
			duk_fb_sprintf(fb, "\\\\");
		} else if (ch == '"') {
			duk_fb_sprintf(fb, "\\\"");
		} else if (ch >= 0x20 && ch <= 0x7e) {
			duk_fb_put_byte(fb, ch);
		} else if (ch == 0xff && !quotes) {
			/* encode \xffbar as _bar if no quotes are applied, this is for
			 * readable internal keys.
			 */
			duk_fb_put_byte(fb, (duk_u8) '_');
		} else {
			duk_fb_sprintf(fb, "\\x%02x", (int) ch);
		}
	}
	if (quotes) {
		duk_fb_put_byte(fb, (duk_u8) '"');
	}
#ifdef DUK_USE_REFERENCE_COUNTING
	/* XXX: limit to quoted strings only, to save keys from being cluttered? */
	duk_fb_sprintf(fb, "/%d", DUK_HEAPHDR_GET_REFCOUNT(&h->hdr));
#endif
}

#ifdef _COMMA
#undef _COMMA
#endif
#define  _COMMA()  do { \
		if (first) { \
			first = 0; \
		} else { \
			duk_fb_put_byte(fb, (duk_u8) ','); \
		} \
	} while (0)

static void print_hobject(duk_dprint_state *st, duk_hobject *h) {
	duk_fixedbuffer *fb = st->fb;
	int i;
	duk_tval *tv;
	duk_hstring *key;
	int first = 1;
	char *brace1 = "{";
	char *brace2 = "}";
	int pushed_loopstack = 0;

	if (duk_fb_is_full(fb)) {
		return;
	}

	print_shared_heaphdr(st, &h->hdr);

	if (h && DUK_HOBJECT_HAS_ARRAY_PART(h)) {
		brace1 = "[";
		brace2 = "]";
	}

	if (!h) {
		duk_fb_put_cstring(fb, "NULL");
		goto finished;
	}

	if (st->depth >= st->depth_limit) {
		if (DUK_HOBJECT_IS_COMPILEDFUNCTION(h)) {
			duk_fb_sprintf(fb, "%sobject/compiledfunction %p%s", brace1, (void *) h, brace2);
		} else if (DUK_HOBJECT_IS_NATIVEFUNCTION(h)) {
			duk_fb_sprintf(fb, "%sobject/nativefunction %p%s", brace1, (void *) h, brace2);
		} else if (DUK_HOBJECT_IS_THREAD(h)) {
			duk_fb_sprintf(fb, "%sobject/thread %p%s", brace1, (void *) h, brace2);
		} else {
			duk_fb_sprintf(fb, "%sobject %p%s", brace1, (void *) h, brace2);  /* may be NULL */
		}
		return;
	}

	for (i = 0; i < st->loop_stack_index; i++) {
		if (st->loop_stack[i] == h) {
			duk_fb_sprintf(fb, "%sLOOP:%p%s", brace1, (void *) h, brace2);
			return;
		}
	}

	/* after this, return paths should 'goto finished' for decrement */
	st->depth++;

	if (st->loop_stack_index >= st->loop_stack_limit) {
		duk_fb_sprintf(fb, "%sOUT-OF-LOOP-STACK%s", brace1, brace2);
		goto finished;
	}
	st->loop_stack[st->loop_stack_index++] = h;
	pushed_loopstack = 1;

	/*
	 *  Notation: double underscore used for internal properties which are not
	 *  stored in the property allocation (e.g. '__valstack').
	 */

	duk_fb_put_cstring(fb, brace1);

	if (h->p) {
		int a_limit;

		a_limit = h->a_size;
		if (st->internal) {
			/* dump all allocated entries, unused entries print as 'unused',
			 * note that these may extend beyond current 'length' and look
			 * a bit funny.
			 */
		} else {
			/* leave out trailing 'unused' elements */
			while (a_limit > 0) {
				tv = DUK_HOBJECT_A_GET_VALUE_PTR(h, a_limit - 1);
				if (!DUK_TVAL_IS_UNDEFINED_UNUSED(tv)) {
					break;
				}
				a_limit--;
			}
		}

		for (i = 0; i < a_limit; i++) {
			tv = DUK_HOBJECT_A_GET_VALUE_PTR(h, i);
			_COMMA();
			print_tval(st, tv);
		}
		for (i = 0; i < h->e_used; i++) {
			key = DUK_HOBJECT_E_GET_KEY(h, i);
			if (!key) {
				continue;
			}
			if (!st->internal &&
			    DUK_HSTRING_GET_BYTELEN(key) > 0 &&
			    DUK_HSTRING_GET_DATA(key)[0] == 0xff) {
				/* FIXME: cleanup to use DUK_HSTRING_FLAG_INTERNAL? */
				continue;
			}
			_COMMA();
			print_hstring(st, key, 0);
			duk_fb_put_byte(fb, (duk_u8) ':');
			if (DUK_HOBJECT_E_SLOT_IS_ACCESSOR(h, i)) {
				duk_fb_sprintf(fb, "[get:%p,set:%p]",
				               DUK_HOBJECT_E_GET_VALUE(h, i).a.get,
				               DUK_HOBJECT_E_GET_VALUE(h, i).a.set);
			} else {
				tv = &DUK_HOBJECT_E_GET_VALUE(h, i).v;
				print_tval(st, tv);
			}
			if (st->heavy) {
				duk_fb_sprintf(fb, "<%02x>", (int) DUK_HOBJECT_E_GET_FLAGS(h, i));
			}
		}
	}
	if (st->internal) {
		if (DUK_HOBJECT_HAS_EXTENSIBLE(h)) {
			_COMMA(); duk_fb_sprintf(fb, "__extensible:true");
		} else {
			;
		}
		if (DUK_HOBJECT_HAS_CONSTRUCTABLE(h)) {
			_COMMA(); duk_fb_sprintf(fb, "__constructable:true");
		} else {
			;
		}
		if (DUK_HOBJECT_HAS_BOUND(h)) {
			_COMMA(); duk_fb_sprintf(fb, "__bound:true");
		} else {
			;
		}
		if (DUK_HOBJECT_HAS_COMPILEDFUNCTION(h)) {
			_COMMA(); duk_fb_sprintf(fb, "__compiledfunction:true");
		} else {
			;
		}
		if (DUK_HOBJECT_HAS_NATIVEFUNCTION(h)) {
			_COMMA(); duk_fb_sprintf(fb, "__nativefunction:true");
		} else {
			;
		}
		if (DUK_HOBJECT_HAS_THREAD(h)) {
			_COMMA(); duk_fb_sprintf(fb, "__thread:true");
		} else {
			;
		}
		if (DUK_HOBJECT_HAS_ARRAY_PART(h)) {
			_COMMA(); duk_fb_sprintf(fb, "__array_part:true");
		} else {
			;
		}
		if (DUK_HOBJECT_HAS_STRICT(h)) {
			_COMMA(); duk_fb_sprintf(fb, "__strict:true");
		} else {
			;
		}
		if (DUK_HOBJECT_HAS_NEWENV(h)) {
			_COMMA(); duk_fb_sprintf(fb, "__newenv:true");
		} else {
			;
		}
		if (DUK_HOBJECT_HAS_NAMEBINDING(h)) {
			_COMMA(); duk_fb_sprintf(fb, "__namebinding:true");
		} else {
			;
		}
		if (DUK_HOBJECT_HAS_CREATEARGS(h)) {
			_COMMA(); duk_fb_sprintf(fb, "__createargs:true");
		} else {
			;
		}
		if (DUK_HOBJECT_HAS_ENVRECCLOSED(h)) {
			_COMMA(); duk_fb_sprintf(fb, "__envrecclosed:true");
		} else {
			;
		}
		if (DUK_HOBJECT_HAS_SPECIAL_ARRAY(h)) {
			_COMMA(); duk_fb_sprintf(fb, "__special_array:true");
		} else {
			;
		}
		if (DUK_HOBJECT_HAS_SPECIAL_STRINGOBJ(h)) {
			_COMMA(); duk_fb_sprintf(fb, "__special_stringobj:true");
		} else {
			;
		}
		if (DUK_HOBJECT_HAS_SPECIAL_ARGUMENTS(h)) {
			_COMMA(); duk_fb_sprintf(fb, "__special_arguments:true");
		} else {
			;
		}
	}
	if (st->internal && DUK_HOBJECT_IS_COMPILEDFUNCTION(h)) {
		duk_hcompiledfunction *f = (duk_hcompiledfunction *) h;
		_COMMA(); duk_fb_put_cstring(fb, "__data:"); print_hbuffer(st, f->data);
		_COMMA(); duk_fb_sprintf(fb, "__nregs:%d", f->nregs);
		_COMMA(); duk_fb_sprintf(fb, "__nargs:%d", f->nargs);
	} else if (st->internal && DUK_HOBJECT_IS_NATIVEFUNCTION(h)) {
		duk_hnativefunction *f = (duk_hnativefunction *) h;
#if 0  /* FIXME: no portable way to print function pointers */
		_COMMA(); duk_fb_sprintf(fb, "__func:%p", (void *) f->func);
#endif
		_COMMA(); duk_fb_sprintf(fb, "__nargs:%d", f->nargs);

	} else if (st->internal && DUK_HOBJECT_IS_THREAD(h)) {
		duk_hthread *t = (duk_hthread *) h;
		_COMMA(); duk_fb_sprintf(fb, "__strict:%d", t->strict);
		_COMMA(); duk_fb_sprintf(fb, "__state:%d", t->state);
		_COMMA(); duk_fb_sprintf(fb, "__unused1:%d", t->unused1);
		_COMMA(); duk_fb_sprintf(fb, "__unused2:%d", t->unused2);
		_COMMA(); duk_fb_sprintf(fb, "__valstack_max:%d", t->valstack_max);
		_COMMA(); duk_fb_sprintf(fb, "__callstack_max:%d", t->callstack_max);
		_COMMA(); duk_fb_sprintf(fb, "__catchstack_max:%d", t->catchstack_max);
		_COMMA(); duk_fb_sprintf(fb, "__valstack:%p", (void *) t->valstack);
		_COMMA(); duk_fb_sprintf(fb, "__valstack_end:%p/%d", (void *) t->valstack_end, (int) (t->valstack_end - t->valstack));
		_COMMA(); duk_fb_sprintf(fb, "__valstack_bottom:%p/%d", (void *) t->valstack_bottom, (int) (t->valstack_bottom - t->valstack));
		_COMMA(); duk_fb_sprintf(fb, "__valstack_top:%p/%d", (void *) t->valstack_top, (int) (t->valstack_top - t->valstack));
		_COMMA(); duk_fb_sprintf(fb, "__catchstack:%p", (void *) t->catchstack);
		_COMMA(); duk_fb_sprintf(fb, "__catchstack_size:%d", t->catchstack_size);
		_COMMA(); duk_fb_sprintf(fb, "__catchstack_top:%d", t->catchstack_top);
		_COMMA(); duk_fb_sprintf(fb, "__resumer:"); print_hobject(st, (duk_hobject *) t->resumer);
		/* XXX: print built-ins array? */

	}
#ifdef DUK_USE_REFERENCE_COUNTING
	if (st->internal) {
		_COMMA(); duk_fb_sprintf(fb, "__refcount:%d", DUK_HEAPHDR_GET_REFCOUNT((duk_heaphdr *) h));
	}
#endif
	if (st->internal) {
		_COMMA(); duk_fb_sprintf(fb, "__class:%d", DUK_HOBJECT_GET_CLASS_NUMBER(h));
	}

	/* prototype should be last, for readability */
	if (st->follow_proto && h->prototype) {
		_COMMA(); duk_fb_put_cstring(fb, "__prototype:"); print_hobject(st, h->prototype);
	}

	duk_fb_put_cstring(fb, brace2);

	if (st->heavy && h->h_size > 0) {
		duk_fb_put_byte(fb, (duk_u8) '<');
		for (i = 0; i < h->h_size; i++) {
			duk_i32 h_idx = DUK_HOBJECT_H_GET_INDEX(h, i);
			if (i > 0) {
				duk_fb_put_byte(fb, (duk_u8) ',');
			}
			if (h_idx == DUK_HOBJECT_HASHIDX_UNUSED) {
				duk_fb_sprintf(fb, "u");
			} else if (h_idx == DUK_HOBJECT_HASHIDX_DELETED) {
				duk_fb_sprintf(fb, "d");
			} else {
				duk_fb_sprintf(fb, "%d", (int) h_idx);
			}
		}
		duk_fb_put_byte(fb, (duk_u8) '>');
	}

 finished:
	st->depth--;
	if (pushed_loopstack) {
		st->loop_stack_index--;
		st->loop_stack[st->loop_stack_index] = NULL;
	}
}

#undef _COMMA

static void print_hbuffer(duk_dprint_state *st, duk_hbuffer *h) {
	duk_fixedbuffer *fb = st->fb;
	size_t i, n;
	duk_u8 *p;

	if (duk_fb_is_full(fb)) {
		return;
	}

	/* terminal type: no depth check */

	if (!h) {
		duk_fb_put_cstring(fb, "NULL");
		return;
	}

	if (DUK_HBUFFER_HAS_DYNAMIC(h)) {
		duk_hbuffer_dynamic *g = (duk_hbuffer_dynamic *) h;
		duk_fb_sprintf(fb, "buffer:dynamic:%p:%d:%d",
		               g->curr_alloc, g->size, g->usable_size);
	} else {
		duk_fb_sprintf(fb, "buffer:fixed:%d", DUK_HBUFFER_GET_SIZE(h));
	}

#ifdef DUK_USE_REFERENCE_COUNTING
	duk_fb_sprintf(fb, "/%d", DUK_HEAPHDR_GET_REFCOUNT(&h->hdr));
#endif

	if (st->hexdump) {
		duk_fb_sprintf(fb, "=[");
		n = DUK_HBUFFER_GET_SIZE(h);
		p = DUK_HBUFFER_GET_DATA_PTR(h);
		for (i = 0; i < n; i++) {
			duk_fb_sprintf(fb, "%02x", (int) p[i]);
		}
		duk_fb_sprintf(fb, "]");
	}
}

static void print_heaphdr(duk_dprint_state *st, duk_heaphdr *h) {
	duk_fixedbuffer *fb = st->fb;

	if (duk_fb_is_full(fb)) {
		return;
	}

	if (!h) {
		duk_fb_put_cstring(fb, "NULL");
		return;
	}

	switch (DUK_HEAPHDR_GET_TYPE(h)) {
	case DUK_HTYPE_STRING:
		print_hstring(st, (duk_hstring *) h, 1);
		break;
	case DUK_HTYPE_OBJECT:
		print_hobject(st, (duk_hobject *) h);
		break;
	case DUK_HTYPE_BUFFER:
		print_hbuffer(st, (duk_hbuffer *) h);
		break;
	default:
		duk_fb_sprintf(fb, "[unknown htype %d]", DUK_HEAPHDR_GET_TYPE(h));
		break;
	}
}

static void print_tval(duk_dprint_state *st, duk_tval *tv) {
	duk_fixedbuffer *fb = st->fb;

	if (duk_fb_is_full(fb)) {
		return;
	}

	/* depth check is done when printing an actual type */

	if (st->heavy) {
		duk_fb_sprintf(fb, "(%p)", (void *) tv);
	}

	if (!tv) {
		duk_fb_put_cstring(fb, "NULL");
		return;
	}

	if (st->binary) {
		int i;
		duk_fb_put_byte(fb, (duk_u8) '[');
		for (i = 0; i < sizeof(*tv); i++) {
			duk_fb_sprintf(fb, "%02x", (int) ((unsigned char *)tv)[i]);
		}
		duk_fb_put_byte(fb, (duk_u8) ']');
	}

	if (st->heavy) {
		duk_fb_put_byte(fb, (duk_u8) '<');
	}
	switch (DUK_TVAL_GET_TAG(tv)) {
	case DUK_TAG_UNDEFINED: {
		if (DUK_TVAL_IS_UNDEFINED_UNUSED(tv)) {
			duk_fb_put_cstring(fb, "unused");
		} else {
			duk_fb_put_cstring(fb, "undefined");
		}
		break;
	}
	case DUK_TAG_NULL: {
		duk_fb_put_cstring(fb, "null");
		break;
	}
	case DUK_TAG_BOOLEAN: {
		duk_fb_put_cstring(fb, DUK_TVAL_GET_BOOLEAN(tv) ? "true" : "false");
		break;
	}
	case DUK_TAG_STRING: {
		/* Note: string is a terminal heap object, so no depth check here */
		print_hstring(st, DUK_TVAL_GET_STRING(tv), 1);
		break;
	}
	case DUK_TAG_OBJECT: {
		print_hobject(st, DUK_TVAL_GET_OBJECT(tv));
		break;
	}
	case DUK_TAG_BUFFER: {
		print_hbuffer(st, DUK_TVAL_GET_BUFFER(tv));
		break;
	}
	case DUK_TAG_POINTER: {
		duk_fb_sprintf(fb, "pointer:%p", DUK_TVAL_GET_POINTER(tv));
		break;
	}
	default: {
		/* IEEE double is approximately 16 decimal digits; print a couple extra */
		DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv));
		duk_fb_sprintf(fb, "%.18g", DUK_TVAL_GET_NUMBER(tv));
		break;
	}
	}
	if (st->heavy) {
		duk_fb_put_byte(fb, (duk_u8) '>');
	}
}

static void print_instr(duk_dprint_state *st, duk_instr ins) {
	duk_fixedbuffer *fb = st->fb;
	int op;
	const char *op_name;
	const char *extraop_name;

	op = DUK_DEC_OP(ins);
	op_name = bc_optab[op];

	/* FIXME: option to fix opcode length so it lines up nicely */

	if (op == DUK_OP_EXTRA) {
		extraop_name = bc_extraoptab[DUK_DEC_A(ins)];

		duk_fb_sprintf(fb, "%s %d, %d",
		               extraop_name, DUK_DEC_B(ins), DUK_DEC_C(ins));
	} else if (op == DUK_OP_JUMP) {
		int diff1 = DUK_DEC_ABC(ins) - DUK_BC_JUMP_BIAS;  /* from next pc */
		int diff2 = diff1 + 1;                            /* from curr pc */

		duk_fb_sprintf(fb, "%s %d (to pc%c%d)",
		               op_name, diff1, (diff2 >= 0 ? '+' : '-'), (diff2 >= 0 ? diff2 : -diff2));
	} else {
		duk_fb_sprintf(fb, "%s %d, %d, %d",
		               op_name, DUK_DEC_A(ins), DUK_DEC_B(ins), DUK_DEC_C(ins));
	}
}

static void print_opcode(duk_dprint_state *st, int opcode) {
	duk_fixedbuffer *fb = st->fb;

	if (opcode < DUK_BC_OP_MIN || opcode > DUK_BC_OP_MAX) {
		duk_fb_sprintf(fb, "?(%d)", opcode);
	} else {
		duk_fb_sprintf(fb, "%s", bc_optab[opcode]);
	}
}

int duk_debug_vsnprintf(char *str, size_t size, const char *format, va_list ap) {
	duk_fixedbuffer fb;
	const char *p = format;
	const char *p_end = p + strlen(format);
	int retval;
	
	memset(&fb, 0, sizeof(fb));
	fb.buffer = (duk_u8 *) str;
	fb.length = size;
	fb.offset = 0;
	fb.truncated = 0;

	while (p < p_end) {
		char ch = *p++;
		const char *p_begfmt = NULL;
		int got_exclamation = 0;
		duk_dprint_state st;

		if (ch != '%') {
			duk_fb_put_byte(&fb, (duk_u8) ch);
			continue;
		}

		/*
		 *  Format tag parsing.  Since we don't understand all the
		 *  possible format tags allowed, we just scan for a terminating
		 *  specifier and keep track of relevant modifiers that we do
		 *  understand.  See man 3 printf.
		 */

		memset(&st, 0, sizeof(st));
		st.fb = &fb;
		st.depth = 0;
		st.depth_limit = 1;
		st.loop_stack_index = 0;
		st.loop_stack_limit = LOOP_STACK_DEPTH;

		p_begfmt = p - 1;
		while (p < p_end) {
			ch = *p++;

			if (ch == '*') {
				/* unsupported: would consume multiple args */
				goto error;
			} else if (ch == '%') {
				duk_fb_put_byte(&fb, (duk_u8) '%');
				break;
			} else if (ch == '!') {
				got_exclamation = 1;
			} else if (got_exclamation && ch == 'd') {
				st.depth_limit = DEEP_DEPTH_LIMIT;
			} else if (got_exclamation && ch == 'p') {
				st.follow_proto = 1;
			} else if (got_exclamation && ch == 'i') {
				st.internal = 1;
			} else if (got_exclamation && ch == 'x') {
				st.hexdump = 1;
			} else if (got_exclamation && ch == 'h') {
				st.heavy = 1;
			} else if (got_exclamation && ch == '@') {
				st.pointer = 1;
			} else if (got_exclamation && ch == '#') {
				st.binary = 1;
			} else if (got_exclamation && ch == 'T') {
				duk_tval *t = va_arg(ap, duk_tval *);
				if (st.pointer && !st.heavy) {
					duk_fb_sprintf(&fb, "(%p)", (void *) t);
				}
				print_tval(&st, t);
				break;
			} else if (got_exclamation && ch == 'O') {
				duk_heaphdr *t = va_arg(ap, duk_heaphdr *);
				if (st.pointer && !st.heavy) {
					duk_fb_sprintf(&fb, "(%p)", (void *) t);
				}
				print_heaphdr(&st, t);
				break;
			} else if (got_exclamation && ch == 'I') {
				duk_instr t = va_arg(ap, duk_instr);
				print_instr(&st, t);
				break;
			} else if (got_exclamation && ch == 'C') {
				int t = va_arg(ap, int);
				print_opcode(&st, t);
				break;
			} else if (!got_exclamation && strchr(ALLOWED_STANDARD_CONVERSION_SPECIFIERS, (int) ch)) {
				char fmtbuf[MAX_FORMAT_TAG_LENGTH];
				int fmtlen;

				fmtlen = p - p_begfmt;
				if (fmtlen < 0 || fmtlen >= sizeof(fmtbuf)) {
					/* format is too large, abort */
					goto error;
				}
				memset(fmtbuf, 0, sizeof(fmtbuf));
				memcpy(fmtbuf, p_begfmt, fmtlen);

				/* assume exactly 1 arg, which is why '*' is forbidden; arg size still
				 * depends on type though.
				 */

				/* FIXME: check size for other types.. actually it would be best to switch
				 * for supported standard formats and get args explicitly
				 */
				if (ch == 'f' || ch == 'g' || ch == 'e') {
					double arg;
					arg = va_arg(ap, double);
					duk_fb_sprintf(&fb, fmtbuf, arg);
				} else {
					void *arg;
					arg = va_arg(ap, void *);
					duk_fb_sprintf(&fb, fmtbuf, arg);
				}
				break;
			} else {
				/* ignore */
			}
		}
	}
	goto done;

 error:
	duk_fb_put_cstring(&fb, "FMTERR");
	/* fall through */

 done:
	retval = fb.offset;
	duk_fb_put_byte(&fb, (duk_u8) 0);

	/* return total chars written excluding terminator */
	return retval;
}

int duk_debug_snprintf(char *str, size_t size, const char *format, ...) {
	int retval;
	va_list ap;
	va_start(ap, format);
	retval = duk_debug_vsnprintf(str, size, format, ap);
	va_end(ap);
	return retval;
}

#endif  /* DUK_USE_DEBUG */

#line 1 "duk_error_augment.c"
/*
 *  Augment an error object with custom fields like raw traceback data.
 *  An error is augmented when it is created, not when it is thrown
 *  (otherwise rethrowing would work poorly).
 *
 *  May throw an error (e.g. alloc error).
 *
 *  Ecmascript allows throwing any values, so all values cannot be
 *  augmented.  Currently, we only augment error values which are Error
 *  instances and are also extensible.
 */

/* include removed: duk_internal.h */

#ifdef DUK_USE_AUGMENT_ERRORS

#ifdef DUK_USE_TRACEBACKS
static void add_traceback(duk_hthread *thr, duk_hthread *thr_callstack, duk_hobject *obj, int err_index) {
	duk_context *ctx = (duk_context *) thr;
	int depth;
	int i, i_min;
	int arr_idx;

	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(thr_callstack != NULL);
	DUK_ASSERT(obj != NULL);
	DUK_ASSERT(err_index >= 0);
	DUK_ASSERT(ctx != NULL);

	/*
	 *  The traceback format is pretty arcane in an attempt to keep it compact
	 *  and cheap to create.  It may change arbitrarily from version to version.
	 *  It should be decoded/accessed through version specific accessors.
	 *
	 *  See doc/error-objects.txt.
	 */

	DUK_DDDPRINT("adding traceback to object: %!O", (duk_heaphdr *) obj);

	duk_push_array(ctx);  /* XXX: specify array size, as we know it */

	depth = DUK_OPT_TRACEBACK_DEPTH;
	i_min = (thr_callstack->callstack_top > depth ? thr_callstack->callstack_top - depth : 0);
	DUK_ASSERT(i_min >= 0);
	arr_idx = 0;

	for (i = thr_callstack->callstack_top - 1; i >= i_min; i--) {
		double d;

		/*
		 *  Note: each API operation potentially resizes the callstack,
		 *  so be careful to re-lookup after every operation.  Currently
		 *  these is no issue because we don't store a temporary 'act'
		 *  pointer at all.
		 */

		/* [... arr] */

		DUK_ASSERT(thr_callstack->callstack[i].func != NULL);
		DUK_ASSERT(thr_callstack->callstack[i].pc >= 0);

		/* add function */
		duk_push_hobject(ctx, thr_callstack->callstack[i].func);  /* -> [... arr func] */
		duk_put_prop_index(ctx, -2, arr_idx);
		arr_idx++;

		/* add a number containing: pc, activation flags */
		d = ((double) thr_callstack->callstack[i].flags) * DUK_DOUBLE_2TO32 +  /* assume PC is at most 32 bits and non-negative */
		    (double) thr_callstack->callstack[i].pc;
		duk_push_number(ctx, d);  /* -> [... arr num] */
		duk_put_prop_index(ctx, -2, arr_idx);
		arr_idx++;

		/* FIXME: some more features to record (somehow):
		 *   - current this binding?
		 */

		/* FIXME: efficient array pushing, e.g. preallocate array, write DIRECTLY to array entries, etc. */
	}

	/* [... arr] */
	duk_put_prop_stridx(ctx, err_index, DUK_STRIDX_TRACEBACK);  /* -> [...] */
}
#endif  /* DUK_USE_TRACEBACKS */

/*
 *  thr: thread containing the error value
 *  thr_callstack: thread which should be used for generating callstack etc.
 */

void duk_err_augment_error(duk_hthread *thr, duk_hthread *thr_callstack, int err_index) {
	duk_context *ctx = (duk_context *) thr;
	duk_hobject *obj;

	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(thr_callstack != NULL);
	DUK_ASSERT(ctx != NULL);

	err_index = duk_require_normalize_index(ctx, err_index);

	/*
	 *  Criteria for augmenting:
	 *
	 *   - augmentation enabled in build (naturally)
	 *   - error value is an extensible object
	 *   - error value internal prototype chain contains the built-in
	 *     Error prototype object (i.e. 'val instanceof Error')
	 */

	obj = duk_require_hobject(ctx, err_index);
	if (!obj) {	
		DUK_DDDPRINT("error value not an object, not augmented");
		return;
	}
	if (!DUK_HOBJECT_HAS_EXTENSIBLE(obj)) {
		DUK_DDDPRINT("error value not extensible, not augmented");
		return;
	}
	if (!duk_hobject_prototype_chain_contains(thr, obj, thr->builtins[DUK_BIDX_ERROR_PROTOTYPE])) {
		DUK_DDDPRINT("error value not inherited from Error, not augmented");
		return;
	}

	/* Yes, augment error. */

	/* FIXME: here we'd like to have a variant of "duk_def_prop_stridx" which
	 * would refuse to add a property if it already exists to avoid any issues
	 * with protected properties.
	 */

#ifdef DUK_USE_TRACEBACKS
	if (duk_hobject_hasprop_raw(thr, obj, DUK_HTHREAD_STRING_TRACEBACK(thr))) {
		DUK_DDDPRINT("error value already has a 'traceback' property, not modifying it");
	} else {
		add_traceback(thr, thr_callstack, obj, err_index);
	}
#endif  /* DUK_USE_TRACEBACKS */

	if (thr_callstack->callstack_top > 0) {
		duk_activation *act;
		duk_hobject *func;
		duk_hbuffer *pc2line;

		act = thr_callstack->callstack + thr_callstack->callstack_top - 1;
		func = act->func;
		if (func) {
			int pc = act->pc;
			duk_u32 line;
			act = NULL;  /* invalidated by pushes */

			pc--;  /* PC points to next instruction, find offending PC */

			duk_push_hobject(ctx, func);

			/* FIXME: now set function name as filename; record function and file name
			 * separately?  Function object is already in traceback though.
			 */
			duk_get_prop_stridx(ctx, -1, DUK_STRIDX_NAME);
			duk_put_prop_stridx(ctx, err_index, DUK_STRIDX_FILE_NAME);

			if (DUK_HOBJECT_IS_COMPILEDFUNCTION(func)) {
				duk_push_false(ctx);
				duk_put_prop_stridx(ctx, err_index, DUK_STRIDX_IS_NATIVE);

				/* FIXME: add PC only if pc2line fails? */
				duk_push_number(ctx, pc);
				duk_put_prop_stridx(ctx, err_index, DUK_STRIDX_PC);

				duk_get_prop_stridx(ctx, -1, DUK_STRIDX_INT_PC2LINE);
				if (duk_is_buffer(ctx, -1)) {
					pc2line = duk_get_hbuffer(ctx, -1);
					DUK_ASSERT(!DUK_HBUFFER_HAS_DYNAMIC(pc2line));
					line = duk_hobject_pc2line_query((duk_hbuffer_fixed *) pc2line, pc);
					duk_push_number(ctx, (double) line);  /* FIXME: u32 */
					duk_put_prop_stridx(ctx, err_index, DUK_STRIDX_LINE_NUMBER);
				}
				duk_pop(ctx);
			} else {
				duk_push_true(ctx);
				duk_put_prop_stridx(ctx, err_index, DUK_STRIDX_IS_NATIVE);
			}

			duk_pop(ctx);
		}
	}
}

#endif  /* DUK_USE_AUGMENT_ERRORS */

#line 1 "duk_error_fatal.c"
/*
 *  Handle fatal error.
 */

/* include removed: duk_internal.h */

void duk_default_fatal_handler(duk_context *ctx, int code) {
	DUK_DPRINT("default fatal handler called, code %d -> calling DUK_PANIC()", code);
	DUK_PANIC(code, "fatal error (default handler), code %d", code);
	DUK_NEVER_HERE();
}


#line 1 "duk_error_longjmp.c"
/*
 *  Do a longjmp call, calling the fatal error handler if no
 *  catchpoint exists.
 */

/* include removed: duk_internal.h */

/* FIXME: noreturn */
void duk_err_longjmp(duk_hthread *thr) {
	DUK_ASSERT(thr != NULL);

	if (!thr->heap->lj.jmpbuf_ptr) {
		/*
		 *  If we don't have a jmpbuf_ptr, there is little we can do
		 *  except panic.  The caller's expectation is that we never
		 *  return.
		 */

		duk_fatal((duk_context *) thr, DUK_ERR_UNCAUGHT_ERROR);
		DUK_NEVER_HERE();
	}

	longjmp(thr->heap->lj.jmpbuf_ptr->jb, DUK_LONGJMP_DUMMY_VALUE);
}

#line 1 "duk_error_macros.c"
/*
 *  Error macro wrapper implementations.
 */

#include <stdarg.h>
/* include removed: duk_internal.h */

#ifdef DUK_USE_VERBOSE_ERRORS

#define  BUFSIZE  256  /* size for formatting buffers */

#ifdef DUK_USE_VARIADIC_MACROS
void duk_err_handle_error(const char *filename, int line, duk_hthread *thr, int code, const char *fmt, ...) {
	va_list ap;
	char msg[BUFSIZE];
	memset(msg, 0, sizeof(msg));
	va_start(ap, fmt);
	(void) vsnprintf(msg, sizeof(msg) - 1, fmt, ap);
	duk_err_create_and_throw(thr, code, msg, filename, line);
	va_end(ap);  /* dead code, but ensures portability (see Linux man page notes) */
}

void duk_err_handle_panic(const char *filename, int line, int code, const char *fmt, ...) {
	va_list ap;
	char msg1[BUFSIZE];
	char msg2[BUFSIZE];
	memset(msg1, 0, sizeof(msg1));
	memset(msg2, 0, sizeof(msg2));
	va_start(ap, fmt);
	(void) vsnprintf(msg1, sizeof(msg1) - 1, fmt, ap);
	(void) snprintf(msg2, sizeof(msg2) - 1, "(%s:%d): %s", filename ? filename : "null", line, msg1);
	DUK_PANIC_HANDLER(code, msg2);
	va_end(ap);  /* dead code */
}
#else  /* DUK_USE_VARIADIC_MACROS */
const char *duk_err_file_stash = NULL;
int duk_err_line_stash = -1;

static void _handle_error(const char *filename, int line, duk_hthread *thr, int code, const char *fmt, va_list ap) {
	char msg[BUFSIZE];
	memset(msg, 0, sizeof(msg));
	(void) vsnprintf(msg, sizeof(msg) - 1, fmt, ap);
	duk_err_create_and_throw(thr, code, msg, filename, line);
}

static void _handle_panic(const char *filename, int line, int code, const char *fmt, va_list ap) {
	char msg1[BUFSIZE];
	char msg2[BUFSIZE];
	memset(msg1, 0, sizeof(msg1));
	memset(msg2, 0, sizeof(msg2));
	(void) vsnprintf(msg1, sizeof(msg1) - 1, fmt, ap);
	(void) snprintf(msg2, sizeof(msg2) - 1, "(%s:%d): %s", filename ? filename : "null", line, msg1);
	DUK_PANIC_HANDLER(code, msg2);
}

void duk_err_handle_error(const char *filename, int line, duk_hthread *thr, int code, const char *fmt, ...) {
	va_list ap;
	va_start(ap, fmt);
	_handle_error(filename, line, thr, code, fmt, ap);
	va_end(ap);  /* dead code */
}

void duk_err_handle_error_stash(duk_hthread *thr, int code, const char *fmt, ...) {
	va_list ap;
	va_start(ap, fmt);
	_handle_error(duk_err_file_stash, duk_err_line_stash, thr, code, fmt, ap);
	va_end(ap);  /* dead code */
}

void duk_err_handle_panic(const char *filename, int line, int code, const char *fmt, ...) {
	va_list ap;
	va_start(ap, fmt);
	_handle_panic(filename, line, code, fmt, ap);
	va_end(ap);  /* dead code */
}

void duk_err_handle_panic_stash(int code, const char *fmt, ...) {
	va_list ap;
	va_start(ap, fmt);
	_handle_panic(duk_err_file_stash, duk_err_line_stash, code, fmt, ap);
	va_end(ap);  /* dead code */
}
#endif  /* DUK_USE_VARIADIC_MACROS */

#else  /* DUK_USE_VERBOSE_ERRORS */

#ifdef DUK_USE_VARIADIC_MACROS
void duk_err_handle_error(duk_hthread *thr, int code) {
	duk_err_create_and_throw(thr, code);
}

void duk_err_handle_panic(int code) {
	DUK_PANIC_HANDLER(code, NULL);
}
#else  /* DUK_USE_VARIADIC_MACROS */
void duk_err_handle_error_nonverbose1(duk_hthread *thr, int code, const char *fmt, ...) {
	duk_err_create_and_throw(thr, code);
}

void duk_err_handle_error_nonverbose2(const char *filename, int line, duk_hthread *thr, int code, const char *fmt, ...) {
	duk_err_create_and_throw(thr, code);
}

void duk_err_handle_panic_nonverbose1(int code, const char *fmt, ...) {
	DUK_PANIC_HANDLER(code, NULL);
}

void duk_err_handle_panic_nonverbose2(const char *filename, int line, int code, const char *fmt, ...) {
	DUK_PANIC_HANDLER(code, NULL);
}
#endif  /* DUK_USE_VARIADIC_MACROS */

#endif  /* DUK_USE_VERBOSE_ERRORS */

#line 1 "duk_error_misc.c"
/*
 *  Error helpers
 */

/* include removed: duk_internal.h */

/*
 *  Get prototype object for an integer error code.
 */

duk_hobject *duk_error_prototype_from_code(duk_hthread *thr, int err_code) {
	switch (err_code) {
	case DUK_ERR_EVAL_ERROR:
		return thr->builtins[DUK_BIDX_EVAL_ERROR_PROTOTYPE];
	case DUK_ERR_RANGE_ERROR:
		return thr->builtins[DUK_BIDX_RANGE_ERROR_PROTOTYPE];
	case DUK_ERR_REFERENCE_ERROR:
		return thr->builtins[DUK_BIDX_REFERENCE_ERROR_PROTOTYPE];
	case DUK_ERR_SYNTAX_ERROR:
		return thr->builtins[DUK_BIDX_SYNTAX_ERROR_PROTOTYPE];
	case DUK_ERR_TYPE_ERROR:
		return thr->builtins[DUK_BIDX_TYPE_ERROR_PROTOTYPE];
	case DUK_ERR_URI_ERROR:
		return thr->builtins[DUK_BIDX_URI_ERROR_PROTOTYPE];

	/* XXX: more specific error classes? */
	case DUK_ERR_UNIMPLEMENTED_ERROR:
	case DUK_ERR_INTERNAL_ERROR:
	case DUK_ERR_ALLOC_ERROR:
	case DUK_ERR_ASSERTION_ERROR:
	case DUK_ERR_API_ERROR:
	case DUK_ERR_ERROR:
	default:
		return thr->builtins[DUK_BIDX_ERROR_PROTOTYPE];
	}
}

/*
 *  Exposed helper for setting up heap longjmp state.
 */

void duk_err_setup_heap_ljstate(duk_hthread *thr, int lj_type) {
	duk_tval tv_tmp;

	/* FIXME: validate lj_type */
	thr->heap->lj.type = lj_type;

	DUK_ASSERT(thr->valstack_top > thr->valstack);
	DUK_TVAL_SET_TVAL(&tv_tmp, &thr->heap->lj.value1);
	DUK_TVAL_SET_TVAL(&thr->heap->lj.value1, thr->valstack_top - 1);
	DUK_TVAL_INCREF(thr, &thr->heap->lj.value1);
	DUK_TVAL_DECREF(thr, &tv_tmp);

	duk_pop((duk_context *) thr);
}


#line 1 "duk_error_throw.c"
/*
 *  Create and throw an Ecmascript error object based on a code and a message.
 *
 *  Used when we throw errors internally.  Ecmascript generated error objects
 *  are created by Ecmascript code, and the throwing is handled by the bytecode
 *  executor.
 */

/* include removed: duk_internal.h */

/*
 *  Helper for calling errhandler.
 *
 *  'thr' must be the currently active thread; the errhandler is called
 *  in its context.  The valstack of 'thr' must have the error value on
 *  top, and will be replaced by another error value based on the return
 *  value of the errhandler.
 *
 *  The helper calls duk_handle_call() recursively in protected mode, but
 *  without an error handler.  Before that call happens, no longjmps
 *  should happen; as a consequence, we must assume that the valstack
 *  contains enough temporary space for arguments and such.
 *
 *  If the errhandler call causes an error to be thrown, that error will
 *  (silently) replace the original error now.  This would be easy to
 *  change and even to signal to the caller.
 *
 *  Note: since further longjmp()s may occur while calling the errhandler
 *  (for many reasons, e.g. a labeled 'break' inside the handler), the
 *  caller can make no assumptions on the thr->heap->lj state after the
 *  call.  This is currently not an issue, because the lj state is only
 *  written after the errhandler finishes.
 */

static void call_errhandler(duk_hthread *thr) {
	int call_flags;
	int rc;

	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(thr->heap != NULL);

	if (!thr->heap->lj.errhandler) {
		DUK_DDDPRINT("no errhandler, return");
		return;
	}

	/* FIXME: assert/require for valstack space */

	/* [ ... errval ] */

	DUK_DDDPRINT("errhandler is %p", (void *) thr->heap->lj.errhandler);
	DUK_DDDPRINT("errhandler dump: %!O", (duk_heaphdr *) thr->heap->lj.errhandler);

	duk_push_hobject((duk_context *) thr, thr->heap->lj.errhandler);
	duk_insert((duk_context *) thr, -2);  /* -> [ ... errhandler errval ] */
	duk_push_undefined((duk_context *) thr);
	duk_insert((duk_context *) thr, -2);  /* -> [ ... errhandler undefined(= this) errval ] */

	/* [ ... errhandler undefined errval ] */

	/*
	 *  DUK_CALL_FLAG_IGNORE_RECLIMIT causes duk_handle_call() to ignore C
	 *  recursion depth limit (and won't increase it either).  This is
	 *  dangerous, but useful because it allows an errhandler to run even
	 *  if the original error is caused by C recursion depth limit.  Because
	 *  errhandler is NULL in the errhandler call, the errhandler call
	 *  can't cause the same situation to occur again.
	 *
	 *  We ignore errors now: a success return and an error value both
	 *  replace the original error value.  (This would be easy to change.)
	 */

	call_flags = DUK_CALL_FLAG_PROTECTED |
	             DUK_CALL_FLAG_IGNORE_RECLIMIT;  /* protected, ignore reclimit, not constructor */

	rc = duk_handle_call(thr,
	                     1,          /* num args */
	                     call_flags, /* call_flags */
	                     NULL);      /* errhandler */
	DUK_UNREF(rc);  /* no need to check now: both success and error are OK */

	/* [ ... errval ] */
}

/*
 *  Create and throw an error
 *
 *  Push an error object on top of the stack, possibly call an errhandler,
 *  and finally longjmp.
 *
 *  If an error occurs while we're dealing with the current error, we might
 *  enter an infinite recursion loop.  This is prevented by detecting a
 *  "double fault" through the heap->handling_error flag; the recursion
 *  then stops at the second level.
 */

#ifdef DUK_USE_VERBOSE_ERRORS
void duk_err_create_and_throw(duk_hthread *thr, duk_u32 code, const char *msg, const char *filename, int line) {
#else
void duk_err_create_and_throw(duk_hthread *thr, duk_u32 code) {
#endif
	duk_context *ctx = (duk_context *) thr;
	int double_error = thr->heap->handling_error;

#ifdef DUK_USE_VERBOSE_ERRORS
	DUK_DDPRINT("duk_err_create_and_throw(): code=%d, msg=%s, filename=%s, line=%d",
	             code, msg ? msg : "null", filename ? filename : "null", line);
#else
	DUK_DDPRINT("duk_err_create_and_throw(): code=%d", code);
#endif

	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(ctx != NULL);

	thr->heap->handling_error = 1;

	/*
	 *  Create and push an error object onto the top of stack.
	 *  If a "double error" occurs, use a fixed error instance
	 *  to avoid further trouble.
	 */

	/* FIXME: if attempt to push beyond allocated valstack, this double fault
	 * handling fails miserably.  We should really write the double error
	 * directly to thr->heap->lj.value1 and avoid valstack use entirely.
	 */

	if (double_error) {
		if (thr->builtins[DUK_BIDX_DOUBLE_ERROR]) {
			DUK_DPRINT("double fault detected -> push built-in fixed 'double error' instance");
			duk_push_hobject(ctx, thr->builtins[DUK_BIDX_DOUBLE_ERROR]);
		} else {
			DUK_DPRINT("double fault detected; there is no built-in fixed 'double error' instance "
			           "-> push the error code as a number");
			duk_push_int(ctx, code);
		}
	} else {
		/* Error object is augmented at its creation here. */
		duk_require_stack(ctx, 1);
#ifdef DUK_USE_VERBOSE_ERRORS
		duk_push_error_object(ctx, code, msg);
#else
		duk_push_error_object(ctx, code, NULL);
#endif
	}

	/*
	 *  Call errhandler (unless error is an alloc error)
	 *
	 *  Note: must back up the current jmpbuf if it is the shared bytecode
	 *  executor one (handled internally by the helper).
	 */

	if (double_error || code == DUK_ERR_ALLOC_ERROR) {
		DUK_DPRINT("alloc or double error: skip calling errhandler to avoid further trouble");
	} else {
		call_errhandler(thr);
	}

	/*
	 *  Finally, longjmp
	 */

	thr->heap->handling_error = 0;

	duk_err_setup_heap_ljstate(thr, DUK_LJ_TYPE_THROW);

	DUK_DDDPRINT("THROW ERROR (INTERNAL): %!iT, %!iT",
	             &thr->heap->lj.value1, &thr->heap->lj.value2);

	duk_err_longjmp(thr);
	DUK_NEVER_HERE();
}

/*
 *  Helper for C function call negative return values.
 */

void duk_error_throw_from_negative_rc(duk_hthread *thr, int rc) {
	const char *msg;
	int code;

	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(rc < 0);

	/* FIXME: this generates quite large code - perhaps select the error
	 * class based on the code and then just use the error 'name'?
	 */

	code = -rc;

	switch (rc) {
	case DUK_RET_UNIMPLEMENTED_ERROR:  msg = "unimplemented"; break;
	case DUK_RET_UNSUPPORTED_ERROR:    msg = "unsupported"; break;
	case DUK_RET_INTERNAL_ERROR:       msg = "internal error"; break;
	case DUK_RET_ALLOC_ERROR:          msg = "alloc error"; break;
	case DUK_RET_ASSERTION_ERROR:      msg = "assertion error"; break;
	case DUK_RET_API_ERROR:            msg = "api error"; break;
	case DUK_RET_UNCAUGHT_ERROR:       msg = "uncaught error"; break;
	case DUK_RET_ERROR:                msg = "error"; break;
	case DUK_RET_EVAL_ERROR:           msg = "eval error"; break;
	case DUK_RET_RANGE_ERROR:          msg = "range error"; break;
	case DUK_RET_REFERENCE_ERROR:      msg = "reference error"; break;
	case DUK_RET_SYNTAX_ERROR:         msg = "syntax error"; break;
	case DUK_RET_TYPE_ERROR:           msg = "type error"; break;
	case DUK_RET_URI_ERROR:            msg = "uri error"; break;
	default:                           msg = "unknown error"; break;
	}

	DUK_UNREF(msg);

	DUK_ERROR(thr, code, "%s (rc %d)", msg, rc);
	DUK_NEVER_HERE();
}


#line 1 "duk_hbuffer_alloc.c"
/*
 *  duk_hbuffer allocation and freeing.
 */

/* include removed: duk_internal.h */

duk_hbuffer *duk_hbuffer_alloc(duk_heap *heap, size_t size, int dynamic) {
	duk_hbuffer *res = NULL;
	size_t alloc_size;

	DUK_DDDPRINT("allocate hbuffer");

	if (dynamic) {
		alloc_size = sizeof(duk_hbuffer_dynamic);
	} else {
		/* FIXME: maybe remove safety NUL term for buffers? */
		alloc_size = sizeof(duk_hbuffer_fixed) + size + 1;  /* +1 for a safety nul term */
	}

	res = DUK_ALLOC(heap, alloc_size);
	if (!res) {
		goto error;
	}

	/* zero everything */
	memset(res, 0, alloc_size);

	if (dynamic) {
		duk_hbuffer_dynamic *h = (duk_hbuffer_dynamic *) res;
		void *ptr;
		if (size > 0) {
			/* FIXME: maybe remove safety NUL term for buffers? */
			DUK_DDDPRINT("dynamic buffer with nonzero size, alloc actual buffer");
			ptr = DUK_ALLOC(heap, size + 1);  /* +1 for a safety nul term */
			if (!ptr) {
				goto error;
			}
			memset(ptr, 0, size + 1);

			h->curr_alloc = ptr;
			h->usable_size = size;  /* snug */
		} else {
#ifdef DUK_USE_EXPLICIT_NULL_INIT
			h->curr_alloc = NULL;
#endif
		}
	}

	res->size = size;

	DUK_HEAPHDR_SET_TYPE(&res->hdr, DUK_HTYPE_BUFFER);
	if (dynamic) {
		DUK_HBUFFER_SET_DYNAMIC(res);
	}
        DUK_HEAP_INSERT_INTO_HEAP_ALLOCATED(heap, &res->hdr);

	DUK_DDDPRINT("allocated hbuffer: %p", res);
	return res;

 error:
	DUK_DDPRINT("hbuffer allocation failed");

	DUK_FREE(heap, res);
	return NULL;
}

#line 1 "duk_hbuffer_ops.c"
/*
 *  duk_hbuffer operations such as resizing and inserting/appending data to
 *  a dynamic buffer.
 *
 *  Append operations append to the end of the buffer and they are relatively
 *  efficient: the buffer is grown with a "spare" part relative to the buffer
 *  size to minimize reallocations.  Insert operations need to move existing
 *  data forward in the buffer with memmove() and are not very efficient.
 *  They are used e.g. by the regexp compiler to "backpatch" regexp bytecode.
 */

/* include removed: duk_internal.h */

/*
 *  Resizing
 */

static size_t add_spare(size_t size) {
	size_t spare = (size / DUK_HBUFFER_SPARE_DIVISOR) + DUK_HBUFFER_SPARE_ADD;
	size_t res;

	/* FIXME: handle corner cases where size is close to size limit (wraparound) */
	res = size + spare;
	DUK_ASSERT(res >= size);

	return res;
}

void duk_hbuffer_resize(duk_hthread *thr, duk_hbuffer_dynamic *buf, size_t new_size, size_t new_usable_size) {
	size_t alloc_size;
	void **ptr;
	void *res;

	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(buf != NULL);
	DUK_ASSERT(new_usable_size >= new_size);
	DUK_ASSERT(DUK_HBUFFER_HAS_DYNAMIC(buf));

	/*
	 *  Note: use indirect realloc variant just in case mark-and-sweep
	 *  (finalizers) might resize this same buffer during garbage
	 *  collection.
	 */

	/* FIXME: maybe remove safety NUL term for buffers? */
	alloc_size = new_usable_size + 1;  /* +1 for safety nul term */
	ptr = &buf->curr_alloc;
	res = DUK_REALLOC_INDIRECT(thr->heap, ptr, alloc_size);
	if (res) {
		DUK_DDDPRINT("resized dynamic buffer %p:%d:%d -> %p:%d:%d",
		             buf->curr_alloc, buf->size, buf->usable_size,
		             res, new_size, new_usable_size);

		/*
		 *  All data in [new_size,new_alloc_size[ should be zeroed.
		 *
		 *  The current memset could be optimized by taking advantage of
		 *  the knowledge that the old "spare" area is already zeroed
		 *  (an invariant which is maintained at all times).  So, this
		 *  could be optimized to two smaller memsets, one to handle a
		 *  size change, and another to handle an alloc size change.
		 *  However, this would probably not be useful in practice, as
		 *  the spare is usually very small.
		 */

		DUK_ASSERT(new_usable_size + 1 > new_size);
		memset((void *) ((char *) res + new_size),
		       0,
		       new_usable_size + 1 - new_size);

		buf->size = new_size;
		buf->usable_size = new_usable_size;
		*ptr = res;
	} else {
		DUK_ERROR(thr, DUK_ERR_ALLOC_ERROR, "failed to resize dynamic buffer from %d:%d to %d:%d",
		          buf->size, buf->usable_size, new_size, new_usable_size);
	}

	DUK_ASSERT(res != NULL);
}

void duk_hbuffer_reset(duk_hthread *thr, duk_hbuffer_dynamic *buf) {
	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(buf != NULL);
	DUK_ASSERT(DUK_HBUFFER_HAS_DYNAMIC(buf));

	duk_hbuffer_resize(thr, buf, 0, 0);
}

void duk_hbuffer_compact(duk_hthread *thr, duk_hbuffer_dynamic *buf) {
	size_t curr_size;

	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(buf != NULL);
	DUK_ASSERT(DUK_HBUFFER_HAS_DYNAMIC(buf));

	curr_size = DUK_HBUFFER_GET_SIZE(buf);
	duk_hbuffer_resize(thr, buf, curr_size, curr_size);
}

/*
 *  Inserts
 */

void duk_hbuffer_insert_bytes(duk_hthread *thr, duk_hbuffer_dynamic *buf, size_t offset, duk_u8 *data, size_t length) {
	char *p;

	/* XXX: allow inserts with offset > curr_size? i.e., insert zeroes automatically? */

	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(buf != NULL);
	DUK_ASSERT(DUK_HBUFFER_HAS_DYNAMIC(buf));
	DUK_ASSERT(offset >= 0);  /* unsigned, so always true */
	DUK_ASSERT(offset <= DUK_HBUFFER_GET_SIZE(buf));  /* equality is OK (= append) */
	DUK_ASSERT(data != NULL);
	DUK_ASSERT(length >= 0);  /* unsigned, so always true */

	if (length == 0) {
		return;
	}

	if (DUK_HBUFFER_DYNAMIC_GET_SPARE_SIZE(buf) < length) {
		duk_hbuffer_resize(thr,
		                   buf,
		                   DUK_HBUFFER_GET_SIZE(buf),
		                   add_spare(DUK_HBUFFER_GET_SIZE(buf) + length));
	}
	DUK_ASSERT(DUK_HBUFFER_DYNAMIC_GET_SPARE_SIZE(buf) >= length);

	p = (char *) DUK_HBUFFER_DYNAMIC_GET_CURR_DATA_PTR(buf);
	if (offset < DUK_HBUFFER_GET_SIZE(buf)) {
		/* not an append */

		DUK_ASSERT(DUK_HBUFFER_GET_SIZE(buf) - offset > 0);  /* not a zero byte memmove */
		memmove((void *) (p + offset + length),
		        (void *) (p + offset),
		        DUK_HBUFFER_GET_SIZE(buf) - offset);
	}

	memcpy((void *) (p + offset),
	       data,
	       length);

	buf->size += length;
}

void duk_hbuffer_insert_byte(duk_hthread *thr, duk_hbuffer_dynamic *buf, size_t offset, duk_u8 byte) {
	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(buf != NULL);
	DUK_ASSERT(DUK_HBUFFER_HAS_DYNAMIC(buf));

	duk_hbuffer_insert_bytes(thr, buf, offset, &byte, 1);
}

size_t duk_hbuffer_insert_cstring(duk_hthread *thr, duk_hbuffer_dynamic *buf, size_t offset, const char *str) {
	size_t len;

	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(buf != NULL);
	DUK_ASSERT(str != NULL);
	DUK_ASSERT(DUK_HBUFFER_HAS_DYNAMIC(buf));

	len = strlen(str);
	duk_hbuffer_insert_bytes(thr, buf, offset, (duk_u8 *) str, len);
	return len;
}

size_t duk_hbuffer_insert_hstring(duk_hthread *thr, duk_hbuffer_dynamic *buf, size_t offset, duk_hstring *str) {
	size_t len;

	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(buf != NULL);
	DUK_ASSERT(str != NULL);
	DUK_ASSERT(DUK_HBUFFER_HAS_DYNAMIC(buf));

	len = DUK_HSTRING_GET_BYTELEN(str);
	duk_hbuffer_insert_bytes(thr, buf, offset, (duk_u8 *) DUK_HSTRING_GET_DATA(str), len);
	return len;
}

size_t duk_hbuffer_insert_xutf8(duk_hthread *thr, duk_hbuffer_dynamic *buf, size_t offset, duk_u32 codepoint) {
	duk_u8 tmp[DUK_UNICODE_MAX_XUTF8_LENGTH];
	size_t len;

	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(buf != NULL);
	DUK_ASSERT(DUK_HBUFFER_HAS_DYNAMIC(buf));
	/* No range assertion for 'codepoint' */

	/* Intentionally no fast path: insertion is not that central */

	len = duk_unicode_encode_xutf8(codepoint, tmp);
	duk_hbuffer_insert_bytes(thr, buf, offset, tmp, len);
	return len;
}

/* Append a Unicode codepoint to the buffer in CESU-8 format, i.e., convert
 * non-BMP characters to surrogate pairs which are then "UTF-8" encoded.
 * If the codepoint is initially a surrogate, it is also encoded into CESU-8.
 * Codepoints above valid Unicode range (> U+10FFFF) are mangled.
 */

size_t duk_hbuffer_insert_cesu8(duk_hthread *thr, duk_hbuffer_dynamic *buf, size_t offset, duk_u32 codepoint) {
	duk_u8 tmp[DUK_UNICODE_MAX_CESU8_LENGTH];
	size_t len;

	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(buf != NULL);
	DUK_ASSERT(DUK_HBUFFER_HAS_DYNAMIC(buf));
	DUK_ASSERT(codepoint >= 0 && codepoint <= 0x10ffff);  /* if not in this range, results are garbage (but no crash) */

	/* Intentionally no fast path: insertion is not that central */

	len = duk_unicode_encode_cesu8(codepoint, tmp);
	duk_hbuffer_insert_bytes(thr, buf, offset, tmp, len);
	return len;
}

/*
 *  Appends
 *
 *  Note: an optimized duk_hbuffer_append_bytes() could be implemented, but
 *  it is more compact to use duk_hbuffer_insert_bytes() instead.  The
 *  important fast paths bypass these functions. anyway.
 */

void duk_hbuffer_append_bytes(duk_hthread *thr, duk_hbuffer_dynamic *buf, duk_u8 *data, size_t length) {
	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(buf != NULL);
	DUK_ASSERT(DUK_HBUFFER_HAS_DYNAMIC(buf));
	DUK_ASSERT(data != NULL);

	duk_hbuffer_insert_bytes(thr, buf, DUK_HBUFFER_GET_SIZE(buf), data, length);
}

void duk_hbuffer_append_byte(duk_hthread *thr, duk_hbuffer_dynamic *buf, duk_u8 byte) {
	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(buf != NULL);
	DUK_ASSERT(DUK_HBUFFER_HAS_DYNAMIC(buf));

	duk_hbuffer_insert_bytes(thr, buf, DUK_HBUFFER_GET_SIZE(buf), &byte, 1);
}

size_t duk_hbuffer_append_cstring(duk_hthread *thr, duk_hbuffer_dynamic *buf, const char *str) {
	size_t len;

	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(buf != NULL);
	DUK_ASSERT(str != NULL);
	DUK_ASSERT(DUK_HBUFFER_HAS_DYNAMIC(buf));

	len = strlen(str);
	duk_hbuffer_insert_bytes(thr, buf, DUK_HBUFFER_GET_SIZE(buf), (duk_u8 *) str, len);
	return len;
}

size_t duk_hbuffer_append_hstring(duk_hthread *thr, duk_hbuffer_dynamic *buf, duk_hstring *str) {
	size_t len;

	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(buf != NULL);
	DUK_ASSERT(str != NULL);
	DUK_ASSERT(DUK_HBUFFER_HAS_DYNAMIC(buf));

	len = DUK_HSTRING_GET_BYTELEN(str);
	duk_hbuffer_insert_bytes(thr, buf, DUK_HBUFFER_GET_SIZE(buf), (duk_u8 *) DUK_HSTRING_GET_DATA(str), len);
	return len;
}

/* Append a Unicode codepoint to the buffer in extended UTF-8 format, i.e.
 * allow codepoints above standard Unicode range (> U+10FFFF) up to seven
 * byte encoding (36 bits, but argument type is 32 bits).  In particular,
 * allows encoding of all unsigned 32-bit integers.  If the codepoint is
 * initially a surrogate, it is encoded without checking (and will become,
 * effectively, CESU-8 encoded).
 */

size_t duk_hbuffer_append_xutf8(duk_hthread *thr, duk_hbuffer_dynamic *buf, duk_u32 codepoint) {
	duk_u8 tmp[DUK_UNICODE_MAX_XUTF8_LENGTH];
	size_t len;

	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(buf != NULL);
	DUK_ASSERT(DUK_HBUFFER_HAS_DYNAMIC(buf));
	/* No range assertion for 'codepoint' */

	if (codepoint < 0x80 && DUK_HBUFFER_DYNAMIC_GET_SPARE_SIZE(buf) > 0) {
		/* fast path: ASCII and there is spare */
		duk_u8 *p = ((duk_u8 *) DUK_HBUFFER_DYNAMIC_GET_CURR_DATA_PTR(buf)) + DUK_HBUFFER_GET_SIZE(buf);
		*p = (duk_u8) codepoint;
		buf->size += 1;
		return 1;
	}

	len = duk_unicode_encode_xutf8(codepoint, tmp);
	duk_hbuffer_insert_bytes(thr, buf, DUK_HBUFFER_GET_SIZE(buf), tmp, len);
	return len;
}

/* Append a Unicode codepoint to the buffer in CESU-8 format, i.e., convert
 * non-BMP characters to surrogate pairs which are then "UTF-8" encoded.
 * If the codepoint is initially a surrogate, it is also encoded into CESU-8.
 * Codepoints above valid Unicode range (> U+10FFFF) are mangled.
 */

size_t duk_hbuffer_append_cesu8(duk_hthread *thr, duk_hbuffer_dynamic *buf, duk_u32 codepoint) {
	duk_u8 tmp[DUK_UNICODE_MAX_CESU8_LENGTH];
	size_t len;

	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(buf != NULL);
	DUK_ASSERT(DUK_HBUFFER_HAS_DYNAMIC(buf));
	DUK_ASSERT(codepoint >= 0 && codepoint <= 0x10ffff);  /* if not in this range, results are garbage (but no crash) */

	if (codepoint < 0x80 && DUK_HBUFFER_DYNAMIC_GET_SPARE_SIZE(buf) > 0) {
		/* fast path: ASCII and there is spare */
		duk_u8 *p = ((duk_u8 *) DUK_HBUFFER_DYNAMIC_GET_CURR_DATA_PTR(buf)) + DUK_HBUFFER_GET_SIZE(buf);
		*p = (duk_u8) codepoint;
		buf->size += 1;
		return 1;
	}

	len = duk_unicode_encode_cesu8(codepoint, tmp);
	duk_hbuffer_insert_bytes(thr, buf, DUK_HBUFFER_GET_SIZE(buf), tmp, len);
	return len;
}

/* Append an duk_u32 in native byte order.  This is used to emit bytecode
 * instructions.
 */

void duk_hbuffer_append_native_u32(duk_hthread *thr, duk_hbuffer_dynamic *buf, duk_u32 val) {
	/* FIXME: relies on duk_u32 being exactly right size */
	duk_hbuffer_insert_bytes(thr,
	                         buf,
	                         DUK_HBUFFER_GET_SIZE(buf),
	                         (duk_u8 *) &val,
	                         sizeof(duk_u32));
}

/*
 *  In-buffer "slices"
 *
 *  Slices are identified with an offset+length pair, referring to the current
 *  buffer data.  A caller cannot otherwise reliably refer to existing data,
 *  because the buffer may be reallocated before a data pointer is referenced.
 */

void duk_hbuffer_remove_slice(duk_hthread *thr, duk_hbuffer_dynamic *buf, size_t offset, size_t length) {
	char *p;
	size_t end_offset;

	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(buf != NULL);
	DUK_ASSERT(DUK_HBUFFER_HAS_DYNAMIC(buf));
	DUK_ASSERT(offset >= 0);                                       /* always true */
	DUK_ASSERT(offset <= DUK_HBUFFER_GET_SIZE(buf));               /* allow equality */
	DUK_ASSERT(length >= 0);                                       /* always true */
	DUK_ASSERT(offset + length <= DUK_HBUFFER_GET_SIZE(buf));      /* allow equality */

	if (length == 0) {
		return;
	}

	p = (char *) DUK_HBUFFER_DYNAMIC_GET_CURR_DATA_PTR(buf);

	end_offset = offset + length;

	if (end_offset < DUK_HBUFFER_GET_SIZE(buf)) {
		/* not strictly from end of buffer; need to shuffle data */
		memmove(p + offset,
		        p + end_offset,
	                DUK_HBUFFER_GET_SIZE(buf) - end_offset);  /* always > 0 */
	}

	memset(p + DUK_HBUFFER_GET_SIZE(buf) - length,
	       0,
	       length);  /* always > 0 */

	buf->size -= length;

	/* Note: no shrink check, intentional */
}

void duk_hbuffer_insert_slice(duk_hthread *thr, duk_hbuffer_dynamic *buf, size_t dst_offset, size_t src_offset, size_t length) {
	char *p;
	size_t src_end_offset;  /* source end (exclusive) in initial buffer */
	size_t len;

	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(buf != NULL);
	DUK_ASSERT(DUK_HBUFFER_HAS_DYNAMIC(buf));
	DUK_ASSERT(dst_offset >= 0);                                   /* always true */
	DUK_ASSERT(dst_offset <= DUK_HBUFFER_GET_SIZE(buf));           /* allow equality */
	DUK_ASSERT(src_offset >= 0);                                   /* always true */
	DUK_ASSERT(src_offset <= DUK_HBUFFER_GET_SIZE(buf));           /* allow equality */
	DUK_ASSERT(length >= 0);                                       /* always true */
	DUK_ASSERT(src_offset + length <= DUK_HBUFFER_GET_SIZE(buf));  /* allow equality */

	if (length == 0) {
		return;
	}

	if (DUK_HBUFFER_DYNAMIC_GET_SPARE_SIZE(buf) < length) {
		duk_hbuffer_resize(thr,
		                   buf,
		                   DUK_HBUFFER_GET_SIZE(buf),
		                   add_spare(DUK_HBUFFER_GET_SIZE(buf) + length));
	}
	DUK_ASSERT(DUK_HBUFFER_DYNAMIC_GET_SPARE_SIZE(buf) >= length);

	p = (char *) DUK_HBUFFER_DYNAMIC_GET_CURR_DATA_PTR(buf);

	/*
	 *  src_offset and dst_offset refer to the state of the buffer
	 *  before any changes are made.  This must be taken into account
	 *  when moving data around; in particular, the source data may
	 *  "straddle" the dst_offset, so the insert may need to be handled
	 *  in two pieces.
	 */

	src_end_offset = src_offset + length;

	/* create a hole for the insert */
	len = DUK_HBUFFER_GET_SIZE(buf) - dst_offset;
	if (len > 0) {
		memmove(p + dst_offset + length,
		        p + dst_offset,
		        len);
	}

	if (src_offset < dst_offset) {
		if (src_end_offset <= dst_offset) {
			/* entire source is before 'dst_offset' */
			memcpy(p + dst_offset,
			       p + src_offset,
			       length);
		} else {
			/* part of the source is before 'dst_offset'; straddles */
			len = dst_offset - src_offset;
			DUK_ASSERT(len >= 1 && len < length);
			DUK_ASSERT(length - len >= 1);
			memcpy(p + dst_offset,
			       p + src_offset,
			       len);
			memcpy(p + dst_offset + len,
			       p + src_offset + length + len,  /* take above memmove() into account */
			       length - len);
		}
	} else {
		/* entire source is after 'dst_offset' */
		memcpy(p + dst_offset,
		       p + src_offset + length,  /* take above memmove() into account */
		       length);
	}

	buf->size += length;
}

void duk_hbuffer_append_slice(duk_hthread *thr, duk_hbuffer_dynamic *buf, size_t src_offset, size_t length) {
	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(buf != NULL);
	DUK_ASSERT(DUK_HBUFFER_HAS_DYNAMIC(buf));
	DUK_ASSERT(src_offset >= 0);                                   /* always true */
	DUK_ASSERT(src_offset <= DUK_HBUFFER_GET_SIZE(buf));           /* allow equality */
	DUK_ASSERT(length >= 0);                                       /* always true */
	DUK_ASSERT(src_offset + length <= DUK_HBUFFER_GET_SIZE(buf));  /* allow equality */

	duk_hbuffer_insert_slice(thr,
	                         buf,
	                         DUK_HBUFFER_GET_SIZE(buf),
	                         src_offset,
	                         length);
}

#line 1 "duk_heap_alloc.c"
/*
 *  duk_heap allocation and freeing.
 */

/* include removed: duk_internal.h */

/* constants for built-in string data depacking */
#define  BITPACK_LETTER_LIMIT  26
#define  BITPACK_UNDERSCORE    26
#define  BITPACK_FF            27
#define  BITPACK_SWITCH1       29
#define  BITPACK_SWITCH        30
#define  BITPACK_SEVENBIT      31

/*
 *  Free a heap object.
 *
 *  Free heap object and its internal (non-heap) pointers.  Assumes that
 *  caller has removed the object from heap allocated list or the string
 *  intern table, and any weak references (which strings may have) have
 *  been already dealt with.
 */

static void free_hobject_inner(duk_heap *heap, duk_hobject *h) {
	DUK_ASSERT(heap != NULL);
	DUK_ASSERT(h != NULL);

	DUK_FREE(heap, h->p);

	if (DUK_HOBJECT_IS_COMPILEDFUNCTION(h)) {
		duk_hcompiledfunction *f = (duk_hcompiledfunction *) h;
		DUK_UNREF(f);
		/* Currently nothing to free; 'data' is a heap object */
	} else if (DUK_HOBJECT_IS_NATIVEFUNCTION(h)) {
		duk_hnativefunction *f = (duk_hnativefunction *) h;
		DUK_UNREF(f);
		/* Currently nothing to free */
	} else if (DUK_HOBJECT_IS_THREAD(h)) {
		duk_hthread *t = (duk_hthread *) h;
		DUK_FREE(heap, t->valstack);
		DUK_FREE(heap, t->callstack);
		DUK_FREE(heap, t->catchstack);
		/* don't free h->resumer, because it exists in the heap */
	}
}

static void free_hbuffer_inner(duk_heap *heap, duk_hbuffer *h) {
	DUK_ASSERT(heap != NULL);
	DUK_ASSERT(h != NULL);

	if (DUK_HBUFFER_HAS_DYNAMIC(h)) {
		duk_hbuffer_dynamic *g = (duk_hbuffer_dynamic *) h;
		DUK_DDDPRINT("free dynamic buffer %p", g->curr_alloc);
		DUK_FREE(heap, g->curr_alloc);
	}
}

void duk_heap_free_heaphdr_raw(duk_heap *heap, duk_heaphdr *hdr) {
	DUK_ASSERT(heap);
	DUK_ASSERT(hdr);

	DUK_DDDPRINT("free heaphdr %p, htype %d", (void *) hdr, (int) DUK_HEAPHDR_GET_TYPE(hdr));

	switch (DUK_HEAPHDR_GET_TYPE(hdr)) {
	case DUK_HTYPE_STRING:
		/* no inner refs to free */
		break;
	case DUK_HTYPE_OBJECT:
		free_hobject_inner(heap, (duk_hobject *) hdr);
		break;
	case DUK_HTYPE_BUFFER:
		free_hbuffer_inner(heap, (duk_hbuffer *) hdr);
		break;
	default:
		DUK_NEVER_HERE();
	}

	DUK_FREE(heap, hdr);
}

/*
 *  Free the heap.
 *
 *  Frees heap-related non-heap-tracked allocations such as the
 *  string intern table; then frees the heap allocated objects;
 *  and finally frees the heap structure itself.  Reference counts
 *  and GC markers are ignored (and not updated) in this process,
 *  and finalizers won't be called.
 *
 *  The heap pointer and heap object pointers must not be used
 *  after this call.
 */

static void free_allocated(duk_heap *heap) {
	duk_heaphdr *curr;
	duk_heaphdr *next;

	curr = heap->heap_allocated;
	while (curr) {
		/* We don't log or warn about freeing zero refcount objects
		 * because they may happen with finalizer processing.
		 */

		DUK_DDDPRINT("FINALFREE (allocated): %!iO", curr);
		next = DUK_HEAPHDR_GET_NEXT(curr);
		duk_heap_free_heaphdr_raw(heap, curr);
		curr = next;
	}
}

#ifdef DUK_USE_REFERENCE_COUNTING
static void free_refzero_list(duk_heap *heap) {
	duk_heaphdr *curr;
	duk_heaphdr *next;

	curr = heap->refzero_list;
	while (curr) {
		DUK_DDDPRINT("FINALFREE (refzero_list): %!iO", curr);
		next = DUK_HEAPHDR_GET_NEXT(curr);
		duk_heap_free_heaphdr_raw(heap, curr);
		curr = next;
	}
}
#endif

#ifdef DUK_USE_MARK_AND_SWEEP
static void free_markandsweep_finalize_list(duk_heap *heap) {
	duk_heaphdr *curr;
	duk_heaphdr *next;

	curr = heap->finalize_list;
	while (curr) {
		DUK_DDDPRINT("FINALFREE (finalize_list): %!iO", curr);
		next = DUK_HEAPHDR_GET_NEXT(curr);
		duk_heap_free_heaphdr_raw(heap, curr);
		curr = next;
	}
}
#endif

static void free_stringtable(duk_heap *heap) {
	int i;

	/* strings are only tracked by stringtable */
	if (heap->st) {
		for (i = 0; i < heap->st_size; i++) {
			duk_hstring *e = heap->st[i];
			if (e == DUK_STRTAB_DELETED_MARKER(heap)) {
				continue;
			}

			/* strings have no inner allocations so free directly */
			DUK_DDDPRINT("FINALFREE (string): %!iO", e);
			DUK_FREE(heap, e);
#if 0  /* not strictly necessary */
			heap->st[i] = NULL;
#endif
		}
		DUK_FREE(heap, heap->st);
#if 0  /* not strictly necessary */
		heap->st = NULL;
#endif
	}
}

void duk_heap_free(duk_heap *heap) {
	DUK_DPRINT("free heap: %p", heap);

	/* Note: heap->heap_thread, heap->curr_thread, heap->heap_object are
	 * on the heap allocated list.
	 */

	DUK_DPRINT("freeing heap objects of heap: %p", heap);
	free_allocated(heap);

#ifdef DUK_USE_REFERENCE_COUNTING
	DUK_DPRINT("freeing refzero list of heap: %p", heap);
	free_refzero_list(heap);
#endif

#ifdef DUK_USE_MARK_AND_SWEEP
	DUK_DPRINT("freeing mark-and-sweep finalize list of heap: %p", heap);
	free_markandsweep_finalize_list(heap);
#endif

	DUK_DPRINT("freeing string table of heap: %p", heap);
	free_stringtable(heap);

	DUK_DPRINT("freeing heap structure: %p", heap);
	heap->free_func(heap->alloc_udata, heap);
}

/*
 *  Allocate a heap.
 *
 *  String table is initialized with built-in strings from genstrings.py.
 */

/* intern built-in strings from precooked data (genstrings.py) */
static int init_heap_strings(duk_heap *heap) {
	duk_bitdecoder_ctx bd_ctx;
	duk_bitdecoder_ctx *bd = &bd_ctx;  /* convenience */
	int i, j;

	memset(&bd_ctx, 0, sizeof(bd_ctx));
	bd->data = (duk_u8 *) duk_strings_data;
	bd->length = DUK_STRDATA_DATA_LENGTH;

	for (i = 0; i < DUK_HEAP_NUM_STRINGS; i++) {
		duk_u8 tmp[DUK_STRDATA_MAX_STRLEN];
		duk_hstring *h;
		int len;
		int mode;
		int t;

		len = duk_bd_decode(bd, 5);
		mode = 32;		/* 0 = uppercase, 32 = lowercase (= 'a' - 'A') */
		for (j = 0; j < len; j++) {
			t = duk_bd_decode(bd, 5);
			if (t < BITPACK_LETTER_LIMIT) {
				t = t + 'A' + mode;
			} else if (t == BITPACK_UNDERSCORE) {
				t = (int) '_';
			} else if (t == BITPACK_FF) {
				/* Internal keys are prefixed with 0xFF in the stringtable
				 * (which makes them invalid UTF-8 on purpose).
				 */
				t = (int) 0xff;
			} else if (t == BITPACK_SWITCH1) {
				t = duk_bd_decode(bd, 5);
				DUK_ASSERT(t >= 0 && t <= 25);
				t = t + 'A' + (mode ^ 32);
			} else if (t == BITPACK_SWITCH) {
				mode = mode ^ 32;
				t = duk_bd_decode(bd, 5);
				DUK_ASSERT(t >= 0 && t <= 25);
				t = t + 'A' + mode;
			} else if (t == BITPACK_SEVENBIT) {
				t = duk_bd_decode(bd, 7);
			}
			tmp[j] = (duk_u8) t;
		}

		DUK_DDDPRINT("intern built-in string %d", i);
		h = duk_heap_string_intern(heap, tmp, len);
		if (!h) {
			goto error;
		}

		/* special flags */

		if (len > 0 && tmp[0] == 0xff) {
			DUK_HSTRING_SET_INTERNAL(h);
		}
		if (i == DUK_STRIDX_EVAL || i == DUK_STRIDX_LC_ARGUMENTS) {
			DUK_HSTRING_SET_EVAL_OR_ARGUMENTS(h);
		}
		if (i >= DUK_STRIDX_START_RESERVED && i < DUK_STRIDX_END_RESERVED) {
			DUK_HSTRING_SET_RESERVED_WORD(h);
			if (i >= DUK_STRIDX_START_STRICT_RESERVED) {
				DUK_HSTRING_SET_STRICT_RESERVED_WORD(h);
			}
		}

		DUK_DDDPRINT("interned: %!O", h);

		/* The incref macro takes a thread pointer but doesn't use it
		 * right now.
		 */
		DUK_HSTRING_INCREF(_never_referenced_, h);

		heap->strs[i] = h;
	}

	return 1;

 error:
	return 0;
}

static int init_heap_thread(duk_heap *heap) {
	duk_hthread *thr;
	
	DUK_DDPRINT("heap init: alloc heap thread");
	thr = duk_hthread_alloc(heap,
	                        DUK_HOBJECT_FLAG_EXTENSIBLE |
	                        DUK_HOBJECT_FLAG_THREAD |
	                        DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_OBJECT));
	if (!thr) {
		DUK_DPRINT("failed to alloc heap_thread");
		return 0;
	}
	thr->state = DUK_HTHREAD_STATE_INACTIVE;
	thr->strs = heap->strs;

	heap->heap_thread = thr;
	DUK_HTHREAD_INCREF(thr, thr);  /* Note: first argument not really used */

	/* 'thr' is now reachable */

	if (!duk_hthread_init_stacks(heap, thr)) {
		return 0;
	}

	/* FIXME: this may now fail, and is not handled correctly */
	duk_hthread_create_builtin_objects(thr);

	return 1;
}

duk_heap *duk_heap_alloc(duk_alloc_function alloc_func,
                         duk_realloc_function realloc_func,
                         duk_free_function free_func,
                         void *alloc_udata,
                         duk_fatal_function fatal_func) {
	duk_heap *res = NULL;

	DUK_DPRINT("allocate heap");

	/* use a raw call, all macros expect the heap to be initialized */
	res = alloc_func(alloc_udata, sizeof(duk_heap));
	if (!res) {
		goto error;
	}

	/* zero everything */
	memset(res, 0, sizeof(*res));

	/* explicit NULL inits */
#ifdef DUK_USE_EXPLICIT_NULL_INIT
	res->alloc_udata = NULL;
	res->heap_allocated = NULL;
#ifdef DUK_USE_REFERENCE_COUNTING
	res->refzero_list = NULL;
	res->refzero_list_tail = NULL;
#endif
#ifdef DUK_USE_MARK_AND_SWEEP
	res->finalize_list = NULL;
#endif
	res->heap_thread = NULL;
	res->curr_thread = NULL;
	res->heap_object = NULL;
	res->st = NULL;
	{
		int i;
	        for (i = 0; i < DUK_HEAP_NUM_STRINGS; i++) {
        	        res->strs[i] = NULL;
	        }
	}
#endif

	/* initialize the structure, roughly in order */
	res->alloc_func = alloc_func;
	res->realloc_func = realloc_func;
	res->free_func = free_func;
	res->alloc_udata = alloc_udata;
	res->fatal_func = fatal_func;

#ifdef DUK_USE_MARK_AND_SWEEP
	res->mark_and_sweep_recursion_limit = DUK_HEAP_DEFAULT_MARK_AND_SWEEP_RECURSION_LIMIT;
	res->mark_and_sweep_trigger_limit = DUK_HEAP_DEFAULT_MARK_AND_SWEEP_TRIGGER_LIMIT;
	/* res->mark_and_sweep_trigger_counter == 0 -> now causes immediate GC; which is OK */
#endif

	res->call_recursion_depth = 0;
	res->call_recursion_limit = DUK_HEAP_DEFAULT_CALL_RECURSION_LIMIT;

	/* FIXME: use the pointer as a seed for now: mix in time at least */

	/* cast through C99 intptr_t to avoid GCC warning:
	 *
	 *   warning: cast from pointer to integer of different size [-Wpointer-to-int-cast]
	 */
	res->hash_seed = (duk_u32) (intptr_t) res;
	res->rnd_state = (duk_u32) (intptr_t) res;

#ifdef DUK_USE_EXPLICIT_NULL_INIT
	res->lj.jmpbuf_ptr = NULL;
#endif
	DUK_ASSERT(res->lj.type == DUK_LJ_TYPE_UNKNOWN);  /* zero */

	DUK_TVAL_SET_UNDEFINED_UNUSED(&res->lj.value1);
	DUK_TVAL_SET_UNDEFINED_UNUSED(&res->lj.value2);

#if (DUK_STRTAB_INITIAL_SIZE < DUK_UTIL_MIN_HASH_PRIME)
#error initial heap stringtable size is defined incorrectly
#endif

	res->st = (duk_hstring **) alloc_func(alloc_udata, sizeof(duk_hstring *) * DUK_STRTAB_INITIAL_SIZE);
	if (!res->st) {
		goto error;
	}
	res->st_size = DUK_STRTAB_INITIAL_SIZE;
#ifdef DUK_USE_EXPLICIT_NULL_INIT
	{
		int i;
	        for (i = 0; i < res->st_size; i++) {
        	        res->st[i] = NULL;
	        }
	}
#else
	memset(res->st, 0, sizeof(duk_hstring *) * DUK_STRTAB_INITIAL_SIZE);
#endif

	/* strcache init */
#ifdef DUK_USE_EXPLICIT_NULL_INIT
	{
		int i;
		for (i = 0; i < DUK_HEAP_STRCACHE_SIZE; i++) {
			res->strcache[i].h = NULL;
		}
	}
#endif

	/* FIXME: error handling is incomplete.  It would be cleanest if
	 * there was a setjmp catchpoint, so that all init code could
	 * freely throw errors.  If that were the case, the return code
	 * passing here could be removed.
	 */

	/* built-in strings */
	DUK_DDPRINT("HEAP: INIT STRINGS");
	if (!init_heap_strings(res)) {
		goto error;
	}

	/* heap thread */
	DUK_DDPRINT("HEAP: INIT HEAP THREAD");
	if (!init_heap_thread(res)) {
		goto error;
	}

	/* heap object */
	DUK_DDPRINT("HEAP: INIT HEAP OBJECT");
	DUK_ASSERT(res->heap_thread != NULL);
	res->heap_object = duk_hobject_alloc(res, DUK_HOBJECT_FLAG_EXTENSIBLE |
	                                          DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_OBJECT));
	if (!res->heap_object) {
		goto error;
	}
	DUK_HOBJECT_INCREF(res->heap_thread, res->heap_object);

	DUK_DPRINT("allocated heap: %p", res);
	return res;

 error:
	DUK_DPRINT("heap allocation failed");

	if (res) {
		/* assumes that allocated pointers and alloc funcs are valid
		 * if res exists
		 */
		DUK_ASSERT(res->alloc_func != NULL);
		DUK_ASSERT(res->realloc_func != NULL);
		DUK_ASSERT(res->free_func != NULL);
		duk_heap_free(res);
	}
	return NULL;
}

#line 1 "duk_heap_hashstring.c"
/*
 *  String hash computation (interning).
 */

/* include removed: duk_internal.h */

/* constants for duk_hashstring() */
#define  STRING_HASH_SHORTSTRING   4096
#define  STRING_HASH_MEDIUMSTRING  (256 * 1024)
#define  STRING_HASH_BLOCKSIZE     256

duk_u32 duk_heap_hashstring(duk_heap *heap, duk_u8 *str, duk_u32 len) {
	/*
	 *  Sampling long strings by byte skipping (like Lua does) is potentially
	 *  a cache problem.  Here we do 'block skipping' instead for long strings:
	 *  hash an initial part, and then sample the rest of the string with
	 *  reasonably sized chunks.
	 *
	 *  Skip should depend on length and bound the total time to roughly
	 *  logarithmic.
	 *
	 *  With current values:
	 *
	 *    1M string => 256 * 241 = 61696 bytes (0.06M) of hashing
	 *    1G string => 256 * 16321 = 4178176 bytes (3.98M) of hashing
	 *
	 *  After an initial part has been hashed, an offset is applied before
	 *  starting the sampling.  The initial offset is computed from the
	 *  hash of the initial part of the string.  The idea is to avoid the
	 *  case that all long strings have certain offset ranges that are never
	 *  sampled.
	 */
	
	/* note: mixing len into seed improves hashing when skipping */
	duk_u32 str_seed = heap->hash_seed ^ len;

	if (len <= STRING_HASH_SHORTSTRING) {
		return duk_util_hashbytes(str, len, str_seed);
	} else {
		duk_u32 hash;
		duk_u32 off;
		duk_u32 skip;

		if (len <= STRING_HASH_MEDIUMSTRING) {
			skip = 16 * STRING_HASH_BLOCKSIZE + STRING_HASH_BLOCKSIZE;
		} else {
			skip = 256 * STRING_HASH_BLOCKSIZE + STRING_HASH_BLOCKSIZE;
		}

		hash = duk_util_hashbytes(str, STRING_HASH_SHORTSTRING, str_seed);
		off = STRING_HASH_SHORTSTRING + (skip * (hash % 256)) / 256;

		/* FIXME: inefficient loop */
		while (off < len) {
			duk_u32 left = len - off;
			duk_u32 now = (left > STRING_HASH_BLOCKSIZE ? STRING_HASH_BLOCKSIZE : left);
			hash ^= duk_util_hashbytes(str + off, now, str_seed);
			off += skip;
		}

		return hash;
	}
}

#line 1 "duk_heap_markandsweep.c"
/*
 *  Mark-and-sweep garbage collection.
 */

/* include removed: duk_internal.h */

#ifdef DUK_USE_MARK_AND_SWEEP

static void mark_heaphdr(duk_heap *heap, duk_heaphdr *h);
static void mark_tval(duk_heap *heap, duk_tval *tv);

/*
 *  Misc
 */

/* Select a thread for mark-and-sweep use.  This needs to change
 * later.
 */
static duk_hthread *get_temp_hthread(duk_heap *heap) {
	if (heap->curr_thread) {
		return heap->curr_thread;
	}
	return heap->heap_thread;  /* may be NULL, too */
}

/*
 *  Marking functions for heap types: mark children recursively
 */

static void mark_hstring(duk_heap *heap, duk_hstring *h) {
	DUK_DDDPRINT("mark_hstring: %p", (void *) h);

	DUK_ASSERT(h);

	/* nothing to process */
}

static void mark_hobject(duk_heap *heap, duk_hobject *h) {
	int i;

	DUK_DDDPRINT("mark_hobject: %p", (void *) h);

	DUK_ASSERT(h);

	/* XXX: use advancing pointers instead of index macros -> faster and smaller? */

	for (i = 0; i < h->e_used; i++) {
		duk_hstring *key = DUK_HOBJECT_E_GET_KEY(h, i);
		if (!key) {
			continue;
		}
		mark_heaphdr(heap, (duk_heaphdr *) key);
		if (DUK_HOBJECT_E_SLOT_IS_ACCESSOR(h, i)) {
			mark_heaphdr(heap, (duk_heaphdr *) DUK_HOBJECT_E_GET_VALUE_PTR(h, i)->a.get);
			mark_heaphdr(heap, (duk_heaphdr *) DUK_HOBJECT_E_GET_VALUE_PTR(h, i)->a.set);
		} else {
			mark_tval(heap, &DUK_HOBJECT_E_GET_VALUE_PTR(h, i)->v);
		}
	}

	for (i = 0; i < h->a_size; i++) {
		mark_tval(heap, DUK_HOBJECT_A_GET_VALUE_PTR(h, i));
	}

	/* hash part is a 'weak reference' and does not contribute */

	mark_heaphdr(heap, (duk_heaphdr *) h->prototype);

	if (DUK_HOBJECT_IS_COMPILEDFUNCTION(h)) {
		duk_hcompiledfunction *f = (duk_hcompiledfunction *) h;
		duk_tval *tv, *tv_end;
		duk_hobject **funcs, **funcs_end;

		/* 'data' is reachable through every compiled function which
		 * contains a reference.
		 */

		mark_heaphdr(heap, (duk_heaphdr *) f->data);

		tv = DUK_HCOMPILEDFUNCTION_GET_CONSTS_BASE(f);
		tv_end = DUK_HCOMPILEDFUNCTION_GET_CONSTS_END(f);
		while (tv < tv_end) {
			mark_tval(heap, tv);
			tv++;
		}

		funcs = DUK_HCOMPILEDFUNCTION_GET_FUNCS_BASE(f);
		funcs_end = DUK_HCOMPILEDFUNCTION_GET_FUNCS_END(f);
		while (funcs < funcs_end) {
			mark_heaphdr(heap, (duk_heaphdr *) *funcs);
			funcs++;
		}
	} else if (DUK_HOBJECT_IS_NATIVEFUNCTION(h)) {
		duk_hnativefunction *f = (duk_hnativefunction *) h;
		DUK_UNREF(f);
		/* nothing to mark */
	} else if (DUK_HOBJECT_IS_THREAD(h)) {
		duk_hthread *t = (duk_hthread *) h;
		duk_tval *tv;

		tv = t->valstack;
		while (tv < t->valstack_end) {
			mark_tval(heap, tv);
			tv++;
		}

		for (i = 0; i < t->callstack_top; i++) {
			duk_activation *act = &t->callstack[i];
			mark_heaphdr(heap, (duk_heaphdr *) act->func);
			mark_heaphdr(heap, (duk_heaphdr *) act->var_env);
			mark_heaphdr(heap, (duk_heaphdr *) act->lex_env);
		}

#if 0  /* nothing now */
		for (i = 0; i < t->catchstack_top; i++) {
			duk_catcher *cat = &t->catchstack[i];
		}
#endif

		mark_heaphdr(heap, (duk_heaphdr *) t->resumer);

		for (i = 0; i < DUK_NUM_BUILTINS; i++) {
			mark_heaphdr(heap, (duk_heaphdr *) t->builtins[i]);
		}
	}
}

/* recursion tracking happens here only */
static void mark_heaphdr(duk_heap *heap, duk_heaphdr *h) {
	DUK_DDDPRINT("mark_heaphdr %p, type %d",
	             (void *) h,
	             h ? DUK_HEAPHDR_GET_TYPE(h) : -1);
	if (!h) {
		return;
	}

	if (DUK_HEAPHDR_HAS_REACHABLE(h)) {
		DUK_DDDPRINT("already marked reachable, skip");
		return;
	}
	DUK_HEAPHDR_SET_REACHABLE(h);

	if (heap->mark_and_sweep_recursion_depth >= heap->mark_and_sweep_recursion_limit) {
		/* log this with a normal debug level because this should be relatively rare */
		DUK_DPRINT("mark-and-sweep recursion limit reached, marking as temproot: %p", (void *) h);
		DUK_HEAP_SET_MARKANDSWEEP_RECLIMIT_REACHED(heap);
		DUK_HEAPHDR_SET_TEMPROOT(h);
		return;
	}

	heap->mark_and_sweep_recursion_depth++;

	switch (DUK_HEAPHDR_GET_TYPE(h)) {
	case DUK_HTYPE_STRING:
		mark_hstring(heap, (duk_hstring *) h);
		break;
	case DUK_HTYPE_OBJECT:
		mark_hobject(heap, (duk_hobject *) h);
		break;
	case DUK_HTYPE_BUFFER:
		/* nothing to mark */
		break;
	default:
		DUK_DPRINT("attempt to mark heaphdr %p with invalid htype %d", (void *) h, (int) DUK_HEAPHDR_GET_TYPE(h));
		DUK_NEVER_HERE();
	}

	heap->mark_and_sweep_recursion_depth--;
}

static void mark_tval(duk_heap *heap, duk_tval *tv) {
	DUK_DDDPRINT("mark_tval %p", (void *) tv);
	if (!tv) {
		return;
	}
	if (DUK_TVAL_IS_HEAP_ALLOCATED(tv)) {
		mark_heaphdr(heap, DUK_TVAL_GET_HEAPHDR(tv)); 
	}
}

/*
 *  Mark the heap.
 */

static void mark_roots_heap(duk_heap *heap) {
	int i;

	DUK_DDPRINT("mark_roots_heap: %p", (void *) heap);

	mark_heaphdr(heap, (duk_heaphdr *) heap->heap_thread);
	mark_heaphdr(heap, (duk_heaphdr *) heap->heap_object);

	for (i = 0; i < DUK_HEAP_NUM_STRINGS; i++) {
		duk_hstring *h = heap->strs[i];
		mark_heaphdr(heap, (duk_heaphdr *) h);
	}

	/* heap->lj.errhandler: not marked, because a borrowed reference
	 * (actual value is required to be in an active part of some
	 * valstack)
	 */

	mark_tval(heap, &heap->lj.value1);
	mark_tval(heap, &heap->lj.value2);
}

/*
 *  Mark refzero_list objects.
 *
 *  Objects on the refzero_list have no inbound references.  They might have
 *  outbound references to objects that we might free, which would invalidate
 *  any references held by the refzero objects.  A refzero object might also
 *  be rescued by refcount finalization.  Refzero objects are treated as
 *  reachability roots to ensure they (or anything they point to) are not
 *  freed in mark-and-sweep.
 */

#ifdef DUK_USE_REFERENCE_COUNTING
static void mark_refzero_list(duk_heap *heap) {
	duk_heaphdr *hdr;

	DUK_DDPRINT("mark_refzero_list: %p", (void *) heap);

	hdr = heap->refzero_list;
	while (hdr) {
		mark_heaphdr(heap, hdr);
		hdr = DUK_HEAPHDR_GET_NEXT(hdr);
	}
}
#endif

/*
 *  Mark unreachable, finalizable objects.
 *
 *  Such objects will be moved aside and their finalizers run later.  They have
 *  to be treated as reachability roots for their properties etc to remain
 *  allocated.  This marking is only done for unreachable values which would
 *  be swept later (refzero_list is thus excluded).
 *
 *  Objects are first marked FINALIZABLE and only then marked as reachability
 *  roots; otherwise circular references might be handled inconsistently.
 */

static void mark_finalizable(duk_heap *heap) {
	duk_hthread *thr;
	duk_heaphdr *hdr;
	int count_finalizable = 0;

	DUK_DDPRINT("mark_finalizable: %p", (void *) heap);

	/* FIXME: placeholder */
	thr = get_temp_hthread(heap);
	DUK_ASSERT(thr != NULL);

	hdr = heap->heap_allocated;
	while (hdr) {
		if (!DUK_HEAPHDR_HAS_REACHABLE(hdr) &&
		    DUK_HEAPHDR_GET_TYPE(hdr) == DUK_HTYPE_OBJECT &&
		    !DUK_HEAPHDR_HAS_FINALIZED(hdr) &&
		    duk_hobject_hasprop_raw(thr, (duk_hobject *) hdr, DUK_HTHREAD_STRING_INT_FINALIZER(thr))) {

			/* heaphdr:
			 *  - is not reachable
			 *  - is an object
			 *  - is not a finalized object
			 *  - has a finalizer
			 */

			DUK_DDPRINT("unreachable heap object will be finalized -> mark as finalizable and treat as a reachability root: %p", hdr);
			DUK_HEAPHDR_SET_FINALIZABLE(hdr);
			count_finalizable ++;
		}

		hdr = DUK_HEAPHDR_GET_NEXT(hdr);
	}

	if (count_finalizable == 0) {
		return;
	}

	DUK_DDPRINT("marked %d heap objects as finalizable, now mark them reachable", count_finalizable);

	hdr = heap->heap_allocated;
	while (hdr) {
		if (DUK_HEAPHDR_HAS_FINALIZABLE(hdr)) {
			mark_heaphdr(heap, hdr);
		}

		hdr = DUK_HEAPHDR_GET_NEXT(hdr);
	}

	/* Caller will finish the marking process if we hit a recursion limit. */
}

/*
 *  Fallback marking handler if recursion limit is reached.
 *
 *  Iterates 'temproots' until recursion limit is no longer hit.  Note
 *  that temproots may reside either in heap allocated list or the
 *  refzero work list.  This is a slow scan, but guarantees that we
 *  finish with a bounded C stack.
 *
 *  Note that nodes may have been marked as temproots before this
 *  scan begun, OR they may have been marked during the scan (as
 *  we process nodes recursively also during the scan).  This is
 *  intended behavior.
 */

#ifdef DUK_USE_DEBUG
static void handle_temproot(duk_heap *heap, duk_heaphdr *hdr, int *count) {
#else
static void handle_temproot(duk_heap *heap, duk_heaphdr *hdr) {
#endif
	if (!DUK_HEAPHDR_HAS_TEMPROOT(hdr)) {
		DUK_DDDPRINT("not a temp root: %p", (void *) hdr);
		return;
	}

	DUK_DDDPRINT("found a temp root: %p", (void *) hdr);
	DUK_HEAPHDR_CLEAR_TEMPROOT(hdr);
	DUK_HEAPHDR_CLEAR_REACHABLE(hdr);  /* done so that mark_heaphdr() works correctly */
	mark_heaphdr(heap, hdr);

#ifdef DUK_USE_DEBUG
	(*count)++;
#endif
}

static void mark_temproots_by_heap_scan(duk_heap *heap) {
	duk_heaphdr *hdr;
#ifdef DUK_USE_DEBUG
	int count;
#endif

	DUK_DDPRINT("mark_temproots_by_heap_scan: %p", (void *) heap);

	while (DUK_HEAP_HAS_MARKANDSWEEP_RECLIMIT_REACHED(heap)) {
		DUK_DDPRINT("recursion limit reached, doing heap scan to continue from temproots");

#ifdef DUK_USE_DEBUG
		count = 0;
#endif
		DUK_HEAP_CLEAR_MARKANDSWEEP_RECLIMIT_REACHED(heap);

		hdr = heap->heap_allocated;
		while (hdr) {
#ifdef DUK_USE_DEBUG
			handle_temproot(heap, hdr, &count);
#else
			handle_temproot(heap, hdr);
#endif
			hdr = DUK_HEAPHDR_GET_NEXT(hdr);
		}

		/* must also check refzero_list */
#ifdef DUK_USE_REFERENCE_COUNTING
		hdr = heap->refzero_list;
		while (hdr) {
#ifdef DUK_USE_DEBUG
			handle_temproot(heap, hdr, &count);
#else
			handle_temproot(heap, hdr);
#endif
			hdr = DUK_HEAPHDR_GET_NEXT(hdr);
		}
#endif  /* DUK_USE_REFERENCE_COUNTING */

#ifdef DUK_USE_DEBUG
		DUK_DDPRINT("temproot mark heap scan processed %d temp roots", count);
#endif
	}
}

/*
 *  Finalize refcounts for heap elements just about to be freed.
 *  This must be done for all objects before freeing to avoid any
 *  stale pointer dereferences.
 *
 *  Note that this must deduce the set of objects to be freed
 *  identically to sweep_heap().
 */

#ifdef DUK_USE_REFERENCE_COUNTING
static void finalize_refcounts(duk_heap *heap) {
	duk_hthread *thr;
	duk_heaphdr *hdr;

	/* FIXME: placeholder */
	thr = get_temp_hthread(heap);
	DUK_ASSERT(thr != NULL);

	DUK_DDPRINT("finalize_refcounts: heap=%p, hthread=%p",
	            (void *) heap, (void *) thr);

	hdr = heap->heap_allocated;
	while (hdr) {
		if (!DUK_HEAPHDR_HAS_REACHABLE(hdr)) {
			/*
			 *  Unreachable object about to be swept.  Finalize target refcounts
			 *  (objects which the unreachable object points to) without doing
			 *  refzero processing.  Recursive decrefs are also prevented when
			 *  refzero processing is disabled.
			 *
			 *  Value cannot be a finalizable object, as they have been made
			 *  temporarily reachable for this round.
			 */

			DUK_DDDPRINT("unreachable object, refcount finalize before sweeping: %p", (void *) hdr);
			duk_heap_refcount_finalize_heaphdr(thr, hdr);
		}

		hdr = DUK_HEAPHDR_GET_NEXT(hdr);
	}
}
#endif  /* DUK_USE_REFERENCE_COUNTING */

/*
 *  Clear (reachable) flags of refzero work list.
 */

#ifdef DUK_USE_REFERENCE_COUNTING
static void clear_refzero_list_flags(duk_heap *heap) {
	duk_heaphdr *hdr;

	DUK_DDPRINT("clear_refzero_list_flags: %p", (void *) heap);

	hdr = heap->refzero_list;
	while (hdr) {
		DUK_HEAPHDR_CLEAR_REACHABLE(hdr);
		DUK_ASSERT(!DUK_HEAPHDR_HAS_FINALIZABLE(hdr));
		DUK_ASSERT(!DUK_HEAPHDR_HAS_FINALIZED(hdr));
		DUK_ASSERT(!DUK_HEAPHDR_HAS_TEMPROOT(hdr));
		hdr = DUK_HEAPHDR_GET_NEXT(hdr);
	}
}
#endif  /* DUK_USE_REFERENCE_COUNTING */

/*
 *  Sweep stringtable
 */

static void sweep_stringtable(duk_heap *heap) {
	duk_hstring *h;
	int i;
#ifdef DUK_USE_DEBUG
	int count_free = 0;
	int count_keep = 0;
#endif

	DUK_DDPRINT("sweep_stringtable: %p", (void *) heap);

	for (i = 0; i < heap->st_size; i++) {
		h = heap->st[i];
		if (h == NULL || h == DUK_STRTAB_DELETED_MARKER(heap)) {
			continue;
		} else if (DUK_HEAPHDR_HAS_REACHABLE((duk_heaphdr *) h)) {
#ifdef DUK_USE_DEBUG
			count_keep++;
#endif
			continue;
		}

#ifdef DUK_USE_DEBUG
		count_free++;
#endif

#if defined(DUK_USE_DEBUG) && defined(DUK_USE_REFERENCE_COUNTING)
		/* Non-zero refcounts should not happen for unreachable strings,
		 * because we refcount finalize all unreachable objects which
		 * should have decreased unreachable string refcounts to zero
		 * (even for cycles).
		 */
		DUK_ASSERT(DUK_HEAPHDR_GET_REFCOUNT((duk_heaphdr *) h) == 0);
#endif

		DUK_DDDPRINT("sweep string, not reachable: %p", (void *) h);

		/* deal with weak references first */
		duk_heap_strcache_string_remove(heap, (duk_hstring *) h);

		/* remove the string (mark DELETED), could also call
		 * duk_heap_string_remove() but that would be slow and
		 * pointless because we already know the slot.
		 */
		heap->st[i] = DUK_STRTAB_DELETED_MARKER(heap);

		/* then free */
#if 1
		DUK_FREE(heap, (duk_heaphdr *) h);  /* no inner refs/allocs, just free directly */
#else
		duk_heap_free_heaphdr_raw(heap, (duk_heaphdr *) h);  /* this would be OK but unnecessary */
#endif
	}

#ifdef DUK_USE_DEBUG
	DUK_DPRINT("mark-and-sweep sweep stringtable: %d freed, %d kept", count_free, count_keep);
#endif
}

/*
 *  Sweep heap
 */

static void sweep_heap(duk_heap *heap, int flags) {
	duk_heaphdr *prev;  /* last element that was left in the heap */
	duk_heaphdr *curr;
	duk_heaphdr *next;
#ifdef DUK_USE_DEBUG
	int count_free = 0;
	int count_keep = 0;
	int count_finalize = 0;
	int count_rescue = 0;
#endif

	DUK_DDPRINT("sweep_heap: %p", (void *) heap);

	prev = NULL;
	curr = heap->heap_allocated;
	heap->heap_allocated = NULL;
	while (curr) {
		/* strings are never placed on the heap allocated list */
		DUK_ASSERT(DUK_HEAPHDR_GET_TYPE(curr) != DUK_HTYPE_STRING);

		next = DUK_HEAPHDR_GET_NEXT(curr);

		if (DUK_HEAPHDR_HAS_REACHABLE(curr)) {
			/*
			 *  Reachable object, keep
			 */

			DUK_DDDPRINT("sweep, reachable: %p", (void *) curr);

			if (DUK_HEAPHDR_HAS_FINALIZABLE(curr)) {
				/*
				 *  If object has been marked finalizable, move it to the
				 *  "to be finalized" work list.  It will be collected on
				 *  the next mark-and-sweep if it is still unreachable
				 *  after running the finalizer.
				 */

				DUK_ASSERT(!DUK_HEAPHDR_HAS_FINALIZED(curr));
				DUK_ASSERT(DUK_HEAPHDR_GET_TYPE(curr) == DUK_HTYPE_OBJECT);
				DUK_DDDPRINT("object has finalizer, move to finalization work list: %p", (void *) curr);

#ifdef DUK_USE_DOUBLE_LINKED_HEAP
				if (heap->finalize_list) {
					DUK_HEAPHDR_SET_PREV(heap->finalize_list, curr);
				}
				DUK_HEAPHDR_SET_PREV(curr, NULL);
#endif
				DUK_HEAPHDR_SET_NEXT(curr, heap->finalize_list);
				heap->finalize_list = curr;
#ifdef DUK_USE_DEBUG
				count_finalize++;
#endif
			} else {
				/*
				 *  Object will be kept; queue object back to heap_allocated (to tail)
				 */

				if (DUK_HEAPHDR_HAS_FINALIZED(curr)) {
					/*
					 *  Object's finalizer was executed on last round, and
					 *  object has been happily rescued.
					 */

					DUK_ASSERT(!DUK_HEAPHDR_HAS_FINALIZABLE(curr));
					DUK_ASSERT(DUK_HEAPHDR_GET_TYPE(curr) == DUK_HTYPE_OBJECT);
					DUK_DDPRINT("object rescued during mark-and-sweep finalization: %p", (void *) curr);
#ifdef DUK_USE_DEBUG
					count_rescue++;
#endif
				} else {
					/*
					 *  Plain, boring reachable object.
					 */
#ifdef DUK_USE_DEBUG
					count_keep++;
#endif
				}

				if (!heap->heap_allocated) {
					heap->heap_allocated = curr;
				}
				if (prev) {
					DUK_HEAPHDR_SET_NEXT(prev, curr);
				}
#ifdef DUK_USE_DOUBLE_LINKED_HEAP
				DUK_HEAPHDR_SET_PREV(curr, prev);
#endif
				prev = curr;
			}

			DUK_HEAPHDR_CLEAR_REACHABLE(curr);
			DUK_HEAPHDR_CLEAR_FINALIZED(curr);
			DUK_HEAPHDR_CLEAR_FINALIZABLE(curr);

			DUK_ASSERT(!DUK_HEAPHDR_HAS_REACHABLE(curr));
			DUK_ASSERT(!DUK_HEAPHDR_HAS_FINALIZED(curr));
			DUK_ASSERT(!DUK_HEAPHDR_HAS_FINALIZABLE(curr));

			curr = next;
		} else {
			/*
			 *  Unreachable object, free
			 */

			DUK_DDDPRINT("sweep, not reachable: %p", (void *) curr);

#if defined(DUK_USE_DEBUG) && defined(DUK_USE_REFERENCE_COUNTING)
			/* Non-zero refcounts should not happen because we refcount
			 * finalize all unreachable objects which should cancel out
			 * refcounts (even for cycles).
			 */
			DUK_ASSERT(DUK_HEAPHDR_GET_REFCOUNT(curr) == 0);
#endif
			DUK_ASSERT(!DUK_HEAPHDR_HAS_FINALIZABLE(curr));

			if (DUK_HEAPHDR_HAS_FINALIZED(curr)) {
				DUK_DDDPRINT("finalized object not rescued: %p", (void *) curr);
			}

			/* Note: object cannot be a finalizable unreachable object, as
			 * they have been marked temporarily reachable for this round,
			 * and are handled above.
			 */

#ifdef DUK_USE_DEBUG
			count_free++;
#endif

			/* weak refs should be handled here, but no weak refs for
			 * any non-string objects exist right now.
			 */

			/* free object and all auxiliary (non-heap) allocs */
			duk_heap_free_heaphdr_raw(heap, curr);

			curr = next;
		}
	}
	if (prev) {
		DUK_HEAPHDR_SET_NEXT(prev, NULL);
	}

#ifdef DUK_USE_DEBUG
	DUK_DPRINT("mark-and-sweep sweep objects (non-string): %d freed, %d kept, %d rescued, %d queued for finalization",
	            count_free, count_keep, count_rescue, count_finalize);
#endif
}

/*
 *  Run (object) finalizers in the "to be finalized" work list.
 */

static void run_object_finalizers(duk_heap *heap) {
	duk_heaphdr *curr;
	duk_heaphdr *next;
#ifdef DUK_USE_DEBUG
	int count = 0;
#endif
	duk_hthread *thr;

	DUK_DDPRINT("run_object_finalizers: %p", (void *) heap);

	/* FIXME: placeholder */
	thr = get_temp_hthread(heap);
	DUK_ASSERT(thr != NULL);

	curr = heap->finalize_list;
	while (curr) {
		DUK_DDDPRINT("mark-and-sweep finalize: %p", (void *) curr);

		DUK_ASSERT(DUK_HEAPHDR_GET_TYPE(curr) == DUK_HTYPE_OBJECT);  /* only objects have finalizers */
		DUK_ASSERT(!DUK_HEAPHDR_HAS_REACHABLE(curr));                /* flags have been already cleared */
		DUK_ASSERT(!DUK_HEAPHDR_HAS_TEMPROOT(curr));
		DUK_ASSERT(!DUK_HEAPHDR_HAS_FINALIZABLE(curr));
		DUK_ASSERT(!DUK_HEAPHDR_HAS_FINALIZED(curr));

		/* run the finalizer */
		duk_hobject_run_finalizer(thr, (duk_hobject *) curr);  /* must never longjmp */

		/* mark FINALIZED, for next mark-and-sweep (will collect unless has become reachable;
		 * prevent running finalizer again if reachable)
		 */
		DUK_HEAPHDR_SET_FINALIZED(curr);

		/* queue back to heap_allocated */
		next = DUK_HEAPHDR_GET_NEXT(curr);
		DUK_HEAP_INSERT_INTO_HEAP_ALLOCATED(heap, curr);

		curr = next;
#ifdef DUK_USE_DEBUG
		count++;
#endif
	}

	/* finalize_list will always be processed completely */
	heap->finalize_list = NULL;

#ifdef DUK_USE_DEBUG
	DUK_DPRINT("mark-and-sweep finalize objects: %d finalizers called", count);
#endif
}

/*
 *  Object compaction.
 *
 *  Compaction is assumed to never throw an error.
 */

static int _protected_compact_object(duk_context *ctx) {
	/* FIXME: for threads, compact value stack, call stack, catch stack? */

	duk_hobject *obj = duk_get_hobject(ctx, -1);
	DUK_ASSERT(obj != NULL);
	duk_hobject_compact_props((duk_hthread *) ctx, obj);
	return 0;
}

#ifdef DUK_USE_DEBUG
static void compact_object_list(duk_heap *heap, duk_hthread *thr, duk_heaphdr *start, int *p_count_check, int *p_count_compact, int *p_count_bytes_saved) {
#else
static void compact_object_list(duk_heap *heap, duk_hthread *thr, duk_heaphdr *start) {
#endif
	duk_heaphdr *curr;
#ifdef DUK_USE_DEBUG
	size_t old_size, new_size;
#endif
	duk_hobject *obj;

	curr = start;
	while (curr) {
		DUK_DDDPRINT("mark-and-sweep compact: %p", (void *) curr);

		if (DUK_HEAPHDR_GET_TYPE(curr) != DUK_HTYPE_OBJECT) {
			goto next;	
		}
		obj = (duk_hobject *) curr;

#ifdef DUK_USE_DEBUG
		old_size = DUK_HOBJECT_P_COMPUTE_SIZE(obj->e_size, obj->a_size, obj->h_size);
#endif

		DUK_DDPRINT("compact object: %p", (void *) obj);
		duk_push_hobject((duk_context *) thr, obj);
		duk_safe_call((duk_context *) thr, _protected_compact_object, 1, 0, DUK_INVALID_INDEX);  /* XXX: replace errhandler with NULL? */

#ifdef DUK_USE_DEBUG
		new_size = DUK_HOBJECT_P_COMPUTE_SIZE(obj->e_size, obj->a_size, obj->h_size);
#endif

#ifdef DUK_USE_DEBUG
		(*p_count_compact)++;
		(*p_count_bytes_saved) += old_size - new_size;
#endif

	 next:
		curr = DUK_HEAPHDR_GET_NEXT(curr);
#ifdef DUK_USE_DEBUG
		(*p_count_check)++;
#endif
	}
}

static void compact_objects(duk_heap *heap) {
	/* FIXME: which lists should participate?  to be finalized? */
#ifdef DUK_USE_DEBUG
	int count_check = 0;
	int count_compact = 0;
	int count_bytes_saved = 0;
#endif
	duk_hthread *thr;

	DUK_DDPRINT("compact_objects: %p", (void *) heap);

	/* FIXME: placeholder */
	thr = get_temp_hthread(heap);
	DUK_ASSERT(thr != NULL);

#ifdef DUK_USE_DEBUG
	compact_object_list(heap, thr, heap->heap_allocated, &count_check, &count_compact, &count_bytes_saved);
	compact_object_list(heap, thr, heap->finalize_list, &count_check, &count_compact, &count_bytes_saved);
#ifdef DUK_USE_REFERENCE_COUNTING
	compact_object_list(heap, thr, heap->refzero_list, &count_check, &count_compact, &count_bytes_saved);
#endif
#else
	compact_object_list(heap, thr, heap->heap_allocated);
	compact_object_list(heap, thr, heap->finalize_list);
#ifdef DUK_USE_REFERENCE_COUNTING
	compact_object_list(heap, thr, heap->refzero_list);
#endif
#endif

#ifdef DUK_USE_DEBUG
	DUK_DPRINT("mark-and-sweep compact objects: %d checked, %d compaction attempts, %d bytes saved by compaction", count_check, count_compact, count_bytes_saved);
#endif
}

/*
 *  Resize stringtable.
 */

static void resize_stringtable(duk_heap *heap) {
	DUK_DDPRINT("resize_stringtable: %p", (void *) heap);
	duk_heap_force_stringtable_resize(heap);
}

/*
 *  Assertion helpers.
 */

#ifdef DUK_USE_ASSERTIONS
static void assert_heaphdr_flags(duk_heap *heap) {
	duk_heaphdr *hdr;

	hdr = heap->heap_allocated;
	while (hdr) {
		DUK_ASSERT(!DUK_HEAPHDR_HAS_REACHABLE(hdr));
		DUK_ASSERT(!DUK_HEAPHDR_HAS_TEMPROOT(hdr));
		DUK_ASSERT(!DUK_HEAPHDR_HAS_FINALIZABLE(hdr));
		/* may have FINALIZED */
		hdr = DUK_HEAPHDR_GET_NEXT(hdr);
	}

#ifdef DUK_USE_REFERENCE_COUNTING
	hdr = heap->refzero_list;
	while (hdr) {
		DUK_ASSERT(!DUK_HEAPHDR_HAS_REACHABLE(hdr));
		DUK_ASSERT(!DUK_HEAPHDR_HAS_TEMPROOT(hdr));
		DUK_ASSERT(!DUK_HEAPHDR_HAS_FINALIZABLE(hdr));
		DUK_ASSERT(!DUK_HEAPHDR_HAS_FINALIZED(hdr));
		hdr = DUK_HEAPHDR_GET_NEXT(hdr);
	}
#endif  /* DUK_USE_REFERENCE_COUNTING */
}

#ifdef DUK_USE_REFERENCE_COUNTING
static void assert_valid_refcounts(duk_heap *heap) {
	duk_heaphdr *hdr = heap->heap_allocated;
	while (hdr) {
		if (DUK_HEAPHDR_GET_REFCOUNT(hdr) == 0 &&
		    DUK_HEAPHDR_HAS_FINALIZED(hdr)) {
			/* An object may be in heap_allocated list with a zero
			 * refcount if it has just been finalized and is waiting
			 * to be collected by the next cycle.
			 */
		} else if (DUK_HEAPHDR_GET_REFCOUNT(hdr) == 0) {
			/* An object may be in heap_allocated list with a zero
			 * refcount also if it is a temporary object created by
			 * a finalizer; because finalization now runs inside
			 * mark-and-sweep, such objects will not be queued to
			 * refzero_list and will thus appear here with refcount
			 * zero.
			 */
		} else if (DUK_HEAPHDR_GET_REFCOUNT(hdr) < 0) {
			DUK_DPRINT("invalid refcount: %d, %p -> %!O",
			           (hdr != NULL ? DUK_HEAPHDR_GET_REFCOUNT(hdr) : 0), (void *) hdr, hdr);
			DUK_ASSERT(DUK_HEAPHDR_GET_REFCOUNT(hdr) > 0);
		}
		hdr = DUK_HEAPHDR_GET_NEXT(hdr);
	}
}
#endif  /* DUK_USE_REFERENCE_COUNTING */
#endif  /* DUK_USE_ASSERTIONS */

/*
 *  Main mark-and-sweep function.
 *
 *  'flags' represents the features requested by the caller.  The current
 *  heap->mark_and_sweep_base_flags is ORed automatically into the flags;
 *  the base flags mask typically prevents certain mark-and-sweep operations
 *  to avoid trouble.
 */

int duk_heap_mark_and_sweep(duk_heap *heap, int flags) {
	/* FIXME: thread selection for mark-and-sweep is currently a hack.
	 * If we don't have a thread, the entire mark-and-sweep is now
	 * skipped (although we could just skip finalizations).
	 */
	if (get_temp_hthread(heap) == NULL) {
		DUK_DPRINT("temporary hack: gc skipped because we don't have a temp thread");

		/* reset voluntary gc trigger count */
		heap->mark_and_sweep_trigger_counter = heap->mark_and_sweep_trigger_limit;
		return DUK_ERR_OK;
	}

	DUK_DPRINT("garbage collect (mark-and-sweep) starting, requested flags: 0x%08x, effective flags: 0x%08x",
	           flags, flags | heap->mark_and_sweep_base_flags);

	flags |= heap->mark_and_sweep_base_flags;

	/*
	 *  Assertions before
	 */

#ifdef DUK_USE_ASSERTIONS
	DUK_ASSERT(!DUK_HEAP_HAS_MARKANDSWEEP_RUNNING(heap));
	DUK_ASSERT(!DUK_HEAP_HAS_MARKANDSWEEP_RECLIMIT_REACHED(heap));
	DUK_ASSERT(heap->mark_and_sweep_recursion_depth == 0);
	DUK_ASSERT(heap->mark_and_sweep_recursion_limit >= 1);
	assert_heaphdr_flags(heap);
#ifdef DUK_USE_REFERENCE_COUNTING
	/* Note: DUK_HEAP_HAS_REFZERO_FREE_RUNNING(heap) may be true; a refcount
	 * finalizer may trigger a mark-and-sweep.
	 */
	assert_valid_refcounts(heap);
#endif  /* DUK_USE_REFERENCE_COUNTING */
#endif  /* DUK_USE_ASSERTIONS */

	/*
	 *  Begin
	 */

	DUK_HEAP_SET_MARKANDSWEEP_RUNNING(heap);

	/*
	 *  Mark roots, hoping that recursion limit is not normally hit.
	 *  If recursion limit is hit, run additional reachability rounds
	 *  starting from "temproots" until marking is complete.
	 *
	 *  Marking happens in two phases: first we mark actual reachability
	 *  roots (and run "temproots" to complete the process).  Then we
	 *  check which objects are unreachable and are finalizable; such
	 *  objects are marked as FINALIZABLE and marked as reachability
	 *  (and "temproots" is run again to complete the process).
	 */

	mark_roots_heap(heap);               /* main reachability roots */
#ifdef DUK_USE_REFERENCE_COUNTING
	mark_refzero_list(heap);             /* refzero_list treated as reachability roots */
#endif
	mark_temproots_by_heap_scan(heap);   /* temproots */

	mark_finalizable(heap);              /* mark finalizable as reachability roots */
	mark_temproots_by_heap_scan(heap);   /* temproots */

	/*
	 *  Sweep garbage and remove marking flags, and move objects with
	 *  finalizers to the finalizer work list.
	 *
	 *  Objects to be swept need to get their refcounts finalized before
	 *  they are swept.  In other words, their target object refcounts
	 *  need to be decreased.  This has to be done before freeing any
	 *  objects to avoid decref'ing dangling pointers (which may happen
	 *  even without bugs, e.g. with reference loops)
	 *
	 *  Because strings don't point to other heap objects, similar
	 *  finalization is not necessary for strings.
	 */

	/* FIXME: more emergency behavior, e.g. find smaller hash sizes etc */

#ifdef DUK_USE_REFERENCE_COUNTING
	finalize_refcounts(heap);
#endif
	sweep_heap(heap, flags);
	sweep_stringtable(heap);
#ifdef DUK_USE_REFERENCE_COUNTING
	clear_refzero_list_flags(heap);
#endif

	/*
	 *  Object compaction (emergency only).
	 *
	 *  Object compaction is a separate step after sweeping, as there is
	 *  more free memory for it to work with.  Also, currently compaction
	 *  may insert new objects into the heap allocated list and the string
	 *  table which we don't want to do during a sweep (the reachability
	 *  flags of such objects would be incorrect).  The objects inserted
	 *  are currently:
	 *
	 *    - a temporary duk_hbuffer for a new properties allocation
	 *    - if array part is abandoned, string keys are interned
	 *
	 *  The object insertions go to the front of the list, so they do not
	 *  cause an infinite loop (they are not compacted).
	 */

	if ((flags & DUK_MS_FLAG_EMERGENCY) &&
	    !(flags & DUK_MS_FLAG_NO_OBJECT_COMPACTION)) {
		compact_objects(heap);
	}

	/*
	 *  String table resize check.
	 *
	 *  Note: this may silently (and safely) fail if GC is caused by an
	 *  allocation call in stringtable resize_hash().  Resize_hash()
	 *  will prevent a recursive call to itself by setting the
	 *  DUK_MS_FLAG_NO_STRINGTABLE_RESIZE in heap->mark_and_sweep_base_flags.
	 */

	/* FIXME: stringtable emergency compaction? */

	if (!(flags & DUK_MS_FLAG_NO_STRINGTABLE_RESIZE)) {
		resize_stringtable(heap);
	} else {
		DUK_DPRINT("stringtable resize skipped because DUK_MS_FLAG_NO_STRINGTABLE_RESIZE is set");
	}

	/*
	 *  Finalize objects in the finalization work list.  Finalized
	 *  objects are queued back to heap_allocated with FINALIZED set.
	 *
	 *  Since finalizers may cause arbitrary side effects, they are
	 *  prevented during string table and object property allocation
	 *  resizing using the DUK_MS_FLAG_NO_FINALIZERS flag in
	 *  heap->mark_and_sweep_base_flags.
	 *
	 *  Finalization currently happens inside "MARKANDSWEEP_RUNNING"
	 *  protection (no mark-and-sweep may be triggered by the
	 *  finalizers).  As a side effect:
	 *
	 *    1) an out-of-memory error inside a finalizer will not
	 *       cause a mark-and-sweep and may cause the finalizer
	 *       to fail unnecessarily
	 *
	 *    2) any temporary objects whose refcount decreases to zero
	 *       during finalization will not be put into refzero_list;
	 *       they can only be collected by another mark-and-sweep
	 *
	 *  This is not optimal, but since the sweep for this phase has
	 *  already happened, this is probably good enough for now.
	 */

	if (!(flags & DUK_MS_FLAG_NO_FINALIZERS)) {
		run_object_finalizers(heap);
	} else {
		DUK_DPRINT("finalizer run skipped because DUK_MS_FLAG_NO_FINALIZERS is set");
	}

	/*
	 *  Finish
	 */

	DUK_HEAP_CLEAR_MARKANDSWEEP_RUNNING(heap);

	/*
	 *  Assertions after
	 */

#ifdef DUK_USE_ASSERTIONS
	DUK_ASSERT(!DUK_HEAP_HAS_MARKANDSWEEP_RUNNING(heap));
	DUK_ASSERT(!DUK_HEAP_HAS_MARKANDSWEEP_RECLIMIT_REACHED(heap));
	DUK_ASSERT(heap->mark_and_sweep_recursion_depth == 0);
	DUK_ASSERT(heap->mark_and_sweep_recursion_limit > 1);
	assert_heaphdr_flags(heap);
#ifdef DUK_USE_REFERENCE_COUNTING
	/* Note: DUK_HEAP_HAS_REFZERO_FREE_RUNNING(heap) may be true; a refcount
	 * finalizer may trigger a mark-and-sweep.
	 */
	assert_valid_refcounts(heap);
#endif  /* DUK_USE_REFERENCE_COUNTING */
#endif  /* DUK_USE_ASSERTIONS */

	/*
	 *  Reset trigger counter
	 */

	/* very simplistic now, should be relative to heap size */
	heap->mark_and_sweep_trigger_counter = heap->mark_and_sweep_trigger_limit;

	DUK_DPRINT("garbage collect (mark-and-sweep) finished (trigger reset to %d)",
	           heap->mark_and_sweep_trigger_counter);
	return DUK_ERR_OK;
}

#else

/* no mark-and-sweep gc */

#endif  /* DUK_USE_MARK_AND_SWEEP */

#line 1 "duk_heap_memory.c"
/*
 *  Memory allocation handling.
 */

/* include removed: duk_internal.h */

/*
 *  Helpers
 *
 *  The fast path checks are done within a macro to ensure "inlining"
 *  while the slow path actions use a helper (which won't typically be
 *  inlined in size optimized builds).
 */

#define  VOLUNTARY_PERIODIC_GC(heap)  do { \
		(heap)->mark_and_sweep_trigger_counter--; \
		if ((heap)->mark_and_sweep_trigger_counter <= 0) { \
			run_voluntary_gc(heap); \
		} \
	} while (0)

static void run_voluntary_gc(duk_heap *heap) {
	if (DUK_HEAP_HAS_MARKANDSWEEP_RUNNING(heap)) {
		DUK_DDPRINT("mark-and-sweep in progress -> skip voluntary mark-and-sweep now");
	} else {
		int flags;
		int rc;

		DUK_DPRINT("triggering voluntary mark-and-sweep");
		flags = 0;
		rc = duk_heap_mark_and_sweep(heap, flags);
		DUK_UNREF(rc);
	}
}

/*
 *  Allocate memory with garbage collection
 */

#ifdef DUK_USE_MARK_AND_SWEEP
void *duk_heap_mem_alloc(duk_heap *heap, size_t size) {
	void *res;
	int rc;
	int i;

	DUK_ASSERT(heap != NULL);
	DUK_ASSERT(size >= 0);

	/*
	 *  Voluntary periodic GC
	 */

	/* FIXME: additionally allocated bytes counter; this is especially
	 * important for mark-and-sweep only mode.
	 */

	VOLUNTARY_PERIODIC_GC(heap);

	/*
	 *  First attempt
	 */

#ifdef DUK_USE_GC_TORTURE
	/* simulate alloc failure on every alloc (except when mark-and-sweep is running) */
	if (!DUK_HEAP_HAS_MARKANDSWEEP_RUNNING(heap)) {
		DUK_DDDPRINT("gc torture enabled, pretend that first alloc attempt fails");
		res = NULL;
		DUK_UNREF(res);
		goto skip_attempt;
	}
#endif
	res = heap->alloc_func(heap->alloc_udata, size);
	if (res || size == 0) {
		/* for zero size allocations NULL is allowed */
		return res;
	}
#ifdef DUK_USE_GC_TORTURE
 skip_attempt:
#endif

	DUK_DPRINT("first alloc attempt failed, attempt to gc and retry");

	/*
	 *  Avoid a GC if GC is already running.  This can happen at a late
	 *  stage in a GC when we try to e.g. resize the stringtable
	 *  or compact objects.
	 */

	if (DUK_HEAP_HAS_MARKANDSWEEP_RUNNING(heap)) {
		DUK_DPRINT("duk_heap_mem_alloc() failed, gc in progress (gc skipped), alloc size %d", size);
		return NULL;
	}

	/*
	 *  Retry with several GC attempts.  Initial attempts are made without
	 *  emergency mode; later attempts use emergency mode which minimizes
	 *  memory allocations forcibly.
	 */

	for (i = 0; i < DUK_HEAP_ALLOC_FAIL_MARKANDSWEEP_LIMIT; i++) {
		int flags;

		flags = 0;
		if (i >= DUK_HEAP_ALLOC_FAIL_MARKANDSWEEP_EMERGENCY_LIMIT - 1) {
			flags |= DUK_MS_FLAG_EMERGENCY;
		}

		rc = duk_heap_mark_and_sweep(heap, flags);
		DUK_UNREF(rc);

		res = heap->alloc_func(heap->alloc_udata, size);
		if (res) {
			DUK_DPRINT("duk_heap_mem_alloc() succeeded after gc (pass %d), alloc size %d",
			           i + 1, size);
			return res;
		}
	}

	DUK_DPRINT("duk_heap_mem_alloc() failed even after gc, alloc size %d", size);
	return NULL;
}
#else  /* DUK_USE_MARK_AND_SWEEP */
/*
 *  Compared to a direct macro expansion this wrapper saves a few
 *  instructions because no heap dereferencing is required.
 */
void *duk_heap_mem_alloc(duk_heap *heap, size_t size) {
	DUK_ASSERT(heap != NULL);
	DUK_ASSERT(size >= 0);

	return heap->alloc_func(heap->alloc_udata, size);
}
#endif  /* DUK_USE_MARK_AND_SWEEP */

void *duk_heap_mem_alloc_zeroed(duk_heap *heap, size_t size) {
	void *res;

	DUK_ASSERT(heap != NULL);
	DUK_ASSERT(size >= 0);

	res = DUK_ALLOC(heap, size);
	if (res) {
		/* assume memset with zero size is OK */
		memset(res, 0, size);
	}
	return res;
}

/*
 *  Reallocate memory with garbage collection
 */

#ifdef DUK_USE_MARK_AND_SWEEP
void *duk_heap_mem_realloc(duk_heap *heap, void *ptr, size_t newsize) {
	void *res;
	int rc;
	int i;

	DUK_ASSERT(heap != NULL);
	/* ptr may be NULL */
	DUK_ASSERT(newsize >= 0);

	/*
	 *  Voluntary periodic GC
	 */

	VOLUNTARY_PERIODIC_GC(heap);

	/*
	 *  First attempt
	 */

#ifdef DUK_USE_GC_TORTURE
	/* simulate alloc failure on every realloc (except when mark-and-sweep is running) */
	if (!DUK_HEAP_HAS_MARKANDSWEEP_RUNNING(heap)) {
		DUK_DDDPRINT("gc torture enabled, pretend that first realloc attempt fails");
		res = NULL;
		DUK_UNREF(res);
		goto skip_attempt;
	}
#endif
	res = heap->realloc_func(heap->alloc_udata, ptr, newsize);
	if (res || newsize == 0) {
		/* for zero size allocations NULL is allowed */
		return res;
	}
#ifdef DUK_USE_GC_TORTURE
 skip_attempt:
#endif

	DUK_DPRINT("first realloc attempt failed, attempt to gc and retry");

	/*
	 *  Avoid a GC if GC is already running.  See duk_heap_mem_alloc().
	 */

	if (DUK_HEAP_HAS_MARKANDSWEEP_RUNNING(heap)) {
		DUK_DPRINT("duk_heap_mem_realloc() failed, gc in progress (gc skipped), alloc size %d", newsize);
		return NULL;
	}

	/*
	 *  Retry with several GC attempts.  Initial attempts are made without
	 *  emergency mode; later attempts use emergency mode which minimizes
	 *  memory allocations forcibly.
	 */

	for (i = 0; i < DUK_HEAP_ALLOC_FAIL_MARKANDSWEEP_LIMIT; i++) {
		int flags;

		flags = 0;
		if (i >= DUK_HEAP_ALLOC_FAIL_MARKANDSWEEP_EMERGENCY_LIMIT - 1) {
			flags |= DUK_MS_FLAG_EMERGENCY;
		}

		rc = duk_heap_mark_and_sweep(heap, flags);
		DUK_UNREF(rc);

		res = heap->realloc_func(heap->alloc_udata, ptr, newsize);
		if (res) {
			DUK_DPRINT("duk_heap_mem_realloc() succeeded after gc (pass %d), alloc size %d",
			           i + 1, newsize);
			return res;
		}
	}

	DUK_DPRINT("duk_heap_mem_realloc() failed even after gc, alloc size %d", newsize);
	return NULL;
}
#else  /* DUK_USE_MARK_AND_SWEEP */
/* saves a few instructions to have this wrapper (see comment on duk_heap_mem_alloc) */
void *duk_heap_mem_realloc(duk_heap *heap, void *ptr, size_t newsize) {
	DUK_ASSERT(heap != NULL);
	/* ptr may be NULL */
	DUK_ASSERT(newsize >= 0);

	return heap->realloc_func(heap->alloc_udata, ptr, newsize);
}
#endif  /* DUK_USE_MARK_AND_SWEEP */

/*
 *  Reallocate memory with garbage collection, using an indirect pointer
 *
 *  This variant is used when a mark-and-sweep (finalizers) might change
 *  the original pointer.  The indirect 'iptr' must have a stable location.
 */

#ifdef DUK_USE_MARK_AND_SWEEP
void *duk_heap_mem_realloc_indirect(duk_heap *heap, void **iptr, size_t newsize) {
	void *res;
	int rc;
	int i;

	DUK_ASSERT(heap != NULL);
	DUK_ASSERT(iptr != NULL);
	/* *iptr may be NULL */
	DUK_ASSERT(newsize >= 0);

	/*
	 *  Voluntary periodic GC
	 */

	VOLUNTARY_PERIODIC_GC(heap);

	/*
	 *  First attempt
	 */

#ifdef DUK_USE_GC_TORTURE
	/* simulate alloc failure on every realloc (except when mark-and-sweep is running) */
	if (!DUK_HEAP_HAS_MARKANDSWEEP_RUNNING(heap)) {
		DUK_DDDPRINT("gc torture enabled, pretend that first indirect realloc attempt fails");
		res = NULL;
		DUK_UNREF(res);
		goto skip_attempt;
	}
#endif
	res = heap->realloc_func(heap->alloc_udata, *iptr, newsize);
	if (res || newsize == 0) {
		/* for zero size allocations NULL is allowed */
		return res;
	}
#ifdef DUK_USE_GC_TORTURE
 skip_attempt:
#endif

	DUK_DPRINT("first indirect realloc attempt failed, attempt to gc and retry");

	/*
	 *  Avoid a GC if GC is already running.  See duk_heap_mem_alloc().
	 */

	if (DUK_HEAP_HAS_MARKANDSWEEP_RUNNING(heap)) {
		DUK_DPRINT("duk_heap_mem_realloc_indirect() failed, gc in progress (gc skipped), alloc size %d", newsize);
		return NULL;
	}

	/*
	 *  Retry with several GC attempts.  Initial attempts are made without
	 *  emergency mode; later attempts use emergency mode which minimizes
	 *  memory allocations forcibly.
	 */

	for (i = 0; i < DUK_HEAP_ALLOC_FAIL_MARKANDSWEEP_LIMIT; i++) {
		int flags;

#ifdef DUK_USE_ASSERTIONS
		void *ptr_pre;  /* ptr before mark-and-sweep */
#endif

#ifdef DUK_USE_ASSERTIONS
		ptr_pre = *iptr;
#endif
		flags = 0;
		if (i >= DUK_HEAP_ALLOC_FAIL_MARKANDSWEEP_EMERGENCY_LIMIT - 1) {
			flags |= DUK_MS_FLAG_EMERGENCY;
		}

		rc = duk_heap_mark_and_sweep(heap, flags);
		DUK_UNREF(rc);
#ifdef DUK_USE_ASSERTIONS
		if (ptr_pre != *iptr) {
			/* useful for debugging */
			DUK_DDPRINT("note: *iptr changed by mark-and-sweep: %p -> %p", ptr_pre, *iptr);
		}
#endif
	
		/* Note: key issue here is to re-lookup *iptr on every attempt -- the
		 * value behind iptr may change after every mark-and-sweep.
		 */

		res = heap->realloc_func(heap->alloc_udata, *iptr, newsize);
		if (res) {
			DUK_DPRINT("duk_heap_mem_realloc_indirect() succeeded after gc (pass %d), alloc size %d",
			           i + 1, newsize);
			return res;
		}
	}

	DUK_DPRINT("duk_heap_mem_realloc_indirect() failed even after gc, alloc size %d", newsize);
	return NULL;
}
#else  /* DUK_USE_MARK_AND_SWEEP */
/* saves a few instructions to have this wrapper (see comment on duk_heap_mem_alloc) */
void *duk_heap_mem_realloc(duk_heap *heap, void *ptr, size_t newsize) {
	return heap->realloc_func(heap->alloc_udata, ptr, newsize);
}
#endif  /* DUK_USE_MARK_AND_SWEEP */

/*
 *  Free memory
 */

#ifdef DUK_USE_MARK_AND_SWEEP
void duk_heap_mem_free(duk_heap *heap, void *ptr) {
	DUK_ASSERT(heap != NULL);
	/* ptr may be NULL */

	/* Must behave like a no-op with NULL and any pointer returned from
	 * malloc/realloc with zero size.
	 */
	heap->free_func(heap->alloc_udata, ptr);

	/* Count free operations toward triggering a GC but never actually trigger
	 * a GC from a free.  Otherwise code which frees internal structures would
	 * need to put in NULLs at every turn to ensure the object is always in
	 * consistent state for a mark-and-sweep.
	 */
	heap->mark_and_sweep_trigger_counter--;
}
#else
/* saves a few instructions to have this wrapper (see comment on duk_heap_mem_alloc) */
void duk_heap_mem_free(duk_heap *heap, void *ptr) {
	DUK_ASSERT(heap != NULL);
	/* ptr may be NULL */

	/* Note: must behave like a no-op with NULL and any pointer
	 * returned from malloc/realloc with zero size.
	 */
	heap->free_func(heap->alloc_udata, ptr);
}
#endif

/*
 *  Checked variants
 */

#ifdef DUK_USE_VERBOSE_ERRORS
void *duk_heap_mem_alloc_checked(duk_hthread *thr, size_t size, const char *filename, int line) {
#else
void *duk_heap_mem_alloc_checked(duk_hthread *thr, size_t size) {
#endif
	void *res;

	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(size >= 0);

	res = DUK_ALLOC(thr->heap, size);
	if (!res) {
		DUK_ERROR(thr, DUK_ERR_ALLOC_ERROR, "memory alloc failed");
	}
	return res;
}

#ifdef DUK_USE_VERBOSE_ERRORS
void *duk_heap_mem_alloc_checked_zeroed(duk_hthread *thr, size_t size, const char *filename, int line) {
#else
void *duk_heap_mem_alloc_checked_zeroed(duk_hthread *thr, size_t size) {
#endif
	void *res;

	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(size >= 0);

	res = DUK_ALLOC(thr->heap, size);
	if (!res) {
		DUK_ERROR(thr, DUK_ERR_ALLOC_ERROR, "memory alloc failed");
	}
	/* assume memset with zero size is OK */
	memset(res, 0, size);
	return res;
}

#ifdef DUK_USE_VERBOSE_ERRORS
void *duk_heap_mem_realloc_checked(duk_hthread *thr, void *ptr, size_t newsize, const char *filename, int line) {
#else
void *duk_heap_mem_realloc_checked(duk_hthread *thr, void *ptr, size_t newsize) {
#endif
	DUK_ASSERT(thr != NULL);
	/* ptr may be NULL */
	DUK_ASSERT(newsize >= 0);

	void *res = DUK_REALLOC(thr->heap, ptr, newsize);
	if (!res) {
		DUK_ERROR(thr, DUK_ERR_ALLOC_ERROR, "memory realloc failed");
	}
	return res;
}

#ifdef DUK_USE_VERBOSE_ERRORS
void *duk_heap_mem_realloc_indirect_checked(duk_hthread *thr, void **iptr, size_t newsize, const char *filename, int line) {
#else
void *duk_heap_mem_realloc_indirect_checked(duk_hthread *thr, void **iptr, size_t newsize) {
#endif
	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(iptr != NULL);
	/* *iptr may be NULL */
	DUK_ASSERT(newsize >= 0);

	void *res = DUK_REALLOC_INDIRECT(thr->heap, iptr, newsize);
	if (!res) {
		DUK_ERROR(thr, DUK_ERR_ALLOC_ERROR, "memory realloc failed");
	}
	return res;
}

/* Note: no need for duk_heap_mem_free_checked(), as free must not fail.
 * There is a DUK_FREE_CHECKED() macro just in case, though.
 */


#line 1 "duk_heap_misc.c"
/*
 *  Support functions for duk_heap.
 */

/* include removed: duk_internal.h */

#if defined(DUK_USE_DOUBLE_LINKED_HEAP) && defined(DUK_USE_REFERENCE_COUNTING)
/* arbitrary remove only works with double linked heap, and is only required by
 * reference counting so far.
 */
void duk_heap_remove_any_from_heap_allocated(duk_heap *heap, duk_heaphdr *hdr) {
	DUK_ASSERT(DUK_HEAPHDR_GET_TYPE(hdr) != DUK_HTYPE_STRING);

	if (DUK_HEAPHDR_GET_PREV(hdr)) {
		DUK_HEAPHDR_SET_NEXT(DUK_HEAPHDR_GET_PREV(hdr), DUK_HEAPHDR_GET_NEXT(hdr));
	} else {
		heap->heap_allocated = DUK_HEAPHDR_GET_NEXT(hdr);
	}
	if (DUK_HEAPHDR_GET_NEXT(hdr)) {
		DUK_HEAPHDR_SET_PREV(DUK_HEAPHDR_GET_NEXT(hdr), DUK_HEAPHDR_GET_PREV(hdr));
	} else {
		;
	}
}
#endif

void duk_heap_insert_into_heap_allocated(duk_heap *heap, duk_heaphdr *hdr) {
	DUK_ASSERT(DUK_HEAPHDR_GET_TYPE(hdr) != DUK_HTYPE_STRING);

#ifdef DUK_USE_DOUBLE_LINKED_HEAP
	if (heap->heap_allocated) {
		DUK_ASSERT(DUK_HEAPHDR_GET_PREV(heap->heap_allocated) == NULL);
		DUK_HEAPHDR_SET_PREV(heap->heap_allocated, hdr);
	}
	DUK_HEAPHDR_SET_PREV(hdr, NULL);
#endif
	DUK_HEAPHDR_SET_NEXT(hdr, heap->heap_allocated);
	heap->heap_allocated = hdr;
}

#line 1 "duk_heap_refcount.c"
/*
 *  Reference counting implementation.
 */

/* include removed: duk_internal.h */

#ifdef DUK_USE_REFERENCE_COUNTING

#ifndef DUK_USE_DOUBLE_LINKED_HEAP
#error internal error, reference counting requires a double linked heap
#endif

/*
 *  Misc
 */

static void queue_refzero(duk_heap *heap, duk_heaphdr *hdr) {
	/* tail insert: don't disturb head in case refzero is running */

	if (heap->refzero_list != NULL) {
		duk_heaphdr *hdr_prev;

		hdr_prev = heap->refzero_list_tail;
		DUK_ASSERT(hdr_prev != NULL);
		DUK_ASSERT(DUK_HEAPHDR_GET_NEXT(hdr_prev) == NULL);

		DUK_HEAPHDR_SET_NEXT(hdr, NULL);
		DUK_HEAPHDR_SET_PREV(hdr, hdr_prev);
		DUK_HEAPHDR_SET_NEXT(hdr_prev, hdr);
		heap->refzero_list_tail = hdr;
	} else {
		DUK_ASSERT(heap->refzero_list_tail == NULL);
		DUK_HEAPHDR_SET_NEXT(hdr, NULL);
		DUK_HEAPHDR_SET_PREV(hdr, NULL);
		heap->refzero_list = hdr;
		heap->refzero_list_tail = hdr;
	}
}

/*
 *  Heap object refcount finalization.
 *
 *  When an object is about to be freed, all other objects it refers to must
 *  be decref'd.  Refcount finalization does NOT free the object or its inner
 *  allocations (mark-and-sweep shares these helpers), it just manipulates
 *  the refcounts.
 *
 *  Note that any of the decref's may cause a refcount to drop to zero, BUT
 *  it will not be processed inline; instead, because refzero is already
 *  running, the objects will just be queued to refzero list and processed
 *  later.  This eliminates C recursion.
 */

static void refcount_finalize_hobject(duk_hthread *thr, duk_hobject *h) {
	int i;

	DUK_ASSERT(h);
	DUK_ASSERT(DUK_HEAPHDR_GET_TYPE((duk_heaphdr *) h) == DUK_HTYPE_OBJECT);

	/* XXX: better to get base and walk forwards? */

	for (i = 0; i < h->e_used; i++) {
		duk_hstring *key = DUK_HOBJECT_E_GET_KEY(h, i);
		if (!key) {
			continue;
		}
		duk_heap_heaphdr_decref(thr, (duk_heaphdr *) key);
		if (DUK_HOBJECT_E_SLOT_IS_ACCESSOR(h, i)) {
			duk_heap_heaphdr_decref(thr, (duk_heaphdr *) DUK_HOBJECT_E_GET_VALUE_GETTER(h, i));
			duk_heap_heaphdr_decref(thr, (duk_heaphdr *) DUK_HOBJECT_E_GET_VALUE_SETTER(h, i));
		} else {
			duk_heap_tval_decref(thr, DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(h, i));
		}
	}

	for (i = 0; i < h->a_size; i++) {
		duk_heap_tval_decref(thr, DUK_HOBJECT_A_GET_VALUE_PTR(h, i));
	}

	/* hash part is a 'weak reference' and does not contribute */

	duk_heap_heaphdr_decref(thr, (duk_heaphdr *) h->prototype);

	if (DUK_HOBJECT_IS_COMPILEDFUNCTION(h)) {
		duk_hcompiledfunction *f = (duk_hcompiledfunction *) h;
		duk_tval *tv, *tv_end;
		duk_hobject **funcs, **funcs_end;

		DUK_ASSERT(f->data != NULL);  /* compiled functions must be created 'atomically' */

		tv = DUK_HCOMPILEDFUNCTION_GET_CONSTS_BASE(f);
		tv_end = DUK_HCOMPILEDFUNCTION_GET_CONSTS_END(f);
		while (tv < tv_end) {
			duk_heap_tval_decref(thr, tv);
			tv++;
		}

		funcs = DUK_HCOMPILEDFUNCTION_GET_FUNCS_BASE(f);
		funcs_end = DUK_HCOMPILEDFUNCTION_GET_FUNCS_END(f);
		while (funcs < funcs_end) {
			duk_heap_heaphdr_decref(thr, (duk_heaphdr *) *funcs);
			funcs++;
		}

		duk_heap_heaphdr_decref(thr, (duk_heaphdr *) f->data);
	} else if (DUK_HOBJECT_IS_NATIVEFUNCTION(h)) {
		duk_hnativefunction *f = (duk_hnativefunction *) h;
		DUK_UNREF(f);
		/* nothing to finalize */
	} else if (DUK_HOBJECT_IS_THREAD(h)) {
		duk_hthread *t = (duk_hthread *) h;
		duk_tval *tv;

		tv = t->valstack;
		while (tv < t->valstack_end) {
			duk_heap_tval_decref(thr, tv);
			tv++;
		}

		for (i = 0; i < t->callstack_top; i++) {
			duk_activation *act = &t->callstack[i];
			duk_heap_heaphdr_decref(thr, (duk_heaphdr *) act->func);
			duk_heap_heaphdr_decref(thr, (duk_heaphdr *) act->var_env);
			duk_heap_heaphdr_decref(thr, (duk_heaphdr *) act->lex_env);
		}

#if 0  /* nothing now */
		for (i = 0; i < t->catchstack_top; i++) {
			duk_catcher *cat = &t->catchstack[i];
		}
#endif

		for (i = 0; i < DUK_NUM_BUILTINS; i++) {
			duk_heap_heaphdr_decref(thr, (duk_heaphdr *) t->builtins[i]);
		}

		duk_heap_heaphdr_decref(thr, (duk_heaphdr *) t->resumer);
	}
}

void duk_heap_refcount_finalize_heaphdr(duk_hthread *thr, duk_heaphdr *hdr) {
	DUK_ASSERT(hdr);

	switch (DUK_HEAPHDR_GET_TYPE(hdr)) {
	case DUK_HTYPE_OBJECT:
		refcount_finalize_hobject(thr, (duk_hobject *) hdr);
		break;
	case DUK_HTYPE_BUFFER:
		/* nothing to finalize */
		break;
	case DUK_HTYPE_STRING:
		/* cannot happen: strings are not put into refzero list (they don't even have the next/prev pointers) */
	default:
		DUK_NEVER_HERE();
	}
}

/*
 *  Refcount memory freeing loop.
 *
 *  Frees objects in the refzero_pending list until the list becomes
 *  empty.  When an object is freed, its references get decref'd and
 *  may cause further objects to be queued for freeing.
 *
 *  This could be expanded to allow incremental freeing: just bail out
 *  early and resume at a future alloc/decref/refzero.
 */

static void refzero_free_pending(duk_hthread *thr) {
	duk_heaphdr *h1, *h2;
	duk_heap *heap;
	int count = 0;

	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(thr->heap != NULL);
	heap = thr->heap;
	DUK_ASSERT(heap != NULL);

	/*
	 *  Detect recursive invocation
	 */

	if (DUK_HEAP_HAS_REFZERO_FREE_RUNNING(heap)) {
		DUK_DDDPRINT("refzero free running, skip run");
		return;
	}

	/*
	 *  Churn refzero_list until empty
	 */

	DUK_HEAP_SET_REFZERO_FREE_RUNNING(heap);
	while (heap->refzero_list) {
		duk_hobject *obj;
		int rescued = 0;

		/*
		 *  Pick an object from the head (don't remove yet).
		 */

		h1 = heap->refzero_list;
		obj = (duk_hobject *) h1;
		DUK_DDPRINT("refzero processing %p: %!O", h1, h1);
		DUK_ASSERT(DUK_HEAPHDR_GET_PREV(h1) == NULL);
		DUK_ASSERT(DUK_HEAPHDR_GET_TYPE(h1) == DUK_HTYPE_OBJECT);  /* currently, always the case */

		/*
		 *  Finalizer check.
		 *
		 *  Note: running a finalizer may have arbitrary side effects, e.g.
		 *  queue more objects on refzero_list (tail), or even trigger a
		 *  mark-and-sweep.
		 *
		 *  Note: quick reject check should match vast majority of
		 *  objects and must be safe (not throw any errors, ever).
		 */

		/* FIXME: If object has FINALIZED, it was finalized by mark-and-sweep on
		 * its previous run.  Any point in running finalizer again here?  If
		 * finalization semantics is changed so that finalizer is only run once,
		 * checking for FINALIZED would happen here.
		 */

		if (duk_hobject_hasprop_raw(thr, obj, DUK_HTHREAD_STRING_INT_FINALIZER(thr))) {
			DUK_DDDPRINT("object has a finalizer, run it");

			DUK_ASSERT(h1->h_refcount == 0);
			h1->h_refcount++;  /* bump refcount to prevent refzero during finalizer processing */

			duk_hobject_run_finalizer(thr, obj);  /* must never longjmp */

			h1->h_refcount--;  /* remove artificial bump */
			DUK_ASSERT(h1->h_refcount >= 0);

			if (h1->h_refcount != 0) {
				DUK_DDDPRINT("-> object refcount after finalization non-zero, object will be rescued");
				rescued = 1;
			} else {
				DUK_DDDPRINT("-> object refcount still zero after finalization, object will be freed");
			}
		}

  		/* Refzero head is still the same.  This is the case even if finalizer
		 * inserted more refzero objects; they are inserted to the tail.
		 */
		DUK_ASSERT(h1 == heap->refzero_list);

		/*
		 *  Remove the object from the refzero list.  This cannot be done
		 *  before a possible finalizer has been executed; the finalizer
		 *  may trigger a mark-and-sweep, and mark-and-sweep must be able
		 *  to traverse a complete refzero_list.
		 */

		h2 = DUK_HEAPHDR_GET_NEXT(h1);
		if (h2) {
			DUK_HEAPHDR_SET_PREV(h2, NULL);  /* not strictly necessary */
			heap->refzero_list = h2;
		} else {
			heap->refzero_list = NULL;
			heap->refzero_list_tail = NULL;
		}

		/*
		 *  Rescue or free.
		 */

		if (rescued) {
			/* yes -> move back to heap allocated */
			DUK_DDPRINT("object rescued during refcount finalization: %p", (void *) h1);
			DUK_HEAPHDR_SET_PREV(h1, NULL);
			DUK_HEAPHDR_SET_NEXT(h1, heap->heap_allocated);
			heap->heap_allocated = h1;
		} else {
			/* no -> decref members, then free */
			refcount_finalize_hobject(thr, obj);
			duk_heap_free_heaphdr_raw(heap, h1);
		}

		count++;
	}
	DUK_HEAP_CLEAR_REFZERO_FREE_RUNNING(heap);

	DUK_DDDPRINT("refzero processed %d objects", count);

	/*
	 *  Once the whole refzero cascade has been freed, check for
	 *  a voluntary mark-and-sweep.
	 */

#ifdef DUK_USE_MARK_AND_SWEEP
	heap->mark_and_sweep_trigger_counter -= count;
	if (heap->mark_and_sweep_trigger_counter <= 0) {
		int rc;
		int emergency = 0;
		DUK_DPRINT("refcount triggering mark-and-sweep");
		rc = duk_heap_mark_and_sweep(heap, emergency);
		DUK_UNREF(rc);
		DUK_DPRINT("refcount triggered mark-and-sweep => rc %d", rc);
	}
#endif
}

/*
 *  Incref and decref functions.
 *
 *  Decref may trigger immediate refzero handling, which may free and finalize
 *  an arbitrary number of objects.
 *  
 */

void duk_heap_tval_incref(duk_tval *tv) {
#if 0
	DUK_DDDPRINT("tval incref %p (%d->%d): %!T",
	             (void *) tv,
	             (tv != NULL && DUK_TVAL_IS_HEAP_ALLOCATED(tv) ? DUK_TVAL_GET_HEAPHDR(tv)->h_refcount : 0),
	             (tv != NULL && DUK_TVAL_IS_HEAP_ALLOCATED(tv) ? DUK_TVAL_GET_HEAPHDR(tv)->h_refcount + 1 : 0),
	             tv);
#endif

	if (!tv) {
		return;
	}

	if (DUK_TVAL_IS_HEAP_ALLOCATED(tv)) {
		duk_heaphdr *h = DUK_TVAL_GET_HEAPHDR(tv);
		if (h) {
			DUK_ASSERT(DUK_HEAPHDR_HTYPE_VALID(h));
			DUK_ASSERT(h->h_refcount >= 0);
			h->h_refcount++;
		}
	}
}

void duk_heap_tval_decref(duk_hthread *thr, duk_tval *tv) {
#if 0
	DUK_DDDPRINT("tval decref %p (%d->%d): %!T",
	             (void *) tv,
	             (tv != NULL && DUK_TVAL_IS_HEAP_ALLOCATED(tv) ? DUK_TVAL_GET_HEAPHDR(tv)->h_refcount : 0),
	             (tv != NULL && DUK_TVAL_IS_HEAP_ALLOCATED(tv) ? DUK_TVAL_GET_HEAPHDR(tv)->h_refcount - 1 : 0),
	             tv);
#endif

	if (!tv) {
		return;
	}

	if (DUK_TVAL_IS_HEAP_ALLOCATED(tv)) {
		duk_heap_heaphdr_decref(thr, DUK_TVAL_GET_HEAPHDR(tv));
	}
}

void duk_heap_heaphdr_incref(duk_heaphdr *h) {
#if 0
	DUK_DDDPRINT("heaphdr incref %p (%d->%d): %!O",
	             (void *) h,
	             (h != NULL ? h->h_refcount : 0),
	             (h != NULL ? h->h_refcount + 1 : 0),
	             h);
#endif

	if (!h) {
		return;
	}
	DUK_ASSERT(DUK_HEAPHDR_HTYPE_VALID(h));
	DUK_ASSERT(h->h_refcount >= 0);

	h->h_refcount++;
}

void duk_heap_heaphdr_decref(duk_hthread *thr, duk_heaphdr *h) {
	duk_heap *heap;

#if 0
	DUK_DDDPRINT("heaphdr decref %p (%d->%d): %!O",
	             (void *) h,
	             (h != NULL ? h->h_refcount : 0),
	             (h != NULL ? h->h_refcount - 1 : 0),
	             h);
#endif

	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(thr->heap != NULL);

	if (!h) {
		return;
	}
	DUK_ASSERT(DUK_HEAPHDR_HTYPE_VALID(h));
	DUK_ASSERT(h->h_refcount >= 1);

	if (--h->h_refcount != 0) {
		return;
	}

	heap = thr->heap;
	DUK_DDDPRINT("refzero %p: %!O", (void *) h, h);

#ifdef DUK_USE_MARK_AND_SWEEP
	/*
	 *  If mark-and-sweep is running, don't process 'refzero' situations at all.
	 *  They may happen because mark-and-sweep needs to finalize refcounts for
	 *  each object it sweeps.  Otherwise the target objects of swept objects
	 *  would have incorrect refcounts.
	 *
	 *  Note: mark-and-sweep could use a separate decref handler to avoid coming
	 *  here at all.  However, mark-and-sweep may also call finalizers, which
	 *  can do arbitrary operations and would use this decref variant anyway.
	 */
	if (DUK_HEAP_HAS_MARKANDSWEEP_RUNNING(heap)) {
		DUK_DDDPRINT("refzero handling suppressed when mark-and-sweep running, object: %p", (void *) h);
		return;
	}
#endif

	switch (DUK_HEAPHDR_GET_TYPE(h)) {
	case DUK_HTYPE_STRING:
		/*
		 *  Strings have no internal references but do have "weak"
		 *  references in the string cache.  Also note that strings
		 *  are not on the heap_allocated list like other heap
		 *  elements.
		 */

		duk_heap_strcache_string_remove(heap, (duk_hstring *) h);
		duk_heap_string_remove(heap, (duk_hstring *) h);
		duk_heap_free_heaphdr_raw(heap, h);
		break;

	case DUK_HTYPE_OBJECT:
		/*
		 *  Objects have internal references.  Must finalize through
		 *  the "refzero" work list.
		 */

		duk_heap_remove_any_from_heap_allocated(heap, h);
		queue_refzero(heap, h);
		refzero_free_pending(thr);
		break;

	case DUK_HTYPE_BUFFER:
		/*
		 *  Buffers have no internal references.  However, a dynamic
		 *  buffer has a separate allocation for the buffer.  This is
		 *  freed by duk_heap_free_heaphdr_raw().
		 */

		duk_heap_remove_any_from_heap_allocated(heap, h);
		duk_heap_free_heaphdr_raw(heap, h);
		break;

	default:
		DUK_DPRINT("invalid heap type in decref: %d", (int) DUK_HEAPHDR_GET_TYPE(h));
		DUK_NEVER_HERE();
	}
}

#else

/* no refcounting */

#endif  /* DUK_USE_REFERENCE_COUNTING */

#line 1 "duk_heap_stringcache.c"
/*
 *  String cache.
 *
 *  Provides a cache to optimize indexed string lookups.  The cache keeps
 *  track of (byte offset, char offset) states for a fixed number of strings.
 *  Otherwise we'd need to scan from either end of the string, as we store
 *  strings in (extended) UTF-8.
 */

/* include removed: duk_internal.h */

/*
 *  Delete references to given hstring from the heap string cache.
 *
 *  String cache references are 'weak': they are not counted towards
 *  reference counts, nor serve as roots for mark-and-sweep.  When an
 *  object is about to be freed, such references need to be removed.
 */

void duk_heap_strcache_string_remove(duk_heap *heap, duk_hstring *h) {
	int i;
	for (i = 0; i < DUK_HEAP_STRCACHE_SIZE; i++) {
		duk_strcache *c = &heap->strcache[i];
		if (c->h == h) {
			DUK_DDPRINT("deleting weak strcache reference to hstring %p from heap %p",
			            (void *) h, (void *) heap);
			c->h = NULL;

			/* XXX: the string shouldn't appear twice, but we now loop to the
			 * end anyway; if fixed, add a looping assertion to ensure there
			 * is no duplicate.
			 */
		}
	}
}

/*
 *  String scanning helpers
 */

static duk_u8 *scan_forwards(duk_u8 *p, duk_u8 *q, duk_u32 n) {
	while (n > 0) {
		for (;;) {
			p++;
			if (p >= q) {
				return NULL;
			}
			if ((*p & 0xc0) != 0x80) {
				break;
			}
		}
		n--;
	}
	return p;
}

static duk_u8 *scan_backwards(duk_u8 *p, duk_u8 *q, duk_u32 n) {
	while (n > 0) {
		for (;;) {
			p--;
			if (p < q) {
				return NULL;
			}
			if ((*p & 0xc0) != 0x80) {
				break;
			}
		}
		n--;
	}
	return p;
}

/*
 *  Convert char offset to byte offset
 *
 *  Avoid using the string cache if possible: for ASCII strings byte and
 *  char offsets are equal and for short strings direct scanning may be
 *  better than using the string cache (which may evict a more important
 *  entry).
 */

/* FIXME: typing throughout */

duk_u32 duk_heap_strcache_offset_char2byte(duk_hthread *thr, duk_hstring *h, duk_u32 char_offset) {
	duk_heap *heap;
	duk_strcache *sce;
	duk_u32 byte_offset;
	int i;
	int use_cache;
	duk_u32 dist_start, dist_end, dist_sce;
	duk_u8 *p_start;
	duk_u8 *p_end;
	duk_u8 *p_found;

	if (char_offset > DUK_HSTRING_GET_CHARLEN(h)) {
		goto error;
	}

	/*
	 *  For ASCII strings, the answer is simple.
	 */

	if (DUK_HSTRING_IS_ASCII(h)) {
		/* clen == blen -> pure ascii */
		return char_offset;
	}

	/*
	 *  For non-ASCII strings, we need to scan forwards or backwards
	 *  from some starting point.  The starting point may be the start
	 *  or end of the string, or some cached midpoint in the string
	 *  cache.
	 *
	 *  For "short" strings we simply scan without checking or updating
	 *  the cache.  For longer strings we check and update the cache as
	 *  necessary, inserting a new cache entry if none exists.
	 */

	DUK_DDDPRINT("non-ascii string %p, char_offset=%d, clen=%d, blen=%d",
	             (void *) h, char_offset, DUK_HSTRING_GET_CHARLEN(h),
	             DUK_HSTRING_GET_BYTELEN(h));

	heap = thr->heap;
	sce = NULL;
	use_cache = (DUK_HSTRING_GET_CHARLEN(h) > DUK_HEAP_STRINGCACHE_NOCACHE_LIMIT);

	if (use_cache) {
#ifdef DUK_USE_DDDEBUG
		DUK_DDDPRINT("stringcache before char2byte (using cache):");
		for (i = 0; i < DUK_HEAP_STRCACHE_SIZE; i++) {
			duk_strcache *c = &heap->strcache[i];
			DUK_DDDPRINT("  [%d] -> h=%p, cidx=%d, bidx=%d", i, c->h, c->cidx, c->bidx);
		}
#endif

		for (i = 0; i < DUK_HEAP_STRCACHE_SIZE; i++) {
			duk_strcache *c = &heap->strcache[i];

			if (c->h == h) {
				sce = c;
				break;
			}
		}
	}

	/*
	 *  Scan from shortest distance:
	 *    - start of string
	 *    - end of string
	 *    - cache entry (if exists)
	 */

	DUK_ASSERT(DUK_HSTRING_GET_CHARLEN(h) >= char_offset);
	dist_start = char_offset;
	dist_end = DUK_HSTRING_GET_CHARLEN(h) - char_offset;

	p_start = (duk_u8 *) DUK_HSTRING_GET_DATA(h);
	p_end = (duk_u8 *) (p_start + DUK_HSTRING_GET_BYTELEN(h));
	p_found = NULL;

	if (sce) {
		if (char_offset >= sce->cidx) {
			dist_sce = char_offset - sce->cidx;
			if ((dist_sce <= dist_start) && (dist_sce <= dist_end)) {
				DUK_DDDPRINT("non-ascii string, use_cache=%d, sce=%p:%d:%d, "
				             "dist_start=%d, dist_end=%d, dist_sce=%d => "
				             "scan forwards from sce",
				             use_cache, (sce ? sce->h : NULL), (sce ? sce->cidx : -1),
				             (sce ? sce->bidx : -1), dist_start, dist_end, dist_sce);

				p_found = scan_forwards(p_start + sce->bidx,
				                        p_end,
				                        dist_sce);
				goto scan_done;
			}
		} else {
			dist_sce = sce->cidx - char_offset;
			if ((dist_sce <= dist_start) && (dist_sce <= dist_end)) {
				DUK_DDDPRINT("non-ascii string, use_cache=%d, sce=%p:%d:%d, "
				             "dist_start=%d, dist_end=%d, dist_sce=%d => "
				             "scan backwards from sce",
				             use_cache, (sce ? sce->h : NULL), (sce ? sce->cidx : -1),
				             (sce ? sce->bidx : -1), dist_start, dist_end, dist_sce);

				p_found = scan_backwards(p_start + sce->bidx,
				                         p_start,
				                         dist_sce);
				goto scan_done;
			}
		}
	}

	/* no sce, or sce scan not best */

	if (dist_start <= dist_end) {
		DUK_DDDPRINT("non-ascii string, use_cache=%d, sce=%p:%d:%d, "
		             "dist_start=%d, dist_end=%d, dist_sce=%d => "
		             "scan forwards from string start",
		             use_cache, (sce ? sce->h : NULL), (sce ? sce->cidx : -1),
		             (sce ? sce->bidx : -1), dist_start, dist_end, dist_sce);

		p_found = scan_forwards(p_start,
		                        p_end,
		                        dist_start);
	} else {
		DUK_DDDPRINT("non-ascii string, use_cache=%d, sce=%p:%d:%d, "
		             "dist_start=%d, dist_end=%d, dist_sce=%d => "
		             "scan backwards from string end",
		             use_cache, (sce ? sce->h : NULL), (sce ? sce->cidx : -1),
		             (sce ? sce->bidx : -1), dist_start, dist_end, dist_sce);

		p_found = scan_backwards(p_end,
		                         p_start,
		                         dist_end);
	}

 scan_done:

	if (!p_found) {
		/* Scan error: this shouldn't normally happen; it could happen if
		 * string is not valid UTF-8 data, and clen/blen are not consistent
		 * with the scanning algorithm.
		 */
		goto error;
	}

	DUK_ASSERT(p_found >= p_start);
	DUK_ASSERT(p_found <= p_end);  /* may be equal */
	byte_offset = (duk_u32) (p_found - p_start);

	DUK_DDDPRINT("-> string %p, cidx %d -> bidx %d", (void *) h, char_offset, byte_offset);

	/*
	 *  Update cache entry (allocating if necessary), and move the
	 *  cache entry to the first place (in an "LRU" policy).
	 */
	
	if (use_cache) {
		/* update entry, allocating if necessary */
		if (!sce) {
			sce = heap->strcache + DUK_HEAP_STRCACHE_SIZE - 1;  /* take last entry */
			sce->h = h;
		}
		DUK_ASSERT(sce != NULL);
		sce->bidx = (duk_u32) (p_found - p_start);
		sce->cidx = char_offset;

		/* LRU: move our entry to first */
		if (sce > &heap->strcache[0]) {
			/*
			 *   A                  C
			 *   B                  A
			 *   C <- sce    ==>    B
			 *   D                  D
			 */
			duk_strcache tmp;

			tmp = *sce;
			memmove((void *) (&heap->strcache[1]),
			        (void *) (&heap->strcache[0]),
			        (size_t) (((char *) sce) - ((char *) &heap->strcache[0])));
			heap->strcache[0] = tmp;

			/* 'sce' points to the wrong entry here, but is no longer used */
		}
#ifdef DUK_USE_DDDEBUG
		DUK_DDDPRINT("stringcache after char2byte (using cache):");
		for (i = 0; i < DUK_HEAP_STRCACHE_SIZE; i++) {
			duk_strcache *c = &heap->strcache[i];
			DUK_DDDPRINT("  [%d] -> h=%p, cidx=%d, bidx=%d", i, c->h, c->cidx, c->bidx);
		}
#endif
	}

	return byte_offset;

 error:
	DUK_ERROR(thr, DUK_ERR_INTERNAL_ERROR, "string scan error");
	return 0;
}


#line 1 "duk_heap_stringtable.c"
/*
 *  Heap stringtable handling, string interning.
 */

/* include removed: duk_internal.h */

#define  HASH_INITIAL(hash,h_size)        DUK_STRTAB_HASH_INITIAL((hash),(h_size))
#define  HASH_PROBE_STEP(hash)            DUK_STRTAB_HASH_PROBE_STEP((hash))
#define  DELETED_MARKER(heap)             DUK_STRTAB_DELETED_MARKER((heap))

/*
 *  Create a hstring and insert into the heap.  The created object
 *  is directly garbage collectable with reference count zero.
 *
 *  The caller must place the interned string into the stringtable
 *  immediately (without chance of a longjmp); otherwise the string
 *  is lost.
 */

static duk_hstring *alloc_init_hstring(duk_heap *heap,
                                       duk_u8 *str,
                                       duk_u32 blen,
                                       duk_u32 strhash) {
	duk_hstring *res = NULL;
	duk_u8 *data;
	duk_u32 alloc_size;

	/* NUL terminate for convenient C access */

	alloc_size = sizeof(duk_hstring) + blen + 1;
	res = DUK_ALLOC(heap, alloc_size);
	if (!res) {
		goto error;
	}

	memset(res, 0, sizeof(duk_hstring));
#ifdef DUK_USE_EXPLICIT_NULL_INIT
	DUK_HEAPHDR_STRING_INIT_NULLS(&res->hdr);
#endif
	DUK_HEAPHDR_SET_TYPE_AND_FLAGS(&res->hdr, DUK_HTYPE_STRING, 0);

	if (duk_js_is_arrayindex_raw_string(str, blen)) {
		DUK_HSTRING_SET_ARRIDX(res);
	}

	res->hash = strhash;
	res->blen = blen;
	res->clen = duk_unicode_unvalidated_utf8_length(str, blen);

	data = (duk_u8 *) (res + 1);
	memcpy(data, str, blen);
	data[blen] = (duk_u8) 0;

	DUK_DDDPRINT("interned string, hash=0x%08x, blen=%d, clen=%d, arridx=%d",
	             DUK_HSTRING_GET_HASH(res),
	             DUK_HSTRING_GET_BYTELEN(res),
	             DUK_HSTRING_GET_CHARLEN(res),
	             DUK_HSTRING_HAS_ARRIDX(res) ? 1 : 0);

	return res;

 error:
	DUK_FREE(heap, res);
	return NULL;
}

/*
 *  Count actually used (non-NULL, non-DELETED) entries
 */

static int count_used(duk_heap *heap) {
	int i;
	int res = 0;

	for (i = 0; i < heap->st_size; i++) {
		if (heap->st[i] != NULL && heap->st[i] != DELETED_MARKER(heap)) {
			res++;
		}
	}
	return res;
}

/*
 *  Hashtable lookup and insert helpers
 */

static void insert_hstring(duk_heap *heap, duk_hstring **entries, duk_u32 size, duk_u32 *p_used, duk_hstring *h) {
	duk_u32 i;
	duk_u32 step;

	DUK_ASSERT(size > 0);

	i = HASH_INITIAL(DUK_HSTRING_GET_HASH(h), size);
	step = HASH_PROBE_STEP(DUK_HSTRING_GET_HASH(h)); 
	for (;;) {
		duk_hstring *e;
		
		e = entries[i];
		if (e == NULL) {
			DUK_DDDPRINT("insert hit (null): %d", i);
			entries[i] = h;
			(*p_used)++;
			break;
		} else if (e == DELETED_MARKER(heap)) {
			/* st_used remains the same, DELETED is counted as used */
			DUK_DDDPRINT("insert hit (deleted): %d", i);
			entries[i] = h;
			break;
		}
		DUK_DDDPRINT("insert miss: %d", i);
		i = (i + step) % size;

		/* looping should never happen */
		DUK_ASSERT(i != HASH_INITIAL(DUK_HSTRING_GET_HASH(h), size));
	}
}

static duk_hstring *find_matching_string(duk_heap *heap, duk_hstring **entries, duk_u32 size, duk_u8 *str, duk_u32 blen, duk_u32 strhash) {
	duk_u32 i;
	duk_u32 step;

	DUK_ASSERT(size > 0);

	i = HASH_INITIAL(strhash, size);
	step = HASH_PROBE_STEP(strhash);
	for (;;) {
		duk_hstring *e;

		e = entries[i];
		if (!e) {
			return NULL;
		}
		if (e != DELETED_MARKER(heap) && DUK_HSTRING_GET_BYTELEN(e) == blen) {
			if (memcmp(str, DUK_HSTRING_GET_DATA(e), blen) == 0) {
				DUK_DDDPRINT("find matching hit: %d (step %d, size %d)", i, step, size);
				return e;
			}
		}
		DUK_DDDPRINT("find matching miss: %d (step %d, size %d)", i, step, size);
		i = (i + step) % size;

		/* looping should never happen */
		DUK_ASSERT(i != HASH_INITIAL(strhash, size));
	}
	DUK_NEVER_HERE();
}

static void remove_matching_hstring(duk_heap *heap, duk_hstring **entries, duk_u32 size, duk_hstring *h) {
	duk_u32 i;
	duk_u32 step;

	DUK_ASSERT(size > 0);

	i = HASH_INITIAL(h->hash, size);
	step = HASH_PROBE_STEP(h->hash);
	for (;;) {
		duk_hstring *e;

		e = entries[i];
		if (!e) {
			DUK_NEVER_HERE();
			break;
		}
		if (e == h) {
			/* st_used remains the same, DELETED is counted as used */
			DUK_DDDPRINT("free matching hit: %d", i);
			entries[i] = DELETED_MARKER(heap);
			break;
		}

		DUK_DDDPRINT("free matching miss: %d", i);
		i = (i + step) % size;

		/* looping should never happen */
		DUK_ASSERT(i != HASH_INITIAL(h->hash, size));
	}
}

/*
 *  Hash resizing and resizing policy
 */

static int resize_hash_raw(duk_heap *heap, duk_u32 new_size) {
#ifdef DUK_USE_MARK_AND_SWEEP
	int prev_mark_and_sweep_base_flags;
#endif
#ifdef DUK_USE_DEBUG
	duk_u32 old_used = heap->st_used;
#endif
	duk_u32 old_size = heap->st_size;
	duk_hstring **old_entries = heap->st;
	duk_hstring **new_entries = NULL;
	duk_u32 new_used = 0;
	duk_u32 i;

#ifdef DUK_USE_DEBUG
	DUK_DDDPRINT("attempt to resize stringtable: %d entries, %d bytes, %d used, %d%% load -> %d entries, %d bytes, %d used, %d%% load",
	             (int) old_size, (int) (sizeof(duk_hstring *) * old_size), (int) old_used,
	             (int) (((double) old_used) / ((double) old_size) * 100.0),
	             (int) new_size, (int) (sizeof(duk_hstring *) * new_size), count_used(heap),
	             (int) (((double) count_used(heap)) / ((double) new_size) * 100.0));
#endif

	DUK_ASSERT(new_size > count_used(heap));  /* required for rehash to succeed, equality not that useful */
	DUK_ASSERT(old_entries);
	DUK_ASSERT((heap->mark_and_sweep_base_flags & DUK_MS_FLAG_NO_STRINGTABLE_RESIZE) == 0);

	/*
	 *  The attempt to allocate may cause a GC.  Such a GC must not attempt to resize
	 *  the stringtable (though it can be swept); finalizer execution and object
	 *  compaction must also be postponed to avoid the pressure to add strings to the
	 *  string table.
	 */

#ifdef DUK_USE_MARK_AND_SWEEP
	prev_mark_and_sweep_base_flags = heap->mark_and_sweep_base_flags;
	heap->mark_and_sweep_base_flags |= \
	        DUK_MS_FLAG_NO_STRINGTABLE_RESIZE |  /* avoid recursive call here */
	        DUK_MS_FLAG_NO_FINALIZERS |          /* avoid pressure to add/remove strings */
	        DUK_MS_FLAG_NO_OBJECT_COMPACTION;    /* avoid array abandoning which interns strings */
#endif

	new_entries = (duk_hstring **) DUK_ALLOC(heap, sizeof(duk_hstring *) * new_size);

#ifdef DUK_USE_MARK_AND_SWEEP
	heap->mark_and_sweep_base_flags = prev_mark_and_sweep_base_flags;
#endif

	if (!new_entries) {
		goto error;
	}

#ifdef DUK_USE_EXPLICIT_NULL_INIT
	for (i = 0; i < new_size; i++) {
		new_entries[i] = NULL;
	}
#else
	memset(new_entries, 0, sizeof(duk_hstring *) * new_size);
#endif

	/* Because new_size > count_used(heap), guaranteed to work */
	for (i = 0; i < old_size; i++) {
		duk_hstring *e;

		e = old_entries[i];
		if (e == NULL || e == DELETED_MARKER(heap)) {
			continue;
		}
		/* checking for DELETED_MARKER is not necessary here, but helper does it now */
		insert_hstring(heap, new_entries, new_size, &new_used, e);
	}

#ifdef DUK_USE_DEBUG
	DUK_DPRINT("resized stringtable: %d entries, %d bytes, %d used, %d%% load -> %d entries, %d bytes, %d used, %d%% load",
	           (int) old_size, (int) (sizeof(duk_hstring *) * old_size), (int) old_used,
	           (int) (((double) old_used) / ((double) old_size) * 100.0),
	           (int) new_size, (int) (sizeof(duk_hstring *) * new_size), (int) new_used,
	           (int) (((double) new_used) / ((double) new_size) * 100.0));
#endif

	DUK_FREE(heap, heap->st);
	heap->st = new_entries;
	heap->st_size = new_size;
	heap->st_used = new_used;  /* may be less, since DELETED entries are NULLed by rehash */

	return DUK_ERR_OK;

 error:
	DUK_FREE(heap, new_entries);
	return DUK_ERR_FAIL;
}

static int resize_hash(duk_heap *heap) {
	duk_u32 new_size;
	int ret;

	new_size = count_used(heap);
	if (new_size >= 0x80000000U) {
		new_size = DUK_STRTAB_HIGHEST_32BIT_PRIME;
	} else {
		new_size = duk_util_get_hash_prime(DUK_STRTAB_GROW_ST_SIZE(new_size));
		new_size = duk_util_get_hash_prime(new_size);
	}
	DUK_ASSERT(new_size > 0);

	/* rehash even if old and new sizes are the same to get rid of
	 * DELETED entries.
	*/ 

	ret = resize_hash_raw(heap, new_size);

	return ret;
}

static int recheck_hash_size(duk_heap *heap, duk_u32 new_used) {
	duk_u32 new_free;
	duk_u32 tmp1;
	duk_u32 tmp2;

	DUK_ASSERT(new_used <= heap->st_size);  /* grow by at most one */
	new_free = heap->st_size - new_used;    /* unsigned intentionally */

	/* new_free / size <= 1 / DIV  <=>  new_free <= size / DIV */
	/* new_used / size <= 1 / DIV  <=>  new_used <= size / DIV */

	tmp1 = heap->st_size / DUK_STRTAB_MIN_FREE_DIVISOR;
	tmp2 = heap->st_size / DUK_STRTAB_MIN_USED_DIVISOR;

	if (new_free <= tmp1 || new_used <= tmp2) {
		/* load factor too low or high, count actually used entries and resize */
		return resize_hash(heap);
	} else {
		return DUK_ERR_OK;
	}
}

/*
 *  Raw intern and lookup
 */

static duk_hstring *do_intern(duk_heap *heap, duk_u8 *str, duk_u32 blen, duk_u32 strhash) {
	duk_hstring *res;

	if (recheck_hash_size(heap, heap->st_used + 1)) {
		return NULL;
	}

	res = alloc_init_hstring(heap, str, blen, strhash);
	if (!res) {
		return NULL;
	}

	insert_hstring(heap, heap->st, heap->st_size, &heap->st_used, res);  /* guaranteed to succeed */

	/* Note: hstring is in heap but has refcount zero and is not strongly reachable.
	 * Caller should increase refcount and make the hstring reachable before any
	 * operations which require allocation (and possible gc).
	 */

	return res;
}

static duk_hstring *do_lookup(duk_heap *heap, duk_u8 *str, duk_u32 blen, duk_u32 *out_strhash) {
	duk_hstring *res;

	DUK_ASSERT(out_strhash);

	*out_strhash = duk_heap_hashstring(heap, str, blen);
	res = find_matching_string(heap, heap->st, heap->st_size, str, blen, *out_strhash);
	return res;
}

/*
 *  Exposed calls
 */

duk_hstring *duk_heap_string_lookup(duk_heap *heap, duk_u8 *str, duk_u32 blen) {
	duk_u32 strhash;  /* dummy */
	return do_lookup(heap, str, blen, &strhash);
}

duk_hstring *duk_heap_string_intern(duk_heap *heap, duk_u8 *str, duk_u32 blen) {
	duk_hstring *res;
	duk_u32 strhash;

	res = do_lookup(heap, str, blen, &strhash);
	if (res) {
		return res;
	}

	res = do_intern(heap, str, blen, strhash);
	return res;  /* may be NULL */
}

duk_hstring *duk_heap_string_intern_checked(duk_hthread *thr, duk_u8 *str, duk_u32 blen) {
	duk_hstring *res = duk_heap_string_intern(thr->heap, str, blen);
	if (!res) {
		DUK_ERROR(thr, DUK_ERR_ALLOC_ERROR, "failed to intern string");
	}
	return res;
}

duk_hstring *duk_heap_string_lookup_u32(duk_heap *heap, duk_u32 val) {
	char buf[DUK_STRTAB_U32_MAX_STRLEN+1];
	sprintf(buf, "%u", (unsigned int) val);
	return duk_heap_string_lookup(heap, (duk_u8 *) buf, strlen(buf));
}

duk_hstring *duk_heap_string_intern_u32(duk_heap *heap, duk_u32 val) {
	char buf[DUK_STRTAB_U32_MAX_STRLEN+1];
	sprintf(buf, "%u", (unsigned int) val);
	return duk_heap_string_intern(heap, (duk_u8 *) buf, strlen(buf));
}

duk_hstring *duk_heap_string_intern_u32_checked(duk_hthread *thr, duk_u32 val) {
	duk_hstring *res = duk_heap_string_intern_u32(thr->heap, val);
	if (!res) {
		DUK_ERROR(thr, DUK_ERR_ALLOC_ERROR, "failed to intern string");
	}
	return res;
}

/* find and remove string from stringtable; caller must free the string itself */
void duk_heap_string_remove(duk_heap *heap, duk_hstring *h) {
	DUK_DDDPRINT("remove string from stringtable: %!O", h);
	remove_matching_hstring(heap, heap->st, heap->st_size, h);
}

/* essentially shrink check after gc */
void duk_heap_force_stringtable_resize(duk_heap *heap) {
	/* force resize so that DELETED entries are eliminated */
	resize_hash(heap);
}

/* Undefine local defines */
#undef  HASH_INITIAL
#undef  HASH_PROBE_STEP
#undef  DELETED_MARKER

#line 1 "duk_hobject_alloc.c"
/*
 *  Hobject allocation.
 *
 *  Provides primitive allocation functions for all object types (plain object,
 *  compiled function, native function, thread).  The object return is not yet
 *  in "heap allocated" list and has a refcount of zero, so caller must careful.
 */

/* include removed: duk_internal.h */

static void init_object_parts(duk_heap *heap, duk_hobject *obj, int hobject_flags) {
#ifdef DUK_USE_EXPLICIT_NULL_INIT
	obj->p = NULL;
#endif

	/* FIXME: macro? sets both heaphdr and object flags */
	obj->hdr.h_flags = hobject_flags;
	DUK_HEAPHDR_SET_TYPE(&obj->hdr, DUK_HTYPE_OBJECT);  /* also goes into flags */

        DUK_HEAP_INSERT_INTO_HEAP_ALLOCATED(heap, &obj->hdr);

	/*
	 *  obj->p is intentionally left as NULL, and duk_hobject_props.c must deal
	 *  with this properly.  This is intentional: empty objects consume a minimum
	 *  amount of memory.  Further, an initial allocation might fail and cause
	 *  'obj' to "leak" (require a mark-and-sweep) since it is not reachable yet.
	 */
}

/*
 *  Allocate an duk_hobject.
 *
 *  The allocated object has no allocation for properties; the caller may
 *  want to force a resize if a desired size is known.
 *
 *  The allocated object has zero reference count and is not reachable.
 *  The caller MUST make the object reachable and increase its reference
 *  count before invoking any operation that might require memory allocation.
 */

duk_hobject *duk_hobject_alloc(duk_heap *heap, int hobject_flags) {
	duk_hobject *res;

	DUK_ASSERT(heap != NULL);

	/* different memory layout, alloc size, and init */
	DUK_ASSERT((hobject_flags & DUK_HOBJECT_FLAG_COMPILEDFUNCTION) == 0);
	DUK_ASSERT((hobject_flags & DUK_HOBJECT_FLAG_NATIVEFUNCTION) == 0);
	DUK_ASSERT((hobject_flags & DUK_HOBJECT_FLAG_THREAD) == 0);

	res = DUK_ALLOC(heap, sizeof(duk_hobject));
	if (!res) {
		return NULL;
	}
	memset(res, 0, sizeof(duk_hobject));

	init_object_parts(heap, res, hobject_flags);

	return res;
}

duk_hcompiledfunction *duk_hcompiledfunction_alloc(duk_heap *heap, int hobject_flags) {
	duk_hcompiledfunction *res;

	res = DUK_ALLOC(heap, sizeof(duk_hcompiledfunction));
	if (!res) {
		return NULL;
	}
	memset(res, 0, sizeof(duk_hcompiledfunction));

	init_object_parts(heap, &res->obj, hobject_flags);

#ifdef DUK_USE_EXPLICIT_NULL_INIT
	res->data = NULL;
	res->funcs = NULL;
	res->bytecode = NULL;
#endif

	return res;
}

duk_hnativefunction *duk_hnativefunction_alloc(duk_heap *heap, int hobject_flags) {
	duk_hnativefunction *res;

	res = DUK_ALLOC(heap, sizeof(duk_hnativefunction));
	if (!res) {
		return NULL;
	}
	memset(res, 0, sizeof(duk_hnativefunction));

	init_object_parts(heap, &res->obj, hobject_flags);

#ifdef DUK_USE_EXPLICIT_NULL_INIT
	res->func = NULL;
#endif

	return res;
}

/*
 *  Allocate a new thread.
 *
 *  Leaves the built-ins array uninitialized.  The caller must either
 *  initialize a new global context or share existing built-ins from
 *  another thread.
 */

duk_hthread *duk_hthread_alloc(duk_heap *heap, int hobject_flags) {
	duk_hthread *res;

	res = DUK_ALLOC(heap, sizeof(duk_hthread));
	if (!res) {
		return NULL;
	}
	memset(res, 0, sizeof(duk_hthread));

	init_object_parts(heap, &res->obj, hobject_flags);

#ifdef DUK_USE_EXPLICIT_NULL_INIT
	res->heap = NULL;
	res->valstack = NULL;
	res->valstack_end = NULL;
	res->valstack_bottom = NULL;
	res->valstack_top = NULL;
	res->callstack = NULL;
	res->catchstack = NULL;
	res->resumer = NULL;
	res->strs = NULL;
	{
		int i;
		for (i = 0; i < DUK_NUM_BUILTINS; i++) {
			res->builtins[i] = NULL;
		}
	}
#endif
	/* when nothing is running, API calls are in non-strict mode */
	DUK_ASSERT(res->strict == 0);

	res->heap = heap;
	res->valstack_max = DUK_VALSTACK_DEFAULT_MAX;
	res->callstack_max = DUK_CALLSTACK_DEFAULT_MAX;
	res->catchstack_max = DUK_CATCHSTACK_DEFAULT_MAX;

	return res;
}

/* FIXME: unused now, remove */
duk_hobject *duk_hobject_alloc_checked(duk_hthread *thr, int hobject_flags) {
	duk_hobject *res = duk_hobject_alloc(thr->heap, hobject_flags);
	if (!res) {
		DUK_ERROR(thr, DUK_ERR_ALLOC_ERROR, "failed to allocate an object");
	}
	return res;
}

#line 1 "duk_hobject_class.c"
/*
 *  Hobject Ecmascript [[Class]].
 */

/* include removed: duk_internal.h */

/* Maybe better to check these elsewhere */
#if (DUK_STRIDX_UC_ARGUMENTS > 255)
#error constant too large
#endif
#if (DUK_STRIDX_ARRAY > 255)
#error constant too large
#endif
#if (DUK_STRIDX_UC_BOOLEAN > 255)
#error constant too large
#endif
#if (DUK_STRIDX_DATE > 255)
#error constant too large
#endif
#if (DUK_STRIDX_ERROR > 255)
#error constant too large
#endif
#if (DUK_STRIDX_UC_FUNCTION > 255)
#error constant too large
#endif
#if (DUK_STRIDX_JSON > 255)
#error constant too large
#endif
#if (DUK_STRIDX_MATH > 255)
#error constant too large
#endif
#if (DUK_STRIDX_UC_NUMBER > 255)
#error constant too large
#endif
#if (DUK_STRIDX_UC_OBJECT > 255)
#error constant too large
#endif
#if (DUK_STRIDX_REG_EXP > 255)
#error constant too large
#endif
#if (DUK_STRIDX_UC_STRING > 255)
#error constant too large
#endif
#if (DUK_STRIDX_GLOBAL > 255)
#error constant too large
#endif
#if (DUK_STRIDX_EMPTY_STRING > 255)
#error constant too large
#endif

/* Note: assumes that these string indexes are 8-bit, genstrings.py must ensure that */
duk_u8 duk_class_number_to_stridx[16] = {
	DUK_STRIDX_EMPTY_STRING,  /* UNUSED, intentionally empty */
	DUK_STRIDX_UC_ARGUMENTS,
	DUK_STRIDX_ARRAY,
	DUK_STRIDX_UC_BOOLEAN,
	DUK_STRIDX_DATE,
	DUK_STRIDX_ERROR,
	DUK_STRIDX_UC_FUNCTION,
	DUK_STRIDX_JSON,
	DUK_STRIDX_MATH,
	DUK_STRIDX_UC_NUMBER,
	DUK_STRIDX_UC_OBJECT,
	DUK_STRIDX_REG_EXP,
	DUK_STRIDX_UC_STRING,
	DUK_STRIDX_GLOBAL,
	DUK_STRIDX_EMPTY_STRING,  /* OBJENV, intentionally empty */
	DUK_STRIDX_EMPTY_STRING,  /* DECENV, intentionally empty */
};

#line 1 "duk_hobject_enum.c"
/*
 *  Hobject enumeration support.
 *
 *  Creates an internal enumeration state object to be used e.g. with for-in
 *  enumeration.  The state object contains a snapshot of target object keys
 *  and internal control state for enumeration.  Enumerator flags allow caller
 *  to e.g. request internal/non-enumerable properties, and to enumerate only
 *  "own" properties.
 *
 *  Also creates the result value for e.g. Object.keys() based on the same
 *  internal structure.
 *
 *  This snapshot-based enumeration approach is used to simplify enumeration:
 *  non-snapshot-based approaches are difficult to reconcile with mutating
 *  the enumeration target, running multiple long-lived enumerators at the
 *  same time, garbage collection details, etc.  The downside is that the
 *  enumerator object is memory inefficient especially for iterating arrays.
 */

/* include removed: duk_internal.h */

/* FIXME: identify enumeration target with an object index (not top of stack) */

/* must match exactly the number of internal properties inserted to enumerator */
#define  ENUM_START_INDEX  2

/*
 *  Helper to sort array index keys.  The keys are in the enumeration object
 *  entry part, starting from ENUM_START_INDEX, and the entry part is dense.
 *
 *  We use insertion sort because it is simple (leading to compact code,)
 *  works nicely in-place, and minimizes operations if data is already sorted
 *  or nearly sorted (which is a very common case here).  It also minimizes
 *  the use of element comparisons in general.  This is nice because element
 *  comparisons here involve re-parsing the string keys into numbers each
 *  time, which is naturally very expensive.
 *
 *  Note that the entry part values are all "true", e.g.
 *
 *    "1" -> true, "3" -> true, "2" -> true
 *
 *  so it suffices to only work in the key part without exchanging any keys,
 *  simplifying the sort.
 *
 *  http://en.wikipedia.org/wiki/Insertion_sort
 *
 *  (Compiles to about 160 bytes now as a stand-alone function.)
 */

static void sort_array_indices(duk_hobject *h_obj) {
	duk_hstring **keys;
	duk_hstring **p_curr, **p_insert, **p_end;
	duk_hstring *h_curr;
	duk_u32 val_highest, val_curr, val_insert;

	DUK_ASSERT(h_obj != NULL);
	DUK_ASSERT(h_obj->e_used >= 2);  /* control props */

	if (h_obj->e_used <= 1 + ENUM_START_INDEX) {
		return;
	}

	keys = DUK_HOBJECT_E_GET_KEY_BASE(h_obj);
	p_end = keys + h_obj->e_used;
	keys += ENUM_START_INDEX;

	DUK_DDDPRINT("keys=%p, p_end=%p (after skipping enum props)",
	             (void *) keys, (void *) p_end);

#ifdef DUK_USE_DDDEBUG
	{
		int i;
		for (i = 0; i < h_obj->e_used; i++) {
			DUK_DDDPRINT("initial: %d %p -> %!O",
			             i,
			             (void *) DUK_HOBJECT_E_GET_KEY_PTR(h_obj, i),
			             (void *) DUK_HOBJECT_E_GET_KEY(h_obj, i));
		}
	}
#endif

	val_highest = DUK_HSTRING_GET_ARRIDX_SLOW(keys[0]);
	for (p_curr = keys + 1; p_curr < p_end; p_curr++) {
		DUK_ASSERT(*p_curr != NULL);
		val_curr = DUK_HSTRING_GET_ARRIDX_SLOW(*p_curr);

		if (val_curr >= val_highest) {
			DUK_DDDPRINT("p_curr=%p, p_end=%p, val_highest=%d, val_curr=%d -> "
			             "already in correct order, next",
			             (void *) p_curr, (void *) p_end, (int) val_highest, (int) val_curr);
			val_highest = val_curr;
			continue;
		}

		DUK_DDDPRINT("p_curr=%p, p_end=%p, val_highest=%d, val_curr=%d -> "
		             "needs to be inserted",
		             (void *) p_curr, (void *) p_end, (int) val_highest, (int) val_curr);
	
		/* Needs to be inserted; scan backwards, since we optimize
		 * for the case where elements are nearly in order.
		 */

		p_insert = p_curr - 1;
		for (;;) {
			val_insert = DUK_HSTRING_GET_ARRIDX_SLOW(*p_insert);
			if (val_insert < val_curr) {
				DUK_DDDPRINT("p_insert=%p, val_insert=%d, val_curr=%d -> insert after this",
				             (void *) p_insert, (int) val_insert, (int) val_curr);
				p_insert++;
				break;
			}
			if (p_insert == keys) {
				DUK_DDDPRINT("p_insert=%p -> out of keys, insert to beginning");
				break;
			}
			DUK_DDDPRINT("p_insert=%p, val_insert=%d, val_curr=%d -> search backwards",
			             (void *) p_insert, (int) val_insert, (int) val_curr);
			p_insert--;
		}

		DUK_DDDPRINT("final p_insert=%p", (void *) p_insert);

		/*        .-- p_insert   .-- p_curr
		 *        v              v
		 *  | ... | insert | ... | curr
		 */

		h_curr = *p_curr;
		DUK_DDDPRINT("memmove: dest=%p, src=%p, size=%d, h_curr=%p",
		             (void *) (p_insert + 1), (void *) p_insert,
		             (int) (p_curr - p_insert), (void *) h_curr);

		memmove((void *) (p_insert + 1),
		        (void *) p_insert,
		        (size_t) ((p_curr - p_insert) * sizeof(duk_hstring *)));
		*p_insert = h_curr;
		/* keep val_highest */
	}

#ifdef DUK_USE_DDDEBUG
	{
		int i;
		for (i = 0; i < h_obj->e_used; i++) {
			DUK_DDDPRINT("final: %d %p -> %!O",
			             i,
			             (void *) DUK_HOBJECT_E_GET_KEY_PTR(h_obj, i),
			             (void *) DUK_HOBJECT_E_GET_KEY(h_obj, i));
		}
	}
#endif
}

/*
 *  Create an internal enumerator object E, which has its keys ordered
 *  to match desired enumeration ordering.  Also initialize internal control
 *  properties for enumeration.
 *
 *  Note: if an array was used to hold enumeration keys instead, an array
 *  scan would be needed to eliminate duplicates found in the prototype chain.
 */

void duk_hobject_enumerator_create(duk_context *ctx, int enum_flags) {
	duk_hthread *thr = (duk_hthread *) ctx;
	duk_hobject *target;
	duk_hobject *curr;
	duk_hobject *res;

	DUK_ASSERT(ctx != NULL);

	DUK_DDDPRINT("create enumerator, stack top: %d", duk_get_top(ctx));

	target = duk_require_hobject(ctx, -1);
	DUK_ASSERT(target != NULL);

	duk_push_object_internal(ctx);

	DUK_DDDPRINT("created internal object");

	/* [target res] */

	duk_push_hstring_stridx(ctx, DUK_STRIDX_INT_TARGET);
	duk_push_hobject(ctx, target);
	duk_put_prop(ctx, -3);

	/* initialize index so that we skip internal control keys */
	duk_push_hstring_stridx(ctx, DUK_STRIDX_INT_NEXT);
	duk_push_int(ctx, ENUM_START_INDEX);
	duk_put_prop(ctx, -3);

	curr = target;
	while (curr) {
		duk_u32 i;

		/*
		 *  Virtual properties.
		 *
		 *  String indices are virtual and always enumerable.  String 'length'
		 *  is virtual and non-enumerable.  Array and arguments object props
		 *  have special behavior but are concrete.
		 */

		if (DUK_HOBJECT_HAS_SPECIAL_STRINGOBJ(curr)) {
			duk_hstring *h_val;

			h_val = duk_hobject_get_internal_value_string(thr->heap, curr);
			DUK_ASSERT(h_val != NULL);  /* string objects must not created without internal value */

			/* FIXME: type for 'i' to match string max len (duk_u32) */
			for (i = 0; i < DUK_HSTRING_GET_CHARLEN(h_val); i++) {
				duk_hstring *k;

				k = duk_heap_string_intern_u32_checked(thr, i);
				DUK_ASSERT(k);
				duk_push_hstring(ctx, k);
				duk_push_true(ctx);

				/* [target res key true] */
				duk_put_prop(ctx, -3);

				/* [target res] */	
			}

			/* 'length' property is not enumerable, but is included if
			 * non-enumerable properties are requested.
			 */

			if (enum_flags & DUK_ENUM_INCLUDE_NONENUMERABLE) {
				duk_push_hstring_stridx(ctx, DUK_STRIDX_LENGTH);
				duk_push_true(ctx);
				duk_put_prop(ctx, -3);
			}
		}

		/*
		 *  Array part
		 *
		 *  Note: ordering between array and entry part must match 'abandon array'
		 *  behavior in duk_hobject_props.c: key order after an array is abandoned
		 *  must be the same.
		 */

		for (i = 0; i < curr->a_size; i++) {
			duk_hstring *k;
			duk_tval *tv;

			tv = DUK_HOBJECT_A_GET_VALUE_PTR(curr, i);
			if (DUK_TVAL_IS_UNDEFINED_UNUSED(tv)) {
				continue;
			}
			k = duk_heap_string_intern_u32_checked(thr, i);
			DUK_ASSERT(k);

			duk_push_hstring(ctx, k);
			duk_push_true(ctx);

			/* [target res key true] */
			duk_put_prop(ctx, -3);

			/* [target res] */
		}

		/*
		 *  Entries part
		 */

		for (i = 0; i < curr->e_used; i++) {
			duk_hstring *k;

			k = DUK_HOBJECT_E_GET_KEY(curr, i);
			if (!k) {
				continue;
			}
			if (!DUK_HOBJECT_E_SLOT_IS_ENUMERABLE(curr, i) &&
			    !(enum_flags & DUK_ENUM_INCLUDE_NONENUMERABLE)) {
				continue;
			}
			if (DUK_HSTRING_HAS_INTERNAL(k) &&
			    !(enum_flags & DUK_ENUM_INCLUDE_INTERNAL)) {
				continue;
			}
			if ((enum_flags & DUK_ENUM_ARRAY_INDICES_ONLY) &&
			    (DUK_HSTRING_GET_ARRIDX_SLOW(k) == DUK_HSTRING_NO_ARRAY_INDEX)) {
				continue;
			}

			DUK_ASSERT(DUK_HOBJECT_E_SLOT_IS_ACCESSOR(curr, i) ||
			           !DUK_TVAL_IS_UNDEFINED_UNUSED(&DUK_HOBJECT_E_GET_VALUE_PTR(curr, i)->v));

			duk_push_hstring(ctx, k);
			duk_push_true(ctx);

			/* [target res key true] */
			duk_put_prop(ctx, -3);

			/* [target res] */
		}

		if (enum_flags & DUK_ENUM_OWN_PROPERTIES_ONLY) {
			break;
		}

		curr = curr->prototype;
	}

	/* [target res] */

	duk_remove(ctx, -2);
	res = duk_require_hobject(ctx, -1);

	/* [res] */

	if ((enum_flags & (DUK_ENUM_ARRAY_INDICES_ONLY | DUK_ENUM_SORT_ARRAY_INDICES)) ==
	                  (DUK_ENUM_ARRAY_INDICES_ONLY | DUK_ENUM_SORT_ARRAY_INDICES)) {
		/*
		 *  Some E5/E5.1 algorithms require that array indices are iterated
		 *  in a strictly ascending order.  This is the case for e.g.
		 *  Array.prototype.forEach() and JSON.stringify() PropertyList
		 *  handling.
		 *
		 *  To ensure this property for arrays with an array part (and
		 *  arbitrary objects too, since e.g. forEach() can be applied
		 *  to an array), the caller can request that we sort the keys
		 *  here.
		 */

		/* FIXME: avoid this at least when target is an Array, it has an
		 * array part, and no ancestor properties were included?  Not worth
		 * it for JSON, but maybe worth it for forEach().
		 */

		/* FIXME: may need a 'length' filter for forEach()
		 */
		DUK_DDDPRINT("sort array indices by caller request");
		sort_array_indices(res);
	}

	/* compact; no need to seal because object is internal */
	duk_hobject_compact_props(thr, res);

	DUK_DDDPRINT("created enumerator object: %!iT", duk_get_tval(ctx, -1));
}

/*
 *  Returns non-zero if a key and/or value was enumerated, and:
 *
 *   [enum] -> [key]        (get_value == 0)
 *   [enum] -> [key value]  (get_value == 1)
 *
 *  Returns zero without pushing anything on the stack otherwise.
 */
int duk_hobject_enumerator_next(duk_context *ctx, int get_value) {
	duk_hthread *thr = (duk_hthread *) ctx;
	duk_hobject *e;
	duk_hobject *target;
	duk_hstring *res = NULL;
	duk_u32 idx;

	DUK_ASSERT(ctx != NULL);

	/* [... enum] */

	e = duk_require_hobject(ctx, -1);

	/* FIXME: use get tval ptr, more efficient */
	duk_get_prop_stridx(ctx, -1, DUK_STRIDX_INT_NEXT);
	idx = (duk_u32) duk_require_number(ctx, -1);
	duk_pop(ctx);
	DUK_DDDPRINT("enumeration: index is: %d", idx);

	duk_get_prop_stridx(ctx, -1, DUK_STRIDX_INT_TARGET);
	target = duk_require_hobject(ctx, -1);
	DUK_ASSERT(target != NULL);
	duk_pop(ctx);  /* still reachable */

	DUK_DDDPRINT("getting next enum value, target=%!iO, enumerator=%!iT",
	             target, duk_get_tval(ctx, -1));

	/* no array part */
	for (;;) {
		duk_hstring *k;

		if (idx >= e->e_used) {
			DUK_DDDPRINT("enumeration: ran out of elements");
			break;
		}

		/* we know these because enum objects are internally created */
		k = DUK_HOBJECT_E_GET_KEY(e, idx);
		DUK_ASSERT(k != NULL);
		DUK_ASSERT(!DUK_HOBJECT_E_SLOT_IS_ACCESSOR(e, idx));
		DUK_ASSERT(!DUK_TVAL_IS_UNDEFINED_UNUSED(&DUK_HOBJECT_E_GET_VALUE(e, idx).v));

		idx++;

		/* recheck that the property still exists */
		if (!duk_hobject_hasprop_raw(thr, target, k)) {
			DUK_DDDPRINT("property deleted during enumeration, skip");
			continue;
		}

		DUK_DDDPRINT("enumeration: found element, key: %!O", k);
		res = k;
		break;
	}

	DUK_DDDPRINT("enumeration: updating next index to %d", idx);

	duk_push_number(ctx, (double) idx);
	duk_put_prop_stridx(ctx, -2, DUK_STRIDX_INT_NEXT);

	/* [... enum] */

	if (res) {
		duk_push_hstring(ctx, res);
		if (get_value) {
			duk_push_hobject(ctx, target);
			duk_dup(ctx, -2);      /* -> [... enum key target key] */
			duk_get_prop(ctx, -2); /* -> [... enum key target val] */
			duk_remove(ctx, -2);   /* -> [... enum key val] */
			duk_remove(ctx, -3);   /* -> [... key val] */
		} else {
			duk_remove(ctx, -2);   /* -> [... key] */
		}
		return 1;
	} else {
		duk_pop(ctx);  /* -> [...] */
		return 0;
	}
}

/*
 *  Get enumerated keys in an Ecmascript array.  Matches Object.keys() behavior
 *  described in E5 Section 15.2.3.14.
 */

int duk_hobject_get_enumerated_keys(duk_context *ctx, int enum_flags) {
	duk_hobject *e;
	int i;
	int idx;

	DUK_ASSERT(ctx != NULL);
	DUK_ASSERT(duk_get_hobject(ctx, -1) != NULL);

	/* Create a temporary enumerator to get the (non-duplicated) key list;
	 * the enumerator state is initialized without being needed, but that
	 * has little impact.
	 */

	duk_hobject_enumerator_create(ctx, enum_flags);
	duk_push_array(ctx);

	/* [target enum res] */

	e = duk_require_hobject(ctx, -2);
	DUK_ASSERT(e != NULL);

	idx = 0;
	for (i = ENUM_START_INDEX; i < e->e_used; i++) {
		duk_hstring *k;

		k = DUK_HOBJECT_E_GET_KEY(e, i);
		DUK_ASSERT(k);  /* enumerator must have no keys deleted */

		/* [target enum res] */
		duk_push_hstring(ctx, k);
		duk_put_prop_index(ctx, -2, idx);
		idx++;
	}

	/* [target enum res] */
	duk_remove(ctx, -2);

	/* [target res] */

	return 1;  /* return 1 to allow callers to tail call */
}

#line 1 "duk_hobject_finalizer.c"
/*
 *  Run an duk_hobject finalizer.  Used for both reference counting
 *  and mark-and-sweep algorithms.  Must never throw an error.
 *
 *  There is no return value.  Any return value or error thrown by
 *  the finalizer is ignored (although errors are debug logged).
 *
 *  Notes:
 *
 *    - The thread used for calling the finalizer is the same as the
 *      'thr' argument.  This may need to change later.
 *
 *    - The finalizer thread 'top' assertions are there because it is
 *      critical that strict stack policy is observed (i.e. no cruft
 *      left on the finalizer stack).
 */

/* include removed: duk_internal.h */

static int _finalize_helper(duk_context *ctx) {
	DUK_ASSERT(ctx != NULL);

	DUK_DDDPRINT("protected finalization helper running");

	/* [... obj] */

	duk_get_prop_stridx(ctx, -1, DUK_STRIDX_INT_FINALIZER);  /* -> [... obj finalizer] */
	if (!duk_is_callable(ctx, -1)) {
		DUK_DDDPRINT("-> no finalizer or finalizer not callable");
		return 0;
	}
	duk_dup(ctx, -2);  /* -> [... obj finalizer obj] */
	DUK_DDDPRINT("-> finalizer found, calling finalizer");
	duk_call(ctx, 1);  /* -> [... obj retval] */
	DUK_DDDPRINT("finalizer finished successfully");
	return 0;

	/* Note: we rely on duk_safe_call() to fix up the stack for the caller,
	 * so we don't need to pop stuff here.  There is no return value;
	 * caller determines rescued status based on object refcount.
	 */
}

void duk_hobject_run_finalizer(duk_hthread *thr, duk_hobject *obj) {
	duk_context *ctx = (duk_context *) thr;
	int rc;
#ifdef DUK_USE_ASSERTIONS
	int entry_top;
#endif

	DUK_DDDPRINT("running object finalizer for object: %p", (void *) obj);

	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(ctx != NULL);
	DUK_ASSERT(obj != NULL);

	/* FIXME: assert stack space */

#ifdef DUK_USE_ASSERTIONS
	entry_top = duk_get_top(ctx);
#endif
	/*
	 *  Get and call the finalizer.  All of this must be wrapped
	 *  in a protected call, because even getting the finalizer
	 *  may trigger an error (getter may throw one, for instance).
	 */

	/* FIXME: use a NULL error handler for the finalizer call? */

	DUK_DDDPRINT("-> finalizer found, calling wrapped finalize helper");
	duk_push_hobject(ctx, obj);  /* this also increases refcount by one */
	rc = duk_safe_call(ctx, _finalize_helper, 0 /*nargs*/, 1 /*nrets*/, DUK_INVALID_INDEX);  /* -> [... obj retval/error] */
	DUK_ASSERT_TOP(ctx, entry_top + 2);  /* duk_safe_call discipline */

	if (rc != DUK_ERR_EXEC_SUCCESS) {
		/* Note: we ask for one return value from duk_safe_call to get this
		 * error debugging here.
		 */
		DUK_DPRINT("wrapped finalizer call failed for object %p (ignored); error: %!T",
		           (void *) obj, duk_get_tval(ctx, -1));
	}
	duk_pop_2(ctx);  /* -> [...] */

	DUK_ASSERT_TOP(ctx, entry_top);
}

#line 1 "duk_hobject_misc.c"
/*
 *  Misc support functions
 */

/* include removed: duk_internal.h */

int duk_hobject_prototype_chain_contains(duk_hthread *thr, duk_hobject *h, duk_hobject *p) {
	duk_u32 sanity;

	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(h != NULL);
	/* allow 'p' to be NULL; then the result is always false */

	sanity = DUK_HOBJECT_PROTOTYPE_CHAIN_SANITY;
	do {
		if (h == p) {
			return 1;
		}

		if (sanity-- == 0) {
			DUK_ERROR(thr, DUK_ERR_INTERNAL_ERROR, "prototype chain max depth reached (loop?)");
		}
		h = h->prototype;
	} while(h);

	return 0;
}

/* FIXME: needed? */
void duk_hobject_set_prototype(duk_hthread *thr, duk_hobject *h, duk_hobject *p) {
#ifdef DUK_USE_REFERENCE_COUNTING
	duk_hobject *tmp;

	DUK_ASSERT(h);
	tmp = h->prototype;
	h->prototype = p;
	DUK_HOBJECT_INCREF(thr, p);  /* avoid problems if p == h->prototype */
	DUK_HOBJECT_DECREF(thr, tmp);
#else
	DUK_ASSERT(h);
	h->prototype = p;
#endif
}

#line 1 "duk_hobject_pc2line.c"
/*
 *  Helpers for creating and querying pc2line debug data, which
 *  converts a bytecode program counter to a source line number.
 *
 *  The run-time pc2line data is bit-packed, and documented in:
 *
 *    doc/function-objects.txt
 */

/* include removed: duk_internal.h */

/* Generate pc2line data for an instruction sequence, leaving a buffer on stack top. */
void duk_hobject_pc2line_pack(duk_hthread *thr, duk_compiler_instr *instrs, size_t length) {
	duk_context *ctx = (duk_context *) thr;
	duk_hbuffer_dynamic *h_buf;
	duk_bitencoder_ctx be_ctx_alloc;
	duk_bitencoder_ctx *be_ctx = &be_ctx_alloc;
	duk_u32 *hdr;
	size_t num_header_entries;
	size_t curr_offset;
	size_t new_size;
	int curr_line, next_line, diff_line;
	int curr_pc;
	int hdr_index;

	/* FIXME: add proper spare handling to dynamic buffer, to minimize
	 * reallocs; currently there is no spare at all.
	 */

	num_header_entries = (length + DUK_PC2LINE_SKIP - 1) / DUK_PC2LINE_SKIP;
	curr_offset = sizeof(duk_u32) + num_header_entries * sizeof(duk_u32) * 2;

	duk_push_dynamic_buffer(ctx, curr_offset);
	h_buf = (duk_hbuffer_dynamic *) duk_get_hbuffer(ctx, -1);
	DUK_ASSERT(h_buf != NULL);
	DUK_ASSERT(DUK_HBUFFER_HAS_DYNAMIC(h_buf));

	hdr = (duk_u32 *) DUK_HBUFFER_DYNAMIC_GET_CURR_DATA_PTR(h_buf);
	DUK_ASSERT(hdr != NULL);
	hdr[0] = (duk_u32) length;  /* valid pc range is [0, length[ */

	curr_pc = 0;
	while (curr_pc < length) {
		new_size = curr_offset + DUK_PC2LINE_MAX_DIFF_LENGTH;
		duk_hbuffer_resize(thr, h_buf, new_size, new_size);

		hdr = (duk_u32 *) DUK_HBUFFER_DYNAMIC_GET_CURR_DATA_PTR(h_buf);
		DUK_ASSERT(hdr != NULL);
		DUK_ASSERT(curr_pc < length);
		hdr_index = 1 + (curr_pc / DUK_PC2LINE_SKIP) * 2;
		curr_line = instrs[curr_pc].line;
		hdr[hdr_index + 0] = curr_line;
		hdr[hdr_index + 1] = curr_offset;

#if 0
		DUK_DDDPRINT("hdr[%d]: pc=%d line=%d offset=%d",
		             curr_pc / DUK_PC2LINE_SKIP,
		             curr_pc,
		             (int) hdr[hdr_index + 0],
		             (int) hdr[hdr_index + 1]);
#endif

		memset(be_ctx, 0, sizeof(*be_ctx));
		be_ctx->data = ((unsigned char *) hdr) + curr_offset;
		be_ctx->length = DUK_PC2LINE_MAX_DIFF_LENGTH;

		for (;;) {
			curr_pc++;
			if (curr_pc >= length) {
				break;
			}
			if ((curr_pc % DUK_PC2LINE_SKIP) == 0) {
				break;
			}
			DUK_ASSERT(curr_pc < length);
			next_line = instrs[curr_pc].line;
			diff_line = next_line - curr_line;

#if 0
			DUK_DDDPRINT("curr_line=%d, next_line=%d -> diff_line=%d", curr_line, next_line, diff_line);
#endif

			if (diff_line == 0) {
				/* 0 */
				duk_be_encode(be_ctx, 0, 1);
			} else if (diff_line >= 1 && diff_line <= 4) {
				/* 1 0 <2 bits> */
				duk_be_encode(be_ctx, (0x02 << 2) + (diff_line - 1), 4);
			} else if (diff_line >= -0x80 && diff_line <= 0x7f) {
				/* 1 1 0 <8 bits> */
				DUK_ASSERT(diff_line + 0x80 >= 0 && diff_line + 0x80 <= 0xff);
				duk_be_encode(be_ctx, (0x06 << 8) + (diff_line + 0x80), 11);
			} else {
				/* 1 1 1 <32 bits>
				 * Encode in two parts to avoid bitencode 24-bit limitation
				 */
				duk_be_encode(be_ctx, (0x07 << 16) + ((curr_line >> 16) & 0xffffU), 19);
				duk_be_encode(be_ctx, curr_line & 0xffffU, 16);
			}

			curr_line = next_line;
		}

		duk_be_finish(be_ctx);
		DUK_ASSERT(!be_ctx->truncated);

		/* be_ctx->offset == length of encoded bitstream */
		curr_offset += be_ctx->offset;
	}

	/* compact */
	new_size = curr_offset;
	duk_hbuffer_resize(thr, h_buf, new_size, new_size);

	duk_to_fixed_buffer(ctx, -1);

	DUK_DDDPRINT("final pc2line data: pc_limit=%d, length=%d, %lf bits/opcode --> %!ixT",
	             length, new_size, (double) new_size * 8.0 / (double) length, duk_get_tval(ctx, -1));
}

duk_u32 duk_hobject_pc2line_query(duk_hbuffer_fixed *buf, int pc) {
	duk_bitdecoder_ctx bd_ctx_alloc;
	duk_bitdecoder_ctx *bd_ctx = &bd_ctx_alloc;
	duk_u32 *hdr;
	size_t start_offset;
	size_t pc_limit;
	int hdr_index;
	int pc_base;
	int n;
	int curr_line;

	DUK_ASSERT(buf != NULL);
	DUK_ASSERT(!DUK_HBUFFER_HAS_DYNAMIC(buf));

	hdr_index = pc / DUK_PC2LINE_SKIP;
	pc_base = hdr_index * DUK_PC2LINE_SKIP;
	n = pc - pc_base;

	if (DUK_HBUFFER_GET_SIZE(buf) <= sizeof(duk_u32)) {
		DUK_DDPRINT("pc2line lookup failed: buffer is smaller than minimal header");
		goto error;
	}

	hdr = (duk_u32 *) DUK_HBUFFER_FIXED_GET_DATA_PTR(buf);
	pc_limit = hdr[0];
	if (pc < 0 || pc >= pc_limit) {
		DUK_DDPRINT("pc2line lookup failed: pc out of bounds (pc=%d, limit=%d)", pc, pc_limit);
		goto error;
	}

	curr_line = hdr[1 + hdr_index * 2];
	start_offset = (size_t) hdr[1 + hdr_index * 2 + 1];
	if (start_offset > DUK_HBUFFER_GET_SIZE(buf)) {
		DUK_DDPRINT("pc2line lookup failed: start_offset out of bounds (start_offset=%d, buffer_size=%d)",
		            start_offset, DUK_HBUFFER_GET_SIZE(buf));
		goto error;
	}

	memset(bd_ctx, 0, sizeof(*bd_ctx));
	bd_ctx->data = ((unsigned char *) hdr) + start_offset;
	bd_ctx->length = DUK_HBUFFER_GET_SIZE(buf) - start_offset;

#if 0
	DUK_DDDPRINT("pc2line lookup: pc=%d -> hdr_index=%d, pc_base=%d, n=%d, start_offset=%d", pc, hdr_index, pc_base, n, start_offset);
#endif

	while (n > 0) {
#if 0
		DUK_DDDPRINT("lookup: n=%d, curr_line=%d", n, curr_line);
#endif

		if (duk_bd_decode_flag(bd_ctx)) {
			if (duk_bd_decode_flag(bd_ctx)) {
				if (duk_bd_decode_flag(bd_ctx)) {
					/* 1 1 1 <32 bits> */
					duk_u32 t;
					t = duk_bd_decode(bd_ctx, 16);  /* workaround: max nbits = 24 now */
					t = (t << 16) + duk_bd_decode(bd_ctx, 16);
					curr_line = t;
				} else {
					/* 1 1 0 <8 bits> */
					int t = duk_bd_decode(bd_ctx, 8);
					curr_line += t - 0x80;
				}
			} else {
				/* 1 0 <2 bits> */
				int t = duk_bd_decode(bd_ctx, 2);
				curr_line += t + 1;
			}
		} else {
			/* 0: no change */
		}

		n--;
	}

	DUK_DDDPRINT("pc2line lookup result: pc %d -> line %d", pc, curr_line);
	return curr_line;

 error:
	DUK_DPRINT("pc2line conversion failed for pc=%d", pc);
	return 0;
}

#line 1 "duk_hobject_props.c"
/*
 *  Hobject property set/get functionality.
 *
 *  This is very central functionality for size, performance, and compliance.
 *  It is also rather intricate; see hobject-algorithms.txt for discussion on
 *  the algorithms and memory-management.txt for discussion on refcounts and
 *  side effect issues.
 *
 *  Notes:
 *
 *    - It might be tempting to assert "refcount nonzero" for objects
 *      being operated on, but that's not always correct: objects with
 *      a zero refcount may be operated on by the refcount implementation
 *      (finalization) for instance.  Hence, no refcount assertions are made.
 *
 *    - Many operations (memory allocation, identifier operations, etc)
 *      may cause arbitrary side effects (e.g. through GC and finalization).
 *      These side effects may invalidate duk_tval pointers which point to
 *      areas subject to reallocation (like value stack).  Heap objects
 *      themselves have stable pointers.  Holding heap object pointers or
 *      duk_tval copies is not problematic with respect to side effects;
 *      care must be taken when holding and using argument duk_tval pointers.
 *
 *    - If a finalizer is executed, it may operate on the the same object
 *      we're currently dealing with.  For instance, the finalizer might
 *      delete a certain property which has already been looked up and
 *      confirmed to exist.
 *
 *      FIXME: there is currently no protection against this.  When finalizer
 *      support is finalized, it may be necessary to block finalizer execution
 *      for all GC which happens during a property access.
 *
 *    - The order of operations for a DECREF matters.  When DECREF is executed,
 *      the entire object graph must be consistent; note that a refzero may
 *      lead to a mark-and-sweep through a refcount finalizer.
 *
 *      FIXME: some refcount operations might be more simple with a raw DECREF
 *      operator which did not process the refzero list at all.  Refzero list
 *      could be checked when all refcount adjustments have been made.
 */

/* include removed: duk_internal.h */

/*
 *  Local defines
 */

#define  NO_ARRAY_INDEX             DUK_HSTRING_NO_ARRAY_INDEX

/* hash probe sequence */
#define  HASH_INITIAL(hash,h_size)  DUK_HOBJECT_HASH_INITIAL((hash),(h_size))
#define  HASH_PROBE_STEP(hash)      DUK_HOBJECT_HASH_PROBE_STEP((hash))

/* marker values for hash part */
#define  HASH_UNUSED                DUK_HOBJECT_HASHIDX_UNUSED
#define  HASH_DELETED               DUK_HOBJECT_HASHIDX_DELETED

/* assert value that suffices for all local calls, including recursion of
 * other than Duktape calls (getters etc)
 */
#define  VALSTACK_SPACE             10

/*
 *  Local prototypes
 */

static int check_arguments_map_for_get(duk_hthread *thr, duk_hobject *obj, duk_hstring *key, duk_propdesc *temp_desc);
static void check_arguments_map_for_put(duk_hthread *thr, duk_hobject *obj, duk_hstring *key, duk_propdesc *temp_desc, int throw_flag);
static void check_arguments_map_for_delete(duk_hthread *thr, duk_hobject *obj, duk_hstring *key, duk_propdesc *temp_desc);

static int handle_put_array_length_smaller(duk_hthread *thr, duk_hobject *obj, duk_u32 old_len, duk_u32 new_len, duk_u32 *out_result_len);
static int handle_put_array_length(duk_hthread *thr, duk_hobject *obj);

static int get_property_desc(duk_hthread *thr, duk_hobject *obj, duk_hstring *key, duk_propdesc *out_desc, int push_value);
static int get_own_property_desc_raw(duk_hthread *thr, duk_hobject *obj, duk_hstring *key, duk_u32 arr_idx, duk_propdesc *out_desc, int push_value);
static int get_own_property_desc(duk_hthread *thr, duk_hobject *obj, duk_hstring *key, duk_propdesc *out_desc, int push_value);

/*
 *  Helpers for managing property storage size
 */

/* Get default hash part size for a certain entry part size. */
static duk_u32 get_default_h_size(duk_u32 e_size) {
	DUK_ASSERT(e_size <= DUK_HOBJECT_MAX_PROPERTIES);

	if (e_size >= DUK_HOBJECT_E_USE_HASH_LIMIT) {
		duk_u32 res;

		/* result: hash_prime(floor(1.2 * e_size)) */
		res = duk_util_get_hash_prime(e_size + e_size / DUK_HOBJECT_H_SIZE_DIVISOR);

		/* if fails, e_size will be zero = not an issue, except performance-wise */
		DUK_ASSERT(res == 0 || res > e_size);
		return res;
	} else {
		return 0;
	}
}

/* Get minimum entry part growth for a certain size. */
static duk_u32 get_min_grow_e(duk_u32 e_size) {
	duk_u32 res;

	DUK_ASSERT(e_size <= DUK_HOBJECT_MAX_PROPERTIES);

	res = (e_size + DUK_HOBJECT_E_MIN_GROW_ADD) / DUK_HOBJECT_E_MIN_GROW_DIVISOR;
	DUK_ASSERT(res >= 1);  /* important for callers */
	return res;
}

/* Get minimum array part growth for a certain size. */
static int get_min_grow_a(int a_size) {
	duk_u32 res;

	DUK_ASSERT(a_size <= DUK_HOBJECT_MAX_PROPERTIES);

	res = (a_size + DUK_HOBJECT_A_MIN_GROW_ADD) / DUK_HOBJECT_A_MIN_GROW_DIVISOR;
	DUK_ASSERT(res >= 1);  /* important for callers */
	return res;
}

/* Count actually used entry part entries (non-NULL keys). */
static int count_used_e_keys(duk_hobject *obj) {
	int i;
	int n = 0;
	duk_hstring **e;

	DUK_ASSERT(obj != NULL);

	e = DUK_HOBJECT_E_GET_KEY_BASE(obj);
	for (i = 0; i < obj->e_used; i++) {
		if (*e++) {
			n++;
		}
	}
	return n;
}

/* Count actually used array part entries and array minimum size.
 * NOTE: 'out_min_size' can be computed much faster by starting from the
 * end and breaking out early when finding first used entry, but this is
 * not needed now.
 */
static void compute_a_stats(duk_hobject *obj, duk_u32 *out_used, duk_u32 *out_min_size) {
	unsigned int i;
	duk_u32 used = 0;
	duk_i32 highest_idx = -1;
	duk_tval *a;

	DUK_ASSERT(obj != NULL);
	DUK_ASSERT(out_used != NULL);
	DUK_ASSERT(out_min_size != NULL);

	a = DUK_HOBJECT_A_GET_BASE(obj);
	for (i = 0; i < obj->a_size; i++) {
		duk_tval *tv = a++;
		if (!DUK_TVAL_IS_UNDEFINED_UNUSED(tv)) {
			used++;
			highest_idx = i;
		}
	}

	*out_used = used;
	*out_min_size = highest_idx + 1;  /* 0 if no used entries */
}

/* Check array density and indicate whether or not the array part should be abandoned. */
static int abandon_array_density_check(duk_u32 a_used, duk_u32 a_size) {
	/*
	 *  Array abandon check; abandon if:
	 *
	 *    new_used / new_size < limit
	 *    new_used < limit * new_size        || limit is 3 bits fixed point
	 *    new_used < limit' / 8 * new_size   || *8
	 *    8*new_used < limit' * new_size     || :8
	 *    new_used < limit' * (new_size / 8)
	 *
	 *  Here, new_used = a_used, new_size = a_size.
	 *
	 *  Note: some callers use approximate values for a_used and/or a_size
	 *  (e.g. dropping a '+1' term).  This doesn't affect the usefulness
	 *  of the check, but may confuse debugging.
	 */

	return (a_used < DUK_HOBJECT_A_ABANDON_LIMIT * (a_size >> 3));
}

/* Fast check for extending array: check whether or not a slow density check is required. */
static int abandon_array_slow_check_required(duk_u32 arr_idx, duk_u32 old_size) {
	/*
	 *  In a fast check we assume old_size equals old_used (i.e., existing
	 *  array is fully dense).
	 *
	 *  Slow check if:
	 *
	 *    (new_size - old_size) / old_size > limit
	 *    new_size - old_size > limit * old_size
	 *    new_size > (1 + limit) * old_size        || limit' is 3 bits fixed point
	 *    new_size > (1 + (limit' / 8)) * old_size || * 8
	 *    8 * new_size > (8 + limit') * old_size   || : 8
	 *    new_size > (8 + limit') * (old_size / 8)
	 *    new_size > limit'' * (old_size / 8)      || limit'' = 9 -> max 25% increase
	 *    arr_idx + 1 > limit'' * (old_size / 8)
	 *
	 *  This check doesn't work well for small values, so old_size is rounded
	 *  up for the check (and the '+ 1' of arr_idx can be ignored in practice):
	 *
	 *    arr_idx > limit'' * ((old_size + 7) / 8)
	 */

	return (arr_idx > DUK_HOBJECT_A_FAST_RESIZE_LIMIT * ((old_size + 7) >> 3));
}
	
/*
 *  Reallocate property allocation, moving properties to the new allocation.
 *
 *  Includes key compaction, rehashing, and can also optionally abandoning
 *  the array part, 'migrating' array entries into the beginning of the
 *  new entry part.  Arguments are not validated here, so e.g. new_h_size
 *  MUST be a valid prime.
 *
 *  There is no support for in-place reallocation or just compacting keys
 *  without resizing the property allocation.  This is intentional to keep
 *  code size minimal.
 *
 *  The implementation is relatively straightforward, except for the array
 *  abandonment process.  Array abandonment requires that new string keys
 *  are interned, which may trigger GC.  All keys interned so far must be
 *  reachable for GC at all times; valstack is used for that now.
 *
 *  Also, a GC triggered during this reallocation process must not interfere
 *  with the object being resized.  This is currently controlled by using
 *  heap->mark_and_sweep_base_flags to indicate that no finalizers will be
 *  executed (as they can affect ANY object) and no objects are compacted
 *  (it would suffice to protect this particular object only, though).
 *
 *  Note: a non-checked variant would be nice but is a bit tricky to
 *  implement for the array abandonment process.  It's easy for
 *  everything else.
 *
 *  Note: because we need to potentially resize the valstack (as part
 *  of abandoning the array part), any tval pointers to the valstack
 *  will become invalid after this call.
 */

static void realloc_props(duk_hthread *thr,
                          duk_hobject *obj,
                          duk_u32 new_e_size,
                          duk_u32 new_a_size,
                          duk_u32 new_h_size,
                          int abandon_array) {
	duk_context *ctx = (duk_context *) thr;
#ifdef DUK_USE_MARK_AND_SWEEP
	int prev_mark_and_sweep_base_flags;
#endif
	duk_u32 new_alloc_size;
	duk_u32 new_e_size_adjusted;
	duk_u8 *new_p;
	duk_hstring **new_e_k;
	duk_propvalue *new_e_pv;
	duk_u8 *new_e_f;
	duk_tval *new_a;
	duk_u32 *new_h;
	duk_u32 new_e_used;
	int i;

	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(ctx != NULL);
	DUK_ASSERT(obj != NULL);
	DUK_ASSERT(!abandon_array || new_a_size == 0);  /* if abandon_array, new_a_size must be 0 */
	DUK_ASSERT(obj->p != NULL || (obj->e_size == 0 && obj->a_size == 0));
	DUK_ASSERT(new_h_size == 0 || new_h_size >= new_e_size);  /* required to guarantee success of rehashing,
	                                                           * intentionally use unadjusted new_e_size
	                                                           */	
	ASSERT_VALSTACK_SPACE(thr, VALSTACK_SPACE);

	/*
	 *  Pre resize assertions.
	 */

#ifdef DUK_USE_ASSERTIONS
	/* XXX: pre checks (such as no duplicate keys) */
#endif

	/*
	 *  Tweak e_size to ensure that the whole e part (key + val + flags)
	 *  is a suitable multiple for alignment (platform specific).
	 *
	 *  We could just pad the flags, but that would make the pointer
	 *  computations to access the parts even more arcane.
	 */

#ifdef DUK_USE_HOBJECT_UNALIGNED_LAYOUT
	/* no need to adjust new_e_size */
	new_e_size_adjusted = new_e_size;
#else
	new_e_size_adjusted = (new_e_size + DUK_HOBJECT_ALIGN_TARGET - 1) & (~(DUK_HOBJECT_ALIGN_TARGET - 1));
	DUK_ASSERT(new_e_size_adjusted >= new_e_size);
#endif

	/*
	 *  Debug logging after adjustment.
	 */

	DUK_DDDPRINT("attempt to resize hobject %p props (%d -> %d bytes), from {p=%p,e_size=%d,e_used=%d,a_size=%d,h_size=%d} to "
	             "{e_size=%d,a_size=%d,h_size=%d}, abandon_array=%d, unadjusted new_e_size=%d",
	             (void *) obj,
	             DUK_HOBJECT_P_COMPUTE_SIZE(obj->e_size, obj->a_size, obj->h_size),
	             DUK_HOBJECT_P_COMPUTE_SIZE(new_e_size_adjusted, new_a_size, new_h_size),
	             (void *) obj->p,
	             (int) obj->e_size,
	             (int) obj->e_used,
	             (int) obj->a_size,
	             (int) obj->h_size,
	             (int) new_e_size_adjusted,
	             (int) new_a_size,
	             (int) new_h_size,
	             abandon_array,
	             new_e_size);

	/*
	 *  Property count check.  This is the only point where we ensure that
	 *  we don't get more (allocated) property space that we can handle.
	 *  There aren't hard limits as such, but some algorithms fail (e.g.
	 *  finding next higher prime, selecting hash part size) if we get too
	 *  close to the 4G property limit.
	 *
	 *  Since this works based on allocation size (not actually used size),
	 *  the limit is a bit approximate but good enough in practice.
	 */

	if (new_e_size_adjusted + new_a_size > DUK_HOBJECT_MAX_PROPERTIES) {
		DUK_ERROR(thr, DUK_ERR_ALLOC_ERROR, "object property limit reached");
	}

	/*
	 *  Compute new alloc size and alloc new area.
	 *
	 *  The new area is allocated as a dynamic buffer and placed into the
	 *  valstack for reachability.  The actual buffer is then detached at
	 *  the end.
	 *
	 *  Note: heap_mark_and_sweep_base_flags are altered here to ensure
	 *  no-one touches this object while we're resizing and rehashing it.
	 *  The flags must be reset on every exit path after it.
	 */

	/* FIXME: mark-and-sweep flags will now prevent of all objects; it would
	 * be more ideal if we could still compact other objects in an emergency
	 * GC.
	 */

#ifdef DUK_USE_MARK_AND_SWEEP
	prev_mark_and_sweep_base_flags = thr->heap->mark_and_sweep_base_flags;
	thr->heap->mark_and_sweep_base_flags |=
                DUK_MS_FLAG_NO_FINALIZERS |         /* avoid attempts to add/remove object keys */
	        DUK_MS_FLAG_NO_OBJECT_COMPACTION;   /* avoid attempt to compact the current object */
#endif

	new_alloc_size = DUK_HOBJECT_P_COMPUTE_SIZE(new_e_size_adjusted, new_a_size, new_h_size);
	DUK_DDDPRINT("new hobject allocation size is %d", new_alloc_size);
	if (new_alloc_size == 0) {
		/* for zero size, don't push anything on valstack */
		DUK_ASSERT(new_e_size_adjusted == 0);
		DUK_ASSERT(new_a_size == 0);
		DUK_ASSERT(new_h_size == 0);
		new_p = NULL;
	} else {
		/* This may trigger mark-and-sweep with arbitrary side effects,
		 * including an attempted resize of the object we're resizing,
		 * executing a finalizer which may add or remove properties of
		 * the object we're resizing etc.
		 */

		/* Note: buffer is dynamic so that we can 'steal' the actual
		 * allocation later.
		 */

		new_p = duk_push_dynamic_buffer(ctx, new_alloc_size);  /* errors out if out of memory */
		DUK_ASSERT(new_p != NULL);  /* since new_alloc_size > 0 */
	}

	new_e_k = (duk_hstring **) new_p;
	new_e_pv = (duk_propvalue *) (new_e_k + new_e_size_adjusted);
	new_e_f = (duk_u8 *) (new_e_pv + new_e_size_adjusted);
	new_a = (duk_tval *) (new_e_f + new_e_size_adjusted);
	new_h = (duk_u32 *) (new_a + new_a_size);
	new_e_used = 0;

	/* if new_p == NULL, all of these pointers are NULL */
	DUK_ASSERT((new_p != NULL) ||
	           (new_e_k == NULL && new_e_pv == NULL && new_e_f == NULL &&
	            new_a == NULL && new_h == NULL));

	DUK_DDDPRINT("new alloc size %d, new_e_k=%p, new_e_pv=%p, new_e_f=%p, new_a=%p, new_h=%p",
	             new_alloc_size, (void *) new_e_k, (void *) new_e_pv, (void *) new_e_f,
	             (void *) new_a, (void *) new_h);

	/*
	 *  Migrate array to start of entries if requested.
	 *
	 *  Note: from an enumeration perspective the order of entry keys matters.
	 *  Array keys should appear wherever they appeared before the array abandon
	 *  operation.
	 */

	if (abandon_array) {
		/*
		 *  Note: assuming new_a_size == 0, and that entry part contains
		 *  no conflicting keys, refcounts do not need to be adjusted for
		 *  the values, as they remain exactly the same.
		 *
		 *  The keys, however, need to be interned, incref'd, and be
		 *  reachable for GC.  Any intern attempt may trigger a GC and
		 *  claim any non-reachable strings, so every key must be reachable
		 *  at all times.
		 *
		 *  A longjmp must not occur here, as the new_p allocation would
		 *  be freed without these keys being decref'd, hence the messy
		 *  decref handling if intern fails.
		 */
		DUK_ASSERT(new_a_size == 0);

		for (i = 0; i < obj->a_size; i++) {
			duk_tval *tv1;
			duk_tval *tv2;
			duk_hstring *key;

			DUK_ASSERT(obj->p != NULL);

			tv1 = DUK_HOBJECT_A_GET_VALUE_PTR(obj, i);
			if (DUK_TVAL_IS_UNDEFINED_UNUSED(tv1)) {
				continue;
			}

			DUK_ASSERT(new_p != NULL && new_e_k != NULL &&
			           new_e_pv != NULL && new_e_f != NULL);

			/*
			 *  Intern key via the valstack to ensure reachability behaves
			 *  properly.  We must avoid longjmp's here so use non-checked
			 *  primitives.
			 *
			 *  Note: duk_check_stack() potentially reallocs the valstack,
			 *  invalidating any duk_tval pointers to valstack.  Callers
			 *  must be careful.
			 */

			/* never shrinks; auto-adds DUK_VALSTACK_INTERNAL_EXTRA, which is generous */
			if (!duk_check_stack(ctx, 1)) {
				goto abandon_error;
			}
			ASSERT_VALSTACK_SPACE(thr, 1);
			key = duk_heap_string_intern_u32(thr->heap, i);
			if (!key) {
				goto abandon_error;
			}
			duk_push_hstring(ctx, key);  /* keep key reachable for GC etc; guaranteed not to fail */

			/* key is now reachable in the valstack */

			DUK_HSTRING_INCREF(thr, key);   /* second incref for the entry reference */
			new_e_k[new_e_used] = key;
			tv2 = &new_e_pv[new_e_used].v;  /* array entries are all plain values */
			DUK_TVAL_SET_TVAL(tv2, tv1);
			new_e_f[new_e_used] = DUK_PROPDESC_FLAG_WRITABLE |
			                      DUK_PROPDESC_FLAG_ENUMERABLE |
			                      DUK_PROPDESC_FLAG_CONFIGURABLE;
			new_e_used++;

			/* Note: new_e_used matches pushed temp key count, and nothing can
			 * fail above between the push and this point.
			 */
		}

		DUK_DDDPRINT("abandon array: pop %d key temps from valstack", new_e_used);
		duk_pop_n(ctx, new_e_used);
	}

	/*
	 *  Copy keys and values in the entry part (compacting them at the same time).
	 */

	for (i = 0; i < obj->e_used; i++) {
		duk_hstring *key;

		DUK_ASSERT(obj->p != NULL);

		key = DUK_HOBJECT_E_GET_KEY(obj, i);
		if (!key) {
			continue;
		}

		DUK_ASSERT(new_p != NULL && new_e_k != NULL &&
		           new_e_pv != NULL && new_e_f != NULL);

		new_e_k[new_e_used] = key;
		new_e_pv[new_e_used] = DUK_HOBJECT_E_GET_VALUE(obj, i);
		new_e_f[new_e_used] = DUK_HOBJECT_E_GET_FLAGS(obj, i);
		new_e_used++;
	}
	/* the entries [new_e_used, new_e_size_adjusted[ are left uninitialized on purpose (ok, not gc reachable) */

	/*
	 *  Copy array elements to new array part.
	 */

	if (new_a_size > obj->a_size) {
		/* copy existing entries as is */
		DUK_ASSERT(new_p != NULL && new_a != NULL);
		if (obj->a_size > 0) {
			/* avoid zero copy; if a_size == 0, obj->p might be NULL */
			DUK_ASSERT(obj->p != NULL);
			memcpy((void *) new_a, (void *) DUK_HOBJECT_A_GET_BASE(obj), sizeof(duk_tval) * obj->a_size);
		}

		/* fill new entries with -unused- (required, gc reachable) */
		for (i = obj->a_size; i < new_a_size; i++) {
			duk_tval *tv = &new_a[i];
			DUK_TVAL_SET_UNDEFINED_UNUSED(tv);
		}
	} else {
#ifdef DUK_USE_ASSERTIONS
		/* caller must have decref'd values above new_a_size (if that is necessary) */
		if (!abandon_array) {
			for (i = new_a_size; i < obj->a_size; i++) {
				duk_tval *tv;
				tv = DUK_HOBJECT_A_GET_VALUE_PTR(obj, i);

				/* current assertion is quite strong: decref's and set to unused */
				DUK_ASSERT(DUK_TVAL_IS_UNDEFINED_UNUSED(tv));
			}
		}
#endif
		if (new_a_size > 0) {
			/* avoid zero copy; if new_a_size == obj->a_size == 0, obj->p might be NULL */
			DUK_ASSERT(obj->a_size > 0);
			DUK_ASSERT(obj->p != NULL);
			memcpy((void *) new_a, (void *) DUK_HOBJECT_A_GET_BASE(obj), sizeof(duk_tval) * new_a_size);
		}
	}

	/*
	 *  Rebuild the hash part always from scratch (guaranteed to finish).
	 *
	 *  Any resize of hash part requires rehashing.  In addition, by rehashing
	 *  get rid of any elements marked deleted (HASH_DELETED) which is critical
	 *  to ensuring the hash part never fills up.
	 */

	if (new_h_size > 0) {
		DUK_ASSERT(new_h != NULL);

		/* fill new_h with u32 0xff = UNUSED */
		memset(new_h, 0xff, sizeof(duk_u32) * new_h_size);

		DUK_ASSERT(new_e_used <= new_h_size);  /* equality not actually possible */
		for (i = 0; i < new_e_used; i++) {
			duk_hstring *key = new_e_k[i];
			int j;
			int step;

			DUK_ASSERT(key != NULL);
			j = HASH_INITIAL(DUK_HSTRING_GET_HASH(key), new_h_size);
			step = HASH_PROBE_STEP(DUK_HSTRING_GET_HASH(key));

			for (;;) {
				DUK_ASSERT(new_h[j] != HASH_DELETED);  /* should never happen */
				if (new_h[j] == HASH_UNUSED) {
					DUK_DDDPRINT("rebuild hit %d -> %d", j, i);
					new_h[j] = i;
					break;
				}
				DUK_DDDPRINT("rebuild miss %d, step %d", j, step);
				j = (j + step) % new_h_size;

				/* guaranteed to finish */
				DUK_ASSERT(j != HASH_INITIAL(DUK_HSTRING_GET_HASH(key), new_h_size));
			}
		}
	} else {
		DUK_DDDPRINT("no hash part, no rehash");
	}

	/*
	 *  Nice debug log.
	 */

	DUK_DDPRINT("resized hobject %p props (%d -> %d bytes), from {p=%p,e_size=%d,e_used=%d,a_size=%d,h_size=%d} to "
	            "{p=%p,e_size=%d,e_used=%d,a_size=%d,h_size=%d}, abandon_array=%d, unadjusted new_e_size=%d",
	            (void *) obj,
	            DUK_HOBJECT_P_COMPUTE_SIZE(obj->e_size, obj->a_size, obj->h_size),
	            (int) new_alloc_size,
	            (void *) obj->p,
	            (int) obj->e_size,
	            (int) obj->e_used,
	            (int) obj->a_size,
	            (int) obj->h_size,
	            (void *) new_p,
	            (int) new_e_size_adjusted,
	            (int) new_e_used,
	            (int) new_a_size,
	            (int) new_h_size,
	            abandon_array,
	            new_e_size);

	/*
	 *  All done, switch properties ('p') allocation to new one.
	 */

	DUK_FREE(thr->heap, obj->p);  /* NULL obj->p is OK */
	obj->p = new_p;
	obj->e_size = new_e_size_adjusted;
	obj->e_used = new_e_used;
	obj->a_size = new_a_size;
	obj->h_size = new_h_size;

	if (new_p) {
		/*
		 *  Detach actual buffer from dynamic buffer in valstack, and
		 *  pop it from the stack.
		 *
		 *  XXX: the buffer object is certainly not reachable at this point,
		 *  so it would be nice to free it forcibly even with only
		 *  mark-and-sweep enabled.  Not a big issue though.
		 */
		duk_hbuffer_dynamic *buf;
		DUK_ASSERT(new_alloc_size > 0);
		DUK_ASSERT(duk_is_buffer(ctx, -1));
		buf = (duk_hbuffer_dynamic *) duk_require_hbuffer(ctx, -1);
		DUK_ASSERT(buf != NULL);
		DUK_ASSERT(DUK_HBUFFER_HAS_DYNAMIC(buf));
		buf->curr_alloc = NULL;
		buf->size = 0;  /* these size resets are not strictly necessary, but nice for consistency */
		buf->usable_size = 0;
		duk_pop(ctx);
	} else {
		DUK_ASSERT(new_alloc_size == 0);
		/* no need to pop, nothing was pushed */
	}

	/* clear array part flag only after switching */
	if (abandon_array) {
		DUK_HOBJECT_CLEAR_ARRAY_PART(obj);
	}

	DUK_DDDPRINT("resize result: %!O", obj);

#ifdef DUK_USE_MARK_AND_SWEEP
	thr->heap->mark_and_sweep_base_flags = prev_mark_and_sweep_base_flags;
#endif

	/*
	 *  Post resize assertions.
	 */

#ifdef DUK_USE_ASSERTIONS
	/* XXX: post checks (such as no duplicate keys) */
#endif
	return;

	/*
	 *  Abandon array failed, need to decref keys already inserted
	 *  into the beginning of new_e_k before unwinding valstack.
	 */

 abandon_error:
	DUK_DPRINT("hobject resize failed during abandon array, decref keys");
	i = new_e_used;
	while (i > 0) {
		i--;
		DUK_ASSERT(new_e_k != NULL);
		DUK_ASSERT(new_e_k[i] != NULL);
		DUK_HSTRING_DECREF(thr, new_e_k[i]);
	}

#ifdef DUK_USE_MARK_AND_SWEEP
	thr->heap->mark_and_sweep_base_flags = prev_mark_and_sweep_base_flags;
#endif

	DUK_ERROR(thr, DUK_ERR_ALLOC_ERROR, "object resize failed (alloc/intern error)");
}

/*
 *  Helpers to resize properties allocation on specific needs.
 */

/* Grow entry part allocation for one additional entry. */
static void grow_props_for_new_entry_item(duk_hthread *thr, duk_hobject *obj) {
	duk_u32 new_e_size;
	duk_u32 new_a_size;
	duk_u32 new_h_size;

	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(obj != NULL);

	new_e_size = obj->e_size + get_min_grow_e(obj->e_size);
	new_h_size = get_default_h_size(new_e_size);
	new_a_size = obj->a_size;
	DUK_ASSERT(new_e_size >= obj->e_size + 1);  /* get_min_grow_e() is always >= 1 */

	realloc_props(thr, obj, new_e_size, new_a_size, new_h_size, 0);
}

/* Grow array part for a new highest array index. */
static void grow_props_for_array_item(duk_hthread *thr, duk_hobject *obj, duk_u32 highest_arr_idx) {
	duk_u32 new_e_size;
	duk_u32 new_a_size;
	duk_u32 new_h_size;

	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(obj != NULL);
	DUK_ASSERT(highest_arr_idx >= obj->a_size);

	/* minimum new length is highest_arr_idx + 1 */

	new_e_size = obj->e_size;
	new_h_size = obj->h_size;
	new_a_size = highest_arr_idx + get_min_grow_a(highest_arr_idx);
	DUK_ASSERT(new_a_size >= highest_arr_idx + 1);  /* get_min_grow_a() is always >= 1 */

	realloc_props(thr, obj, new_e_size, new_a_size, new_h_size, 0);
}

/* Abandon array part, moving array entries into entries part.
 * This requires a props resize, which is a heavy operation.
 * We also compact the entries part while we're at it, although
 * this is not strictly required.
 */
static void abandon_array_checked(duk_hthread *thr, duk_hobject *obj) {
	duk_u32 new_e_size;
	duk_u32 new_a_size;
	duk_u32 new_h_size;
	duk_u32 e_used;
	duk_u32 a_used;
	duk_u32 a_size;

	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(obj != NULL);

	e_used = count_used_e_keys(obj);
	compute_a_stats(obj, &a_used, &a_size);

	/*
	 *  Must guarantee all actually used array entries will fit into
	 *  new entry part.  Add one growth step to ensure we don't run out
	 *  of space right away.
	 */

	new_e_size = e_used + a_used;
	new_e_size = new_e_size + get_min_grow_e(new_e_size);
	new_a_size = 0;
	new_h_size = get_default_h_size(new_e_size);

	DUK_DDPRINT("abandon array part for hobject %p, "
	            "array stats before: e_used=%d, a_used=%d, a_size=%d; "
	            "resize to e_size=%d, a_size=%d, h_size=%d",
	            (void *) obj, e_used, a_used, a_size,
	            new_e_size, new_a_size, new_h_size);

	realloc_props(thr, obj, new_e_size, new_a_size, new_h_size, 1);
}

/*
 *  Compact an object.  Minimizes allocation size for objects which are
 *  not likely to be extended.  This is useful for internal and non-
 *  extensible objects, but can also be called for non-extensible objects.
 *  May abandon the array part if it is computed to be too sparse.
 *
 *  This call is relatively expensive, as it needs to scan both the
 *  entries and the array part.
 *
 *  The call may fail due to allocation error.
 */

void duk_hobject_compact_props(duk_hthread *thr, duk_hobject *obj) {
	duk_u32 e_size;       /* currently used -> new size */
	duk_u32 a_size;       /* currently required */
	duk_u32 a_used;       /* actually used */
	duk_u32 h_size;
	int abandon_array;

	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(obj != NULL);

	e_size = count_used_e_keys(obj);
	compute_a_stats(obj, &a_used, &a_size);

	DUK_DDPRINT("compacting hobject, used e keys %d, used a keys %d, min a size %d, "
	            "resized array density would be: %d/%d = %d",
	            e_size, a_used, a_size,
	            a_used, a_size,
	            (double) a_used / (double) a_size);

	if (abandon_array_density_check(a_used, a_size)) {
		DUK_DDPRINT("decided to abandon array during compaction, a_used=%d, a_size=%d",
		            a_used, a_size);
		abandon_array = 1;
		e_size += a_used;
		a_size = 0;
	} else {
		DUK_DDPRINT("decided to keep array during compaction");
		abandon_array = 0;
	}

	if (e_size >= DUK_HOBJECT_E_USE_HASH_LIMIT) {
		h_size = get_default_h_size(e_size);
	} else {
		h_size = 0;
	}

	DUK_DDPRINT("compacting hobject -> new e_size %d, new a_size=%d, new h_size=%d, abandon_array=%d",
	            e_size, a_size, h_size, abandon_array);

	realloc_props(thr, obj, e_size, a_size, h_size, abandon_array);
}

/*
 *  Find an existing key from entry part either by linear scan or by
 *  using the hash index (if it exists).
 *
 *  Sets entry index (and possibly the hash index) to output variables,
 *  which allows the caller to update the entry and hash entries in-place.
 *  If entry is not found, both values are set to -1.  If entry is found
 *  but there is no hash part, h_idx is set to -1.
 */

void duk_hobject_find_existing_entry(duk_hobject *obj, duk_hstring *key, int *e_idx, int *h_idx) {
	DUK_ASSERT(obj != NULL);
	DUK_ASSERT(key != NULL);
	DUK_ASSERT(e_idx != NULL);
	DUK_ASSERT(h_idx != NULL);

	*e_idx = -1;
	*h_idx = -1;

	if (obj->h_size > 0) {
		/* hash lookup */
		int i;
		int n;
		int step;
		duk_u32 *h_base;

		DUK_DDDPRINT("duk_hobject_find_existing_entry() using hash part for lookup");

		h_base = DUK_HOBJECT_H_GET_BASE(obj);
		n = obj->h_size;
		i = HASH_INITIAL(DUK_HSTRING_GET_HASH(key), n);
		step = HASH_PROBE_STEP(DUK_HSTRING_GET_HASH(key));

		for (;;) {
			duk_u32 t;

			DUK_ASSERT(i >= 0 && i < obj->h_size);
			t = h_base[i];
			DUK_ASSERT(t == HASH_UNUSED || t == HASH_DELETED ||
			           (t >= 0 && t < obj->e_size));

			if (t == HASH_UNUSED) {
				break;
			} else if (t == HASH_DELETED) {
				DUK_DDDPRINT("lookup miss (deleted) i=%d, t=%d", i, t);
			} else {
				DUK_ASSERT(t < obj->e_size);
				if (DUK_HOBJECT_E_GET_KEY(obj, t) == key) {
					DUK_DDDPRINT("lookup hit i=%d, t=%d -> key %p", i, t, (void *) key);
					*e_idx = t;
					*h_idx = i;
					return;
				}
				DUK_DDDPRINT("lookup miss i=%d, t=%d", i, t);
			}
			i = (i + step) % n;

			/* guaranteed to finish, as hash is never full */
			DUK_ASSERT(i != HASH_INITIAL(DUK_HSTRING_GET_HASH(key), n));
		}
	} else {
		/* linear scan */
		int i;
		int n;
		duk_hstring **h_keys_base;
		DUK_DDDPRINT("duk_hobject_find_existing_entry() using linear scan for lookup");

		h_keys_base = DUK_HOBJECT_E_GET_KEY_BASE(obj);
		n = obj->e_used;
		for (i = 0; i < n; i++) {
			if (h_keys_base[i] == key) {
				*e_idx = i;
				DUK_ASSERT(*h_idx == -1);
				return;
			}
		}
	}

	/* not found */
	DUK_ASSERT(*e_idx == -1 && *h_idx == -1);
}

/* For internal use: get non-accessor entry value */
duk_tval *duk_hobject_find_existing_entry_tval_ptr(duk_hobject *obj, duk_hstring *key) {
	int e_idx;
	int h_idx;

	DUK_ASSERT(obj != NULL);
	DUK_ASSERT(key != NULL);

	duk_hobject_find_existing_entry(obj, key, &e_idx, &h_idx);
	if (e_idx >= 0) {
		DUK_ASSERT(!DUK_HOBJECT_E_SLOT_IS_ACCESSOR(obj, e_idx));
		return DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(obj, e_idx);
	} else {
		return NULL;
	}
}

/* For internal use: get array part value */
duk_tval *duk_hobject_find_existing_array_entry_tval_ptr(duk_hobject *obj, duk_u32 i) {
	duk_tval *tv;

	DUK_ASSERT(obj != NULL);

	if (!DUK_HOBJECT_HAS_ARRAY_PART(obj)) {
		return NULL;
	}
	if (i >= obj->a_size) {
		return NULL;
	}
	tv = DUK_HOBJECT_A_GET_VALUE_PTR(obj, i);
	return tv;
}

/*
 *  Allocate and initialize a new entry, resizing the properties allocation
 *  if necessary.  Returns entry index (e_idx) or throws an error if alloc fails.
 *
 *  Sets the key of the entry (increasing the key's refcount), and updates
 *  the hash part if it exists.  Caller must set value and flags, and update
 *  the entry value refcount.  A decref for the previous value is not necessary.
 */

static int alloc_entry_checked(duk_hthread *thr, duk_hobject *obj, duk_hstring *key) {
	duk_u32 idx;

	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(obj != NULL);
	DUK_ASSERT(key != NULL);
	DUK_ASSERT(obj->e_used <= obj->e_size);

#ifdef DUK_USE_ASSERTIONS
	/* key must not already exist in entry part */
	{
		int i;
		for (i = 0; i < obj->e_used; i++) {
			DUK_ASSERT(DUK_HOBJECT_E_GET_KEY(obj, i) != key);
		}
	}
#endif

	if (obj->e_used >= obj->e_size) {
		/* only need to guarantee 1 more slot, but allocation growth is in chunks */
		DUK_DDDPRINT("entry part full, allocate space for one more entry");
		grow_props_for_new_entry_item(thr, obj);
	}
	DUK_ASSERT(obj->e_used < obj->e_size);
	idx = obj->e_used++;

	/* previous value is assumed to be garbage, so don't touch it */
	DUK_HOBJECT_E_SET_KEY(obj, idx, key);
	DUK_HSTRING_INCREF(thr, key);

	if (obj->h_size > 0) {
		int i = HASH_INITIAL(DUK_HSTRING_GET_HASH(key), obj->h_size);
		int step = HASH_PROBE_STEP(DUK_HSTRING_GET_HASH(key));
		duk_u32 *h_base = DUK_HOBJECT_H_GET_BASE(obj);

		for (;;) {
			duk_u32 t = h_base[i];
			if (t == HASH_UNUSED || t == HASH_DELETED) {
				DUK_DDDPRINT("alloc_entry_checked() inserted key into hash part, %d -> %d", i, idx);
				DUK_ASSERT(i >= 0 && i < obj->h_size);
				DUK_ASSERT(idx >= 0 && idx < obj->e_size);
				h_base[i] = idx;
				break;
			}
			DUK_DDDPRINT("alloc_entry_checked() miss %d", i);
			i = (i + step) % obj->h_size;

			/* guaranteed to find an empty slot */
			DUK_ASSERT(i != HASH_INITIAL(DUK_HSTRING_GET_HASH(key), obj->h_size));
		}
	}

	/* Note: we could return the hash index here too, but it's not
	 * needed right now.
	 */

	DUK_ASSERT(idx >= 0 && idx < obj->e_size && idx < obj->e_used);
	return idx;
}

/*
 *  Object internal value
 *
 *  Returned value is guaranteed to be reachable / incref'd, caller does not need 
 *  to incref OR decref.
 */

/* FIXME: is this wrapper useful?  just a 'get_own_prop' call and normal stack ops? */

int duk_hobject_get_internal_value(duk_heap *heap, duk_hobject *obj, duk_tval *tv_out) {
	int e_idx;
	int h_idx;

	DUK_ASSERT(heap != NULL);
	DUK_ASSERT(obj != NULL);
	DUK_ASSERT(tv_out != NULL);

	DUK_TVAL_SET_UNDEFINED_UNUSED(tv_out);

	/* always in entry part, no need to look up parents etc */
	duk_hobject_find_existing_entry(obj, DUK_HEAP_STRING_INT_VALUE(heap), &e_idx, &h_idx);
	if (e_idx >= 0) {
		DUK_ASSERT(!DUK_HOBJECT_E_SLOT_IS_ACCESSOR(obj, e_idx));
		DUK_TVAL_SET_TVAL(tv_out, DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(obj, e_idx));
		return 1;
	}
	return 0;
}

duk_hstring *duk_hobject_get_internal_value_string(duk_heap *heap, duk_hobject *obj) {
	duk_tval tv;

	DUK_ASSERT(heap != NULL);
	DUK_ASSERT(obj != NULL);

	if (duk_hobject_get_internal_value(heap, obj, &tv)) {
		duk_hstring *h;
		DUK_ASSERT(DUK_TVAL_IS_STRING(&tv));
		h = DUK_TVAL_GET_STRING(&tv);
		return h;
	}

	return NULL;
}

/*
 *  Arguments handling helpers (argument map mainly).
 *
 *  An arguments object has special behavior for some numeric indices.
 *  Accesses may translate to identifier operations which may have
 *  arbitrary side effects (potentially invalidating any duk_tval
 *  pointers).
 */

/* Lookup 'key' from arguments internal 'map', perform a variable lookup
 * if mapped, and leave the result on top of stack (and return non-zero).
 * Used in E5 Section 10.6 algorithms [[Get]] and [[GetOwnProperty]].
 */
static int lookup_arguments_map(duk_hthread *thr,
                                duk_hobject *obj,
                                duk_hstring *key,
                                duk_propdesc *temp_desc,
                                duk_hobject **out_map,
                                duk_hobject **out_varenv) {
	duk_context *ctx = (duk_context *) thr;
	duk_hobject *map;
	duk_hobject *varenv;
	int rc;

	ASSERT_VALSTACK_SPACE(thr, VALSTACK_SPACE);

	DUK_DDDPRINT("arguments map lookup: thr=%p, obj=%p, key=%p, temp_desc=%p "
	             "(obj -> %!O, key -> %!O)",
	             (void *) thr, (void *) obj, (void *) key, (void *) temp_desc,
	             obj, key);

	if (!get_own_property_desc(thr, obj, DUK_HTHREAD_STRING_INT_MAP(thr), temp_desc, 1)) {  /* push_value = 1 */
		DUK_DDDPRINT("-> no 'map'");
		return 0;
	}

	map = duk_require_hobject(ctx, -1);
	DUK_ASSERT(map != NULL);
	duk_pop(ctx);  /* map is reachable through obj */
	
	if (!get_own_property_desc(thr, map, key, temp_desc, 1)) {  /* push_value = 1 */
		DUK_DDDPRINT("-> 'map' exists, but key not in map");
		return 0;
	}

	/* [... varname] */
	DUK_DDDPRINT("-> 'map' exists, and contains key, key is mapped to argument/variable binding %!T",
	             duk_get_tval(ctx, -1));
	DUK_ASSERT(duk_is_string(ctx, -1));  /* guaranteed when building arguments */

	/* get varenv for varname (callee's declarative lexical environment) */
	rc = get_own_property_desc(thr, obj, DUK_HTHREAD_STRING_INT_VARENV(thr), temp_desc, 1);  /* push_value = 1 */
	DUK_UNREF(rc);
	DUK_ASSERT(rc != 0);  /* arguments MUST have an initialized lexical environment reference */
	varenv = duk_require_hobject(ctx, -1);
	DUK_ASSERT(varenv != NULL);
	duk_pop(ctx);  /* varenv remains reachable through 'obj' */

	DUK_DDDPRINT("arguments varenv is: %!dO", varenv);

	/* success: leave varname in stack */
	*out_map = map;
	*out_varenv = varenv;
	return 1;  /* [... varname] */
}

/* Lookup 'key' from arguments internal 'map', and leave replacement value
 * on stack top if mapped (and return non-zero).
 * Used in E5 Section 10.6 algorithm for [[GetOwnProperty]] (used by [[Get]]).
 */
static int check_arguments_map_for_get(duk_hthread *thr, duk_hobject *obj, duk_hstring *key, duk_propdesc *temp_desc) {
	duk_context *ctx = (duk_context *) thr;
	duk_hobject *map;
	duk_hobject *varenv;
	duk_hstring *varname;

	ASSERT_VALSTACK_SPACE(thr, VALSTACK_SPACE);

	if (!lookup_arguments_map(thr, obj, key, temp_desc, &map, &varenv)) {
		DUK_DDDPRINT("arguments: key not mapped, no special get behavior");
		return 0;
	}

	/* [... varname] */

	varname = duk_require_hstring(ctx, -1);
	DUK_ASSERT(varname != NULL);
	duk_pop(ctx);  /* varname is still reachable */

	DUK_DDDPRINT("arguments object automatic getvar for a bound variable; "
	             "key=%!O, varname=%!O",
	             (duk_heaphdr *) key,
	             (duk_heaphdr *) varname);

	(void) duk_js_getvar_envrec(thr, varenv, varname, 1 /*throw*/);

	/* [... value this_binding] */

	duk_pop(ctx);

	/* leave result on stack top */
	return 1;
}

/* Lookup 'key' from arguments internal 'map', perform a variable write if mapped.
 * Used in E5 Section 10.6 algorithm for [[DefineOwnProperty]] (used by [[Put]]).
 * Assumes stack top contains 'put' value (which is NOT popped).
 */
static void check_arguments_map_for_put(duk_hthread *thr, duk_hobject *obj, duk_hstring *key, duk_propdesc *temp_desc, int throw_flag) {
	duk_context *ctx = (duk_context *) thr;
	duk_hobject *map;
	duk_hobject *varenv;
	duk_hstring *varname;

	ASSERT_VALSTACK_SPACE(thr, VALSTACK_SPACE);

	if (!lookup_arguments_map(thr, obj, key, temp_desc, &map, &varenv)) {
		DUK_DDDPRINT("arguments: key not mapped, no special put behavior");
		return;
	}

	/* [... put_value varname] */

	varname = duk_require_hstring(ctx, -1);
	DUK_ASSERT(varname != NULL);
	duk_pop(ctx);  /* varname is still reachable */

	DUK_DDDPRINT("arguments object automatic putvar for a bound variable; "
	             "key=%!O, varname=%!O, value=%!T",
	             (duk_heaphdr *) key,
	             (duk_heaphdr *) varname,
	             duk_require_tval(ctx, -1));

	/* [... put_value] */

	/*
	 *  Note: although arguments object variable mappings are only established
	 *  for non-strict functions (and a call to a non-strict function created
	 *  the arguments object in question), an inner strict function may be doing
	 *  the actual property write.  Hence the throw_flag applied here comes from
	 *  the property write call.
	 */

	duk_js_putvar_envrec(thr, varenv, varname, duk_require_tval(ctx, -1), throw_flag);

	/* [... put_value] */
}

/* Lookup 'key' from arguments internal 'map', delete mapping if found.
 * Used in E5 Section 10.6 algorithm for [[Delete]].  Note that the
 * variable/argument itself (where the map points) is not deleted.
 */
static void check_arguments_map_for_delete(duk_hthread *thr, duk_hobject *obj, duk_hstring *key, duk_propdesc *temp_desc) {
	duk_context *ctx = (duk_context *) thr;
	duk_hobject *map;

	ASSERT_VALSTACK_SPACE(thr, VALSTACK_SPACE);

	if (!get_own_property_desc(thr, obj, DUK_HTHREAD_STRING_INT_MAP(thr), temp_desc, 1)) {  /* push_value = 1 */
		DUK_DDDPRINT("arguments: key not mapped, no special delete behavior");
		return;
	}

	map = duk_require_hobject(ctx, -1);
	DUK_ASSERT(map != NULL);
	duk_pop(ctx);  /* map is reachable through obj */
	
	DUK_DDDPRINT("-> have 'map', delete key %!O from map (if exists); ignore result", key);

	/* Note: no recursion issue, we can trust 'map' to behave */
	DUK_ASSERT(!DUK_HOBJECT_HAS_SPECIAL_BEHAVIOR(map));
	DUK_DDDPRINT("map before deletion: %!O", map);
	(void) duk_hobject_delprop_raw(thr, map, key, 0);  /* ignore result */
	DUK_DDDPRINT("map after deletion: %!O", map);
}

/*
 *  Ecmascript compliant [[GetOwnProperty]](P), for internal use only.
 *
 *  If property is found:
 *    - Fills descriptor fields to 'out_desc'
 *    - If 'push_value' is non-zero, pushes a value related to the
 *      property onto the stack ('undefined' for accessor properties).
 *    - Returns non-zero
 *
 *  If property is not found:
 *    - 'out_desc' is left in untouched state (possibly garbage)
 *    - Nothing is pushed onto the stack (not even with push_value set)
 *    - Returns zero
 *
 *  Notes:
 *
 *    - Getting a property descriptor may cause an allocation (and hence
 *      GC) to take place, hence reachability and refcount of all related
 *      values matter.  Reallocation of value stack, properties, etc may
 *      invalidate many duk_tval pointers (concretely, those which reside
 *      in memory areas subject to reallocation).  However, heap object
 *      pointers are never affected (heap objects have stable pointers).
 *
 *    - The value of a plain property is always reachable and has a non-zero
 *      reference count.
 *
 *    - The value of a virtual property is not necessarily reachable from
 *      elsewhere and may have a refcount of zero.  Hence we push it onto
 *      the valstack for the caller, which ensures it remains reachable
 *      while it is needed.
 *
 *    - There are no virtual accessor properties.  Hence, all getters and
 *      setters are always related to concretely stored properties, which
 *      ensures that the get/set functions in the resulting descriptor are
 *      reachable and have non-zero refcounts.  Should there be virtual
 *      accessor properties later, this would need to change.
 */

static int get_own_property_desc_raw(duk_hthread *thr, duk_hobject *obj, duk_hstring *key, duk_u32 arr_idx, duk_propdesc *out_desc, int push_value) {
	duk_context *ctx = (duk_context *) thr;
	duk_tval *tv;

	DUK_DDDPRINT("get_own_property_desc: thr=%p, obj=%p, key=%p, out_desc=%p, push_value=%d, arr_idx=%d (obj -> %!O, key -> %!O)",
	             (void *) thr, (void *) obj, (void *) key, (void *) out_desc, push_value, arr_idx,
	             (duk_heaphdr *) obj, (duk_heaphdr *) key);

	DUK_ASSERT(ctx != NULL);
	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(thr->heap != NULL);
	DUK_ASSERT(obj != NULL);
	DUK_ASSERT(key != NULL);
	DUK_ASSERT(out_desc != NULL);
	ASSERT_VALSTACK_SPACE(thr, VALSTACK_SPACE);

	/* FIXME: optimize this filling behavior later */
	out_desc->flags = 0;
	out_desc->get = NULL;
	out_desc->set = NULL;
	out_desc->e_idx = -1;
	out_desc->h_idx = -1;
	out_desc->a_idx = -1;

	/*
	 *  Array part
	 */

	if (DUK_HOBJECT_HAS_ARRAY_PART(obj) && arr_idx != NO_ARRAY_INDEX) {
		if (arr_idx < obj->a_size) {
			tv = DUK_HOBJECT_A_GET_VALUE_PTR(obj, arr_idx);
			if (!DUK_TVAL_IS_UNDEFINED_UNUSED(tv)) {
				DUK_DDDPRINT("-> found in array part");
				if (push_value) {
					duk_push_tval(ctx, tv);
				}
				/* implicit attributes */
				out_desc->flags = DUK_PROPDESC_FLAG_WRITABLE |
				                  DUK_PROPDESC_FLAG_CONFIGURABLE |
				                  DUK_PROPDESC_FLAG_ENUMERABLE;
				out_desc->a_idx = arr_idx;
				goto prop_found;
			}
		}
		/* assume array part is comprehensive (contains all array indexed elements
		 * or none of them); hence no need to check the entries part here.
		 */
		DUK_DDDPRINT("-> not found as a concrete property (has array part, "
		             "should be there if present)");
		goto prop_not_found_concrete;
	}

	/*
	 *  Entries part
	 */

	duk_hobject_find_existing_entry(obj, key, &out_desc->e_idx, &out_desc->h_idx);
	if (out_desc->e_idx >= 0) {
		int e_idx = out_desc->e_idx;
		out_desc->flags = DUK_HOBJECT_E_GET_FLAGS(obj, e_idx);
		if (out_desc->flags & DUK_PROPDESC_FLAG_ACCESSOR) {
			DUK_DDDPRINT("-> found accessor property in entry part");
			out_desc->get = DUK_HOBJECT_E_GET_VALUE_GETTER(obj, e_idx);
			out_desc->set = DUK_HOBJECT_E_GET_VALUE_SETTER(obj, e_idx);
			if (push_value) {
				/* a dummy undefined value is pushed to make valstack
				 * behavior uniform for caller
				 */
				duk_push_undefined(ctx);
			}
		} else {
			DUK_DDDPRINT("-> found plain property in entry part");
			tv = DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(obj, e_idx);
			if (push_value) {
				duk_push_tval(ctx, tv);
			}
		}
		goto prop_found;
	}

	/*
	 *  Not found as a concrete property, check whether a String object
	 *  virtual property matches.
	 */

 prop_not_found_concrete:

	if (DUK_HOBJECT_HAS_SPECIAL_STRINGOBJ(obj)) {
		DUK_DDDPRINT("string object special property get for key: %!O, arr_idx: %d", key, arr_idx);

		if (arr_idx != NO_ARRAY_INDEX) {
			duk_hstring *h_val;

			DUK_DDDPRINT("array index exists");

 			h_val = duk_hobject_get_internal_value_string(thr->heap, obj);
			DUK_ASSERT(h_val);
			if (arr_idx < DUK_HSTRING_GET_CHARLEN(h_val)) {
				DUK_DDDPRINT("-> found, array index inside string");
				if (push_value) {
					duk_push_hstring(ctx, h_val);
					duk_substring(ctx, -1, arr_idx, arr_idx + 1);  /* [str] -> [substr] */
				}
				out_desc->flags = DUK_PROPDESC_FLAG_ENUMERABLE;  /* E5 Section 15.5.5.2 */

				DUK_ASSERT(!DUK_HOBJECT_HAS_SPECIAL_ARGUMENTS(obj));
				return 1;  /* cannot be e.g. arguments special, since special 'traits' are mutually exclusive */
			} else {
				/* index is above internal string length -> property is fully normal */
				DUK_DDDPRINT("array index outside string -> normal property");
			}
		} else if (key == DUK_HTHREAD_STRING_LENGTH(thr)) {
			duk_hstring *h_val;

			DUK_DDDPRINT("-> found, key is 'length', length special behavior");

 			h_val = duk_hobject_get_internal_value_string(thr->heap, obj);
			DUK_ASSERT(h_val != NULL);
			if (push_value) {
				duk_push_number(ctx, (double) DUK_HSTRING_GET_CHARLEN(h_val));
			}
			out_desc->flags = 0;  /* E5 Section 15.5.5.1 */

			DUK_ASSERT(!DUK_HOBJECT_HAS_SPECIAL_ARGUMENTS(obj));
			return 1;  /* cannot be arguments special */
		}
	}

	/* Array properties have special behavior but they are concrete,
	 * so no special handling here.
	 *
	 * Arguments special behavior (E5 Section 10.6, [[GetOwnProperty]]
	 * is only relevant as a post-check implemented below; hence no
	 * check here.
	 */

	/*
	 *  Not found as concrete or virtual
	 */

	DUK_DDDPRINT("-> not found (virtual, entry part, or array part)");
	return 0;

	/*
	 *  Found
	 *
	 *  Arguments object has special post-processing, see E5 Section 10.6,
	 *  description of [[GetOwnProperty]] variant for arguments.
	 */

 prop_found:
	DUK_DDDPRINT("-> property found, checking for arguments special post-behavior");

	/* Notes:
	 *  - only numbered indices are relevant, so arr_idx fast reject is good
	 *    (this is valid unless there are more than 4**32-1 arguments).
	 *  - since variable lookup has no side effects, this can be skipped if
	 *    push_value == 0.
	 */

	if (DUK_HOBJECT_HAS_SPECIAL_ARGUMENTS(obj) &&
	    arr_idx != NO_ARRAY_INDEX &&
	    push_value) {
		duk_propdesc temp_desc;

		/* magically bound variable cannot be an accessor */
		DUK_ASSERT((out_desc->flags & DUK_PROPDESC_FLAG_ACCESSOR) == 0);

		/* replaces top of stack with new value if necessary */
		DUK_ASSERT(push_value != 0);

		if (check_arguments_map_for_get(thr, obj, key, &temp_desc)) {
			DUK_DDDPRINT("-> arguments special behavior overrides result: %!T -> %!T",
			             duk_get_tval(ctx, -2), duk_get_tval(ctx, -1));
			/* [... old_result result] -> [... result] */
			duk_remove(ctx, -2);
		}
	}

	return 1;
}

static int get_own_property_desc(duk_hthread *thr, duk_hobject *obj, duk_hstring *key, duk_propdesc *out_desc, int push_value) {
	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(obj != NULL);
	DUK_ASSERT(key != NULL);
	DUK_ASSERT(out_desc != NULL);
	ASSERT_VALSTACK_SPACE(thr, VALSTACK_SPACE);

	return get_own_property_desc_raw(thr,
	                                 obj,
	                                 key,
	                                 DUK_HSTRING_GET_ARRIDX_SLOW(key),
	                                 out_desc,
	                                 push_value);
}

/*
 *  Ecmascript compliant [[GetProperty]](P), for internal use only.
 *
 *  If property is found:
 *    - Fills descriptor fields to 'out_desc'
 *    - If 'push_value' is non-zero, pushes a value related to the
 *      property onto the stack ('undefined' for accessor properties).
 *    - Returns non-zero
 *
 *  If property is not found:
 *    - 'out_desc' is left in untouched state (possibly garbage)
 *    - Nothing is pushed onto the stack (not even with push_value set)
 *    - Returns zero
 *
 *  May cause arbitrary side effects and invalidate (most) duk_tval
 *  pointers.
 */

static int get_property_desc(duk_hthread *thr, duk_hobject *obj, duk_hstring *key, duk_propdesc *out_desc, int push_value) {
	duk_hobject *curr;
	duk_u32 arr_idx;
	duk_u32 sanity;

	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(thr->heap != NULL);
	DUK_ASSERT(obj != NULL);
	DUK_ASSERT(key != NULL);
	DUK_ASSERT(out_desc != NULL);
	ASSERT_VALSTACK_SPACE(thr, VALSTACK_SPACE);

	arr_idx = DUK_HSTRING_GET_ARRIDX_FAST(key);

	DUK_DDDPRINT("get_property_desc: thr=%p, obj=%p, key=%p, out_desc=%p, push_value=%d, arr_idx=%d (obj -> %!O, key -> %!O)",
	             (void *) thr, (void *) obj, (void *) key, (void *) out_desc, push_value, arr_idx,
	             (duk_heaphdr *) obj, (duk_heaphdr *) key);

	curr = obj;
	DUK_ASSERT(curr != NULL);
	sanity = DUK_HOBJECT_PROTOTYPE_CHAIN_SANITY;
	do {
		if (get_own_property_desc_raw(thr, curr, key, arr_idx, out_desc, push_value)) {
			/* stack contains value, 'out_desc' is set */
			return 1;
		}

		/* not found in 'curr', next in prototype chain; impose max depth */
		if (sanity-- == 0) {
			DUK_ERROR(thr, DUK_ERR_INTERNAL_ERROR, "prototype chain max depth reached (loop?)");
		}
		curr = curr->prototype;
	} while(curr);

	/* out_desc is left untouched (possibly garbage), caller must use return
	 * value to determine whether out_desc can be looked up
	 */

	return 0;
}

/*
 *  Shallow fast path checks for accessing array elements with numeric
 *  indices.  The goal is to try to avoid coercing an array index to an
 *  (interned) string for the most common lookups, in particular, for
 *  standard Array objects.
 *
 *  Interning is avoided but only for a very narrow set of cases:
 *    - Object has array part, index is within array allocation, and
 *      value is not unused (= key exists)
 *    - Object has no interfering special behavior (arguments or
 *      string object special behaviors interfere, array special
 *      behavior does not).
 *
 *  Current shortcoming: if key does not exist (even if it is within
 *  the array allocation range) a slow path lookup with interning is
 *  always required.  This can probably be fixed so that there is a
 *  quick fast path for non-existent elements as well, at least for
 *  standard Array objects.
 */

#if 0  /* XXX: unused now */
static duk_tval *shallow_fast_path_array_check_u32(duk_hobject *obj, duk_u32 key_idx) {
	duk_tval *tv;

	if ((!DUK_HOBJECT_HAS_SPECIAL_ARGUMENTS(obj)) &&
	    (!DUK_HOBJECT_HAS_SPECIAL_STRINGOBJ(obj)) &&
	    (DUK_HOBJECT_HAS_ARRAY_PART(obj)) &&
	    (key_idx < obj->a_size)) {
		/* technically required to check, but obj->a_size check covers this */
		DUK_ASSERT(key_idx != 0xffffffffU);

		DUK_DDDPRINT("fast path attempt (key is an array index, no special "
		             "string/arguments behavior, object has array part, key "
		             "inside array size)"); 

		DUK_ASSERT(obj->a_size > 0);  /* true even for key_idx == 0 */
		tv = DUK_HOBJECT_A_GET_VALUE_PTR(obj, key_idx);
		if (!DUK_TVAL_IS_UNDEFINED_UNUSED(tv)) {
			DUK_DDDPRINT("-> fast path successful");
			return tv;
		}

		/*
		 *  Not found, fall back to slow path.
		 *
		 *  Note: this approach has the unfortunate side effect that accesses
		 *  to undefined entries (or entries outside valid array range) cause
		 *  a string intern operation.
		 */

		DUK_DDDPRINT("fast path attempt failed, fall back to slow path");
	}

	return NULL;
}
#endif

static duk_tval *shallow_fast_path_array_check_tval(duk_hobject *obj, duk_tval *key_tv) {
	duk_tval *tv;

	if (DUK_TVAL_IS_NUMBER(key_tv) &&
	    (!DUK_HOBJECT_HAS_SPECIAL_ARGUMENTS(obj)) &&
	    (!DUK_HOBJECT_HAS_SPECIAL_STRINGOBJ(obj)) &&
	    (DUK_HOBJECT_HAS_ARRAY_PART(obj))) {
		double d;
		duk_u32 idx;

		DUK_DDDPRINT("fast path attempt (key is a number, no special string/arguments "
		             "behavior, object has array part)");

		/* FIXME: faster way to do this index coercion and validation? */

 		d = DUK_TVAL_GET_NUMBER(key_tv);
		idx = (duk_u32) d;
		if ((double) idx == d) {
			/* Note: idx is not necessarily a valid array index (0xffffffffU is not valid) */
			DUK_ASSERT(idx >= 0 && idx <= 0xffffffffU);

			if (idx < obj->a_size) {
				/* technically required to check, but obj->a_size check covers this */
				DUK_ASSERT(idx != 0xffffffffU);

				DUK_DDDPRINT("key is a valid array index and inside array part");
				tv = DUK_HOBJECT_A_GET_VALUE_PTR(obj, idx);
				if (!DUK_TVAL_IS_UNDEFINED_UNUSED(tv)) {
					DUK_DDDPRINT("-> fast path successful");
					return tv;
				}
			} else {
				DUK_DDDPRINT("key is outside array part");
			}
		} else {
			DUK_DDDPRINT("key is not a valid array index");
		}

		/*
		 *  Not found in array part, use slow path.
		 */

		DUK_DDDPRINT("fast path attempt failed, fall back to slow path");
	}

	return NULL;
}

/*
 *  GETPROP: Ecmascript property read.
 */

int duk_hobject_getprop(duk_hthread *thr, duk_tval *tv_obj, duk_tval *tv_key) {
	duk_context *ctx = (duk_context *) thr;
	duk_tval tv_obj_copy;
	duk_tval tv_key_copy;
	duk_hobject *curr = NULL;
	duk_hstring *key = NULL;
	duk_u32 arr_idx = NO_ARRAY_INDEX;
	duk_propdesc desc;
	duk_u32 sanity;

	DUK_DDDPRINT("getprop: thr=%p, obj=%p, key=%p (obj -> %!T, key -> %!T)",
	             (void *) thr, (void *) tv_obj, (void *) tv_key, tv_obj, tv_key);

	DUK_ASSERT(ctx != NULL);
	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(thr->heap != NULL);
	DUK_ASSERT(tv_obj != NULL);
	DUK_ASSERT(tv_key != NULL);

	ASSERT_VALSTACK_SPACE(thr, VALSTACK_SPACE);

	/*
	 *  Make a copy of tv_obj, tv_key, and tv_val to avoid any issues of
	 *  them being invalidated by a valstack resize.
	 *
	 *  FIXME: this is now an overkill for many fast paths.  Rework this
	 *  to be faster (although switching to a valstack discipline might
	 *  be a better solution overall).
	 */

	DUK_TVAL_SET_TVAL(&tv_obj_copy, tv_obj);
	DUK_TVAL_SET_TVAL(&tv_key_copy, tv_key);
	tv_obj = &tv_obj_copy;
	tv_key = &tv_key_copy;

	/*
	 *  Coercion and fast path processing
	 */

	switch (DUK_TVAL_GET_TAG(tv_obj)) {
	case DUK_TAG_UNDEFINED:
	case DUK_TAG_NULL:
	case DUK_TAG_BUFFER:
	case DUK_TAG_POINTER: {
		/* FIXME: add a prototype object for buffers and perhaps for pointers? */

		/* Note: unconditional throw */
		DUK_DDDPRINT("base object is undefined, null, buffer, or pointer -> reject");
		DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, "invalid base reference for property read");
		return 0;
	}

	case DUK_TAG_BOOLEAN: {
		DUK_DDDPRINT("base object is a boolean, start lookup from boolean prototype");
		curr = thr->builtins[DUK_BIDX_BOOLEAN_PROTOTYPE];
		break;
	}

	case DUK_TAG_STRING: {
		duk_hstring *h = DUK_TVAL_GET_STRING(tv_obj);

		if (DUK_TVAL_IS_NUMBER(tv_key)) {
			double t = DUK_TVAL_GET_NUMBER(tv_key);
			duk_u32 idx = (duk_u32) t;

			DUK_DDDPRINT("base object is string, key is number %!T, int value %d", tv_key, (int) idx);

			if ((double) idx == t &&                     /* is whole and >= 0 */
			    idx < DUK_HSTRING_GET_CHARLEN(h)) {      /* and inside string */
				duk_push_hstring(ctx, h);
				duk_substring(ctx, -1, idx, idx + 1);  /* [str] -> [substr] */

				DUK_DDDPRINT("-> %!T (base is a string, key is a whole number "
				             "inside string length -> return char)",
				             duk_get_tval(ctx, -1));
				return 1;
			}
		}

		duk_push_tval(ctx, tv_key);
		duk_to_string(ctx, -1);
		key = duk_get_hstring(ctx, -1);
		arr_idx = DUK_HSTRING_GET_ARRIDX_FAST(key);
		DUK_ASSERT(key != NULL);

		DUK_DDDPRINT("base object string, key is a non-fast-path number; after coercion key is %!T, arr_idx %d", duk_get_tval(ctx, -1), arr_idx);

		if (key == DUK_HTHREAD_STRING_LENGTH(thr)) {
			duk_pop(ctx);  /* [key] -> [] */
			duk_push_number(ctx, (double) DUK_HSTRING_GET_CHARLEN(h));  /* [] -> [res] */

			DUK_DDDPRINT("-> %!T (base is string, key is 'length' after coercion -> "
			             "return string length)",
			             duk_get_tval(ctx, -1));
			return 1;
		}

		if (arr_idx != NO_ARRAY_INDEX &&
		    arr_idx < DUK_HSTRING_GET_CHARLEN(h)) {
			duk_pop(ctx);  /* [key] -> [] */
			duk_push_hstring(ctx, h);
			duk_substring(ctx, -1, arr_idx, arr_idx + 1);  /* [str] -> [substr] */

			DUK_DDDPRINT("-> %!T (base is string, key is an index inside string length "
			             "after coercion -> return char)",
			             duk_get_tval(ctx, -1));
			return 1;
		}

		DUK_DDDPRINT("base object is a string, start lookup from string prototype");
		curr = thr->builtins[DUK_BIDX_STRING_PROTOTYPE];
		goto lookup;  /* avoid double coercion */
	}

	case DUK_TAG_OBJECT: {
		duk_tval *tmp;

		curr = DUK_TVAL_GET_OBJECT(tv_obj);
		DUK_ASSERT(curr != NULL);

		tmp = shallow_fast_path_array_check_tval(curr, tv_key);
		if (tmp) {
			duk_push_tval(ctx, tmp);

			DUK_DDDPRINT("-> %!T (base is object, key is a number, array part "
			             "fast path)",
			             duk_get_tval(ctx, -1));
			return 1;
		}

		if (DUK_HOBJECT_HAS_SPECIAL_ARGUMENTS(curr)) {
			duk_push_tval(ctx, tv_key);
			duk_to_string(ctx, -1);
			key = duk_get_hstring(ctx, -1);
			arr_idx = DUK_HSTRING_GET_ARRIDX_FAST(key);
			DUK_ASSERT(key != NULL);

			if (check_arguments_map_for_get(thr, curr, key, &desc)) {
				DUK_DDDPRINT("-> %!T (base is object with arguments special behavior, "
				             "key matches magically bound property -> skip standard "
				             "Get with replacement value)",
				             duk_get_tval(ctx, -1));

				/* no need for 'caller' post-check, because 'key' must be an array index */

				duk_remove(ctx, -2);  /* [key result] -> [result] */
				return 1;
			}

			goto lookup;  /* avoid double coercion */
		}
		break;
	}

	default: {
		/* number */
		DUK_DDDPRINT("base object is a number, start lookup from number prototype");
		DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv_obj));
		curr = thr->builtins[DUK_BIDX_NUMBER_PROTOTYPE];
		break;
	}
	}

	/* key coercion (unless already coerced above) */
	DUK_ASSERT(key == NULL);
	duk_push_tval(ctx, tv_key);
	duk_to_string(ctx, -1);
	key = duk_get_hstring(ctx, -1);
	arr_idx = DUK_HSTRING_GET_ARRIDX_FAST(key);
	DUK_ASSERT(key != NULL);

	/*
	 *  Property lookup
	 */

 lookup:
	/* [key] (coerced) */
	DUK_ASSERT(curr != NULL);
	DUK_ASSERT(key != NULL);

	sanity = DUK_HOBJECT_PROTOTYPE_CHAIN_SANITY;
	do {
		/* 1 = push_value */
		if (!get_own_property_desc_raw(thr, curr, key, arr_idx, &desc, 1)) {
			goto next_in_chain;
		}

		if (desc.get != NULL) {
			/* accessor with defined getter */
			DUK_ASSERT((desc.flags & DUK_PROPDESC_FLAG_ACCESSOR) != 0);

			duk_pop(ctx);                     /* [key undefined] -> [key] */
			duk_push_hobject(ctx, desc.get);
			duk_push_tval(ctx, tv_obj);       /* note: original, uncoerced base */
			duk_call_method(ctx, 0);          /* [key getter this] -> [key retval] */
		} else {
			/* [key value] or [key undefined] */

			/* data property or accessor without getter */
			DUK_ASSERT(((desc.flags & DUK_PROPDESC_FLAG_ACCESSOR) == 0) ||
			           (desc.get == NULL));

			/* if accessor without getter, return value is undefined */
			DUK_ASSERT(((desc.flags & DUK_PROPDESC_FLAG_ACCESSOR) == 0) ||
			           duk_is_undefined(ctx, -1));

			/* Note: for an accessor without getter, falling through to
			 * check for "caller" special behavior is unnecessary as
			 * "undefined" will never activate the behavior.  But it does
			 * no harm, so we'll do it anyway.
			 */
		}

		goto found;  /* [key result] */

	 next_in_chain:
		if (sanity-- == 0) {
			DUK_ERROR(thr, DUK_ERR_INTERNAL_ERROR, "prototype chain max depth reached (loop?)");
		}
		curr = curr->prototype;
	} while(curr);

	/*
	 *  Not found
	 */

	duk_to_undefined(ctx, -1);  /* [key] -> [undefined] (default value) */

	DUK_DDDPRINT("-> %!T (not found)",
	             duk_get_tval(ctx, -1));
	return 0;

	/*
	 *  Found; post-processing (Function and arguments objects)
	 */

 found:
	/* [key result] */

	if (key == DUK_HTHREAD_STRING_CALLER(thr) &&
	    DUK_TVAL_IS_OBJECT(tv_obj)) {
		duk_hobject *orig = DUK_TVAL_GET_OBJECT(tv_obj);

		if (DUK_HOBJECT_IS_NONBOUND_FUNCTION(orig) ||
		    DUK_HOBJECT_HAS_SPECIAL_ARGUMENTS(orig)) {
			duk_hobject *h;

			/* FIXME: is this behavior desired for bound functions too?
			 * E5.1 Section 15.3.4.5 step 6 seems to indicate so, while
			 * E5.1 Section 15.3.5.4 "NOTE" indicates that bound functions
			 * have a default [[Get]] method.
			 *
			 * Also, must the value Function object be a non-bound function?
			 */

			DUK_ASSERT(!DUK_HOBJECT_HAS_BOUND(orig));

			h = duk_get_hobject(ctx, -1);  /* NULL if not an object */
			if (h &&
			    DUK_HOBJECT_IS_FUNCTION(h) &&
			    DUK_HOBJECT_HAS_STRICT(h)) {
				/* XXX: sufficient to check 'strict', assert for 'is function' */
				DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, "attempt to read 'caller' which is strict");
			}
		}
	}

	duk_remove(ctx, -2);  /* [key result] -> [result] */

	DUK_DDDPRINT("-> %!T (found)",
	             duk_get_tval(ctx, -1));
	return 1;
}

/*
 *  HASPROP: Ecmascript property existence check ("in" operator).
 *
 *  Interestingly, the 'in' operator does not do any coercion of
 *  the target object.
 */

int duk_hobject_hasprop(duk_hthread *thr, duk_tval *tv_obj, duk_tval *tv_key) {
	duk_context *ctx = (duk_context *) thr;
	duk_hobject *obj;
	duk_hstring *key;
	int rc;
	duk_propdesc dummy;

	DUK_DDDPRINT("hasprop: thr=%p, obj=%p, key=%p (obj -> %!T, key -> %!T)",
	             (void *) thr, (void *) tv_obj, (void *) tv_key, tv_obj, tv_key);

	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(thr->heap != NULL);
	DUK_ASSERT(tv_obj != NULL);
	DUK_ASSERT(tv_key != NULL);

	ASSERT_VALSTACK_SPACE(thr, VALSTACK_SPACE);

	/* No need to make a copy of the input duk_tvals here. */

	if (!DUK_TVAL_IS_OBJECT(tv_obj)) {
		/* Note: unconditional throw */
		DUK_DDDPRINT("base object is not an object -> reject");
		DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, "invalid base reference for property existence check");
	}
	obj = DUK_TVAL_GET_OBJECT(tv_obj);
	DUK_ASSERT(obj != NULL);

	duk_push_tval(ctx, tv_key);
	duk_to_string(ctx, -1);
	key = duk_get_hstring(ctx, -1);
	DUK_ASSERT(key != NULL);

	/* XXX: inline into a prototype walking loop? */

	rc = get_property_desc(thr, obj, key, &dummy, 0);  /* push_value = 0 */

	duk_pop(ctx);  /* [key] -> [] */
	return rc;
}

/*
 *  HASPROP variant used internally.
 *
 *  This primitive must never throw an error, caller's rely on this.
 */

int duk_hobject_hasprop_raw(duk_hthread *thr, duk_hobject *obj, duk_hstring *key) {
	duk_propdesc dummy;

	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(thr->heap != NULL);
	DUK_ASSERT(obj != NULL);
	DUK_ASSERT(key != NULL);

	ASSERT_VALSTACK_SPACE(thr, VALSTACK_SPACE);

	return get_property_desc(thr, obj, key, &dummy, 0);  /* push_value = 0 */
}


/*
 *  Helper: handle Array object 'length' write which automatically
 *  deletes properties, see E5 Section 15.4.5.1, step 3.  This is
 *  quite tricky to get right.
 *
 *  Used by duk_hobject_putprop().
 */

static duk_u32 get_old_array_length(duk_hthread *thr, duk_hobject *obj, duk_propdesc *temp_desc) {
	int rc;
	duk_tval *tv;
	duk_u32 res;

	ASSERT_VALSTACK_SPACE(thr, VALSTACK_SPACE);

	/* FIXME: this assumption is actually invalid, because e.g. Array.prototype.push()
	 * can create an array whose length is above 2**32.
	 */

	/* Call only for objects with array special behavior, as we assume
	 * that the length property always exists, and always contains a
	 * valid number value (in unsigned 32-bit range).
	 */

	rc = get_own_property_desc_raw(thr, obj, DUK_HTHREAD_STRING_LENGTH(thr), NO_ARRAY_INDEX, temp_desc, 0);
	DUK_UNREF(rc);
	DUK_ASSERT(rc != 0);  /* arrays MUST have a 'length' property */
	DUK_ASSERT(temp_desc->e_idx >= 0);

	tv = DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(obj, temp_desc->e_idx);
	DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv));  /* array 'length' is always a number, as we coerce it */
	DUK_ASSERT(DUK_TVAL_GET_NUMBER(tv) >= 0.0);
	DUK_ASSERT(DUK_TVAL_GET_NUMBER(tv) <= (double) 0xffffffffU);
	res = (duk_u32) DUK_TVAL_GET_NUMBER(tv);
	DUK_ASSERT((double) res == DUK_TVAL_GET_NUMBER(tv));

	return res;
}

static duk_u32 to_new_array_length_checked(duk_hthread *thr) {
	duk_context *ctx = (duk_context *) thr;
	duk_u32 res;

	/* Input value should be on stack top and will be coerced and
	 * left on stack top.
	 */

	/* FIXME: coerce in_val to new_len, check that this is correct */
	res = ((duk_u32) duk_to_number(ctx, -1)) & 0xffffffffU;
	if (res != duk_get_number(ctx, -1)) {
		DUK_ERROR(thr, DUK_ERR_RANGE_ERROR, "invalid array length");
	}
	return res;
}

/* Delete elements required by a smaller length, taking into account
 * potentially non-configurable elements.  Returns non-zero if all
 * elements could be deleted, and zero if all or some elements could
 * not be deleted.  Also writes final "target length" to 'out_result_len'.
 * This is the length value that should go into the 'length' property
 * (must be set by the caller).  Never throws an error.
 */
static int handle_put_array_length_smaller(duk_hthread *thr,
                                           duk_hobject *obj,
                                           duk_u32 old_len,
                                           duk_u32 new_len,
                                           duk_u32 *out_result_len) {
	duk_u32 target_len;
	duk_u32 i;
	duk_u32 arr_idx;
	duk_hstring *key;
	duk_tval *tv;
	duk_tval tv_tmp;
	int rc;

	DUK_DDDPRINT("new array length smaller than old (%d -> %d), "
	             "probably need to remove elements",
	             old_len, new_len);

	/*
	 *  New length is smaller than old length, need to delete properties above
	 *  the new length.
	 *
	 *  If array part exists, this is straightforward: array entries cannot
	 *  be non-configurable so this is guaranteed to work.
	 *
	 *  If array part does not exist, array-indexed values are scattered
	 *  in the entry part, and some may not be configurable (preventing length
	 *  from becoming lower than their index + 1).  To handle the algorithm
	 *  in E5 Section 15.4.5.1, step l correctly, we scan the entire property
	 *  set twice.
	 */

	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(obj != NULL);
	DUK_ASSERT(new_len < old_len);
	DUK_ASSERT(out_result_len != NULL);
	ASSERT_VALSTACK_SPACE(thr, VALSTACK_SPACE);

	if (DUK_HOBJECT_HAS_ARRAY_PART(obj)) {
		/*
		 *  All defined array-indexed properties are in the array part
		 *  (we assume the array part is comprehensive), and all array
		 *  entries are writable, configurable, and enumerable.  Thus,
		 *  nothing can prevent array entries from being deleted.
		 */

		DUK_DDDPRINT("have array part, easy case");

		if (old_len < obj->a_size) {
			/* XXX: assertion that entries >= old_len are already unused */
			i = old_len;
		} else {
			i = obj->a_size;
		}
		DUK_ASSERT(i <= obj->a_size);

		while (i > new_len) {
			i--;
			tv = DUK_HOBJECT_A_GET_VALUE_PTR(obj, i);
			DUK_TVAL_SET_TVAL(&tv_tmp, tv);
			DUK_TVAL_SET_UNDEFINED_UNUSED(tv);
			DUK_TVAL_DECREF(thr, &tv_tmp);
		}

		*out_result_len = new_len;
		return 1;
	} else {
		/*
		 *  Entries part is a bit more complex
		 */

		/* stage 1: find highest preventing non-configurable entry (if any) */

		DUK_DDDPRINT("no array part, slow case");

		DUK_DDDPRINT("array length write, no array part, stage 1: find target_len "
		             "(highest preventing non-configurable entry (if any))");

		target_len = new_len;
		for (i = 0; i < obj->e_used; i++) {
			key = DUK_HOBJECT_E_GET_KEY(obj, i);
			if (!key) {
				DUK_DDDPRINT("skip entry index %d: null key", i);
				continue;
			}
			if (!DUK_HSTRING_HAS_ARRIDX(key)) {
				DUK_DDDPRINT("skip entry index %d: key not an array index", i);
				continue;
			}

			DUK_ASSERT(DUK_HSTRING_HAS_ARRIDX(key));  /* XXX: macro checks for array index flag, which is unnecessary here */
			arr_idx = DUK_HSTRING_GET_ARRIDX_SLOW(key);
			DUK_ASSERT(arr_idx != NO_ARRAY_INDEX);
			DUK_ASSERT(arr_idx < old_len);  /* consistency requires this */

			if (arr_idx < new_len) {
				DUK_DDDPRINT("skip entry index %d: key is array index %d, below new_len", i, arr_idx);
				continue;
			}
			if (DUK_HOBJECT_E_SLOT_IS_CONFIGURABLE(obj, i)) {
				DUK_DDDPRINT("skip entry index %d: key is a relevant array index %d, but configurable", i, arr_idx);
				continue;
			}

			/* relevant array index is non-configurable, blocks write */
			if (arr_idx >= target_len) {
				DUK_DDDPRINT("entry at index %d has arr_idx %d, is not configurable, "
				             "update target_len %d -> %d",
				             i, arr_idx, target_len, arr_idx + 1);
				target_len = arr_idx + 1;
			}
		}

		/* stage 2: delete configurable entries above target length */

		DUK_DDDPRINT("old_len=%d, new_len=%d, target_len=%d",
		             old_len, new_len, target_len);

		DUK_DDDPRINT("array length write, no array part, stage 2: remove "
		             "entries >= target_len");

		for (i = 0; i < obj->e_used; i++) {
			key = DUK_HOBJECT_E_GET_KEY(obj, i);
			if (!key) {
				DUK_DDDPRINT("skip entry index %d: null key", i);
				continue;
			}
			if (!DUK_HSTRING_HAS_ARRIDX(key)) {
				DUK_DDDPRINT("skip entry index %d: key not an array index", i);
				continue;
			}

			DUK_ASSERT(DUK_HSTRING_HAS_ARRIDX(key));  /* XXX: macro checks for array index flag, which is unnecessary here */
			arr_idx = DUK_HSTRING_GET_ARRIDX_SLOW(key);
			DUK_ASSERT(arr_idx != NO_ARRAY_INDEX);
			DUK_ASSERT(arr_idx < old_len);  /* consistency requires this */

			if (arr_idx < target_len) {
				DUK_DDDPRINT("skip entry index %d: key is array index %d, below target_len", i, arr_idx);
				continue;
			}
			DUK_ASSERT(DUK_HOBJECT_E_SLOT_IS_CONFIGURABLE(obj, i));  /* stage 1 guarantees */

			DUK_DDDPRINT("delete entry index %d: key is array index %d", i, arr_idx);

			/*
			 *  Slow delete, but we don't care as we're already in a very slow path.
			 *  The delete always succeeds: key has no special behavior, property
			 *  is configurable, and no resize occurs.
			 */
			rc = duk_hobject_delprop_raw(thr, obj, key, 0);
			DUK_UNREF(rc);
			DUK_ASSERT(rc != 0);
		}

		/* stage 3: update length (done by caller), decide return code */

		DUK_DDDPRINT("array length write, no array part, stage 3: update length (done by caller)");

		*out_result_len = target_len;

		if (target_len == new_len) {
			DUK_DDDPRINT("target_len matches new_len, return success");
			return 1;
		}
		DUK_DDDPRINT("target_len does not match new_len (some entry prevented "
		             "full length adjustment), return error");
		return 0;
	}

	DUK_NEVER_HERE();
	*out_result_len = 0;
	return 0;
}

/* FIXME: is valstack top best place for argument? */
static int handle_put_array_length(duk_hthread *thr, duk_hobject *obj) {
	duk_context *ctx = (duk_context *) thr;
	duk_propdesc desc;
	duk_u32 old_len;
	duk_u32 new_len;
	duk_u32 result_len;
	duk_tval *tv;
	int rc;

	DUK_DDDPRINT("handling a put operation to array 'length' special property, "
	             "new val: %!T",
	             duk_get_tval(ctx, -1));

	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(ctx != NULL);
	DUK_ASSERT(obj != NULL);

	ASSERT_VALSTACK_SPACE(thr, VALSTACK_SPACE);

	DUK_ASSERT(duk_is_valid_index(ctx, -1));

	/*
	 *  Get old and new length
	 */

	old_len = get_old_array_length(thr, obj, &desc);
	duk_dup(ctx, -1);  /* [in_val in_val] */
	new_len = to_new_array_length_checked(thr);
	duk_pop(ctx);  /* [in_val in_val] -> [in_val] */
	DUK_DDDPRINT("old_len=%d, new_len=%d", old_len, new_len);

	/*
	 *  Writability check
	 */

	if (!(desc.flags & DUK_PROPDESC_FLAG_WRITABLE)) {
		DUK_DDDPRINT("length is not writable, fail");
		return 0;
	}

	/*
	 *  New length not lower than old length => no changes needed
	 *  (not even array allocation).
	 */

	if (new_len >= old_len) {
		DUK_DDDPRINT("new length is higher than old length, just update length, no deletions");

		DUK_ASSERT(desc.e_idx >= 0);
		DUK_ASSERT(!DUK_HOBJECT_E_SLOT_IS_ACCESSOR(obj, desc.e_idx));
		tv = DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(obj, desc.e_idx);
		DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv));
		DUK_TVAL_SET_NUMBER(tv, (double) new_len);  /* no decref needed for a number */
		DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv));
		return 1;
	}

	DUK_DDDPRINT("new length is lower than old length, probably must delete entries");

	/*
	 *  New length lower than old length => delete elements, then
	 *  update length.
	 *
	 *  Note: even though a bunch of elements have been deleted, the 'desc' is
	 *  still valid as properties haven't been resized (and entries compacted).
	 */

	rc = handle_put_array_length_smaller(thr, obj, old_len, new_len, &result_len);
	DUK_ASSERT(result_len >= new_len && result_len <= old_len);

	DUK_ASSERT(desc.e_idx >= 0);
	DUK_ASSERT(!DUK_HOBJECT_E_SLOT_IS_ACCESSOR(obj, desc.e_idx));
	tv = DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(obj, desc.e_idx);
	DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv));
	DUK_TVAL_SET_NUMBER(tv, (double) result_len);  /* no decref needed for a number */
	DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv));

	/*
	 *  FIXME: shrink array allocation or entries compaction here?
	 */

	return rc;
}

/*
 *  PUTPROP: Ecmascript property write.
 *
 *  Unlike Ecmascript primitive which returns nothing, returns 1 to indicate
 *  success and 0 to indicate failure (assuming throw is not set).
 *
 *  This is an extremely tricky function.  Some examples:
 *
 *    * Currently a decref may trigger a GC, which may compact an object's
 *      property allocation.  Consequently, any entry indices (e_idx) will
 *      be potentially invalidated by a decref.
 *
 *    * Special behaviors (strings, arrays, arguments object) require,
 *      among other things:
 *
 *      - Preprocessing before and postprocessing after an actual property
 *        write.  For example, array index write requires pre-checking the
 *        array 'length' property for access control, and may require an
 *        array 'length' update after the actual write has succeeded (but
 *        not if it fails).
 *
 *      - Deletion of multiple entries, as a result of array 'length' write.
 *
 *    * Input values are taken as pointers which may point to the valstack.
 *      If valstack is resized because of the put (this may happen at least
 *      when the array part is abandoned), the pointers can be invalidated.
 *      (We currently make a copy of all of the input values to avoid issues.)
 */

int duk_hobject_putprop(duk_hthread *thr, duk_tval *tv_obj, duk_tval *tv_key, duk_tval *tv_val, int throw_flag) {
	duk_context *ctx = (duk_context *) thr;
	duk_tval tv_obj_copy;
	duk_tval tv_key_copy;
	duk_tval tv_val_copy;
	duk_hobject *orig = NULL;  /* NULL if tv_obj is primitive */
	duk_hobject *curr;
	duk_hstring *key = NULL;
	duk_propdesc desc;
	duk_tval *tv;
	duk_u32 arr_idx;
	int rc;
	int e_idx;
	duk_u32 sanity;
	duk_u32 new_array_length = 0;  /* 0 = no update */

	DUK_DDDPRINT("putprop: thr=%p, obj=%p, key=%p, val=%p, throw=%d "
	             "(obj -> %!T, key -> %!T, val -> %!T)",
	             (void *) thr, (void *) tv_obj, (void *) tv_key, (void *) tv_val,
	             (int) throw_flag, tv_obj, tv_key, tv_val);

	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(thr->heap != NULL);
	DUK_ASSERT(ctx != NULL);
	DUK_ASSERT(tv_obj != NULL);
	DUK_ASSERT(tv_key != NULL);
	DUK_ASSERT(tv_val != NULL);

	ASSERT_VALSTACK_SPACE(thr, VALSTACK_SPACE);

	/*
	 *  Make a copy of tv_obj, tv_key, and tv_val to avoid any issues of
	 *  them being invalidated by a valstack resize.
	 *
	 *  FIXME: this is an overkill for some paths, so optimize this later
	 *  (or maybe switch to a stack arguments model entirely).
	 */

	DUK_TVAL_SET_TVAL(&tv_obj_copy, tv_obj);
	DUK_TVAL_SET_TVAL(&tv_key_copy, tv_key);
	DUK_TVAL_SET_TVAL(&tv_val_copy, tv_val);
	tv_obj = &tv_obj_copy;
	tv_key = &tv_key_copy;
	tv_val = &tv_val_copy;

	/*
	 *  Coercion and fast path processing.
	 */

	switch (DUK_TVAL_GET_TAG(tv_obj)) {
	case DUK_TAG_UNDEFINED:
	case DUK_TAG_NULL:
	case DUK_TAG_BUFFER:
	case DUK_TAG_POINTER: {
		/* FIXME: prototype objects for buffer and perhaps pointer? */

		/* Note: unconditional throw */
		DUK_DDDPRINT("base object is undefined, null, buffer, or pointer -> reject (object=%!iT)", tv_obj);
		DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, "invalid base reference for property write");
		return 0;
	}

	case DUK_TAG_BOOLEAN: {
		DUK_DDDPRINT("base object is a boolean, start lookup from boolean prototype");
		curr = thr->builtins[DUK_BIDX_BOOLEAN_PROTOTYPE];
		break;
	}

	case DUK_TAG_STRING: {
		duk_hstring *h = DUK_TVAL_GET_STRING(tv_obj);

		/*
		 *  Note: currently no fast path for array writes.
		 */

		DUK_ASSERT(key == NULL);
		duk_push_tval(ctx, tv_key);
		duk_to_string(ctx, -1);
		key = duk_get_hstring(ctx, -1);
		arr_idx = DUK_HSTRING_GET_ARRIDX_FAST(key);
		DUK_ASSERT(key != NULL);

		if (key == DUK_HTHREAD_STRING_LENGTH(thr)) {
			goto fail_not_writable;
		}

		if (arr_idx != NO_ARRAY_INDEX &&
		    arr_idx < DUK_HSTRING_GET_CHARLEN(h)) {
			goto fail_not_writable;
		}

		DUK_DDDPRINT("base object is a string, start lookup from string prototype");
		curr = thr->builtins[DUK_BIDX_STRING_PROTOTYPE];
		goto lookup;  /* avoid double coercion */
	
	}

	case DUK_TAG_OBJECT: {
		/* Note: no fast paths for property put now */
		orig = DUK_TVAL_GET_OBJECT(tv_obj);
		DUK_ASSERT(orig != NULL);
		curr = orig;
		break;
	}

	default: {
		/* number */
		DUK_DDDPRINT("base object is a number, start lookup from number prototype");
		DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv_obj));
		curr = thr->builtins[DUK_BIDX_NUMBER_PROTOTYPE];
		break;
	}
	}

	DUK_ASSERT(key == NULL);
	duk_push_tval(ctx, tv_key);
	duk_to_string(ctx, -1);
	key = duk_get_hstring(ctx, -1);
	arr_idx = DUK_HSTRING_GET_ARRIDX_FAST(key);
	DUK_ASSERT(key != NULL);

 lookup:

	/*
	 *  Check whether the property already exists in the prototype chain.
	 *  Note that the actual write goes into the original base object
	 *  (except if an accessor property captures the write).
	 */

	/* [key] */

	DUK_ASSERT(curr != NULL);
	sanity = DUK_HOBJECT_PROTOTYPE_CHAIN_SANITY;
	do {
		/* 0 = don't push current value */
		if (!get_own_property_desc_raw(thr, curr, key, arr_idx, &desc, 0)) {
			goto next_in_chain;
		}

		if (desc.flags & DUK_PROPDESC_FLAG_ACCESSOR) {
			/*
			 *  Found existing accessor property (own or inherited).
			 *  Call setter with 'this' set to orig, and value as the only argument.
			 *
			 *  Note: no special arguments object behavior, because [[Put]] never
			 *  calls [[DefineOwnProperty]] (E5 Section 8.12.5, step 5.b).
			 */

			duk_hobject *setter;

			DUK_DDPRINT("put to an own or inherited accessor, calling setter");

			setter = DUK_HOBJECT_E_GET_VALUE_SETTER(curr, desc.e_idx);
			if (!setter) {
				goto fail_no_setter;
			}
			duk_push_hobject(ctx, setter);
			duk_push_tval(ctx, tv_obj);  /* note: original, uncoerced base */
			duk_push_tval(ctx, tv_val);  /* [key setter this val] */
			duk_call_method(ctx, 1);     /* -> [key retval] */
			duk_pop(ctx);                /* ignore retval -> [key] */
			goto success_no_arguments_special;
		}

		if (orig == NULL) {
			/*
			 *  Found existing own or inherited plain property, but original
			 *  base is a primitive value.
			 */
			DUK_DDPRINT("attempt to create a new property in a primitive base object");
			goto fail_base_primitive;
		}

		if (curr != orig) {
			/*
			 *  Found existing inherited plain property.
			 *  Do an access control check, and if OK, write
			 *  new property to 'orig'.
			 */
			if (!DUK_HOBJECT_HAS_EXTENSIBLE(orig)) {
				DUK_DDPRINT("found existing inherited plain property, but original object is not extensible");
				goto fail_not_extensible;
			}
			if (!(desc.flags & DUK_PROPDESC_FLAG_WRITABLE)) {
				DUK_DDPRINT("found existing inherited plain property, original object is extensible, but inherited property is not writable");
				goto fail_not_writable;
			}
			DUK_DDPRINT("put to new property, object extensible, inherited property found and is writable");
			goto create_new;
		} else {
			/*
			 *  Found existing own (non-inherited) plain property.
			 *  Do an access control check and update in place.
			 */
			if (!(desc.flags & DUK_PROPDESC_FLAG_WRITABLE)) {
				DUK_DDPRINT("found existing own (non-inherited) plain property, but property is not writable");
				goto fail_not_writable;
			}
			DUK_DDPRINT("put to existing own plain property, property is writable");
			goto update_old;
		}
		DUK_NEVER_HERE();

	 next_in_chain:
		if (sanity-- == 0) {
			DUK_ERROR(thr, DUK_ERR_INTERNAL_ERROR, "prototype chain max depth reached (loop?)");
		}
		curr = curr->prototype;
	} while (curr);

	/*
	 *  Property not found in prototype chain.
	 */

	DUK_DDDPRINT("property not found in prototype chain");

	if (orig == NULL) {
		DUK_DDPRINT("attempt to create a new property in a primitive base object");
		goto fail_base_primitive;
	}

	if (!DUK_HOBJECT_HAS_EXTENSIBLE(orig)) {
		DUK_DDPRINT("put to a new property (not found in prototype chain), but original object not extensible");
		goto fail_not_extensible;
	}

	goto create_new;

 update_old:

	/*
	 *  Update an existing property of the base object.
	 */

	/* [key] */

	DUK_DDDPRINT("update an existing property of the original object");

	DUK_ASSERT(orig != NULL);

	/* Currently there are no writable virtual properties,
	 * so the property must be concretely stored in either
	 * the array or the entry part.
	 */
	DUK_ASSERT(desc.a_idx >= 0 || desc.e_idx >= 0);

	if (DUK_HOBJECT_HAS_SPECIAL_ARRAY(orig) &&
	    key == DUK_HTHREAD_STRING_LENGTH(thr)) {
		/*
		 *  Write to 'length' of an array is a very complex case
		 *  handled in a helper which updates both the array elements
		 *  and writes the new 'length'.  The write may result in an
		 *  unconditional RangeError or a partial write (indicated
		 *  by a return code).
		 *
		 *  Note: the helper has an unnecessary writability check
		 *  for 'length', we already know it is writable.
		 */

		DUK_DDDPRINT("writing existing 'length' property to array special, invoke complex helper");

		/* FIXME: the helper currently assumes stack top contains new
		 * 'length' value and the whole calling convention is not very
		 * compatible with what we need.
		 */

		duk_push_tval(ctx, tv_val);  /* [key val] */
		rc = handle_put_array_length(thr, orig);
		duk_pop(ctx);  /* [key val] -> [key] */
		if (!rc) {
			goto fail_array_length_partial;
		}

		/* key is 'length', cannot match argument special behavior */
		goto success_no_arguments_special;
	}

	if (desc.e_idx >= 0) {
		duk_tval tv_tmp;

		tv = DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(orig, desc.e_idx);
		DUK_DDDPRINT("previous entry value: %!iT", tv);
		DUK_TVAL_SET_TVAL(&tv_tmp, tv);
		DUK_TVAL_SET_TVAL(tv, tv_val);
		DUK_TVAL_INCREF(thr, tv);
		DUK_TVAL_DECREF(thr, &tv_tmp);  /* note: may trigger gc and props compaction, must be last */
		/* don't touch property attributes or hash part */
		DUK_DDPRINT("put to an existing entry at index %d -> new value %!iT", desc.e_idx, tv);
	} else {
		/* Note: array entries are always writable, so the writability check
		 * above is pointless for them.  The check could be avoided with some
		 * refactoring but is probably not worth it.
		 */
		duk_tval tv_tmp;

		DUK_ASSERT(desc.a_idx >= 0);
		tv = DUK_HOBJECT_A_GET_VALUE_PTR(orig, desc.a_idx);
		DUK_DDDPRINT("previous array value: %!iT", tv);
		DUK_TVAL_SET_TVAL(&tv_tmp, tv);
		DUK_TVAL_SET_TVAL(tv, tv_val);
		DUK_TVAL_INCREF(thr, tv);
		DUK_TVAL_DECREF(thr, &tv_tmp);  /* note: may trigger gc and props compaction, must be last */
		DUK_DDPRINT("put to an existing array entry at index %d -> new value %!iT", desc.a_idx, tv);
	}

	/* Regardless of whether property is found in entry or array part,
	 * it may have arguments special behavior (array indices may reside
	 * in entry part for abandoned / non-existent array parts).
	 */
	goto success_with_arguments_special;

 create_new:

	/*
	 *  Create a new property in the original object.
	 *
	 *  Special properties need to be reconsidered here from a write
	 *  perspective (not just property attributes perspective).
	 *  However, the property does not exist in the object already,
	 *  so this limits the kind of special properties that apply.
	 */

	/* [key] */

	DUK_DDDPRINT("create new property to original object");

	DUK_ASSERT(orig != NULL);

	/* Not possible because array object 'length' is present
	 * from its creation and cannot be deleted, and is thus
	 * caught as an existing property above.
	 */
	DUK_ASSERT(!(DUK_HOBJECT_HAS_SPECIAL_ARRAY(orig) &&
	             key == DUK_HTHREAD_STRING_LENGTH(thr)));

	if (DUK_HOBJECT_HAS_SPECIAL_ARRAY(orig) &&
	    arr_idx != NO_ARRAY_INDEX) {
		/* automatic length update */
		duk_u32 old_len;

		old_len = get_old_array_length(thr, orig, &desc);

		if (arr_idx >= old_len) {
			DUK_DDDPRINT("write new array entry requires length update "
			             "(arr_idx=%d, old_len=%d)",
			             arr_idx, old_len);

			if (!(desc.flags & DUK_PROPDESC_FLAG_WRITABLE)) {
				DUK_DDPRINT("attempt to extend array, but array 'length' is not writable");
				goto fail_not_writable;
			}

			/* Note: actual update happens once write has been completed
			 * without error below.  The write should always succeed
			 * from a specification viewpoint, but we may e.g. run out
			 * of memory.  It's safer in this order.
			 */

			DUK_ASSERT(arr_idx != 0xffffffffU);
			new_array_length = arr_idx + 1;  /* flag for later write */
		} else {
			DUK_DDDPRINT("write new array entry does not require length update "
			             "(arr_idx=%d, old_len=%d)",
			             arr_idx, old_len);
		}
	}

 /* write_to_array_part: */

	/*
	 *  Write to array part?
	 *
	 *  Note: array abandonding requires a property resize which uses
	 *  'rechecks' valstack for temporaries and may cause any existing
	 *  valstack pointers to be invalidated.  To protect against this,
	 *  tv_obj, tv_key, and tv_val are copies of the original inputs.
	 */

	if (arr_idx != NO_ARRAY_INDEX &&
	    DUK_HOBJECT_HAS_ARRAY_PART(orig)) {
		if (arr_idx < orig->a_size) {
			goto no_array_growth;
		}

		/*
		 *  Array needs to grow, but we don't want it becoming too sparse.
		 *  If it were to become sparse, abandon array part, moving all
		 *  array entries into the entries part (for good).
		 *
		 *  Since we don't keep track of actual density (used vs. size) of
		 *  the array part, we need to estimate somehow.  The check is made
		 *  in two parts:
		 *
		 *    - Check whether the resize need is small compared to the
		 *      current size (relatively); if so, resize without further
		 *      checking (essentially we assume that the original part is
		 *      "dense" so that the result would be dense enough).
		 *
		 *    - Otherwise, compute the resize using an actual density
		 *      measurement based on counting the used array entries.
		 */

		DUK_DDDPRINT("write to new array requires array resize, decide whether to do a "
		             "fast resize without abandon check (arr_idx=%d, old_size=%d)",
		             arr_idx, orig->a_size);

		if (abandon_array_slow_check_required(arr_idx, orig->a_size)) {
			duk_u32 old_used;
			duk_u32 old_size;

			DUK_DDDPRINT("=> fast check is NOT OK, do slow check for array abandon");

			compute_a_stats(orig, &old_used, &old_size);

			DUK_DDDPRINT("abandon check, array stats: old_used=%d, old_size=%d, arr_idx=%d",
			             old_used, old_size, arr_idx);

			/* Note: intentionally use approximations to shave a few instructions:
			 *   a_used = old_used  (accurate: old_used + 1)
			 *   a_size = arr_idx   (accurate: arr_idx + 1)
			 */
			if (abandon_array_density_check(old_used, arr_idx)) {
				DUK_DDPRINT("write to new array entry beyond current length, "
				            "decided to abandon array part (would become too sparse)");

				/* abandoning requires a props allocation resize and
				 * 'rechecks' the valstack, invalidating any existing
				 * valstack value pointers!
				 */
				abandon_array_checked(thr, orig);
				DUK_ASSERT(!DUK_HOBJECT_HAS_ARRAY_PART(orig));

				goto write_to_entry_part;
			}

			DUK_DDDPRINT("=> decided to keep array part");
		} else {
			DUK_DDDPRINT("=> fast resize is OK");
		}

		DUK_DDPRINT("write to new array entry beyond current length, "
		            "decided to extend current allocation");

		grow_props_for_array_item(thr, orig, arr_idx);

	 no_array_growth:

		/* Note: assume array part is comprehensive, so that either
		 * the write goes to the array part, or we've abandoned the
		 * array above (and will not come here).
		 */

		DUK_ASSERT(DUK_HOBJECT_HAS_ARRAY_PART(orig));
		DUK_ASSERT(arr_idx < orig->a_size);

		tv = DUK_HOBJECT_A_GET_VALUE_PTR(orig, arr_idx);
		/* prev value must be unused, no decref */
		DUK_ASSERT(DUK_TVAL_IS_UNDEFINED_UNUSED(tv));
		DUK_TVAL_SET_TVAL(tv, tv_val);
		DUK_TVAL_INCREF(thr, tv);
		DUK_DDPRINT("put to new array entry: %d -> %!T", (int) arr_idx, tv);

		/* Note: array part values are [[Writable]], [[Enumerable]],
		 * and [[Configurable]] which matches the required attributes
		 * here.
		 */
		goto entry_updated;
	}

 write_to_entry_part:

	/*
	 *  Write to entry part
	 */

	/* entry allocation updates hash part and increases the key
	 * refcount; may need a props allocation resize but doesn't
	 * 'recheck' the valstack.
	 */
	e_idx = alloc_entry_checked(thr, orig, key);
	DUK_ASSERT(e_idx >= 0);

	tv = DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(orig, e_idx);
	/* prev value can be garbage, no decref */
	DUK_TVAL_SET_TVAL(tv, tv_val);
	DUK_TVAL_INCREF(thr, tv);
	DUK_HOBJECT_E_SET_FLAGS(orig, e_idx, DUK_PROPDESC_FLAGS_WEC);
	goto entry_updated;

 entry_updated:

	/*
	 *  Possible pending array length update, which must only be done
	 *  if the actual entry write succeeded.
	 */	

	if (new_array_length > 0) {
		/*
		 *  Note: zero works as a "no update" marker because the new length
		 *  can never be zero after a new property is written.
		 *
		 *  Note: must re-lookup because calls above (e.g. alloc_entry_checked())
		 *  may realloc and compact properties and hence change e_idx.
		 */

		DUK_DDDPRINT("write successful, pending array length update to: %d", new_array_length);

		rc = get_own_property_desc_raw(thr, orig, DUK_HTHREAD_STRING_LENGTH(thr), NO_ARRAY_INDEX, &desc, 0);
		DUK_UNREF(rc);
		DUK_ASSERT(rc != 0);
		DUK_ASSERT(desc.e_idx >= 0);

		tv = DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(orig, desc.e_idx);
		DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv));
		DUK_TVAL_SET_NUMBER(tv, (double) new_array_length);  /* no need for decref/incref because value is a number */
	}

	/*
	 *  Arguments special behavior not possible for new properties: all
	 *  magically bound properties are initially present in the arguments
	 *  object, and if they are deleted, the binding is also removed from
	 *  parameter map.
	 */

	goto success_no_arguments_special;

 success_with_arguments_special:

	/*
	 *  Arguments objects have special [[DefineOwnProperty]] which updates
	 *  the internal 'map' of arguments for writes to currently mapped
	 *  arguments.  More conretely, writes to mapped arguments generate
	 *  a write to a bound variable.
	 *
	 *  The [[Put]] algorithm invokes [[DefineOwnProperty]] for existing
	 *  data properties and new properties, but not for existing accessors.
	 *  Hence, in E5 Section 10.6 ([[DefinedOwnProperty]] algorithm), we
	 *  have a Desc with 'Value' (and possibly other properties too), and
	 *  we end up in step 5.b.i.
	 */

	if (arr_idx != NO_ARRAY_INDEX &&
	    DUK_HOBJECT_HAS_SPECIAL_ARGUMENTS(orig)) {
		/* Note: only numbered indices are relevant, so arr_idx fast reject
		 * is good (this is valid unless there are more than 4**32-1 arguments).
		 */

		DUK_DDDPRINT("putprop successful, arguments special behavior needed");

		/* Note: we can reuse 'desc' here */

		/* FIXME: top of stack must contain value, which helper doesn't touch,
		 * rework to use tv_val directly?
		 */

		duk_push_tval(ctx, tv_val);
		(void) check_arguments_map_for_put(thr, orig, key, &desc, throw_flag);
		duk_pop(ctx);
	}
	/* fall thru */

 success_no_arguments_special:
	/* shared exit path now */
	DUK_DDDPRINT("result: success");
	duk_pop(ctx);  /* remove key */
	return 1;

 fail_base_primitive:
	DUK_DDDPRINT("result: error, base primitive");
	if (throw_flag) {
		DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, "non-object base reference");
	}
	duk_pop(ctx);  /* remove key */
	return 0;

 fail_not_extensible:
	DUK_DDDPRINT("result: error, not extensible");
	if (throw_flag) {
		DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, "object not extensible");
	}
	duk_pop(ctx);  /* remove key */
	return 0;
	
 fail_not_writable:
	DUK_DDDPRINT("result: error, not writable");
	if (throw_flag) {
		DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, "property not writable");
	}
	duk_pop(ctx);  /* remove key */
	return 0;

 fail_array_length_partial:
	DUK_DDDPRINT("result: error, array length write only partially successful");
	if (throw_flag) {
		DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, "array length write failed");
	}
	duk_pop(ctx);  /* remove key */
	return 0;

 fail_no_setter:
	DUK_DDDPRINT("result: error, accessor property without setter");
	if (throw_flag) {
		DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, "undefined setter for accessor");
	}
	duk_pop(ctx);  /* remove key */
	return 0;
}

/*
 *  Ecmascript compliant [[Delete]](P, Throw).
 */

int duk_hobject_delprop_raw(duk_hthread *thr, duk_hobject *obj, duk_hstring *key, int throw_flag) {
	duk_propdesc desc;
	duk_tval *tv;
	duk_tval tv_tmp;
	duk_u32 arr_idx;

	DUK_DDDPRINT("delprop_raw: thr=%p, obj=%p, key=%p, throw=%d (obj -> %!O, key -> %!O)",
	             (void *) thr, (void *) obj, (void *) key, (int) throw_flag,
	             (duk_heaphdr *) obj, (duk_heaphdr *) key);

	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(thr->heap != NULL);
	DUK_ASSERT(obj != NULL);
	DUK_ASSERT(key != NULL);

	ASSERT_VALSTACK_SPACE(thr, VALSTACK_SPACE);

	arr_idx = DUK_HSTRING_GET_ARRIDX_FAST(key);

	/* 0 = don't push current value */
	if (!get_own_property_desc_raw(thr, obj, key, arr_idx, &desc, 0)) {
		DUK_DDDPRINT("property not found, succeed always");
		goto success;
	}

	if ((desc.flags & DUK_PROPDESC_FLAG_CONFIGURABLE) == 0) {
		goto fail_not_configurable;
	}

	/* currently there are no deletable virtual properties */
	DUK_ASSERT(desc.a_idx >= 0 || desc.e_idx >= 0);

	if (desc.a_idx >= 0) {
		DUK_ASSERT(desc.e_idx < 0);

		tv = DUK_HOBJECT_A_GET_VALUE_PTR(obj, desc.a_idx);
		DUK_TVAL_SET_TVAL(&tv_tmp, tv);
		DUK_TVAL_SET_UNDEFINED_UNUSED(tv);
		DUK_TVAL_DECREF(thr, &tv_tmp);
		goto success;
	} else {
		DUK_ASSERT(desc.a_idx < 0);

		/* remove hash entry (no decref) */
		if (desc.h_idx >= 0) {
			duk_u32 *h_base = DUK_HOBJECT_H_GET_BASE(obj);

			DUK_DDDPRINT("removing hash entry at h_idx %d", desc.h_idx);
			DUK_ASSERT(obj->h_size > 0);
			DUK_ASSERT(desc.h_idx < obj->h_size);
			h_base[desc.h_idx] = HASH_DELETED;
		} else {
			DUK_ASSERT(obj->h_size == 0);
		}

		/* remove value */
		DUK_DDDPRINT("before removing value, e_idx %d, key %p, key at slot %p",
		             desc.e_idx, key, DUK_HOBJECT_E_GET_KEY(obj, desc.e_idx));
		DUK_DDDPRINT("removing value at e_idx %d", desc.e_idx);
		if (DUK_HOBJECT_E_SLOT_IS_ACCESSOR(obj, desc.e_idx)) {
			duk_hobject *tmp;

			tmp = DUK_HOBJECT_E_GET_VALUE_GETTER(obj, desc.e_idx);
			DUK_HOBJECT_E_SET_VALUE_GETTER(obj, desc.e_idx, NULL);
			DUK_UNREF(tmp);
			DUK_HOBJECT_DECREF(thr, tmp);

			tmp = DUK_HOBJECT_E_GET_VALUE_SETTER(obj, desc.e_idx);
			DUK_HOBJECT_E_SET_VALUE_SETTER(obj, desc.e_idx, NULL);
			DUK_UNREF(tmp);
			DUK_HOBJECT_DECREF(thr, tmp);
		} else {
			tv = DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(obj, desc.e_idx);
			DUK_TVAL_SET_TVAL(&tv_tmp, tv);
			DUK_TVAL_SET_UNDEFINED_UNUSED(tv);
			DUK_TVAL_DECREF(thr, &tv_tmp);
		}
		/* this is not strictly necessary because if key == NULL, value MUST be ignored */
		DUK_HOBJECT_E_SET_FLAGS(obj, desc.e_idx, 0);
		DUK_TVAL_SET_UNDEFINED_UNUSED(DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(obj, desc.e_idx));

		/* remove key */
		DUK_DDDPRINT("before removing key, e_idx %d, key %p, key at slot %p",
		             desc.e_idx, key, DUK_HOBJECT_E_GET_KEY(obj, desc.e_idx));
		DUK_DDDPRINT("removing key at e_idx %d", desc.e_idx);
		DUK_ASSERT(key == DUK_HOBJECT_E_GET_KEY(obj, desc.e_idx));
		DUK_HOBJECT_E_SET_KEY(obj, desc.e_idx, NULL);
		DUK_HSTRING_DECREF(thr, key);
		goto success;
	}

	DUK_NEVER_HERE();
	
 success:
	/*
	 *  Argument special [[Delete]] behavior (E5 Section 10.6) is
	 *  a post-check, keeping arguments internal 'map' in sync with
	 *  any successful deletes (note that property does not need to
	 *  exist for delete to 'succeed').
	 *
	 *  Delete key from 'map'.  Since 'map' only contains array index
	 *  keys, we can use arr_idx for a fast skip.
	 */

	DUK_DDDPRINT("delete successful, check for arguments special behavior");

	if (arr_idx != NO_ARRAY_INDEX && DUK_HOBJECT_HAS_SPECIAL_ARGUMENTS(obj)) {
		/* Note: only numbered indices are relevant, so arr_idx fast reject
		 * is good (this is valid unless there are more than 4**32-1 arguments).
		 */

		DUK_DDDPRINT("delete successful, arguments special behavior needed");

		/* Note: we can reuse 'desc' here */
		(void) check_arguments_map_for_delete(thr, obj, key, &desc);
	}

	DUK_DDDPRINT("delete successful");
	return 1;

 fail_not_configurable:
	DUK_DDDPRINT("delete failed: property found, not configurable");

	if (throw_flag) {
		DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, "property not configurable");
	}
	return 0;
}


/*
 *  DELPROP: Ecmascript property deletion.
 */

int duk_hobject_delprop(duk_hthread *thr, duk_tval *tv_obj, duk_tval *tv_key, int throw_flag) {
	duk_context *ctx = (duk_context *) thr;
	duk_hstring *key = NULL;
	duk_u32 arr_idx = NO_ARRAY_INDEX;
	int rc;

	DUK_DDDPRINT("delprop: thr=%p, obj=%p, key=%p (obj -> %!T, key -> %!T)",
	             (void *) thr, (void *) tv_obj, (void *) tv_key, tv_obj, tv_key);

	DUK_ASSERT(ctx != NULL);
	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(thr->heap != NULL);
	DUK_ASSERT(tv_obj != NULL);
	DUK_ASSERT(tv_key != NULL);

	ASSERT_VALSTACK_SPACE(thr, VALSTACK_SPACE);

	if (DUK_TVAL_IS_UNDEFINED(tv_obj) ||
	    DUK_TVAL_IS_NULL(tv_obj)) {
		/* Note: unconditional throw */
		DUK_DDDPRINT("base object is undefined or null -> reject");
		DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, "invalid base reference for property delete");
	}

	/* FIXME: because we need to do this, just take args through stack? */
	duk_push_tval(ctx, tv_obj);
	duk_push_tval(ctx, tv_key);

	duk_to_string(ctx, -1);
	key = duk_get_hstring(ctx, -1);
	DUK_ASSERT(key != NULL);

	tv_obj = duk_get_tval(ctx, -2);
	if (DUK_TVAL_IS_OBJECT(tv_obj)) {
		duk_hobject *obj = DUK_TVAL_GET_OBJECT(tv_obj);

		DUK_ASSERT(obj != NULL);

		rc = duk_hobject_delprop_raw(thr, obj, key, throw_flag);

		duk_pop_2(ctx);  /* [obj key] -> [] */
		return rc;
	} else if (DUK_TVAL_IS_STRING(tv_obj)) {
		duk_hstring *h = DUK_TVAL_GET_STRING(tv_obj);

		DUK_ASSERT(h != NULL);

		if (key == DUK_HTHREAD_STRING_LENGTH(thr)) {
			goto fail_not_configurable;
		}

		arr_idx = DUK_HSTRING_GET_ARRIDX_FAST(key);

		if (arr_idx != NO_ARRAY_INDEX &&
		    arr_idx < DUK_HSTRING_GET_CHARLEN(h)) {
			goto fail_not_configurable;
		}
	}

	/* string without matching properties, or any other primitive base */

	duk_pop_2(ctx);  /* [obj key] -> [] */
	return 1;

 fail_not_configurable:
	if (throw_flag) {
		DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, "property not configurable");
	}
	return 0;
}

/*
 *  Internal helper to define a property with specific flags, ignoring
 *  normal semantics such as extensibility, write protection etc.
 *  Overwrites any existing value and attributes.  If target has an array
 *  part, asserts that propflags are correct (WEC).
 *
 *  Does not support:
 *    - virtual properties
 *    - getter/setter properties
 *    - array abandoning: if array part exists, it is always extended
 *
 *  Stack: [... in_val] -> []
 *
 *  Used for e.g. built-in initialization and environment record
 *  operations.
 */

void duk_hobject_define_property_internal(duk_hthread *thr, duk_hobject *obj, duk_hstring *key, int propflags) {
	duk_context *ctx = (duk_context *) thr;
	duk_propdesc desc;
	duk_u32 arr_idx;
	int e_idx;
	duk_tval tv_tmp;
	duk_tval *tv1 = NULL;
	duk_tval *tv2 = NULL;

	DUK_DDDPRINT("define new property (internal): thr=%p, obj=%!O, key=%!O, flags=0x%02x, val=%!T",
	             (void *) thr, obj, key, propflags, duk_get_tval(ctx, -1));

	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(thr->heap != NULL);
	DUK_ASSERT(obj != NULL);
	DUK_ASSERT(key != NULL);
	DUK_ASSERT((propflags & ~DUK_PROPDESC_FLAGS_MASK) == 0);

	ASSERT_VALSTACK_SPACE(thr, VALSTACK_SPACE);
	DUK_ASSERT(duk_is_valid_index(ctx, -1));  /* contains value */

	arr_idx = DUK_HSTRING_GET_ARRIDX_SLOW(key);

	if (get_own_property_desc_raw(thr, obj, key, arr_idx, &desc, 0)) {  /* push_value = 0 */
		if (desc.e_idx >= 0) {
			DUK_DDDPRINT("property already exists in the entry part -> update value and attributes");
			DUK_ASSERT(!DUK_HOBJECT_E_SLOT_IS_ACCESSOR(obj, desc.e_idx));

			DUK_HOBJECT_E_SET_FLAGS(obj, desc.e_idx, propflags);
			tv1 = DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(obj, desc.e_idx);
		} else if (desc.a_idx >= 0) {
			DUK_DDDPRINT("property already exists in the array part -> update value (assert attributes)");
			DUK_ASSERT(propflags == DUK_PROPDESC_FLAGS_WEC);

			tv1 = DUK_HOBJECT_A_GET_VALUE_PTR(obj, desc.a_idx);
		} else {
			DUK_DDDPRINT("property already exists but is virtual -> failure");
			DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, "attempt to redefine virtual property");
			DUK_NEVER_HERE();
		}

		goto write_value;
	}

	if (DUK_HOBJECT_HAS_ARRAY_PART(obj)) {
		if (arr_idx != NO_ARRAY_INDEX) {
			DUK_DDDPRINT("property does not exist, object has array part -> possibly extend array part and write value (assert attributes)");
			DUK_ASSERT(propflags == DUK_PROPDESC_FLAGS_WEC);

			/* always grow the array, no sparse / abandon support here */
			if (arr_idx >= obj->a_size) {
				grow_props_for_array_item(thr, obj, arr_idx);
			}

			DUK_ASSERT(arr_idx < obj->a_size);
			tv1 = DUK_HOBJECT_A_GET_VALUE_PTR(obj, arr_idx);
			goto write_value;			
		}
	}

	DUK_DDDPRINT("property does not exist, object belongs in entry part -> allocate new entry and write value and attributes");
	e_idx = alloc_entry_checked(thr, obj, key);  /* increases key refcount */
	DUK_ASSERT(e_idx >= 0);
	DUK_HOBJECT_E_SET_FLAGS(obj, e_idx, propflags);
	tv1 = DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(obj, e_idx);
	/* new entry: previous value is garbage; set to undefined to share write_value */
	DUK_TVAL_SET_UNDEFINED_ACTUAL(tv1);
	goto write_value;

 write_value:
	/* tv1 points to value storage */

	tv2 = duk_require_tval(ctx, -1);  /* late lookup, avoid side effects */
	DUK_DDDPRINT("writing/updating value: %!T -> %!T", tv1, tv2);

	DUK_TVAL_SET_TVAL(&tv_tmp, tv1);
	DUK_TVAL_SET_TVAL(tv1, tv2);
	DUK_TVAL_INCREF(thr, tv1);
	DUK_TVAL_DECREF(thr, &tv_tmp);    /* side effects */

	duk_pop(ctx);  /* remove in_val */
}

/*
 *  Internal helpers for managing object 'length'
 */

/* FIXME: awkward helpers */

void duk_hobject_set_length(duk_hthread *thr, duk_hobject *obj, duk_u32 length) {
	duk_context *ctx = (duk_context *) thr;
	duk_push_hobject(ctx, obj);
	duk_push_hstring_stridx(ctx, DUK_STRIDX_LENGTH);
	duk_push_number(ctx, (double) length);  /* FIXME: push_u32 */
	(void) duk_hobject_putprop(thr, duk_get_tval(ctx, -3), duk_get_tval(ctx, -2), duk_get_tval(ctx, -1), 0);
	duk_pop_n(ctx, 3);
}

void duk_hobject_set_length_zero(duk_hthread *thr, duk_hobject *obj) {
	duk_hobject_set_length(thr, obj, 0);
}

duk_u32 duk_hobject_get_length(duk_hthread *thr, duk_hobject *obj) {
	duk_context *ctx = (duk_context *) thr;
	double val;
	duk_push_hobject(ctx, obj);
	duk_push_hstring_stridx(ctx, DUK_STRIDX_LENGTH);
	(void) duk_hobject_getprop(thr, duk_get_tval(ctx, -2), duk_get_tval(ctx, -1));
	val = duk_to_number(ctx, -1);
	duk_pop_n(ctx, 3);
	if (val >= 0.0 && val < 4294967296.0) {  /* XXX: constant */
		return (duk_u32) val;
	}
	return 0;
}

/*
 *  Object.getOwnPropertyDescriptor()  (E5 Sections 15.2.3.3, 8.10.4)
 *
 *  This is an actual function call.
 */

int duk_hobject_object_get_own_property_descriptor(duk_context *ctx) {
	duk_hthread *thr = (duk_hthread *) ctx;
	duk_hobject *obj;
	duk_hstring *key;
	duk_propdesc pd;
	int rc;

	DUK_ASSERT(ctx != NULL);
	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(thr->heap != NULL);

	obj = duk_require_hobject(ctx, 0);
	(void) duk_to_string(ctx, 1);
	key = duk_require_hstring(ctx, 1);

	DUK_ASSERT(obj != NULL);
	DUK_ASSERT(key != NULL);

	ASSERT_VALSTACK_SPACE(thr, VALSTACK_SPACE);

	rc = get_own_property_desc(thr, obj, key, &pd, 1);  /* push_value = 1 */
	if (!rc) {
		duk_push_undefined(ctx);

		/* [obj key undefined] */
		return 1;
	}

	duk_push_object(ctx);

	/* [obj key value desc] */

	if (DUK_PROPDESC_IS_ACCESSOR(&pd)) {
		if (pd.get) {
			duk_push_hobject(ctx, pd.get);
			duk_put_prop_stridx(ctx, -2, DUK_STRIDX_GET);
		}
		if (pd.set) {
			duk_push_hobject(ctx, pd.set);
			duk_put_prop_stridx(ctx, -2, DUK_STRIDX_SET);
		}
	} else {
		duk_dup(ctx, -2);  /* [obj key value desc value] */
		duk_put_prop_stridx(ctx, -2, DUK_STRIDX_VALUE);
		duk_push_boolean(ctx, DUK_PROPDESC_IS_WRITABLE(&pd));
		duk_put_prop_stridx(ctx, -2, DUK_STRIDX_WRITABLE);

		/* [obj key value desc] */
	}
	duk_push_boolean(ctx, DUK_PROPDESC_IS_ENUMERABLE(&pd));
	duk_put_prop_stridx(ctx, -2, DUK_STRIDX_ENUMERABLE);
	duk_push_boolean(ctx, DUK_PROPDESC_IS_CONFIGURABLE(&pd));
	duk_put_prop_stridx(ctx, -2, DUK_STRIDX_CONFIGURABLE);

	/* [obj key value desc] */
	return 1;
}

/*
 *  NormalizePropertyDescriptor().
 *
 *  Internal helper to convert an external property descriptor on stack top
 *  to a normalized form with plain, coerced values.  The original descriptor
 *  object is not altered.
 */

/* FIXME: very basic optimization -> duk_get_prop_stridx_top */

static void normalize_property_descriptor(duk_context *ctx) {
	duk_hthread *thr = (duk_hthread *) ctx;
	int idx_in;
	int idx_out;
	int is_data_desc = 0;
	int is_acc_desc = 0;
	int target_top;

	DUK_ASSERT(ctx != NULL);
	DUK_ASSERT(duk_is_object(ctx, -1));

	idx_in = duk_require_normalize_index(ctx, -1);
	duk_push_object(ctx);  /* [... desc_in desc_out] */
	idx_out = idx_in + 1;

	/* this approach allows us to be care-free with the "stack policy"
	 * until the very end.
	 */
	target_top = duk_get_top(ctx);

	if (duk_get_prop_stridx(ctx, idx_in, DUK_STRIDX_VALUE)) {
		is_data_desc = 1;
		duk_put_prop_stridx(ctx, idx_out, DUK_STRIDX_VALUE);
	}

	if (duk_get_prop_stridx(ctx, idx_in, DUK_STRIDX_WRITABLE)) {
		is_data_desc = 1;
		duk_to_boolean(ctx, -1);
		duk_put_prop_stridx(ctx, idx_out, DUK_STRIDX_WRITABLE);
	}

	if (duk_get_prop_stridx(ctx, idx_in, DUK_STRIDX_GET)) {
		duk_tval *tv = duk_require_tval(ctx, -1);
		is_acc_desc = 1;
		if (DUK_TVAL_IS_UNDEFINED(tv) ||
		    (DUK_TVAL_IS_OBJECT(tv) &&
		     DUK_HOBJECT_IS_CALLABLE(DUK_TVAL_GET_OBJECT(tv)))) {
			duk_put_prop_stridx(ctx, idx_out, DUK_STRIDX_GET);
		} else {
			goto type_error;
		}
	}

	if (duk_get_prop_stridx(ctx, idx_in, DUK_STRIDX_SET)) {
		duk_tval *tv = duk_require_tval(ctx, -1);
		is_acc_desc = 1;
		if (DUK_TVAL_IS_UNDEFINED(tv) ||
		    (DUK_TVAL_IS_OBJECT(tv) &&
		     DUK_HOBJECT_IS_CALLABLE(DUK_TVAL_GET_OBJECT(tv)))) {
			duk_put_prop_stridx(ctx, idx_out, DUK_STRIDX_SET);
		} else {
			goto type_error;
		}
	}

	if (duk_get_prop_stridx(ctx, idx_in, DUK_STRIDX_ENUMERABLE)) {
		duk_to_boolean(ctx, -1);
		duk_put_prop_stridx(ctx, idx_out, DUK_STRIDX_ENUMERABLE);
	}

	if (duk_get_prop_stridx(ctx, idx_in, DUK_STRIDX_CONFIGURABLE)) {
		duk_to_boolean(ctx, -1);
		duk_put_prop_stridx(ctx, idx_out, DUK_STRIDX_CONFIGURABLE);
	}

	/* pop any crud */
	duk_set_top(ctx, target_top);

	if (is_data_desc && is_acc_desc) {
		goto type_error;
	}

	/* [... desc_in desc_out] */

	duk_remove(ctx, -2);

	/* [... desc_out] */

	return;

 type_error:
	DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, "invalid descriptor");
}

/*
 *  Object.defineProperty()  (E5 Section 15.2.3.6)
 *
 *  Inlines ToPropertyDescriptor() and all [[DefineOwnProperty]] special
 *  behaviors.
 *
 *  Note: Ecmascript compliant [[DefineOwnProperty]](P, Desc, Throw) is not
 *  implemented directly, but Object.defineProperty() serves its purpose.
 *  We don't need the [[DefineOwnProperty]] internally and we don't have a
 *  property descriptor with 'missing values' so it's easier to avoid it
 *  entirely.
 *
 *  This is an actual function call.
 */

/* FIXME: this is a major target for size optimization */

int duk_hobject_object_define_property(duk_context *ctx) {
	duk_hthread *thr = (duk_hthread *) ctx;
	duk_hobject *obj;
	duk_hstring *key;
	duk_hobject *desc;
	duk_u32 arr_idx;
	int idx_desc;
	duk_tval tv;
	int has_enumerable;
	int has_configurable;
	int has_writable;
	int has_value;
	int has_get;
	int has_set;
	int is_enumerable;
	int is_configurable;
	int is_writable;
	int idx_value;
	duk_hobject *get;
	duk_hobject *set;
	int new_flags;
	duk_propdesc curr;
	duk_u32 arridx_new_array_length;  /* != 0 => post-update for array 'length' (used when key is an array index) */
	duk_u32 arrlen_old_len;
	duk_u32 arrlen_new_len;
	int pending_write_protect;
	int throw_flag = 1;   /* Object.defineProperty() calls [[DefineOwnProperty]] with Throw=true */

	DUK_DDDPRINT("Object.defineProperty(): thr=%p obj=%!T key=%!T desc=%!T",
	             (void *) thr, duk_get_tval(ctx, 0), duk_get_tval(ctx, 1), duk_get_tval(ctx, 2));

	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(thr->heap != NULL);
	DUK_ASSERT(ctx != NULL);

	ASSERT_VALSTACK_SPACE(thr, VALSTACK_SPACE);

	obj = duk_require_hobject(ctx, 0);
	(void) duk_to_string(ctx, 1);
	key = duk_require_hstring(ctx, 1);
	desc = duk_require_hobject(ctx, 2);
	DUK_UNREF(desc);
	idx_desc = 2;

	DUK_ASSERT(obj != NULL);
	DUK_ASSERT(key != NULL);
	DUK_ASSERT(desc != NULL);

	arr_idx = DUK_HSTRING_GET_ARRIDX_SLOW(key);

	DUK_DDDPRINT("Object.defineProperty(): thr=%p obj=%!O key=%!O arr_idx=0x%08x desc=%!O",
	             (void *) thr, (duk_heaphdr *) obj, (duk_heaphdr *) key, (int) arr_idx, (duk_heaphdr *) desc);

	has_enumerable = 0;
	has_configurable = 0;
	has_value = 0;
	has_writable = 0;
	has_get = 0;
	has_set = 0;
	is_enumerable = 0;
	is_configurable = 0;
	is_writable = 0;
	idx_value = -1;
	get = NULL;
	set = NULL;
	arridx_new_array_length = 0;
	pending_write_protect = 0;
	arrlen_old_len = 0;
	arrlen_new_len = 0;

	/*
	 *  Extract property descriptor values as required in ToPropertyDescriptor().
	 *  However, don't create an explicit property descriptor object: we don't
	 *  want to create a new Ecmascript object, and the internal property descriptor
	 *  does not support partial descriptors.
	 *
	 *  Note that ToPropertyDescriptor() does coercions with potential errors, so
	 *  all coercions must be done first.  Boolean conversion of 'undefined' is false.
	 */

	has_enumerable = duk_get_prop_stridx(ctx, idx_desc, DUK_STRIDX_ENUMERABLE);
	is_enumerable = duk_to_boolean(ctx, -1);
	duk_pop(ctx);

	has_configurable = duk_get_prop_stridx(ctx, idx_desc, DUK_STRIDX_CONFIGURABLE);
	is_configurable = duk_to_boolean(ctx, -1);
	duk_pop(ctx);

	has_value = duk_get_prop_stridx(ctx, idx_desc, DUK_STRIDX_VALUE);
	if (has_value) {
		/* Note: we don't want to store a pointer to an duk_tval in the
		 * valstack here, because a valstack resize (which may occur
		 * on any gc) might invalidate it.
		 */
		idx_value = duk_require_top_index(ctx);
	} else {
		idx_value = -1;
	}
	/* leave value on stack intentionally to ensure we can refer to it later */

	has_writable = duk_get_prop_stridx(ctx, idx_desc, DUK_STRIDX_WRITABLE);
	is_writable = duk_to_boolean(ctx, -1);
	duk_pop(ctx);

	has_get = duk_get_prop_stridx(ctx, idx_desc, DUK_STRIDX_GET);
	get = NULL;
	if (has_get && !duk_is_undefined(ctx, -1)) {
		/* FIXME: get = duk_require_callable_hobject(ctx, -1)? */
		get = duk_require_hobject(ctx, -1);
		DUK_ASSERT(get != NULL);
		if (!DUK_HOBJECT_IS_CALLABLE(get)) {
			goto fail_invalid_desc;
		}
	}
	/* leave get on stack */

	has_set = duk_get_prop_stridx(ctx, idx_desc, DUK_STRIDX_SET);
	set = NULL;
	if (has_set && !duk_is_undefined(ctx, -1)) {
		set = duk_require_hobject(ctx, -1);
		DUK_ASSERT(set != NULL);
		if (!DUK_HOBJECT_IS_CALLABLE(set)) {
			goto fail_invalid_desc;
		}
	}
	/* leave set on stack */

	if ((has_set || has_get) && (has_value || has_writable)) {
		goto fail_invalid_desc;
	}

	/* [obj key desc value get set] */

	DUK_DDDPRINT("has_enumerable=%d is_enumerable=%d "
	             "has_configurable=%d is_configurable=%d "
	             "has_writable=%d is_writable=%d "
	             "has_value=%d value=%!T "
	             "has_get=%d get=%p=%!O "
	             "has_set=%d set=%p=%!O ",
	             has_enumerable, is_enumerable,
	             has_configurable, is_configurable,
	             has_writable, is_writable,
	             has_value, duk_get_tval(ctx, idx_value),
	             has_get, (void *) get, (duk_heaphdr *) get,
	             has_set, (void *) set, (duk_heaphdr *) set);

	/*
	 *  Array special behaviors can be implemented at this point.  The local variables
	 *  are essentially a 'value copy' of the input descriptor (Desc), which is modified
	 *  by the Array [[DefineOwnProperty]] (E5 Section 15.4.5.1).
	 */

	if (!DUK_HOBJECT_HAS_SPECIAL_ARRAY(obj)) {
		goto skip_array_special;
	}

	if (key == DUK_HTHREAD_STRING_LENGTH(thr)) {
		/* E5 Section 15.4.5.1, step 3, steps a - i are implemented here, j - n at the end */
		if (!has_value) {
			DUK_DDDPRINT("special array behavior for 'length', but no value in descriptor -> normal behavior");
			goto skip_array_special;
		}
	
		DUK_DDDPRINT("special array behavior for 'length', value present in descriptor -> special behavior");

		/*
		 *  Get old and new length
		 */

		/* Note: reuse 'curr' as a temp propdesc */
		arrlen_old_len = get_old_array_length(thr, obj, &curr);

		duk_dup(ctx, idx_value);
		arrlen_new_len = to_new_array_length_checked(thr);
		duk_replace(ctx, idx_value);  /* step 3.e: replace 'Desc.[[Value]]' */

		DUK_DDDPRINT("old_len=%d, new_len=%d", arrlen_old_len, arrlen_new_len);

		if (arrlen_new_len >= arrlen_old_len) {
			/* standard behavior, step 3.f.i */
			DUK_DDDPRINT("new length is same or higher as previous => standard behavior");
			goto skip_array_special;
		}
		DUK_DDDPRINT("new length is smaller than previous => special post behavior");

		/* FIXME: consolidated algorithm step 15.f -> redundant? */
		if (!(curr.flags & DUK_PROPDESC_FLAG_WRITABLE)) {
			/* Note: 'curr' refers to 'length' propdesc */
			goto fail_not_writable_array_length;
		}

		/* steps 3.h and 3.i */
		if (has_writable || !is_writable) {
			DUK_DDDPRINT("desc writable is false, force it back to true, and flag pending write protect");
			is_writable = 1;
			pending_write_protect = 1;
		}

		/* remaining actual steps are carried out if standard DefineOwnProperty succeeds */
	} else if (arr_idx != NO_ARRAY_INDEX) {
		/* FIXME: any chance of unifying this with the 'length' key handling? */

		/* E5 Section 15.4.5.1, step 4 */
		duk_u32 old_len;

		/* Note: use 'curr' as a temp propdesc */
		old_len = get_old_array_length(thr, obj, &curr);

		if (arr_idx >= old_len) {
			DUK_DDDPRINT("defineProperty requires array length update "
			             "(arr_idx=%d, old_len=%d)",
			             arr_idx, old_len);

			if (!(curr.flags & DUK_PROPDESC_FLAG_WRITABLE)) {
				/* Note: 'curr' refers to 'length' propdesc */
				goto fail_not_writable_array_length;
			}

			/* actual update happens once write has been completed without
			 * error below.
			 */
			DUK_ASSERT(arr_idx != 0xffffffffU);
			arridx_new_array_length = arr_idx + 1;
		} else {
			DUK_DDDPRINT("defineProperty does not require length update "
			             "(arr_idx=%d, old_len=%d) -> standard behavior",
			             arr_idx, old_len);
		}
	}
 skip_array_special:

	/*
	 *  Actual Object.defineProperty() default algorithm.
	 */

	/*
	 *  First check whether property exists; if not, simple case.  This covers
	 *  steps 1-4.
	 */

	if (!get_own_property_desc_raw(thr, obj, key, arr_idx, &curr, 1)) {
		DUK_DDDPRINT("property does not exist");

		if (!DUK_HOBJECT_HAS_EXTENSIBLE(obj)) {
			goto fail_not_extensible;
		}

		/* FIXME: share final setting code for value and flags?  difficult because
		 * refcount code is different.  Share entry allocation?  But can't allocate
		 * until array index checked.
		 */

		/* steps 4.a and 4.b are tricky */
		if (has_set || has_get) {
			int e_idx;

			DUK_DDDPRINT("create new accessor property");

			DUK_ASSERT(has_set || set == NULL);
			DUK_ASSERT(has_get || get == NULL);
			DUK_ASSERT(!has_value);
			DUK_ASSERT(!has_writable);

			new_flags = DUK_PROPDESC_FLAG_ACCESSOR;  /* defaults, E5 Section 8.6.1, Table 7 */
			if (has_enumerable && is_enumerable) {
				new_flags |= DUK_PROPDESC_FLAG_ENUMERABLE;
			}
			if (has_configurable && is_configurable) {
				new_flags |= DUK_PROPDESC_FLAG_CONFIGURABLE;
			}

			if (arr_idx != NO_ARRAY_INDEX && DUK_HOBJECT_HAS_ARRAY_PART(obj)) {
				DUK_DDDPRINT("accessor cannot go to array part, abandon array");
				abandon_array_checked(thr, obj);
			}

			/* write to entry part */
			e_idx = alloc_entry_checked(thr, obj, key);
			DUK_ASSERT(e_idx >= 0);

			DUK_HOBJECT_E_SET_VALUE_GETTER(obj, e_idx, get);
			DUK_HOBJECT_E_SET_VALUE_SETTER(obj, e_idx, set);
			DUK_HOBJECT_INCREF(thr, get);
			DUK_HOBJECT_INCREF(thr, set);

			DUK_HOBJECT_E_SET_FLAGS(obj, e_idx, new_flags);
			goto success_specials;
		} else {
			int e_idx;
			duk_tval *tv2;

			DUK_DDDPRINT("create new data property");

			DUK_ASSERT(!has_set);
			DUK_ASSERT(!has_get);

			new_flags = 0;  /* defaults, E5 Section 8.6.1, Table 7 */
			if (has_writable && is_writable) {
				new_flags |= DUK_PROPDESC_FLAG_WRITABLE;
			}
			if (has_enumerable && is_enumerable) {
				new_flags |= DUK_PROPDESC_FLAG_ENUMERABLE;
			}
			if (has_configurable && is_configurable) {
				new_flags |= DUK_PROPDESC_FLAG_CONFIGURABLE;
			}
			if (has_value) {
				duk_tval *tv_tmp = duk_require_tval(ctx, idx_value);
				DUK_TVAL_SET_TVAL(&tv, tv_tmp);
			} else {
				DUK_TVAL_SET_UNDEFINED_ACTUAL(&tv);  /* default value */
			}

			if (arr_idx != NO_ARRAY_INDEX && DUK_HOBJECT_HAS_ARRAY_PART(obj)) {
				if (new_flags == DUK_PROPDESC_FLAGS_WEC) {
#if 0
					DUK_DDDPRINT("new data property attributes match array defaults, attempt to write to array part");
					/* may become sparse...*/
#endif
					/* FIXME: handling for array part missing now; this doesn't affect
					 * compliance but causes array entry writes using defineProperty()
					 * to always abandon array part.
					 */
				}
				DUK_DDDPRINT("new data property cannot go to array part, abandon array");
				abandon_array_checked(thr, obj);
				/* fall through */
			}

			/* write to entry part */
			e_idx = alloc_entry_checked(thr, obj, key);
			DUK_ASSERT(e_idx >= 0);
			tv2 = DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(obj, e_idx);
			DUK_TVAL_SET_TVAL(tv2, &tv);
			DUK_TVAL_INCREF(thr, tv2);

			DUK_HOBJECT_E_SET_FLAGS(obj, e_idx, new_flags);
			goto success_specials;
		}
		DUK_NEVER_HERE();
	}

	/* we currently assume virtual properties are not configurable (as none of them are) */
	DUK_ASSERT((curr.e_idx >= 0 || curr.a_idx >= 0) || !(curr.flags & DUK_PROPDESC_FLAG_CONFIGURABLE));

	/* [obj key desc value get set curr_value] */

	/*
	 *  Property already exists.  Steps 5-6 detect whether any changes need
	 *  to be made.
	 */

	if (has_enumerable) {
		if (is_enumerable) {
			if (!(curr.flags & DUK_PROPDESC_FLAG_ENUMERABLE)) {
				goto need_check;
			}
		} else {
			if (curr.flags & DUK_PROPDESC_FLAG_ENUMERABLE) {
				goto need_check;
			}
		}
	}
	if (has_configurable) {
		if (is_configurable) {
			if (!(curr.flags & DUK_PROPDESC_FLAG_CONFIGURABLE)) {
				goto need_check;
			}
		} else {
			if (curr.flags & DUK_PROPDESC_FLAG_CONFIGURABLE) {
				goto need_check;
			}
		}
	}
	if (has_value) {
		duk_tval *tmp1;
		duk_tval *tmp2;
	
		/* attempt to change from accessor to data property */
		if (curr.flags & DUK_PROPDESC_FLAG_ACCESSOR) {
			goto need_check;
		}

		tmp1 = duk_require_tval(ctx, -1);         /* curr value */
		tmp2 = duk_require_tval(ctx, idx_value);  /* new value */
		if (!duk_js_samevalue(tmp1, tmp2)) {
			goto need_check;
		}
	}
	if (has_writable) {
		/* attempt to change from accessor to data property */
		if (curr.flags & DUK_PROPDESC_FLAG_ACCESSOR) {
			goto need_check;
		}

		if (is_writable) {
			if (!(curr.flags & DUK_PROPDESC_FLAG_WRITABLE)) {
				goto need_check;
			}
		} else {
			if (curr.flags & DUK_PROPDESC_FLAG_WRITABLE) {
				goto need_check;
			}
		}
	}
	if (has_set) {
		if (curr.flags & DUK_PROPDESC_FLAG_ACCESSOR) {
			if (set != curr.set) {
				goto need_check;
			}
		} else {
			goto need_check;
		}
	}
	if (has_get) {
		if (curr.flags & DUK_PROPDESC_FLAG_ACCESSOR) {
			if (get != curr.get) {
				goto need_check;
			}
		} else {
			goto need_check;
		}
	}

	/* property exists, either 'desc' is empty, or all values
	 * match (SameValue)
	 */
	goto success_no_specials;

 need_check:

	/*
	 *  Some change(s) need to be made.  Steps 7-11.
	 */

	/* shared checks for all descriptor types */
	if (!(curr.flags & DUK_PROPDESC_FLAG_CONFIGURABLE)) {
		if (has_configurable && is_configurable) {
			goto fail_not_configurable;
		}
		if (has_enumerable) {
			if (curr.flags & DUK_PROPDESC_FLAG_ENUMERABLE) {
				if (!is_enumerable) {
					goto fail_not_configurable;
				}
			} else {
				if (is_enumerable) {
					goto fail_not_configurable;
				}
			}
		}
	}

	/* descriptor type specific checks */
	if (has_set || has_get) {
		/* IsAccessorDescriptor(desc) == true */
		DUK_ASSERT(!has_writable);
		DUK_ASSERT(!has_value);

		if (curr.flags & DUK_PROPDESC_FLAG_ACCESSOR) {
			/* curr and desc are accessors */
			if (!(curr.flags & DUK_PROPDESC_FLAG_CONFIGURABLE)) {
				if (has_set && set != curr.set) {
					goto fail_not_configurable;
				}
				if (has_get && get != curr.get) {
					goto fail_not_configurable;
				}
			}
		} else {
			int rc;
			duk_tval tv_tmp;
			duk_tval *tv1;

			/* curr is data, desc is accessor */
			if (!(curr.flags & DUK_PROPDESC_FLAG_CONFIGURABLE)) {
				goto fail_not_configurable;
			}

			DUK_DDDPRINT("convert property to accessor property");
			if (curr.a_idx >= 0) {
				int rc;

				DUK_DDDPRINT("property to convert is stored in an array entry, abandon array and re-lookup");
				abandon_array_checked(thr, obj);
				duk_pop(ctx);  /* remove old value */
				rc = get_own_property_desc_raw(thr, obj, key, arr_idx, &curr, 1);
				DUK_UNREF(rc);
				DUK_ASSERT(rc != 0);
				DUK_ASSERT(curr.e_idx >= 0 && curr.a_idx < 0);
			}

			DUK_ASSERT(!DUK_HOBJECT_E_SLOT_IS_ACCESSOR(obj, curr.e_idx));

			tv1 = DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(obj, curr.e_idx);
			DUK_TVAL_SET_TVAL(&tv_tmp, tv1);
			DUK_TVAL_SET_UNDEFINED_UNUSED(tv1);
			DUK_TVAL_DECREF(thr, &tv_tmp);

			DUK_HOBJECT_E_SET_VALUE_GETTER(obj, curr.e_idx, NULL);
			DUK_HOBJECT_E_SET_VALUE_SETTER(obj, curr.e_idx, NULL);
			DUK_HOBJECT_E_SLOT_CLEAR_WRITABLE(obj, curr.e_idx);
			DUK_HOBJECT_E_SLOT_SET_ACCESSOR(obj, curr.e_idx);

			DUK_DDDPRINT("flags after data->accessor conversion: 0x%02x", (int) DUK_HOBJECT_E_GET_FLAGS(obj, curr.e_idx));

			/* re-lookup to update curr.flags -- FIXME: faster to update directly */
			duk_pop(ctx);  /* remove old value */
			rc = get_own_property_desc_raw(thr, obj, key, arr_idx, &curr, 1);
			DUK_UNREF(rc);
			DUK_ASSERT(rc != 0);
		}
	} else if (has_value || has_writable) {
		/* IsDataDescriptor(desc) == true */
		DUK_ASSERT(!has_set);
		DUK_ASSERT(!has_get);

		if (curr.flags & DUK_PROPDESC_FLAG_ACCESSOR) {
			int rc;
			duk_hobject *tmp;

			/* curr is accessor, desc is data */
			if (!(curr.flags & DUK_PROPDESC_FLAG_CONFIGURABLE)) {
				goto fail_not_configurable;
			}

			/* curr is accessor -> cannot be in array part */
			DUK_ASSERT(curr.e_idx >= 0 && curr.a_idx < 0);

			DUK_DDDPRINT("convert property to data property");

			DUK_ASSERT(DUK_HOBJECT_E_SLOT_IS_ACCESSOR(obj, curr.e_idx));
			tmp = DUK_HOBJECT_E_GET_VALUE_GETTER(obj, curr.e_idx);
			DUK_UNREF(tmp);
			DUK_HOBJECT_E_SET_VALUE_GETTER(obj, curr.e_idx, NULL);
			DUK_HOBJECT_DECREF(thr, tmp);
			tmp = DUK_HOBJECT_E_GET_VALUE_SETTER(obj, curr.e_idx);
			DUK_UNREF(tmp);
			DUK_HOBJECT_E_SET_VALUE_SETTER(obj, curr.e_idx, NULL);
			DUK_HOBJECT_DECREF(thr, tmp);

			DUK_TVAL_SET_UNDEFINED_ACTUAL(DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(obj, curr.e_idx));
			DUK_HOBJECT_E_SLOT_CLEAR_WRITABLE(obj, curr.e_idx);
			DUK_HOBJECT_E_SLOT_CLEAR_ACCESSOR(obj, curr.e_idx);

			DUK_DDDPRINT("flags after accessor->data conversion: 0x%02x", (int) DUK_HOBJECT_E_GET_FLAGS(obj, curr.e_idx));

			/* re-lookup to update curr.flags -- FIXME: faster to update directly */
			duk_pop(ctx);  /* remove old value */
			rc = get_own_property_desc_raw(thr, obj, key, arr_idx, &curr, 1);
			DUK_UNREF(rc);
			DUK_ASSERT(rc != 0);
		} else {
			/* curr and desc are data */
			if (!(curr.flags & DUK_PROPDESC_FLAG_CONFIGURABLE)) {
				if (!(curr.flags & DUK_PROPDESC_FLAG_WRITABLE) && has_writable && is_writable) {
					goto fail_not_configurable;
				}
				/* Note: changing from writable to non-writable is OK */
				if (!(curr.flags & DUK_PROPDESC_FLAG_WRITABLE) && has_value) {
					duk_tval *tmp1 = duk_require_tval(ctx, -1);         /* curr value */
					duk_tval *tmp2 = duk_require_tval(ctx, idx_value);  /* new value */
					if (!duk_js_samevalue(tmp1, tmp2)) {
						goto fail_not_configurable;
					}
				}
			}
		}
	} else {
		/* IsGenericDescriptor(desc) == true; this means in practice that 'desc'
		 * only has [[Enumerable]] or [[Configurable]] flag updates, which are
		 * allowed at this point.
		 */

		DUK_ASSERT(!has_value && !has_writable && !has_get && !has_set);
	}

	/*
	 *  Start doing property attributes updates.  Steps 12-13.
	 *
	 *  Start by computing new attribute flags without writing yet.
	 *  Property type conversion is done above if necessary.
	 */

	new_flags = curr.flags;

	if (has_enumerable) {
		if (is_enumerable) {
			new_flags |= DUK_PROPDESC_FLAG_ENUMERABLE;
		} else {
			new_flags &= ~DUK_PROPDESC_FLAG_ENUMERABLE;
		}
	}
	if (has_configurable) {
		if (is_configurable) {
			new_flags |= DUK_PROPDESC_FLAG_CONFIGURABLE;
		} else {
			new_flags &= ~DUK_PROPDESC_FLAG_CONFIGURABLE;
		}
	}
	if (has_writable) {
		if (is_writable) {
			new_flags |= DUK_PROPDESC_FLAG_WRITABLE;
		} else {
			new_flags &= ~DUK_PROPDESC_FLAG_WRITABLE;
		}
	}

	/* FIXME: write protect after flag? -> any chance of handling it here? */

	DUK_DDDPRINT("new flags that we want to write: 0x%02x", new_flags);

	/*
	 *  Check whether we need to abandon an array part (if it exists)
	 */

	if (curr.a_idx >= 0) {
		int rc;

		DUK_ASSERT(curr.e_idx < 0);

		if (new_flags == DUK_PROPDESC_FLAGS_WEC) {
			duk_tval *tv1, *tv2;
			duk_tval tv_tmp;

			DUK_DDDPRINT("array index, new property attributes match array defaults, update in-place");

			DUK_ASSERT(curr.flags == DUK_PROPDESC_FLAGS_WEC);  /* must have been, since in array part */
			DUK_ASSERT(!has_set);
			DUK_ASSERT(!has_get);

			tv2 = duk_require_tval(ctx, idx_value);
			tv1 = DUK_HOBJECT_A_GET_VALUE_PTR(obj, curr.a_idx);
			DUK_TVAL_SET_TVAL(&tv_tmp, tv1);
			DUK_TVAL_SET_TVAL(tv1, tv2);
			DUK_TVAL_INCREF(thr, tv1);
			DUK_TVAL_DECREF(thr, &tv_tmp);
			goto success_specials;
		}

		DUK_DDDPRINT("array index, new property attributes do not match array defaults, abandon array and re-lookup");
		abandon_array_checked(thr, obj);
		duk_pop(ctx);  /* remove old value */
		rc = get_own_property_desc_raw(thr, obj, key, arr_idx, &curr, 1);
		DUK_UNREF(rc);
		DUK_ASSERT(rc != 0);
		DUK_ASSERT(curr.e_idx >= 0 && curr.a_idx < 0);
	}

	DUK_DDDPRINT("updating existing property in entry part");

	/* array case is handled comprehensively above */
	DUK_ASSERT(curr.e_idx >= 0 && curr.a_idx < 0);

	DUK_DDDPRINT("update existing property attributes");
	DUK_HOBJECT_E_SET_FLAGS(obj, curr.e_idx, new_flags);

	if (has_set) {
		duk_hobject *tmp;

		DUK_DDDPRINT("update existing property setter");
		DUK_ASSERT(DUK_HOBJECT_E_SLOT_IS_ACCESSOR(obj, curr.e_idx));

		tmp = DUK_HOBJECT_E_GET_VALUE_SETTER(obj, curr.e_idx);
		DUK_UNREF(tmp);
		DUK_HOBJECT_E_SET_VALUE_SETTER(obj, curr.e_idx, set);
		DUK_HOBJECT_INCREF(thr, set);
		DUK_HOBJECT_DECREF(thr, tmp);
	}
	if (has_get) {
		duk_hobject *tmp;

		DUK_DDDPRINT("update existing property getter");
		DUK_ASSERT(DUK_HOBJECT_E_SLOT_IS_ACCESSOR(obj, curr.e_idx));

		tmp = DUK_HOBJECT_E_GET_VALUE_GETTER(obj, curr.e_idx);
		DUK_UNREF(tmp);
		DUK_HOBJECT_E_SET_VALUE_GETTER(obj, curr.e_idx, get);
		DUK_HOBJECT_INCREF(thr, get);
		DUK_HOBJECT_DECREF(thr, tmp);
	}
	if (has_value) {
		duk_tval *tv1, *tv2;
		duk_tval tv_tmp;

		DUK_DDDPRINT("update existing property value");
		DUK_ASSERT(!DUK_HOBJECT_E_SLOT_IS_ACCESSOR(obj, curr.e_idx));

		tv2 = duk_require_tval(ctx, idx_value);
		tv1 = DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(obj, curr.e_idx);
		DUK_TVAL_SET_TVAL(&tv_tmp, tv1);
		DUK_TVAL_SET_TVAL(tv1, tv2);
		DUK_TVAL_INCREF(thr, tv1);
		DUK_TVAL_DECREF(thr, &tv_tmp);
	}

	/*
	 *  Standard algorithm succeeded without errors, check for special post-behaviors.
	 *
	 *  Arguments special behavior in E5 Section 10.6 occurs after the standard
	 *  [[DefineOwnProperty]] has completed successfully.
	 *
	 *  Array special behavior in E5 Section 15.4.5.1 is implemented partly
	 *  prior to the default [[DefineOwnProperty]], but:
	 *    - for an array index key (e.g. "10") the final 'length' update occurs here
	 *    - for 'length' key the element deletion and 'length' update occurs here
	 */

 success_specials:

	/* [obj key desc value get set curr_value] */

	if (DUK_HOBJECT_HAS_SPECIAL_ARRAY(obj)) {
		if (arridx_new_array_length > 0) {
			duk_tval *tmp;
			int rc;

			/*
			 *  Note: zero works as a "no update" marker because the new length
			 *  can never be zero after a new property is written.
			 */

			/* E5 Section 15.4.5.1, steps 4.e.i - 4.e.ii */

			DUK_DDDPRINT("defineProperty successful, pending array length update to: %d", arridx_new_array_length);

			/* Note: reuse 'curr' */
			rc = get_own_property_desc_raw(thr, obj, DUK_HTHREAD_STRING_LENGTH(thr), NO_ARRAY_INDEX, &curr, 0);
			DUK_UNREF(rc);
			DUK_ASSERT(rc != 0);
			DUK_ASSERT(curr.e_idx >= 0);

			tmp = DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(obj, curr.e_idx);
			DUK_ASSERT(DUK_TVAL_IS_NUMBER(tmp));
			DUK_TVAL_SET_NUMBER(tmp, (double) arridx_new_array_length);  /* no need for decref/incref because value is a number */
		}
		if (key == DUK_HTHREAD_STRING_LENGTH(thr) && arrlen_new_len < arrlen_old_len) {
			/*
			 *  E5 Section 15.4.5.1, steps 3.k - 3.n.  The order at the end combines
			 *  the error case 3.l.iii and the success case 3.m-3.n.
			 *
			 *  Note: 'length' is always in entries part, so no array abandon issues for
			 *  'writable' update.
			 */

			/* FIXME: investigate whether write protect can be handled above, if we
			 * just update length here while ignoring its protected status
			 */

			duk_tval *tmp;
			duk_u32 result_len;
			int rc;

			DUK_DDDPRINT("defineProperty successful, key is 'length', special array behavior, "
			             "doing array element deletion and length update");

			rc = handle_put_array_length_smaller(thr, obj, arrlen_old_len, arrlen_new_len, &result_len);

			/* update length (curr points to length, and we assume it's still valid) */
			DUK_ASSERT(result_len >= arrlen_new_len && result_len <= arrlen_old_len);

			DUK_ASSERT(curr.e_idx >= 0);
			DUK_ASSERT(!DUK_HOBJECT_E_SLOT_IS_ACCESSOR(obj, curr.e_idx));
			tmp = DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(obj, curr.e_idx);
			DUK_ASSERT(DUK_TVAL_IS_NUMBER(tmp));
			DUK_TVAL_SET_NUMBER(tmp, (double) result_len);  /* no decref needed for a number */
			DUK_ASSERT(DUK_TVAL_IS_NUMBER(tmp));

			if (pending_write_protect) {
				DUK_DDDPRINT("setting array length non-writable (pending writability update)");
				DUK_HOBJECT_E_SLOT_CLEAR_WRITABLE(obj, curr.e_idx);
			}

			/*
			 *  FIXME: shrink array allocation or entries compaction here?
			 */

			if (!rc) {
				goto fail_array_length_partial;
			}
		}
	} else if (arr_idx != NO_ARRAY_INDEX && DUK_HOBJECT_HAS_SPECIAL_ARGUMENTS(obj)) {
		duk_hobject *map;
		duk_hobject *varenv;

		DUK_ASSERT(arridx_new_array_length == 0);
		DUK_ASSERT(!DUK_HOBJECT_HAS_SPECIAL_ARRAY(obj));  /* traits are separate; in particular, arguments not an array */

		map = NULL;
		varenv = NULL;
		if (!lookup_arguments_map(thr, obj, key, &curr, &map, &varenv)) {
			goto success_no_specials;
		}
		DUK_ASSERT(map != NULL);
		DUK_ASSERT(varenv != NULL);

		/* [obj key desc value get set curr_value varname] */

		if (has_set || has_get) {
			/* = IsAccessorDescriptor(Desc) */
			DUK_DDDPRINT("defineProperty successful, key mapped to arguments 'map' "
			             "changed to an accessor, delete arguments binding");

			(void) duk_hobject_delprop_raw(thr, map, key, 0);  /* ignore result */
		} else {
			/* Note: this order matters (final value before deleting map entry must be done) */
			DUK_DDDPRINT("defineProperty successful, key mapped to arguments 'map', "
			             "check for value update / binding deletion");

			if (has_value) {
				duk_hstring *varname;

				DUK_DDDPRINT("defineProperty successful, key mapped to arguments 'map', "
				             "update bound value (variable/argument)");

				varname = duk_require_hstring(ctx, -1);
				DUK_ASSERT(varname != NULL);

				DUK_DDDPRINT("arguments object automatic putvar for a bound variable; "
				             "key=%!O, varname=%!O, value=%!T",
				             (duk_heaphdr *) key,
				             (duk_heaphdr *) varname,
				             duk_require_tval(ctx, idx_value));

				/* strict flag for putvar comes from our caller (currently: fixed) */
				duk_js_putvar_envrec(thr, varenv, varname, duk_require_tval(ctx, idx_value), throw_flag);
			}
			if (has_writable && !is_writable) {
				DUK_DDDPRINT("defineProperty successful, key mapped to arguments 'map', "
				             "changed to non-writable, delete arguments binding");

				(void) duk_hobject_delprop_raw(thr, map, key, 0);  /* ignore result */
			}
		}

		/* 'varname' is in stack in this else branch, leaving an unbalanced stack below,
		 * but this doesn't matter now.
		 */
	}

 success_no_specials:
	/* no need to unwind stack (rewound automatically) */
	duk_set_top(ctx, 1);  /* -> [ obj ] */
	return 1;

 fail_invalid_desc:
	DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, "invalid descriptor");
	return 0;

 fail_not_writable_array_length:
	DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, "array length not writable");
	return 0;

 fail_not_extensible:
	DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, "object not extensible");
	return 0;

 fail_not_configurable:
	DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, "property not configurable");
	return 0;

 fail_array_length_partial:
	DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, "array length write failed");
	return 0;
}

/*
 *  Object.defineProperties()  (E5 Section 15.2.3.7)
 *
 *  This is an actual function call.
 */

int duk_hobject_object_define_properties(duk_context *ctx) {
	duk_require_hobject(ctx, 0);  /* target */
	duk_to_object(ctx, 1);        /* properties object */

	DUK_DDDPRINT("target=%!iT, properties=%!iT", duk_get_tval(ctx, 0), duk_get_tval(ctx, 1));

	duk_push_object(ctx);
	duk_enum(ctx, 1, DUK_ENUM_OWN_PROPERTIES_ONLY /*enum_flags*/);

	/* [hobject props descriptors enum(props)] */

	DUK_DDDPRINT("enum(properties)=%!iT", duk_get_tval(ctx, 3));

	for (;;) {
		if (!duk_next(ctx, 3, 1 /*get_value*/)) {
			break;
		}

		DUK_DDDPRINT("-> key=%!iT, desc=%!iT", duk_get_tval(ctx, -2), duk_get_tval(ctx, -1));

		/* [hobject props descriptors enum(props) key desc] */

		normalize_property_descriptor(ctx);
		
		/* [hobject props descriptors enum(props) key desc_norm] */

		duk_put_prop(ctx, 2);

		/* [hobject props descriptors enum(props)] */
	}

	DUK_DDDPRINT("-> descriptors=%!iT, desc=%!iT", duk_get_tval(ctx, 2));

	/* We rely on 'descriptors' having the same key order as 'props'
	 * to match the array semantics of E5 Section 15.2.3.7.
	 */

	duk_pop(ctx);
	duk_enum(ctx, 2, 0 /*enum_flags*/);

	/* [hobject props descriptors enum(descriptors)] */

	DUK_DDDPRINT("enum(descriptors)=%!iT", duk_get_tval(ctx, 3));

	for (;;) {
		if (!duk_next(ctx, 3, 1 /*get_value*/)) {
			break;
		}

		DUK_DDDPRINT("-> key=%!iT, desc=%!iT", duk_get_tval(ctx, -2), duk_get_tval(ctx, -1));

		/* [hobject props descriptors enum(descriptors) key desc_norm] */

		duk_dup(ctx, 0);
		duk_insert(ctx, -3);

		/* [hobject props descriptors enum(descriptors) hobject key desc_norm] */

		/* FIXME: need access to the -original- Object.defineProperty function
		 * object here (the property is configurable so a caller may have changed
		 * it).  This is not a good approach.
		 */
		duk_push_c_function(ctx, duk_hobject_object_define_property, 3);
		duk_insert(ctx, -4);

		/* [hobject props descriptors enum(descriptors) Object.defineProperty hobject key desc_norm] */

		duk_call(ctx, 3);

		/* [hobject props descriptors enum(descriptors) retval] */

		/* FIXME: call which ignores result would be nice */

		duk_pop(ctx);
	}

	/* [hobject props descriptors enum(descriptors)] */

	duk_dup(ctx, 0);
	
	/* [hobject props descriptors enum(descriptors) hobject] */

	return 1;
}

/*
 *  Object.prototype.hasOwnProperty() and Object.prototype.propertyIsEnumerable().
 */

int duk_hobject_object_ownprop_helper(duk_context *ctx, int required_desc_flags) {
	duk_hthread *thr = (duk_hthread *) ctx;
	duk_hstring *h_v;
	duk_hobject *h_obj;
	duk_propdesc desc;
	int ret;

	/* coercion order matters */
	h_v = duk_to_hstring(ctx, 0);
	DUK_ASSERT(h_v != NULL);

	duk_push_this_coercible_to_object(ctx);
	h_obj = duk_get_hobject(ctx, 1);
	DUK_ASSERT(h_obj != NULL);

	ret = get_own_property_desc(thr, h_obj, h_v, &desc, 0 /*push_value*/);

	duk_push_boolean(ctx, ret && ((desc.flags & required_desc_flags) == required_desc_flags));
	return 1;
}

/* FIXME change these into actual function calls? */

/*
 *  Object.seal() and Object.freeze()  (E5 Sections 15.2.3.8 and 15.2.3.9)
 * 
 *  Since the algorithms are similar, a helper provides both functions.
 *  Freezing is essentially sealing + making plain properties non-writable.
 *
 *  Note: virtual (non-concrete) properties which are non-configurable but
 *  writable would pose some problems, but such properties do not currently
 *  exist (all virtual properties are non-configurable and non-writable).
 *  If they did exist, the non-configurability does NOT prevent them from
 *  becoming non-writable.  However, this change should be recorded somehow
 *  so that it would turn up (e.g. when getting the property descriptor),
 *  requiring some additional flags in the object.
 */

void duk_hobject_object_seal_freeze_helper(duk_hthread *thr, duk_hobject *obj, int freeze) {
	int i;

	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(thr->heap != NULL);
	DUK_ASSERT(obj != NULL);

	ASSERT_VALSTACK_SPACE(thr, VALSTACK_SPACE);

	/*
	 *  Abandon array part because all properties must become non-configurable.
	 *  Note that this is now done regardless of whether this is always the case
	 *  (skips check, but performance problem if caller would do this many times
	 *  for the same object; not likely).
	 */

	abandon_array_checked(thr, obj);
	DUK_ASSERT(obj->a_size == 0);

	for (i = 0; i < obj->e_used; i++) {
		duk_u8 *fp;

		/* since abandon_array_checked() causes a resize, there should be no gaps in keys */
		DUK_ASSERT(DUK_HOBJECT_E_GET_KEY(obj, i) != NULL);

		/* avoid multiple computations of flags address; bypasses macros */
		fp = DUK_HOBJECT_E_GET_FLAGS_PTR(obj, i);
		if (freeze && !((*fp) & DUK_PROPDESC_FLAG_ACCESSOR)) {
			*fp &= ~(DUK_PROPDESC_FLAG_WRITABLE | DUK_PROPDESC_FLAG_CONFIGURABLE);
		} else {
			*fp &= ~DUK_PROPDESC_FLAG_CONFIGURABLE;
		}
	}

	DUK_HOBJECT_CLEAR_EXTENSIBLE(obj);

	/* no need to compact since we already did that in abandon_array_checked()
	 * (regardless of whether an array part existed or not.
	 */

	return;
}

/*
 *  Object.isSealed() and Object.isFrozen()  (E5 Sections 15.2.3.11, 15.2.3.13)
 *
 *  Since the algorithms are similar, a helper provides both functions.
 *  Freezing is essentially sealing + making plain properties non-writable.
 *
 *  Note: all virtual (non-concrete) properties are currently non-configurable
 *  and non-writable (and there are no accessor virtual properties), so they don't
 *  need to be considered here now.
 */

int duk_hobject_object_is_sealed_frozen_helper(duk_hobject *obj, int is_frozen) {
	int i;

	DUK_ASSERT(obj != NULL);

	/* Note: no allocation pressure, no need to check refcounts etc */

	/* must not be extensible */
	if (DUK_HOBJECT_HAS_EXTENSIBLE(obj)) {
		return 0;
	}

	/* all virtual properties are non-configurable and non-writable */

	/* entry part must not contain any configurable properties, or
	 * writable properties (if is_frozen).
	 */
	for (i = 0; i < obj->e_used; i++) {
		unsigned int flags;

		if (!DUK_HOBJECT_E_GET_KEY(obj, i)) {
			continue;
		}

		/* avoid multiple computations of flags address; bypasses macros */
		flags = (unsigned int) DUK_HOBJECT_E_GET_FLAGS(obj, i);

		if (flags & DUK_PROPDESC_FLAG_CONFIGURABLE) {
			return 0;
		}
		if (is_frozen &&
		    !(flags & DUK_PROPDESC_FLAG_ACCESSOR) &&
		    (flags & DUK_PROPDESC_FLAG_WRITABLE)) {
			return 0;
		}
	}

	/* array part must not contain any non-unused properties, as they would
	 * be configurable and writable.
	 */
	for (i = 0; i < obj->a_size; i++) {
		duk_tval *tv = DUK_HOBJECT_A_GET_VALUE_PTR(obj, i);
		if (!DUK_TVAL_IS_UNDEFINED_UNUSED(tv)) {
			return 0;
		}
	}

	return 1;
}

/*
 *  Object.preventExtensions() and Object.isExtensible()  (E5 Sections 15.2.3.10, 15.2.3.13)
 *
 *  Implemented directly in macros:
 *
 *    DUK_HOBJECT_OBJECT_PREVENT_EXTENSIONS()
 *    DUK_HOBJECT_OBJECT_IS_EXTENSIBLE()
 */

/* Undefine local defines */

#undef  NO_ARRAY_INDEX
#undef  HASH_INITIAL
#undef  HASH_PROBE_STEP
#undef  HASH_UNUSED
#undef  HASH_DELETED
#undef  VALSTACK_SPACE

#line 1 "duk_hthread_alloc.c"
/*
 *  duk_hthread allocation and freeing.
 */

/* include removed: duk_internal.h */

/*
 *  Allocate initial stacks for a thread.  Note that 'thr' must be reachable
 *  as a garbage collection may be triggered by the allocation attempts.
 *  Returns zero (without leaking memory) if init fails.
 */

int duk_hthread_init_stacks(duk_heap *heap, duk_hthread *thr) {
	size_t alloc_size;
	int i;

	DUK_ASSERT(heap != NULL);
	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(thr->valstack == NULL);
	DUK_ASSERT(thr->valstack_end == NULL);
	DUK_ASSERT(thr->valstack_bottom == NULL);
	DUK_ASSERT(thr->valstack_top == NULL);
	DUK_ASSERT(thr->callstack == NULL);
	DUK_ASSERT(thr->catchstack == NULL);

	/* valstack */
	alloc_size = sizeof(duk_tval) * DUK_VALSTACK_INITIAL_SIZE;
	thr->valstack = DUK_ALLOC(heap, alloc_size);
	if (!thr->valstack) {
		goto fail;
	}
	memset(thr->valstack, 0, alloc_size);
	thr->valstack_end = thr->valstack + DUK_VALSTACK_INITIAL_SIZE;
	thr->valstack_bottom = thr->valstack;
	thr->valstack_top = thr->valstack;

	for (i = 0; i < DUK_VALSTACK_INITIAL_SIZE; i++) {
		DUK_TVAL_SET_UNDEFINED_UNUSED(&thr->valstack[i]);
	}

	/* callstack */
	alloc_size = sizeof(duk_activation) * DUK_CALLSTACK_INITIAL_SIZE;
	thr->callstack = DUK_ALLOC(heap, alloc_size);
	if (!thr->callstack) {
		goto fail;
	}
	memset(thr->callstack, 0, alloc_size);
	thr->callstack_size = DUK_CALLSTACK_INITIAL_SIZE;
	DUK_ASSERT(thr->callstack_top == 0);

	/* catchstack */
	alloc_size = sizeof(duk_catcher) * DUK_CATCHSTACK_INITIAL_SIZE;
	thr->catchstack = DUK_ALLOC(heap, alloc_size);
	if (!thr->catchstack) {
		goto fail;
	}
	memset(thr->catchstack, 0, alloc_size);
	thr->catchstack_size = DUK_CATCHSTACK_INITIAL_SIZE;
	DUK_ASSERT(thr->catchstack_top == 0);

	return 1;

 fail:
	DUK_FREE(heap, thr->valstack);
	DUK_FREE(heap, thr->callstack);
	DUK_FREE(heap, thr->catchstack);

	thr->valstack = NULL;
	thr->callstack = NULL;
	thr->catchstack = NULL;
	return 0;
}

#line 1 "duk_hthread_builtins.c"
/*
 *  Initialize built-in objects.  Current thread must have a valstack
 *  and initialization errors may longjmp, so a setjmp() catch point
 *  must exist.
 */

/* include removed: duk_internal.h */

/*
 *  Helper union to convert between raw bytes and a double portably.
 */

typedef union {
	unsigned char b[8];
	double d;
} duk_double_and_bytes;

/*
 *  Encoding constants, must match genbuiltins.py
 */

#define  CLASS_BITS                  4
#define  BIDX_BITS                   6
#define  STRIDX_BITS                 9  /* FIXME: try to optimize to 8 */
#define  NATIDX_BITS                 8
#define  NUM_NORMAL_PROPS_BITS       6
#define  NUM_FUNC_PROPS_BITS         6
#define  PROP_FLAGS_BITS             3
#define  STRING_LENGTH_BITS          8
#define  STRING_CHAR_BITS            7
#define  LENGTH_PROP_BITS            3
#define  NARGS_BITS                  3
#define  PROP_TYPE_BITS              3

#define  NARGS_VARARGS_MARKER        0x07
#define  NO_CLASS_MARKER             0x00   /* 0 = DUK_HOBJECT_CLASS_UNUSED */
#define  NO_BIDX_MARKER              0x3f
#define  NO_STRIDX_MARKER            0xff

#define  PROP_TYPE_DOUBLE            0
#define  PROP_TYPE_STRING            1
#define  PROP_TYPE_STRIDX            2
#define  PROP_TYPE_BUILTIN           3
#define  PROP_TYPE_UNDEFINED         4
#define  PROP_TYPE_BOOLEAN_TRUE      5
#define  PROP_TYPE_BOOLEAN_FALSE     6

/*
 *  Create built-in objects by parsing an init bitstream generated
 *  by genbuiltins.py.
 */

void duk_hthread_create_builtin_objects(duk_hthread *thr) {
	duk_context *ctx = (duk_context *) thr;
	duk_bitdecoder_ctx bd_ctx;
	duk_bitdecoder_ctx *bd = &bd_ctx;  /* convenience */
	duk_hobject *h;
	int i, j;

	DUK_DPRINT("INITBUILTINS BEGIN");

	memset(&bd_ctx, 0, sizeof(bd_ctx));
	bd->data = (duk_u8 *) duk_builtins_data;
	bd->length = DUK_BUILTINS_DATA_LENGTH;

	/*
	 *  First create all built-in bare objects on the empty valstack.
	 *  During init, their indices will correspond to built-in indices.
	 *
	 *  Built-ins will be reachable from both valstack and thr->builtins.
	 */

	DUK_DDPRINT("create empty built-ins");
	DUK_ASSERT_TOP(ctx, 0);
	for (i = 0; i < DUK_NUM_BUILTINS; i++) {
		int class_num;
		int len = -1;

		class_num = duk_bd_decode(bd, CLASS_BITS);

		if (duk_bd_decode_flag(bd)) {
			len = duk_bd_decode(bd, LENGTH_PROP_BITS);
		}

		if (class_num == DUK_HOBJECT_CLASS_FUNCTION) {
			int natidx;
			int stridx;
			int c_nargs;
			duk_c_function c_func;

			DUK_DDDPRINT("len=%d", len);
			DUK_ASSERT(len >= 0);

			natidx = duk_bd_decode(bd, NATIDX_BITS);
			stridx = duk_bd_decode(bd, STRIDX_BITS);
			c_func = duk_builtin_native_functions[natidx];

			c_nargs = len;
			if (duk_bd_decode_flag(bd)) {
				c_nargs = duk_bd_decode(bd, NARGS_BITS);
				if (c_nargs == NARGS_VARARGS_MARKER) {
					c_nargs = DUK_VARARGS;
				}
			}

			duk_push_c_function(ctx, c_func, c_nargs);

			h = duk_require_hobject(ctx, -1);
			DUK_ASSERT(h != NULL);

			/* Currently all built-in native functions are strict.
			 * duk_push_c_function() now sets strict flag, so
			 * assert for it.
			 */
			DUK_ASSERT(DUK_HOBJECT_HAS_STRICT(h));

			/* FIXME: function properties */

			duk_push_hstring_stridx(ctx, stridx);
			duk_def_prop_stridx(ctx, -2, DUK_STRIDX_NAME, DUK_PROPDESC_FLAGS_NONE);

			/* Almost all global level Function objects are constructable
			 * but not all: Function.prototype is a non-constructable,
			 * callable Function.
			 */
			if (duk_bd_decode_flag(bd)) {
				DUK_HOBJECT_SET_CONSTRUCTABLE(h);
			}
		} else {
			/* FIXME: ARRAY_PART for Array prototype? */

			duk_push_object_helper(ctx,
			                       DUK_HOBJECT_FLAG_EXTENSIBLE,
			                       -1);  /* no prototype or class yet */

			h = duk_require_hobject(ctx, -1);
			DUK_ASSERT(h != NULL);
		}

		DUK_HOBJECT_SET_CLASS_NUMBER(h, class_num);

		thr->builtins[i] = h;
		DUK_HOBJECT_INCREF(thr, &h->hdr);

		if (len >= 0) {
			/*
			 *  For top-level objects, 'length' property has the following
			 *  default attributes: non-writable, non-enumerable, non-configurable
			 *  (E5 Section 15).
			 *
			 *  However, 'length' property for Array.prototype has attributes
			 *  expected of an Array instance which are different: writable,
			 *  non-enumerable, non-configurable (E5 Section 15.4.5.2).
			 *
			 *  This is currently determined implicitly based on class; there are
			 *  no attribute flags in the init data.
			 */

			duk_push_int(ctx, len);
			duk_def_prop_stridx(ctx,
			                    -2,
			                    DUK_STRIDX_LENGTH,
			                    (class_num == DUK_HOBJECT_CLASS_ARRAY ?  /* only Array.prototype matches */
			                     DUK_PROPDESC_FLAGS_W : DUK_PROPDESC_FLAGS_NONE));
		}

		/* enable special behaviors last */

		if (class_num == DUK_HOBJECT_CLASS_ARRAY) {
			DUK_HOBJECT_SET_SPECIAL_ARRAY(h);
		}
		if (class_num == DUK_HOBJECT_CLASS_STRING) {
			DUK_HOBJECT_SET_SPECIAL_STRINGOBJ(h);
		}

		/* some assertions */

		DUK_ASSERT(DUK_HOBJECT_HAS_EXTENSIBLE(h));
		/* DUK_HOBJECT_FLAG_CONSTRUCTABLE varies */
		DUK_ASSERT(!DUK_HOBJECT_HAS_BOUND(h));
		DUK_ASSERT(!DUK_HOBJECT_HAS_COMPILEDFUNCTION(h));
		/* DUK_HOBJECT_FLAG_NATIVEFUNCTION varies */
		DUK_ASSERT(!DUK_HOBJECT_HAS_THREAD(h));
		DUK_ASSERT(!DUK_HOBJECT_HAS_ARRAY_PART(h));       /* currently, even for Array.prototype */
		/* DUK_HOBJECT_FLAG_STRICT varies */
		DUK_ASSERT(!DUK_HOBJECT_HAS_NATIVEFUNCTION(h) ||  /* all native functions have NEWENV */
		           DUK_HOBJECT_HAS_NEWENV(h));
		DUK_ASSERT(!DUK_HOBJECT_HAS_NAMEBINDING(h));
		DUK_ASSERT(!DUK_HOBJECT_HAS_CREATEARGS(h));
		DUK_ASSERT(!DUK_HOBJECT_HAS_ENVRECCLOSED(h));
		/* DUK_HOBJECT_FLAG_SPECIAL_ARRAY varies */
		/* DUK_HOBJECT_FLAG_SPECIAL_STRINGOBJ varies */
		DUK_ASSERT(!DUK_HOBJECT_HAS_SPECIAL_ARGUMENTS(h));

		DUK_DDDPRINT("created built-in %d, class=%d, length=%d", i, class_num, len);
	}

	/*
	 *  Then decode the builtins init data (see genbuiltins.py) to
	 *  init objects
	 */

	DUK_DDPRINT("initialize built-in object properties");
	for (i = 0; i < DUK_NUM_BUILTINS; i++) {
		unsigned char t;
		int num;

		DUK_DDDPRINT("initializing built-in object at index %d", i);
		h = thr->builtins[i];

		t = duk_bd_decode(bd, BIDX_BITS);
		if (t != NO_BIDX_MARKER) {
			DUK_DDDPRINT("set prototype: built-in %d", (int) t);
			DUK_HOBJECT_SET_PROTOTYPE(thr, h, thr->builtins[t]);
		}

		t = duk_bd_decode(bd, BIDX_BITS);
		if (t != NO_BIDX_MARKER) {
			/* 'prototype' property for all built-in objects (which have it) has attributes:
			 *  [[Writable]] = false,
			 *  [[Enumerable]] = false,
			 *  [[Configurable]] = false
			 */
			DUK_DDDPRINT("set external prototype: built-in %d", (int) t);
			duk_def_prop_stridx_builtin(ctx, i, DUK_STRIDX_PROTOTYPE, t, DUK_PROPDESC_FLAGS_NONE);
		}

		t = duk_bd_decode(bd, BIDX_BITS);
		if (t != NO_BIDX_MARKER) {
			/* 'constructor' property for all built-in objects (which have it) has attributes:
			 *  [[Writable]] = true,
			 *  [[Enumerable]] = false,	
			 *  [[Configurable]] = true
			 */
			DUK_DDDPRINT("set external constructor: built-in %d", (int) t);
			duk_def_prop_stridx_builtin(ctx, i, DUK_STRIDX_CONSTRUCTOR, t, DUK_PROPDESC_FLAGS_WC);
		}

		/* normal valued properties */
		num = duk_bd_decode(bd, NUM_NORMAL_PROPS_BITS);
		DUK_DDDPRINT("built-in object %d, %d normal valued properties", i, num);
		for (j = 0; j < num; j++) {
			int stridx;
			int prop_flags;

			stridx = duk_bd_decode(bd, STRIDX_BITS);

			/*
			 *  Property attribute defaults are defined in E5 Section 15 (first
			 *  few pages); there is a default for all properties and a special
			 *  default for 'length' properties.  Variation from the defaults is
			 *  signaled using a single flag bit in the bitstream.
			 */

			if (duk_bd_decode_flag(bd)) {
				prop_flags = duk_bd_decode(bd, PROP_FLAGS_BITS);
			} else {
				if (stridx == DUK_STRIDX_LENGTH) {
					prop_flags = DUK_PROPDESC_FLAGS_NONE;
				} else {
					prop_flags = DUK_PROPDESC_FLAGS_WC;
				}
			}

			t = duk_bd_decode(bd, PROP_TYPE_BITS);

			DUK_DDDPRINT("built-in %d, normal-valued property %d, stridx %d, flags 0x%02x, type %d",
			             i, j, stridx, prop_flags, (int) t);

			switch (t) {
			case PROP_TYPE_DOUBLE: {
				duk_double_and_bytes tmp;
				int k;

				for (k = 0; k < 8; k++) {
					/* Encoding endianness must match target memory layout,
					 * build scripts and genbuiltins.py must ensure this.
					 */
					tmp.b[k] = duk_bd_decode(bd, 8);
				}

				duk_push_number(ctx, tmp.d);  /* push operation normalizes NaNs */
				break;
			}
			case PROP_TYPE_STRING: {
				int n;
				int k;
				char *p;

				n = duk_bd_decode(bd, STRING_LENGTH_BITS);
				p = (char *) duk_push_fixed_buffer(ctx, n);
				for (k = 0; k < n; k++) {
					*p++ = duk_bd_decode(bd, STRING_CHAR_BITS);
				}

				duk_to_string(ctx, -1);
				break;
			}
			case PROP_TYPE_STRIDX: {
				int n;

				n = duk_bd_decode(bd, STRIDX_BITS);
				DUK_ASSERT(n >= 0 && n < DUK_HEAP_NUM_STRINGS);
				duk_push_hstring_stridx(ctx, n);
				break;
			}
			case PROP_TYPE_BUILTIN: {
				int bidx;

				bidx = duk_bd_decode(bd, BIDX_BITS);
				DUK_ASSERT(bidx != NO_BIDX_MARKER);
				duk_dup(ctx, bidx);
				break;
			}
			case PROP_TYPE_UNDEFINED: {
				duk_push_undefined(ctx);
				break;
			}
			case PROP_TYPE_BOOLEAN_TRUE: {
				duk_push_true(ctx);
				break;
			}
			case PROP_TYPE_BOOLEAN_FALSE: {
				duk_push_false(ctx);
				break;
			}
			default: {
				/* exhaustive */
				DUK_NEVER_HERE();
			}
			}

			duk_def_prop_stridx(ctx, i, stridx, prop_flags);
		}

		/* native function properties */
		num = duk_bd_decode(bd, NUM_FUNC_PROPS_BITS);
		DUK_DDDPRINT("built-in object %d, %d function valued properties", i, num);
		for (j = 0; j < num; j++) {
			int stridx;
			int natidx;
			int c_nargs;
			int c_length;
			duk_c_function c_func;
			duk_hnativefunction *h_func;

			stridx = duk_bd_decode(bd, STRIDX_BITS);
			natidx = duk_bd_decode(bd, NATIDX_BITS);

			c_length = duk_bd_decode(bd, LENGTH_PROP_BITS);
			c_nargs = c_length;
			if (duk_bd_decode_flag(bd)) {
				c_nargs = duk_bd_decode(bd, NARGS_BITS);
				if (c_nargs == NARGS_VARARGS_MARKER) {
					c_nargs = DUK_VARARGS;
				}
			}

			c_func = duk_builtin_native_functions[natidx];

			DUK_DDDPRINT("built-in %d, function-valued property %d, stridx %d, natidx %d, length %d, nargs %d",
			             i, j, stridx, natidx, c_length, (c_nargs == DUK_VARARGS ? -1 : c_nargs));

			/* [ (builtin objects) ] */

			duk_push_c_function(ctx, c_func, c_nargs);
			h_func = duk_require_hnativefunction(ctx, -1);
			DUK_UNREF(h_func);

			/* Currently all built-in native functions are strict.
			 * This doesn't matter for many functions, but e.g.
			 * String.prototype.charAt (and other string functions)
			 * rely on being strict so that their 'this' binding is
			 * not automatically coerced.
			 */
			DUK_HOBJECT_SET_STRICT((duk_hobject *) h_func);

			/* [ (builtin objects) func ] */

			duk_push_int(ctx, c_length);
			duk_def_prop_stridx(ctx, -2, DUK_STRIDX_LENGTH, DUK_PROPDESC_FLAGS_NONE);

			duk_push_hstring_stridx(ctx, stridx);
			duk_def_prop_stridx(ctx, -2, DUK_STRIDX_NAME, DUK_PROPDESC_FLAGS_NONE);

			/* FIXME: other properties of function instances; 'arguments', 'caller'. */

			DUK_DDPRINT("built-in object %d, function property %d -> %!T", i, j, duk_get_tval(ctx, -1));

			/* [ (builtin objects) func ] */

			/*
			 *  The default property attributes are correct for all
			 *  function valued properties of built-in objects now.
			 */

			duk_def_prop_stridx(ctx, i, stridx, DUK_PROPDESC_FLAGS_WC);

			/* [ (builtin objects) ] */
		}
	}

	/*
	 *  Special post-tweaks, for cases not covered by the init data format.
	 *
	 *  - Set Date.prototype.toGMTString to Date.prototype.toUTCString.
	 *    toGMTString is required to have the same Function object as
	 *    toUTCString in E5 Section B.2.6.  Note that while Smjs respects
	 *    this, V8 does not (the Function objects are distinct).
	 *
	 *  - Make DoubleError non-extensible.
	 */

	duk_get_prop_stridx(ctx, DUK_BIDX_DATE_PROTOTYPE, DUK_STRIDX_TO_UTC_STRING);
	duk_def_prop_stridx(ctx, DUK_BIDX_DATE_PROTOTYPE, DUK_STRIDX_TO_GMT_STRING, DUK_PROPDESC_FLAGS_WC);

	h = duk_require_hobject(ctx, DUK_BIDX_DOUBLE_ERROR);
	DUK_ASSERT(h != NULL);
	DUK_HOBJECT_CLEAR_EXTENSIBLE(h);

	/*
	 *  Since built-ins are not often extended, compact them.
	 */

	DUK_DDPRINT("compact built-ins");
	for (i = 0; i < DUK_NUM_BUILTINS; i++) {
		duk_hobject_compact_props(thr, thr->builtins[i]);
	}

	DUK_DPRINT("INITBUILTINS END");

#ifdef DUK_USE_DDEBUG
	for (i = 0; i < DUK_NUM_BUILTINS; i++) {
		DUK_DDPRINT("built-in object %d after initialization and compacting: %!@iO", i, thr->builtins[i]);
	}
#endif
	
#ifdef DUK_USE_DDDEBUG
	for (i = 0; i < DUK_NUM_BUILTINS; i++) {
		DUK_DDDPRINT("built-in object %d after initialization and compacting", i);
		DUK_DEBUG_DUMP_HOBJECT(thr->builtins[i]);
	}
#endif

	/*
	 *  Pop built-ins from stack: they are now INCREF'd and
	 *  reachable from the builtins[] array.
	 */

	duk_pop_n(ctx, DUK_NUM_BUILTINS);
	DUK_ASSERT_TOP(ctx, 0);
}

void duk_hthread_copy_builtin_objects(duk_hthread *thr_from, duk_hthread *thr_to) {
	int i;

	for (i = 0; i < DUK_NUM_BUILTINS; i++) {
		thr_to->builtins[i] = thr_from->builtins[i];
		DUK_HOBJECT_INCREF(thr, thr_to->builtins[i]);  /* side effect free */
	}
}

#line 1 "duk_hthread_misc.c"
/*
 *  Thread support.
 */

/* include removed: duk_internal.h */

/* FIXME: separate "executor" thread and "target" thread for DECREF?
 * This function is a bit dangerous because we free the built-ins and
 * DECREF.  If a built-in gets DECREF'd to zero and has a finalizer,
 * we might have some problems in the finalizer.
 */

void duk_hthread_terminate(duk_hthread *thr) {
	int i;

	DUK_ASSERT(thr != NULL);

	duk_hthread_callstack_unwind(thr, 0);  /* side effects, possibly errors */

	duk_hthread_catchstack_unwind(thr, 0);

	thr->valstack_bottom = thr->valstack;
	duk_set_top((duk_context *) thr, 0);  /* unwinds valstack, updating refcounts */

	for (i = 0; i < DUK_NUM_BUILTINS; i++) {
#ifdef DUK_USE_REFERENCE_COUNTING
		duk_hobject *h = thr->builtins[i];
#endif
		thr->builtins[i] = NULL;
#ifdef DUK_USE_REFERENCE_COUNTING
		DUK_HOBJECT_DECREF(thr, h);
#endif
	}

	thr->state = DUK_HTHREAD_STATE_TERMINATED;

	/* FIXME: shrink to minimum size, but don't free stacks? */
}

duk_activation *duk_hthread_get_current_activation(duk_hthread *thr) {
	DUK_ASSERT(thr != NULL);

	if (thr->callstack_top > 0) {
		return thr->callstack + thr->callstack_top - 1;
	} else {
		return NULL;
	}
}

#line 1 "duk_hthread_stacks.c"
/*
 *  Manipulation of thread stacks (valstack, callstack, catchstack).
 *
 *  Ideally unwinding of stacks should have no side effects, which would
 *  then favor separate unwinding and shrink check primitives for each
 *  stack type.  A shrink check may realloc and thus have side effects.
 *
 *  However, currently callstack unwinding itself has side effects, as it
 *  needs to DECREF multiple objects, close environment records, etc.
 *  Stacks must thus be unwound in the correct order by the caller.
 *
 *  (FIXME: This should be probably reworked so that there is a shared
 *  unwind primitive which handles all stacks as requested, and knows
 *  the proper order for unwinding.)
 *
 *  Valstack entries above 'top' are always kept initialized to
 *  "undefined unused".  Callstack and catchstack entries above 'top'
 *  are not zeroed and are left as garbage.
 *
 *  Value stack handling is mostly a part of the API implementation.
 */

/* include removed: duk_internal.h */

/* check that there is space for at least one new entry */
void duk_hthread_callstack_grow(duk_hthread *thr) {
	int old_size;
	int new_size;

	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(thr->callstack_top >= 0 &&
	           thr->callstack_size >= thr->callstack_top);

	if (thr->callstack_top < thr->callstack_size) {
		return;
	}

	old_size = thr->callstack_size;
	new_size = old_size + DUK_CALLSTACK_GROW_STEP;

	/* this is a bit approximate (errors out before max is reached); this is OK */
	if (new_size >= thr->callstack_max) {
		DUK_ERROR(thr, DUK_ERR_INTERNAL_ERROR, "callstack limit reached");
	}

	DUK_DDPRINT("growing callstack %d -> %d", old_size, new_size);

	/*
	 *  Note: must use indirect variant of DUK_REALLOC() because underlying
	 *  pointer may be changed by mark-and-sweep.
	 */

	thr->callstack = DUK_REALLOC_INDIRECT_CHECKED(thr, (void **) &thr->callstack, sizeof(duk_activation) * new_size);
	thr->callstack_size = new_size;

	/* note: any entries above the callstack top are garbage and not zeroed */
}

void duk_hthread_callstack_shrink_check(duk_hthread *thr) {
	int new_size;
	duk_activation *p;

	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(thr->callstack_top >= 0 &&
	           thr->callstack_size >= thr->callstack_top);

	if (thr->callstack_size - thr->callstack_top < DUK_CALLSTACK_SHRINK_THRESHOLD) {
		return;
	}

	new_size = thr->callstack_top + DUK_CALLSTACK_SHRINK_SPARE;
	DUK_ASSERT(new_size >= thr->callstack_top);

	DUK_DDPRINT("shrinking callstack %d -> %d", thr->callstack_size, new_size);

	/*
	 *  Note: must use indirect variant of DUK_REALLOC() because underlying
	 *  pointer may be changed by mark-and-sweep.
	 */

	/* shrink failure is not fatal */
	p = (duk_activation *) DUK_REALLOC_INDIRECT(thr->heap, (void **) &thr->callstack, sizeof(duk_activation) * new_size);
	if (p) {
		thr->callstack = p;
		thr->callstack_size = new_size;
	} else {
		DUK_DPRINT("callstack shrink failed, ignoring");
	}

	/* note: any entries above the callstack top are garbage and not zeroed */
}

void duk_hthread_callstack_unwind(duk_hthread *thr, int new_top) {
	int idx;

	DUK_ASSERT(thr);
	DUK_ASSERT(thr->heap);
	DUK_ASSERT(new_top >= 0);
	DUK_ASSERT(new_top <= thr->callstack_top);  /* cannot grow */

	/*
	 *  The loop below must avoid issues with potential callstack
	 *  reallocations.  A resize (and other side effects) may happen
	 *  e.g. due to finalizer/errhandler calls caused by a refzero or
	 *  mark-and-sweep.  Arbitrary finalizers may run, because when
	 *  an environment record is refzero'd, it may refer to arbitrary
	 *  values which also become refzero'd.
	 *
	 *  So, the pointer 'p' is re-looked-up below whenever a side effect
	 *  might have changed it.
	 */

	idx = thr->callstack_top;
	while (idx > new_top) {
		duk_activation *p;
#ifdef DUK_USE_REFERENCE_COUNTING
		duk_hobject *tmp;
#endif

		idx--;
		DUK_ASSERT(idx >= 0 && idx < thr->callstack_size);  /* true, despite side effect resizes */

		p = &thr->callstack[idx];
		DUK_ASSERT(p->func != NULL);

		/*
		 *  Close environment record(s) if they exist.
		 *
		 *  Only variable environments are closed.  If lex_env != var_env, it
		 *  cannot currently contain any register bound declarations.
		 *
		 *  Only environments created for a NEWENV function are closed.  If an
		 *  environment is created for e.g. an eval call, it must not be closed.
		 */

		if (!DUK_HOBJECT_HAS_NEWENV(p->func)) {
			DUK_DDDPRINT("skip closing environments, envs not owned by this activation");
			goto skip_env_close;
		}

		if (p->var_env != NULL) {
			DUK_DDDPRINT("closing var_env record %p -> %!O",
			             (void *) p->var_env, (duk_heaphdr *) p->var_env);
			duk_js_close_environment_record(thr, p->var_env, p->func, p->idx_bottom);
			p = &thr->callstack[idx];  /* avoid side effect issues */
		}

#if 0
		if (p->lex_env != NULL) {
			if (p->lex_env == p->var_env) {
				/* common case, already closed, so skip */
				DUK_DDPRINT("lex_env and var_env are the same and lex_env "
				            "already closed -> skip closing lex_env");
				;
			} else {
				DUK_DDPRINT("closing lex_env record %p -> %!O",
				            (void *) p->lex_env, (duk_heaphdr *) p->lex_env);
				duk_js_close_environment_record(thr, p->lex_env, p->func, p->idx_bottom);
				p = &thr->callstack[idx];  /* avoid side effect issues */
			}
		}
#endif

		DUK_ASSERT((p->lex_env == NULL) ||
		           ((duk_hobject_find_existing_entry_tval_ptr(p->lex_env, DUK_HEAP_STRING_INT_CALLEE(thr)) == NULL) &&
		            (duk_hobject_find_existing_entry_tval_ptr(p->lex_env, DUK_HEAP_STRING_INT_VARMAP(thr)) == NULL) &&
		            (duk_hobject_find_existing_entry_tval_ptr(p->lex_env, DUK_HEAP_STRING_INT_THREAD(thr)) == NULL) &&
		            (duk_hobject_find_existing_entry_tval_ptr(p->lex_env, DUK_HEAP_STRING_INT_REGBASE(thr)) == NULL)));

		DUK_ASSERT((p->var_env == NULL) ||
		           ((duk_hobject_find_existing_entry_tval_ptr(p->var_env, DUK_HEAP_STRING_INT_CALLEE(thr)) == NULL) &&
		            (duk_hobject_find_existing_entry_tval_ptr(p->var_env, DUK_HEAP_STRING_INT_VARMAP(thr)) == NULL) &&
		            (duk_hobject_find_existing_entry_tval_ptr(p->var_env, DUK_HEAP_STRING_INT_THREAD(thr)) == NULL) &&
		            (duk_hobject_find_existing_entry_tval_ptr(p->var_env, DUK_HEAP_STRING_INT_REGBASE(thr)) == NULL)));

	 skip_env_close:

		/*
		 *  Update preventcount
		 */

		if (p->flags & DUK_ACT_FLAG_PREVENT_YIELD) {
			DUK_ASSERT(thr->callstack_preventcount >= 1);
			thr->callstack_preventcount--;
		}

		/*
		 *  Reference count updates
		 *
		 *  Note: careful manipulation of refcounts.  The top is
		 *  not updated yet, so all the activations are reachable
		 *  for mark-and-sweep (which may be triggered by decref).
		 *  However, the pointers are NULL so this is not an issue.
		 */

#ifdef DUK_USE_REFERENCE_COUNTING
		tmp = p->var_env;
#endif
		p->var_env = NULL;
#ifdef DUK_USE_REFERENCE_COUNTING
		DUK_HOBJECT_DECREF(thr, tmp);
		p = &thr->callstack[idx];  /* avoid side effect issues */
#endif

#ifdef DUK_USE_REFERENCE_COUNTING
		tmp = p->lex_env;
#endif
		p->lex_env = NULL;
#ifdef DUK_USE_REFERENCE_COUNTING
		DUK_HOBJECT_DECREF(thr, tmp);
		p = &thr->callstack[idx];  /* avoid side effect issues */
#endif

		/* Note: this may cause a corner case situation where a finalizer
		 * may see a currently reachable activation whose 'func' is NULL.
		 */
#ifdef DUK_USE_REFERENCE_COUNTING
		tmp = p->func;
#endif
		p->func = NULL;
#ifdef DUK_USE_REFERENCE_COUNTING
		DUK_HOBJECT_DECREF(thr, tmp);
		p = &thr->callstack[idx];  /* avoid side effect issues */
#endif
	}

	thr->callstack_top = new_top;

	/*
	 *  We could clear the book-keeping variables for the topmost activation,
	 *  but don't do so now.
	 */
#if 0
	if (thr->callstack_top > 0) {
		duk_activation *p = thr->callstack + thr->callstack_top - 1;
		p->idx_retval = -1;
	}
#endif

	/* Note: any entries above the callstack top are garbage and not zeroed.
	 * Also topmost activation idx_retval is garbage and not zeroed.
	 */
}

void duk_hthread_catchstack_grow(duk_hthread *thr) {
	int old_size;
	int new_size;

	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(thr->catchstack_top >= 0 &&
	           thr->catchstack_size >= thr->catchstack_top);

	if (thr->catchstack_top < thr->catchstack_size) {
		return;
	}

	old_size = thr->catchstack_size;
	new_size = old_size + DUK_CATCHSTACK_GROW_STEP;

	/* this is a bit approximate (errors out before max is reached); this is OK */
	if (new_size >= thr->catchstack_max) {
		DUK_ERROR(thr, DUK_ERR_INTERNAL_ERROR, "catchstack limit reached");
	}

	DUK_DDPRINT("growing catchstack %d -> %d", old_size, new_size);

	/*
	 *  Note: must use indirect variant of DUK_REALLOC() because underlying
	 *  pointer may be changed by mark-and-sweep.
	 */

	thr->catchstack = DUK_REALLOC_INDIRECT_CHECKED(thr, (void **) &thr->catchstack, sizeof(duk_catcher) * new_size);
	thr->catchstack_size = new_size;

	/* note: any entries above the catchstack top are garbage and not zeroed */
}

void duk_hthread_catchstack_shrink_check(duk_hthread *thr) {
	int new_size;
	duk_catcher *p;

	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(thr->catchstack_top >= 0 &&
	           thr->catchstack_size >= thr->catchstack_top);

	if (thr->catchstack_size - thr->catchstack_top < DUK_CATCHSTACK_SHRINK_THRESHOLD) {
		return;
	}

	new_size = thr->catchstack_top + DUK_CATCHSTACK_SHRINK_SPARE;
	DUK_ASSERT(new_size >= thr->catchstack_top);

	DUK_DDPRINT("shrinking catchstack %d -> %d", thr->catchstack_size, new_size);

	/*
	 *  Note: must use indirect variant of DUK_REALLOC() because underlying
	 *  pointer may be changed by mark-and-sweep.
	 */

	/* shrink failure is not fatal */
	p = (duk_catcher *) DUK_REALLOC_INDIRECT(thr->heap, (void **) &thr->catchstack, sizeof(duk_catcher) * new_size);
	if (p) {
		thr->catchstack = p;
		thr->catchstack_size = new_size;
	} else {
		DUK_DPRINT("catchstack shrink failed, ignoring");
	}

	/* note: any entries above the catchstack top are garbage and not zeroed */
}

void duk_hthread_catchstack_unwind(duk_hthread *thr, int new_top) {
	int idx;

	DUK_ASSERT(thr);
	DUK_ASSERT(thr->heap);
	DUK_ASSERT(new_top >= 0);
	DUK_ASSERT(new_top <= thr->catchstack_top);  /* cannot grow */

	/*
	 *  Since there are no references in the catcher structure,
	 *  unwinding is quite simple.  The only thing we need to
	 *  look out for is popping a possible lexical environment
	 *  established for an active catch clause.
	 */

	idx = thr->catchstack_top;
	while (idx > new_top) {
		duk_catcher *p;
		duk_activation *act;
		duk_hobject *env;

		idx--;
		DUK_ASSERT(idx >= 0 && idx < thr->catchstack_size);

		p = &thr->catchstack[idx];

		if (DUK_CAT_HAS_LEXENV_ACTIVE(p)) {
			DUK_DDDPRINT("unwinding catchstack idx %d: lexical environment active", idx);

			/* FIXME: Here we have a nasty dependency: the need to manipulate
			 * the callstack means that catchstack must always be unwound by
			 * the caller before unwinding the callstack.  This should be fixed
			 * later.
			 */

			act = &thr->callstack[p->callstack_index];
			DUK_ASSERT(act >= thr->callstack);
			DUK_ASSERT(act < &thr->callstack[thr->callstack_top]);
			DUK_ASSERT(act->lex_env != NULL);  /* must be, since env was created */

			DUK_DDDPRINT("callstack_index=%d, lex_env=%!iO", p->callstack_index, act->lex_env);

			env = act->lex_env;
			act->lex_env = env->prototype;
			DUK_HOBJECT_DECREF(thr, env);
		}
	}

	thr->catchstack_top = new_top;

	/* note: any entries above the catchstack top are garbage and not zeroed */
}

#line 1 "duk_js_call.c"
/*
 *  Call handling.
 *
 *  The main work horse functions are:
 *    - duk_handle_call(): call to a C/Ecmascript functions
 *    - duk_handle_safe_call(): make a protected C call within current activation
 *    - duk_handle_ecma_call_setup(): Ecmascript-to-Ecmascript calls, including
 *      tail calls and coroutine resume
 */

/* include removed: duk_internal.h */

/*
 *  Arguments object creation.
 *
 *  Creating arguments objects is a bit finicky, see E5 Section 10.6 for the
 *  specific requirements.  Much of the arguments object special behavior is
 *  implemented in duk_hobject_props.c, and is enabled by the object flag
 *  DUK_HOBJECT_FLAG_SPECIAL_ARGUMENTS.
 */

static void create_arguments_object(duk_hthread *thr,
                                    duk_hobject *func,
                                    duk_hobject *varenv,
                                    int idx_argbase,            /* idx of first argument on stack */
                                    int num_stack_args) {       /* num args starting from idx_argbase */
	duk_context *ctx = (duk_context *) thr;
	duk_hobject *arg;          /* 'arguments' */
	duk_hobject *formals;      /* formals for 'func' (may be NULL if func is a C function) */
	int i_arg;
	int i_map;
	int i_mappednames;
	int i_formals;
	int i_argbase;
	int n_formals;
	int idx;
	int need_map;

	DUK_DDDPRINT("creating arguments object for func=%!iO, varenv=%!iO, idx_argbase=%d, num_stack_args=%d",
	             (duk_heaphdr *) func, (duk_heaphdr *) varenv, idx_argbase, num_stack_args);

	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(func != NULL);
	DUK_ASSERT(DUK_HOBJECT_IS_NONBOUND_FUNCTION(func));
	DUK_ASSERT(varenv != NULL);
	DUK_ASSERT(idx_argbase >= 0);  /* assumed to bottom relative */
	DUK_ASSERT(num_stack_args >= 0);

	need_map = 0;

	i_argbase = idx_argbase;
	DUK_ASSERT(i_argbase >= 0);

	duk_push_hobject(ctx, func);
	duk_get_prop_stridx(ctx, -1, DUK_STRIDX_INT_FORMALS);
	formals = duk_get_hobject(ctx, -1);
	n_formals = 0;
	if (formals) {
		duk_get_prop_stridx(ctx, -1, DUK_STRIDX_LENGTH);
		n_formals = duk_require_int(ctx, -1);
		duk_pop(ctx);
	}
	duk_remove(ctx, -2);  /* leave formals on stack for later use */
	i_formals = duk_require_top_index(ctx);

	DUK_ASSERT(n_formals >= 0);
	DUK_ASSERT(formals != NULL || n_formals == 0);

	DUK_DDDPRINT("func=%!O, formals=%!O, n_formals=%d", func, formals, n_formals);

	/* [ ... formals ] */

	/*
	 *  Create required objects:
	 *    - 'arguments' object: array-like, but not an array
	 *    - 'map' object: internal object, tied to 'arguments'
	 *    - 'mappedNames' object: temporary value used during construction
	 */

	i_arg = duk_push_object_helper(ctx,
	                               DUK_HOBJECT_FLAG_EXTENSIBLE |
	                               DUK_HOBJECT_FLAG_ARRAY_PART |
	                               DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_ARGUMENTS),
	                               DUK_BIDX_OBJECT_PROTOTYPE);
	DUK_ASSERT(i_arg >= 0);
	arg = duk_require_hobject(ctx, -1);
	DUK_ASSERT(arg != NULL);

	i_map = duk_push_object_helper(ctx,
	                               DUK_HOBJECT_FLAG_EXTENSIBLE |
	                               DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_OBJECT),
	                               -1);  /* no prototype */
	DUK_ASSERT(i_map >= 0);

	i_mappednames = duk_push_object_helper(ctx,
	                                       DUK_HOBJECT_FLAG_EXTENSIBLE |
	                                       DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_OBJECT),
	                                       -1);  /* no prototype */
	DUK_ASSERT(i_mappednames >= 0);

	/* [... formals arguments map mappedNames] */

	DUK_DDDPRINT("created arguments related objects: "
	             "arguments at index %d -> %!O "
	             "map at index %d -> %!O "
	             "mappednames at index %d -> %!O",
	             i_arg, duk_get_hobject(ctx, i_arg),
	             i_map, duk_get_hobject(ctx, i_map),
	             i_mappednames, duk_get_hobject(ctx, i_mappednames));

	/*
	 *  Init arguments properties, map, etc.
	 */

	duk_push_int(ctx, num_stack_args);
	duk_def_prop_stridx(ctx, i_arg, DUK_STRIDX_LENGTH, DUK_PROPDESC_FLAGS_WC);

	/*
	 *  Init argument related properties
	 */

	/* step 11 */
	idx = num_stack_args - 1;
	while (idx >= 0) {
		DUK_DDDPRINT("arg idx %d, argbase=%d, argidx=%d", idx, i_argbase, i_argbase + idx);

		DUK_DDDPRINT("define arguments[%d]=arg", idx);
		duk_push_int(ctx, idx);
		duk_dup(ctx, i_argbase + idx);
		duk_def_prop(ctx, i_arg, DUK_PROPDESC_FLAGS_WEC);
		DUK_DDDPRINT("defined arguments[%d]=arg", idx);

		/* step 11.c is relevant only if non-strict (checked in 11.c.ii) */
		if (!DUK_HOBJECT_HAS_STRICT(func) && idx < n_formals) {
			DUK_ASSERT(formals != NULL);

			DUK_DDDPRINT("strict function, index within formals (%d < %d)", idx, n_formals);

			duk_get_prop_index(ctx, i_formals, idx);
			DUK_ASSERT(duk_is_string(ctx, -1));

			duk_dup(ctx, -1);  /* [... name name] */

			if (!duk_has_prop(ctx, i_mappednames)) {
				/* steps 11.c.ii.1 - 11.c.ii.4, but our internal book-keeping
				 * differs from the reference model
				 */

				/* [... name] */

				need_map = 1;

				DUK_DDDPRINT("set mappednames[%s]=%d", duk_get_string(ctx, -1), idx);
				duk_dup(ctx, -1);         /* name */
				duk_push_int(ctx, idx);   /* index */
				duk_to_string(ctx, -1);
				duk_def_prop(ctx, i_mappednames, DUK_PROPDESC_FLAGS_WEC);  /* out of spec, must be configurable */

				DUK_DDDPRINT("set map[%d]=%s", idx, duk_get_string(ctx, -1));
				duk_push_int(ctx, idx);   /* index */
				duk_to_string(ctx, -1);
				duk_dup(ctx, -2);         /* name */
				duk_def_prop(ctx, i_map, DUK_PROPDESC_FLAGS_WEC);  /* out of spec, must be configurable */
			} else {
				/* duk_has_prop() popped the second 'name' */
			}

			/* [... name] */
			duk_pop(ctx);  /* pop 'name' */
		}

		idx--;
	}

	DUK_DDDPRINT("actual arguments processed");

	/* step 12 */
	if (need_map) {
		DUK_DDDPRINT("adding 'map' and 'varenv' to arguments object");

		/* should never happen for a strict callee */
		DUK_ASSERT(!DUK_HOBJECT_HAS_STRICT(func));

		duk_dup(ctx, i_map);
		duk_def_prop_stridx(ctx, i_arg, DUK_STRIDX_INT_MAP, DUK_PROPDESC_FLAGS_NONE);  /* out of spec, don't care */

		/* The variable environment for magic variable bindings needs to be
		 * given by the caller and recorded in the arguments object.
		 *
		 * See E5 Section 10.6, the creation of setters/getters.
		 *
		 * The variable environment also provides access to the callee, so
		 * an explicit (internal) callee property is not needed.
		 */

		duk_push_hobject(ctx, varenv);
		duk_def_prop_stridx(ctx, i_arg, DUK_STRIDX_INT_VARENV, DUK_PROPDESC_FLAGS_NONE);  /* out of spec, don't care */
	}

	/* steps 13-14 */
	if (DUK_HOBJECT_HAS_STRICT(func)) {
		/*
		 *  Note: callee/caller are throwers and are not deletable etc.
		 *  They could be implemented as virtual properties, but currently
		 *  there is no support for virtual properties which are accessors
		 *  (only plain virtual properties).  This would not be difficult
		 *  to change in duk_hobject_props, but we can make the throwers
		 *  normal, concrete properties just as easily.
		 *
		 *  Note that the specification requires that the *same* thrower
		 *  built-in object is used here!  See E5 Section 10.6 main
		 *  algoritm, step 14, and Section 13.2.3 which describes the
		 *  thrower.  See test case test-arguments-throwers.js.
		 */

		DUK_DDDPRINT("strict function, setting caller/callee to throwers");

		duk_def_prop_stridx_thrower(ctx, i_arg, DUK_STRIDX_CALLER, DUK_PROPDESC_FLAGS_NONE);
		duk_def_prop_stridx_thrower(ctx, i_arg, DUK_STRIDX_CALLEE, DUK_PROPDESC_FLAGS_NONE);
	} else {
		DUK_DDDPRINT("non-strict function, setting callee to actual value");
		duk_push_hobject(ctx, func);
		duk_def_prop_stridx(ctx, i_arg, DUK_STRIDX_CALLEE, DUK_PROPDESC_FLAGS_WC);
	}

	/* set special behavior only after we're done */
	if (need_map) {
		/*
		 *  Note: special behaviors are only enabled for arguments
		 *  objects which have a parameter map (see E5 Section 10.6
		 *  main algorithm, step 12).
		 *
		 *  In particular, a non-strict arguments object with no
		 *  mapped formals does *NOT* get special behavior, even
		 *  for e.g. "caller" property.  This seems counterintuitive
		 *  but seems to be the case.
		 */

		/* cannot be strict (never mapped variables) */
		DUK_ASSERT(!DUK_HOBJECT_HAS_STRICT(func));

		DUK_DDDPRINT("enabling special behavior for arguments object");
		DUK_HOBJECT_SET_SPECIAL_ARGUMENTS(arg);
	} else {
		DUK_DDDPRINT("not enabling special behavior for arguments object");
	}

	/* nice log */
	DUK_DDDPRINT("final arguments related objects: "
	             "arguments at index %d -> %!O "
	             "map at index %d -> %!O "
	             "mappednames at index %d -> %!O",
	             i_arg, duk_get_hobject(ctx, i_arg),
	             i_map, duk_get_hobject(ctx, i_map),
	             i_mappednames, duk_get_hobject(ctx, i_mappednames));

	/* [args(n) [crud] formals arguments map mappednames] -> [args [crud] arguments] */
	duk_pop_2(ctx);
	duk_remove(ctx, -2);
}

/* Helper for creating the arguments object and adding it to the env record
 * on top of the value stack.  This helper has a very strict dependency on
 * the shape of the input stack.
 */
static void handle_createargs_for_call(duk_hthread *thr,
                                       duk_hobject *func,
                                       duk_hobject *env,
                                       int num_stack_args) {
	duk_context *ctx = (duk_context *) thr;

	DUK_DDDPRINT("creating arguments object for function call");

	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(func != NULL);
	DUK_ASSERT(env != NULL);
	DUK_ASSERT(DUK_HOBJECT_HAS_CREATEARGS(func));
	DUK_ASSERT(duk_get_top(ctx) >= num_stack_args + 1);

	/* [... arg1 ... argN envobj] */

	create_arguments_object(thr,
	                        func,
	                        env,
	                        duk_get_top(ctx) - num_stack_args - 1,    /* idx_argbase */
	                        num_stack_args);

	/* [... arg1 ... argN envobj argobj] */

	duk_def_prop_stridx(ctx,
	                    -2,
	                    DUK_STRIDX_LC_ARGUMENTS,
	                    DUK_HOBJECT_HAS_STRICT(func) ? DUK_PROPDESC_FLAGS_E :   /* strict: non-deletable, non-writable */
	                                                   DUK_PROPDESC_FLAGS_WE);  /* non-strict: non-deletable, writable */
	/* [... arg1 ... argN envobj] */
}

/*
 *  Helper for handling a "bound function" chain when a call is being made.
 *
 *  Follows the bound function chain until a non-bound function is found.
 *  Prepends the bound arguments to the value stack (at idx_func + 2),
 *  updating 'num_stack_args' in the process.  The 'this' binding is also
 *  updated if necessary (at idx_func + 1).
 *
 *  FIXME: bound function chains could be collapsed at bound function creation
 *  time so that each bound function would point directly to a non-bound
 *  function.  This would make call time handling much easier.
 */

static void handle_bound_chain_for_call(duk_hthread *thr,
                                        int idx_func,
                                        int *p_num_stack_args,   /* may be changed by call */
                                        duk_hobject **p_func) {  /* changed by call */
	duk_context *ctx = (duk_context *) thr;
	int num_stack_args;
	duk_hobject *func;
	duk_u32 sanity;

	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(p_num_stack_args != NULL);
	DUK_ASSERT(p_func != NULL);
	DUK_ASSERT(*p_func != NULL);
	DUK_ASSERT(DUK_HOBJECT_HAS_BOUND(*p_func));

	num_stack_args = *p_num_stack_args;
	func = *p_func;

	sanity = DUK_HOBJECT_BOUND_CHAIN_SANITY;
	do {	
		int i, len;

		if (!DUK_HOBJECT_HAS_BOUND(func)) {
			break;
		}

		DUK_DDDPRINT("bound function encountered, ptr=%p", (void *) func);

		/* XXX: this could be more compact by accessing the internal properties
		 * directly as own properties (they cannot be inherited, and are not
		 * externally visible).
		 */

		DUK_DDDPRINT("bound function encountered, ptr=%p, num_stack_args=%d",
		             (void *) func, num_stack_args);

		/* [ ... func this arg1 ... argN ] */

		duk_get_prop_stridx(ctx, idx_func, DUK_STRIDX_INT_THIS);
		duk_replace(ctx, idx_func + 1);  /* idx_this = idx_func + 1 */

		/* [ ... func this arg1 ... argN ] */

		/* XXX: duk_get_length? */
		duk_get_prop_stridx(ctx, idx_func, DUK_STRIDX_INT_ARGS);  /* -> [ ... func this arg1 ... argN _args ] */
		duk_get_prop_stridx(ctx, -1, DUK_STRIDX_LENGTH);          /* -> [ ... func this arg1 ... argN _args length ] */
		len = duk_require_int(ctx, -1);
		duk_pop(ctx);
		for (i = 0; i < len; i++) {
			/* FIXME: very slow - better to bulk allocate a gap, and copy
			 * from args_array directly (we know it has a compact array
			 * part, etc).
			 */

			/* [ ... func this <some bound args> arg1 ... argN _args ] */
			duk_get_prop_index(ctx, -1, i);
			duk_insert(ctx, idx_func + 2 + i);  /* idx_args = idx_func + 2 */
		}
		num_stack_args += len;  /* must be updated to work properly (e.g. creation of 'arguments') */
		duk_pop(ctx);

		/* [ ... func this <bound args> arg1 ... argN ] */

		duk_get_prop_stridx(ctx, idx_func, DUK_STRIDX_INT_TARGET);
		duk_replace(ctx, idx_func);  /* replace also in stack; not strictly necessary */
		func = duk_require_hobject(ctx, idx_func);

		DUK_DDDPRINT("bound function handled, num_stack_args=%d, idx_func=%d",
		             num_stack_args, idx_func);
	} while (--sanity > 0);

	if (sanity == 0) {
		DUK_ERROR(thr, DUK_ERR_INTERNAL_ERROR, "function call bound chain sanity exceeded");
	}

	DUK_DDDPRINT("final non-bound function is: %p", (void *) func);

	DUK_ASSERT(!DUK_HOBJECT_HAS_BOUND(func));
	DUK_ASSERT(DUK_HOBJECT_HAS_COMPILEDFUNCTION(func) || DUK_HOBJECT_HAS_NATIVEFUNCTION(func));

	/* write back */
	*p_num_stack_args = num_stack_args;
	*p_func = func;
}

/*
 *  Helper for setting up var_env and lex_env of an activation,
 *  assuming it does NOT have the DUK_HOBJECT_FLAG_NEWENV flag.
 */

static void handle_oldenv_for_call(duk_hthread *thr,
                                   duk_hobject *func,
                                   duk_activation *act) {
	duk_tval *tv;

	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(func != NULL);
	DUK_ASSERT(act != NULL);
	DUK_ASSERT(!DUK_HOBJECT_HAS_NEWENV(func));
	DUK_ASSERT(!DUK_HOBJECT_HAS_CREATEARGS(func));

	tv = duk_hobject_find_existing_entry_tval_ptr(func, DUK_HEAP_STRING_INT_LEXENV(thr));
	if (tv) {
		DUK_ASSERT(DUK_TVAL_IS_OBJECT(tv));
		DUK_ASSERT(DUK_HOBJECT_IS_ENV(DUK_TVAL_GET_OBJECT(tv)));
		act->lex_env = DUK_TVAL_GET_OBJECT(tv);

		tv = duk_hobject_find_existing_entry_tval_ptr(func, DUK_HEAP_STRING_INT_VARENV(thr));
		if (tv) {
			DUK_ASSERT(DUK_TVAL_IS_OBJECT(tv));
			DUK_ASSERT(DUK_HOBJECT_IS_ENV(DUK_TVAL_GET_OBJECT(tv)));
			act->var_env = DUK_TVAL_GET_OBJECT(tv);
		} else {
			act->var_env = act->lex_env;
		}
	} else {
		act->lex_env = thr->builtins[DUK_BIDX_GLOBAL_ENV];
		act->var_env = act->lex_env;
	}

	DUK_HOBJECT_INCREF(thr, act->lex_env);
	DUK_HOBJECT_INCREF(thr, act->var_env);
}

/*
 *  Determine the effective 'this' binding and coerce the current value
 *  on the valstack to the effective one (in-place, at idx_this).
 *
 *  The current this value in the valstack (at idx_this) represents either:
 *    - the caller's requested 'this' binding; or
 *    - a 'this' binding accumulated from the bound function chain
 *
 *  The final 'this' binding for the target function may still be
 *  different, and is determined as described in E5 Section 10.4.3.
 *
 *  For global and eval code (E5 Sections 10.4.1 and 10.4.2), we assume
 *  that the caller has provided the correct 'this' binding explicitly
 *  when calling, i.e.:
 *
 *    - global code: this=global object
 *    - direct eval: this=copy from eval() caller's this binding
 *    - other eval:  this=global object
 *
 *  Note: this function may cause a recursive function call with arbitrary
 *  side effects, because ToObject() may be called.
 */

static void handle_coerce_effective_this_binding(duk_hthread *thr,
                                                 duk_hobject *func,
                                                 int idx_this) {
	duk_context *ctx = (duk_context *) thr;

	if (DUK_HOBJECT_HAS_STRICT(func)) {
		DUK_DDDPRINT("this binding: strict -> use directly");
	} else {
		duk_tval *tv_this = duk_require_tval(ctx, idx_this);
		duk_hobject *obj_global;

		if (DUK_TVAL_IS_OBJECT(tv_this)) {
			DUK_DDDPRINT("this binding: non-strict, object -> use directly");
		} else if (DUK_TVAL_IS_UNDEFINED(tv_this) || DUK_TVAL_IS_NULL(tv_this)) {
			DUK_DDDPRINT("this binding: non-strict, undefined/null -> use global object");
			obj_global = thr->builtins[DUK_BIDX_GLOBAL];
			if (obj_global) {
				duk_push_hobject(ctx, obj_global);
			} else {
				/*
				 *  This may only happen if built-ins are being "torn down".
				 *  This behavior is out of specification scope.
				 */
				DUK_DPRINT("this binding: wanted to use global object, but it is NULL -> using undefined instead");
				duk_push_undefined(ctx);
			}
			duk_replace(ctx, idx_this);
		} else {
			DUK_DDDPRINT("this binding: non-strict, not object/undefined/null -> use ToObject(value)");
			duk_to_object(ctx, idx_this);  /* may have side effects */
		}
	}
}

/*
 *  Helper for making various kinds of calls.
 *
 *  Call flags:
 *
 *    DUK_CALL_FLAG_PROTECTED        <-->  protected call
 *    DUK_CALL_FLAG_IGNORE_RECLIMIT  <-->  ignore C recursion limit,
 *                                         for errhandler calls
 *    DUK_CALL_FLAG_CONSTRUCTOR_CALL <-->  for 'new Foo()' calls
 *
 *  Input stack:
 *
 *    [ func this arg1 ... argN ]
 *
 *  Output stack:
 *
 *    [ retval ]         (DUK_ERR_EXEC_SUCCESS)
 *    [ errobj ]         (DUK_ERR_EXEC_ERROR (normal error), protected call)
 *    [ (unspecified) ]  (DUK_ERR_EXEC_TERM (terminal error), protected call)
 *                       (if error and not a protected call --> longjmp)
 *
 *  Even when executing a protected call, if an error happens during error
 *  handling (e.g. we run out of memory while setting up the return stack),
 *  the error is propagated to the previous catchpoint).  If no catchpoint
 *  exists, the fatal error handler is called.  Also, API errors (such as
 *  invalid indices) are thrown directly.
 *
 *  See 'execution.txt'.
 *
 *  The allowed thread states for making a call are:
 *    - thr matches heap->curr_thread, and thr is already RUNNING
 *    - thr does not match heap->curr_thread (may be NULL or other),
 *      and thr is INACTIVE (in this case, a setjmp() catchpoint is
 *      always used for thread book-keeping to work properly)
 *
 *  Like elsewhere, gotos are used to keep indent level minimal and
 *  avoiding a dozen helpers with awkward plumbing.
 *
 *  Note: setjmp() and local variables have a nasty interaction,
 *  see execution.txt; non-volatile locals modified after setjmp()
 *  call are not guaranteed to keep their value.
 */

int duk_handle_call(duk_hthread *thr,
                    int num_stack_args,
                    int call_flags,
                    duk_hobject *errhandler) {  /* borrowed */
	duk_context *ctx = (duk_context *) thr;
	int entry_valstack_bottom_index;
	int entry_callstack_top;
	int entry_catchstack_top;
	int entry_call_recursion_depth;
	int need_setjmp;
	duk_hthread *entry_curr_thread;
	duk_u8 entry_thread_state;
	int idx_func;         /* valstack index of 'func' and retval (relative to entry valstack_bottom) */
	int idx_args;         /* valstack index of start of args (arg1) (relative to entry valstack_bottom) */
	int nargs;            /* # argument registers target function wants (< 0 => "as is") */
	int nregs;            /* # total registers target function wants on entry (< 0 => "as is") */
	duk_hobject *func;    /* 'func' on stack (borrowed reference) */
	duk_activation *act;
	duk_hobject *env;
	duk_jmpbuf *old_jmpbuf_ptr = NULL;
	duk_hobject *old_errhandler = NULL;
	duk_jmpbuf our_jmpbuf;
	duk_tval tv_tmp;
	int retval = DUK_ERR_EXEC_ERROR;
	int rc;

	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(ctx != NULL);
	DUK_ASSERT(num_stack_args >= 0);
	DUK_ASSERT(errhandler == NULL || DUK_HOBJECT_IS_CALLABLE(errhandler));
	DUK_ASSERT_REFCOUNT_NONZERO_HEAPHDR((duk_heaphdr *) errhandler);

	/* XXX: currently NULL allocations are not supported; remove if later allowed */
	DUK_ASSERT(thr->valstack != NULL);
	DUK_ASSERT(thr->callstack != NULL);
	DUK_ASSERT(thr->catchstack != NULL);

	/*
	 *  Preliminaries, required by setjmp() handler.
	 *
	 *  Must be careful not to throw an unintended error here.
	 *
	 *  Note: careful with indices like '-x'; if 'x' is zero, it
	 *  refers to valstack_bottom.
	 */

	entry_valstack_bottom_index = (int) (thr->valstack_bottom - thr->valstack);
	entry_callstack_top = thr->callstack_top;
	entry_catchstack_top = thr->catchstack_top;
	entry_call_recursion_depth = thr->heap->call_recursion_depth;
	entry_curr_thread = thr->heap->curr_thread;  /* Note: may be NULL if first call */
	entry_thread_state = thr->state;
	idx_func = duk_normalize_index(ctx, -num_stack_args - 2);  /* idx_func must be valid, note: non-throwing! */
	idx_args = idx_func + 2;                                   /* idx_args is not necessarily valid if num_stack_args == 0 (idx_args then equals top) */

	/* Need a setjmp() catchpoint if a protected call OR if we need to
	 * do mandatory cleanup.
	 */
	need_setjmp = ((call_flags & DUK_CALL_FLAG_PROTECTED) != 0) || (thr->heap->curr_thread != thr);

	DUK_DDPRINT("duk_handle_call: thr=%p, num_stack_args=%d, "
	            "call_flags=%d (protected=%d, ignorerec=%d, constructor=%d), need_setjmp=%d, "
	            "errhandler=%p, valstack_top=%d, idx_func=%d, idx_args=%d, rec_depth=%d/%d, "
	            "entry_valstack_bottom_index=%d, entry_callstack_top=%d, entry_catchstack_top=%d, "
	            "entry_call_recursion_depth=%d, entry_curr_thread=%p, entry_thread_state=%d",
	            (void *) thr,
	            num_stack_args,
	            call_flags,
	            ((call_flags & DUK_CALL_FLAG_PROTECTED) != 0 ? 1 : 0),
	            ((call_flags & DUK_CALL_FLAG_IGNORE_RECLIMIT) != 0 ? 1 : 0),
	            ((call_flags & DUK_CALL_FLAG_CONSTRUCTOR_CALL) != 0 ? 1 : 0),
	            need_setjmp,
	            (void *) errhandler,
	            duk_get_top(ctx),
	            idx_func,
	            idx_args,
	            thr->heap->call_recursion_depth,
	            thr->heap->call_recursion_limit,
	            entry_valstack_bottom_index,
	            entry_callstack_top,
	            entry_catchstack_top,
	            entry_call_recursion_depth,
	            (void *) entry_curr_thread,
	            entry_thread_state);

#ifdef DUK_USE_DDDEBUG
	DUK_DPRINT("callstack before call setup:");
	DUK_DEBUG_DUMP_CALLSTACK(thr);
#endif

	if (idx_func < 0 || idx_args < 0) {
		/*
		 *  Since stack indices are not reliable, we can't do anything useful
		 *  here.  Invoke the existing setjmp catcher, or if it doesn't exist,
		 *  call the fatal error handler.
		 */

		DUK_ERROR(thr, DUK_ERR_API_ERROR, "invalid arguments");
	}

	/*
	 *  Setup a setjmp() catchpoint first because even the call setup
	 *  may fail.
	 */

	if (!need_setjmp) {
		DUK_DDDPRINT("don't need a setjmp catchpoint");
		goto handle_call;
	}

	old_errhandler = thr->heap->lj.errhandler;
	old_jmpbuf_ptr = thr->heap->lj.jmpbuf_ptr;

	thr->heap->lj.errhandler = errhandler;  /* may be NULL */
	thr->heap->lj.jmpbuf_ptr = &our_jmpbuf;

	if (setjmp(thr->heap->lj.jmpbuf_ptr->jb) == 0) {
		DUK_DDDPRINT("setjmp catchpoint setup complete, errhandler=%p",
		             (void *) thr->heap->lj.errhandler);
		goto handle_call;
	}

	/*
	 *  Error during setup, call, or postprocessing of the call.
	 *  The error value is in heap->lj.value1.
	 *
	 *  Note: any local variables accessed here must have their value
	 *  assigned *before* the setjmp() call, OR they must be declared
	 *  volatile.  Otherwise their value is not guaranteed to be correct.
	 *
	 *  The following are such variables:
	 *    - duk_handle_call() parameters
	 *    - entry_*
	 *    - idx_func
	 *    - idx_args
	 *
	 *  The very first thing we do is restore the previous setjmp catcher.
	 *  This means that any error in error handling will propagate outwards
	 *  instead of causing a setjmp() re-entry above.  The *only* actual
	 *  errors that should happen here are allocation errors.
	 */

	DUK_DDDPRINT("error caught during protected duk_handle_call(): %!T",
	             &thr->heap->lj.value1);

	DUK_ASSERT(thr->heap->lj.type == DUK_LJ_TYPE_THROW);
	DUK_ASSERT(thr->callstack_top >= entry_callstack_top);
	DUK_ASSERT(thr->catchstack_top >= entry_catchstack_top);

	/*
	 *  Restore previous setjmp catchpoint
	 */

	/* Note: either pointer may be NULL (at entry), so don't assert */
	DUK_DDDPRINT("restore jmpbuf_ptr: %p -> %p, errhandler: %p -> %p",
	             (thr && thr->heap ? thr->heap->lj.jmpbuf_ptr : NULL),
	             old_jmpbuf_ptr,
	             (thr && thr->heap ? thr->heap->lj.errhandler : NULL),
	             old_errhandler);

	thr->heap->lj.jmpbuf_ptr = old_jmpbuf_ptr;
	thr->heap->lj.errhandler = old_errhandler;

	if (!(call_flags & DUK_CALL_FLAG_PROTECTED)) {
		/*
		 *  Caller did not request a protected call but a setjmp
		 *  catchpoint was set up to allow cleanup.  So, clean up
		 *  and rethrow.
		 *
		 *  Note: this case happens e.g. when heap->curr_thread is
		 *  NULL on entry.
		 *
		 *  FIXME: maybe we should let the caller clean up instead.
		 */

		DUK_DDDPRINT("call is not protected -> clean up and rethrow");

#if 0  /*FIXME*/
		thr->heap->curr_thread = entry_curr_thread;  /* may be NULL */
		thr->state = entry_thread_state;

		DUK_ASSERT((thr->state == DUK_HTHREAD_STATE_INACTIVE && thr->heap->curr_thread == NULL) ||  /* first call */
		           (thr->state == DUK_HTHREAD_STATE_INACTIVE && thr->heap->curr_thread != NULL) ||  /* other call */
		           (thr->state == DUK_HTHREAD_STATE_RUNNING && thr->heap->curr_thread == thr));     /* current thread */
#endif
		/* XXX: should setjmp catcher be responsible for this instead? */
		thr->heap->call_recursion_depth = entry_call_recursion_depth;
		duk_err_longjmp(thr);
		DUK_NEVER_HERE();
	}

	duk_hthread_catchstack_unwind(thr, entry_catchstack_top);
	duk_hthread_callstack_unwind(thr, entry_callstack_top);
	thr->valstack_bottom = thr->valstack + entry_valstack_bottom_index;

	/* [ ... func this (crud) errobj ] */

	/* FIXME: is there space?  better implementation: write directly over
	 * 'func' slot to avoid valstack grow issues.
	 */
	duk_push_tval(ctx, &thr->heap->lj.value1);

	/* [ ... func this (crud) errobj ] */

	duk_replace(ctx, idx_func);
	duk_set_top(ctx, idx_func + 1);

	/* [ ... errobj ] */

	/* ensure there is internal valstack spare before we exit; this may
	 * throw an alloc error
	 */

	duk_require_valstack_resize((duk_context *) thr,
	                            (thr->valstack_top - thr->valstack) +            /* top of current func */
	                                DUK_VALSTACK_INTERNAL_EXTRA,                 /* + spare => min_new_size */
	                            1);                                              /* allow_shrink */

	/* Note: currently a second setjmp restoration is done at the target;
	 * this is OK, but could be refactored away.
	 */
	retval = DUK_ERR_EXEC_ERROR;
	goto shrink_and_finished;

 handle_call:
	/*
	 *  Thread state check and book-keeping.
	 */

	if (thr == thr->heap->curr_thread) {
		/* same thread */
		if (thr->state != DUK_HTHREAD_STATE_RUNNING) {
			/* should actually never happen, but check anyway */
			goto thread_state_error;
		}
	} else {
		/* different thread */
		DUK_ASSERT(thr->heap->curr_thread == NULL ||
		           thr->heap->curr_thread->state == DUK_HTHREAD_STATE_RUNNING);
		if (thr->state != DUK_HTHREAD_STATE_INACTIVE) {
			goto thread_state_error;
		}
		thr->heap->curr_thread = thr;
		thr->state = DUK_HTHREAD_STATE_RUNNING;

		/* Note: multiple threads may be simultaneously in the RUNNING
		 * state, but not in the same "resume chain".
		 */
	}

	DUK_ASSERT(thr->heap->curr_thread == thr);
	DUK_ASSERT(thr->state == DUK_HTHREAD_STATE_RUNNING);

	/*
	 *  C call recursion depth check, which provides a reasonable upper
	 *  bound on maximum C stack size (arbitrary C stack growth is only
	 *  possible by recursive handle_call / handle_safe_call calls).
	 */

	DUK_ASSERT(thr->heap->call_recursion_depth >= 0);
	DUK_ASSERT(thr->heap->call_recursion_depth <= thr->heap->call_recursion_limit);

	if (call_flags & DUK_CALL_FLAG_IGNORE_RECLIMIT) {
		DUK_DDPRINT("ignoring reclimit for this call (probably an errhandler call)");
	} else {	
		if (thr->heap->call_recursion_depth >= thr->heap->call_recursion_limit) {
			DUK_ERROR(thr, DUK_ERR_INTERNAL_ERROR, "maximum C call stack depth reached");
		}
		thr->heap->call_recursion_depth++;
	}

	/*
	 *  Check the function type, handle bound function chains,
	 *  and prepare parameters for the rest of the call handling.
	 *  Also figure out the effective 'this' binding, which
	 *  replaces the current value at idx_func + 1.
	 *
	 *  If the target function is a 'bound' one, follow the chain
	 *  of 'bound' functions until a non-bound function is found.
	 *  During this process, bound arguments are 'prepended' to
	 *  existing ones, and the "this" binding is overridden.
	 *  See E5 Section 15.3.4.5.1.
	 */

	if (!duk_is_callable(thr, idx_func)) {
		DUK_ERROR(ctx, DUK_ERR_TYPE_ERROR, "call target not callable");
	}
	func = duk_get_hobject(thr, idx_func);
	DUK_ASSERT(func != NULL);

	if (DUK_HOBJECT_HAS_BOUND(func)) {
		/* slow path for bound functions */
		handle_bound_chain_for_call(thr, idx_func, &num_stack_args, &func);
	}
	DUK_ASSERT(!DUK_HOBJECT_HAS_BOUND(func));
	DUK_ASSERT(DUK_HOBJECT_IS_COMPILEDFUNCTION(func) ||
	           DUK_HOBJECT_IS_NATIVEFUNCTION(func));

	handle_coerce_effective_this_binding(thr, func, idx_func + 1);
	DUK_DDDPRINT("effective 'this' binding is: %!T", duk_get_tval(ctx, idx_func + 1));

	nargs = 0;
	nregs = 0;
	if (DUK_HOBJECT_IS_COMPILEDFUNCTION(func)) {
		nargs = ((duk_hcompiledfunction *) func)->nargs;
		nregs = ((duk_hcompiledfunction *) func)->nregs;
		DUK_ASSERT(nregs >= nargs);
	} else if (DUK_HOBJECT_IS_NATIVEFUNCTION(func)) {
		/* Note: nargs (and nregs) may be negative for a native,
		 * function, which indicates that the function wants the
		 * input stack "as is" (i.e. handles "vararg" arguments).
		 */
		nargs = ((duk_hnativefunction *) func)->nargs;
		nregs = nargs;
	} else {
		/* XXX: this should be an assert */
		DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, "call target not a function");
	}

	/* [ ... func this arg1 ... argN ] */

	/*
	 *  Check stack sizes and resize if necessary.
	 *
	 *  Call stack is grown by one, catch stack doesn't grow here.
	 *  Value stack may either grow or shrink, depending on the number
	 *  of func registers and the number of actual arguments.
	 */

	duk_hthread_callstack_grow(thr);

	/* if nregs >= 0, func wants args clamped to 'nargs'; else it wants
	 * all args (= 'num_stack_args')
	 */

	duk_require_valstack_resize((duk_context *) thr,
	                            (thr->valstack_bottom - thr->valstack) +         /* bottom of current func */
	                                idx_args +                                   /* bottom of new func */
	                                (nregs >= 0 ? nregs : num_stack_args) +      /* num entries of new func at entry */
	                                DUK_VALSTACK_INTERNAL_EXTRA,                 /* + spare => min_new_size */
	                            1);                                              /* allow_shrink */

	/*
	 *  Update idx_retval of current activation.
	 *
	 *  Although it might seem this is not necessary (bytecode executor
	 *  does this for Ecmascript-to-Ecmascript calls; other calls are
	 *  handled here), this turns out to be necessary for handling yield
	 *  and resume.  For them, an Ecmascript-to-native call happens, and
	 *  the Ecmascript call's idx_retval must be set for things to work.
	 */

	if (thr->callstack_top > 0) {
		/* now set unconditionally, regardless of whether current activation
		 * is native or not.
	 	 */
		(thr->callstack + thr->callstack_top - 1)->idx_retval = entry_valstack_bottom_index + idx_func;
	}

	/*
	 *  Setup a preliminary activation.
	 *
	 *  Don't touch valstack_bottom or valstack_top yet so that Duktape API
	 *  calls work normally.
	 */

	/* [ ... func this arg1 ... argN ] */

	DUK_ASSERT(thr->callstack_top < thr->callstack_size);
	act = &thr->callstack[thr->callstack_top];
	thr->callstack_top++;
	DUK_ASSERT(thr->callstack_top <= thr->callstack_size);
	DUK_ASSERT(thr->valstack_top > thr->valstack_bottom);  /* at least effective 'this' */
	DUK_ASSERT(!DUK_HOBJECT_HAS_BOUND(func));

	act->flags = 0;
	if (DUK_HOBJECT_HAS_STRICT(func)) {
		act->flags |= DUK_ACT_FLAG_STRICT;
	}
	if (call_flags & DUK_CALL_FLAG_CONSTRUCTOR_CALL) {
		act->flags |= DUK_ACT_FLAG_CONSTRUCT;
		act->flags |= DUK_ACT_FLAG_PREVENT_YIELD;
	}
	if (DUK_HOBJECT_IS_NATIVEFUNCTION(func)) {
		act->flags |= DUK_ACT_FLAG_PREVENT_YIELD;
	}
	if (call_flags & DUK_CALL_FLAG_DIRECT_EVAL) {
		act->flags |= DUK_ACT_FLAG_DIRECT_EVAL;
	}

	act->func = func;
	act->var_env = NULL;
	act->lex_env = NULL;
	act->pc = 0;
	act->idx_bottom = entry_valstack_bottom_index + idx_args;
#if 0  /* topmost activation idx_retval is considered garbage, no need to init */
	act->idx_retval = -1;  /* idx_retval is a 'caller' retval, so init to "unused" here */
#endif

	if (act->flags & DUK_ACT_FLAG_PREVENT_YIELD) {
		/* duk_hthread_callstack_unwind() will decrease this on unwind */
		thr->callstack_preventcount++;
	}

	DUK_HOBJECT_INCREF(thr, func);  /* act->func */

	/* [... func this arg1 ... argN] */

#ifdef DUK_USE_DDDEBUG
	DUK_DPRINT("pushed new activation:");
	DUK_DEBUG_DUMP_ACTIVATION(thr, thr->callstack + thr->callstack_top - 1);
#endif

	/*
	 *  Environment record creation and 'arguments' object creation.
	 *  Named function expression name binding is handled by the
	 *  compiler; the compiled function's parent env will contain
	 *  the (immutable) binding already.
	 *
	 *  This handling is now identical for C and Ecmascript functions.
	 *  C functions always have the 'NEWENV' flag set, so their
	 *  environment record initialization is delayed (which is good).
	 *
	 *  Delayed creation (on demand) is handled in duk_js_var.c.
	 */

	DUK_ASSERT(!DUK_HOBJECT_HAS_BOUND(func));  /* bound function chain has already been resolved */

	if (!DUK_HOBJECT_HAS_NEWENV(func)) {
		/* use existing env (e.g. for non-strict eval); cannot have
		 * an own 'arguments' object (but can refer to the existing one)
		 */

		DUK_ASSERT(!DUK_HOBJECT_HAS_CREATEARGS(func));

		handle_oldenv_for_call(thr, func, act);

		DUK_ASSERT(act->lex_env != NULL);
		DUK_ASSERT(act->var_env != NULL);
		goto env_done;
	}

	DUK_ASSERT(DUK_HOBJECT_HAS_NEWENV(func));

	if (!DUK_HOBJECT_HAS_CREATEARGS(func)) {
		/* no need to create environment record now; leave as NULL */
		DUK_ASSERT(act->lex_env == NULL);
		DUK_ASSERT(act->var_env == NULL);
		goto env_done;
	}

	/* third arg: absolute index (to entire valstack) of idx_bottom of new activation */
	env = duk_create_activation_environment_record(thr, func, act->idx_bottom);
	DUK_ASSERT(env != NULL);
	
	/* [... func this arg1 ... argN envobj] */

	DUK_ASSERT(DUK_HOBJECT_HAS_CREATEARGS(func));
	handle_createargs_for_call(thr, func, env, num_stack_args);

	/* [... func this arg1 ... argN envobj] */

	act->lex_env = env;
	act->var_env = env;
	DUK_HOBJECT_INCREF(thr, env);
	DUK_HOBJECT_INCREF(thr, env);  /* XXX: incref by count (2) directly */
	duk_pop(ctx);

 env_done:
	/* [... func this arg1 ... argN] */

	/*
	 *  Setup value stack: clamp to 'nargs', fill up to 'nregs'
	 */

	/* XXX: replace with a single operation */

	if (nregs >= 0) {
		duk_set_top(ctx, idx_args + nargs);  /* clamp anything above nargs */
		duk_set_top(ctx, idx_args + nregs);  /* extend with undefined */
	} else {
		/* 'func' wants stack "as is" */
	}

#ifdef DUK_USE_DDDEBUG
	DUK_DPRINT("callstack after call setup:");
	DUK_DEBUG_DUMP_CALLSTACK(thr);
#endif

	/*
	 *  Determine call type; then setup activation and call
	 */

	if (DUK_HOBJECT_IS_COMPILEDFUNCTION(func)) {
		goto ecmascript_call;
	} else {
		goto native_call;
	}
	DUK_NEVER_HERE();

	/*
	 *  Native (C) call
	 */

 native_call:
	/*
	 *  Shift to new valstack_bottom.
	 */

	thr->valstack_bottom = thr->valstack_bottom + idx_args;
	/* keep current valstack_top */
	DUK_ASSERT(thr->valstack_bottom >= thr->valstack);
	DUK_ASSERT(thr->valstack_top >= thr->valstack_bottom);
	DUK_ASSERT(thr->valstack_end >= thr->valstack_top);
	DUK_ASSERT(((duk_hnativefunction *) func)->func != NULL);

	/* [... func this | arg1 ... argN] ('this' must precede new bottom) */

	/*
	 *  Actual function call and return value check.
	 *
	 *  Return values:
	 *    0    success, no return value (default to 'undefined')
	 *    1    success, one return value on top of stack
	 *  < 0    error, throw a "magic" error
	 *  other  invalid
	 */

	rc = ((duk_hnativefunction *) func)->func((duk_context *) thr);

	if (rc < 0) {
		duk_error_throw_from_negative_rc(thr, rc);
		DUK_NEVER_HERE();
	} else if (rc > 1) {
		DUK_ERROR(thr, DUK_ERR_API_ERROR, "c function returned invalid rc");
	}
	DUK_ASSERT(rc == 0 || rc == 1);

	/*
	 *  Unwind stack(s) and shift back to old valstack_bottom.
	 */

	DUK_ASSERT(thr->catchstack_top == entry_catchstack_top);
	DUK_ASSERT(thr->callstack_top == entry_callstack_top + 1);

#if 0  /* should be no need to unwind */
	duk_hthread_catchstack_unwind(thr, entry_catchstack_top);
#endif
	duk_hthread_callstack_unwind(thr, entry_callstack_top);

	thr->valstack_bottom = thr->valstack + entry_valstack_bottom_index;
	/* keep current valstack_top */

	DUK_ASSERT(thr->valstack_bottom >= thr->valstack);
	DUK_ASSERT(thr->valstack_top >= thr->valstack_bottom);
	DUK_ASSERT(thr->valstack_end >= thr->valstack_top);
	DUK_ASSERT(thr->valstack_top - thr->valstack_bottom >= idx_func + 1);

	/*
	 *  Manipulate value stack so that return value is on top
	 *  (pushing an 'undefined' if necessary).
	 */

	/* XXX: should this happen in the callee's activation or after unwinding? */
	if (rc == 0) {
		duk_require_stack(ctx, 1);
		duk_push_undefined(ctx);
	}
	/* [... func this (crud) retval] */

	DUK_DDDPRINT("native call retval -> %!T (rc=%d)", duk_get_tval(ctx, -1), rc);

	duk_replace(ctx, idx_func);
	duk_set_top(ctx, idx_func + 1);

	/* [... retval] */

	/* ensure there is internal valstack spare before we exit */

	duk_require_valstack_resize((duk_context *) thr,
	                            (thr->valstack_top - thr->valstack) +            /* top of current func */
	                                DUK_VALSTACK_INTERNAL_EXTRA,                 /* + spare => min_new_size */
	                            1);                                              /* allow_shrink */


	/*
	 *  Shrink checks and return with success.
	 */

	retval = DUK_ERR_EXEC_SUCCESS;
	goto shrink_and_finished;	

	/*
	 *  Ecmascript call
	 */

 ecmascript_call:

	/*
	 *  Shift to new valstack_bottom.
	 */

	thr->valstack_bottom = thr->valstack_bottom + idx_args;
	/* keep current valstack_top */
	DUK_ASSERT(thr->valstack_bottom >= thr->valstack);
	DUK_ASSERT(thr->valstack_top >= thr->valstack_bottom);
	DUK_ASSERT(thr->valstack_end >= thr->valstack_top);

	/* [... func this | arg1 ... argN] ('this' must precede new bottom) */

	/*
	 *  Bytecode executor call.
	 *
	 *  Execute bytecode, handling any recursive function calls and
	 *  thread resumptions.  Returns when execution would return from
	 *  the entry level activation.  When the executor returns, a
	 *  single return value is left on the stack top.
	 *
	 *  The only possible longjmp() is an error (DUK_LJ_TYPE_THROW),
	 *  other types are handled internally by the executor.
	 *
	 */

	DUK_DDDPRINT("entering bytecode execution");
	duk_js_execute_bytecode(thr);
	DUK_DDDPRINT("returned from bytecode execution");

	/*
	 *  Unwind stack(s) and shift back to old valstack_bottom.
	 */

	DUK_ASSERT(thr->callstack_top == entry_callstack_top + 1);

	duk_hthread_catchstack_unwind(thr, entry_catchstack_top);
	duk_hthread_callstack_unwind(thr, entry_callstack_top);

	thr->valstack_bottom = thr->valstack + entry_valstack_bottom_index;
	/* keep current valstack_top */

	DUK_ASSERT(thr->valstack_bottom >= thr->valstack);
	DUK_ASSERT(thr->valstack_top >= thr->valstack_bottom);
	DUK_ASSERT(thr->valstack_end >= thr->valstack_top);
	DUK_ASSERT(thr->valstack_top - thr->valstack_bottom >= idx_func + 1);

	/*
	 *  Manipulate value stack so that return value is on top.
	 */

	/* [... func this (crud) retval] */

	duk_replace(ctx, idx_func);
	duk_set_top(ctx, idx_func + 1);

	/* [... retval] */

	/* ensure there is internal valstack spare before we exit */

	duk_require_valstack_resize((duk_context *) thr,
	                            (thr->valstack_top - thr->valstack) +            /* top of current func */
	                                DUK_VALSTACK_INTERNAL_EXTRA,                 /* + spare => min_new_size */
	                            1);                                              /* allow_shrink */

	/*
	 *  Shrink checks and return with success.
	 */

	retval = DUK_ERR_EXEC_SUCCESS;
	goto shrink_and_finished;	

 shrink_and_finished:
	/* these are "soft" shrink checks, whose failures are ignored */
	/* XXX: would be nice if fast path was inlined */
	duk_hthread_catchstack_shrink_check(thr);
	duk_hthread_callstack_shrink_check(thr);
	goto finished;

 finished:
	if (need_setjmp) {
		/* Note: either pointer may be NULL (at entry), so don't assert;
		 * this is now done potentially twice, which is OK
		 */
		DUK_DDDPRINT("restore jmpbuf_ptr: %p -> %p, errhandler: %p -> %p (possibly already done)",
		             (thr && thr->heap ? thr->heap->lj.jmpbuf_ptr : NULL),
		             old_jmpbuf_ptr,
		             (thr && thr->heap ? thr->heap->lj.errhandler : NULL),
		             old_errhandler);
		thr->heap->lj.jmpbuf_ptr = old_jmpbuf_ptr;
		thr->heap->lj.errhandler = old_errhandler;

		/* These are just convenience "wiping" of state */
		thr->heap->lj.type = DUK_LJ_TYPE_UNKNOWN;
		thr->heap->lj.iserror = 0;

		/* FIXME: what about side effects here? finalizer runs should be shielded
		 * from errors so even out-of-memory should not be an issue here.
		 */
		DUK_TVAL_SET_TVAL(&tv_tmp, &thr->heap->lj.value1);
		DUK_TVAL_SET_UNDEFINED_UNUSED(&thr->heap->lj.value1);
		DUK_TVAL_DECREF(thr, &tv_tmp);

		DUK_TVAL_SET_TVAL(&tv_tmp, &thr->heap->lj.value2);
		DUK_TVAL_SET_UNDEFINED_UNUSED(&thr->heap->lj.value2);
		DUK_TVAL_DECREF(thr, &tv_tmp);

		DUK_DDDPRINT("setjmp catchpoint torn down");
	}

	thr->heap->curr_thread = entry_curr_thread;  /* may be NULL */
	thr->state = entry_thread_state;

	DUK_ASSERT((thr->state == DUK_HTHREAD_STATE_INACTIVE && thr->heap->curr_thread == NULL) ||  /* first call */
	           (thr->state == DUK_HTHREAD_STATE_INACTIVE && thr->heap->curr_thread != NULL) ||  /* other call */
	           (thr->state == DUK_HTHREAD_STATE_RUNNING && thr->heap->curr_thread == thr));     /* current thread */
	
	thr->heap->call_recursion_depth = entry_call_recursion_depth;

	return retval;

 thread_state_error:
	DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, "invalid thread state for call (%d)", thr->state);
	DUK_NEVER_HERE();
	return DUK_ERR_EXEC_ERROR;  /* never executed */
}

/*
 *  Manipulate value stack so that exactly 'num_stack_rets' return
 *  values are at 'idx_retbase' in every case, assuming there are
 *  'rc' return values on top of stack.
 *
 *  This is a bit tricky, because the called C function operates in
 *  the same activation record and may have e.g. popped the stack
 *  empty (below idx_retbase).
 */

static void safe_call_adjust_valstack(duk_hthread *thr, int idx_retbase, int num_stack_rets, int num_actual_rets) {
	duk_context *ctx = (duk_context *) thr;
	int idx_rcbase;

	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(idx_retbase >= 0);
	DUK_ASSERT(num_stack_rets >= 0);
	DUK_ASSERT(num_actual_rets >= 0);

	idx_rcbase = duk_get_top(ctx) - num_actual_rets;  /* base of known return values */

	DUK_DDDPRINT("adjust valstack after func call: "
	             "num_stack_rets=%d, num_actual_rets=%d, stack_top=%d, idx_retbase=%d, idx_rcbase=%d",
	             num_stack_rets, num_actual_rets, duk_get_top(ctx), idx_retbase, idx_rcbase);

	DUK_ASSERT(idx_rcbase >= 0);  /* caller must check */

	/* ensure space for final configuration (idx_retbase + num_stack_rets) and
	 * intermediate configurations
	 */
	duk_require_stack_top(ctx,
	                      (idx_rcbase > idx_retbase ? idx_rcbase : idx_retbase) +
	                      num_stack_rets);

	/* chop extra retvals away / extend with undefined */
	duk_set_top(ctx, idx_rcbase + num_stack_rets);

	if (idx_rcbase >= idx_retbase) {
		int count = idx_rcbase - idx_retbase;
		int i;

		DUK_DDDPRINT("elements at/after idx_retbase have enough to cover func retvals "
		             "(idx_retbase=%d, idx_rcbase=%d)", idx_retbase, idx_rcbase);

		/* nuke values at idx_retbase to get the first retval (initially
		 * at idx_rcbase) to idx_retbase
		 */

		DUK_ASSERT(count >= 0);

		for (i = 0; i < count; i++) {
			/* XXX: inefficient; block remove primitive */
			duk_remove(ctx, idx_retbase);
		}
	} else {
		int count = idx_retbase - idx_rcbase;
		int i;

		DUK_DDDPRINT("not enough elements at/after idx_retbase to cover func retvals "
		             "(idx_retbase=%d, idx_rcbase=%d)", idx_retbase, idx_rcbase);

		/* insert 'undefined' values at idx_rcbase to get the
		 * return values to idx_retbase
		 */

		DUK_ASSERT(count > 0);

		for (i = 0; i < count; i++) {
			/* XXX: inefficient; block insert primitive */
			duk_push_undefined(ctx);
			duk_insert(ctx, idx_rcbase);
		}
	}
}

/*
 *  Make a "C protected call" within the current activation.
 *
 *  The allowed thread states for making a call are the same as for
 *  duk_handle_call().
 *
 *  Note that like duk_handle_call(), even if this call is protected,
 *  there are a few situations where the current (pre-entry) setjmp
 *  catcher (or a fatal error handler if no such catcher exists) is
 *  invoked:
 *
 *    - Blatant API argument errors (e.g. num_stack_args is invalid,
 *      so we can't form a reasonable return stack)
 *
 *    - Errors during error handling, e.g. failure to reallocate
 *      space in the value stack due to an alloc error
 *
 *  Such errors propagate outwards, ultimately to the fatal error
 *  handler if nothing else.
 */

/* FIXME: bump preventcount by one for the duration of this call? */

int duk_handle_safe_call(duk_hthread *thr,
                         duk_safe_call_function func,
                         int num_stack_args,
                         int num_stack_rets,
                         duk_hobject *errhandler) {
	duk_context *ctx = (duk_context *) thr;
	int entry_valstack_bottom_index;
	int entry_callstack_top;
	int entry_catchstack_top;
	int entry_call_recursion_depth;
	duk_hthread *entry_curr_thread;
	duk_u8 entry_thread_state;
	duk_jmpbuf *old_jmpbuf_ptr = NULL;
	duk_hobject *old_errhandler = NULL;
	duk_jmpbuf our_jmpbuf;
	duk_tval tv_tmp;
	int idx_retbase;
	int retval;
	int rc;

	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(ctx != NULL);
	DUK_ASSERT(errhandler == NULL || DUK_HOBJECT_IS_CALLABLE(errhandler));
	DUK_ASSERT_REFCOUNT_NONZERO_HEAPHDR((duk_heaphdr *) errhandler);

	/* Note: careful with indices like '-x'; if 'x' is zero, it refers to bottom */
	entry_valstack_bottom_index = (int) (thr->valstack_bottom - thr->valstack);
	entry_callstack_top = thr->callstack_top;
	entry_catchstack_top = thr->catchstack_top;
	entry_call_recursion_depth = thr->heap->call_recursion_depth;
	entry_curr_thread = thr->heap->curr_thread;  /* Note: may be NULL if first call */
	entry_thread_state = thr->state;
	idx_retbase = duk_get_top(ctx) - num_stack_args;  /* Note: not a valid stack index if num_stack_args == 0 */

	/* Note: cannot portably debug print a function pointer, hence 'func' not printed! */
	DUK_DDPRINT("duk_handle_safe_call: thr=%p, num_stack_args=%d, num_stack_rets=%d, "
	            "errhandler=%p, valstack_top=%d, idx_retbase=%d, rec_depth=%d/%d, "
	            "entry_valstack_bottom_index=%d, entry_callstack_top=%d, entry_catchstack_top=%d, "
	            "entry_call_recursion_depth=%d, entry_curr_thread=%p, entry_thread_state=%d",
	            (void *) thr, num_stack_args, num_stack_rets,
	            (void *) errhandler, duk_get_top(ctx), idx_retbase, thr->heap->call_recursion_depth,
	            thr->heap->call_recursion_limit, entry_valstack_bottom_index,
	            entry_callstack_top, entry_catchstack_top, entry_call_recursion_depth,
	            entry_curr_thread, entry_thread_state);

	if (idx_retbase < 0) {
		/*
		 *  Since stack indices are not reliable, we can't do anything useful
		 *  here.  Invoke the existing setjmp catcher, or if it doesn't exist,
		 *  call the fatal error handler.
		 */

		DUK_ERROR(thr, DUK_ERR_API_ERROR, "invalid arguments");
	}

	/* setjmp catchpoint setup */

	old_errhandler = thr->heap->lj.errhandler;
	old_jmpbuf_ptr = thr->heap->lj.jmpbuf_ptr;

	thr->heap->lj.errhandler = errhandler;  /* may be NULL */
	thr->heap->lj.jmpbuf_ptr = &our_jmpbuf;

	if (setjmp(thr->heap->lj.jmpbuf_ptr->jb) == 0) {
		goto handle_call;
	}

	/*
	 *  Error during call.  The error value is at heap->lj.value1.
	 *
	 *  Careful with variable accesses here; must be assigned to before
	 *  setjmp() or be declared volatile.  See duk_handle_call().
	 *
	 *  The following are such variables:
	 *    - duk_handle_safe_call() parameters
	 *    - entry_*
	 *    - idx_retbase
	 *
	 *  The very first thing we do is restore the previous setjmp catcher.
	 *  This means that any error in error handling will propagate outwards
	 *  instead of causing a setjmp() re-entry above.  The *only* actual
	 *  errors that should happen here are allocation errors.
	 */

	DUK_DDDPRINT("error caught during protected duk_handle_safe_call()");

	DUK_ASSERT(thr->heap->lj.type == DUK_LJ_TYPE_THROW);
	DUK_ASSERT(thr->callstack_top >= entry_callstack_top);
	DUK_ASSERT(thr->catchstack_top >= entry_catchstack_top);

	/* Note: either pointer may be NULL (at entry), so don't assert;
	 * these are now restored twice which is OK.
	 */
	thr->heap->lj.jmpbuf_ptr = old_jmpbuf_ptr;
	thr->heap->lj.errhandler = old_errhandler;

	duk_hthread_catchstack_unwind(thr, entry_catchstack_top);
	duk_hthread_callstack_unwind(thr, entry_callstack_top);
	thr->valstack_bottom = thr->valstack + entry_valstack_bottom_index;

	/* [ ... | (crud) ] */

	/* FIXME: space in valstack?  see discussion in duk_handle_call. */
	duk_push_tval(ctx, &thr->heap->lj.value1);

	/* [ ... | (crud) errobj ] */

	DUK_ASSERT(duk_get_top(ctx) >= 1);  /* at least errobj must be on stack */

	/* check that the valstack has space for the final amount and any
	 * intermediate space needed; this is unoptimal but should be safe
	 */
	duk_require_stack_top(ctx, idx_retbase + num_stack_rets);  /* final configuration */
	duk_require_stack(ctx, num_stack_rets);

	safe_call_adjust_valstack(thr, idx_retbase, num_stack_rets, 1);  /* 1 = num actual 'return values' */

	/* [ ... | ] or [ ... | errobj (M * undefined)] where M = num_stack_rets - 1 */

#ifdef DUK_USE_DDDEBUG
	DUK_DDPRINT("protected safe_call error handling finished, thread dump:");
	DUK_DEBUG_DUMP_HTHREAD(thr);
#endif

	retval = DUK_ERR_EXEC_ERROR;
	goto shrink_and_finished;

	/*
	 *  Handle call (inside setjmp)
	 */

 handle_call:

	DUK_DDDPRINT("safe_call setjmp catchpoint setup complete, errhandler=%p",
	             (void *) thr->heap->lj.errhandler);

	/*
	 *  Thread state check and book-keeping.
	 */

	if (thr == thr->heap->curr_thread) {
		/* same thread */
		if (thr->state != DUK_HTHREAD_STATE_RUNNING) {
			/* should actually never happen, but check anyway */
			goto thread_state_error;
		}
	} else {
		/* different thread */
		DUK_ASSERT(thr->heap->curr_thread == NULL ||
		           thr->heap->curr_thread->state == DUK_HTHREAD_STATE_RUNNING);
		if (thr->state != DUK_HTHREAD_STATE_INACTIVE) {
			goto thread_state_error;
		}
		thr->heap->curr_thread = thr;
		thr->state = DUK_HTHREAD_STATE_RUNNING;

		/* Note: multiple threads may be simultaneously in the RUNNING
		 * state, but not in the same "resume chain".
		 */
	}

	DUK_ASSERT(thr->heap->curr_thread == thr);
	DUK_ASSERT(thr->state == DUK_HTHREAD_STATE_RUNNING);

	/*
	 *  Recursion limit check.
	 *
	 *  Note: there is no need for an "ignore recursion limit" flag
	 *  for duk_handle_safe_call now.
	 */

	DUK_ASSERT(thr->heap->call_recursion_depth >= 0);
	DUK_ASSERT(thr->heap->call_recursion_depth <= thr->heap->call_recursion_limit);
	if (thr->heap->call_recursion_depth >= thr->heap->call_recursion_limit) {
		DUK_ERROR(thr, DUK_ERR_INTERNAL_ERROR, "maximum C call stack depth reached");
	}
	thr->heap->call_recursion_depth++;

	/*
	 *  Valstack spare check
	 */

	duk_require_stack(ctx, 0);  /* internal spare */

	/*
	 *  Make the C call
	 */

	rc = func(ctx);

	DUK_DDDPRINT("safe_call, func rc=%d", rc);

	/*
	 *  Valstack manipulation for results
	 */

	/* we're running inside the caller's activation, so no change in call/catch stack or valstack bottom */
	DUK_ASSERT(thr->callstack_top == entry_callstack_top);
	DUK_ASSERT(thr->catchstack_top == entry_catchstack_top);
	DUK_ASSERT(thr->valstack_bottom - thr->valstack == entry_valstack_bottom_index);
	DUK_ASSERT(thr->valstack_bottom >= thr->valstack);
	DUK_ASSERT(thr->valstack_top >= thr->valstack_bottom);
	DUK_ASSERT(thr->valstack_end >= thr->valstack_top);

	if (rc < 0) {
		duk_error_throw_from_negative_rc(thr, rc);
	}
	DUK_ASSERT(rc >= 0);

	if (duk_get_top(ctx) < rc) {
		DUK_ERROR(thr, DUK_ERR_API_ERROR, "not enough stack values for safe_call rc");
	}

	safe_call_adjust_valstack(thr, idx_retbase, num_stack_rets, rc);

	/* Note: no need from callstack / catchstack shrink check */
	retval = DUK_ERR_EXEC_SUCCESS;
	goto finished;

 shrink_and_finished:
	/* these are "soft" shrink checks, whose failures are ignored */
	/* XXX: would be nice if fast path was inlined */
	duk_hthread_catchstack_shrink_check(thr);
	duk_hthread_callstack_shrink_check(thr);
	goto finished;

 finished:
	/* Note: either pointer may be NULL (at entry), so don't assert */
	thr->heap->lj.jmpbuf_ptr = old_jmpbuf_ptr;
	thr->heap->lj.errhandler = old_errhandler;

	/* These are just convenience "wiping" of state */
	thr->heap->lj.type = DUK_LJ_TYPE_UNKNOWN;
	thr->heap->lj.iserror = 0;

	/* FIXME: what about side effects here? finalizer runs should be shielded
	 * from errors so even out-of-memory should not be an issue here.
	 */
	DUK_TVAL_SET_TVAL(&tv_tmp, &thr->heap->lj.value1);
	DUK_TVAL_SET_UNDEFINED_UNUSED(&thr->heap->lj.value1);
	DUK_TVAL_DECREF(thr, &tv_tmp);

	DUK_TVAL_SET_TVAL(&tv_tmp, &thr->heap->lj.value2);
	DUK_TVAL_SET_UNDEFINED_UNUSED(&thr->heap->lj.value2);
	DUK_TVAL_DECREF(thr, &tv_tmp);

	DUK_DDDPRINT("setjmp catchpoint torn down");

	thr->heap->curr_thread = entry_curr_thread;  /* may be NULL */
	thr->state = entry_thread_state;

	DUK_ASSERT((thr->state == DUK_HTHREAD_STATE_INACTIVE && thr->heap->curr_thread == NULL) ||  /* first call */
	           (thr->state == DUK_HTHREAD_STATE_INACTIVE && thr->heap->curr_thread != NULL) ||  /* other call */
	           (thr->state == DUK_HTHREAD_STATE_RUNNING && thr->heap->curr_thread == thr));     /* current thread */

	thr->heap->call_recursion_depth = entry_call_recursion_depth;

	/* stack discipline consistency check */
	DUK_ASSERT(duk_get_top(ctx) == idx_retbase + num_stack_rets);

	return retval;

 thread_state_error:
	DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, "invalid thread state for safe_call (%d)", thr->state);
	DUK_NEVER_HERE();
	return DUK_ERR_EXEC_ERROR;  /* never executed */
}

/*
 *  Helper for handling an Ecmascript-to-Ecmascript call or an Ecmascript
 *  function (initial) __duk__.resume().
 *
 *  Compared to normal calls handled by duk_handle_call(), there are a
 *  bunch of differences:
 *
 *    - the call is never protected, and current errhandler is not changed
 *    - there is no C recursion depth increase (hence an "ignore recursion
 *      limit" flag is not applicable)
 *    - instead of making the call, this helper just performs the thread
 *      setup and returns; the bytecode executor then restarts execution
 *      internally
 *    - ecmascript functions are never 'vararg' functions (they access
 *      varargs through the 'arguments' object)
 *
 *  The callstack of the target contains an earlier Ecmascript call in case
 *  of an Ecmascript-to-Ecmascript call (whose idx_retval is updated), or
 *  is empty in case of an initial __duk__.resume().
 */

void duk_handle_ecma_call_setup(duk_hthread *thr,
                                int num_stack_args,
                                int call_flags) {
	duk_context *ctx = (duk_context *) thr;
	int entry_valstack_bottom_index;
	int idx_func;         /* valstack index of 'func' and retval (relative to entry valstack_bottom) */
	int idx_args;         /* valstack index of start of args (arg1) (relative to entry valstack_bottom) */
	int nargs;            /* # argument registers target function wants (< 0 => never for ecma calls) */
	int nregs;            /* # total registers target function wants on entry (< 0 => never for ecma calls) */
	duk_hobject *func;    /* 'func' on stack (borrowed reference) */
	duk_activation *act;
	duk_hobject *env;

	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(ctx != NULL);
	DUK_ASSERT(!((call_flags & DUK_CALL_FLAG_IS_RESUME) != 0 && (call_flags & DUK_CALL_FLAG_IS_TAILCALL) != 0));

	/* XXX: assume these? */
	DUK_ASSERT(thr->valstack != NULL);
	DUK_ASSERT(thr->callstack != NULL);
	DUK_ASSERT(thr->catchstack != NULL);

	/* no need to handle thread state book-keeping here */
	DUK_ASSERT((call_flags & DUK_CALL_FLAG_IS_RESUME) != 0 ||
	           (thr->state == DUK_HTHREAD_STATE_RUNNING &&
	            thr->heap->curr_thread == thr));

	/* if a tailcall:
	 *   - an Ecmascript activation must be on top of the callstack
	 *   - there cannot be any active catchstack entries
	 */
#ifdef DUK_USE_ASSERTIONS
	if (call_flags & DUK_CALL_FLAG_IS_TAILCALL) {
		int our_callstack_index;
		int i;

		DUK_ASSERT(thr->callstack_top >= 1);
		our_callstack_index = thr->callstack_top - 1;
		DUK_ASSERT(our_callstack_index >= 0 && our_callstack_index < thr->callstack_size);
		DUK_ASSERT(thr->callstack[our_callstack_index].func != NULL);
		DUK_ASSERT(DUK_HOBJECT_IS_COMPILEDFUNCTION(thr->callstack[our_callstack_index].func));

		/* now checks entire callstack, would suffice to check just the top entry */
		for (i = 0; i < thr->catchstack_top; i++) {
			DUK_ASSERT(thr->catchstack[i].callstack_index < our_callstack_index);
		}
	}
#endif  /* DUK_USE_ASSERTIONS */

	entry_valstack_bottom_index = (int) (thr->valstack_bottom - thr->valstack);
	idx_func = duk_normalize_index(thr, -num_stack_args - 2);
	idx_args = idx_func + 2;

	DUK_DDPRINT("handle_ecma_call_setup: thr=%p, "
	            "num_stack_args=%d, call_flags=%d (resume=%d, tailcall=%d), "
	            "idx_func=%d, idx_args=%d, entry_valstack_bottom_index=%d",
	            (void *) thr,
	            num_stack_args,
	            call_flags,
	            ((call_flags & DUK_CALL_FLAG_IS_RESUME) != 0 ? 1 : 0),
	            ((call_flags & DUK_CALL_FLAG_IS_TAILCALL) != 0 ? 1 : 0),
	            idx_func,
	            idx_args,
	            entry_valstack_bottom_index);

#ifdef DUK_USE_DDDEBUG
	DUK_DPRINT("callstack before call setup:");
	DUK_DEBUG_DUMP_CALLSTACK(thr);
#endif

	if (idx_func < 0 || idx_args < 0) {
		/* XXX: assert? compiler is responsible for this never happening */
		DUK_ERROR(thr, DUK_ERR_API_ERROR, "invalid func index");
	}

	/*
	 *  Check the function type, handle bound function chains,
	 *  and prepare parameters for the rest of the call handling.
	 *  Also figure out the effective 'this' binding, which replaces
	 *  the current value at idx_func + 1.
	 *
	 *  If the target function is a 'bound' one, follow the chain
	 *  of 'bound' functions until a non-bound function is found.
	 *  During this process, bound arguments are 'prepended' to
	 *  existing ones, and the "this" binding is overridden.
	 *  See E5 Section 15.3.4.5.1.
	 */

	if (!duk_is_callable(thr, idx_func)) {
		DUK_ERROR(ctx, DUK_ERR_TYPE_ERROR, "call target not callable");
	}
	func = duk_get_hobject(thr, idx_func);
	DUK_ASSERT(func != NULL);

	if (DUK_HOBJECT_HAS_BOUND(func)) {
		/* slow path for bound functions */
		handle_bound_chain_for_call(thr, idx_func, &num_stack_args, &func);
	}
	DUK_ASSERT(!DUK_HOBJECT_HAS_BOUND(func));
	DUK_ASSERT(DUK_HOBJECT_IS_COMPILEDFUNCTION(func));  /* caller must ensure this */

	handle_coerce_effective_this_binding(thr, func, idx_func + 1);
	DUK_DDDPRINT("effective 'this' binding is: %!T", duk_get_tval(ctx, idx_func + 1));

	nargs = ((duk_hcompiledfunction *) func)->nargs;
	nregs = ((duk_hcompiledfunction *) func)->nregs;
	DUK_ASSERT(nregs >= nargs);

	/* [ ... func this arg1 ... argN ] */

	/*
	 *  Preliminary activation record and valstack manipulation.
	 *  The concrete actions depend on whether the we're dealing
	 *  with a tailcall (reuse an existing activation), a resume,
	 *  or a normal call.
	 *
	 *  The basic actions, in varying order, are:
	 *
	 *    - Check stack size for call handling
	 *    - Grow call stack if necessary (non-tail-calls)
	 *    - Update current activation (idx_retval) if necessary
	 *      (non-tail, non-resume calls)
	 *    - Move start of args (idx_args) to valstack bottom
	 *      (tail calls)
	 *
	 *  Don't touch valstack_bottom or valstack_top yet so that Duktape API
	 *  calls work normally.
	 */

	/* XXX: some overlapping code; cleanup */

	if (call_flags & DUK_CALL_FLAG_IS_TAILCALL) {
#ifdef DUK_USE_REFERENCE_COUNTING
		duk_hobject *tmp;
#endif
		duk_tval *tv1, *tv2;
		duk_tval tv_tmp;
		int i;

		DUK_DDDPRINT("is tailcall, reusing activation at callstack top, at index %d",
		             thr->callstack_top - 1);

		DUK_ASSERT(thr->callstack_top <= thr->callstack_size);
		act = thr->callstack + thr->callstack_top - 1;

		DUK_ASSERT(!DUK_HOBJECT_HAS_BOUND(func));
		DUK_ASSERT(!DUK_HOBJECT_HAS_NATIVEFUNCTION(func));
		DUK_ASSERT(DUK_HOBJECT_HAS_COMPILEDFUNCTION(func));

		/* Note: since activation is still reachable, refcount manipulation
		 * must be very careful to avoid side effect issues.  Also, 'act'
		 * must be looked up again (finalizer calls may reallocate).
		 */

		/* XXX: a 'raw' decref + explicit refzero check afterwards would be
		 * very useful here.
		 */

#ifdef DUK_USE_REFERENCE_COUNTING
		tmp = act->var_env;
#endif
		act->var_env = NULL;
#ifdef DUK_USE_REFERENCE_COUNTING
		DUK_HOBJECT_DECREF(thr, tmp);  /* side effects */
		act = thr->callstack + thr->callstack_top - 1;
#endif

#ifdef DUK_USE_REFERENCE_COUNTING
		tmp = act->lex_env;
#endif
		act->lex_env = NULL;
#ifdef DUK_USE_REFERENCE_COUNTING
		DUK_HOBJECT_DECREF(thr, tmp);  /* side effects */
		act = thr->callstack + thr->callstack_top - 1;
#endif

		DUK_DDDPRINT("tailcall -> decref func");
#ifdef DUK_USE_REFERENCE_COUNTING
		tmp = act->func;
		DUK_ASSERT(tmp != NULL);
		DUK_ASSERT(DUK_HOBJECT_IS_COMPILEDFUNCTION(tmp));
#endif
		act->func = func;  /* don't want an intermediate exposed state with func == NULL */
		act->pc = 0;       /* don't want an intermediate exposed state with invalid pc */
#ifdef DUK_USE_REFERENCE_COUNTING
		DUK_HOBJECT_INCREF(thr, func);
		DUK_HOBJECT_DECREF(thr, tmp);  /* side effects */
		act = thr->callstack + thr->callstack_top - 1;
#endif

		act->flags = (DUK_HOBJECT_HAS_STRICT(func) ?
		              DUK_ACT_FLAG_STRICT | DUK_ACT_FLAG_TAILCALLED :
	        	      DUK_ACT_FLAG_TAILCALLED);
	
		/* act->func: already updated */
		/* act->var_env: already NULLed */
		/* act->lex_env: already NULLed */
		/* act->pc: already zeroed */
		act->idx_bottom = entry_valstack_bottom_index;  /* tail call -> reuse current "frame" */
		DUK_ASSERT(nregs >= 0);
#if 0  /* topmost activation idx_retval is considered garbage, no need to init */
		act->idx_retval = -1;  /* idx_retval is a 'caller' retval, so init to "unused" here */
#endif

		/*
		 *  Manipulate valstack so that args are on the current bottom and the
		 *  previous caller's 'this' binding (which is the value preceding the
		 *  current bottom) is replaced with the new 'this' binding:
		 *
		 *       [ ... this_old | (crud) func this_new arg1 ... argN ]
		 *  -->  [ ... this_new | arg1 ... argN ]
		 *
		 *  For tailcalling to work properly, the valstack bottom must not grow
		 *  here; otherwise crud would accumulate on the valstack.
		 */

		tv1 = thr->valstack_bottom - 1;
		tv2 = thr->valstack_bottom + idx_func + 1;
		DUK_ASSERT(tv1 >= thr->valstack && tv1 < thr->valstack_top);  /* tv1 is -below- valstack_bottom */
		DUK_ASSERT(tv2 >= thr->valstack_bottom && tv2 < thr->valstack_top);
		DUK_TVAL_SET_TVAL(&tv_tmp, tv1);
		DUK_TVAL_SET_TVAL(tv1, tv2);
		DUK_TVAL_INCREF(thr, tv1);
		DUK_TVAL_DECREF(thr, &tv_tmp);  /* side effects */
		
		for (i = 0; i < idx_args; i++) {
			/* XXX: block removal API primitive */
			/* Note: 'func' is popped from valstack here, but it is
			 * already reachable from the activation.
			 */
			duk_remove(ctx, 0);
		}
		idx_func = 0;  /* really 'not applicable' anymore, should not be referenced after this */
		idx_args = 0;

		/* [ ... this_new | arg1 ... argN ] */

		/* now we can also do the valstack resize check */

		duk_require_valstack_resize((duk_context *) thr,
		                            (thr->valstack_bottom - thr->valstack) +         /* bottom of current func */
		                                idx_args +                                   /* bottom of new func (always 0 here) */
		                                nregs +                                      /* num entries of new func at entry */
		                                DUK_VALSTACK_INTERNAL_EXTRA,                 /* + spare => min_new_size */
		                            1);                                              /* allow_shrink */
	} else {
		DUK_DDDPRINT("not a tailcall, pushing a new activation to callstack, to index %d",
		             thr->callstack_top);

		duk_hthread_callstack_grow(thr);

		/* func wants args clamped to 'nargs' */

		duk_require_valstack_resize((duk_context *) thr,
		                            (thr->valstack_bottom - thr->valstack) +         /* bottom of current func */
		                                idx_args +                                   /* bottom of new func */
		                                nregs +                                      /* num entries of new func at entry */
		                                DUK_VALSTACK_INTERNAL_EXTRA,                 /* + spare => min_new_size */
		                            1);                                              /* allow_shrink */

		if (call_flags & DUK_CALL_FLAG_IS_RESUME) {
			DUK_DDDPRINT("is resume -> no update to current activation (may not even exist)");
		} else {
			DUK_DDDPRINT("update to current activation idx_retval");
			DUK_ASSERT(thr->callstack_top < thr->callstack_size);
			DUK_ASSERT(thr->callstack_top >= 1);
			act = thr->callstack + thr->callstack_top - 1;
			DUK_ASSERT(act->func != NULL);
			DUK_ASSERT(DUK_HOBJECT_IS_COMPILEDFUNCTION(act->func));
			act->idx_retval = entry_valstack_bottom_index + idx_func;
		}

		DUK_ASSERT(thr->callstack_top < thr->callstack_size);
		act = &thr->callstack[thr->callstack_top];
		thr->callstack_top++;
		DUK_ASSERT(thr->callstack_top <= thr->callstack_size);

		DUK_ASSERT(!DUK_HOBJECT_HAS_BOUND(func));
		DUK_ASSERT(!DUK_HOBJECT_HAS_NATIVEFUNCTION(func));
		DUK_ASSERT(DUK_HOBJECT_HAS_COMPILEDFUNCTION(func));

		act->flags = (DUK_HOBJECT_HAS_STRICT(func) ?
		              DUK_ACT_FLAG_STRICT :
	        	      0);
		act->func = func;
		act->var_env = NULL;
		act->lex_env = NULL;
		act->pc = 0;
		act->idx_bottom = entry_valstack_bottom_index + idx_args;
		DUK_ASSERT(nregs >= 0);
#if 0  /* topmost activation idx_retval is considered garbage, no need to init */
		act->idx_retval = -1;  /* idx_retval is a 'caller' retval, so init to "unused" here */
#endif

		DUK_HOBJECT_INCREF(thr, func);  /* act->func */
	}

	/* [... func this arg1 ... argN]  (not tail call)
	 * [this | arg1 ... argN]         (tail call)
	 *
	 * idx_args updated to match
	 */

#ifdef DUK_USE_DDDEBUG
	DUK_DPRINT("pushed new activation:");
	DUK_DEBUG_DUMP_ACTIVATION(thr, thr->callstack + thr->callstack_top - 1);
#endif

	/*
	 *  Environment record creation and 'arguments' object creation.
	 *  Named function expression name binding is handled by the
	 *  compiler; the compiled function's parent env will contain
	 *  the (immutable) binding already.
	 *
	 *  Delayed creation (on demand) is handled in duk_js_var.c.
	 */

	DUK_ASSERT(!DUK_HOBJECT_HAS_BOUND(func));  /* bound function chain has already been resolved */

	if (!DUK_HOBJECT_HAS_NEWENV(func)) {
		/* use existing env (e.g. for non-strict eval); cannot have
		 * an own 'arguments' object (but can refer to the existing one)
		 */

		handle_oldenv_for_call(thr, func, act);

		DUK_ASSERT(act->lex_env != NULL);
		DUK_ASSERT(act->var_env != NULL);
		goto env_done;
	}

	DUK_ASSERT(DUK_HOBJECT_HAS_NEWENV(func));

	if (!DUK_HOBJECT_HAS_CREATEARGS(func)) {
		/* no need to create environment record now; leave as NULL */
		DUK_ASSERT(act->lex_env == NULL);
		DUK_ASSERT(act->var_env == NULL);
		goto env_done;
	}

	/* third arg: absolute index (to entire valstack) of idx_bottom of new activation */
	env = duk_create_activation_environment_record(thr, func, act->idx_bottom);
	DUK_ASSERT(env != NULL);

	/* [... arg1 ... argN envobj] */

	DUK_ASSERT(DUK_HOBJECT_HAS_CREATEARGS(func));
	handle_createargs_for_call(thr, func, env, num_stack_args);

	/* [... arg1 ... argN envobj] */

	act->lex_env = env;
	act->var_env = env;
	DUK_HOBJECT_INCREF(thr, act->lex_env);
	DUK_HOBJECT_INCREF(thr, act->var_env);
	duk_pop(ctx);

 env_done:
	/* [... arg1 ... argN] */

	/*
	 *  Setup value stack: clamp to 'nargs', fill up to 'nregs'
	 */

	/* XXX: replace with a single operation */

	DUK_ASSERT(nregs >= 0);
	duk_set_top(ctx, idx_args + nargs);  /* clamp anything above nargs */
	duk_set_top(ctx, idx_args + nregs);  /* extend with undefined */

#ifdef DUK_USE_DDDEBUG
	DUK_DPRINT("callstack after call setup:");
	DUK_DEBUG_DUMP_CALLSTACK(thr);
#endif

	/*
	 *  Shift to new valstack_bottom.
	 */

	thr->valstack_bottom = thr->valstack_bottom + idx_args;
	/* keep current valstack_top */
	DUK_ASSERT(thr->valstack_bottom >= thr->valstack);
	DUK_ASSERT(thr->valstack_top >= thr->valstack_bottom);
	DUK_ASSERT(thr->valstack_end >= thr->valstack_top);

	/*
	 *  Return to bytecode executor, which will resume execution from
	 *  the topmost activation.
	 */
}

#line 1 "duk_js_compiler.c"
/*
 *  Ecmascript compiler.
 *
 *  Parses an input string and generates a function template result.
 *  Compilation may happen in multiple contexts (global code, eval
 *  code, function code).
 *
 *  The parser uses a traditional top-down recursive parsing for the
 *  statement level, and an operator precedence based top-down approach
 *  for the expression level.  The attempt is to minimize the C stack
 *  depth.  Bytecode is generated directly without an intermediate
 *  representation (tree), at the cost of needing two passes over each
 *  function.
 *
 *  The top-down recursive parser functions are named "parse_XXX".
 *
 *  Recursion limits are in key functions to prevent arbitrary C recursion:
 *  function body parsing, statement parsing, and expression parsing.
 *
 *  See doc/compiler.txt for discussion on the design.
 */

/* include removed: duk_internal.h */

/* if highest bit of a register number is set, it refers to a constant instead */
#define  CONST_MARKER                 DUK_JS_CONST_MARKER

/* for array and object literals */
#define  MAX_ARRAY_INIT_VALUES        20
#define  MAX_OBJECT_INIT_PAIRS        10

/* FIXME: hack, remove when const lookup is not O(n) */
#define  GETCONST_MAX_CONSTS_CHECK    256

#define  RECURSION_INCREASE(comp_ctx,thr)  do { \
		DUK_DDDPRINT("RECURSION INCREASE: %s:%d", __FILE__, __LINE__); \
		recursion_increase((comp_ctx)); \
	} while(0)

#define  RECURSION_DECREASE(comp_ctx,thr)  do { \
		DUK_DDDPRINT("RECURSION DECREASE: %s:%d", __FILE__, __LINE__); \
		recursion_decrease((comp_ctx)); \
	} while(0)

/* Note: slots limits below are quite approximate right now, and because they
 * overlap (in control flow), some can be eliminated.
 */

#define  COMPILE_ENTRY_SLOTS          8
#define  FUNCTION_INIT_REQUIRE_SLOTS  16
#define  FUNCTION_BODY_REQUIRE_SLOTS  16
#define  PARSE_STATEMENTS_SLOTS       16

/*
 *  Prototypes
 */

/* lexing */
static void advance_helper(duk_compiler_ctx *comp_ctx, int expect);
static void advance_expect(duk_compiler_ctx *comp_ctx, int expect);
static void advance(duk_compiler_ctx *ctx);

/* function helpers */
static void init_function_valstack_slots(duk_compiler_ctx *comp_ctx);
static void reset_function_for_pass2(duk_compiler_ctx *comp_ctx);
static void init_varmap_and_prologue_for_pass2(duk_compiler_ctx *comp_ctx, int *out_stmt_value_reg);
static void convert_to_function_template(duk_compiler_ctx *comp_ctx);

/* code emission */
static int get_current_pc(duk_compiler_ctx *comp_ctx);
static duk_compiler_instr *get_instr_ptr(duk_compiler_ctx *comp_ctx, int pc);
static void emit(duk_compiler_ctx *comp_ctx, duk_instr ins);
#if 0  /* unused */
static void emit_op_only(duk_compiler_ctx *comp_ctx, int op);
#endif
static void emit_a_b_c(duk_compiler_ctx *comp_ctx, int op, int a, int b, int c);
static void emit_a_b(duk_compiler_ctx *comp_ctx, int op, int a, int b);
#if 0  /* unused */
static void emit_a(duk_compiler_ctx *comp_ctx, int op, int a);
#endif
static void emit_a_bc(duk_compiler_ctx *comp_ctx, int op, int a, int bc);
static void emit_abc(duk_compiler_ctx *comp_ctx, int op, int abc);
static void emit_extraop_b_c(duk_compiler_ctx *comp_ctx, int extraop, int b, int c);
static void emit_extraop_b(duk_compiler_ctx *comp_ctx, int extraop, int b);
static void emit_extraop_only(duk_compiler_ctx *comp_ctx, int extraop);
static void emit_loadint(duk_compiler_ctx *comp_ctx, int reg, int val);
static void emit_jump(duk_compiler_ctx *comp_ctx, int target_pc);
static int emit_jump_empty(duk_compiler_ctx *comp_ctx);
static void insert_jump_empty(duk_compiler_ctx *comp_ctx, int jump_pc);
static void patch_jump(duk_compiler_ctx *comp_ctx, int jump_pc, int target_pc);
static void patch_jump_here(duk_compiler_ctx *comp_ctx, int jump_pc);
static void patch_trycatch(duk_compiler_ctx *comp_ctx, int trycatch_pc, int reg_catch, int const_varname, int flags);
static void emit_if_false_skip(duk_compiler_ctx *comp_ctx, int regconst);
static void emit_if_true_skip(duk_compiler_ctx *comp_ctx, int regconst);
static void emit_invalid(duk_compiler_ctx *comp_ctx);

/* FIXME */
static void copy_ivalue(duk_compiler_ctx *comp_ctx, duk_ivalue *src, duk_ivalue *dst);
static void copy_ispec(duk_compiler_ctx *comp_ctx, duk_ispec *src, duk_ispec *dst);
static int is_whole_get_i32(double x, duk_i32 *ival);

/* ivalue/ispec helpers */
static int alloctemps(duk_compiler_ctx *comp_ctx, int num);
static int alloctemp(duk_compiler_ctx *comp_ctx);
static void settemp_checkmax(duk_compiler_ctx *comp_ctx, int temp_next);
static int getconst(duk_compiler_ctx *comp_ctx);
static int ispec_toregconst_raw(duk_compiler_ctx *comp_ctx,
                                duk_ispec *x,
                                int forced_reg,
                                int allow_const,
                                int require_temp);
static int ispec_toforcedreg(duk_compiler_ctx *comp_ctx, duk_ispec *x, int forced_reg);
static void ivalue_toplain_raw(duk_compiler_ctx *comp_ctx, duk_ivalue *x, int forced_reg);
static void ivalue_toplain(duk_compiler_ctx *comp_ctx, duk_ivalue *x);
static void ivalue_toplain_ignore(duk_compiler_ctx *comp_ctx, duk_ivalue *x);
static int ivalue_toregconst_raw(duk_compiler_ctx *comp_ctx,
                                 duk_ivalue *x,
                                 int forced_reg,
                                 int allow_const,
                                 int require_temp);
static int ivalue_toreg(duk_compiler_ctx *comp_ctx, duk_ivalue *x);
#if 0  /* unused */
static int ivalue_totempreg(duk_compiler_ctx *comp_ctx, duk_ivalue *x);
#endif
static int ivalue_toforcedreg(duk_compiler_ctx *comp_ctx, duk_ivalue *x, int forced_reg);
static int ivalue_toregconst(duk_compiler_ctx *comp_ctx, duk_ivalue *x);

/* identifier handling */
static int lookup_active_register_binding(duk_compiler_ctx *comp_ctx);
static int lookup_lhs(duk_compiler_ctx *ctx, int *out_reg_varbind, int *out_reg_varname);

/* label handling */
static void add_label(duk_compiler_ctx *comp_ctx, duk_hstring *h_label, int pc_label, int label_id);
static void update_label_flags(duk_compiler_ctx *comp_ctx, int label_id, int flags);
static void lookup_active_label(duk_compiler_ctx *comp_ctx, duk_hstring *h_label, int is_break, int *out_label_id, int *out_label_catch_depth, int *out_label_pc, int *out_is_closest);
static void reset_labels_to_length(duk_compiler_ctx *comp_ctx, int len);

/* top-down expression parser */
static void expr_nud(duk_compiler_ctx *comp_ctx, duk_ivalue *res);
static void expr_led(duk_compiler_ctx *comp_ctx, duk_ivalue *left, duk_ivalue *res);
static int expr_lbp(duk_compiler_ctx *comp_ctx);
static int expr_is_empty(duk_compiler_ctx *comp_ctx);

/* exprtop is the top level variant which resets nud/led counts */
static void expr(duk_compiler_ctx *comp_ctx, duk_ivalue *res, int rbp_flags);
static void exprtop(duk_compiler_ctx *ctx, duk_ivalue *res, int rbp_flags);

/* convenience helpers */
static int expr_toreg(duk_compiler_ctx *comp_ctx, duk_ivalue *res, int rbp_flags);
#if 0  /* unused */
static int expr_totempreg(duk_compiler_ctx *comp_ctx, duk_ivalue *res, int rbp);
#endif
static int expr_toforcedreg(duk_compiler_ctx *comp_ctx, duk_ivalue *res, int rbp_flags, int forced_reg);
static int expr_toregconst(duk_compiler_ctx *comp_ctx, duk_ivalue *res, int rbp_flags);
static void expr_toplain(duk_compiler_ctx *comp_ctx, duk_ivalue *res, int rbp_flags);
static void expr_toplain_ignore(duk_compiler_ctx *comp_ctx, duk_ivalue *res, int rbp_flags);
static int exprtop_toreg(duk_compiler_ctx *comp_ctx, duk_ivalue *res, int rbp_flags);
#if 0  /* unused */
static int exprtop_totempreg(duk_compiler_ctx *comp_ctx, duk_ivalue *res, int rbp_flags);
static int exprtop_toforcedreg(duk_compiler_ctx *comp_ctx, duk_ivalue *res, int rbp_flags, int forced_reg);
#endif
static int exprtop_toregconst(duk_compiler_ctx *comp_ctx, duk_ivalue *res, int rbp_flags);
#if 0  /* unused */
static void exprtop_toplain_ignore(duk_compiler_ctx *comp_ctx, duk_ivalue *res, int rbp_flags);
#endif

/* expression parsing helpers */
static int parse_arguments(duk_compiler_ctx *comp_ctx, duk_ivalue *res);
static void nud_array_literal(duk_compiler_ctx *comp_ctx, duk_ivalue *res);
static void nud_object_literal(duk_compiler_ctx *comp_ctx, duk_ivalue *res);
static int nud_object_literal_key_check(duk_compiler_ctx *comp_ctx, int new_key_flags);

/* statement parsing */
static void parse_variable_declaration(duk_compiler_ctx *comp_ctx, duk_ivalue *res, int expr_flags, int *out_reg_varname, int *out_reg_varbind);
static void parse_var_statement(duk_compiler_ctx *comp_ctx, duk_ivalue *res);
static void parse_for_statement(duk_compiler_ctx *comp_ctx, duk_ivalue *res, int pc_label_site);
static void parse_switch_statement(duk_compiler_ctx *comp_ctx, duk_ivalue *res, int pc_label_site);
static void parse_if_statement(duk_compiler_ctx *comp_ctx, duk_ivalue *res);
static void parse_do_statement(duk_compiler_ctx *comp_ctx, duk_ivalue *res, int pc_label_site);
static void parse_while_statement(duk_compiler_ctx *comp_ctx, duk_ivalue *res, int pc_label_site);
static void parse_break_or_continue_statement(duk_compiler_ctx *comp_ctx, duk_ivalue *res);
static void parse_return_statement(duk_compiler_ctx *comp_ctx, duk_ivalue *res);
static void parse_throw_statement(duk_compiler_ctx *comp_ctx, duk_ivalue *res);
static void parse_try_statement(duk_compiler_ctx *comp_ctx, duk_ivalue *res);
static void parse_with_statement(duk_compiler_ctx *comp_ctx, duk_ivalue *res);
static void parse_statement(duk_compiler_ctx *comp_ctx, duk_ivalue *res, int allow_source_elem);
static void parse_statements(duk_compiler_ctx *comp_ctx, int allow_source_elem, int expect_eof);

static void parse_function_body(duk_compiler_ctx *comp_ctx, int expect_eof, int implicit_return_value);
static void parse_function_formals(duk_compiler_ctx *comp_ctx);
static void parse_function_like_raw(duk_compiler_ctx *comp_ctx, int is_decl, int is_setget);
static int parse_function_like_fnum(duk_compiler_ctx *comp_ctx, int is_decl, int is_setget);

/*
 *  Parser control values for tokens.  The token table is ordered by the
 *  DUK_TOK_XXX defines.
 *
 *  The binding powers are for lbp() use (i.e. for use in led() context).
 *  Binding powers are positive for typing convenience, and bits at the
 *  top should be reserved for flags.  Binding power step must be higher
 *  than 1 so that binding power "lbp - 1" can be used for right associative
 *  operators.  Currently a step of 2 is used (which frees one more bit for
 *  flags).
 */

/* FIXME: actually single step levels would work just fine, clean up */

/* binding power "levels" (see doc/compiler.txt) */
#define  BP_INVALID                0             /* always terminates led() */
#define  BP_EOF                    2
#define  BP_CLOSING                4             /* token closes expression, e.g. ')', ']' */
#define  BP_FOR_EXPR               BP_CLOSING    /* bp to use when parsing a top level Expression */
#define  BP_COMMA                  6
#define  BP_ASSIGNMENT             8
#define  BP_CONDITIONAL            10
#define  BP_LOR                    12
#define  BP_LAND                   14
#define  BP_BOR                    16
#define  BP_BXOR                   18
#define  BP_BAND                   20
#define  BP_EQUALITY               22
#define  BP_RELATIONAL             24
#define  BP_SHIFT                  26
#define  BP_ADDITIVE               28
#define  BP_MULTIPLICATIVE         30
#define  BP_POSTFIX                32
#define  BP_CALL                   34
#define  BP_MEMBER                 36

#define  TOKEN_LBP_BP_MASK         0x1f
#define  TOKEN_LBP_FLAG_NO_REGEXP  (1 << 5)   /* regexp literal must not follow this token */
#define  TOKEN_LBP_FLAG_TERMINATES (1 << 6)   /* FIXME: terminates expression; e.g. post-increment/-decrement */
#define  TOKEN_LBP_FLAG_UNUSED     (1 << 7)   /* spare */

#define  TOKEN_LBP_GET_BP(x)       ((int) (((x) & TOKEN_LBP_BP_MASK) * 2))

#define  MK_LBP(bp)                ((bp) >> 1)    /* bp is assumed to be even */
#define  MK_LBP_FLAGS(bp,flags)    (((bp) >> 1) | (flags))

static duk_i8 token_lbp[] = {
	MK_LBP(BP_EOF),                                 /* DUK_TOK_EOF */
	MK_LBP(BP_INVALID),                             /* DUK_TOK_LINETERM */
	MK_LBP(BP_INVALID),                             /* DUK_TOK_COMMENT */
	MK_LBP_FLAGS(BP_INVALID, TOKEN_LBP_FLAG_NO_REGEXP),  /* DUK_TOK_IDENTIFIER */
	MK_LBP(BP_INVALID),                             /* DUK_TOK_BREAK */
	MK_LBP(BP_INVALID),                             /* DUK_TOK_CASE */
	MK_LBP(BP_INVALID),                             /* DUK_TOK_CATCH */
	MK_LBP(BP_INVALID),                             /* DUK_TOK_CONTINUE */
	MK_LBP(BP_INVALID),                             /* DUK_TOK_DEBUGGER */
	MK_LBP(BP_INVALID),                             /* DUK_TOK_DEFAULT */
	MK_LBP(BP_INVALID),                             /* DUK_TOK_DELETE */
	MK_LBP(BP_INVALID),                             /* DUK_TOK_DO */
	MK_LBP(BP_INVALID),                             /* DUK_TOK_ELSE */
	MK_LBP(BP_INVALID),                             /* DUK_TOK_FINALLY */
	MK_LBP(BP_INVALID),                             /* DUK_TOK_FOR */
	MK_LBP(BP_INVALID),                             /* DUK_TOK_FUNCTION */
	MK_LBP(BP_INVALID),                             /* DUK_TOK_IF */
	MK_LBP(BP_RELATIONAL),                          /* DUK_TOK_IN */
	MK_LBP(BP_RELATIONAL),                          /* DUK_TOK_INSTANCEOF */
	MK_LBP(BP_INVALID),                             /* DUK_TOK_NEW */
	MK_LBP(BP_INVALID),                             /* DUK_TOK_RETURN */
	MK_LBP(BP_INVALID),                             /* DUK_TOK_SWITCH */
	MK_LBP_FLAGS(BP_INVALID, TOKEN_LBP_FLAG_NO_REGEXP),  /* DUK_TOK_THIS */
	MK_LBP(BP_INVALID),                             /* DUK_TOK_THROW */
	MK_LBP(BP_INVALID),                             /* DUK_TOK_TRY */
	MK_LBP(BP_INVALID),                             /* DUK_TOK_TYPEOF */
	MK_LBP(BP_INVALID),                             /* DUK_TOK_VAR */
	MK_LBP(BP_INVALID),                             /* DUK_TOK_VOID */
	MK_LBP(BP_INVALID),                             /* DUK_TOK_WHILE */
	MK_LBP(BP_INVALID),                             /* DUK_TOK_WITH */
	MK_LBP(BP_INVALID),                             /* DUK_TOK_CLASS */
	MK_LBP(BP_INVALID),                             /* DUK_TOK_CONST */
	MK_LBP(BP_INVALID),                             /* DUK_TOK_ENUM */
	MK_LBP(BP_INVALID),                             /* DUK_TOK_EXPORT */
	MK_LBP(BP_INVALID),                             /* DUK_TOK_EXTENDS */
	MK_LBP(BP_INVALID),                             /* DUK_TOK_IMPORT */
	MK_LBP(BP_INVALID),                             /* DUK_TOK_SUPER */
	MK_LBP_FLAGS(BP_INVALID, TOKEN_LBP_FLAG_NO_REGEXP),  /* DUK_TOK_NULL */
	MK_LBP_FLAGS(BP_INVALID, TOKEN_LBP_FLAG_NO_REGEXP),  /* DUK_TOK_TRUE */
	MK_LBP_FLAGS(BP_INVALID, TOKEN_LBP_FLAG_NO_REGEXP),  /* DUK_TOK_FALSE */
	MK_LBP(BP_INVALID),                             /* DUK_TOK_GET */
	MK_LBP(BP_INVALID),                             /* DUK_TOK_SET */
	MK_LBP(BP_INVALID),                             /* DUK_TOK_IMPLEMENTS */
	MK_LBP(BP_INVALID),                             /* DUK_TOK_INTERFACE */
	MK_LBP(BP_INVALID),                             /* DUK_TOK_LET */
	MK_LBP(BP_INVALID),                             /* DUK_TOK_PACKAGE */
	MK_LBP(BP_INVALID),                             /* DUK_TOK_PRIVATE */
	MK_LBP(BP_INVALID),                             /* DUK_TOK_PROTECTED */
	MK_LBP(BP_INVALID),                             /* DUK_TOK_PUBLIC */
	MK_LBP(BP_INVALID),                             /* DUK_TOK_STATIC */
	MK_LBP(BP_INVALID),                             /* DUK_TOK_YIELD */
	MK_LBP(BP_INVALID),                             /* DUK_TOK_LCURLY */
	MK_LBP_FLAGS(BP_INVALID, TOKEN_LBP_FLAG_NO_REGEXP),  /* DUK_TOK_RCURLY */
	MK_LBP(BP_MEMBER),                              /* DUK_TOK_LBRACKET */
	MK_LBP_FLAGS(BP_CLOSING, TOKEN_LBP_FLAG_NO_REGEXP),  /* DUK_TOK_RBRACKET */
	MK_LBP(BP_CALL),                                /* DUK_TOK_LPAREN */
	MK_LBP_FLAGS(BP_CLOSING, TOKEN_LBP_FLAG_NO_REGEXP),  /* DUK_TOK_RPAREN */
	MK_LBP(BP_MEMBER),                              /* DUK_TOK_PERIOD */
	MK_LBP(BP_INVALID),                             /* DUK_TOK_SEMICOLON */
	MK_LBP(BP_COMMA),                               /* DUK_TOK_COMMA */
	MK_LBP(BP_RELATIONAL),                          /* DUK_TOK_LT */
	MK_LBP(BP_RELATIONAL),                          /* DUK_TOK_GT */
	MK_LBP(BP_RELATIONAL),                          /* DUK_TOK_LE */
	MK_LBP(BP_RELATIONAL),                          /* DUK_TOK_GE */
	MK_LBP(BP_EQUALITY),                            /* DUK_TOK_EQ */
	MK_LBP(BP_EQUALITY),                            /* DUK_TOK_NEQ */
	MK_LBP(BP_EQUALITY),                            /* DUK_TOK_SEQ */
	MK_LBP(BP_EQUALITY),                            /* DUK_TOK_SNEQ */
	MK_LBP(BP_ADDITIVE),                            /* DUK_TOK_ADD */
	MK_LBP(BP_ADDITIVE),                            /* DUK_TOK_SUB */
	MK_LBP(BP_MULTIPLICATIVE),                      /* DUK_TOK_MUL */
	MK_LBP(BP_MULTIPLICATIVE),                      /* DUK_TOK_DIV */
	MK_LBP(BP_MULTIPLICATIVE),                      /* DUK_TOK_MOD */
	MK_LBP(BP_POSTFIX),                             /* DUK_TOK_INCREMENT */
	MK_LBP(BP_POSTFIX),                             /* DUK_TOK_DECREMENT */
	MK_LBP(BP_SHIFT),                               /* DUK_TOK_ALSHIFT */
	MK_LBP(BP_SHIFT),                               /* DUK_TOK_ARSHIFT */
	MK_LBP(BP_SHIFT),                               /* DUK_TOK_RSHIFT */
	MK_LBP(BP_BAND),                                /* DUK_TOK_BAND */
	MK_LBP(BP_BOR),                                 /* DUK_TOK_BOR */
	MK_LBP(BP_BXOR),                                /* DUK_TOK_BXOR */
	MK_LBP(BP_INVALID),                             /* DUK_TOK_LNOT */
	MK_LBP(BP_INVALID),                             /* DUK_TOK_BNOT */
	MK_LBP(BP_LAND),                                /* DUK_TOK_LAND */
	MK_LBP(BP_LOR),                                 /* DUK_TOK_LOR */
	MK_LBP(BP_CONDITIONAL),                         /* DUK_TOK_QUESTION */
	MK_LBP(BP_INVALID),                             /* DUK_TOK_COLON */
	MK_LBP(BP_ASSIGNMENT),                          /* DUK_TOK_EQUALSIGN */
	MK_LBP(BP_ASSIGNMENT),                          /* DUK_TOK_ADD_EQ */
	MK_LBP(BP_ASSIGNMENT),                          /* DUK_TOK_SUB_EQ */
	MK_LBP(BP_ASSIGNMENT),                          /* DUK_TOK_MUL_EQ */
	MK_LBP(BP_ASSIGNMENT),                          /* DUK_TOK_DIV_EQ */
	MK_LBP(BP_ASSIGNMENT),                          /* DUK_TOK_MOD_EQ */
	MK_LBP(BP_ASSIGNMENT),                          /* DUK_TOK_ALSHIFT_EQ */
	MK_LBP(BP_ASSIGNMENT),                          /* DUK_TOK_ARSHIFT_EQ */
	MK_LBP(BP_ASSIGNMENT),                          /* DUK_TOK_RSHIFT_EQ */
	MK_LBP(BP_ASSIGNMENT),                          /* DUK_TOK_BAND_EQ */
	MK_LBP(BP_ASSIGNMENT),                          /* DUK_TOK_BOR_EQ */
	MK_LBP(BP_ASSIGNMENT),                          /* DUK_TOK_BXOR_EQ */
	MK_LBP_FLAGS(BP_INVALID, TOKEN_LBP_FLAG_NO_REGEXP),  /* DUK_TOK_NUMBER */
	MK_LBP_FLAGS(BP_INVALID, TOKEN_LBP_FLAG_NO_REGEXP),  /* DUK_TOK_STRING */
	MK_LBP_FLAGS(BP_INVALID, TOKEN_LBP_FLAG_NO_REGEXP),  /* DUK_TOK_REGEXP */
};

/*
 *  Misc helpers
 */

static void recursion_increase(duk_compiler_ctx *comp_ctx) {
	DUK_ASSERT(comp_ctx != NULL);
	DUK_ASSERT(comp_ctx->recursion_depth >= 0);
	if (comp_ctx->recursion_depth >= comp_ctx->recursion_limit) {
		DUK_ERROR(comp_ctx->thr, DUK_ERR_INTERNAL_ERROR, "compiler recursion limit reached");
	}
	comp_ctx->recursion_depth++;
}

static void recursion_decrease(duk_compiler_ctx *comp_ctx) {
	DUK_ASSERT(comp_ctx != NULL);
	DUK_ASSERT(comp_ctx->recursion_depth > 0);
	comp_ctx->recursion_depth--;
}

static int hstring_is_eval_or_arguments(duk_compiler_ctx *comp_ctx, duk_hstring *h) {
	DUK_ASSERT(h != NULL);
	return DUK_HSTRING_HAS_EVAL_OR_ARGUMENTS(h);
}

static int hstring_is_eval_or_arguments_in_strict_mode(duk_compiler_ctx *comp_ctx, duk_hstring *h) {
	DUK_ASSERT(h != NULL);
	return (comp_ctx->curr_func.is_strict &&
	        DUK_HSTRING_HAS_EVAL_OR_ARGUMENTS(h));
}

/*
 *  Parser advance() token eating functions
 */

/* FIXME: valstack handling is awkward.  Add a valstack helper which
 * avoids dup():ing; valstack_copy(src, dst)?
 */

static void advance_helper(duk_compiler_ctx *comp_ctx, int expect) {
	duk_hthread *thr = comp_ctx->thr;
	duk_context *ctx = (duk_context *) thr;
	int regexp;

	DUK_ASSERT(comp_ctx->curr_token.t >= 0 && comp_ctx->curr_token.t <= DUK_TOK_MAXVAL);  /* MAXVAL is inclusive */

	/*
	 *  Use current token to decide whether a RegExp can follow.
	 *
	 *  We can use either 't' or 't_nores'; the latter would not
	 *  recognize keywords.  Some keywords can be followed by a
	 *  RegExp (e.g. "return"), so using 't' is better.  This is
	 *  not trivial, see doc/compiler.txt.
	 */

	regexp = 1;
	if (token_lbp[comp_ctx->curr_token.t] & TOKEN_LBP_FLAG_NO_REGEXP) {
		regexp = 0;
	}
	if (comp_ctx->curr_func.reject_regexp_in_adv) {
		comp_ctx->curr_func.reject_regexp_in_adv = 0;
		regexp = 0;
	}

	if (expect >= 0 && comp_ctx->curr_token.t != expect) {
		DUK_ERROR(thr, DUK_ERR_SYNTAX_ERROR, "parse error (expected token %d, got %d on line %d)",
		          expect, comp_ctx->curr_token.t, comp_ctx->curr_token.start_line);
	}

	/* make current token the previous; need to fiddle with valstack "backing store" */
	memcpy(&comp_ctx->prev_token, &comp_ctx->curr_token, sizeof(duk_token));
	duk_dup(ctx, comp_ctx->tok11_idx);
	duk_replace(ctx, comp_ctx->tok21_idx);
	duk_dup(ctx, comp_ctx->tok12_idx);
	duk_replace(ctx, comp_ctx->tok22_idx);

	/* parse new token */
	duk_lexer_parse_js_input_element(&comp_ctx->lex,
	                                 &comp_ctx->curr_token,
	                                 comp_ctx->curr_func.is_strict,
	                                 regexp);

	DUK_DDDPRINT("advance: curr: tok=%d/%d,%d-%d,term=%d,%!T,%!T "
	             "prev: tok=%d/%d,%d-%d,term=%d,%!T,%!T",
	             comp_ctx->curr_token.t,
	             comp_ctx->curr_token.t_nores,
	             comp_ctx->curr_token.start_line,
	             comp_ctx->curr_token.end_line,
	             comp_ctx->curr_token.lineterm,
	             duk_get_tval(ctx, comp_ctx->tok11_idx),
	             duk_get_tval(ctx, comp_ctx->tok12_idx),
	             comp_ctx->prev_token.t,
	             comp_ctx->prev_token.t_nores,
	             comp_ctx->prev_token.start_line,
	             comp_ctx->prev_token.end_line,
	             comp_ctx->prev_token.lineterm,
	             duk_get_tval(ctx, comp_ctx->tok21_idx),
	             duk_get_tval(ctx, comp_ctx->tok22_idx));
}

/* advance, expecting current token to be a specific token; parse next token in regexp context */
static void advance_expect(duk_compiler_ctx *comp_ctx, int expect) {
	advance_helper(comp_ctx, expect);
}

/* advance, whatever the current token is; parse next token in regexp context */
static void advance(duk_compiler_ctx *comp_ctx) {
	advance_helper(comp_ctx, -1);
}

/*
 *  Helpers for duk_compiler_func.
 */

/* init function state: inits valstack allocations */
static void init_function_valstack_slots(duk_compiler_ctx *comp_ctx) {
	duk_compiler_func *func = &comp_ctx->curr_func;
	duk_hthread *thr = comp_ctx->thr;
	duk_context *ctx = (duk_context *) thr;
	int entry_top;

	entry_top = duk_get_top(ctx);

	memset(func, 0, sizeof(*func));  /* intentional overlap with earlier memzero */
#ifdef DUK_USE_EXPLICIT_NULL_INIT
	func->h_name = NULL;
	func->h_code = NULL;
	func->h_consts = NULL;
	func->h_funcs = NULL;
	func->h_decls = NULL;
	func->h_labelnames = NULL;
	func->h_labelinfos = NULL;
	func->h_argnames = NULL;
	func->h_varmap = NULL;
#endif

	duk_require_stack(ctx, FUNCTION_INIT_REQUIRE_SLOTS);

	/* FIXME: getter for dynamic buffer */

	duk_push_dynamic_buffer(ctx, 0);
	func->code_idx = entry_top + 0;
	func->h_code = (duk_hbuffer_dynamic *) duk_get_hbuffer(ctx, entry_top + 0);
	DUK_ASSERT(func->h_code != NULL);
	DUK_ASSERT(DUK_HBUFFER_HAS_DYNAMIC(func->h_code));

	duk_push_array(ctx);
	func->consts_idx = entry_top + 1;
	func->h_consts = duk_get_hobject(ctx, entry_top + 1);
	DUK_ASSERT(func->h_consts != NULL);

	duk_push_array(ctx);
	func->funcs_idx = entry_top + 2;
	func->h_funcs = duk_get_hobject(ctx, entry_top + 2);
	DUK_ASSERT(func->h_funcs != NULL);

	duk_push_array(ctx);
	func->decls_idx = entry_top + 3;
	func->h_decls = duk_get_hobject(ctx, entry_top + 3);
	DUK_ASSERT(func->h_decls != NULL);

	duk_push_array(ctx);
	func->labelnames_idx = entry_top + 4;
	func->h_labelnames = duk_get_hobject(ctx, entry_top + 4);
	DUK_ASSERT(func->h_labelnames != NULL);

	duk_push_dynamic_buffer(ctx, 0);
	func->labelinfos_idx = entry_top + 5;
	func->h_labelinfos = (duk_hbuffer_dynamic *) duk_get_hbuffer(ctx, entry_top + 5);
	DUK_ASSERT(func->h_labelinfos != NULL);
	DUK_ASSERT(DUK_HBUFFER_HAS_DYNAMIC(func->h_labelinfos));

	duk_push_array(ctx);
	func->argnames_idx = entry_top + 6;
	func->h_argnames = duk_get_hobject(ctx, entry_top + 6);
	DUK_ASSERT(func->h_argnames != NULL);

	duk_push_object_internal(ctx);
	func->varmap_idx = entry_top + 7;
	func->h_varmap = duk_get_hobject(ctx, entry_top + 7);
	DUK_ASSERT(func->h_varmap != NULL);
}

/* reset function state (prepare for pass 2) */
static void reset_function_for_pass2(duk_compiler_ctx *comp_ctx) {
	duk_compiler_func *func = &comp_ctx->curr_func;
	duk_hthread *thr = comp_ctx->thr;

	/* FIXME: reset buffers while keeping existing spare */

	duk_hbuffer_reset(thr, func->h_code);
	duk_hobject_set_length_zero(thr, func->h_consts);
	duk_hobject_set_length_zero(thr, func->h_funcs);
	duk_hobject_set_length_zero(thr, func->h_labelnames);
	duk_hbuffer_reset(thr, func->h_labelinfos);
	/* keep func->h_argnames; it is fixed for all passes */
}

/* cleanup varmap from any null entries, compact it, etc; returns number
 * of final entries after cleanup.
 */
static int cleanup_varmap(duk_compiler_ctx *comp_ctx) {
	duk_hthread *thr = comp_ctx->thr;
	duk_context *ctx = (duk_context *) thr;
	duk_hobject *h_varmap;
	duk_hstring *h_key;
	duk_tval *tv;
	int i, e_used;
	int ret;

	/* [ ... varmap ] */

	h_varmap = duk_get_hobject(ctx, -1);
	DUK_ASSERT(h_varmap != NULL);

	ret = 0;
	e_used = h_varmap->e_used;
	for (i = 0; i < e_used; i++) {
		h_key = DUK_HOBJECT_E_GET_KEY(h_varmap, i);
		if (!h_key) {
			continue;
		}

		DUK_ASSERT(!DUK_HOBJECT_E_SLOT_IS_ACCESSOR(h_varmap, i));

		/* The entries can either be register numbers or 'null' values.
		 * Thus, no need to DECREF them and get side effects.  DECREF'ing
		 * the keys (strings) can cause memory to be freed but no side
		 * effects as strings don't have finalizers.  This is why we can
		 * rely on the object properties not changing from underneath us.
		 */

		tv = DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(h_varmap, i);
		if (!DUK_TVAL_IS_NUMBER(tv)) {
			DUK_ASSERT(!DUK_TVAL_IS_HEAP_ALLOCATED(tv));
			DUK_TVAL_SET_UNDEFINED_UNUSED(tv);
			DUK_HOBJECT_E_SET_KEY(h_varmap, i, NULL);
			DUK_HSTRING_DECREF(thr, h_key);
		} else {
			ret++;
		}
	}

	duk_compact(ctx, -1);

	return ret;
}

/* convert duk_compiler_func into a function template, leaving the result
 * on top of stack.
 */
/* FIXME: awkward and bloated asm -- use faster internal accesses */
static void convert_to_function_template(duk_compiler_ctx *comp_ctx) {
	duk_compiler_func *func = &comp_ctx->curr_func;
	duk_hthread *thr = comp_ctx->thr;
	duk_context *ctx = (duk_context *) thr;
	duk_hcompiledfunction *h_res;
	duk_hbuffer_fixed *h_data;
	size_t consts_count;
	size_t funcs_count;
	size_t code_count;
	size_t code_size;
	size_t data_size;
	size_t i;
	duk_tval *p_const;
	duk_hobject **p_func;
	duk_instr *p_instr;
	duk_compiler_instr *q_instr;
	duk_tval *tv;

	DUK_DDDPRINT("converting duk_compiler_func to function/template");
	DUK_DDPRINT("code=%!xO consts=%!O funcs=%!O", func->h_code, func->h_consts, func->h_funcs);

	/*
	 *  Push result object and init its flags
	 */

	/* Valstack should suffice here, required on function valstack init */

	(void) duk_push_compiledfunction(ctx);
	h_res = (duk_hcompiledfunction *) duk_get_hobject(ctx, -1);  /* FIXME: specific getter */

	if (func->is_function) {
		DUK_DDDPRINT("function -> set NEWENV");
		DUK_HOBJECT_SET_NEWENV((duk_hobject *) h_res);

		if (!func->is_arguments_shadowed) {
			/* arguments object would be accessible; note that shadowing
			 * bindings are arguments or function declarations, neither
			 * of which are deletable, so this is safe.
			 */

			if (func->id_access_arguments || func->may_direct_eval) {
				DUK_DDDPRINT("function may access 'arguments' object directly or "
				             "indirectly -> set CREATEARGS");
				DUK_HOBJECT_SET_CREATEARGS((duk_hobject *) h_res);
			}
		}
	} else if (func->is_eval && func->is_strict) {
		DUK_DDDPRINT("strict eval code -> set NEWENV");
		DUK_HOBJECT_SET_NEWENV((duk_hobject *) h_res);
	} else {
		/* non-strict eval: env is caller's env or global env (direct vs. indirect call)
		 * global code: env is is global env
		 */
		DUK_DDDPRINT("non-strict eval code or global code -> no NEWENV");
		DUK_ASSERT(!DUK_HOBJECT_HAS_NEWENV((duk_hobject *) h_res));
	}

	if (func->is_function && !func->is_decl && func->h_name != NULL) {
		DUK_DDDPRINT("function expression with a name -> set NAMEBINDING");
		DUK_HOBJECT_SET_NAMEBINDING((duk_hobject *) h_res);
	}

	if (func->is_strict) {
		DUK_DDDPRINT("function is strict -> set STRICT");
		DUK_HOBJECT_SET_STRICT((duk_hobject *) h_res);
	}

	/*
	 *  Build function fixed size 'data' buffer, which contains bytecode,
	 *  constants, and inner function references.
	 *
	 *  During the building phase 'data' is reachable but incomplete.
	 *  Only incref's occur during building (no refzero or GC happens),
	 *  so the building process is atomic.
	 */

	consts_count = duk_hobject_get_length(comp_ctx->thr, func->h_consts);
	funcs_count = duk_hobject_get_length(comp_ctx->thr, func->h_funcs);
	code_count = DUK_HBUFFER_GET_SIZE(func->h_code) / sizeof(duk_compiler_instr);
	code_size = code_count * sizeof(duk_instr);

	data_size = consts_count * sizeof(duk_tval) +
	            funcs_count * sizeof(duk_hobject *) +
	            code_size;

	DUK_DDDPRINT("consts_count=%d, funcs_count=%d, code_size=%d -> "
	             "data_size=%d*%d + %d*%d + %d = %d",
	             (int) consts_count, (int) funcs_count, (int) code_size,
	             (int) consts_count, (int) sizeof(duk_tval),
	             (int) funcs_count, (int) sizeof(duk_hobject *),
	             (int) code_size, (int) data_size);

	duk_push_fixed_buffer(ctx, data_size);
	h_data = (duk_hbuffer_fixed *) duk_get_hbuffer(ctx, -1);
	DUK_ASSERT(h_data != NULL);

	h_res->data = (duk_hbuffer *) h_data;
	DUK_HEAPHDR_INCREF(thr, h_data);

	p_const = (duk_tval *) DUK_HBUFFER_FIXED_GET_DATA_PTR(h_data);
	for (i = 0; i < consts_count; i++) {
		tv = duk_hobject_find_existing_array_entry_tval_ptr(func->h_consts, i);
		DUK_ASSERT(tv != NULL);
		DUK_TVAL_SET_TVAL(p_const, tv);
		p_const++;
		DUK_TVAL_INCREF(thr, tv);  /* may be a string constant */

		DUK_DDDPRINT("constant: %!T", tv);
	}

	p_func = (duk_hobject **) p_const;
	h_res->funcs = p_func;
	for (i = 0; i < funcs_count; i++) {
		duk_hobject *h;
		tv = duk_hobject_find_existing_array_entry_tval_ptr(func->h_funcs, i);
		DUK_ASSERT(tv != NULL);
		DUK_ASSERT(DUK_TVAL_IS_OBJECT(tv));
		h = DUK_TVAL_GET_OBJECT(tv);
		DUK_ASSERT(h != NULL);
		DUK_ASSERT(DUK_HOBJECT_IS_COMPILEDFUNCTION(h));
		*p_func++ = h;
		DUK_HOBJECT_INCREF(thr, h);

		DUK_DDDPRINT("inner function: %p -> %!iO", (void *) h, h);
	}

	p_instr = (duk_instr *) p_func;
	h_res->bytecode = p_instr;

	/* copy bytecode instructions one at a time */
	DUK_ASSERT(DUK_HBUFFER_HAS_DYNAMIC(func->h_code));
	q_instr = (duk_compiler_instr *) DUK_HBUFFER_DYNAMIC_GET_CURR_DATA_PTR(func->h_code);
	for (i = 0; i < code_count; i++) {
		p_instr[i] = q_instr[i].ins;
	}
	/* Note: 'q_instr' is still used below */

	duk_pop(ctx);  /* 'data' (and everything in it) is reachable through h_res now */

	/*
	 *  Init object properties
	 *
	 *  Properties should be added in decreasing order of access frequency.
	 *  (Not very critical for function templates.)
	 */

	DUK_DDDPRINT("init function properties");

	/* [ ... res ] */

	/* _varmap: omitted if function is guaranteed not to do slow path identifier
	 * accesses or if it would turn out to be empty of actual register mappings
	 * after a cleanup.
	 */
	if (func->id_access_slow ||     /* directly uses slow accesses */
	    func->may_direct_eval ||    /* may indirectly slow access through a direct eval */
	    funcs_count > 0) {          /* has inner functions which may slow access (XXX: this can be optimized by looking at the inner functions) */
		int num_used;
		duk_dup(ctx, func->varmap_idx);
		num_used = cleanup_varmap(comp_ctx);
		DUK_DDDPRINT("cleaned up varmap: %!T (num_used=%d)", duk_get_tval(ctx, -1), num_used);

		if (num_used > 0) {
			duk_def_prop_stridx(ctx, -2, DUK_STRIDX_INT_VARMAP, DUK_PROPDESC_FLAGS_NONE);
		} else {
			DUK_DDDPRINT("varmap is empty after cleanup -> no need to add");
			duk_pop(ctx);
		}
	}

	/* _formals: omitted if function is guaranteed not to need a (non-strict) arguments object */
	if (1) {  /* FIXME: condition */
		/* FIXME: if omitted, recheck handling for 'length' in duk_js_push_closure();
		 * it currently relies on _formals being set.
		 */
		duk_dup(ctx, func->argnames_idx);
		duk_def_prop_stridx(ctx, -2, DUK_STRIDX_INT_FORMALS, DUK_PROPDESC_FLAGS_NONE);
	}

	/* _name */
	if (func->h_name) {
		duk_push_hstring(ctx, func->h_name);
		duk_def_prop_stridx(ctx, -2, DUK_STRIDX_NAME, DUK_PROPDESC_FLAGS_NONE);
	}

	/* _source */
	if (0) {
		/* FIXME: Currently function source code is not stored, as it is not
		 * required by the standard.  Source code should not be stored by
		 * default (user should enable it explicitly), and the source should
		 * probably be compressed with a trivial text compressor; average
		 * compression of 20-30% is quite easy to achieve even with a trivial
		 * compressor (RLE + backwards lookup).
		 */

		/*
		 *  For global or eval code this is straightforward.  For functions
		 *  created with the Function constructor we only get the source for
		 *  the body and must manufacture the "function ..." part.
		 *
		 *  For instance, for constructed functions (v8):
		 *
		 *    > a = new Function("foo", "bar", "print(foo)");
		 *    [Function]
		 *    > a.toString()
		 *    'function anonymous(foo,bar) {\nprint(foo)\n}'
		 *
		 *  Similarly for e.g. getters (v8):
		 *
		 *    > x = { get a(foo,bar) { print(foo); } }
		 *    { a: [Getter] }
		 *    > Object.getOwnPropertyDescriptor(x, 'a').get.toString()
		 *    'function a(foo,bar) { print(foo); }'
		 */

		/* FIXME: need tokenizer indices for start and end to substring */
		/* FIXME: always normalize function declaration part? */
		/* FIXME: if we keep _formals, only need to store body */
#if 0
		duk_push_string(ctx, "FIXME");
		duk_def_prop_stridx(ctx, -2, DUK_STRIDX_INT_SOURCE, DUK_PROPDESC_FLAGS_NONE);
#endif
	}

	/* _pc2line */
	if (1) {  /* FIXME: condition */
		/*
		 *  Size-optimized pc->line mapping.
		 */

		duk_hobject_pc2line_pack(thr, q_instr, code_count);  /* -> pushes fixed buffer */
		duk_def_prop_stridx(ctx, -2, DUK_STRIDX_INT_PC2LINE, DUK_PROPDESC_FLAGS_NONE);

		/* FIXME: if assertions enabled, walk through all valid PCs
		 * and check line mapping.
		 */
	}

	/* _filename */
	if (comp_ctx->h_filename) {
		/*
		 *  Source filename (or equivalent), for identifying thrown errors.
		 */

		duk_push_hstring(ctx, comp_ctx->h_filename);
		duk_def_prop_stridx(ctx, -2, DUK_STRIDX_INT_FILENAME, DUK_PROPDESC_FLAGS_NONE);
	}

	/*
	 *  Init remaining result fields
	 *
	 *  'nregs' controls how large a register frame is allocated.
	 *
	 *  'nargs' controls how many formal arguments are written to registers:
	 *  r0, ... r(nargs-1).  The remaining registers are initialized to
	 *  undefined.
	 */

	DUK_ASSERT(func->temp_max >= 0);
	h_res->nregs = func->temp_max;
	h_res->nargs = duk_hobject_get_length(thr, func->h_argnames);
	DUK_ASSERT(h_res->nregs >= h_res->nargs);  /* pass2 allocation handles this */

	DUK_DDPRINT("converted function: %!ixT", duk_get_tval(ctx, -1));

#ifdef DUK_USE_DDDEBUG
	{
		duk_hcompiledfunction *h;
		duk_instr *p, *p_start, *p_end;

		h = (duk_hcompiledfunction *) duk_get_hobject(ctx, -1);
		p_start = (duk_instr *) DUK_HCOMPILEDFUNCTION_GET_CODE_BASE(h);
		p_end = (duk_instr *) DUK_HCOMPILEDFUNCTION_GET_CODE_END(h);

		p = p_start;
		while (p < p_end) {
			DUK_DDDPRINT("BC %04d: %!I        ; 0x%08x op=%d (%!C) a=%d b=%d c=%d",
			             (int) (p - p_start),
			             (*p),
			             (int) (*p),
			             (int) DUK_DEC_OP(*p),
			             (int) DUK_DEC_OP(*p),
			             (int) DUK_DEC_A(*p),
			             (int) DUK_DEC_B(*p),
			             (int) DUK_DEC_C(*p));
			p++;
		}
	}
#endif
}

/*
 *  Code emission helpers
 */

/* FIXME: clarify on when and where CONST_MARKER is allowed */
/* FIXME: opcode specific assertions on when consts are allowed */

/* FIXME: macro smaller than call? */
static int get_current_pc(duk_compiler_ctx *comp_ctx) {
	return DUK_HBUFFER_GET_SIZE(comp_ctx->curr_func.h_code) / sizeof(duk_compiler_instr);
}

static duk_compiler_instr *get_instr_ptr(duk_compiler_ctx *comp_ctx, int pc) {
	duk_compiler_func *f = &comp_ctx->curr_func;
	char *p;
	duk_compiler_instr *code_begin, *code_end;

	p = (char *) DUK_HBUFFER_DYNAMIC_GET_CURR_DATA_PTR(f->h_code);
	code_begin = (duk_compiler_instr *) p;
	code_end = (duk_compiler_instr *) (p + DUK_HBUFFER_GET_SIZE(f->h_code));
	DUK_UNREF(code_end);

	DUK_ASSERT(pc >= 0);
	DUK_ASSERT(pc < (code_end - code_begin));

	return &code_begin[pc];
}

/* emit instruction; could return PC but that's not needed in the majority
 * of cases.
 */
static void emit(duk_compiler_ctx *comp_ctx, duk_instr ins) {
	duk_hbuffer_dynamic *h;
	int line;
	duk_compiler_instr instr;

	h = comp_ctx->curr_func.h_code;
	line = comp_ctx->curr_token.start_line;  /* approximation, close enough */

	instr.ins = ins;
	instr.line = line;

	duk_hbuffer_append_bytes(comp_ctx->thr, h, (duk_u8 *) &instr, sizeof(instr));
}

#if 0 /* unused */
static void emit_op_only(duk_compiler_ctx *comp_ctx, int op) {
	emit(comp_ctx, DUK_ENC_OP_ABC(op, 0));
}
#endif

static void emit_a_b_c(duk_compiler_ctx *comp_ctx, int op, int a, int b, int c) {
	duk_instr ins;

	DUK_ASSERT(op >= DUK_BC_OP_MIN && op <= DUK_BC_OP_MAX);
	if (b & CONST_MARKER) {
		b = (b & ~CONST_MARKER) + 256;
		DUK_ASSERT(b >= DUK_BC_B_MIN + 256 && b <= DUK_BC_B_MAX);
	}
	if (c & CONST_MARKER) {
		c = (c & ~CONST_MARKER) + 256;
		DUK_ASSERT(c >= DUK_BC_C_MIN + 256 && c <= DUK_BC_C_MAX);
	}
	DUK_ASSERT(a >= DUK_BC_A_MIN && a <= DUK_BC_A_MAX);
	DUK_ASSERT(b >= DUK_BC_B_MIN && b <= DUK_BC_B_MAX);
	DUK_ASSERT(c >= DUK_BC_C_MIN && c <= DUK_BC_C_MAX);

	ins = DUK_ENC_OP_A_B_C(op, a, b, c);
	DUK_DDDPRINT("emit: 0x%08x line=%d pc=%d op=%d (%!C) a=%d b=%d c=%d (%!I)",
	             ins, comp_ctx->curr_token.start_line, get_current_pc(comp_ctx), op, op, a, b, c, ins);
	emit(comp_ctx, ins);
}

static void emit_a_b(duk_compiler_ctx *comp_ctx, int op, int a, int b) {
	emit_a_b_c(comp_ctx, op, a, b, 0);
}

#if 0  /* unused */
static void emit_a(duk_compiler_ctx *comp_ctx, int op, int a) {
	emit_a_b_c(comp_ctx, op, a, 0, 0);
}
#endif

static void emit_a_bc(duk_compiler_ctx *comp_ctx, int op, int a, int bc) {
	duk_instr ins;

	/* allow caller to give a const number with the CONST_MARKER */
	bc = bc & (~CONST_MARKER);

	DUK_ASSERT(op >= DUK_BC_OP_MIN && op <= DUK_BC_OP_MAX);
	DUK_ASSERT(a >= DUK_BC_A_MIN && a <= DUK_BC_A_MAX);
	DUK_ASSERT(bc >= DUK_BC_BC_MIN && bc <= DUK_BC_BC_MAX);
	DUK_ASSERT((bc & CONST_MARKER) == 0);

	ins = DUK_ENC_OP_A_BC(op, a, bc);
	DUK_DDDPRINT("emit: 0x%08x line=%d pc=%d op=%d (%!C) a=%d bc=%d (%!I)",
	             ins, comp_ctx->curr_token.start_line, get_current_pc(comp_ctx), op, op, a, bc, ins);
	emit(comp_ctx, ins);
}

static void emit_abc(duk_compiler_ctx *comp_ctx, int op, int abc) {
	duk_instr ins;

	DUK_ASSERT(op >= DUK_BC_OP_MIN && op <= DUK_BC_OP_MAX);
	DUK_ASSERT(abc >= DUK_BC_ABC_MIN && abc <= DUK_BC_ABC_MAX);
	DUK_ASSERT((abc & CONST_MARKER) == 0);

	ins = DUK_ENC_OP_ABC(op, abc);
	DUK_DDDPRINT("emit: 0x%08x line=%d pc=%d op=%d (%!C) abc=%d (%!I)",
	             ins, comp_ctx->curr_token.start_line, get_current_pc(comp_ctx), op, op, abc, ins);
	emit(comp_ctx, ins);
}

static void emit_extraop_b_c(duk_compiler_ctx *comp_ctx, int extraop, int b, int c) {
	DUK_ASSERT(extraop >= DUK_BC_EXTRAOP_MIN && extraop <= DUK_BC_EXTRAOP_MAX);
	emit_a_b_c(comp_ctx, DUK_OP_EXTRA, extraop, b, c);
}

static void emit_extraop_b(duk_compiler_ctx *comp_ctx, int extraop, int b) {
	DUK_ASSERT(extraop >= DUK_BC_EXTRAOP_MIN && extraop <= DUK_BC_EXTRAOP_MAX);
	emit_a_b_c(comp_ctx, DUK_OP_EXTRA, extraop, b, 0);
}

static void emit_extraop_only(duk_compiler_ctx *comp_ctx, int extraop) {
	DUK_ASSERT(extraop >= DUK_BC_EXTRAOP_MIN && extraop <= DUK_BC_EXTRAOP_MAX);
	emit_a_b_c(comp_ctx, DUK_OP_EXTRA, extraop, 0, 0);
}

static void emit_loadint(duk_compiler_ctx *comp_ctx, int reg, int val) {
	/* FIXME: typing */
	/* FIXME: LDINTX support */
	DUK_DDDPRINT("emit loadint: %d -> reg %d", val, reg);
	DUK_ASSERT(reg >= DUK_BC_A_MIN && reg <= DUK_BC_A_MAX);
	DUK_ASSERT(val + DUK_BC_LDINT_BIAS >= DUK_BC_BC_MIN);
	DUK_ASSERT(val + DUK_BC_LDINT_BIAS <= DUK_BC_BC_MAX);
	emit_a_bc(comp_ctx, DUK_OP_LDINT, reg, val + DUK_BC_LDINT_BIAS);
}

static void emit_jump(duk_compiler_ctx *comp_ctx, int target_pc) {
	duk_hbuffer_dynamic *h;
	int curr_pc;
	int offset;

	h = comp_ctx->curr_func.h_code;
	curr_pc = DUK_HBUFFER_GET_SIZE(h) / sizeof(duk_compiler_instr);
	offset = target_pc - curr_pc - 1;
	DUK_ASSERT(offset + DUK_BC_JUMP_BIAS >= DUK_BC_ABC_MIN);
	DUK_ASSERT(offset + DUK_BC_JUMP_BIAS <= DUK_BC_ABC_MAX);
	emit_abc(comp_ctx, DUK_OP_JUMP, offset + DUK_BC_JUMP_BIAS);
}

static int emit_jump_empty(duk_compiler_ctx *comp_ctx) {
	int ret;

	ret = get_current_pc(comp_ctx);  /* useful for patching jumps later */
	emit_abc(comp_ctx, DUK_OP_JUMP, 0);
	return ret;
}

/* Insert an empty jump in the middle of code emitted earlier.  This is
 * currently needed for compiling for-in.
 */
static void insert_jump_empty(duk_compiler_ctx *comp_ctx, int jump_pc) {
	duk_hbuffer_dynamic *h;
	int line;
	duk_compiler_instr instr;
	size_t offset;

	h = comp_ctx->curr_func.h_code;
	line = comp_ctx->curr_token.start_line;  /* approximation, close enough */

	instr.ins = DUK_ENC_OP_ABC(DUK_OP_JUMP, 0);
	instr.line = line;
	offset = jump_pc * sizeof(duk_compiler_instr);

	duk_hbuffer_insert_bytes(comp_ctx->thr, h, offset, (duk_u8 *) &instr, sizeof(instr));
}

/* Does not assume that jump_pc contains a DUK_OP_JUMP previously; this is intentional
 * to allow e.g. an INVALID opcode be overwritten with a JUMP (label management uses this).
 */
static void patch_jump(duk_compiler_ctx *comp_ctx, int jump_pc, int target_pc) {
	duk_compiler_instr *instr;
	int offset;

	/* allow negative PCs, behave as a no-op */
	if (jump_pc < 0) {
		DUK_DDDPRINT("patch_jump(): nop call, jump_pc=%d (<0), target_pc=%d", jump_pc, target_pc);
		return;
	}
	DUK_ASSERT(jump_pc >= 0);

	/* FIXME: range assert */
	instr = get_instr_ptr(comp_ctx, jump_pc);
	DUK_ASSERT(instr != NULL);

	/* FIXME: range assert */
	offset = target_pc - jump_pc - 1;

	instr->ins = DUK_ENC_OP_ABC(DUK_OP_JUMP, offset + DUK_BC_JUMP_BIAS);
	DUK_DDDPRINT("patch_jump(): jump_pc=%d, target_pc=%d, offset=%d", jump_pc, target_pc, offset);
}

static void patch_jump_here(duk_compiler_ctx *comp_ctx, int jump_pc) {
	patch_jump(comp_ctx, jump_pc, get_current_pc(comp_ctx));
}

static void patch_trycatch(duk_compiler_ctx *comp_ctx, int trycatch_pc, int reg_catch, int const_varname, int flags) {
	duk_compiler_instr *instr;

	instr = get_instr_ptr(comp_ctx, trycatch_pc);
	DUK_ASSERT(instr != NULL);

	instr->ins = DUK_ENC_OP_A_B_C(DUK_OP_TRYCATCH, flags, reg_catch, const_varname);
}

static void emit_if_false_skip(duk_compiler_ctx *comp_ctx, int regconst) {
	emit_a_b_c(comp_ctx, DUK_OP_IF, 0 /*false*/, regconst, 0);
}

static void emit_if_true_skip(duk_compiler_ctx *comp_ctx, int regconst) {
	emit_a_b_c(comp_ctx, DUK_OP_IF, 1 /*true*/, regconst, 0);
}

static void emit_invalid(duk_compiler_ctx *comp_ctx) {
	emit_abc(comp_ctx, DUK_OP_INVALID, 0);
}

/*
 *  Peephole optimizer for finished bytecode.
 *
 *  Does not remove opcodes; currently only straightens out unconditional
 *  jump chains which are generated by several control structures.
 */

static void peephole_optimize_bytecode(duk_compiler_ctx *comp_ctx) {
	duk_hbuffer_dynamic *h;
	duk_compiler_instr *bc;
	int iter;
	int i, n;
	int count_opt;

	h = comp_ctx->curr_func.h_code;
	DUK_ASSERT(h != NULL);
	DUK_ASSERT(DUK_HBUFFER_HAS_DYNAMIC(h));

	bc = (duk_compiler_instr *) DUK_HBUFFER_DYNAMIC_GET_CURR_DATA_PTR(h);
	n = DUK_HBUFFER_GET_SIZE(h) / sizeof(duk_compiler_instr);

	for (iter = 0; iter < DUK_COMPILER_PEEPHOLE_MAXITER; iter++) {
		count_opt = 0;

		for (i = 0; i < n; i++) {
			duk_instr ins;
			int target_pc1;
			int target_pc2;

			ins = bc[i].ins;
			if (DUK_DEC_OP(ins) != DUK_OP_JUMP) {
				continue;
			}
	
			target_pc1 = i + 1 + DUK_DEC_ABC(ins) - DUK_BC_JUMP_BIAS;
			DUK_DDDPRINT("consider jump at pc %d; target_pc=%d", i, target_pc1);
			DUK_ASSERT(target_pc1 >= 0);
			DUK_ASSERT(target_pc1 < n);

			/* Note: if target_pc1 == i, we'll optimize a jump to itself.
			 * This does not need to be checked for explicitly; the case
			 * is rare and max iter breaks us out.
			 */

			ins = bc[target_pc1].ins;
			if (DUK_DEC_OP(ins) != DUK_OP_JUMP) {
				continue;
			}

			target_pc2 = target_pc1 + 1 + DUK_DEC_ABC(ins) - DUK_BC_JUMP_BIAS;

			DUK_DDDPRINT("optimizing jump at pc %d; old target is %d -> new target is %d",
			             i, target_pc1, target_pc2);

			bc[i].ins = DUK_ENC_OP_ABC(DUK_OP_JUMP, target_pc2 - (i + 1) + DUK_BC_JUMP_BIAS);

			count_opt++;
		}

		DUK_DDPRINT("optimized %d jumps on peephole round %d", count_opt, iter + 1);

		if (count_opt == 0) {
			break;
		}
	}
}

/*
 *  Intermediate value helpers
 */

#define  ISREG(comp_ctx,x)              (((x) & CONST_MARKER) == 0)
#define  ISTEMP(comp_ctx,x)             (ISREG((comp_ctx), (x)) && (x) >= ((comp_ctx)->curr_func.temp_first))
#define  GETTEMP(comp_ctx)              ((comp_ctx)->curr_func.temp_next)
#define  SETTEMP(comp_ctx,x)            ((comp_ctx)->curr_func.temp_next = (x))  /* dangerous: must only lower (temp_max not updated) */
#define  SETTEMP_CHECKMAX(comp_ctx,x)   settemp_checkmax((comp_ctx),(x))
#define  ALLOCTEMP(comp_ctx)            alloctemp((comp_ctx))
#define  ALLOCTEMPS(comp_ctx,count)     alloctemps((comp_ctx),(count))

/* FIXME: some code might benefit from SETTEMP_IFTEMP(ctx,x) */

static void copy_ispec(duk_compiler_ctx *comp_ctx, duk_ispec *src, duk_ispec *dst) {
	duk_context *ctx = (duk_context *) comp_ctx->thr;

	/* FIXME: use "dup+replace" primitive */
	dst->t = src->t;
	dst->regconst = src->regconst;
	duk_dup(ctx, src->valstack_idx);
	duk_replace(ctx, dst->valstack_idx);
}

static void copy_ivalue(duk_compiler_ctx *comp_ctx, duk_ivalue *src, duk_ivalue *dst) {
	duk_context *ctx = (duk_context *) comp_ctx->thr;

	/* FIXME: use "dup+replace" primitive */
	dst->t = src->t;
	dst->op = src->op;
	dst->x1.t = src->x1.t;
	dst->x1.regconst = src->x1.regconst;
	dst->x2.t = src->x2.t;
	dst->x2.regconst = src->x2.regconst;
	duk_dup(ctx, src->x1.valstack_idx);
	duk_replace(ctx, dst->x1.valstack_idx);
	duk_dup(ctx, src->x2.valstack_idx);
	duk_replace(ctx, dst->x2.valstack_idx);
}

/* FIXME: to util */
static int is_whole_get_i32(double x, duk_i32 *ival) {
	duk_i32 t;

	if (fpclassify(x) != FP_NORMAL) {
		return 0;
	}

	t = (duk_i32) x;
	if ((double) t == x) {
		*ival = t;
		return 1;
	}

	return 0;
}

static int alloctemps(duk_compiler_ctx *comp_ctx, int num) {
	int res;

	res = comp_ctx->curr_func.temp_next;
	comp_ctx->curr_func.temp_next += num;

	/* FIXME: placeholder, catches most cases */
	if (comp_ctx->curr_func.temp_next > 256) { /* 256 is OK */
		DUK_ERROR(comp_ctx->thr, DUK_ERR_INTERNAL_ERROR, "out of temp regs");
	}

	/* maintain highest 'used' temporary, needed to figure out nregs of function */
	if (comp_ctx->curr_func.temp_next > comp_ctx->curr_func.temp_max) {
		comp_ctx->curr_func.temp_max = comp_ctx->curr_func.temp_next;
	}

	return res;
}

static int alloctemp(duk_compiler_ctx *comp_ctx) {
	return alloctemps(comp_ctx, 1);
}

static void settemp_checkmax(duk_compiler_ctx *comp_ctx, int temp_next) {
	comp_ctx->curr_func.temp_next = temp_next;
	if (temp_next > comp_ctx->curr_func.temp_max) {
		comp_ctx->curr_func.temp_max = temp_next;
	}
}

/* get const for value at valstack top */
static int getconst(duk_compiler_ctx *comp_ctx) {
	duk_hthread *thr = comp_ctx->thr;
	duk_context *ctx = (duk_context *) thr;
	duk_compiler_func *f = &comp_ctx->curr_func;
	duk_tval *tv1;
	int i, n, n_check;

	n = duk_get_length(ctx, f->consts_idx);

	tv1 = duk_get_tval(ctx, -1);
	DUK_ASSERT(tv1 != NULL);

	/* sanity workaround for handling functions with a large number of
	 * constants at least somewhat reasonably.
	 */
	n_check = (n > GETCONST_MAX_CONSTS_CHECK ? GETCONST_MAX_CONSTS_CHECK : n);
	for (i = 0; i < n_check; i++) {
		duk_tval *tv2 = DUK_HOBJECT_A_GET_VALUE_PTR(f->h_consts, i);

		/* Strict equality is NOT enough, because we cannot use the same
		 * constant for e.g. +0 and -0.
		 */
		if (duk_js_samevalue(tv1, tv2)) {
			DUK_DDDPRINT("reused existing constant for %!T -> const index %d", tv1, i);
			duk_pop(ctx);
			return i | CONST_MARKER;
		}
	}

	/* FIXME: placeholder, catches most cases */
	if (n > 255) { /* 255 is OK */
		DUK_ERROR(comp_ctx->thr, DUK_ERR_INTERNAL_ERROR, "out of consts");
	}

	DUK_DDDPRINT("allocating new constant for %!T -> const index %d", tv1, n);
	(void) duk_put_prop_index(ctx, f->consts_idx, n);  /* invalidates tv1, tv2 */
	return n | CONST_MARKER;
}

/* Get the value represented by an duk_ispec to a register or constant.
 * The caller can control the result by indicating whether or not:
 *
 *   (1) a constant is allowed (sometimes the caller needs the result to
 *       be in a register)
 *
 *   (2) a temporary register is required (usually when caller requires
 *       the register to be safely mutable; normally either a bound
 *       register or a temporary register are both OK)
 *
 *   (3) a forced register target needs to be used
 *
 * Bytecode may be emitted to generate the necessary value.  The return
 * value is either a register or a constant.
 */

static int ispec_toregconst_raw(duk_compiler_ctx *comp_ctx,
                                duk_ispec *x,
                                int forced_reg,
                                int allow_const,
                                int require_temp) {
	duk_hthread *thr = comp_ctx->thr;
	duk_context *ctx = (duk_context *) thr;

	DUK_DDDPRINT("ispec_toregconst_raw(): x={%d:%d:%!T}, "
	             "forced_reg=%d, allow_const=%d, require_temp=%d",
	             x->t, x->regconst, duk_get_tval(ctx, x->valstack_idx),
	             forced_reg, allow_const, require_temp);

	switch (x->t) {
	case DUK_ISPEC_VALUE: {
		duk_tval *tv;

		tv = duk_get_tval(ctx, x->valstack_idx);
		DUK_ASSERT(tv != NULL);

		switch (DUK_TVAL_GET_TAG(tv)) {
		case DUK_TAG_UNDEFINED: {
			/* Note: although there is no 'undefined' literal, undefined
			 * values can occur during compilation as a result of e.g.
			 * the 'void' operator.
			 */
			int dest = (forced_reg >= 0 ? forced_reg : ALLOCTEMP(comp_ctx));
			emit_extraop_b_c(comp_ctx, DUK_EXTRAOP_LDUNDEF, dest, 0);
			return dest; 
		}
		case DUK_TAG_NULL: {
			int dest = (forced_reg >= 0 ? forced_reg : ALLOCTEMP(comp_ctx));
			emit_extraop_b_c(comp_ctx, DUK_EXTRAOP_LDNULL, dest, 0);
			return dest;
		}
		case DUK_TAG_BOOLEAN: {
			int dest = (forced_reg >= 0 ? forced_reg : ALLOCTEMP(comp_ctx));
			emit_extraop_b_c(comp_ctx, DUK_EXTRAOP_LDBOOL, dest, DUK_TVAL_GET_BOOLEAN(tv));
			return dest;
		}
		case DUK_TAG_POINTER: {
			DUK_NEVER_HERE();
			break;
		}
		case DUK_TAG_STRING: {
			duk_hstring *h;
			int dest;
			int constidx;

			h = DUK_TVAL_GET_STRING(tv);
			DUK_UNREF(h);
			DUK_ASSERT(h != NULL);

#if 0  /* FIXME: to be implemented? */
			/* Use special opcodes to load short strings */
			if (DUK_HSTRING_GET_BYTELEN(h) <= 2) {
				/* Encode into a single opcode (18 bits can encode 1-2 bytes + length indicator) */
			} else if (DUK_HSTRING_GET_BYTELEN(h) <= 6) {
				/* Encode into a double constant (53 bits can encode 6*8 = 48 bits + 3-bit length */
			}
#endif
			duk_dup(ctx, x->valstack_idx);
			constidx = getconst(comp_ctx);

			if (allow_const) {
				return constidx;
			}

			dest = (forced_reg >= 0 ? forced_reg : ALLOCTEMP(comp_ctx));
			emit_a_bc(comp_ctx, DUK_OP_LDCONST, dest, constidx);
			return dest;
		}
		case DUK_TAG_OBJECT: {
			DUK_NEVER_HERE();
			break;
		}
		case DUK_TAG_BUFFER: {
			DUK_NEVER_HERE();
			break;
		}
		default: {
			/* number */
			int constidx;
			int dest;
			double dval;
			duk_i32 ival;

			DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv));
			dval = DUK_TVAL_GET_NUMBER(tv);

			if (!allow_const) {
				/* A number can be loaded either through a constant or
				 * using LDINT+LDINTX.  Which is better depends on the
				 * context and how many times a certain constant would
				 * be reused.
				 *
				 * Currently, use LDINT if a constant is not allowed
				 * and a LDINT would work.
				 */

				if (is_whole_get_i32(dval, &ival)) {  /* FIXME: to util */
					ival += DUK_BC_LDINT_BIAS;
					if (ival >= DUK_BC_BC_MIN && ival <= DUK_BC_BC_MAX) {
						dest = (forced_reg >= 0 ? forced_reg : ALLOCTEMP(comp_ctx));
						emit_a_bc(comp_ctx, DUK_OP_LDINT, dest, ival);
						return dest;
					}
				}
			}

			duk_dup(ctx, x->valstack_idx);
			constidx = getconst(comp_ctx);

			if (allow_const) {
				return constidx;
			} else {
				dest = (forced_reg >= 0 ? forced_reg : ALLOCTEMP(comp_ctx));
				emit_a_bc(comp_ctx, DUK_OP_LDCONST, dest, constidx);
				return dest;
			}
		}
		}  /* end switch */
	}
	case DUK_ISPEC_REGCONST: {
		if ((x->regconst & CONST_MARKER) && !allow_const) {
			int dest = (forced_reg >= 0 ? forced_reg : ALLOCTEMP(comp_ctx));
			emit_a_bc(comp_ctx, DUK_OP_LDCONST, dest, x->regconst);
			return dest;
		} else {
			if (forced_reg >= 0) {
				if (x->regconst != forced_reg) {
					emit_a_bc(comp_ctx, DUK_OP_LDREG, forced_reg, x->regconst);
				}
				return forced_reg;
			} else {
				if (require_temp && !ISTEMP(comp_ctx, x->regconst)) {
					int dest = ALLOCTEMP(comp_ctx);
					emit_a_bc(comp_ctx, DUK_OP_LDREG, dest, x->regconst);
					return dest;
				} else {
					return x->regconst;
				}
			}
		}
	}
	default: {
		break;
	}
	}

	DUK_ERROR(thr, DUK_ERR_INTERNAL_ERROR, "ispec_toregconst_raw() internal error");
	return 0;	/* FIXME: notreached */
}

static int ispec_toforcedreg(duk_compiler_ctx *comp_ctx, duk_ispec *x, int forced_reg) {
	return ispec_toregconst_raw(comp_ctx, x, forced_reg, 0 /*allow_const*/, 0 /*require_temp*/);
}

/* Coerce an duk_ivalue to a 'plain' value by generating the necessary
 * arithmetic operations, property access, or variable access bytecode.
 *
 * The duk_ivalue argument ('x') is converted into a plain value as a
 * side effect.
 */
static void ivalue_toplain_raw(duk_compiler_ctx *comp_ctx, duk_ivalue *x, int forced_reg) {
	duk_hthread *thr = comp_ctx->thr;
	duk_context *ctx = (duk_context *) thr;

	DUK_DDDPRINT("ivalue_toplain_raw(): x={t=%d,op=%d,x1={%d:%d:%!T},x2={%d:%d:%!T}}, "
	             "forced_reg=%d",
	             x->t, x->op,
	             x->x1.t, x->x1.regconst, duk_get_tval(ctx, x->x1.valstack_idx),
	             x->x2.t, x->x2.regconst, duk_get_tval(ctx, x->x2.valstack_idx),
	             forced_reg);

	switch (x->t) {
	case DUK_IVAL_PLAIN: {
		return;
	}
	/* FIXME: support unary arithmetic ivalues (useful?) */
	case DUK_IVAL_ARITH: {
		int arg1;
		int arg2;
		int dest;
		duk_tval *tv1;
		duk_tval *tv2;

		DUK_DDDPRINT("arith to plain conversion");

		/* inline arithmetic check for constant values */
		/* FIXME: use the exactly same arithmetic function here as in executor */
		if (x->x1.t == DUK_ISPEC_VALUE && x->x2.t == DUK_ISPEC_VALUE) {
			tv1 = duk_get_tval(ctx, x->x1.valstack_idx);
			tv2 = duk_get_tval(ctx, x->x2.valstack_idx);
			DUK_ASSERT(tv1 != NULL);
			DUK_ASSERT(tv2 != NULL);

			DUK_DDDPRINT("arith: tv1=%!T, tv2=%!T", tv1, tv2);

			if (DUK_TVAL_IS_NUMBER(tv1) && DUK_TVAL_IS_NUMBER(tv2)) {
				double d1 = DUK_TVAL_GET_NUMBER(tv1);
				double d2 = DUK_TVAL_GET_NUMBER(tv2);
				double d3;
				int accept = 1;

				DUK_DDDPRINT("arith inline check: d1=%lf, d2=%lf, op=%d", d1, d2, x->op);
				switch (x->op) {
				case DUK_OP_ADD:	d3 = d1 + d2; break;
				case DUK_OP_SUB:	d3 = d1 - d2; break;
				case DUK_OP_MUL:	d3 = d1 * d2; break;
				case DUK_OP_DIV:	d3 = d1 / d2; break;
				default:		accept = 0; break;
				}

				if (accept) {
					x->t = DUK_IVAL_PLAIN;
					DUK_ASSERT(x->x1.t == DUK_ISPEC_VALUE);
					DUK_TVAL_SET_NUMBER(tv1, d3);  /* old value is number: no refcount */
					return;
				}
			} else if (x->op == DUK_OP_ADD && DUK_TVAL_IS_STRING(tv1) && DUK_TVAL_IS_STRING(tv2)) {
				/* inline string concatenation */
				duk_dup(ctx, x->x1.valstack_idx);
				duk_dup(ctx, x->x2.valstack_idx);
				duk_concat(ctx, 2);
				duk_replace(ctx, x->x1.valstack_idx);
				x->t = DUK_IVAL_PLAIN;
				DUK_ASSERT(x->x1.t == DUK_ISPEC_VALUE);
				return;
			}
		}

		arg1 = ispec_toregconst_raw(comp_ctx, &x->x1, -1, 1, 0);  /* no forced reg, allow const, no require temp */
		arg2 = ispec_toregconst_raw(comp_ctx, &x->x2, -1, 1, 0);  /* same flags */

		/* If forced reg, use it as destination.  Otherwise try to
		 * use either coerced ispec if it is a temporary.
		 */
		if (forced_reg >= 0) {
			dest = forced_reg;
		} else if (ISTEMP(comp_ctx, arg1)) {
			dest = arg1;
		} else if (ISTEMP(comp_ctx, arg2)) {
			dest = arg2;
		} else {
			dest = ALLOCTEMP(comp_ctx);
		}

		emit_a_b_c(comp_ctx, x->op, dest, arg1, arg2);

		x->t = DUK_IVAL_PLAIN;
		x->x1.t = DUK_ISPEC_REGCONST;
		x->x1.regconst = dest;
		return;
	}
	case DUK_IVAL_PROP: {
		int arg1 = ispec_toregconst_raw(comp_ctx, &x->x1, -1, 1, 0);  /* no forced reg, allow const, no require temp */
		int arg2 = ispec_toregconst_raw(comp_ctx, &x->x2, -1, 1, 0);  /* same flags */
		int dest;

		if (forced_reg >= 0) {
			dest = forced_reg;
		} else if (ISTEMP(comp_ctx, arg1)) {
			dest = arg1;
		} else if (ISTEMP(comp_ctx, arg2)) {
			dest = arg2;
		} else {
			dest = ALLOCTEMP(comp_ctx);
		}

		emit_a_b_c(comp_ctx, DUK_OP_GETPROP, dest, arg1, arg2);

		x->t = DUK_IVAL_PLAIN;
		x->x1.t = DUK_ISPEC_REGCONST;
		x->x1.regconst = dest;
		return;
	}
	case DUK_IVAL_VAR: {
		/* x1 must be a string */
		int dest;
		int reg_varbind;
		int reg_varname;

		DUK_ASSERT(x->x1.t == DUK_ISPEC_VALUE);

		duk_dup(ctx, x->x1.valstack_idx);
		if (lookup_lhs(comp_ctx, &reg_varbind, &reg_varname)) {
			x->t = DUK_IVAL_PLAIN;
			x->x1.t = DUK_ISPEC_REGCONST;
			x->x1.regconst = reg_varbind;
		} else {
			dest = (forced_reg >= 0 ? forced_reg : ALLOCTEMP(comp_ctx));
			emit_a_b(comp_ctx, DUK_OP_GETVAR, dest, reg_varname);
			x->t = DUK_IVAL_PLAIN;
			x->x1.t = DUK_ISPEC_REGCONST;
			x->x1.regconst = dest;
		}
		return;
	}
	case DUK_IVAL_NONE:
	default: {
		break;
	}
	}

	DUK_ERROR(thr, DUK_ERR_INTERNAL_ERROR, "ivalue_toplain_raw() internal error");
	return;	/* FIXME: unreachable */
}

/* evaluate to plain value, no forced register (temp/bound reg both ok) */
static void ivalue_toplain(duk_compiler_ctx *comp_ctx, duk_ivalue *x) {
	ivalue_toplain_raw(comp_ctx, x, -1);  /* no forced reg */
}

/* evaluate to final form (e.g. coerce GETPROP to code), throw away temp */
static void ivalue_toplain_ignore(duk_compiler_ctx *comp_ctx, duk_ivalue *x) {
	int temp;
	temp = GETTEMP(comp_ctx);
	ivalue_toplain_raw(comp_ctx, x, -1);  /* no forced reg */
	SETTEMP(comp_ctx, temp);
}

/* Coerce an duk_ivalue to a register or constant; result register may
 * be a temp or a bound register.
 *
 * The duk_ivalue argument ('x') is converted into a regconst as a
 * side effect.
 */
static int ivalue_toregconst_raw(duk_compiler_ctx *comp_ctx,
                                 duk_ivalue *x,
                                 int forced_reg,
                                 int allow_const,
                                 int require_temp) {
	duk_hthread *thr = comp_ctx->thr;
	duk_context *ctx = (duk_context *) thr;
	int reg;

	DUK_UNREF(thr);
	DUK_UNREF(ctx);

	DUK_DDDPRINT("ivalue_toregconst_raw(): x={t=%d,op=%d,x1={%d:%d:%!T},x2={%d:%d:%!T}}, "
	             "forced_reg=%d, allow_const=%d, require_temp=%d",
	             x->t, x->op,
	             x->x1.t, x->x1.regconst, duk_get_tval(ctx, x->x1.valstack_idx),
	             x->x2.t, x->x2.regconst, duk_get_tval(ctx, x->x2.valstack_idx),
	             forced_reg, allow_const, require_temp);

	/* first coerce to a plain value */
	ivalue_toplain_raw(comp_ctx, x, forced_reg);
	DUK_ASSERT(x->t == DUK_IVAL_PLAIN);

	/* then to a register */
	reg = ispec_toregconst_raw(comp_ctx, &x->x1, forced_reg, allow_const, require_temp);
	x->x1.t = DUK_ISPEC_REGCONST;
	x->x1.regconst = reg;

	return reg;
}

static int ivalue_toreg(duk_compiler_ctx *comp_ctx, duk_ivalue *x) {
	return ivalue_toregconst_raw(comp_ctx, x, -1, 0, 0);  /* no forced reg, don't allow const, don't require temp */
}

#if 0  /* unused */
static int ivalue_totempreg(duk_compiler_ctx *comp_ctx, duk_ivalue *x) {
	return ivalue_toregconst_raw(comp_ctx, x, -1, 0, 1);  /* no forced reg, don't allow const, require temp */
}
#endif

static int ivalue_toforcedreg(duk_compiler_ctx *comp_ctx, duk_ivalue *x, int forced_reg) {
	return ivalue_toregconst_raw(comp_ctx, x, forced_reg, 0, 0);  /* forced reg, don't allow const, don't require temp */
}

static int ivalue_toregconst(duk_compiler_ctx *comp_ctx, duk_ivalue *x) {
	return ivalue_toregconst_raw(comp_ctx, x, -1, 1, 0);  /* no forced reg, allow const, don't require temp */
}

/* The issues below can be solved with better flags */

/* FIXME: many operations actually want toforcedtemp() -- brand new temp? */
/* FIXME: need a toplain_ignore() which will only coerce a value to a temp
 * register if it might have a side effect.  Side-effect free values do not
 * need to be coerced.
 */

/*
 *  Identifier handling
 */

static int lookup_active_register_binding(duk_compiler_ctx *comp_ctx) {
	duk_hthread *thr = comp_ctx->thr;
	duk_context *ctx = (duk_context *) thr;
	duk_hstring *h_varname;
	int ret;

	DUK_DDDPRINT("resolving identifier reference to '%!T'", duk_get_tval(ctx, -1));

	/*
	 *  Special name handling
	 */

	h_varname = duk_get_hstring(ctx, -1);
	DUK_ASSERT(h_varname != NULL);

	if (h_varname == DUK_HTHREAD_STRING_LC_ARGUMENTS(thr)) {
		DUK_DDDPRINT("flagging function as accessing 'arguments'");
		comp_ctx->curr_func.id_access_arguments = 1;
	}

	/*
	 *  Inside one or more 'with' statements fall back to slow path always.
	 *  (See e.g. test-stmt-with.js.)
	 */

	if (comp_ctx->curr_func.with_depth > 0) {
		DUK_DDDPRINT("identifier lookup inside a 'with' -> fall back to slow path");
		goto slow_path;
	}

	/*
	 *  Any catch bindings ("catch (e)") also affect identifier binding.
	 *
	 *  Currently, the varmap is modified for the duration of the catch
	 *  clause to ensure any identifier accesses with the catch variable
	 *  name will use slow path.
	 */

	duk_get_prop(ctx, comp_ctx->curr_func.varmap_idx);
	if (duk_is_number(ctx, -1)) {
		ret = duk_to_int(ctx, -1);
		duk_pop(ctx);
	} else {
		duk_pop(ctx);
		goto slow_path;
	}

	DUK_DDDPRINT("identifier lookup -> reg %d", ret);
	return ret;

 slow_path:
	DUK_DDDPRINT("identifier lookup -> slow path");

	comp_ctx->curr_func.id_access_slow = 1;
	return -1;
}

/* Lookup an identifier name in the current varmap, indicating whether the
 * identifier is register-bound and if not, allocating a constant for the
 * identifier name.  Returns 1 if register-bound, 0 otherwise.
 */
static int lookup_lhs(duk_compiler_ctx *comp_ctx, int *out_reg_varbind, int *out_reg_varname) {
	duk_hthread *thr = comp_ctx->thr;
	duk_context *ctx = (duk_context *) thr;
	int reg_varbind;
	int reg_varname;

	/* [ ... varname ] */

	duk_dup_top(ctx);
	reg_varbind = lookup_active_register_binding(comp_ctx);

	if (reg_varbind >= 0) {
		*out_reg_varbind = reg_varbind;
		*out_reg_varname = -1;
		duk_pop(ctx);
		return 1;
	} else {
		reg_varname = getconst(comp_ctx);
		*out_reg_varbind = -1;
		*out_reg_varname = reg_varname;
		return 0;
	}
}

/*
 *  Label handling
 *
 *  Labels are initially added with flags prohibiting both break and continue.
 *  When the statement type is finally uncovered (after potentially multiple
 *  labels), all the labels are updated to allow/prohibit break and continue.
 */

static void add_label(duk_compiler_ctx *comp_ctx, duk_hstring *h_label, int pc_label, int label_id) {
	duk_hthread *thr = comp_ctx->thr;
	duk_context *ctx = (duk_context *) thr;
	size_t n;
	size_t new_size;
	char *p;
	duk_labelinfo *li_start, *li;

	/* Duplicate (shadowing) labels are not allowed, except for the empty
	 * labels (which are used as default labels for switch and iteration
	 * statements).
	 *
	 * We could also allow shadowing of non-empty pending labels without any
	 * other issues than breaking the required label shadowing requirements
	 * of the E5 specification, see Section 12.12.
	 */

	p = (char *) DUK_HBUFFER_DYNAMIC_GET_CURR_DATA_PTR(comp_ctx->curr_func.h_labelinfos);
	li_start = (duk_labelinfo *) p;
	li = (duk_labelinfo *) (p + DUK_HBUFFER_GET_SIZE(comp_ctx->curr_func.h_labelinfos));
	n = (size_t) (li - li_start);

	while (li > li_start) {
		li--;

		if (li->h_label == h_label && h_label != DUK_HTHREAD_STRING_EMPTY_STRING(thr)) {
			DUK_ERROR(thr, DUK_ERR_SYNTAX_ERROR, "duplicate (non-empty) label");
		}
	}

	/* XXX: awkward */
	duk_push_hstring(ctx, h_label);
	(void) duk_put_prop_index(ctx, comp_ctx->curr_func.labelnames_idx, n);

	new_size = (n + 1) * sizeof(duk_labelinfo);
	duk_hbuffer_resize(thr, comp_ctx->curr_func.h_labelinfos, new_size, new_size);
	/* FIXME: spare handling, slow now */

	/* relookup after possible realloc */
	p = (char *) DUK_HBUFFER_DYNAMIC_GET_CURR_DATA_PTR(comp_ctx->curr_func.h_labelinfos);
	li_start = (duk_labelinfo *) p;
	li = (duk_labelinfo *) (p + DUK_HBUFFER_GET_SIZE(comp_ctx->curr_func.h_labelinfos));
	li--;

	/* Labels need to be recorded as pending before we know whether they will be
	 * actually be used as part of an iteration statement or a switch statement.
	 * The flags to allow break/continue are updated when we figure out the
	 * statement type.
	 */

	li->flags = 0;
	li->label_id = label_id;
	li->h_label = h_label;
	li->catch_depth = comp_ctx->curr_func.catch_depth;   /* catch depth from current func */
	li->pc_label = pc_label;

	DUK_DDDPRINT("registered label: flags=0x%08x, id=%d, name=%!O, catch_depth=%d, pc_label=%d",
	             li->flags, li->label_id, li->h_label, li->catch_depth, li->pc_label);
}

/* Update all labels with matching label_id. */
static void update_label_flags(duk_compiler_ctx *comp_ctx, int label_id, int flags) {
	char *p;
	duk_labelinfo *li_start, *li;

	p = (char *) DUK_HBUFFER_DYNAMIC_GET_CURR_DATA_PTR(comp_ctx->curr_func.h_labelinfos);
	li_start = (duk_labelinfo *) p;
	li = (duk_labelinfo *) (p + DUK_HBUFFER_GET_SIZE(comp_ctx->curr_func.h_labelinfos));

	/* Match labels starting from latest; once label_id no longer matches, we can
	 * safely exit without checking the rest of the labels (only the topmost labels
	 * are ever updated).
	 */
	while (li > li_start) {
		li--;

		if (li->label_id != label_id) {
			break;
		}

		DUK_DDDPRINT("updating label flags for li=%p, label_id=%d, flags=%d",
		             (void *) li, label_id, flags);

		li->flags = flags;
	}
}

/* Lookup active label information.  Break/continue distinction is necessary to handle switch
 * statement related labels correctly: a switch will only catch a 'break', not a 'continue'.
 *
 * An explicit label cannot appear multiple times in the active set, but empty labels (unlabelled
 * iteration and switch statements) can.  A break will match the closest unlabelled or labelled
 * statement.  A continue will match the closest unlabelled or labelled iteration statement.  It is
 * a syntax error if a continue matches a labelled switch statement; because an explicit label cannot
 * be duplicated, the continue cannot match any valid label outside the switch.
 *
 * A side effect of these rules is that a LABEL statement related to a switch should never actually
 * catch a continue abrupt completion at run-time.  Hence an INVALID opcode can be placed in the
 * continue slot of the switch's LABEL statement.
 */

/* FIXME: awkward, especially the bunch of separate output values -> output struct? */
static void lookup_active_label(duk_compiler_ctx *comp_ctx, duk_hstring *h_label, int is_break, int *out_label_id, int *out_label_catch_depth, int *out_label_pc, int *out_is_closest) {
	duk_hthread *thr = comp_ctx->thr;
	duk_context *ctx = (duk_context *) thr;
	char *p;
	duk_labelinfo *li_start, *li_end, *li;
	int match = 0;

	DUK_DDDPRINT("looking up active label: label='%!O', is_break=%d", h_label, is_break);

	DUK_UNREF(ctx);

	p = (char *) DUK_HBUFFER_DYNAMIC_GET_CURR_DATA_PTR(comp_ctx->curr_func.h_labelinfos);
	li_start = (duk_labelinfo *) p;
	li_end = (duk_labelinfo *) (p + DUK_HBUFFER_GET_SIZE(comp_ctx->curr_func.h_labelinfos));
	li = li_end;

	/* Match labels starting from latest label because there can be duplicate empty
	 * labels in the label set.
	 */
	while (li > li_start) {
		li--;

		if (li->h_label != h_label) {
			DUK_DDDPRINT("labelinfo[%d] ->'%!O' != %!O",
			             (int) (li - li_start), li->h_label, h_label);
			continue;
		}

		DUK_DDDPRINT("labelinfo[%d] -> '%!O' label name matches (still need to check type)",
		             (int) (li - li_start), h_label);

		/* currently all labels accept a break, so no explicit check for it now */
		DUK_ASSERT(li->flags & DUK_LABEL_FLAG_ALLOW_BREAK);

		if (is_break) {
			/* break matches always */
			match = 1;
			break;
		} else if (li->flags & DUK_LABEL_FLAG_ALLOW_CONTINUE) {
			/* iteration statements allow continue */
			match = 1;
			break;
		} else {
			/* continue matched this label -- we can only continue if this is the empty
			 * label, for which duplication is allowed, and thus there is hope of
			 * finding a match deeper in the label stack.
			 */
			if (h_label != DUK_HTHREAD_STRING_EMPTY_STRING(thr)) {
				DUK_ERROR(thr, DUK_ERR_SYNTAX_ERROR, "continue label matches an invalid statement type");
			} else {
				DUK_DDDPRINT("continue matched an empty label which does not "
				             "allow a continue -> continue lookup deeper in label stack");
			}
		}
	}
	/* FIXME: match flag is awkward, rework */
	if (!match) {
		DUK_ERROR(thr, DUK_ERR_SYNTAX_ERROR, "cannot resolve label");
	}

	DUK_DDDPRINT("label match: %!O -> label_id %d, catch_depth=%d, pc_label=%d",
	             h_label, li->label_id, li->catch_depth, li->pc_label);

	*out_label_id = li->label_id;
	*out_label_catch_depth = li->catch_depth;
	*out_label_pc = li->pc_label;
	*out_is_closest = (li == li_end - 1);
}

static void reset_labels_to_length(duk_compiler_ctx *comp_ctx, int len) {
	duk_hthread *thr = comp_ctx->thr;
	duk_context *ctx = (duk_context *) thr;
	size_t new_size;

	/* FIXME: duk_set_length */
	new_size = sizeof(duk_labelinfo) * len;
	duk_push_int(ctx, len);
	duk_put_prop_stridx(ctx, comp_ctx->curr_func.labelnames_idx, DUK_STRIDX_LENGTH);
	duk_hbuffer_resize(thr, comp_ctx->curr_func.h_labelinfos, new_size, new_size);  /* FIXME: spare handling */
}

/*
 *  Expression parsing: expr_nud(), expr_led(), expr_lbp(), and helpers.
 *
 *  - expr_nud(): ("null denotation"): process prev_token as a "start" of an expression (e.g. literal)
 *  - expr_led(): ("left denotation"): process prev_token in the "middle" of an expression (e.g. operator)
 *  - expr_lbp(): ("left-binding power"): return left-binding power of curr_token
 */

/* object literal key tracking flags */
#define  OBJ_LIT_KEY_PLAIN  (1 << 0)  /* key encountered as a plain property */
#define  OBJ_LIT_KEY_GET    (1 << 1)  /* key encountered as a getter */
#define  OBJ_LIT_KEY_SET    (1 << 2)  /* key encountered as a setter */

static void nud_array_literal(duk_compiler_ctx *comp_ctx, duk_ivalue *res) {
	duk_hthread *thr = comp_ctx->thr;
	int reg_obj;            /* result reg */
	int max_init_values;    /* max # of values initialized in one MPUTARR set */
	int temp_start;         /* temp reg value for start of loop */
	int num_values;         /* number of values in current MPUTARR set */
	int curr_idx;           /* current (next) array index */
	int start_idx;          /* start array index of current MPUTARR set */
	int init_idx;           /* last array index explicitly initialized, +1 */
	int reg_temp;           /* temp reg */
	int require_comma;      /* next loop requires a comma */

	/* DUK_TOK_LBRACKET already eaten, current token is right after that */
	DUK_ASSERT(comp_ctx->prev_token.t == DUK_TOK_LBRACKET);

	max_init_values = MAX_ARRAY_INIT_VALUES;  /* XXX: depend on available temps? */

	reg_obj = ALLOCTEMP(comp_ctx);
	emit_extraop_b_c(comp_ctx, DUK_EXTRAOP_NEWARR, reg_obj, 0);  /* XXX: patch initial size afterwards? */
 	temp_start = GETTEMP(comp_ctx);

	/*
	 *  Emit initializers in sets of maximum max_init_values.
	 *  Corner cases such as single value initializers do not have
	 *  special handling now.
	 *
	 *  Elided elements must not be emitted as 'undefined' values,
	 *  because such values would be enumerable (which is incorrect).
	 *  Also note that trailing elisions must be reflected in the
	 *  length of the final array but cause no elements to be actually
	 *  inserted.
	 */

	curr_idx = 0;
	init_idx = 0;         /* tracks maximum initialized index + 1 */
	start_idx = 0;
	require_comma = 0;

	for (;;) {
		num_values = 0;
		SETTEMP(comp_ctx, temp_start);

		if (comp_ctx->curr_token.t == DUK_TOK_RBRACKET) {
			break;
		}

		for (;;) {
			if (comp_ctx->curr_token.t == DUK_TOK_RBRACKET) {
				/* the outer loop will recheck and exit */
				break;
			}

			/* comma check */
			if (require_comma) {
				if (comp_ctx->curr_token.t == DUK_TOK_COMMA) {
					/* comma after a value, expected */
					advance(comp_ctx);
					require_comma = 0;
					continue;
				} else {
					goto syntax_error;
				}
			} else {
				if (comp_ctx->curr_token.t == DUK_TOK_COMMA) {
					/* elision - flush */
					curr_idx++;
					advance(comp_ctx);
					/* if num_values > 0, MPUTARR emitted by outer loop after break */
					break;
				}
			}
			/* else an array initializer element */

			/* initial index */
			if (num_values == 0) {
				start_idx = curr_idx;
				reg_temp = ALLOCTEMP(comp_ctx);
				emit_loadint(comp_ctx, reg_temp, start_idx);
			}

			reg_temp = ALLOCTEMP(comp_ctx);   /* alloc temp just in case, to update max temp */
			SETTEMP(comp_ctx, reg_temp);      /* hope that the sub-expression writes to reg_temp */
			expr_toforcedreg(comp_ctx, res, BP_COMMA /*rbp_flags*/, reg_temp /*forced_reg*/);
			SETTEMP(comp_ctx, reg_temp + 1);

			num_values++;
			curr_idx++;
			require_comma = 1;

			if (num_values >= max_init_values) {
				/* MPUTARR emitted by outer loop */
				break;
			}
		}

		if (num_values > 0) {
			emit_a_b_c(comp_ctx, DUK_OP_MPUTARR, reg_obj, temp_start, num_values);
			init_idx = start_idx + num_values;
#if 0  /* these are not necessary, as they're done at the top of the loop */
			num_values = 0;
			SETTEMP(comp_ctx, temp_start);
#endif
		}	
	}

	DUK_ASSERT(comp_ctx->curr_token.t == DUK_TOK_RBRACKET);
	advance(comp_ctx);

	DUK_DDDPRINT("array literal done, curridx=%d, initidx=%d", curr_idx, init_idx);

	/* trailing elisions? */
	if (curr_idx > init_idx) {
		/* yes, must set array length explicitly */
		DUK_DDDPRINT("array literal has trailing elisions which affect its length");
		reg_temp = ALLOCTEMP(comp_ctx);
		emit_loadint(comp_ctx, reg_temp, curr_idx);
		emit_extraop_b_c(comp_ctx, DUK_EXTRAOP_SETALEN, reg_obj, reg_temp);
	}

	SETTEMP(comp_ctx, temp_start);

	res->t = DUK_IVAL_PLAIN;
	res->x1.t = DUK_ISPEC_REGCONST;
	res->x1.regconst = reg_obj;
	return;

 syntax_error:
	DUK_ERROR(thr, DUK_ERR_SYNTAX_ERROR, "invalid array literal");
}

/* duplicate/invalid key checks; returns 1 if syntax error */
static int nud_object_literal_key_check(duk_compiler_ctx *comp_ctx, int new_key_flags) {
	duk_hthread *thr = comp_ctx->thr;
	duk_context *ctx = (duk_context *) thr;
	int key_flags;

	/* [ ... key_obj key ] */

	DUK_ASSERT(duk_is_string(ctx, -1));

	/*
	 *  'key_obj' tracks keys encountered so far by associating an
	 *  integer with flags with already encountered keys.  The checks
	 *  below implement E5 Section 11.1.5, step 4 for production:
	 *
	 *    PropertyNameAndValueList: PropertyNameAndValueList , PropertyAssignment
	 */

	duk_dup(ctx, -1);       /* [ ... key_obj key key ] */
	duk_get_prop(ctx, -3);  /* [ ... key_obj key val ] */
	key_flags = duk_to_int(ctx, -1);
	duk_pop(ctx);           /* [ ... key_obj key ] */

	if (new_key_flags & OBJ_LIT_KEY_PLAIN) {
		if ((key_flags & OBJ_LIT_KEY_PLAIN) && comp_ctx->curr_func.is_strict) {
			/* step 4.a */
			DUK_DDDPRINT("duplicate key: plain key appears twice in strict mode");
			return 1;
		}
		if (key_flags & (OBJ_LIT_KEY_GET | OBJ_LIT_KEY_SET)) {
			/* step 4.c */
			DUK_DDDPRINT("duplicate key: plain key encountered after setter/getter");
			return 1;
		}
	} else {
		if (key_flags & OBJ_LIT_KEY_PLAIN) {
			/* step 4.b */
			DUK_DDDPRINT("duplicate key: getter/setter encountered after plain key");
			return 1;
		}
		if (key_flags & new_key_flags) {
			/* step 4.d */
			DUK_DDDPRINT("duplicate key: getter/setter encountered twice");
			return 1;
		}
	}

	new_key_flags |= key_flags;
	DUK_DDDPRINT("setting/updating key %!T flags: 0x%08x -> 0x%08x",
	             duk_get_tval(ctx, -1), key_flags, new_key_flags);
	duk_dup(ctx, -1);
	duk_push_int(ctx, new_key_flags);   /* [ ... key_obj key key flags ] */
	duk_put_prop(ctx, -4);              /* [ ... key_obj key ] */

	return 0;
}

static void nud_object_literal(duk_compiler_ctx *comp_ctx, duk_ivalue *res) {
	duk_hthread *thr = comp_ctx->thr;
	duk_context *ctx = (duk_context *) thr;
	int reg_obj;            /* result reg */
	int max_init_pairs;     /* max # of key-value pairs initialized in one MPUTOBJ set */
	int temp_start;         /* temp reg value for start of loop */
	int num_pairs;          /* number of pairs in current MPUTOBJ set */
	int reg_key;            /* temp reg for key literal */
	int reg_temp;           /* temp reg */
	int first;		/* first value: comma must not precede the value */

	DUK_ASSERT(comp_ctx->prev_token.t == DUK_TOK_LCURLY);

	max_init_pairs = MAX_OBJECT_INIT_PAIRS;  /* XXX: depend on available temps? */

	reg_obj = ALLOCTEMP(comp_ctx);
	emit_extraop_b_c(comp_ctx, DUK_EXTRAOP_NEWOBJ, reg_obj, 0);  /* XXX: patch initial size afterwards? */
	temp_start = GETTEMP(comp_ctx);

	/* temp object for tracking / detecting duplicate keys */
	duk_push_object(ctx);

	/*
	 *  Emit initializers in sets of maximum max_init_pairs keys.
	 *  Setter/getter is handled separately and terminates the
	 *  current set of initializer values.  Corner cases such as
	 *  single value initializers do not have special handling now.
	 */

	first = 1;
	for (;;) {
		num_pairs = 0;
		SETTEMP(comp_ctx, temp_start);

		if (comp_ctx->curr_token.t == DUK_TOK_RCURLY) {
			break;
		}

		for (;;) {
			/*
			 *  Three possible element formats:
			 *    1) PropertyName : AssignmentExpression
			 *    2) get PropertyName () { FunctionBody }
			 *    3) set PropertyName ( PropertySetParameterList ) { FunctionBody }
			 *
			 *  PropertyName can be IdentifierName (includes reserved words), a string
			 *  literal, or a number literal.  Note that IdentifierName allows 'get' and
			 *  'set' too, so we need to look ahead to the next token to distinguish:
			 *
			 *     { get : 1 }
			 *
			 *  and
			 *
			 *     { get foo() { return 1 } }
			 *     { get get() { return 1 } }    // 'get' as getter propertyname
			 *
			 *  Finally, a trailing comma is allowed.
			 *
			 *  Key name is coerced to string at compile time (and ends up as a
			 *  a string constant) even for numeric keys (e.g. "{1:'foo'}").
			 *  These could be emitted using e.g. LDINT, but that seems hardly
			 *  worth the effort and would increase code size.
			 */ 

			DUK_DDDPRINT("object literal inner loop, curr_token->t = %d", comp_ctx->curr_token.t);

			if (comp_ctx->curr_token.t == DUK_TOK_RCURLY) {
				/* the outer loop will recheck and exit */
				break;
			}
			if (num_pairs >= max_init_pairs) {
				/* MPUTOBJ emitted by outer loop */
				break;
			}

			if (first) {
				first = 0;
			} else {
				if (comp_ctx->curr_token.t != DUK_TOK_COMMA) {
					goto syntax_error;
				}
				advance(comp_ctx);
				if (comp_ctx->curr_token.t == DUK_TOK_RCURLY) {
					/* trailing comma followed by rcurly */
					break;
				}
			}

			/* advance to get one step of lookup */		
			advance(comp_ctx);

			if ((comp_ctx->prev_token.t == DUK_TOK_GET || comp_ctx->prev_token.t == DUK_TOK_SET) &&
			     comp_ctx->curr_token.t != DUK_TOK_COLON) {
				/* getter/setter */
				int is_getter = (comp_ctx->prev_token.t == DUK_TOK_GET);
				int fnum;
				int reg_temp;

				if (comp_ctx->curr_token.t_nores == DUK_TOK_IDENTIFIER ||
				    comp_ctx->curr_token.t_nores == DUK_TOK_STRING) {
					/* same handling for identifiers and strings */
					DUK_ASSERT(comp_ctx->curr_token.str1 != NULL);
					duk_push_hstring(ctx, comp_ctx->curr_token.str1);
				} else if (comp_ctx->curr_token.t == DUK_TOK_NUMBER) {
					duk_push_number(ctx, comp_ctx->curr_token.num);
					duk_to_string(ctx, -1);
				} else {
					goto syntax_error;
				}

				DUK_ASSERT(duk_is_string(ctx, -1));
				if (nud_object_literal_key_check(comp_ctx,
				                                 (is_getter ? OBJ_LIT_KEY_GET : OBJ_LIT_KEY_SET))) {
					goto syntax_error;
				}
				reg_key = getconst(comp_ctx);

				if (num_pairs > 0) {
					emit_a_b_c(comp_ctx, DUK_OP_MPUTOBJ, reg_obj, temp_start, num_pairs);
					num_pairs = 0;
					SETTEMP(comp_ctx, temp_start);
				}

				/* curr_token = get/set name */
				fnum = parse_function_like_fnum(comp_ctx, 0 /*is_decl*/, 1 /*is_setget*/);

				DUK_ASSERT(GETTEMP(comp_ctx) == temp_start);
				reg_temp = ALLOCTEMP(comp_ctx);
				emit_a_bc(comp_ctx, DUK_OP_LDCONST, reg_temp, reg_key);
				reg_temp = ALLOCTEMP(comp_ctx);
				emit_a_bc(comp_ctx, DUK_OP_CLOSURE, reg_temp, fnum);
				emit_extraop_b_c(comp_ctx,
				                 (is_getter ? DUK_EXTRAOP_INITGET : DUK_EXTRAOP_INITSET),
				                 reg_obj,
				                 temp_start);   /* temp_start+0 = key, temp_start+1 = closure */

				SETTEMP(comp_ctx, temp_start);
			} else {
				/* normal key/value */
				if (comp_ctx->prev_token.t_nores == DUK_TOK_IDENTIFIER ||
				    comp_ctx->prev_token.t_nores == DUK_TOK_STRING) {
					/* same handling for identifiers and strings */
					DUK_ASSERT(comp_ctx->prev_token.str1 != NULL);
					duk_push_hstring(ctx, comp_ctx->prev_token.str1);
				} else if (comp_ctx->prev_token.t == DUK_TOK_NUMBER) {
					duk_push_number(ctx, comp_ctx->prev_token.num);
					duk_to_string(ctx, -1);
				} else {
					goto syntax_error;
				}

				DUK_ASSERT(duk_is_string(ctx, -1));
				if (nud_object_literal_key_check(comp_ctx, OBJ_LIT_KEY_PLAIN)) {
					goto syntax_error;
				}
				reg_key = getconst(comp_ctx);

				reg_temp = ALLOCTEMP(comp_ctx);
				emit_a_bc(comp_ctx, DUK_OP_LDCONST, reg_temp, reg_key);
				advance_expect(comp_ctx, DUK_TOK_COLON);

				reg_temp = ALLOCTEMP(comp_ctx);  /* alloc temp just in case, to update max temp */
				SETTEMP(comp_ctx, reg_temp);
				expr_toforcedreg(comp_ctx, res, BP_COMMA /*rbp_flags*/, reg_temp /*forced_reg*/);
				SETTEMP(comp_ctx, reg_temp + 1);

				num_pairs++;
			}
		}

		if (num_pairs > 0) {
			emit_a_b_c(comp_ctx, DUK_OP_MPUTOBJ, reg_obj, temp_start, num_pairs);
#if 0  /* these are not necessary, as they're done at the top of the loop */
			num_pairs = 0;
			SETTEMP(comp_ctx, temp_start);
#endif
		}
	}

	DUK_ASSERT(comp_ctx->curr_token.t == DUK_TOK_RCURLY);
	advance(comp_ctx);

	SETTEMP(comp_ctx, temp_start);

	res->t = DUK_IVAL_PLAIN;
	res->x1.t = DUK_ISPEC_REGCONST;
	res->x1.regconst = reg_obj;

	DUK_DDDPRINT("final tracking object: %!T", duk_get_tval(ctx, -1));
	duk_pop(ctx);
	return;

 syntax_error:
	DUK_ERROR(thr, DUK_ERR_SYNTAX_ERROR, "invalid object literal");
}

/* Parse argument list.  Arguments are written to temps starting from
 * "next temp".  Returns number of arguments parsed.  Expects left paren
 * to be already eaten, and eats the right paren before returning.
 */
static int parse_arguments(duk_compiler_ctx *comp_ctx, duk_ivalue *res) {
	int nargs = 0;
	int tr;

	/* Note: expect that caller has already eaten the left paren */

	DUK_DDDPRINT("start parsing arguments, prev_token.t=%d, curr_token.t=%d",
	             comp_ctx->prev_token.t, comp_ctx->curr_token.t);

	for (;;) {
		if (comp_ctx->curr_token.t == DUK_TOK_RPAREN) {
			break;
		}
		if (nargs > 0) {
			advance_expect(comp_ctx, DUK_TOK_COMMA);
		}

		/* We want the argument expression value to go to "next temp"
		 * without additional moves.  That should almost always be the
		 * case, but we double check after expression parsing.
		 *
		 * This is not the cleanest possible approach.
		 */

		tr = ALLOCTEMP(comp_ctx);  /* bump up "allocated" reg count, just in case */
		SETTEMP(comp_ctx, tr);

		/* binding power must be high enough to NOT allow comma expressions directly */
		expr_toforcedreg(comp_ctx, res, BP_COMMA /*rbp_flags*/, tr);  /* always allow 'in', coerce to 'tr' just in case */

		SETTEMP(comp_ctx, tr + 1);
		nargs++;

		DUK_DDDPRINT("argument #%d written into reg %d", nargs, tr);
	}

	/* eat the right paren */
	advance_expect(comp_ctx, DUK_TOK_RPAREN);

	DUK_DDDPRINT("end parsing arguments");

	return nargs;
}

static int expr_is_empty(duk_compiler_ctx *comp_ctx) {
	/* empty expressions can be detected conveniently with nud/led counts */
	return (comp_ctx->curr_func.nud_count == 0) &&
	       (comp_ctx->curr_func.led_count == 0);
}

static void expr_nud(duk_compiler_ctx *comp_ctx, duk_ivalue *res) {
	duk_hthread *thr = comp_ctx->thr;
	duk_context *ctx = (duk_context *) thr;
	duk_token *tk;
	int temp_at_entry;
	int tok;
	duk_u32 args;	/* temp variable to pass constants to shared code */

	/*
	 *  ctx->prev_token	token to process with expr_nud()
	 *  ctx->curr_token	updated by caller
	 *
	 *  Note: the token in the switch below has already been eaten.
	 */

	temp_at_entry = GETTEMP(comp_ctx);

	comp_ctx->curr_func.nud_count++;

	tk = &comp_ctx->prev_token;
	tok = tk->t;
	res->t = DUK_IVAL_NONE;

	DUK_DDDPRINT("expr_nud(), prev_token.t=%d, allow_in=%d, paren_level=%d",
	             tk->t, comp_ctx->curr_func.allow_in, comp_ctx->curr_func.paren_level);

	switch (tok) {

	/* PRIMARY EXPRESSIONS */

	case DUK_TOK_THIS: {
		int reg_temp;
		reg_temp = ALLOCTEMP(comp_ctx);
		emit_extraop_b(comp_ctx, DUK_EXTRAOP_LDTHIS, reg_temp);
		res->t = DUK_IVAL_PLAIN;
		res->x1.t = DUK_ISPEC_REGCONST;
		res->x1.regconst = reg_temp;
		return;
	}
	case DUK_TOK_IDENTIFIER: {
		res->t = DUK_IVAL_VAR;
		res->x1.t = DUK_ISPEC_VALUE;
		duk_push_hstring(ctx, tk->str1);
		duk_replace(ctx, res->x1.valstack_idx);
		return;
	}
	case DUK_TOK_NULL: {
		duk_push_null(ctx);
		goto plain_value;
	}
	case DUK_TOK_TRUE: {
		duk_push_true(ctx);
		goto plain_value;
	}
	case DUK_TOK_FALSE: {
		duk_push_false(ctx);
		goto plain_value;
	}
	case DUK_TOK_NUMBER: {
		duk_push_number(ctx, tk->num);
		goto plain_value;
	}
	case DUK_TOK_STRING: {
		DUK_ASSERT(tk->str1 != NULL);
		duk_push_hstring(ctx, tk->str1);
		goto plain_value;
	}
	case DUK_TOK_REGEXP: {
		int reg_temp;
		int reg_re_bytecode;  /* const */
		int reg_re_source;    /* const */

		DUK_ASSERT(tk->str1 != NULL);
		DUK_ASSERT(tk->str2 != NULL);

		DUK_DDDPRINT("emitting regexp op, str1=%!O, str2=%!O", tk->str1, tk->str2);

		reg_temp = ALLOCTEMP(comp_ctx);
		duk_push_hstring(ctx, tk->str1);
		duk_push_hstring(ctx, tk->str2);

		/* [ ... pattern flags ] */

		duk_regexp_compile(thr);

		/* [ ... escaped_source bytecode ] */

		reg_re_bytecode = getconst(comp_ctx);
		reg_re_source = getconst(comp_ctx);

		emit_a_b_c(comp_ctx,
		           DUK_OP_REGEXP,
		           reg_temp /*a*/,
		           reg_re_bytecode /*b*/,
		           reg_re_source /*c*/);

		res->t = DUK_IVAL_PLAIN;
		res->x1.t = DUK_ISPEC_REGCONST;
		res->x1.regconst = reg_temp;
		return;
	}
	case DUK_TOK_LBRACKET: {
		DUK_DDDPRINT("parsing array literal");
		nud_array_literal(comp_ctx, res);
		return;
	}
	case DUK_TOK_LCURLY: {
		DUK_DDDPRINT("parsing object literal");
		nud_object_literal(comp_ctx, res);
		return;
	}
	case DUK_TOK_LPAREN: {
		int prev_allow_in;

		comp_ctx->curr_func.paren_level++;
		prev_allow_in = comp_ctx->curr_func.allow_in;
		comp_ctx->curr_func.allow_in = 1; /* reset 'allow_in' for parenthesized expression */

		expr(comp_ctx, res, BP_FOR_EXPR /*rbp_flags*/);  /* Expression, terminates at a ')' */

		advance_expect(comp_ctx, DUK_TOK_RPAREN);
		comp_ctx->curr_func.allow_in = prev_allow_in;
		comp_ctx->curr_func.paren_level--;
		return;
	}

	/* MEMBER/NEW/CALL EXPRESSIONS */

	case DUK_TOK_NEW: {
		/*
		 *  Parsing an expression starting with 'new' is tricky because
		 *  there are multiple possible productions deriving from
		 *  LeftHandSideExpression which begin with 'new'.
		 *
		 *  We currently resort to one-token lookahead to distinguish the
		 *  cases.  Hopefully this is correct.  The binding power must be
		 *  such that parsing ends at an LPAREN (CallExpression) but not at
		 *  a PERIOD or LBRACKET (MemberExpression).
		 *
		 *  See doc/compiler.txt for discussion on the parsing approach,
		 *  and testcases/test-dev-new.js for a bunch of documented tests.
		 */

		int reg_target;
		int nargs;

		DUK_DDDPRINT("begin parsing new expression");

		reg_target = ALLOCTEMP(comp_ctx);
		expr_toforcedreg(comp_ctx, res, BP_CALL /*rbp_flags*/, reg_target /*forced_reg*/);
		SETTEMP(comp_ctx, reg_target + 1);

		if (comp_ctx->curr_token.t == DUK_TOK_LPAREN) {
			/* 'new' MemberExpression Arguments */
			DUK_DDDPRINT("new expression has argument list");
			advance(comp_ctx);
			nargs = parse_arguments(comp_ctx, res);  /* parse args starting from "next temp", reg_target + 1 */
			/* right paren eaten */
		} else {
			/* 'new' MemberExpression */
			DUK_DDDPRINT("new expression has no argument list");
			nargs = 0;
		}

		emit_a_b_c(comp_ctx, DUK_OP_NEW, reg_target /*target*/, reg_target /*start*/, nargs /*num_args*/);

		DUK_DDDPRINT("end parsing new expression");

		res->t = DUK_IVAL_PLAIN;
		res->x1.t = DUK_ISPEC_REGCONST;
		res->x1.regconst = reg_target;
		return;
	}

	/* FUNCTION EXPRESSIONS */

	case DUK_TOK_FUNCTION: {
		/* Function expression.  Note that any statement beginning with 'function'
		 * is handled by the statement parser as a function declaration, or a
		 * non-standard function expression/statement (or a SyntaxError).  We only
		 * handle actual function expressions (occurring inside an expression) here.
		 */

		int reg_temp;
		int fnum;

		reg_temp = ALLOCTEMP(comp_ctx);

		/* curr_token follows 'function' */
		fnum = parse_function_like_fnum(comp_ctx, 0 /*is_decl*/, 0 /*is_setget*/);
		DUK_DDDPRINT("parsed inner function -> fnum %d", fnum);

		emit_a_bc(comp_ctx, DUK_OP_CLOSURE, reg_temp /*a*/, fnum /*bc*/);

		res->t = DUK_IVAL_PLAIN;
		res->x1.t = DUK_ISPEC_REGCONST;
		res->x1.regconst = reg_temp;
		return;
	}

	/* UNARY EXPRESSIONS */

	case DUK_TOK_DELETE: {
		/* Delete semantics are a bit tricky.  The description in E5 specification
		 * is kind of confusing, because it distinguishes between resolvability of
		 * a reference (which is only known at runtime) seemingly at compile time
		 * (= SyntaxError throwing).
		 */
		expr(comp_ctx, res, BP_MULTIPLICATIVE /*rbp_flags*/);  /* UnaryExpression */
		if (res->t == DUK_IVAL_VAR) {
			/* not allowed in strict mode, regardless of whether resolves;
			 * in non-strict mode DELVAR handles both non-resolving and
			 * resolving cases (the specification description is a bit confusing).
			 */

			int reg_temp;
			int reg_varname;
			int reg_varbind;

			if (comp_ctx->curr_func.is_strict) {
				DUK_ERROR(thr, DUK_ERR_SYNTAX_ERROR, "cannot delete identifier");
			}

			SETTEMP(comp_ctx, temp_at_entry);
			reg_temp = ALLOCTEMP(comp_ctx);

			duk_dup(ctx, res->x1.valstack_idx);
			if (lookup_lhs(comp_ctx, &reg_varbind, &reg_varname)) {
				/* register bound variables are non-configurable -> always false */
				emit_extraop_b_c(comp_ctx, DUK_EXTRAOP_LDBOOL, reg_temp, 0);
			} else {
				duk_dup(ctx, res->x1.valstack_idx);
				reg_varname = getconst(comp_ctx);
				emit_a_b(comp_ctx, DUK_OP_DELVAR, reg_temp, reg_varname);
			}
			res->t = DUK_IVAL_PLAIN;
			res->x1.t = DUK_ISPEC_REGCONST;
			res->x1.regconst = reg_temp;
		} else if (res->t == DUK_IVAL_PROP) {
			int reg_temp;
			int reg_obj;
			int reg_key;

			SETTEMP(comp_ctx, temp_at_entry);
			reg_temp = ALLOCTEMP(comp_ctx);
			reg_obj = ispec_toregconst_raw(comp_ctx, &res->x1, -1 /*forced_reg*/, 0 /*allow_const*/, 0 /*require_temp*/);  /* don't allow const */
			reg_key = ispec_toregconst_raw(comp_ctx, &res->x2, -1 /*forced_reg*/, 1 /*allow_const*/, 0 /*require_temp*/);
			emit_a_b_c(comp_ctx, DUK_OP_DELPROP, reg_temp, reg_obj, reg_key);

			res->t = DUK_IVAL_PLAIN;
			res->x1.t = DUK_ISPEC_REGCONST;
			res->x1.regconst = reg_temp;
		} else {
			/* non-Reference deletion is always 'true', even in strict mode */
			duk_push_true(ctx);
			goto plain_value;
		}
		return;
	}
	case DUK_TOK_VOID: {
		expr_toplain_ignore(comp_ctx, res, BP_MULTIPLICATIVE /*rbp_flags*/);  /* UnaryExpression */
		duk_push_undefined(ctx);
		goto plain_value;
	}
	case DUK_TOK_TYPEOF: {
		/* 'typeof' must handle unresolvable references without throwing
		 * a ReferenceError (E5 Section 11.4.3).  Register mapped values
		 * will never be unresolvable so special handling is only required
		 * when an identifier is a "slow path" one.
		 */
		expr(comp_ctx, res, BP_MULTIPLICATIVE /*rbp_flags*/);  /* UnaryExpression */

		if (res->t == DUK_IVAL_VAR) {
			int reg_varbind;
			int reg_varname;
			int tr;

			duk_dup(ctx, res->x1.valstack_idx);
			if (!lookup_lhs(comp_ctx, &reg_varbind, &reg_varname)) {
				DUK_DDDPRINT("typeof for an identifier name which could not be resolved "
				             "at compile time, need to use special run-time handling");
				tr = ALLOCTEMP(comp_ctx);
				emit_extraop_b_c(comp_ctx, DUK_EXTRAOP_TYPEOFID, tr, reg_varname);

				res->t = DUK_IVAL_PLAIN;
				res->x1.t = DUK_ISPEC_REGCONST;
				res->x1.regconst = tr;
				return;
			}
		}

		args = (DUK_EXTRAOP_TYPEOF << 8) + 0;
		goto unary_extraop;
	}
	case DUK_TOK_INCREMENT: {
		args = (DUK_OP_INC << 8) + 0;
		goto preincdec;
	}
	case DUK_TOK_DECREMENT: {
		args = (DUK_OP_DEC << 8) + 0;
		goto preincdec;
	}
	case DUK_TOK_ADD: {
		/* unary plus */
		expr(comp_ctx, res, BP_MULTIPLICATIVE /*rbp_flags*/);  /* UnaryExpression */
		if (res->t == DUK_IVAL_PLAIN && res->x1.t == DUK_ISPEC_VALUE &&
		    duk_is_number(ctx, res->x1.valstack_idx)) {
			/* unary plus of a number is identity */
			;
		} else {
			args = (DUK_OP_UNP << 8) + 0;
			goto unary;
		}
		return;
	}
	case DUK_TOK_SUB: {
		/* unary minus */
		expr(comp_ctx, res, BP_MULTIPLICATIVE /*rbp_flags*/);  /* UnaryExpression */
		if (res->t == DUK_IVAL_PLAIN && res->x1.t == DUK_ISPEC_VALUE &&
		    duk_is_number(ctx, res->x1.valstack_idx)) {
			/* this optimization is important to handle negative literals (which are not directly
			 * provided by the lexical grammar
			 */
			duk_tval *tv_num = duk_get_tval(ctx, res->x1.valstack_idx);
			double d;

			DUK_ASSERT(tv_num != NULL);
			DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv_num));
			d = DUK_TVAL_GET_NUMBER(tv_num);
			DUK_TVAL_SET_NUMBER(tv_num, -d);  /* FIXME: OK for NaN, Infinity?  NaN normalization? */
		} else {
			args = (DUK_OP_UNM << 8) + 0;
			goto unary;
		}
		return;
	}
	case DUK_TOK_BNOT: {
		expr(comp_ctx, res, BP_MULTIPLICATIVE /*rbp_flags*/);  /* UnaryExpression */
		args = (DUK_OP_BNOT << 8) + 0;
		goto unary;
	}
	case DUK_TOK_LNOT: {
		expr(comp_ctx, res, BP_MULTIPLICATIVE /*rbp_flags*/);  /* UnaryExpression */
		args = (DUK_OP_LNOT << 8) + 0;
		goto unary;
	}

	}

	DUK_ERROR(thr, DUK_ERR_SYNTAX_ERROR, "unexpected token to expr_nud(): %d", tok);
	return;

 unary:
	{
		/* Note: must coerce to a (writable) temp register, so that e.g. "!x" where x
		 * is a reg-mapped variable works correctly (does not mutate the variable register).
		 */

		int tr;
		tr = ivalue_toregconst_raw(comp_ctx, res, -1 /*forced_reg*/, 0 /*allow_const*/, 1 /*require_temp*/);
		emit_a_b(comp_ctx, args >> 8, tr, tr);
		res->t = DUK_IVAL_PLAIN;
		res->x1.t = DUK_ISPEC_REGCONST;
		res->x1.regconst = tr;
		return;
	}

 unary_extraop:
	{
		/* FIXME: refactor into unary2: above? */
		int tr;
		tr = ivalue_toregconst_raw(comp_ctx, res, -1 /*forced_reg*/, 0 /*allow_const*/, 1 /*require_temp*/);
		emit_extraop_b_c(comp_ctx, args >> 8, tr, tr);
		res->t = DUK_IVAL_PLAIN;
		res->x1.t = DUK_ISPEC_REGCONST;
		res->x1.regconst = tr;
		return;
	}

 preincdec:
	{
		/* preincrement and predecrement */
		int reg_res;
		int args_op = args >> 8;

		reg_res = ALLOCTEMP(comp_ctx);

		expr(comp_ctx, res, BP_MULTIPLICATIVE /*rbp_flags*/);  /* UnaryExpression */
		if (res->t == DUK_IVAL_VAR) {
			duk_hstring *h_varname;
			int reg_varbind;
			int reg_varname;

			h_varname = duk_get_hstring(ctx, res->x1.valstack_idx);
			DUK_ASSERT(h_varname != NULL);

			if (hstring_is_eval_or_arguments_in_strict_mode(comp_ctx, h_varname)) {
				goto syntax_error;
			}

			duk_dup(ctx, res->x1.valstack_idx);
			if (lookup_lhs(comp_ctx, &reg_varbind, &reg_varname)) {
				emit_a_b(comp_ctx, args_op, reg_varbind, reg_varbind);
				emit_a_bc(comp_ctx, DUK_OP_LDREG, reg_res, reg_varbind);
			} else {
				emit_a_b(comp_ctx, DUK_OP_GETVAR, reg_res, reg_varname);
				emit_a_b(comp_ctx, args_op, reg_res, reg_res);
				emit_a_b_c(comp_ctx, DUK_OP_PUTVAR, 0, reg_varname, reg_res);
			}

			DUK_DDDPRINT("postincdec to '%!O' -> reg_varbind=%d, reg_varname=%d",
			             h_varname, reg_varbind, reg_varname);
		} else if (res->t == DUK_IVAL_PROP) {
			int reg_obj;  /* allocate to reg only (not const) */
			int reg_key;
			reg_obj = ispec_toregconst_raw(comp_ctx, &res->x1, -1 /*forced_reg*/, 0 /*allow_const*/, 0 /*require_temp*/);
			reg_key = ispec_toregconst_raw(comp_ctx, &res->x2, -1 /*forced_reg*/, 1 /*allow_const*/, 0 /*require_temp*/);
			emit_a_b_c(comp_ctx, DUK_OP_GETPROP, reg_res, reg_obj, reg_key);
			emit_a_b(comp_ctx, args_op, reg_res, reg_res);
			emit_a_b_c(comp_ctx, DUK_OP_PUTPROP, reg_obj, reg_key, reg_res);
		} else {
			/* Technically return value is not needed because INVLHS will
			 * unconditially throw a ReferenceError.  Coercion is necessary
			 * for proper semantics (consider ToNumber() called for an object).
			 */
			ivalue_toforcedreg(comp_ctx, res, reg_res);
			emit_extraop_b_c(comp_ctx, DUK_EXTRAOP_TONUM, reg_res, reg_res);  /* for side effects */
			emit_extraop_only(comp_ctx, DUK_EXTRAOP_INVLHS);
		}
		res->t = DUK_IVAL_PLAIN;
		res->x1.t = DUK_ISPEC_REGCONST;
		res->x1.regconst = reg_res;
		SETTEMP(comp_ctx, reg_res + 1);
		return;
	}

 plain_value:
	{
		/* Stack top contains plain value */
		res->t = DUK_IVAL_PLAIN;
		res->x1.t = DUK_ISPEC_VALUE;
		duk_replace(ctx, res->x1.valstack_idx);
		return;
	}

 syntax_error:
	DUK_ERROR(thr, DUK_ERR_SYNTAX_ERROR, "invalid expression");
}

/* FIXME: add flag to indicate whether caller cares about return value; this
 * affects e.g. handling of assignment expressions.  This change needs API
 * changes elsewhere too.
 */
static void expr_led(duk_compiler_ctx *comp_ctx, duk_ivalue *left, duk_ivalue *res) {
	duk_hthread *thr = comp_ctx->thr;
	duk_context *ctx = (duk_context *) thr;
	duk_token *tk;
	int tok;
	duk_u32 args;	/* temp variable to pass constants and flags to shared code */

	/*
	 *  ctx->prev_token	token to process with expr_led()
	 *  ctx->curr_token	updated by caller
	 */

	comp_ctx->curr_func.led_count++;

	/* The token in the switch has already been eaten here */
	tk = &comp_ctx->prev_token;
	tok = tk->t;

	DUK_DDDPRINT("expr_led(), prev_token.t=%d, allow_in=%d, paren_level=%d",
	             tk->t, comp_ctx->curr_func.allow_in, comp_ctx->curr_func.paren_level);

	/* FIXME: default priority for infix operators is expr_lbp(tok) -> get it here? */

	switch (tok) {

	/* PRIMARY EXPRESSIONS */

	case DUK_TOK_PERIOD: {
		/* FIXME: this now coerces an identifier into a GETVAR to a temp, which
		 * causes an extra LDREG in call setup.  It's sufficient to coerce to a
		 * unary ivalue?
		 */
		ivalue_toplain(comp_ctx, left);

		/* NB: must accept reserved words as property name */
		if (comp_ctx->curr_token.t_nores != DUK_TOK_IDENTIFIER) {
			DUK_ERROR(thr, DUK_ERR_SYNTAX_ERROR, "expecting identifier name");
		}

		/* FIXME: use "dup+replace" primitive */
		res->t = DUK_IVAL_PROP;
		copy_ispec(comp_ctx, &left->x1, &res->x1);  /* left.x1 -> res.x1 */
		DUK_ASSERT(comp_ctx->curr_token.str1 != NULL);
		duk_push_hstring(ctx, comp_ctx->curr_token.str1);
		duk_replace(ctx, res->x2.valstack_idx);
		res->x2.t = DUK_ISPEC_VALUE;

		/* special RegExp literal handling after IdentifierName */
		comp_ctx->curr_func.reject_regexp_in_adv = 1;

		advance(comp_ctx);
		return;
	}
	case DUK_TOK_LBRACKET: {
		/* FIXME: optimize temp reg use */
		/* FIXME: similar coercion issue as in DUK_TOK_PERIOD */

		ivalue_toplain(comp_ctx, left);

		expr_toplain(comp_ctx, res, BP_FOR_EXPR /*rbp_flags*/);  /* Expression, ']' terminates */

		advance_expect(comp_ctx, DUK_TOK_RBRACKET);

		/* FIXME: coerce to regs? it might be better for enumeration use, where the
		 * same PROP ivalue is used multiple times.  Or perhaps coerce PROP further
		 * there?
		 */

		res->t = DUK_IVAL_PROP;
		copy_ispec(comp_ctx, &res->x1, &res->x2);   /* res.x1 -> res.x2 */
		copy_ispec(comp_ctx, &left->x1, &res->x1);  /* left.x1 -> res.x1 */
		return;
	}
	case DUK_TOK_LPAREN: {
		/* function call */
		int reg_cs = ALLOCTEMPS(comp_ctx, 2);
		int nargs;
		int call_flags = 0;

		/*
		 *  FIXME: attempt to get the call result to "next temp" whenever
		 *  possible to avoid unnecessary register shuffles.
		 *
		 *  FIXME: CSPROP (and CSREG) can overwrite the call target register, and save one temp,
		 *  if the call target is a temporary register and at the top of the temp reg "stack".
		 */

		/*
		 *  Setup call: target and 'this' binding.  Three cases:
		 *
		 *    1. Identifier base (e.g. "foo()")
		 *    2. Property base (e.g. "foo.bar()")
		 *    3. Register base (e.g. "foo()()"; i.e. when a return value is a function)
		 */

		if (left->t == DUK_IVAL_VAR) {
			duk_hstring *h_varname;
			int reg_varname;
			int reg_varbind;

			DUK_DDDPRINT("function call with identifier base");

			h_varname = duk_get_hstring(ctx, left->x1.valstack_idx);
			DUK_ASSERT(h_varname != NULL);
			if (h_varname == DUK_HTHREAD_STRING_EVAL(thr)) {
				/* Potential direct eval call detected, flag the CALL
				 * so that a run-time "direct eval" check is made and
				 * special behavior may be triggered.  Note that this
				 * does not prevent 'eval' from being register bound.
				 */
				DUK_DDDPRINT("function call with identifier 'eval' "
				             "-> enabling EVALCALL flag, marking function "
				             "as may_direct_eval");
				call_flags |= DUK_BC_CALL_FLAG_EVALCALL;

				comp_ctx->curr_func.may_direct_eval = 1;
			}

			duk_dup(ctx, left->x1.valstack_idx);
			if (lookup_lhs(comp_ctx, &reg_varbind, &reg_varname)) {
				emit_a_b(comp_ctx, DUK_OP_CSREG, reg_cs + 0, reg_varbind);
			} else {
				emit_a_b(comp_ctx, DUK_OP_CSVAR, reg_cs + 0, reg_varname);
			}
		} else if (left->t == DUK_IVAL_PROP) {
			DUK_DDDPRINT("function call with property base");
			
			ispec_toforcedreg(comp_ctx, &left->x1, reg_cs + 0);  /* base */
			ispec_toforcedreg(comp_ctx, &left->x2, reg_cs + 1);  /* key */
			emit_a_b_c(comp_ctx, DUK_OP_CSPROP, reg_cs + 0, reg_cs + 0, reg_cs + 1);  /* in-place setup */
		} else {
			DUK_DDDPRINT("function call with register base");

			ivalue_toforcedreg(comp_ctx, left, reg_cs + 0);
			emit_a_b(comp_ctx, DUK_OP_CSREG, reg_cs + 0, reg_cs + 0);  /* in-place setup */
		}

		SETTEMP(comp_ctx, reg_cs + 2);
		nargs = parse_arguments(comp_ctx, res);  /* parse args starting from "next temp" */

		/* FIXME: opcode inconsistency with NEW now, which uses explicit result reg */

		/* Tailcalls are handled by back-patching the TAILCALL flag to the
		 * already emitted instruction later (in return statement parser).
		 */

		emit_a_b_c(comp_ctx, DUK_OP_CALL, call_flags /*flags*/, reg_cs /*basereg*/, nargs /*numargs*/);
		SETTEMP(comp_ctx, reg_cs + 1);    /* result in csreg */

		res->t = DUK_IVAL_PLAIN;
		res->x1.t = DUK_ISPEC_REGCONST;
		res->x1.regconst = reg_cs;
		return;
	}

	/* POSTFIX EXPRESSION */

	case DUK_TOK_INCREMENT: {
		args = (DUK_OP_INC << 8) + 0;
		goto postincdec;
	}
	case DUK_TOK_DECREMENT: {
		args = (DUK_OP_DEC << 8) + 0;
		goto postincdec;
	}

	/* MULTIPLICATIVE EXPRESSION */

	case DUK_TOK_MUL: {
		args = (DUK_OP_MUL << 8) + BP_MULTIPLICATIVE;  /* UnaryExpression */
		goto binary;
	}
	case DUK_TOK_DIV: {
		args = (DUK_OP_DIV << 8) + BP_MULTIPLICATIVE;  /* UnaryExpression */
		goto binary;
	}
	case DUK_TOK_MOD: {
		args = (DUK_OP_MOD << 8) + BP_MULTIPLICATIVE;  /* UnaryExpression */
		goto binary;
	}

	/* ADDITIVE EXPRESSION */

	case DUK_TOK_ADD: {
		args = (DUK_OP_ADD << 8) + BP_ADDITIVE;  /* MultiplicativeExpression */
		goto binary;
	}
	case DUK_TOK_SUB: {
		args = (DUK_OP_SUB << 8) + BP_ADDITIVE;  /* MultiplicativeExpression */
		goto binary;
	}

	/* SHIFT EXPRESSION */

	case DUK_TOK_ALSHIFT: {
		/* << */
		args = (DUK_OP_BASL << 8) + BP_SHIFT;
		goto binary;
	}
	case DUK_TOK_ARSHIFT: {
		/* >> */
		args = (DUK_OP_BASR << 8) + BP_SHIFT;
		goto binary;
	}
	case DUK_TOK_RSHIFT: {
		/* >>> */
		args = (DUK_OP_BLSR << 8) + BP_SHIFT;
		goto binary;
	}

	/* RELATIONAL EXPRESSION */

	case DUK_TOK_LT: {
		/* < */
		args = (DUK_OP_LT << 8) + BP_RELATIONAL;
		goto binary;
	}
	case DUK_TOK_GT: {
		args = (DUK_OP_GT << 8) + BP_RELATIONAL;
		goto binary;
	}
	case DUK_TOK_LE: {
		args = (DUK_OP_LE << 8) + BP_RELATIONAL;
		goto binary;
	}
	case DUK_TOK_GE: {
		args = (DUK_OP_GE << 8) + BP_RELATIONAL;
		goto binary;
	}
	case DUK_TOK_INSTANCEOF: {
		args = (DUK_OP_INSTOF << 8) + BP_RELATIONAL;
		goto binary;
	}
	case DUK_TOK_IN: {
		args = (DUK_OP_IN << 8) + BP_RELATIONAL;
		goto binary;
	}

	/* EQUALITY EXPRESSION */

	case DUK_TOK_EQ: {
		args = (DUK_OP_EQ << 8) + BP_EQUALITY;
		goto binary;
	}
	case DUK_TOK_NEQ: {
		args = (DUK_OP_NEQ << 8) + BP_EQUALITY;
		goto binary;
	}
	case DUK_TOK_SEQ: {
		args = (DUK_OP_SEQ << 8) + BP_EQUALITY;
		goto binary;
	}
	case DUK_TOK_SNEQ: {
		args = (DUK_OP_SNEQ << 8) + BP_EQUALITY;
		goto binary;
	}

	/* BITWISE EXPRESSIONS */

	case DUK_TOK_BAND: {
		args = (DUK_OP_BAND << 8) + BP_BAND;
		goto binary;
	}
	case DUK_TOK_BXOR: {
		args = (DUK_OP_BXOR << 8) + BP_BXOR;
		goto binary;
	}
	case DUK_TOK_BOR: {
		args = (DUK_OP_BOR << 8) + BP_BOR;
		goto binary;
	}

	/* LOGICAL EXPRESSIONS */

	case DUK_TOK_LAND: {
		/* syntactically left-associative but parsed as right-associative */
		args = (1 << 8) + BP_LAND - 1;
		goto binary_logical;
	}
	case DUK_TOK_LOR: {
		/* syntactically left-associative but parsed as right-associative */
		args = (0 << 8) + BP_LOR - 1;
		goto binary_logical;
	}

	/* CONDITIONAL EXPRESSION */

	case DUK_TOK_QUESTION: {
		/* FIXME: common reg allocation need is to reuse a sub-expression's temp reg,
		 * but only if it really is a temp.  Nothing fancy here now.
		 */
		int reg_temp;
		int pc_jump1;
		int pc_jump2;

		reg_temp = ALLOCTEMP(comp_ctx);
		ivalue_toforcedreg(comp_ctx, left, reg_temp);
		emit_if_true_skip(comp_ctx, reg_temp);
		pc_jump1 = emit_jump_empty(comp_ctx);  /* jump to false */
		expr_toforcedreg(comp_ctx, res, BP_COMMA /*rbp_flags*/, reg_temp /*forced_reg*/);  /* AssignmentExpression */
		advance_expect(comp_ctx, DUK_TOK_COLON);
		pc_jump2 = emit_jump_empty(comp_ctx);  /* jump to end */
		patch_jump_here(comp_ctx, pc_jump1);
		expr_toforcedreg(comp_ctx, res, BP_COMMA /*rbp_flags*/, reg_temp /*forced_reg*/);  /* AssignmentExpression */
		patch_jump_here(comp_ctx, pc_jump2);

		SETTEMP(comp_ctx, reg_temp + 1);
		res->t = DUK_IVAL_PLAIN;
		res->x1.t = DUK_ISPEC_REGCONST;
		res->x1.regconst = reg_temp;
		return;
	}

	/* ASSIGNMENT EXPRESSION */

	case DUK_TOK_EQUALSIGN: {
		/*
		 *  Assignments are right associative, allows e.g.
		 *    a = 5;
		 *    a += b = 9;   // same as a += (b = 9)
		 *  -> expression value 14, a = 14, b = 9
		 *
		 *  Right associativiness is reflected in the BP for recursion,
		 *  "-1" ensures assignment operations are allowed.
		 *
		 *  FIXME: just use BP_COMMA (i.e. no need for 2-step bp levels)?
		 */
		args = (DUK_OP_INVALID << 8) + BP_ASSIGNMENT - 1;   /* DUK_OP_INVALID marks a 'plain' assignment */
		goto assign;
	}
	case DUK_TOK_ADD_EQ: {
		/* right associative */
		args = (DUK_OP_ADD << 8) + BP_ASSIGNMENT - 1;
		goto assign;
	}
	case DUK_TOK_SUB_EQ: {
		/* right associative */
		args = (DUK_OP_SUB << 8) + BP_ASSIGNMENT - 1;
		goto assign;
	}
	case DUK_TOK_MUL_EQ: {
		/* right associative */
		args = (DUK_OP_MUL << 8) + BP_ASSIGNMENT - 1;
		goto assign;
	}
	case DUK_TOK_DIV_EQ: {
		/* right associative */
		args = (DUK_OP_DIV << 8) + BP_ASSIGNMENT - 1;
		goto assign;
	}
	case DUK_TOK_MOD_EQ: {
		/* right associative */
		args = (DUK_OP_MOD << 8) + BP_ASSIGNMENT - 1;
		goto assign;
	}
	case DUK_TOK_ALSHIFT_EQ: {
		/* right associative */
		args = (DUK_OP_BASL << 8) + BP_ASSIGNMENT - 1;
		goto assign;
	}
	case DUK_TOK_ARSHIFT_EQ: {
		/* right associative */
		args = (DUK_OP_BASR << 8) + BP_ASSIGNMENT - 1;
		goto assign;
	}
	case DUK_TOK_RSHIFT_EQ: {
		/* right associative */
		args = (DUK_OP_BLSR << 8) + BP_ASSIGNMENT - 1;
		goto assign;
	}
	case DUK_TOK_BAND_EQ: {
		/* right associative */
		args = (DUK_OP_BAND << 8) + BP_ASSIGNMENT - 1;
		goto assign;
	}
	case DUK_TOK_BOR_EQ: {
		/* right associative */
		args = (DUK_OP_BOR << 8) + BP_ASSIGNMENT - 1;
		goto assign;
	}
	case DUK_TOK_BXOR_EQ: {
		/* right associative */
		args = (DUK_OP_BXOR << 8) + BP_ASSIGNMENT - 1;
		goto assign;
	}

	/* COMMA */

	case DUK_TOK_COMMA: {
		/* right associative */

		ivalue_toplain_ignore(comp_ctx, left);  /* need side effects, not value */
		expr_toplain(comp_ctx, res, BP_COMMA - 1 /*rbp_flags*/);

		/* return 'res' (of right part) as our result */
		return;
	}

	default: {
		break;
	}
	}

	DUK_ERROR(thr, DUK_ERR_SYNTAX_ERROR, "unexpected token to expr_led(): %d", tok);
	return;

#if 0
	/* FIXME: shared handling for 'expr_lhs'? */
	if (comp_ctx->curr_func.paren_level == 0 && XXX) {
		comp_ctx->curr_func.expr_lhs = 0;
	}
#endif

 binary:
	/*
	 *  Shared handling of binary operations
	 *
	 *  args = (opcode << 8) + rbp
	 */
	{
		ivalue_toplain(comp_ctx, left);
		expr_toplain(comp_ctx, res, args & 0xff /*rbp_flags*/);

		/* combine left->x1 and res->x1 (right->x1, really) -> (left->x1 OP res->x1) */
		DUK_ASSERT(left->t == DUK_IVAL_PLAIN);
		DUK_ASSERT(res->t == DUK_IVAL_PLAIN);

		res->t = DUK_IVAL_ARITH;
		res->op = args >> 8;

		res->x2.t = res->x1.t;
		res->x2.regconst = res->x1.regconst;
		duk_dup(ctx, res->x1.valstack_idx);
		duk_replace(ctx, res->x2.valstack_idx);

		res->x1.t = left->x1.t;
		res->x1.regconst = left->x1.regconst;
		duk_dup(ctx, left->x1.valstack_idx);
		duk_replace(ctx, res->x1.valstack_idx);

		DUK_DDDPRINT("binary op, res: t=%d, x1.t=%d, x2.t=%d", res->t, res->x1.t, res->x2.t);
		return;
	}

 binary_logical:
	/*
	 *  Shared handling for logical AND and logical OR.
	 *
	 *  args = (truthval << 8) + rbp
	 *
	 *  Truthval determines when to skip right-hand-side.
	 *  For logical AND truthval=1, for logical OR truthval=0.
	 *
	 *  See doc/compiler.txt for discussion on compiling logical
	 *  AND and OR expressions.  The approach here is very simplistic,
	 *  generating extra jumps and multiple evaluations of truth values,
	 *  but generates code on-the-fly with only local back-patching.
	 *
	 *  Both logical AND and OR are syntactically left-associated.
	 *  However, logical ANDs are compiled as right associative
	 *  expressions, i.e. "A && B && C" as "A && (B && C)", to allow
	 *  skip jumps to skip over the entire tail.  Similarly for logical OR.
	 */

	{
		int reg_temp;
		int pc_jump;
		int args_truthval = args >> 8;
		int args_rbp = args & 0xff;

		/* FIXME: unoptimal use of temps, resetting */

		reg_temp = ALLOCTEMP(comp_ctx);

		ivalue_toforcedreg(comp_ctx, left, reg_temp);
		emit_a_b(comp_ctx, DUK_OP_IF, args_truthval, reg_temp);  /* skip jump conditionally */
		pc_jump = emit_jump_empty(comp_ctx);
		expr_toforcedreg(comp_ctx, res, args_rbp /*rbp_flags*/, reg_temp /*forced_reg*/);
		patch_jump_here(comp_ctx, pc_jump);

		res->t = DUK_IVAL_PLAIN;
		res->x1.t = DUK_ISPEC_REGCONST;
		res->x1.regconst = reg_temp;
		return;
	}

 assign:
	/*
	 *  Shared assignment expression handling
	 *
	 *  args = (opcode << 8) + rbp
	 *
	 *  If 'opcode' is DUK_OP_INVALID, plain assignment without arithmetic.
	 *  Syntactically valid left-hand-side forms which are not accepted as
	 *  left-hand-side values (e.g. as in "f() = 1") must NOT cause a
	 *  SyntaxError, but rather a run-time ReferenceError.
	 */

	{
		int args_op = args >> 8;
		int args_rbp = args & 0xff;

		/* FIXME: here we need to know if 'left' is left-hand-side compatible.
		 * That information is no longer available from current expr parsing
		 * state; it would need to be carried into the 'left' ivalue or by
		 * some other means.
		 */

		if (left->t == DUK_IVAL_VAR) {
			duk_hstring *h_varname;
			int reg_varbind;
			int reg_varname;
			int reg_res;
			int reg_temp;

			/* already in fluly evaluated form */
			DUK_ASSERT(left->x1.t == DUK_ISPEC_VALUE);

			expr_toreg(comp_ctx, res, args_rbp /*rbp_flags*/);
			DUK_ASSERT(res->t == DUK_IVAL_PLAIN && res->x1.t == DUK_ISPEC_REGCONST);

			h_varname = duk_get_hstring(ctx, left->x1.valstack_idx);
			DUK_ASSERT(h_varname != NULL);

			/* E5 Section 11.13.1 (and others for other assignments), step 4 */
			if (hstring_is_eval_or_arguments_in_strict_mode(comp_ctx, h_varname)) {
				goto syntax_error_lvalue;
			}

			duk_dup(ctx, left->x1.valstack_idx);
			(void) lookup_lhs(comp_ctx, &reg_varbind, &reg_varname);

			DUK_DDDPRINT("assign to '%!O' -> reg_varbind=%d, reg_varname=%d",
			             h_varname, reg_varbind, reg_varname);

			if (args_op == DUK_OP_INVALID) {
				reg_res = res->x1.regconst;
			} else {
				reg_temp = ALLOCTEMP(comp_ctx);
				if (reg_varbind >= 0) {
					emit_a_b_c(comp_ctx, args_op, reg_temp, reg_varbind, res->x1.regconst);
				} else {
					emit_a_b(comp_ctx, DUK_OP_GETVAR, reg_temp, reg_varname);
					emit_a_b_c(comp_ctx, args_op, reg_temp, reg_temp, res->x1.regconst);
				}
				reg_res = reg_temp;
			}

			if (reg_varbind >= 0) {
				emit_a_bc(comp_ctx, DUK_OP_LDREG, reg_varbind, reg_res);
			} else {
				emit_a_b_c(comp_ctx, DUK_OP_PUTVAR, 0, reg_varname, reg_res);
			}

			res->t = DUK_IVAL_PLAIN;
			res->x1.t = DUK_ISPEC_REGCONST;
			res->x1.regconst = reg_res;
		} else if (left->t == DUK_IVAL_PROP) {
			/* E5 Section 11.13.1 (and others) step 4 never matches for prop writes -> no check */
			int reg_obj;
			int reg_key;
			int reg_res;
			int reg_temp;

			expr_toregconst(comp_ctx, res, args_rbp /*rbp_flags*/);
			DUK_ASSERT(res->t == DUK_IVAL_PLAIN && res->x1.t == DUK_ISPEC_REGCONST);

			/* Don't allow a constant for the object (even for a number etc), as
			 * it goes into the 'A' field of the opcode.
			 */

			reg_obj = ispec_toregconst_raw(comp_ctx, &left->x1, -1 /*forced_reg*/, 0 /*allow_const*/, 0 /*require_temp*/);
			reg_key = ispec_toregconst_raw(comp_ctx, &left->x2, -1 /*forced_reg*/, 1 /*allow_const*/, 0 /*require_temp*/);
	
			if (args_op == DUK_OP_INVALID) {
				reg_res = res->x1.regconst;
			} else {
				reg_temp = ALLOCTEMP(comp_ctx);
				emit_a_b_c(comp_ctx, DUK_OP_GETPROP, reg_temp, reg_obj, reg_key);
				emit_a_b_c(comp_ctx, args_op, reg_temp, reg_temp, res->x1.regconst);
				reg_res = reg_temp;
			}

			emit_a_b_c(comp_ctx, DUK_OP_PUTPROP, reg_obj, reg_key, reg_res);

			res->t = DUK_IVAL_PLAIN;
			res->x1.t = DUK_ISPEC_REGCONST;
			res->x1.regconst = reg_res;
		} else {
			/* No support for lvalues returned from new or function call expressions.
			 * However, these must NOT cause compile-time SyntaxErrors, but run-time
			 * ReferenceErrors.  Both left and right sides of the assignment must be
			 * evaluated before throwing a ReferenceError.  For instance:
			 *
			 *     f() = g();
			 *
			 * must result in f() being evaluated, then g() being evaluated, and
			 * finally, a ReferenceError being thrown.  See E5 Section 11.13.1.
			 */

			int reg_res;

			/* first evaluate LHS fully to ensure all side effects are out */
			ivalue_toplain_ignore(comp_ctx, left);

			/* then evaluate RHS fully (its value becomes the expression value too) */
			reg_res = expr_toregconst(comp_ctx, res, args_rbp /*rbp_flags*/);
	
			emit_extraop_only(comp_ctx, DUK_EXTRAOP_INVLHS);

			/* FIXME: this value is irrelevant because of INVLHS? */

			res->t = DUK_IVAL_PLAIN;
			res->x1.t = DUK_ISPEC_REGCONST;
			res->x1.regconst = reg_res;
		}

		return;
	}

 postincdec:
	{
		/*
		 *  Post-increment/decrement will return the original value as its
		 *  result value.  However, even that value will be coerced using
		 *  ToNumber().
		 *
		 *  FIXME: the current solution for this is very ugly.
		 *
		 *  Note that post increment/decrement has a "no LineTerminator here"
		 *  restriction.  This is handled by expr_lbp(), which forcibly terminates
		 *  the previous expression if a LineTerminator occurs before '++'/'--'.
		 */

		int reg_res;
		int args_op = args >> 8;

		reg_res = ALLOCTEMP(comp_ctx);

		if (left->t == DUK_IVAL_VAR) {
			duk_hstring *h_varname;
			int reg_varbind;
			int reg_varname;

			h_varname = duk_get_hstring(ctx, left->x1.valstack_idx);
			DUK_ASSERT(h_varname != NULL);

			if (hstring_is_eval_or_arguments_in_strict_mode(comp_ctx, h_varname)) {
				goto syntax_error;
			}

			duk_dup(ctx, left->x1.valstack_idx);
			if (lookup_lhs(comp_ctx, &reg_varbind, &reg_varname)) {
				emit_a_bc(comp_ctx, DUK_OP_LDREG, reg_res, reg_varbind);
				emit_extraop_b_c(comp_ctx, DUK_EXTRAOP_TONUM, reg_res, reg_res);
				emit_a_b(comp_ctx, args_op, reg_varbind, reg_res);
			} else {
				int reg_temp = ALLOCTEMP(comp_ctx);
				emit_a_b(comp_ctx, DUK_OP_GETVAR, reg_res, reg_varname);
				emit_extraop_b_c(comp_ctx, DUK_EXTRAOP_TONUM, reg_res, reg_res);
				emit_a_b(comp_ctx, args_op, reg_temp, reg_res);
				emit_a_b_c(comp_ctx, DUK_OP_PUTVAR, 0, reg_varname, reg_temp);
			}

			DUK_DDDPRINT("postincdec to '%!O' -> reg_varbind=%d, reg_varname=%d",
			             h_varname, reg_varbind, reg_varname);
		} else if (left->t == DUK_IVAL_PROP) {
			int reg_obj;  /* allocate to reg only (not const) */
			int reg_key;
			int reg_temp = ALLOCTEMP(comp_ctx);
			reg_obj = ispec_toregconst_raw(comp_ctx, &left->x1, -1 /*forced_reg*/, 0 /*allow_const*/, 0 /*require_temp*/);
			reg_key = ispec_toregconst_raw(comp_ctx, &left->x2, -1 /*forced_reg*/, 1 /*allow_const*/, 0 /*require_temp*/);
			emit_a_b_c(comp_ctx, DUK_OP_GETPROP, reg_res, reg_obj, reg_key);
			emit_extraop_b_c(comp_ctx, DUK_EXTRAOP_TONUM, reg_res, reg_res);
			emit_a_b(comp_ctx, args_op, reg_temp, reg_res);
			emit_a_b_c(comp_ctx, DUK_OP_PUTPROP, reg_obj, reg_key, reg_temp);
		} else {
			/* Technically return value is not needed because INVLHS will
			 * unconditially throw a ReferenceError.  Coercion is necessary
			 * for proper semantics (consider ToNumber() called for an object).
			 */
			ivalue_toforcedreg(comp_ctx, left, reg_res);
			emit_extraop_b_c(comp_ctx, DUK_EXTRAOP_TONUM, reg_res, reg_res);  /* for side effects */
			emit_extraop_only(comp_ctx, DUK_EXTRAOP_INVLHS);
		}

		res->t = DUK_IVAL_PLAIN;
		res->x1.t = DUK_ISPEC_REGCONST;
		res->x1.regconst = reg_res;
		SETTEMP(comp_ctx, reg_res + 1);
		return;
	}

 syntax_error:
	DUK_ERROR(thr, DUK_ERR_SYNTAX_ERROR, "invalid expression");
	return;

 syntax_error_lvalue:
	DUK_ERROR(thr, DUK_ERR_SYNTAX_ERROR, "invalid lvalue");
	return;
}

static int expr_lbp(duk_compiler_ctx *comp_ctx) {
	int tok = comp_ctx->curr_token.t;

	DUK_ASSERT(tok >= DUK_TOK_MINVAL && tok <= DUK_TOK_MAXVAL);
	DUK_ASSERT(sizeof(token_lbp) == DUK_TOK_MAXVAL + 1);

	/* FIXME: integrate support for this into led() instead?
	 * Similar issue as post-increment/post-decrement.
	 */

	/* prevent expr_led() by using a binding power less than anything valid */
	if (tok == DUK_TOK_IN && !comp_ctx->curr_func.allow_in) {
		return 0;
	}

	if ((tok == DUK_TOK_DECREMENT || tok == DUK_TOK_INCREMENT) &&
	    (comp_ctx->curr_token.lineterm)) {
		/* '++' or '--' in a post-increment/decrement position,
		 * and a LineTerminator occurs between the operator and
		 * the preceding expression.  Force the previous expr
		 * to terminate, in effect treating e.g. "a,b\n++" as
		 * "a,b;++" (= SyntaxError).
		 */
		return 0;
	}

	return TOKEN_LBP_GET_BP(token_lbp[tok]);  /* format is bit packed */
}

/*
 *  Expression parsing.
 *
 *  Upon entry to 'expr' and its variants, 'curr_tok' is assumed to be the
 *  first token of the expression.  Upon exit, 'curr_tok' will be the first
 *  token not part of the expression (e.g. semicolon terminating an expression
 *  statement).
 */

#define  EXPR_RBP_MASK           0xff
#define  EXPR_FLAG_REJECT_IN     (1 << 8)
#define  EXPR_FLAG_ALLOW_EMPTY   (1 << 9)

/* main expression parser function */
static void expr(duk_compiler_ctx *comp_ctx, duk_ivalue *res, int rbp_flags) {
	duk_hthread *thr = comp_ctx->thr;
	duk_context *ctx = (duk_context *) thr;
	duk_ivalue tmp_alloc;   /* 'res' is used for "left", and 'tmp' for "right" */
	duk_ivalue *tmp = &tmp_alloc;
	int rbp;

	RECURSION_INCREASE(comp_ctx, thr);

	/* filter out flags from exprtop rbp_flags here to save space */
	rbp = rbp_flags & EXPR_RBP_MASK;

	DUK_DDDPRINT("expr(), rbp_flags=%d, rbp=%d, allow_in=%d, paren_level=%d",
	             rbp_flags, rbp, comp_ctx->curr_func.allow_in, comp_ctx->curr_func.paren_level);

	memset(&tmp_alloc, 0, sizeof(tmp_alloc));
	tmp->x1.valstack_idx = duk_get_top(ctx);
	tmp->x2.valstack_idx = tmp->x1.valstack_idx + 1;
	duk_push_undefined(ctx);
	duk_push_undefined(ctx);

	/* FIXME: where to release temp regs in intermediate expressions?
	 * e.g. 1+2+3 -> don't inflate temp register count when parsing this.
	 * that particular expression temp regs can be forced here.
	 */

	/* FIXME: increase ctx->expr_tokens here for every consumed token
	 * (this would be a nice statistic)?
	 */

	if (comp_ctx->curr_token.t == DUK_TOK_SEMICOLON || comp_ctx->curr_token.t == DUK_TOK_RPAREN) {
		/* FIXME: incorrect hack for testing */
		DUK_DDDPRINT("empty expression");
		if (!(rbp_flags & EXPR_FLAG_ALLOW_EMPTY)) {
			DUK_ERROR(thr, DUK_ERR_SYNTAX_ERROR, "empty expression not allowed");
		}
		res->t = DUK_IVAL_PLAIN;
		res->x1.t = DUK_ISPEC_VALUE;
		duk_push_undefined(ctx);
		duk_replace(ctx, res->x1.valstack_idx);
		goto cleanup;
	}

	advance(comp_ctx);
	expr_nud(comp_ctx, res);  /* reuse 'res' as 'left' */
	while (rbp < expr_lbp(comp_ctx)) {
		advance(comp_ctx);
		expr_led(comp_ctx, res, tmp);
		copy_ivalue(comp_ctx, tmp, res);  /* tmp -> res */
	}

 cleanup:
	/* final result is already in 'res' */

	duk_pop_2(ctx);

	RECURSION_DECREASE(comp_ctx, thr);
}

static void exprtop(duk_compiler_ctx *comp_ctx, duk_ivalue *res, int rbp_flags) {
	duk_hthread *thr = comp_ctx->thr;

	/* Note: these variables must reside in 'curr_func' instead of the global
	 * context: when parsing function expressions, expression parsing is nested.
	 */
	comp_ctx->curr_func.nud_count = 0;
	comp_ctx->curr_func.led_count = 0;
	comp_ctx->curr_func.paren_level = 0;
	comp_ctx->curr_func.expr_lhs = 1;
	comp_ctx->curr_func.allow_in = (rbp_flags & EXPR_FLAG_REJECT_IN ? 0 : 1);

	expr(comp_ctx, res, rbp_flags);

	if (!(rbp_flags & EXPR_FLAG_ALLOW_EMPTY) && expr_is_empty(comp_ctx)) {
		DUK_ERROR(thr, DUK_ERR_SYNTAX_ERROR, "empty expression not allowed");
	}
}

/* A bunch of helpers (for size optimization) that combine expr()/exprtop()
 * and result conversions.
 *
 * Each helper needs at least 2-3 calls to make it worth while to wrap.
 */

static int expr_toreg(duk_compiler_ctx *comp_ctx, duk_ivalue *res, int rbp_flags) {
	expr(comp_ctx, res, rbp_flags);
	return ivalue_toreg(comp_ctx, res);
}

#if 0  /* unused */
static int expr_totempreg(duk_compiler_ctx *comp_ctx, duk_ivalue *res, int rbp_flags) {
	expr(comp_ctx, res, rbp_flags);
	return ivalue_totempreg(comp_ctx, res);
}
#endif

static int expr_toforcedreg(duk_compiler_ctx *comp_ctx, duk_ivalue *res, int rbp_flags, int forced_reg) {
	expr(comp_ctx, res, rbp_flags);
	return ivalue_toforcedreg(comp_ctx, res, forced_reg);
}

static int expr_toregconst(duk_compiler_ctx *comp_ctx, duk_ivalue *res, int rbp_flags) {
	expr(comp_ctx, res, rbp_flags);
	return ivalue_toregconst(comp_ctx, res);
}

static void expr_toplain(duk_compiler_ctx *comp_ctx, duk_ivalue *res, int rbp_flags) {
	expr(comp_ctx, res, rbp_flags);
	ivalue_toplain(comp_ctx, res);
}

static void expr_toplain_ignore(duk_compiler_ctx *comp_ctx, duk_ivalue *res, int rbp_flags) {
	expr(comp_ctx, res, rbp_flags);
	ivalue_toplain_ignore(comp_ctx, res);
}

static int exprtop_toreg(duk_compiler_ctx *comp_ctx, duk_ivalue *res, int rbp_flags) {
	exprtop(comp_ctx, res, rbp_flags);
	return ivalue_toreg(comp_ctx, res);
}

#if 0  /* unused */
static int exprtop_totempreg(duk_compiler_ctx *comp_ctx, duk_ivalue *res, int rbp_flags) {
	exprtop(comp_ctx, res, rbp_flags);
	return ivalue_totempreg(comp_ctx, res);
}
#endif

#if 0  /* unused */
static int exprtop_toforcedreg(duk_compiler_ctx *comp_ctx, duk_ivalue *res, int rbp_flags, int forced_reg) {
	exprtop(comp_ctx, res, rbp_flags);
	return ivalue_toforcedreg(comp_ctx, res, forced_reg);
}
#endif

static int exprtop_toregconst(duk_compiler_ctx *comp_ctx, duk_ivalue *res, int rbp_flags) {
	exprtop(comp_ctx, res, rbp_flags);
	return ivalue_toregconst(comp_ctx, res);
}

#if 0  /* unused */
static void exprtop_toplain_ignore(duk_compiler_ctx *comp_ctx, duk_ivalue *res, int rbp_flags) {
	exprtop(comp_ctx, res, rbp_flags);
	ivalue_toplain_ignore(comp_ctx, res);
}
#endif

/*
 *  Parse an individual source element (top level statement) or a statement.
 *
 *  Handles labeled statements automatically (peeling away labels before
 *  parsing an expression that follows the label(s)).
 *
 *  Upon entry, 'curr_tok' contains the first token of the statement (parsed
 *  in "allow regexp literal" mode).  Upon exit, 'curr_tok' contains the first
 *  token following the statement (if the statement has a terminator, this is
 *  the token after the terminator).
 */

#ifdef HAS_VAL
#undef HAS_VAL
#endif
#ifdef HAS_TERM
#undef HAS_TERM
#endif
#ifdef ALLOW_AUTO_SEMI_ALWAYS
#undef ALLOW_AUTO_SEMI_ALWAYS
#endif
#ifdef STILL_PROLOGUE
#undef STILL_PROLOGUE
#endif
#ifdef IS_TERMINAL
#undef IS_TERMINAL
#endif

#define  HAS_VAL                  (1 << 0)  /* stmt has non-empty value */
#define  HAS_TERM                 (1 << 1)  /* stmt has explicit/implicit semicolon terminator */
#define  ALLOW_AUTO_SEMI_ALWAYS   (1 << 2)  /* allow automatic semicolon even without lineterm (compatibility) */
#define  STILL_PROLOGUE           (1 << 3)  /* statement does not terminate directive prologue */
#define  IS_TERMINAL              (1 << 4)  /* statement is guaranteed to be terminal (control doesn't flow to next statement) */

/* Parse a single variable declaration (e.g. "i" or "i=10").  A leading 'var'
 * has already been eaten.  These is no return value in 'res', it is used only
 * as a temporary.
 *
 * When called from 'for-in' statement parser, the initializer expression must
 * not allow the 'in' token.  The caller supply additional expression parsing
 * flags (like EXPR_FLAG_REJECT_IN) in 'expr_flags'.
 *
 * Finally, out_reg_varname and out_reg_varbind are updated to reflect where
 * the identifier is bound:
 *
 *    If register bound:      out_reg_varbind >= 0, out_reg_varname < 0
 *    If not register bound:  out_reg_varbind < 0, out_reg_varname >= 0
 *
 * These allow the caller to use the variable for further assignment, e.g.
 * as is done in 'for-in' parsing.
 */

static void parse_variable_declaration(duk_compiler_ctx *comp_ctx, duk_ivalue *res, int expr_flags, int *out_reg_varname, int *out_reg_varbind) {
	duk_hthread *thr = comp_ctx->thr;
	duk_context *ctx = (duk_context *) thr;
	duk_hstring *h_varname;
	int reg_varname;
	int reg_varbind;

	/* assume 'var' has been eaten */

	/* Note: Identifier rejects reserved words */
	if (comp_ctx->curr_token.t != DUK_TOK_IDENTIFIER) {
		goto syntax_error;
	}
	h_varname = comp_ctx->curr_token.str1;

	DUK_ASSERT(h_varname != NULL);

	/* strict mode restrictions (E5 Section 12.2.1) */
	if (hstring_is_eval_or_arguments_in_strict_mode(comp_ctx, h_varname)) {
		goto syntax_error;
	}

	/* register declarations in first pass */
	if (comp_ctx->curr_func.in_scanning) {
		int n;
		DUK_DDDPRINT("register variable declaration %!O in pass 1", h_varname);
		n = duk_get_length(ctx, comp_ctx->curr_func.decls_idx);  /*FIXME: primitive for pushing*/
		duk_push_hstring(ctx, h_varname);
		duk_put_prop_index(ctx, comp_ctx->curr_func.decls_idx, n);
		duk_push_int(ctx, DUK_DECL_TYPE_VAR + (0 << 8));
		duk_put_prop_index(ctx, comp_ctx->curr_func.decls_idx, n + 1);
	}

	duk_push_hstring(ctx, h_varname);  /* push before advancing to keep reachable */

	/* register binding lookup is based on varmap (even in first pass) */
	duk_dup_top(ctx);
	(void) lookup_lhs(comp_ctx, &reg_varbind, &reg_varname);

	advance(comp_ctx);  /* eat identifier */

	if (comp_ctx->curr_token.t == DUK_TOK_EQUALSIGN) {
		advance(comp_ctx);

		DUK_DDDPRINT("vardecl, assign to '%!O' -> reg_varbind=%d, reg_varname=%d",
		             h_varname, reg_varbind, reg_varname);

		exprtop(comp_ctx, res, BP_COMMA | expr_flags /*rbp_flags*/);  /* AssignmentExpression */

		if (reg_varbind >= 0) {
			ivalue_toforcedreg(comp_ctx, res, reg_varbind);
		} else {
			int reg_val;
			reg_val = ivalue_toreg(comp_ctx, res);
			emit_a_b_c(comp_ctx, DUK_OP_PUTVAR, 0, reg_varname, reg_val);
		}
	}

	duk_pop(ctx);  /* pop varname */

	*out_reg_varname = reg_varname;
	*out_reg_varbind = reg_varbind;

	return;

 syntax_error:
	DUK_ERROR(thr, DUK_ERR_SYNTAX_ERROR, "invalid variable declaration");
}

static void parse_var_statement(duk_compiler_ctx *comp_ctx, duk_ivalue *res) {
	int reg_varname;
	int reg_varbind;

	advance(comp_ctx);  /* eat 'var' */

	for (;;) {
		/* reg_varname and reg_varbind are ignored here */
		parse_variable_declaration(comp_ctx, res, 0, &reg_varname, &reg_varbind);

		if (comp_ctx->curr_token.t != DUK_TOK_COMMA) {
			break;
		}
		advance(comp_ctx);
	} 
}

static void parse_for_statement(duk_compiler_ctx *comp_ctx, duk_ivalue *res, int pc_label_site) {
	duk_hthread *thr = comp_ctx->thr;
	duk_context *ctx = (duk_context *) thr;
	int pc_v34_lhs;    /* start variant 3/4 left-hand-side code (L1 in doc/compiler.txt example) */
	int temp_reset;    /* knock back "next temp" to this whenever possible */
	int reg_temps;     /* preallocated temporaries (2) for variants 3 and 4 */

	DUK_DDDPRINT("start parsing a for/for-in statement");

	/* Two temporaries are preallocated here for variants 3 and 4 which need
	 * registers which are never clobbered by expressions in the loop
	 * (concretely: for the enumerator object and the next enumerated value).
	 * Variants 1 and 2 "release" these temps.
	 */

	reg_temps = ALLOCTEMPS(comp_ctx, 2);

	temp_reset = GETTEMP(comp_ctx);

	/*
	 *  For/for-in main variants are:
	 *
	 *    1. for (ExpressionNoIn_opt; Expression_opt; Expression_opt) Statement
	 *    2. for (var VariableDeclarationNoIn; Expression_opt; Expression_opt) Statement
	 *    3. for (LeftHandSideExpression in Expression) Statement
	 *    4. for (var VariableDeclarationNoIn in Expression) Statement
	 *
	 *  Parsing these without arbitrary lookahead or backtracking is relatively
	 *  tricky but we manage to do so for now.
	 *
	 *  See doc/compiler.txt for a detailed discussion of control flow
	 *  issues, evaluation order issues, etc.
	 */
	
	advance(comp_ctx);  /* eat 'for' */
	advance_expect(comp_ctx, DUK_TOK_LPAREN);

	DUK_DDDPRINT("detecting for/for-in loop variant, pc=%d", get_current_pc(comp_ctx));

	/* a label site has been emitted by parse_statement() automatically
	 * (it will also emit the ENDLABEL).
	 */

	if (comp_ctx->curr_token.t == DUK_TOK_VAR) {
		/*
		 *  Variant 2 or 4
		 */

		int reg_varname;  /* variable name reg/const, if variable not register-bound */
		int reg_varbind;  /* variable binding register if register-bound (otherwise < 0) */

		advance(comp_ctx);  /* eat 'var' */
		parse_variable_declaration(comp_ctx, res, EXPR_FLAG_REJECT_IN, &reg_varname, &reg_varbind);
		SETTEMP(comp_ctx, temp_reset);

		if (comp_ctx->curr_token.t == DUK_TOK_IN) {
			/*
			 *  Variant 4
			 */

			DUK_DDDPRINT("detected for variant 4: for (var VariableDeclarationNoIn in Expression) Statement");
			pc_v34_lhs = get_current_pc(comp_ctx);  /* jump is inserted here */
			if (reg_varbind >= 0) {
				emit_a_bc(comp_ctx, DUK_OP_LDREG, reg_varbind, reg_temps + 0);
			} else {
				emit_a_b_c(comp_ctx, DUK_OP_PUTVAR, 0, reg_varname, reg_temps + 0);
			}
			goto parse_3_or_4;
		} else {
			/*
			 *  Variant 2
			 */

			DUK_DDDPRINT("detected for variant 2: for (var VariableDeclarationNoIn; Expression_opt; Expression_opt) Statement");
			for (;;) {
				/* more initializers */
				if (comp_ctx->curr_token.t != DUK_TOK_COMMA) {
					break;
				}
				DUK_DDDPRINT("variant 2 has another variable initializer");

				advance(comp_ctx);  /* eat comma */
				parse_variable_declaration(comp_ctx, res, EXPR_FLAG_REJECT_IN, &reg_varname, &reg_varbind);
			}
			goto parse_1_or_2;
		}
	} else {
		/*
		 *  Variant 1 or 3
		 */

		pc_v34_lhs = get_current_pc(comp_ctx);  /* jump is inserted here (variant 3) */

		/* FIXME: note that exprtop() here can clobber any reg above current temp_next,
		 * so any loop variables (e.g. enumerator) must be *preallocated* ... */

		/* don't coerce yet to a plain value (variant 3 needs special handling) */
		exprtop(comp_ctx, res, BP_FOR_EXPR | EXPR_FLAG_REJECT_IN | EXPR_FLAG_ALLOW_EMPTY /*rbp_flags*/);  /* Expression */
		if (comp_ctx->curr_token.t == DUK_TOK_IN) {
			/*
			 *  Variant 3
			 */

			/* FIXME: need to determine LHS type, and check that it is LHS compatible */
			DUK_DDDPRINT("detected for variant 3: for (LeftHandSideExpression in Expression) Statement");
			if (expr_is_empty(comp_ctx)) {
				goto syntax_error;  /* LeftHandSideExpression does not allow empty expression */
			}

			if (res->t == DUK_IVAL_VAR) {
				int reg_varname;
				int reg_varbind;

				duk_dup(ctx, res->x1.valstack_idx);
				if (lookup_lhs(comp_ctx, &reg_varbind, &reg_varname)) {
					emit_a_bc(comp_ctx, DUK_OP_LDREG, reg_varbind, reg_temps + 0);
				} else {
					emit_a_b_c(comp_ctx, DUK_OP_PUTVAR, 0, reg_varname, reg_temps + 0);
				}
			} else if (res->t == DUK_IVAL_PROP) {
				/* Don't allow a constant for the object (even for a number etc), as
				 * it goes into the 'A' field of the opcode.
				 */
				int reg_obj;
				int reg_key;
				reg_obj = ispec_toregconst_raw(comp_ctx, &res->x1, -1 /*forced_reg*/, 0 /*allow_const*/, 0 /*require_temp*/);
				reg_key = ispec_toregconst_raw(comp_ctx, &res->x2, -1 /*forced_reg*/, 1 /*allow_const*/, 0 /*require_temp*/);
				emit_a_b_c(comp_ctx, DUK_OP_PUTPROP, reg_obj, reg_key, reg_temps + 0);
			} else {
				ivalue_toplain_ignore(comp_ctx, res);  /* just in case */
				emit_extraop_only(comp_ctx, DUK_EXTRAOP_INVLHS);
			}
			goto parse_3_or_4;
		} else {
			/*
			 *  Variant 1
			 */

			DUK_DDDPRINT("detected for variant 1: for (ExpressionNoIn_opt; Expression_opt; Expression_opt) Statement");
			ivalue_toplain_ignore(comp_ctx, res);
			goto parse_1_or_2;
		}
	}

 parse_1_or_2:
	/*
	 *  Parse variant 1 or 2.  The first part expression (which differs
	 *  in the variants) has already been parsed and its code emitted.
	 *
	 *  reg_temps + 0: unused
	 *  reg_temps + 1: unused
	 */
	{
		int reg_cond;
		int pc_l1, pc_l2, pc_l3, pc_l4;
		int pc_jumpto_l3, pc_jumpto_l4;
		int expr_c_empty;

		DUK_DDDPRINT("shared code for parsing variants 1 and 2");

		/* "release" preallocated temps since we won't need them */
		temp_reset = reg_temps + 0;
		SETTEMP(comp_ctx, temp_reset);

		advance_expect(comp_ctx, DUK_TOK_SEMICOLON);

		pc_l1 = get_current_pc(comp_ctx);
		exprtop(comp_ctx, res, BP_FOR_EXPR | EXPR_FLAG_ALLOW_EMPTY /*rbp_flags*/);  /* Expression_opt */
		if (expr_is_empty(comp_ctx)) {
			/* no need to coerce */
			pc_jumpto_l3 = emit_jump_empty(comp_ctx);  /* to body */
			pc_jumpto_l4 = -1;  /* omitted */
		} else {
			reg_cond = ivalue_toregconst(comp_ctx, res);
			emit_if_false_skip(comp_ctx, reg_cond);
			pc_jumpto_l3 = emit_jump_empty(comp_ctx);  /* to body */
			pc_jumpto_l4 = emit_jump_empty(comp_ctx);  /* to exit */
		}
		SETTEMP(comp_ctx, temp_reset);

		advance_expect(comp_ctx, DUK_TOK_SEMICOLON);

		pc_l2 = get_current_pc(comp_ctx);
		exprtop(comp_ctx, res, BP_FOR_EXPR | EXPR_FLAG_ALLOW_EMPTY /*rbp_flags*/);  /* Expression_opt */
		if (expr_is_empty(comp_ctx)) {
			/* no need to coerce */
			expr_c_empty = 1;
			/* JUMP L1 omitted */
		} else {
			ivalue_toplain_ignore(comp_ctx, res);
			expr_c_empty = 0;
			emit_jump(comp_ctx, pc_l1);
		}
		SETTEMP(comp_ctx, temp_reset);

		advance_expect(comp_ctx, DUK_TOK_RPAREN);

		pc_l3 = get_current_pc(comp_ctx);
		parse_statement(comp_ctx, res, 0 /*allow_source_elem*/);
		if (expr_c_empty) {
			emit_jump(comp_ctx, pc_l1);
		} else {
			emit_jump(comp_ctx, pc_l2);
		}
		/* temp reset is not necessary after parse_statement(), which already does it */

		pc_l4 = get_current_pc(comp_ctx);

		DUK_DDDPRINT("patching jumps: jumpto_l3: %d->%d, jumpto_l4: %d->%d, "
		             "break: %d->%d, continue: %d->%d",
			     pc_jumpto_l3, pc_l3, pc_jumpto_l4, pc_l4,
		             pc_label_site + 1, pc_l4, pc_label_site + 2, pc_l2);

		patch_jump(comp_ctx, pc_jumpto_l3, pc_l3);
		patch_jump(comp_ctx, pc_jumpto_l4, pc_l4);
		patch_jump(comp_ctx, pc_label_site + 1, pc_l4);  /* break jump */
		patch_jump(comp_ctx, pc_label_site + 2, pc_l2);  /* continue jump */
	}
	goto finished;

 parse_3_or_4:
	/*
	 *  Parse variant 3 or 4.
	 *
	 *  For variant 3 (e.g. "for (A in C) D;") the code for A (except the
	 *  final property/variable write) has already been emitted.  The first
	 *  instruction of that code is at pc_v34_lhs; a JUMP needs to be inserted
	 *  there to satisfy control flow needs.
	 *
	 *  For variant 4, if the variable declaration had an initializer
	 *  (e.g. "for (var A = B in C) D;") the code for the assignment
	 *  (B) has already been emitted.
	 *
	 *  Variables set before entering here:
	 *
	 *    pc_v34_lhs:    insert a "JUMP L2" here (see doc/compiler.txt example).
	 *    reg_temps + 0: iteration target value (written to LHS)
	 *    reg_temps + 1: enumerator object
	 */
	{
		int pc_l1, pc_l2, pc_l3, pc_l4, pc_l5;
		int pc_jumpto_l2, pc_jumpto_l3, pc_jumpto_l4, pc_jumpto_l5;
		int reg_target;

		DUK_DDDPRINT("shared code for parsing variants 3 and 4, pc_v34_lhs=%d", pc_v34_lhs);

		SETTEMP(comp_ctx, temp_reset);

		/* First we need to insert a jump in the middle of previously
		 * emitted code to get the control flow right.  No jumps can
		 * cross the position where the jump is inserted.  See doc/compiler.txt
		 * for discussion on the intricacies of control flow and side effects
		 * for variants 3 and 4.
		 */

		insert_jump_empty(comp_ctx, pc_v34_lhs);
		pc_jumpto_l2 = pc_v34_lhs;  /* inserted jump */
		pc_l1 = pc_v34_lhs + 1;     /* +1, right after inserted jump */

		/* The code for writing reg_temps + 0 to the left hand side has already
		 * been emitted.
		 */

		pc_jumpto_l3 = emit_jump_empty(comp_ctx);  /* -> loop body */

		advance(comp_ctx);  /* eat 'in' */

		/* Parse enumeration target and initialize enumerator.  For 'null' and 'undefined',
		 * INITENUM will creates a 'null' enumerator which works like an empty enumerator
		 * (E5 Section 12.6.4, step 3).  Note that INITENUM requires the value to be in a
		 * register (constant not allowed).
	 	 */

		pc_l2 = get_current_pc(comp_ctx);
		reg_target = exprtop_toreg(comp_ctx, res, BP_FOR_EXPR /*rbp_flags*/);  /* Expression */
		emit_extraop_b_c(comp_ctx, DUK_EXTRAOP_INITENUM, reg_temps + 1, reg_target);
		pc_jumpto_l4 = emit_jump_empty(comp_ctx);
		SETTEMP(comp_ctx, temp_reset);

		advance_expect(comp_ctx, DUK_TOK_RPAREN);

		pc_l3 = get_current_pc(comp_ctx);
		parse_statement(comp_ctx, res, 0 /*allow_source_elem*/);
		/* temp reset is not necessary after parse_statement(), which already does it */

		pc_l4 = get_current_pc(comp_ctx);
		emit_extraop_b_c(comp_ctx, DUK_EXTRAOP_NEXTENUM, reg_temps + 0, reg_temps + 1);
		pc_jumpto_l5 = emit_jump_empty(comp_ctx);  /* NEXTENUM jump slot: executed when enum finished */
		emit_jump(comp_ctx, pc_l1);  /* jump to next loop, using reg_v34_iter as iterated value */

		pc_l5 = get_current_pc(comp_ctx);

		/* XXX: since the enumerator may be a memory expensive object,
		 * perhaps clear it explicitly here?  If so, break jump must
		 * go through this clearing operation.
		 */

		DUK_DDDPRINT("patching jumps: jumpto_l2: %d->%d, jumpto_l3: %d->%d, "
			     "jumpto_l4: %d->%d, jumpto_l5: %d->%d, "
		             "break: %d->%d, continue: %d->%d",
			     pc_jumpto_l2, pc_l2, pc_jumpto_l3, pc_l3,
			     pc_jumpto_l4, pc_l4, pc_jumpto_l5, pc_l5,
		             pc_label_site + 1, pc_l5, pc_label_site + 2, pc_l4);

		patch_jump(comp_ctx, pc_jumpto_l2, pc_l2);
		patch_jump(comp_ctx, pc_jumpto_l3, pc_l3);
		patch_jump(comp_ctx, pc_jumpto_l4, pc_l4);
		patch_jump(comp_ctx, pc_jumpto_l5, pc_l5);
		patch_jump(comp_ctx, pc_label_site + 1, pc_l5);  /* break jump */
		patch_jump(comp_ctx, pc_label_site + 2, pc_l4);  /* continue jump */
	}
	goto finished;

 finished:
	DUK_DDDPRINT("end parsing a for/for-in statement");
	return;

 syntax_error:		
	DUK_ERROR(thr, DUK_ERR_SYNTAX_ERROR, "invalid for statement");
}

static void parse_switch_statement(duk_compiler_ctx *comp_ctx, duk_ivalue *res, int pc_label_site) {
	duk_hthread *thr = comp_ctx->thr;
	int temp_at_loop;
	int reg_switch;        /* reg/const for switch value */
	int reg_case;          /* reg/const for case value */
	int reg_temp;          /* general temp register */
	int pc_prevcase = -1;
	int pc_prevstmt = -1;
	int pc_default = -1;   /* -1 == not set, -2 == pending (next statement list) */

	/* Note: negative pc values are ignored when patching jumps, so no explicit checks needed */

	/*
	 *  Switch is pretty complicated because of several conflicting concerns:
	 *
	 *    - Want to generate code without an intermediate representation,
	 *      i.e., in one go
	 *
	 *    - Case selectors are expressions, not values, and may thus e.g. throw
	 *      exceptions (which causes evaluation order concerns)
	 *
	 *    - Evaluation semantics of case selectors and default clause need to be 
	 *      carefully implemented to provide correct behavior even with case value
	 *      side effects
	 *
	 *    - Fall through case and default clauses; avoiding dead JUMPs if case
	 *      ends with an unconditional jump (a break or a continue)
	 *
	 *    - The same case value may occur multiple times, but evaluation rules
	 *      only process the first match before switching to a "propagation" mode
	 *      where case values are no longer evaluated
	 *
	 *  See E5 Section 12.11.  Also see doc/compiler.txt for compilation
	 *  discussion.
	 */

	advance(comp_ctx);
	advance_expect(comp_ctx, DUK_TOK_LPAREN);
	reg_switch = exprtop_toregconst(comp_ctx, res, BP_FOR_EXPR /*rbp_flags*/);
	advance_expect(comp_ctx, DUK_TOK_RPAREN);
	advance_expect(comp_ctx, DUK_TOK_LCURLY);

	DUK_DDDPRINT("switch value in register %d", reg_switch);

	temp_at_loop = GETTEMP(comp_ctx);

	for (;;) {
		/* sufficient for keeping temp reg numbers in check */
		SETTEMP(comp_ctx, temp_at_loop);

		if (comp_ctx->curr_token.t == DUK_TOK_RCURLY) {
			break;
		}

		if (comp_ctx->curr_token.t == DUK_TOK_CASE) {
			/*
			 *  Case clause.
			 *
			 *  Note: cannot use reg_case as a temp register (for SEQ target)
			 *  because it may be a constant.
			 */

			patch_jump_here(comp_ctx, pc_prevcase);  /* chain jumps for case
			                                          * evaluation and checking
			                                          */

			advance(comp_ctx);
			reg_case = exprtop_toregconst(comp_ctx, res, BP_FOR_EXPR /*rbp_flags*/);
			advance_expect(comp_ctx, DUK_TOK_COLON);

			reg_temp = ALLOCTEMP(comp_ctx);
			emit_a_b_c(comp_ctx, DUK_OP_SEQ, reg_temp, reg_switch, reg_case);
			emit_if_true_skip(comp_ctx, reg_temp);

			/* jump to next case clause */
			pc_prevcase = emit_jump_empty(comp_ctx);  /* no match, next case */

			/* statements go here (if any) on next loop */
		} else if (comp_ctx->curr_token.t == DUK_TOK_DEFAULT) {
			/*
			 *  Default clause.
			 */

			if (pc_default >= 0) {
				goto syntax_error;
			}
			advance(comp_ctx);
			advance_expect(comp_ctx, DUK_TOK_COLON);

			/* default clause matches next statement list (if any) */
			pc_default = -2;
		} else {
			/*
			 *  Else must be a statement list, possible terminators are
			 *  'case', 'default', and '}'.
			 */

			int num_stmts = 0;
			int tok;

			if (pc_default == -2) {
				pc_default = get_current_pc(comp_ctx);
			}

			/* Note: this is correct even for default clause statements:
			 * they participate in 'fall-through' behavior even if the
			 * default clause is in the middle.
			 */
			patch_jump_here(comp_ctx, pc_prevstmt);  /* chain jumps for 'fall-through'
			                                          * after a case matches.
			                                          */

			for (;;) {
				tok = comp_ctx->curr_token.t;
				if (tok == DUK_TOK_CASE || tok == DUK_TOK_DEFAULT ||
				    tok == DUK_TOK_RCURLY) {
					break;
				}
				num_stmts++;
				parse_statement(comp_ctx, res, 0 /*allow_source_elem*/);
			}

			/* fall-through jump to next code of next case (backpatched) */
			pc_prevstmt = emit_jump_empty(comp_ctx);

			/* FIXME: would be nice to omit this jump when the jump is not
			 * reachable, at least in the obvious cases (such as the case
			 * ending with a 'break'.
			 *
			 * Perhaps parse_statement() could provide some info on whether
			 * the statement is a "dead end"?
			 *
			 * If implemented, just set pc_prevstmt to -1 when not needed.
			 */
		}
	}

	DUK_ASSERT(comp_ctx->curr_token.t == DUK_TOK_RCURLY);
	advance(comp_ctx);

	/* default case control flow patchup; note that if pc_prevcase < 0
	 * (i.e. no case clauses), control enters default case automatically.
	 */
	if (pc_default >= 0) {
		/* default case exists: go there if no case matches */
		patch_jump(comp_ctx, pc_prevcase, pc_default);
	} else {
		/* default case does not exist, or no statements present
		 * after default case: finish case evaluation
		 */
		patch_jump_here(comp_ctx, pc_prevcase);
	}

	/* fall-through control flow patchup; note that pc_prevstmt may be
	 * < 0 (i.e. no case clauses), in which case this is a no-op.
	 */
	patch_jump_here(comp_ctx, pc_prevstmt);

	/* continue jump not patched, an INVALID opcode remains there */
	patch_jump_here(comp_ctx, pc_label_site + 1);  /* break jump */

	/* Note: 'fast' breaks will jump to pc_label_site + 1, which will
	 * then jump here.  The double jump will be eliminated by a
	 * peephole pass, resulting in an optimal jump here.  The label
	 * site jumps will remain in bytecode and will waste code size.
	 */

	return;

 syntax_error:
	DUK_ERROR(thr, DUK_ERR_SYNTAX_ERROR, "invalid switch statement");
}

static void parse_if_statement(duk_compiler_ctx *comp_ctx, duk_ivalue *res) {
	int temp_reset;
	int reg_cond;
	int pc_jump_false;

	DUK_DDDPRINT("begin parsing if statement");

	temp_reset = GETTEMP(comp_ctx);

	advance(comp_ctx);  /* eat 'if' */
	advance_expect(comp_ctx, DUK_TOK_LPAREN);

	reg_cond = exprtop_toregconst(comp_ctx, res, BP_FOR_EXPR /*rbp_flags*/);
	emit_if_true_skip(comp_ctx, reg_cond);
	pc_jump_false = emit_jump_empty(comp_ctx);  /* jump to end or else part */
	SETTEMP(comp_ctx, temp_reset);

	advance_expect(comp_ctx, DUK_TOK_RPAREN);

	parse_statement(comp_ctx, res, 0 /*allow_source_elem*/);

	/* The 'else' ambiguity is resolved by 'else' binding to the innermost
	 * construct, so greedy matching is correct here.
	 */

	if (comp_ctx->curr_token.t == DUK_TOK_ELSE) {
		int pc_jump_end;

		DUK_DDDPRINT("if has else part");

		advance(comp_ctx);

		pc_jump_end = emit_jump_empty(comp_ctx);  /* jump from true part to end */
		patch_jump_here(comp_ctx, pc_jump_false);

		parse_statement(comp_ctx, res, 0 /*allow_source_elem*/);

		patch_jump_here(comp_ctx, pc_jump_end);
	} else {
		DUK_DDDPRINT("if does not have else part");

		patch_jump_here(comp_ctx, pc_jump_false);
	}

	DUK_DDDPRINT("end parsing if statement");
}

static void parse_do_statement(duk_compiler_ctx *comp_ctx, duk_ivalue *res, int pc_label_site) {
	int reg_cond;
	int pc_start;

	DUK_DDDPRINT("begin parsing do statement");

	advance(comp_ctx);  /* eat 'do' */

	pc_start = get_current_pc(comp_ctx);
	patch_jump_here(comp_ctx, pc_label_site + 2);  /* continue jump */
	parse_statement(comp_ctx, res, 0 /*allow_source_elem*/);

	advance_expect(comp_ctx, DUK_TOK_WHILE);
	advance_expect(comp_ctx, DUK_TOK_LPAREN);

	reg_cond = exprtop_toregconst(comp_ctx, res, BP_FOR_EXPR /*rbp_flags*/);
	emit_if_false_skip(comp_ctx, reg_cond);
	emit_jump(comp_ctx, pc_start);
	/* no need to reset temps, as we're finished emitting code */

	advance_expect(comp_ctx, DUK_TOK_RPAREN);

	patch_jump_here(comp_ctx, pc_label_site + 1);  /* break jump */

	DUK_DDDPRINT("end parsing do statement");
}

static void parse_while_statement(duk_compiler_ctx *comp_ctx, duk_ivalue *res, int pc_label_site) {
	int temp_reset;
	int reg_cond;
	int pc_start;
	int pc_jump_false;

	DUK_DDDPRINT("begin parsing while statement");

	temp_reset = GETTEMP(comp_ctx);

	advance(comp_ctx);  /* eat 'while' */

	advance_expect(comp_ctx, DUK_TOK_LPAREN);

	pc_start = get_current_pc(comp_ctx);
	patch_jump_here(comp_ctx, pc_label_site + 2);  /* continue jump */

	reg_cond = exprtop_toregconst(comp_ctx, res, BP_FOR_EXPR /*rbp_flags*/);
	emit_if_true_skip(comp_ctx, reg_cond);
	pc_jump_false = emit_jump_empty(comp_ctx);
	SETTEMP(comp_ctx, temp_reset);

	advance_expect(comp_ctx, DUK_TOK_RPAREN);

	parse_statement(comp_ctx, res, 0 /*allow_source_elem*/);
	emit_jump(comp_ctx, pc_start);

	patch_jump_here(comp_ctx, pc_jump_false);
	patch_jump_here(comp_ctx, pc_label_site + 1);  /* break jump */

	DUK_DDDPRINT("end parsing while statement");
}

static void parse_break_or_continue_statement(duk_compiler_ctx *comp_ctx, duk_ivalue *res) {
	duk_hthread *thr = comp_ctx->thr;
	int is_break = (comp_ctx->curr_token.t == DUK_TOK_BREAK);
	int label_id;
	int label_catch_depth;
	int label_pc;  /* points to LABEL; pc+1 = jump site for break; pc+2 = jump site for continue */
	int label_is_closest;

	advance(comp_ctx);  /* eat 'break' or 'continue' */

	if (comp_ctx->curr_token.t == DUK_TOK_SEMICOLON ||  /* explicit semi follows */
	    comp_ctx->curr_token.lineterm ||                /* automatic semi will be inserted */
	    comp_ctx->curr_token.allow_auto_semi) {         /* automatic semi will be inserted */
		/* break/continue without label */

		lookup_active_label(comp_ctx, DUK_HTHREAD_STRING_EMPTY_STRING(thr), is_break, &label_id, &label_catch_depth, &label_pc, &label_is_closest);
	} else if (comp_ctx->curr_token.t == DUK_TOK_IDENTIFIER) {
		/* break/continue with label (label cannot be a reserved word, production is 'Identifier' */
		DUK_ASSERT(comp_ctx->curr_token.str1 != NULL);
		lookup_active_label(comp_ctx, comp_ctx->curr_token.str1, is_break, &label_id, &label_catch_depth, &label_pc, &label_is_closest);
		advance(comp_ctx);
	} else {
		DUK_ERROR(thr, DUK_ERR_SYNTAX_ERROR, "invalid break/continue label");
	}

	/* Use a fast break/continue when possible.  A fast break/continue is
	 * just a jump to the LABEL break/continue jump slot, which then jumps
	 * to an appropriate place (for break, going through ENDLABEL correctly).
	 * The peephole optimizer will optimize the jump to a direct one.
	 */

	if (label_catch_depth == comp_ctx->curr_func.catch_depth &&
	    label_is_closest) {
		DUK_DDDPRINT("break/continue: is_break=%d, label_id=%d, label_is_closest=%d, "
		             "label_catch_depth=%d, catch_depth=%d "
		             "-> use fast variant (direct jump)",
		             is_break, label_id, label_is_closest, label_catch_depth,
		             comp_ctx->curr_func.catch_depth);

		emit_jump(comp_ctx, label_pc + (is_break ? 1 : 2));
	} else {
		DUK_DDDPRINT("break/continue: is_break=%d, label_id=%d, label_is_closest=%d, "
		             "label_catch_depth=%d, catch_depth=%d "
		             "-> use slow variant (longjmp)",
		             is_break, label_id, label_is_closest, label_catch_depth,
		             comp_ctx->curr_func.catch_depth);

		emit_abc(comp_ctx,
		         is_break ? DUK_OP_BREAK : DUK_OP_CONTINUE,
		         label_id);
	}
}

static void parse_return_statement(duk_compiler_ctx *comp_ctx, duk_ivalue *res) {
	duk_hthread *thr = comp_ctx->thr;
	int reg_val;
	int ret_flags;

	advance(comp_ctx);  /* eat 'return' */

	/* A 'return' statement is only allowed inside an actual function body,
	 * not as part of eval or global code.
	 */
	if (!comp_ctx->curr_func.is_function) {
		DUK_ERROR(thr, DUK_ERR_SYNTAX_ERROR, "invalid return");
	}

	/* Use a fast return when possible.  A fast return does not cause a longjmp()
	 * unnecessarily.  A fast return can be done when no TCF catchers are active
	 * (this includes 'try' and 'with' statements).  Active label catches do not
	 * prevent a fast return; they're unwound on return automatically.
	 */

	ret_flags = 0;

	if (comp_ctx->curr_token.t == DUK_TOK_SEMICOLON ||  /* explicit semi follows */
	    comp_ctx->curr_token.lineterm ||                /* automatic semi will be inserted */
	    comp_ctx->curr_token.allow_auto_semi) {         /* automatic semi will be inserted */
		DUK_DDDPRINT("empty return value -> undefined");
		reg_val = 0;
	} else {
		int pc_before_expr;
		int pc_after_expr;

		DUK_DDDPRINT("return with a value");

		pc_before_expr = get_current_pc(comp_ctx);
		reg_val = exprtop_toregconst(comp_ctx, res, BP_FOR_EXPR /*rbp_flags*/);
		pc_after_expr = get_current_pc(comp_ctx);

		/* Tail call check: if last opcode emitted was CALL, and
		 * the context allows it, change the CALL to a tailcall.
		 */

		if (comp_ctx->curr_func.catch_depth == 0 &&   /* no catchers */
		    pc_after_expr > pc_before_expr) {         /* at least one opcode emitted */
			duk_compiler_instr *instr;

			instr = get_instr_ptr(comp_ctx, pc_after_expr - 1);
			DUK_ASSERT(instr != NULL);

			if (DUK_DEC_OP(instr->ins) == DUK_OP_CALL) {
				DUK_DDDPRINT("return statement detected a tail call opportunity: "
				             "catch depth is 0, exprtop() emitted >= 1 instructions, "
				             "and last instruction is a CALL "
				             "-> set TAILCALL flag");
				/* just flip the single bit */
				instr->ins |= DUK_ENC_OP_A_B_C(0, DUK_BC_CALL_FLAG_TAILCALL, 0, 0);

				/* no need to emit a RETURN */
				return;
			}
		}

		ret_flags = DUK_BC_RETURN_FLAG_HAVE_RETVAL;
	}

	if (comp_ctx->curr_func.catch_depth == 0) {
		DUK_DDDPRINT("fast return allowed -> use fast return");
		ret_flags |= DUK_BC_RETURN_FLAG_FAST;
	} else {
		DUK_DDDPRINT("fast return not allowed -> use slow return");
	}

	emit_a_b(comp_ctx, DUK_OP_RETURN, ret_flags /*flags*/, reg_val /*reg*/);
}

static void parse_throw_statement(duk_compiler_ctx *comp_ctx, duk_ivalue *res) {
	int reg_val;

	advance(comp_ctx);  /* eat 'throw' */

	if (comp_ctx->curr_token.t == DUK_TOK_SEMICOLON ||  /* explicit semi follows */
	    comp_ctx->curr_token.lineterm ||                /* automatic semi will be inserted */
	    comp_ctx->curr_token.allow_auto_semi) {         /* automatic semi will be inserted */
		DUK_DDDPRINT("empty throw value -> undefined");
		reg_val = ALLOCTEMP(comp_ctx);
		emit_extraop_b_c(comp_ctx, DUK_EXTRAOP_LDUNDEF, reg_val, 0);
	} else {
		DUK_DDDPRINT("throw with a value");

		/* FIXME: currently must be a register, not a const */
		reg_val = exprtop_toreg(comp_ctx, res, BP_FOR_EXPR /*rbp_flags*/);
	}

	emit_extraop_b_c(comp_ctx, DUK_EXTRAOP_THROW, reg_val, 0);
}

static void parse_try_statement(duk_compiler_ctx *comp_ctx, duk_ivalue *res) {
	duk_hthread *thr = comp_ctx->thr;
	duk_context *ctx = (duk_context *) thr;
	int reg_catch;      /* reg_catch+0 and reg_catch+1 are reserved for TRYCATCH */
	int const_varname = 0;
	int trycatch_flags = 0;
	int pc_trycatch = -1;
	int pc_catch = -1;
	int pc_finally = -1;

	/*
	 *  See the following documentation for discussion:
	 *
	 *    doc/execution.txt: control flow details
	 *
	 *  Try, catch, and finally "parts" are Blocks, not Statements, so
	 *  they must always be delimited by curly braces.  This is unlike e.g.
	 *  the if statement, which accepts any Statement.  This eliminates any
	 *  questions of matching parts of nested try statements.  The Block
	 *  parsing is implemented inline here (instead of calling out).
	 *
	 *  Finally part has a 'let scoped' variable, which requires a few kinks
	 *  here.
	 */

	comp_ctx->curr_func.catch_depth++;

	advance(comp_ctx);  /* eat 'try' */

	reg_catch = ALLOCTEMPS(comp_ctx, 2);

	pc_trycatch = get_current_pc(comp_ctx);
	emit_invalid(comp_ctx);  /* TRYCATCH, cannot emit know (not enough info) */
	emit_invalid(comp_ctx);  /* jump for 'catch' case */
	emit_invalid(comp_ctx);  /* jump for 'finally' case or end (if no finally) */

	/* try part */
	advance_expect(comp_ctx, DUK_TOK_LCURLY);
	parse_statements(comp_ctx, 0 /*allow_source_elem*/, 0 /*expect_eof*/);
	/* the DUK_TOK_RCURLY is eaten by parse_statements() */
	emit_extraop_only(comp_ctx, DUK_EXTRAOP_ENDTRY);

	if (comp_ctx->curr_token.t == DUK_TOK_CATCH) {
		/*
		 *  The catch variable must be updated to reflect the new allocated
		 *  register for the duration of the catch clause.  We need to store
		 *  and restore the original value for the varmap entry (if any).
		 */

		/*
		 *  Note: currently register bindings must be fixed for the entire
		 *  function.  So, even though the catch variable is in a register
		 *  we know, we must use an explicit environment record and slow path
		 *  accesses to read/write the catch binding to make closures created
		 *  within the catch clause work correctly.  This restriction should
		 *  be fixable (at least in common cases) later.
		 *
		 *  See: test-dev-bug-catch-binding-2.js.
		 *
		 *  FIXME: improve to get fast path access to most catch clauses.
		 */

		duk_hstring *h_var;
		int varmap_value;  /* for storing/restoring the varmap binding for catch variable */

		DUK_DDDPRINT("stack top at start of catch clause: %d", duk_get_top(ctx));

		trycatch_flags |= DUK_BC_TRYCATCH_FLAG_HAVE_CATCH;

		pc_catch = get_current_pc(comp_ctx);

		advance(comp_ctx);
		advance_expect(comp_ctx, DUK_TOK_LPAREN);

		if (comp_ctx->curr_token.t != DUK_TOK_IDENTIFIER) {
			/* Identifier, i.e. don't allow reserved words */
			goto syntax_error;
		}
		h_var = comp_ctx->curr_token.str1;
		DUK_ASSERT(h_var != NULL);

		duk_push_hstring(ctx, h_var);  /* keep in on valstack, use borrowed ref below */

		if (comp_ctx->curr_func.is_strict &&
		    ((h_var == DUK_HTHREAD_STRING_EVAL(thr)) ||
		     (h_var == DUK_HTHREAD_STRING_LC_ARGUMENTS(thr)))) {
			DUK_DDDPRINT("catch identifier 'eval' or 'arguments' in strict mode -> SyntaxError");
			goto syntax_error;
		}

		duk_dup_top(ctx);
		const_varname = getconst(comp_ctx);
		DUK_DDDPRINT("catch clause, const_varname=0x%08x (%d)", const_varname, const_varname);

		advance(comp_ctx);
		advance_expect(comp_ctx, DUK_TOK_RPAREN);

		advance_expect(comp_ctx, DUK_TOK_LCURLY);

		DUK_DDDPRINT("varmap before modifying for catch clause: %!iT", duk_get_tval(ctx, comp_ctx->curr_func.varmap_idx));

		duk_dup_top(ctx);
		duk_get_prop(ctx, comp_ctx->curr_func.varmap_idx);
		if (duk_is_undefined(ctx, -1)) {
			varmap_value = -2;
		} else if (duk_is_null(ctx, -1)) {
			varmap_value = -1;
		} else {
			DUK_ASSERT(duk_is_number(ctx, -1));
			varmap_value = duk_get_int(ctx, -1);
			DUK_ASSERT(varmap_value >= 0);
		}
		duk_pop(ctx);

#if 0  /* something like this is what we'd like to do, but it doesn't work for closures created inside the catch clause */
		duk_dup_top(ctx);
		duk_push_int(ctx, reg_catch + 0);
		duk_put_prop(ctx, comp_ctx->curr_func.varmap_idx);
#endif
		duk_dup_top(ctx);
		duk_push_null(ctx);
		duk_put_prop(ctx, comp_ctx->curr_func.varmap_idx);

		emit_a_b_c(comp_ctx, DUK_OP_PUTVAR, 0 /*unused */, const_varname /*varname*/, reg_catch + 0 /*value*/);

		DUK_DDDPRINT("varmap before parsing catch clause: %!iT", duk_get_tval(ctx, comp_ctx->curr_func.varmap_idx));

		parse_statements(comp_ctx, 0 /*allow_source_elem*/, 0 /*expect_eof*/);
		/* the DUK_TOK_RCURLY is eaten by parse_statements() */

		if (varmap_value == -2) {
			/* not present */
			duk_del_prop(ctx, comp_ctx->curr_func.varmap_idx);
		} else {
			if (varmap_value == -1) {
				duk_push_null(ctx);
			} else {
				DUK_ASSERT(varmap_value >= 0);
				duk_push_int(ctx, varmap_value);
			}
			duk_put_prop(ctx, comp_ctx->curr_func.varmap_idx);
		}
		/* varname is popped by above code */

		DUK_DDDPRINT("varmap after restore catch clause: %!iT", duk_get_tval(ctx, comp_ctx->curr_func.varmap_idx));

		emit_extraop_only(comp_ctx, DUK_EXTRAOP_ENDCATCH);

		/*
		 *  FIXME: for now, indicate that an expensive catch binding
		 *  declarative environment is always needed.  If we don't
		 *  need it, we don't need the const_varname either.
		 */

		trycatch_flags |= DUK_BC_TRYCATCH_FLAG_CATCH_BINDING;

		DUK_DDDPRINT("stack top at end of catch clause: %d", duk_get_top(ctx));
	}

	if (comp_ctx->curr_token.t == DUK_TOK_FINALLY) {
		trycatch_flags |= DUK_BC_TRYCATCH_FLAG_HAVE_FINALLY;

		pc_finally = get_current_pc(comp_ctx);

		advance(comp_ctx);

		advance_expect(comp_ctx, DUK_TOK_LCURLY);
		parse_statements(comp_ctx, 0 /*allow_source_elem*/, 0 /*expect_eof*/);
		/* the DUK_TOK_RCURLY is eaten by parse_statements() */
		emit_extraop_b(comp_ctx, DUK_EXTRAOP_ENDFIN, reg_catch);  /* rethrow */
	}

	if (!(trycatch_flags & DUK_BC_TRYCATCH_FLAG_HAVE_CATCH) &&
	    !(trycatch_flags & DUK_BC_TRYCATCH_FLAG_HAVE_FINALLY)) {
		/* must have catch and/or finally */
		goto syntax_error;
	}

	patch_trycatch(comp_ctx,
	               pc_trycatch,
	               reg_catch,
	               const_varname,
	               trycatch_flags);

	if (trycatch_flags & DUK_BC_TRYCATCH_FLAG_HAVE_CATCH) {
		DUK_ASSERT(pc_catch >= 0);
		patch_jump(comp_ctx, pc_trycatch + 1, pc_catch);
	}

	if (trycatch_flags & DUK_BC_TRYCATCH_FLAG_HAVE_FINALLY) {
		DUK_ASSERT(pc_finally >= 0);
		patch_jump(comp_ctx, pc_trycatch + 2, pc_finally);
	} else {
		/* without finally, the second jump slot is used to jump to end of stmt */
		patch_jump_here(comp_ctx, pc_trycatch + 2);
	}

	comp_ctx->curr_func.catch_depth--;
	return;

 syntax_error:
	DUK_ERROR(thr, DUK_ERR_SYNTAX_ERROR, "invalid try statement");
}

static void parse_with_statement(duk_compiler_ctx *comp_ctx, duk_ivalue *res) {
	int pc_trycatch;
	int pc_finished;
	int reg_catch;
	int reg_target;
	int trycatch_flags;

	advance(comp_ctx);  /* eat 'with' */

	reg_catch = ALLOCTEMPS(comp_ctx, 2);

	advance_expect(comp_ctx, DUK_TOK_LPAREN);
	reg_target = exprtop_toregconst(comp_ctx, res, BP_FOR_EXPR /*rbp_flags*/);
	advance_expect(comp_ctx, DUK_TOK_RPAREN);

	pc_trycatch = get_current_pc(comp_ctx);
	trycatch_flags = DUK_BC_TRYCATCH_FLAG_WITH_BINDING;
	emit_a_b_c(comp_ctx, DUK_OP_TRYCATCH, trycatch_flags /*a*/, reg_catch /*b*/, reg_target /*c*/);
	emit_invalid(comp_ctx);  /* catch jump */
	emit_invalid(comp_ctx);  /* finished jump */

	parse_statement(comp_ctx, res, 0 /*allow_source_elem*/);
	emit_extraop_only(comp_ctx, DUK_EXTRAOP_ENDTRY);

	pc_finished = get_current_pc(comp_ctx);

	patch_jump(comp_ctx, pc_trycatch + 2, pc_finished);
}

static int stmt_label_site(duk_compiler_ctx *comp_ctx, int label_id) {
	/* if a site already exists, nop: max one label site per statement */
	if (label_id >= 0) {
		return label_id;
	}

	label_id = comp_ctx->curr_func.label_next++;
	DUK_DDDPRINT("allocated new label id for label site: %d", label_id);

	emit_abc(comp_ctx, DUK_OP_LABEL, label_id);
	emit_invalid(comp_ctx);
	emit_invalid(comp_ctx);

	return label_id;
}

/* Parse a single statement.
 *
 * Creates a label site (with an empty label) automatically for iteration
 * statements.  Also "peels off" any label statements for explicit labels.
 */
static void parse_statement(duk_compiler_ctx *comp_ctx, duk_ivalue *res, int allow_source_elem) {
	duk_hthread *thr = comp_ctx->thr;
	duk_context *ctx = (duk_context *) thr;
	int dir_prol_at_entry;
	int temp_at_entry;
	int labels_len_at_entry;
	int pc_at_entry;   /* assumed to also be PC of "LABEL" */
	int stmt_id;
	int stmt_flags = 0;
	int label_id = -1;
	int tok;

	RECURSION_INCREASE(comp_ctx, thr);

	temp_at_entry = GETTEMP(comp_ctx);
	pc_at_entry = get_current_pc(comp_ctx);
	labels_len_at_entry = duk_get_length(ctx, comp_ctx->curr_func.labelnames_idx);
	stmt_id = comp_ctx->curr_func.stmt_next++;
	dir_prol_at_entry = comp_ctx->curr_func.in_directive_prologue;

	DUK_UNREF(stmt_id);

	DUK_DDDPRINT("parsing a statement, stmt_id=%d, temp_at_entry=%d, labels_len_at_entry=%d, "
	             "is_strict=%d, in_directive_prologue=%d, catch_depth=%d",
	             stmt_id, temp_at_entry, labels_len_at_entry, comp_ctx->curr_func.is_strict,
	             comp_ctx->curr_func.in_directive_prologue, comp_ctx->curr_func.catch_depth);

	/* The directive prologue flag is cleared by default so that it is
	 * unset for any recursive statement parsing.  It is only "revived"
	 * if a directive is detected.  (We could also make directives only
	 * allowed if 'allow_source_elem' was true.)
	 */
	comp_ctx->curr_func.in_directive_prologue = 0;

 retry_parse:

	DUK_DDDPRINT("try stmt parse, stmt_id=%d, label_id=%d, allow_source_elem=%d, catch_depth=%d",
	             stmt_id, label_id, allow_source_elem, comp_ctx->curr_func.catch_depth);

	/*
	 *  Detect iteration statements; if encountered, establish an
	 *  empty label.
	 */

	tok = comp_ctx->curr_token.t;
	if (tok == DUK_TOK_FOR || tok == DUK_TOK_DO || tok == DUK_TOK_WHILE ||
	    tok == DUK_TOK_SWITCH) {
		DUK_DDDPRINT("iteration/switch statement -> add empty label");

		label_id = stmt_label_site(comp_ctx, label_id);
		add_label(comp_ctx,
		          DUK_HTHREAD_STRING_EMPTY_STRING(thr),
		          pc_at_entry /*pc_label*/,
		          label_id);
	}

	/*
	 *  Main switch for statement / source element type.
	 */

	switch (comp_ctx->curr_token.t) {
	case DUK_TOK_FUNCTION: {
		/*
		 *  Function declaration, function expression, or (non-standard)
		 *  function statement.
		 *
		 *  The E5 specification only allows function declarations at
		 *  the top level (in "source elements").  An ExpressionStatement
		 *  is explicitly not allowed to begin with a "function" keyword
		 *  (E5 Section 12.4).  Hence any non-error semantics for such
		 *  non-top-level statements are non-standard.
		 */

		if (allow_source_elem) {
			/* FunctionDeclaration: not strictly a statement but handled as such */
			int fnum;

			DUK_DDDPRINT("function declaration statement");

			advance(comp_ctx);  /* eat 'function' */
			fnum = parse_function_like_fnum(comp_ctx, 1 /*is_decl*/, 0 /*is_setget*/);

			if (comp_ctx->curr_func.in_scanning) {
				int n;
				duk_hstring *h_funcname;

				duk_get_prop_index(ctx, comp_ctx->curr_func.funcs_idx, fnum);
				duk_get_prop_stridx(ctx, -1, DUK_STRIDX_NAME);  /* -> [ ... func name ] */
				h_funcname = duk_get_hstring(ctx, -1);
				DUK_ASSERT(h_funcname != NULL);

				DUK_DDDPRINT("register function declaration %!O in pass 1, fnum %d", h_funcname, fnum);
				n = duk_get_length(ctx, comp_ctx->curr_func.decls_idx);  /*FIXME: primitive for pushing*/
				duk_push_hstring(ctx, h_funcname);
				duk_put_prop_index(ctx, comp_ctx->curr_func.decls_idx, n);
				duk_push_int(ctx, DUK_DECL_TYPE_FUNC + (fnum << 8));
				duk_put_prop_index(ctx, comp_ctx->curr_func.decls_idx, n + 1);

				duk_pop_n(ctx, 2);
			}

			/* no statement value (unlike function expression) */
			stmt_flags = 0;
			break;
		} else {
			/* FIXME: add support for non-standard function statements and/or
			 * non-top-level function expressions.
			 */

			if (1) {
				/* Standard behavior */
				DUK_ERROR(thr, DUK_ERR_SYNTAX_ERROR, "function declaration not allowed outside of top level");
			} else if (0) {
				/* Non-standard: interpret as a function expression inside an ExpressionStatement */
				DUK_DDDPRINT("function expression (inside an expression statement; non-standard)");
				stmt_flags = (HAS_VAL);  /* FIXME -- e.g. HAS_TERM? */
			} else if (0) {
				/* Non-standard: interpret as a function statement */
				DUK_DDDPRINT("function statement (non-standard)");
				stmt_flags = 0;  /* FIXME */
			}
		}
		DUK_ERROR(thr, DUK_ERR_UNIMPLEMENTED_ERROR, "non-standard function expression/statement unimplemented");
		break;
	}
	case DUK_TOK_LCURLY: {
		DUK_DDDPRINT("block statement");
		advance(comp_ctx);
		parse_statements(comp_ctx, 0 /*allow_source_elem*/, 0 /*expect_eof*/);
		/* the DUK_TOK_RCURLY is eaten by parse_statements() */
		stmt_flags = 0;
		break;
	}
	case DUK_TOK_VAR: {
		DUK_DDDPRINT("variable declaration statement");
		parse_var_statement(comp_ctx, res);
		stmt_flags = HAS_TERM;
		break;
	}
	case DUK_TOK_SEMICOLON: {
		/* empty statement with an explicit semicolon */
		DUK_DDDPRINT("empty statement");
		stmt_flags = HAS_TERM;
		break;
	}
	case DUK_TOK_IF: {
		DUK_DDDPRINT("if statement");
		parse_if_statement(comp_ctx, res);
		stmt_flags = 0;
		break;
	}
	case DUK_TOK_DO: {
		/*
		 *  Do-while statement is mostly trivial, but there is special
		 *  handling for automatic semicolon handling (triggered by the
		 *  ALLOW_AUTO_SEMI_ALWAYS) flag related to a bug filed at:
		 *
		 *    https://bugs.ecmascript.org/show_bug.cgi?id=8
		 *
		 *  See doc/compiler.txt for details.
		 */
		DUK_DDDPRINT("do statement");
		DUK_ASSERT(label_id >= 0);
		update_label_flags(comp_ctx,
		                   label_id,
		                   DUK_LABEL_FLAG_ALLOW_BREAK | DUK_LABEL_FLAG_ALLOW_CONTINUE);
		parse_do_statement(comp_ctx, res, pc_at_entry);
		stmt_flags = HAS_TERM | ALLOW_AUTO_SEMI_ALWAYS;  /* ALLOW_AUTO_SEMI_ALWAYS workaround */
		break;
	}
	case DUK_TOK_WHILE: {
		DUK_DDDPRINT("while statement");
		DUK_ASSERT(label_id >= 0);
		update_label_flags(comp_ctx,
		                   label_id,
		                   DUK_LABEL_FLAG_ALLOW_BREAK | DUK_LABEL_FLAG_ALLOW_CONTINUE);
		parse_while_statement(comp_ctx, res, pc_at_entry);
		stmt_flags = 0;
		break;
	}
	case DUK_TOK_FOR: {
		/*
		 *  For/for-in statement is complicated to parse because
		 *  determining the statement type (three-part for vs. a
		 *  for-in) requires potential backtracking.
		 *
		 *  See the helper for the messy stuff.
		 */
		DUK_DDDPRINT("for/for-in statement");
		DUK_ASSERT(label_id >= 0);
		update_label_flags(comp_ctx,
		                   label_id,
		                   DUK_LABEL_FLAG_ALLOW_BREAK | DUK_LABEL_FLAG_ALLOW_CONTINUE);
		parse_for_statement(comp_ctx, res, pc_at_entry);
		stmt_flags = 0;
		break;
	}
	case DUK_TOK_CONTINUE:
	case DUK_TOK_BREAK: {
		DUK_DDDPRINT("break/continue statement");
		parse_break_or_continue_statement(comp_ctx, res);
		stmt_flags = HAS_TERM | IS_TERMINAL;
		break;
	}
	case DUK_TOK_RETURN: {
		DUK_DDDPRINT("return statement");
		parse_return_statement(comp_ctx, res);
		stmt_flags = HAS_TERM | IS_TERMINAL;
		break;
	}
	case DUK_TOK_WITH: {
		DUK_DDDPRINT("with statement");
		comp_ctx->curr_func.with_depth++;
		parse_with_statement(comp_ctx, res);
		comp_ctx->curr_func.with_depth--;
		stmt_flags = 0;
		break;
	}
	case DUK_TOK_SWITCH: {
		/*
		 *  The switch statement is pretty messy to compile.
		 *  See the helper for details.
		 */
		DUK_DDDPRINT("switch statement");
		DUK_ASSERT(label_id >= 0);
		update_label_flags(comp_ctx,
		                   label_id,
		                   DUK_LABEL_FLAG_ALLOW_BREAK);  /* don't allow continue */
		parse_switch_statement(comp_ctx, res, pc_at_entry);
		stmt_flags = 0;
		break;
	}
	case DUK_TOK_THROW: {
		DUK_DDDPRINT("throw statement");
		parse_throw_statement(comp_ctx, res);
		stmt_flags = HAS_TERM | IS_TERMINAL;
		break;
	}
	case DUK_TOK_TRY: {
		DUK_DDDPRINT("try statement");
		parse_try_statement(comp_ctx, res);
		stmt_flags = 0;
		break;
	}
	case DUK_TOK_DEBUGGER: {
		DUK_DDDPRINT("debugger statement: ignored");
		advance(comp_ctx);
		stmt_flags = HAS_TERM;
		break;
	}
	default: {
		/*
		 *  Else, must be one of:
		 *    - ExpressionStatement, possibly a directive (String)
		 *    - LabelledStatement (Identifier followed by ':')
		 *
		 *  Expressions beginning with 'function' keyword are covered by a case
		 *  above (such expressions are not allowed in standard E5 anyway).
		 *  Also expressions starting with '{' are interpreted as block
		 *  statements.  See E5 Section 12.4.
		 *
		 *  Directive detection is tricky; see E5 Section 14.1 on directive
		 *  prologue.  A directive is an expression statement with a single
		 *  string literal and an explicit or automatic semicolon.  Escape
		 *  characters are significant and no parens etc are allowed:
		 *
		 *    'use strict';          // valid 'use strict' directive
		 *    'use\u0020strict';     // valid directive, not a 'use strict' directive
		 *    ('use strict');        // not a valid directive
		 *
		 *  The expression is determined to consist of a single string literal
		 *  based on expr_nud() and expr_led() call counts.  The string literal
		 *  of a 'use strict' directive is determined to lack any escapes based
		 *  num_escapes count from the lexer.  Note that other directives may be
		 *  allowed to contain escapes, so a directive with escapes does not
		 *  terminate a directive prologue.
		 *
		 *  We rely on the fact that the expression parser will not emit any
		 *  code for a single token expression.  However, it will generate an
		 *  intermediate value which we will then successfully ignore.
		 *
		 *  A similar approach is used for labels.
		 */

		int single_token;

		DUK_DDDPRINT("expression statement");
		exprtop(comp_ctx, res, BP_FOR_EXPR /*rbp_flags*/);

		single_token = (comp_ctx->curr_func.nud_count == 1 &&  /* one token */
		                comp_ctx->curr_func.led_count == 0);   /* no operators */

		if (single_token &&
		    comp_ctx->prev_token.t == DUK_TOK_IDENTIFIER &&
		    comp_ctx->curr_token.t == DUK_TOK_COLON) {
			/*
			 *  Detected label
			 */

			duk_hstring *h_lab;

			/* expected ival */
			DUK_ASSERT(res->t == DUK_IVAL_VAR);
			DUK_ASSERT(res->x1.t == DUK_ISPEC_VALUE);
			DUK_ASSERT(DUK_TVAL_IS_STRING(duk_get_tval(ctx, res->x1.valstack_idx)));
			h_lab = comp_ctx->prev_token.str1;
			DUK_ASSERT(h_lab != NULL);

			DUK_DDDPRINT("explicit label site for label '%!O'", h_lab);

			advance(comp_ctx);  /* eat colon */

			label_id = stmt_label_site(comp_ctx, label_id);

			add_label(comp_ctx,
			          h_lab,
			          pc_at_entry /*pc_label*/,
			          label_id);
	
			/* a statement following a label cannot be a source element
			 * (a function declaration).
			 */
			allow_source_elem = 0;

			DUK_DDDPRINT("label handled, retry statement parsing");
			goto retry_parse;
		}

		stmt_flags = 0;

		if (dir_prol_at_entry &&                           /* still in prologue */
		    single_token &&                                /* single string token */
		    comp_ctx->prev_token.t == DUK_TOK_STRING) {
			/*
			 *  Detected a directive

			 */
			duk_hstring *h_dir;

			/* expected ival */
			DUK_ASSERT(res->t == DUK_IVAL_PLAIN);
			DUK_ASSERT(res->x1.t == DUK_ISPEC_VALUE);
			DUK_ASSERT(DUK_TVAL_IS_STRING(duk_get_tval(ctx, res->x1.valstack_idx)));
			h_dir = comp_ctx->prev_token.str1;
			DUK_ASSERT(h_dir != NULL);

			stmt_flags |= STILL_PROLOGUE;

			/* Note: escaped characters differentiate directives */

			if (comp_ctx->prev_token.num_escapes > 0) {
				DUK_DDDPRINT("directive contains escapes: valid directive "
				             "but we ignore such directives");
			} else {
				/* FIXME: how to compare 'use strict' most compactly?
				 * We don't necessarily want to add it to the built-ins
				 * because it's not needed at run time.
				 */

				if (DUK_HSTRING_GET_BYTELEN(h_dir) == 10 &&
				    strncmp((const char *) DUK_HSTRING_GET_DATA(h_dir), "use strict", 10) == 0) {
					DUK_DDDPRINT("use strict directive detected: strict flag %d -> %d",
					             comp_ctx->curr_func.is_strict, 1);
					comp_ctx->curr_func.is_strict = 1;
				} else {
					DUK_DDPRINT("unknown directive: '%!O', ignoring but not terminating "
					            "directive prologue", (duk_hobject *) h_dir);
				}
			}
		} else {
			DUK_DDDPRINT("non-directive expression statement or no longer in prologue; "
			             "prologue terminated if still active");
                }

		stmt_flags |= HAS_VAL | HAS_TERM;
	}
	}  /* end switch (tok) */

	/*
	 *  Statement value handling.
	 *
	 *  Global code and eval code has an implicit return value
	 *  which comes from the last statement with a value
	 *  (technically a non-"empty" continuation, which is
	 *  different from an empty statement).
	 *
	 *  Since we don't know whether a later statement will
	 *  override the value of the current statement, we need
	 *  to coerce the statement value to a register allocated
	 *  for implicit return values.  In other cases we need
	 *  to coerce the statement value to a plain value to get
	 *  any side effects out (consider e.g. "foo.bar;").
	 */

	/* FIXME: what about statements which leave a half-cooked value in 'res'
	 * but have no stmt value?  Any such statements?
	 */

	if (stmt_flags & HAS_VAL) {
		int reg_stmt_value = comp_ctx->curr_func.reg_stmt_value;
		if (reg_stmt_value >= 0) {
			ivalue_toforcedreg(comp_ctx, res, reg_stmt_value);
		} else {
			ivalue_toplain_ignore(comp_ctx, res);
		}
	} else {
		;
	}

	/*
	 *  Statement terminator check, including automatic semicolon
	 *  handling.  After this step, 'curr_tok' should be the first
	 *  token after a possible statement terminator.
	 */

	if (stmt_flags & HAS_TERM) {
		if (comp_ctx->curr_token.t == DUK_TOK_SEMICOLON) {
			DUK_DDDPRINT("explicit semicolon terminates statement");
			advance(comp_ctx);
		} else {
			if (comp_ctx->curr_token.allow_auto_semi) {
				DUK_DDDPRINT("automatic semicolon terminates statement");
			} else if (stmt_flags & ALLOW_AUTO_SEMI_ALWAYS) {
				/* FIXME: make this lenience dependent on flags or strictness? */
				DUK_DDDPRINT("automatic semicolon terminates statement (allowed for compatibility "
				             "even though no lineterm present before next token)");
			} else {
				DUK_ERROR(thr, DUK_ERR_SYNTAX_ERROR, "unterminated statement");
			}
		}
	} else {
		DUK_DDDPRINT("statement has no terminator");
	}

	/*
	 *  Directive prologue tracking.
	 */

	if (stmt_flags & STILL_PROLOGUE) {
		DUK_DDDPRINT("setting in_directive_prologue");
		comp_ctx->curr_func.in_directive_prologue = 1;
	}

	/*
	 *  Cleanups (all statement parsing flows through here).
	 *
	 *  Pop label site and reset labels.  Reset 'next temp' to value at
	 *  entry to reuse temps.
	 */

	if (label_id >= 0) {
		emit_abc(comp_ctx, DUK_OP_ENDLABEL, label_id);
	}

	SETTEMP(comp_ctx, temp_at_entry);

	reset_labels_to_length(comp_ctx, labels_len_at_entry);

	/* FIXME: return indication of "terminalness" (e.g. a 'throw' is terminal) */

	RECURSION_DECREASE(comp_ctx, thr);
}

#undef HAS_VAL
#undef HAS_TERM
#undef ALLOW_AUTO_SEMI_ALWAYS

/*
 *  Parse a statement list.
 *
 *  Handles automatic semicolon insertion and implicit return value.
 *
 *  Upon entry, 'curr_tok' should contain the first token of the first
 *  statement (parsed in the "allow regexp literal" mode).  Upon exit,
 *  'curr_tok' contains the token following the statement list terminator
 *  (EOF or closing brace).
 */

static void parse_statements(duk_compiler_ctx *comp_ctx, int allow_source_elem, int expect_eof) {
	duk_hthread *thr = comp_ctx->thr;
	duk_context *ctx = (duk_context *) thr;
	duk_ivalue res_alloc;
	duk_ivalue *res = &res_alloc;

	/* Setup state.  Initial ivalue is 'undefined'. */

	duk_require_stack(ctx, PARSE_STATEMENTS_SLOTS);

	/* FIXME: 'res' setup can be moved to function body level; in fact, two 'res'
	 * intermediate values suffice for parsing of each function.  Nesting is needed
	 * for nested functions (which may occur inside expressions).
	 */

	memset(&res_alloc, 0, sizeof(res_alloc));
	res->t = DUK_IVAL_PLAIN;
	res->x1.t = DUK_ISPEC_VALUE;
	res->x1.valstack_idx = duk_get_top(ctx);
	res->x2.valstack_idx = res->x1.valstack_idx + 1;
	duk_push_undefined(ctx);
	duk_push_undefined(ctx);

	/* Parse statements until a closing token (EOF or '}') is found. */

	for (;;) {
		/* Check whether statement list ends. */

		if (expect_eof) {
			if (comp_ctx->curr_token.t == DUK_TOK_EOF) {
				break;
			}
		} else {
			if (comp_ctx->curr_token.t == DUK_TOK_RCURLY) {
				break;
			}
		}

		/* Check statement type based on the first token type.
		 *
		 * Note: expression parsing helpers expect 'curr_tok' to
		 * contain the first token of the expression upon entry.
		 */

		DUK_DDDPRINT("TOKEN %d (non-whitespace, non-comment)", comp_ctx->curr_token.t);

		parse_statement(comp_ctx, res, allow_source_elem);
	}

	advance(comp_ctx);

	/* Tear down state. */

	duk_pop_2(ctx);
}

/*
 *  Declaration binding instantiation conceptually happens when calling a
 *  function; for us it essentially means that function prologue.  The
 *  conceptual process is described in E5 Section 10.5.
 *
 *  We need to keep track of all encountered identifiers to (1) create an
 *  identifier-to-register map ("varmap"); and (2) detect duplicate
 *  declarations.  Identifiers which are not bound to registers still need
 *  to be tracked for detecting duplicates.  Currently such identifiers
 *  are put into the varmap with a 'null' value, which is later cleaned up.
 *
 *  Some bindings in E5 are not configurable (= deletable) and almost all
 *  are mutable (writable).  Exceptions are:
 * 
 *    - The 'arguments' binding, established only if no shadowing argument
 *      or function declaration exists.  We handle 'arguments' creation
 *      and binding through an explicit slow path environment record.
 *
 *    - The "name" binding for a named function expression.  This is also
 *      handled through an explicit slow path environment record.
 */

/* FIXME: add support for variables to not be register bound always, to 
 * handle cases with a very large number of variables?
 */

static void init_varmap_and_prologue_for_pass2(duk_compiler_ctx *comp_ctx, int *out_stmt_value_reg) {
	duk_hthread *thr = comp_ctx->thr;
	duk_context *ctx = (duk_context *) thr;
	duk_hstring *h_name;
	int configurable_bindings;
	int num_args;
	int num_decls;
	int reg_name;
	int declvar_flags;
	int i;
#ifdef DUK_USE_ASSERTIONS
	int entry_top;
#endif

#ifdef DUK_USE_ASSERTIONS
	entry_top = duk_get_top(ctx);
#endif

	/*
	 *  Preliminaries
	 */

	configurable_bindings = comp_ctx->curr_func.is_eval;
	DUK_DDDPRINT("configurable_bindings=%d", configurable_bindings);

	/* varmap is already in comp_ctx->curr_func.varmap_idx */

	/*
	 *  Function formal arguments, always bound to registers
	 */

	num_args = duk_get_length(ctx, comp_ctx->curr_func.argnames_idx);
	DUK_DDDPRINT("num_args=%d", num_args);
	for (i = 0; i < num_args; i++) {
		duk_get_prop_index(ctx, comp_ctx->curr_func.argnames_idx, i);
		h_name = duk_get_hstring(ctx, -1);
		DUK_ASSERT(h_name != NULL);

		if (comp_ctx->curr_func.is_strict) {
			if (hstring_is_eval_or_arguments(comp_ctx, h_name)) {
				DUK_DDDPRINT("arg named 'eval' or 'arguments' in strict mode -> SyntaxError");
				goto error_argname;
			}
			duk_dup_top(ctx);
			if (duk_has_prop(ctx, comp_ctx->curr_func.varmap_idx)) {
				DUK_DDDPRINT("duplicate arg name in strict mode -> SyntaxError");
				goto error_argname;
			}

			/* Ensure argument name is not a reserved word in current
			 * (final) strictness.  Formal argument parsing may not
			 * catch reserved names if strictness changes during
			 * parsing.
			 *
			 * We only need to do this in strict mode because non-strict
			 * keyword are always detected in formal argument parsing.
			 */

			if (DUK_HSTRING_HAS_STRICT_RESERVED_WORD(h_name)) {
				goto error_argname;
			}
		}

		/* overwrite any previous binding of the same name; the effect is
		 * that last argument of a certain name wins.
		 */

		/* only functions can have arguments */
		DUK_ASSERT(comp_ctx->curr_func.is_function);
		duk_push_int(ctx, i);  /* -> [ ... name index ] */
		duk_put_prop(ctx, comp_ctx->curr_func.varmap_idx); /* -> [ ... ] */

		/* no code needs to be emitted, the regs already have values */
	}

	/* use temp_next for tracking register allocations */
	SETTEMP_CHECKMAX(comp_ctx, num_args);

	if (out_stmt_value_reg) {
		*out_stmt_value_reg = ALLOCTEMP(comp_ctx);
	}

	/*
	 *  Function declarations
	 */

	num_decls = duk_get_length(ctx, comp_ctx->curr_func.decls_idx);
	DUK_DDDPRINT("num_decls=%d -> %!T", num_decls, duk_get_tval(ctx, comp_ctx->curr_func.decls_idx));
	for (i = 0; i < num_decls; i += 2) {
		int decl_type;
		int fnum;

		duk_get_prop_index(ctx, comp_ctx->curr_func.decls_idx, i + 1);  /* decl type */
		decl_type = duk_to_int(ctx, -1);
		fnum = decl_type >> 8;  /* FIXME: macros */
		decl_type = decl_type & 0xff;
		duk_pop(ctx);

		if (decl_type != DUK_DECL_TYPE_FUNC) {
			continue;
		}

		duk_get_prop_index(ctx, comp_ctx->curr_func.decls_idx, i);  /* decl name */

		if (comp_ctx->curr_func.is_function) {
			int reg_bind;
			duk_dup_top(ctx);
			if (duk_has_prop(ctx, comp_ctx->curr_func.varmap_idx)) {
				/* shadowed; update value */
				duk_dup_top(ctx);
				duk_get_prop(ctx, comp_ctx->curr_func.varmap_idx);
				reg_bind = duk_to_int(ctx, -1);  /* [ ... name reg_bind ] */
				emit_a_bc(comp_ctx, DUK_OP_CLOSURE, reg_bind, fnum);
			} else {
				/* function: always register bound */
				reg_bind = ALLOCTEMP(comp_ctx);
				emit_a_bc(comp_ctx, DUK_OP_CLOSURE, reg_bind, fnum);
				duk_push_int(ctx, reg_bind);
			}
		} else {
			/* Function declaration for global/eval code is emitted even
			 * for duplicates, because of E5 Section 10.5, step 5.e of
			 * E5.1 (special behavior for variable bound to global object).
			 *
			 * DECLVAR will not re-declare a variable as such, but will
			 * update the binding value.
			 */

			int reg_temp = ALLOCTEMP(comp_ctx);
			duk_dup_top(ctx);
			reg_name = getconst(comp_ctx);
			duk_push_null(ctx);

			emit_a_bc(comp_ctx, DUK_OP_CLOSURE, reg_temp, fnum);

			declvar_flags = DUK_PROPDESC_FLAG_WRITABLE |
			                DUK_PROPDESC_FLAG_ENUMERABLE |
			                DUK_BC_DECLVAR_FLAG_FUNC_DECL;

			if (configurable_bindings) {
				declvar_flags |= DUK_PROPDESC_FLAG_CONFIGURABLE;
			}

			emit_a_b_c(comp_ctx, DUK_OP_DECLVAR, declvar_flags /*flags*/, reg_name /*name*/, reg_temp /*value*/);

			SETTEMP(comp_ctx, reg_temp);  /* forget temp */
		}

		DUK_DDDPRINT("function declaration to varmap: %!T -> %!T", duk_get_tval(ctx, -2), duk_get_tval(ctx, -1));

		duk_put_prop(ctx, comp_ctx->curr_func.varmap_idx);  /* [ ... name reg/null ] -> [ ... ] */
	}

	/*
	 *  'arguments' binding is special; if a shadowing argument or
	 *  function declaration exists, an arguments object will
	 *  definitely not be needed, regardless of whether the identifier
	 *  'arguments' is referenced inside the function body.
	 */

	if (duk_has_prop_stridx(ctx, comp_ctx->curr_func.varmap_idx, DUK_STRIDX_LC_ARGUMENTS)) {
		DUK_DDDPRINT("'arguments' is shadowed by argument or function declaration "
		             "-> arguments object creation can be skipped");
		comp_ctx->curr_func.is_arguments_shadowed = 1;
	}

	/*
	 *  Variable declarations.
	 *
	 *  Unlike function declarations, variable declaration values don't get
	 *  assigned on entry.  If a binding of the same name already exists, just
	 *  ignore it silently.
	 */

	for (i = 0; i < num_decls; i += 2) {
		int decl_type;

		duk_get_prop_index(ctx, comp_ctx->curr_func.decls_idx, i + 1);  /* decl type */
		decl_type = duk_to_int(ctx, -1);
		decl_type = decl_type & 0xff;
		duk_pop(ctx);

		if (decl_type != DUK_DECL_TYPE_VAR) {
			continue;
		}

		duk_get_prop_index(ctx, comp_ctx->curr_func.decls_idx, i);  /* decl name */

		if (duk_has_prop(ctx, comp_ctx->curr_func.varmap_idx)) {
			/* shadowed, ignore */
		} else {
			duk_get_prop_index(ctx, comp_ctx->curr_func.decls_idx, i);  /* decl name */
			h_name = duk_get_hstring(ctx, -1);
			DUK_ASSERT(h_name != NULL);

			if (h_name == DUK_HTHREAD_STRING_LC_ARGUMENTS(thr) &&
			    !comp_ctx->curr_func.is_arguments_shadowed) {
				/* E5 Section steps 7-8 */
				DUK_DDDPRINT("'arguments' not shadowed by a function declaration, "
				             "but appears as a variable declaration -> treat as "
				             "a no-op for variable declaration purposes");
				duk_pop(ctx);
				continue;
			}

			if (comp_ctx->curr_func.is_function) {
				int reg_bind = ALLOCTEMP(comp_ctx);
				/* no need to init reg, it will be undefined on entry */
				duk_push_int(ctx, reg_bind);
			} else {
				duk_dup_top(ctx);
				reg_name = getconst(comp_ctx);
				duk_push_null(ctx);

				declvar_flags = DUK_PROPDESC_FLAG_WRITABLE |
			                        DUK_PROPDESC_FLAG_ENUMERABLE |
				                DUK_BC_DECLVAR_FLAG_UNDEF_VALUE;
				if (configurable_bindings) {
					declvar_flags |= DUK_PROPDESC_FLAG_CONFIGURABLE;
				}

				emit_a_b_c(comp_ctx, DUK_OP_DECLVAR, declvar_flags /*flags*/, reg_name /*name*/, 0 /*value*/);
			}

			duk_put_prop(ctx, comp_ctx->curr_func.varmap_idx);  /* [ ... name reg/null ] -> [ ... ] */
		}
	}

	/*
	 *  Wrap up
	 */

	DUK_DDDPRINT("varmap: %!T, is_arguments_shadowed=%d",
	             duk_get_tval(ctx, comp_ctx->curr_func.varmap_idx),
	             comp_ctx->curr_func.is_arguments_shadowed);

	DUK_ASSERT_TOP(ctx, entry_top);
	return;

 error_argname:
	DUK_ERROR(thr, DUK_ERR_SYNTAX_ERROR, "invalid arg name");
}

/*
 *  Parse a function-body-like expression (FunctionBody or Program
 *  in E5 grammar) using a two-pass parse.  The productions appear
 *  in the following contexts:
 *
 *    - function expression
 *    - function statement
 *    - function declaration
 *    - getter in object literal
 *    - setter in object literal
 *    - global code
 *    - eval code
 *    - Function constructor body
 *
 *  This function only parses the statement list of the body; the argument
 *  list and possible function name must be initialized by the caller.
 *  For instance, for Function constructor, the argument names are originally
 *  on the value stack.  The parsing of statements ends either at an EOF or
 *  a closing brace; this is controlled by an input flag.
 *
 *  Note that there are many differences affecting parsing and even code
 *  generation:
 *
 *    - Global and eval code have an implicit return value generated
 *      by the last statement; function code does not
 *
 *    - Global code, eval code, and Function constructor body end in
 *      an EOF, other bodies in a closing brace ('}')
 *
 *  Upon entry, 'curr_tok' is ignored and the function will pull in the
 *  first token on its own.  Upon exit, 'curr_tok' is the terminating
 *  token (EOF or closing brace).
 */

static void parse_function_body(duk_compiler_ctx *comp_ctx, int expect_eof, int implicit_return_value) {
	duk_compiler_func *func = &comp_ctx->curr_func;
	duk_hthread *thr = comp_ctx->thr;
	duk_context *ctx = (duk_context *) thr;
	int reg_stmt_value = -1;
	duk_lexer_point lex_pt;
	int temp_first;

	DUK_ASSERT(comp_ctx != NULL);
	DUK_ASSERT(func != NULL);

	RECURSION_INCREASE(comp_ctx, thr);

	duk_require_stack(ctx, FUNCTION_BODY_REQUIRE_SLOTS);

	/*
	 *  Store lexer position for a later rewind
	 */

	DUK_LEXER_GETPOINT(&comp_ctx->lex, &lex_pt);

	/*
	 *  Program code (global and eval code) has an implicit return value
	 *  from the last statement value (e.g. eval("1; 2+3;") returns 3).
	 *  This is not the case with functions.  If implicit statement return
	 *  value is requested, all statements are coerced to a register
	 *  allocated here, and used in the implicit return statement below.
	 */

	/* FIXME: this is pointless here because pass 1 is throw-away */
	if (implicit_return_value) {
		reg_stmt_value = ALLOCTEMP(comp_ctx);

		/* If an implicit return value is needed by caller, it must be
		 * initialized to 'undefined' because we don't know whether any
		 * non-empty (where "empty" is a continuation type, and different
		 * from an empty statement) statements will be executed.
		 *
		 * However, since 1st pass is a throwaway one, no need to emit
		 * it here.
		 */
#if 0
		emit_extraop_b_c(comp_ctx, DUK_EXTRAOP_LDUNDEF, 0, 0);
#endif
	}

	/*
	 *  First pass parsing.
	 */

	func->in_directive_prologue = 1;
	func->in_scanning = 1;
	func->may_direct_eval = 0;
	func->id_access_arguments = 0;
	func->id_access_slow = 0;
	func->reg_stmt_value = reg_stmt_value;

	/* Need to set curr_token.t because lexing regexp mode depends on current
	 * token type.  Zero value causes "allow regexp" mode.
	 */
	comp_ctx->curr_token.t = 0;
	advance(comp_ctx);  /* parse_statements() expects curr_tok to be set; parse in "allow regexp literal" mode with current strictness */

	DUK_DDDPRINT("begin 1st pass");
	parse_statements(comp_ctx,
	                 1,             /* allow source elements */
	                 expect_eof);   /* expect EOF instead of } */
	DUK_DDDPRINT("end 1st pass");

	/*
	 *  Rewind lexer.
	 *
	 *  parse_statements() expects curr_tok to be set; parse in "allow regexp
	 *  literal" mode with current strictness.
	 *
	 *  curr_token line number info should be initialized for pass 2 before
	 *  generating prologue, to ensure prologue bytecode gets nice line numbers.
	 */

	DUK_DDDPRINT("rewind lexer");
	DUK_LEXER_SETPOINT(&comp_ctx->lex, &lex_pt);
	comp_ctx->curr_token.t = 0;  /* this is needed for regexp mode */
	advance(comp_ctx);

	/*
	 *  Reset function state and perform register allocation, which creates
	 *  'varmap' for second pass.  Function prologue for variable declarations,
	 *  binding value initializations etc is emitted as a by-product.
	 *
	 *  Strict mode restrictions for duplicate and invalid argument
	 *  names are checked here now that we know whether the function
	 *  is actually strict.  See: test-dev-strict-mode-boundary.js.
	 */

	reset_function_for_pass2(comp_ctx);
	func->in_directive_prologue = 1;
	func->in_scanning = 0;

	/* must be able to emit code, alloc consts, etc. */

	init_varmap_and_prologue_for_pass2(comp_ctx,
	                                 (implicit_return_value ? &reg_stmt_value : NULL));
	func->reg_stmt_value = reg_stmt_value;

	temp_first = GETTEMP(comp_ctx);

	func->temp_first = temp_first;
	func->temp_next = temp_first;
	func->stmt_next = 0;
	func->label_next = 0;

	/* FIXME: init or assert catch depth etc -- all values */
	func->id_access_arguments = 0;
	func->id_access_slow = 0;

	/*
	 *  Check function name validity now that we know strictness.
	 *  This only applies to function declarations and expressions,
	 *  not setter/getter name.
	 *
	 *  See: test-dev-strict-mode-boundary.js
	 */

	if (func->is_function && !func->is_setget && func->h_name != NULL) {
		if (func->is_strict) {
			if (hstring_is_eval_or_arguments(comp_ctx, func->h_name)) {
				DUK_DDDPRINT("func name is 'eval' or 'arguments' in strict mode");
				goto error_funcname;
			}
			if (DUK_HSTRING_HAS_STRICT_RESERVED_WORD(func->h_name)) {
				DUK_DDDPRINT("func name is a reserved word in strict mode");
				goto error_funcname;
			}
		} else {
			if (DUK_HSTRING_HAS_RESERVED_WORD(func->h_name) &&
			    !DUK_HSTRING_HAS_STRICT_RESERVED_WORD(func->h_name)) {
				DUK_DDDPRINT("func name is a reserved word in non-strict mode");
				goto error_funcname;
			}
		}
	}

	/*
	 *  Second pass parsing.
	 */

	if (implicit_return_value) {
		emit_extraop_b_c(comp_ctx, DUK_EXTRAOP_LDUNDEF, 0, 0);
	}

	DUK_DDDPRINT("begin 2nd pass");
	parse_statements(comp_ctx,
	                 1,             /* allow source elements */
	                 expect_eof);   /* expect EOF instead of } */
	DUK_DDDPRINT("end 2nd pass");

	/*
	 *  Emit a final RETURN.
	 *
	 *  It would be nice to avoid emitting an unnecessary "return" opcode
	 *  if the current PC is not reachable.  However, this cannot be reliably
	 *  detected; even if the previous instruction is an unconditional jump,
	 *  there may be a previous jump which jumps to current PC (which is the
	 *  case for iteration and conditional statements, for instance).
	 */

	/* FIXME: request a "last statement is terminal" from parse_statement() and parse_statements();
	 * we could avoid the last RETURN if we could ensure there is no way to get here
	 * (directly or via a jump)
	 */

	DUK_ASSERT(comp_ctx->curr_func.catch_depth == 0);  /* fast returns are always OK here */
	if (reg_stmt_value >= 0) {
		emit_a_b(comp_ctx,
		         DUK_OP_RETURN,
		         DUK_BC_RETURN_FLAG_HAVE_RETVAL | DUK_BC_RETURN_FLAG_FAST /*flags*/,
		         reg_stmt_value /*reg*/);
	} else {
		emit_a_b(comp_ctx,
		         DUK_OP_RETURN,
		         DUK_BC_RETURN_FLAG_FAST /*flags*/,
		         0 /*reg*/);
	}

	/*
	 *  Peephole optimize JUMP chains.
	 */

	peephole_optimize_bytecode(comp_ctx);

	/*
	 *  comp_ctx->curr_func is now ready to be converted into an actual
	 *  function template.
	 */

	RECURSION_DECREASE(comp_ctx, thr);
	return;

 error_funcname:
	DUK_ERROR(thr, DUK_ERR_SYNTAX_ERROR, "invalid function name");
}

/*
 *  Parse a function-like expression:
 *
 *    - function expression
 *    - function declaration
 *    - function statement (non-standard)
 *    - setter/getter
 *
 *  Adds the function to comp_ctx->curr_func function table and returns the
 *  function number.
 *
 *  On entry, curr_token points to:
 *
 *    - the token after 'function' for function expression/declaration/statement
 *    - the token after 'set' or 'get' for setter/getter
 */

/* Parse formals. */
static void parse_function_formals(duk_compiler_ctx *comp_ctx) {
	duk_hthread *thr = comp_ctx->thr;
	duk_context *ctx = (duk_context *) thr;
	int first = 1;
	int n;

	for (;;) {
		if (comp_ctx->curr_token.t == DUK_TOK_RPAREN) {
			break;
		}

		if (first) {
			/* no comma */
			first = 0;
		} else {
			advance_expect(comp_ctx, DUK_TOK_COMMA);
		}

		/* Note: when parsing a formal list in non-strict context, e.g.
		 * "implements" is parsed as an identifier.  When the function is
		 * later detected to be strict, the argument list must be rechecked
		 * against a larger set of reserved words (that of strict mode).
		 * This is handled by ``parse_function_body()``.  Here we recognize
		 * whatever tokens are considered reserved in current strictness
		 * (which is not always enough).
		 */

		if (comp_ctx->curr_token.t != DUK_TOK_IDENTIFIER) {
			DUK_ERROR(thr, DUK_ERR_SYNTAX_ERROR, "expected identifier");
		}
		DUK_ASSERT(comp_ctx->curr_token.t == DUK_TOK_IDENTIFIER);
		DUK_ASSERT(comp_ctx->curr_token.str1 != NULL);
		DUK_DDDPRINT("formal argument: %!O", comp_ctx->curr_token.str1);

		/* FIXME: append primitive */
		duk_push_hstring(ctx, comp_ctx->curr_token.str1);
		n = duk_get_length(ctx, comp_ctx->curr_func.argnames_idx);
		duk_put_prop_index(ctx, comp_ctx->curr_func.argnames_idx, n);

		advance(comp_ctx);  /* eat identifier */
	}
}

/* Parse a function-like expression, assuming that 'comp_ctx->curr_func' is
 * correctly set up.  Assumes that curr_token is just after 'function' (or
 * 'set'/'get' etc).
 */
static void parse_function_like_raw(duk_compiler_ctx *comp_ctx, int is_decl, int is_setget) {
	duk_hthread *thr = comp_ctx->thr;
	duk_context *ctx = (duk_context *) thr;

	DUK_ASSERT(comp_ctx->curr_func.num_formals == 0);
	DUK_ASSERT(comp_ctx->curr_func.is_function == 1);
	DUK_ASSERT(comp_ctx->curr_func.is_eval == 0);
	DUK_ASSERT(comp_ctx->curr_func.is_global == 0);
	DUK_ASSERT(comp_ctx->curr_func.is_setget == is_setget);
	DUK_ASSERT(comp_ctx->curr_func.is_decl == is_decl);

	/*
	 *  Function name (if any)
	 *
	 *  We don't check for prohibited names here, because we don't
	 *  yet know whether the function will be strict.  Function body
	 *  parsing handles this retroactively.
	 *
	 *  For function expressions and declarations function name must
	 *  be an Identifer (excludes reserved words).  For setter/getter
	 *  it is a PropertyName which allows reserved words and also
	 *  strings and numbers (e.g. "{ get 1() { ... } }").
	 */

	if (is_setget) {
		/* PropertyName -> IdentifierName | StringLiteral | NumericLiteral */
		if (comp_ctx->curr_token.t_nores == DUK_TOK_IDENTIFIER ||
		    comp_ctx->curr_token.t == DUK_TOK_STRING) {
			duk_push_hstring(ctx, comp_ctx->curr_token.str1);       /* keep in valstack */
		} else if (comp_ctx->curr_token.t == DUK_TOK_NUMBER) {
			duk_push_number(ctx, comp_ctx->curr_token.num);
			duk_to_string(ctx, -1);
		} else {
			DUK_ERROR(thr, DUK_ERR_SYNTAX_ERROR, "invalid getter/setter name");
		}
		comp_ctx->curr_func.h_name = duk_get_hstring(ctx, -1);  /* borrowed reference */
		DUK_ASSERT(comp_ctx->curr_func.h_name != NULL);
		advance(comp_ctx);
	} else {
		/* Function name is an Identifier (not IdentifierName), but we get
		 * the raw name (not recognizing keywords) here and perform the name
		 * checks only after pass 1.
		 */
		if (comp_ctx->curr_token.t_nores == DUK_TOK_IDENTIFIER) {
			duk_push_hstring(ctx, comp_ctx->curr_token.str1);       /* keep in valstack */
			comp_ctx->curr_func.h_name = duk_get_hstring(ctx, -1);  /* borrowed reference */
			DUK_ASSERT(comp_ctx->curr_func.h_name != NULL);
			advance(comp_ctx);
		} else {
			/* valstack will be unbalanced, which is OK */
			DUK_ASSERT(!is_setget);
			if (is_decl) {
				DUK_ERROR(thr, DUK_ERR_SYNTAX_ERROR, "function name required");
			}
		}
	}

	DUK_DDDPRINT("function name: %!O", comp_ctx->curr_func.h_name);

	/*
	 *  Formal argument list
	 *
	 *  We don't check for prohibited names or for duplicate argument
	 *  names here, becase we don't yet know whether the function will
	 *  be strict.  Function body parsing handles this retroactively.
	 */

	advance_expect(comp_ctx, DUK_TOK_LPAREN);

	parse_function_formals(comp_ctx);

	DUK_ASSERT(comp_ctx->curr_token.t == DUK_TOK_RPAREN);
	advance(comp_ctx);

	/*
	 *  Parse function body
	 */

	parse_function_body(comp_ctx,
	                    0,   /* expect_eof */
	                    0);  /* implicit_return_value */

	/*
	 *  Convert duk_compiler_func to a function template and add it
	 *  to the parent function table.
	 */

	convert_to_function_template(comp_ctx);  /* -> [ ... func ] */
}

/* Parse an inner function, adding the function template to the current function's
 * function table.  Return a function number to be used by the outer function.
 */
static int parse_function_like_fnum(duk_compiler_ctx *comp_ctx, int is_decl, int is_setget) {
	duk_hthread *thr = comp_ctx->thr;
	duk_context *ctx = (duk_context *) thr;
	duk_compiler_func old_func;
	int entry_top;
	int n_funcs;

	/*
	 *  Preliminaries: remember valstack top on entry (to restore it later);
	 *  switch to using a new function.
	 */

	entry_top = duk_get_top(ctx);
	memcpy(&old_func, &comp_ctx->curr_func, sizeof(duk_compiler_func));

	memset(&comp_ctx->curr_func, 0, sizeof(duk_compiler_func));
	init_function_valstack_slots(comp_ctx);
	DUK_ASSERT(comp_ctx->curr_func.num_formals == 0);

	/* inherit initial strictness from parent */
	comp_ctx->curr_func.is_strict = old_func.is_strict;

	comp_ctx->curr_func.is_function = 1;
	comp_ctx->curr_func.is_eval = 0;
	comp_ctx->curr_func.is_global = 0;
	comp_ctx->curr_func.is_setget = is_setget;
	comp_ctx->curr_func.is_decl = is_decl;

	parse_function_like_raw(comp_ctx, is_decl, is_setget);  /* pushes function template */

	/* FIXME: append primitive */
	n_funcs = duk_get_length(ctx, old_func.funcs_idx);

	/* FIXME: placeholder, catches most cases; this limit is actually too tight
	 * because CLOSURE can handle much more.
	 */
	if (n_funcs > 255) {
		DUK_ERROR(comp_ctx->thr, DUK_ERR_INTERNAL_ERROR, "out of funcs");
	}

	(void) duk_put_prop_index(ctx, old_func.funcs_idx, n_funcs);  /* autoincrements length */

	/*
	 *  Cleanup: restore original function, restore valstack state.
	 */
	
	memcpy(&comp_ctx->curr_func, &old_func, sizeof(duk_compiler_func));
	duk_set_top(ctx, entry_top);

	DUK_ASSERT_TOP(ctx, entry_top);

	return n_funcs;
}

#if 0
	/* FIXME: avoid two-pass parsing for parent functions both two passes
	 * (leading to fourfold parsing of second level functions)?  If we want
	 * do fancy parent variable lookups, the parent must be in its second
	 * pass for us to know all statically declared variables and functions.
	 * So perhaps do a single pass if parent is in its first pass, throw
	 * away the results and do two passes on parent's second pass.
	 */
#endif

/*
 *  Compile input string into an executable function template without
 *  arguments.
 *
 *  The string is parsed as the "Program" production of Ecmascript E5.
 *  Compilation context can be either global code or eval code (see E5
 *  Sections 14 and 15.1.2.1).
 *
 *  Input stack:  [ ... sourcecode ]
 *  Output stack: [ ... func_template ]
 */

/* FIXME: source code property */

void duk_js_compile(duk_hthread *thr, int flags) {
	duk_context *ctx = (duk_context *) thr;
	duk_hstring *h_sourcecode;
	duk_compiler_ctx comp_ctx_alloc;
	duk_compiler_ctx *comp_ctx = &comp_ctx_alloc;
	duk_lexer_point lex_pt_alloc;
	duk_lexer_point *lex_pt = &lex_pt_alloc;
	duk_compiler_func *func = &comp_ctx_alloc.curr_func;
	int entry_top;
	int is_strict;
	int is_eval;
	int is_funcexpr;

	DUK_ASSERT(thr != NULL);

	is_eval = (flags & DUK_JS_COMPILE_FLAG_EVAL ? 1 : 0);
	is_strict = (flags & DUK_JS_COMPILE_FLAG_STRICT ? 1 : 0);
	is_funcexpr = (flags & DUK_JS_COMPILE_FLAG_FUNCEXPR ? 1 : 0);

	/*
	 *  Arguments check
	 */

	entry_top = duk_get_top(ctx);
	h_sourcecode = duk_require_hstring(ctx, -1);
	DUK_ASSERT(entry_top >= 1);

	/*
	 *  Init compiler and lexer contexts
	 */

	memset(comp_ctx, 0, sizeof(*comp_ctx));
#ifdef DUK_USE_EXPLICIT_NULL_INIT
	comp_ctx->thr = NULL;
	comp_ctx->h_filename = NULL;
	comp_ctx->prev_token.str1 = NULL;
	comp_ctx->prev_token.str2 = NULL;
	comp_ctx->curr_token.str1 = NULL;
	comp_ctx->curr_token.str2 = NULL;
#endif

	duk_require_stack(ctx, COMPILE_ENTRY_SLOTS);

	duk_push_dynamic_buffer(ctx, 0);       /* entry_top + 0 */
	duk_push_undefined(ctx);               /* entry_top + 1 */
	duk_push_undefined(ctx);               /* entry_top + 2 */
	duk_push_undefined(ctx);               /* entry_top + 3 */
	duk_push_undefined(ctx);               /* entry_top + 4 */

	/* FIXME: h_filename */
	comp_ctx->thr = thr;
	comp_ctx->tok11_idx = entry_top + 1;
	comp_ctx->tok12_idx = entry_top + 2;
	comp_ctx->tok21_idx = entry_top + 3;
	comp_ctx->tok22_idx = entry_top + 4;
	comp_ctx->recursion_limit = DUK_COMPILER_RECURSION_LIMIT;

	DUK_LEXER_INITCTX(&comp_ctx->lex);   /* just zeroes/NULLs */
	comp_ctx->lex.thr = thr;
	comp_ctx->lex.input = DUK_HSTRING_GET_DATA(h_sourcecode);
	comp_ctx->lex.input_length = DUK_HSTRING_GET_BYTELEN(h_sourcecode);
	comp_ctx->lex.slot1_idx = comp_ctx->tok11_idx;
	comp_ctx->lex.slot2_idx = comp_ctx->tok12_idx;
	comp_ctx->lex.buf_idx = entry_top + 0;
	comp_ctx->lex.buf = (duk_hbuffer_dynamic *) duk_get_hbuffer(ctx, entry_top + 0);
	DUK_ASSERT(comp_ctx->lex.buf != NULL);
	DUK_ASSERT(DUK_HBUFFER_HAS_DYNAMIC(comp_ctx->lex.buf));

#if 0  /* not needed */
	memset(lex_pt, 0, sizeof(*lex_pt));
#endif
	lex_pt->offset = 0;
	lex_pt->line = 1;
	DUK_LEXER_SETPOINT(&comp_ctx->lex, lex_pt);    /* fills window */

	/*
	 *  Initialize function state for a zero-argument function
	 */

	init_function_valstack_slots(comp_ctx);
	DUK_ASSERT(func->num_formals == 0);

	if (is_funcexpr) {
		/* funcexpr is now used for Function constructor, anonymous */
	} else {
		duk_push_hstring_stridx(ctx, (is_eval ? DUK_STRIDX_EVAL :
		                                        DUK_STRIDX_GLOBAL));
		func->h_name = duk_get_hstring(ctx, -1);
	}

	/*
	 *  Parse a function body or a function-like expression, depending
	 *  on flags.
	 */

	func->is_strict = is_strict;
	func->is_setget = 0;
	func->is_decl = 0;

	if (is_funcexpr) {
		func->is_function = 1;
		func->is_eval = 0;
		func->is_global = 0;

		advance(comp_ctx);  /* init 'curr_token' */
		advance_expect(comp_ctx, DUK_TOK_FUNCTION);
		(void) parse_function_like_raw(comp_ctx,
		                               0,      /* is_decl */
		                               0);     /* is_setget */
	} else {
		func->is_function = 0;
		func->is_eval = is_eval;
		func->is_global = !is_eval;

		parse_function_body(comp_ctx,
		                    1,             /* expect_eof */
		                    1);            /* implicit_return_value */
	}

	/*
	 *  Convert duk_compiler_func to a function template
	 */

	convert_to_function_template(comp_ctx);

	/*
	 *  Mangle stack for result
	 */

	/* [ ... sourcecode (temps) func ] */

	DUK_ASSERT(entry_top - 1 >= 0);
	duk_replace(ctx, entry_top - 1);  /* replace sourcecode with func */
	duk_set_top(ctx, entry_top);

	/* [ ... func ] */

	DUK_ASSERT_TOP(ctx, entry_top);
}

#line 1 "duk_js_executor.c"
/*
 *  Ecmascript bytecode executor.
 */

/* include removed: duk_internal.h */

/*
 *  Local forward declarations
 */

static void reconfig_valstack(duk_hthread *thr, int act_idx, int retval_count);

/*
 *  Helper for finding the final non-bound function in a "bound function" chain.
 */

/* FIXME: overlap with other helpers, rework */
static duk_hobject *find_nonbound_function(duk_hthread *thr, duk_hobject *func) {
	duk_context *ctx = (duk_context *) thr;
	duk_u32 sanity;

	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(func != NULL);
	DUK_ASSERT(DUK_HOBJECT_HAS_BOUND(func));

	sanity = DUK_HOBJECT_BOUND_CHAIN_SANITY;
	do {	
		if (!DUK_HOBJECT_HAS_BOUND(func)) {
			break;
		}

		duk_push_hobject(ctx, func);
		duk_get_prop_stridx(ctx, -1, DUK_STRIDX_INT_TARGET);
		func = duk_require_hobject(ctx, -1);
		duk_pop_2(ctx);
	} while (--sanity > 0);

	if (sanity == 0) {
		DUK_ERROR(thr, DUK_ERR_INTERNAL_ERROR, "function call bound chain sanity exceeded");
	}

	DUK_ASSERT(!DUK_HOBJECT_HAS_BOUND(func));
	DUK_ASSERT(DUK_HOBJECT_HAS_COMPILEDFUNCTION(func) || DUK_HOBJECT_HAS_NATIVEFUNCTION(func));

	return func;
}

/*
 *  Arithmetic, binary, and logical helpers.
 *
 *  Note: there is no opcode for logical AND or logical OR; this is on
 *  purpose, because the evalution order semantics for them make such
 *  opcodes pretty pointless (short circuiting means they are most
 *  comfortably implemented as jumps).  However, a logical NOT opcode
 *  is useful.
 *
 *  Note: careful with duk_tval pointers here: they are potentially
 *  invalidated by any DECREF and almost any API call.
 */

static double _compute_mod(double d1, double d2) {
	/*
	 *  Ecmascript modulus ('%') does not match IEEE 754 "remainder"
	 *  operation (implemented by remainder() in C99) but does seem
	 *  to match ANSI C fmod().
	 *
	 *  Compare E5 Section 11.5.3 and "man fmod".
	 */

	return fmod(d1, d2);
}

static void _vm_arith_add(duk_hthread *thr, duk_tval *tv_x, duk_tval *tv_y, int idx_z) {
	/*
	 *  Addition operator is different from other arithmetic
	 *  operations in that it also provides string concatenation.
	 *  Hence it is implemented separately.
	 *
	 *  There is a fast path for number addition.  Other cases go
	 *  through potentially multiple coercions as described in the
	 *  E5 specification.  It may be possible to reduce the number
	 *  of coercions, but this must be done carefully to preserve
	 *  the exact semantics.
	 *
	 *  E5 Section 11.6.1.
	 */

	duk_context *ctx = (duk_context *) thr;
	double val;

	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(ctx != NULL);
	DUK_ASSERT(tv_x != NULL);  /* may be reg or const */
	DUK_ASSERT(tv_y != NULL);  /* may be reg or const */
	DUK_ASSERT(idx_z >= 0 && idx_z < duk_get_top(ctx));

	/*
	 *  Fast paths
	 */

	if (DUK_TVAL_IS_NUMBER(tv_x) && DUK_TVAL_IS_NUMBER(tv_y)) {
		duk_tval tv_tmp;
		duk_tval *tv_z;

		val = DUK_TVAL_GET_NUMBER(tv_x) + DUK_TVAL_GET_NUMBER(tv_y);
		DUK_DOUBLE_NORMALIZE_NAN_CHECK(&val);
		DUK_ASSERT(DUK_DOUBLE_IS_NORMALIZED(&val));

		tv_z = &thr->valstack_bottom[idx_z];
		DUK_TVAL_SET_TVAL(&tv_tmp, tv_z);
		DUK_TVAL_SET_NUMBER(tv_z, val);
		DUK_ASSERT(!DUK_TVAL_IS_HEAP_ALLOCATED(tv_z));  /* no need to incref */
		DUK_TVAL_DECREF(thr, &tv_tmp);   /* side effects */
		return;
	}

	/*
	 *  Slow path: potentially requires function calls for coercion
	 */

	duk_push_tval(ctx, tv_x);
	duk_push_tval(ctx, tv_y);
	duk_to_primitive(ctx, -2, DUK_HINT_NONE);  /* side effects -> don't use tv_x, tv_y after */
	duk_to_primitive(ctx, -1, DUK_HINT_NONE);

	if (duk_is_string(ctx, -2) || duk_is_string(ctx, -1)) {
		duk_to_string(ctx, -2);
		duk_to_string(ctx, -1);
		duk_concat(ctx, 2);  /* [... s1 s2] -> [... s1+s2] */
		duk_replace(ctx, idx_z);  /* side effects */
	} else {
		double d1, d2;

		d1 = duk_to_number(ctx, -2);
		d2 = duk_to_number(ctx, -1);
		DUK_ASSERT(duk_is_number(ctx, -2));
		DUK_ASSERT(duk_is_number(ctx, -1));
		DUK_ASSERT(DUK_DOUBLE_IS_NORMALIZED(&d1));
		DUK_ASSERT(DUK_DOUBLE_IS_NORMALIZED(&d2));

		val = d1 + d2;
		DUK_DOUBLE_NORMALIZE_NAN_CHECK(&val);
		DUK_ASSERT(DUK_DOUBLE_IS_NORMALIZED(&val));

		duk_pop_2(ctx);
		duk_push_number(ctx, val);
		duk_replace(ctx, idx_z);  /* side effects */
	}
}

static void _vm_arith_binary_op(duk_hthread *thr, duk_tval *tv_x, duk_tval *tv_y, int idx_z, int opcode) {
	/*
	 *  Arithmetic operations other than '+' have number-only semantics
	 *  and are implemented here.  The separate switch-case here means a
	 *  "double dispatch" of the arithmetic opcode, but saves code space.
	 *
	 *  E5 Sections 11.5, 11.5.1, 11.5.2, 11.5.3, 11.6, 11.6.1, 11.6.2, 11.6.3.
	 */

	duk_context *ctx = (duk_context *) thr;
	duk_tval tv_tmp;
	duk_tval *tv_z;
	double d1, d2, val;

	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(ctx != NULL);
	DUK_ASSERT(tv_x != NULL);  /* may be reg or const */
	DUK_ASSERT(tv_y != NULL);  /* may be reg or const */
	DUK_ASSERT(idx_z >= 0 && idx_z < duk_get_top(ctx));

	if (DUK_TVAL_IS_NUMBER(tv_x) && DUK_TVAL_IS_NUMBER(tv_y)) {
		/* fast path */
		d1 = DUK_TVAL_GET_NUMBER(tv_x);
		d2 = DUK_TVAL_GET_NUMBER(tv_y);
	} else {
		duk_push_tval(ctx, tv_x);
		duk_push_tval(ctx, tv_y);
		d1 = duk_to_number(ctx, -2);  /* side effects */
		d2 = duk_to_number(ctx, -1);
		DUK_ASSERT(duk_is_number(ctx, -2));
		DUK_ASSERT(duk_is_number(ctx, -1));
		DUK_ASSERT(DUK_DOUBLE_IS_NORMALIZED(&d1));
		DUK_ASSERT(DUK_DOUBLE_IS_NORMALIZED(&d2));
		duk_pop_2(ctx);
	}

	switch (opcode) {
	case DUK_OP_SUB: {
		val = d1 - d2;
		break;
	}
	case DUK_OP_MUL: {
		val = d1 * d2;
		break;
	}
	case DUK_OP_DIV: {
		val = d1 / d2;
		break;
	}
	case DUK_OP_MOD: {
		val = _compute_mod(d1, d2);
		break;
	}
	default: {
		val = NAN;  /* should not happen */
		break;
	}
	}

	/* important to use normalized NaN with 8-byte tagged types */
	DUK_DOUBLE_NORMALIZE_NAN_CHECK(&val);
	DUK_ASSERT(DUK_DOUBLE_IS_NORMALIZED(&val));
	
	tv_z = &thr->valstack_bottom[idx_z];
	DUK_TVAL_SET_TVAL(&tv_tmp, tv_z);
	DUK_TVAL_SET_NUMBER(tv_z, val);
	DUK_ASSERT(!DUK_TVAL_IS_HEAP_ALLOCATED(tv_z));  /* no need to incref */
	DUK_TVAL_DECREF(thr, &tv_tmp);   /* side effects */
}


static void _vm_bitwise_binary_op(duk_hthread *thr, duk_tval *tv_x, duk_tval *tv_y, int idx_z, int opcode) {
	/*
	 *  Binary bitwise operations use different coercions (ToInt32, ToUint32)
	 *  depending on the operation.  We coerce the arguments first using
	 *  ToInt32(), and then cast to an 32-bit value if necessary.  Note that
	 *  such casts must be correct even if there is no native 32-bit type
	 *  (e.g., duk_i32 and duk_u32 are 64-bit).
	 *
	 *  E5 Sections 11.10, 11.7.1, 11.7.2, 11.7.3
	 */

	duk_context *ctx = (duk_context *) thr;
	duk_tval tv_tmp;
	duk_tval *tv_z;
	duk_i32 i1, i2;
	double val;

	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(ctx != NULL);
	DUK_ASSERT(tv_x != NULL);  /* may be reg or const */
	DUK_ASSERT(tv_y != NULL);  /* may be reg or const */
	DUK_ASSERT(idx_z >= 0 && idx_z < duk_get_top(ctx));

	duk_push_tval(ctx, tv_x);
	duk_push_tval(ctx, tv_y);
	i1 = duk_to_int32(ctx, -2);
	i2 = duk_to_int32(ctx, -1);
	duk_pop_2(ctx);

	switch (opcode) {
	case DUK_OP_BAND: {
		val = (double) (i1 & i2);
		break;
	}
	case DUK_OP_BOR: {
		val = (double) (i1 | i2);
		break;
	}
	case DUK_OP_BXOR: {
		val = (double) (i1 ^ i2);
		break;
	}
	case DUK_OP_BASL: {
		/* Signed shift, named "arithmetic" (asl) because the result
		 * is signed, e.g. 4294967295 << 1 -> -2.  Note that result
		 * must be masked.
		 */

		duk_u32 u2;
		duk_i32 i3;

		u2 = ((duk_u32) i2) & 0xffffffffU;
		i3 = i1 << (u2 & 0x1f);                 /* E5 Section 11.7.1, steps 7 and 8 */
		i3 = i3 & ((duk_i32) 0xffffffffU);      /* Note: left shift, should mask */
		val = (double) i3;
		break;
	}
	case DUK_OP_BASR: {
		/* signed shift */

		duk_u32 u2;

		u2 = ((duk_u32) i2) & 0xffffffffU;
		val = (double) (i1 >> (u2 & 0x1f));     /* E5 Section 11.7.2, steps 7 and 8 */
		break;
	}
	case DUK_OP_BLSR: {
		/* unsigned shift */

		duk_u32 u1;
		duk_u32 u2;

		u1 = ((duk_u32) i1) & 0xffffffffU;
		u2 = ((duk_u32) i2) & 0xffffffffU;

		val = (double) (u1 >> (u2 & 0x1f));     /* E5 Section 11.7.2, steps 7 and 8 */
		break;
	}
	default: {
		val = (double) 0;  /* should not happen */
		break;
	}
	}

	DUK_ASSERT(!DUK_DOUBLE_IS_NAN(&val));        /* 'val' is never NaN, so no need to normalize */
	DUK_ASSERT(DUK_DOUBLE_IS_NORMALIZED(&val));  /* always normalized */

	tv_z = &thr->valstack_bottom[idx_z];
	DUK_TVAL_SET_TVAL(&tv_tmp, tv_z);
	DUK_TVAL_SET_NUMBER(tv_z, val);
	DUK_ASSERT(!DUK_TVAL_IS_HEAP_ALLOCATED(tv_z));  /* no need to incref */
	DUK_TVAL_DECREF(thr, &tv_tmp);   /* side effects */
}

static void _vm_arith_unary_op(duk_hthread *thr, duk_tval *tv_x, int idx_z, int opcode) {
	/*
	 *  Arithmetic operations other than '+' have number-only semantics
	 *  and are implemented here.  The separate switch-case here means a
	 *  "double dispatch" of the arithmetic opcode, but saves code space.
	 *
	 *  E5 Sections 11.5, 11.5.1, 11.5.2, 11.5.3, 11.6, 11.6.1, 11.6.2, 11.6.3.
	 */

	duk_context *ctx = (duk_context *) thr;
	duk_tval tv_tmp;
	duk_tval *tv_z;
	double d1, val;

	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(ctx != NULL);
	DUK_ASSERT(tv_x != NULL);  /* may be reg or const */
	DUK_ASSERT(idx_z >= 0 && idx_z < duk_get_top(ctx));

	if (DUK_TVAL_IS_NUMBER(tv_x)) {
		/* fast path */
		d1 = DUK_TVAL_GET_NUMBER(tv_x);
	} else {
		duk_push_tval(ctx, tv_x);
		d1 = duk_to_number(ctx, -1);  /* side effects */
		DUK_ASSERT(duk_is_number(ctx, -1));
		DUK_ASSERT(DUK_DOUBLE_IS_NORMALIZED(&d1));
		duk_pop(ctx);
	}

	switch (opcode) {
	case DUK_OP_UNM: {
		val = -d1;
		break;
	}
	case DUK_OP_UNP: {
		val = d1;
		break;
	}
	case DUK_OP_INC: {
		val = d1 + 1.0;
		break;
	}
	case DUK_OP_DEC: {
		val = d1 - 1.0;
		break;
	}
	default: {
		val = NAN;  /* should not happen */
		break;
	}
	}

	DUK_DOUBLE_NORMALIZE_NAN_CHECK(&val);
	DUK_ASSERT(DUK_DOUBLE_IS_NORMALIZED(&val));

	tv_z = &thr->valstack_bottom[idx_z];
	DUK_TVAL_SET_TVAL(&tv_tmp, tv_z);
	DUK_TVAL_SET_NUMBER(tv_z, val);
	DUK_ASSERT(!DUK_TVAL_IS_HEAP_ALLOCATED(tv_z));  /* no need to incref */
	DUK_TVAL_DECREF(thr, &tv_tmp);   /* side effects */
}

static void _vm_bitwise_not(duk_hthread *thr, duk_tval *tv_x, int idx_z) {
	/*
	 *  E5 Section 11.4.8
	 */

	duk_context *ctx = (duk_context *) thr;
	duk_tval tv_tmp;
	duk_tval *tv_z;
	duk_i32 i1, i2;
	double val;

	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(ctx != NULL);
	DUK_ASSERT(tv_x != NULL);  /* may be reg or const */
	DUK_ASSERT(idx_z >= 0 && idx_z < duk_get_top(ctx));

	duk_push_tval(ctx, tv_x);
	i1 = duk_to_int32(ctx, -1);
	duk_pop(ctx);

	i2 = ~i1;
	val = (double) i2;

	DUK_ASSERT(!DUK_DOUBLE_IS_NAN(&val));        /* 'val' is never NaN, so no need to normalize */
	DUK_ASSERT(DUK_DOUBLE_IS_NORMALIZED(&val));  /* always normalized */

	tv_z = &thr->valstack_bottom[idx_z];
	DUK_TVAL_SET_TVAL(&tv_tmp, tv_z);
	DUK_TVAL_SET_NUMBER(tv_z, val);
	DUK_ASSERT(!DUK_TVAL_IS_HEAP_ALLOCATED(tv_z));  /* no need to incref */
	DUK_TVAL_DECREF(thr, &tv_tmp);   /* side effects */
}

static void _vm_logical_not(duk_hthread *thr, duk_tval *tv_x, int idx_z) {
	/*
	 *  E5 Section 11.4.9
	 */

	duk_context *ctx = (duk_context *) thr;
	int val;

	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(ctx != NULL);
	DUK_ASSERT(tv_x != NULL);  /* may be reg or const */
	DUK_ASSERT(idx_z >= 0 && idx_z < duk_get_top(ctx));

	/* FIXME: very awkward */
	duk_push_tval(ctx, tv_x);
	val = duk_to_boolean(ctx, -1);
	duk_pop(ctx);
	duk_push_boolean(ctx, (val ? 0 : 1));
	duk_replace(ctx, idx_z);
}

/*
 *  Longjmp handler for the bytecode executor (and a bunch of static
 *  helpers for it).
 *
 *  Any type of longjmp() can be caught here, including intra-function
 *  longjmp()s like 'break', 'continue', (slow) 'return', 'yield', etc.
 *
 *  Error policy: FIXME.
 *
 *  Returns:
 *    0   restart execution
 *    1   bytecode executor finished
 *    2   rethrow longjmp
 */

/* FIXME: duk_api operations for cross-thread reg manipulation? */
/* FIXME: post-condition: value stack must be correct; for ecmascript functions, clamped to 'nregs' */

#define  LONGJMP_RESTART   0  /* state updated, restart bytecode execution */
#define  LONGJMP_FINISHED  1  /* exit bytecode executor with return value */
#define  LONGJMP_RETHROW   2  /* exit bytecode executor by rethrowing an error to caller */

/* only called when act_idx points to an Ecmascript function */
static void reconfig_valstack(duk_hthread *thr, int act_idx, int retval_count) {
	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(act_idx >= 0);
	DUK_ASSERT(thr->callstack[act_idx].func != NULL);
	DUK_ASSERT(DUK_HOBJECT_IS_COMPILEDFUNCTION(thr->callstack[act_idx].func));
	DUK_ASSERT(thr->callstack[act_idx].idx_retval >= 0);

	thr->valstack_bottom = thr->valstack + thr->callstack[act_idx].idx_bottom;

	/* clamp so that retval is at the top (retval_count == 1) or register just before
	 * intended retval is at the top (retval_count == 0, happens e.g. with 'finally').
	 */
	duk_set_top((duk_context *) thr, 
	            thr->callstack[act_idx].idx_retval -
	            thr->callstack[act_idx].idx_bottom +
	            retval_count);

	/*
	 *  When returning to an Ecmascript function, extend the valstack
	 *  top to 'nregs' always.
	 */

	/* FIXME: checkstack here for nregs + internal spare */

	duk_set_top((duk_context *) thr,
	            ((duk_hcompiledfunction *) thr->callstack[act_idx].func)->nregs);
}

static void handle_catch_or_finally(duk_hthread *thr, int cat_idx, int is_finally) {
	duk_context *ctx = (duk_context *) thr;
	duk_tval tv_tmp;
	duk_tval *tv1;

	DUK_DDDPRINT("handling catch/finally, cat_idx=%d, is_finally=%d",
	             cat_idx, is_finally);

	/*
	 *  Set caught value and longjmp type to catcher regs.
	 */

	DUK_DDDPRINT("writing catch registers: idx_base=%d -> %!T, idx_base+1=%d -> %!T",
	             thr->catchstack[cat_idx].idx_base,
	             &thr->heap->lj.value1,
	             thr->catchstack[cat_idx].idx_base + 1,
	             &thr->heap->lj.value2);

	tv1 = &thr->valstack[thr->catchstack[cat_idx].idx_base];
	DUK_TVAL_SET_TVAL(&tv_tmp, tv1);
	DUK_TVAL_SET_TVAL(tv1, &thr->heap->lj.value1);
	DUK_TVAL_INCREF(thr, tv1);
	DUK_TVAL_DECREF(thr, &tv_tmp);  /* side effects */

	tv1 = &thr->valstack[thr->catchstack[cat_idx].idx_base + 1];
	DUK_TVAL_SET_TVAL(&tv_tmp, tv1);
	DUK_TVAL_SET_NUMBER(tv1, (double) thr->heap->lj.type);
	DUK_ASSERT(!DUK_TVAL_IS_HEAP_ALLOCATED(tv1));   /* no need to incref */
	DUK_TVAL_DECREF(thr, &tv_tmp);  /* side effects */

	/*
	 *  Unwind catchstack and callstack.
	 *
	 *  The 'cat_idx' catcher is always kept, even when executing finally.
	 */

	duk_hthread_catchstack_unwind(thr, cat_idx + 1);
	duk_hthread_callstack_unwind(thr, thr->catchstack[cat_idx].callstack_index + 1);

	/*
	 *  Reconfigure valstack to 'nregs' (this is always the case for
	 *  Ecmascript functions).
	 */

	DUK_ASSERT(thr->callstack_top >= 1);
	DUK_ASSERT(thr->callstack[thr->callstack_top - 1].func != NULL);
	DUK_ASSERT(DUK_HOBJECT_IS_COMPILEDFUNCTION(thr->callstack[thr->callstack_top - 1].func));

	thr->valstack_bottom = thr->valstack + (thr->callstack + thr->callstack_top - 1)->idx_bottom;
	duk_set_top((duk_context *) thr, ((duk_hcompiledfunction *) (thr->callstack + thr->callstack_top - 1)->func)->nregs);

	/*
	 *  Reset PC: resume execution from catch or finally jump slot.
	 */

	(thr->callstack + thr->callstack_top - 1)->pc =
		thr->catchstack[cat_idx].pc_base + (is_finally ? 1 : 0);

	/*
	 *  If entering a 'catch' block which requires an automatic
	 *  catch variable binding, create the lexical environment.
	 *
	 *  The binding is mutable (= writable) but not deletable.
	 *  Step 4 for the catch production in E5 Section 12.14;
	 *  no value is given for CreateMutableBinding 'D' argument,
	 *  which implies the binding is not deletable.
	 */

	if (!is_finally && DUK_CAT_HAS_CATCH_BINDING_ENABLED(&thr->catchstack[cat_idx])) {
		duk_activation *act;
		duk_hobject *new_env;

		DUK_DDDPRINT("catcher has an automatic catch binding");

		/* Note: 'act' is dangerous here because it may get invalidate at many
		 * points, so we re-lookup it multiple times.
		 */
		DUK_ASSERT(thr->callstack_top >= 1);
		act = thr->callstack + thr->callstack_top - 1;

		if (act->lex_env == NULL) {
			DUK_DDDPRINT("delayed environment initialization");

			/* this may have side effects, so re-lookup act */
			duk_js_init_activation_environment_records_delayed(thr, act);
			act = thr->callstack + thr->callstack_top - 1;
		}
		DUK_ASSERT(act->lex_env != NULL);
		DUK_ASSERT(act->func != NULL);

		(void) duk_push_object_helper(ctx,
		                              DUK_HOBJECT_FLAG_EXTENSIBLE |
		                              DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_DECENV),
		                              -1);  /* no prototype, updated below */
		new_env = duk_require_hobject(ctx, -1);
		DUK_ASSERT(new_env != NULL);
		DUK_DDDPRINT("new_env allocated: %!iO", new_env);

		/* Note: currently the catch binding is handled without a register
		 * binding because we don't support dynamic register bindings (they
		 * must be fixed for an entire function).  So, there is no need to
		 * record regbases etc.
		 */

		DUK_ASSERT(thr->catchstack[cat_idx].h_varname != NULL);
		duk_push_hstring(ctx, thr->catchstack[cat_idx].h_varname);
		duk_push_tval(ctx, &thr->heap->lj.value1);
		duk_def_prop(ctx, -3, DUK_PROPDESC_FLAGS_W);  /* writable, not configurable */

		act = thr->callstack + thr->callstack_top - 1;
		DUK_HOBJECT_SET_PROTOTYPE(thr, new_env, act->lex_env);  /* updates refcounts */

		act = thr->callstack + thr->callstack_top - 1;
		act->lex_env = new_env;
		DUK_HOBJECT_INCREF(thr, new_env);  /* reachable through activation */

		DUK_CAT_SET_LEXENV_ACTIVE(&thr->catchstack[cat_idx]);

		duk_pop(ctx);

		DUK_DDDPRINT("new_env finished: %!iO", new_env);
	}

	if (is_finally) {
		DUK_CAT_CLEAR_FINALLY_ENABLED(&thr->catchstack[cat_idx]);
	} else {
		DUK_CAT_CLEAR_CATCH_ENABLED(&thr->catchstack[cat_idx]);
	}
}

static void handle_label(duk_hthread *thr, int cat_idx) {
	/* no callstack changes, no value stack changes */

	/* +0 = break, +1 = continue */
	(thr->callstack + thr->callstack_top - 1)->pc =
		thr->catchstack[cat_idx].pc_base + (thr->heap->lj.type == DUK_LJ_TYPE_CONTINUE ? 1 : 0);

	duk_hthread_catchstack_unwind(thr, cat_idx + 1);  /* keep label catcher */
}

/* Note: called for DUK_LJ_TYPE_YIELD and for DUK_LJ_TYPE_RETURN, when a
 * return terminates a thread and yields to the resumer.
 */
static void handle_yield(duk_hthread *thr, duk_hthread *resumer, int act_idx) {
	duk_tval tv_tmp;
	duk_tval *tv1;

	/* this may also be called for DUK_LJ_TYPE_RETURN; this is OK as long as
	 * lj.value1 is correct.
	 */

	DUK_ASSERT(resumer->callstack[act_idx].func != NULL);
	DUK_ASSERT(DUK_HOBJECT_IS_COMPILEDFUNCTION(resumer->callstack[act_idx].func));  /* resume caller must be an ecmascript func */

	/* FIXME: api primitive */
	DUK_DDDPRINT("resume idx_retval is %d", resumer->callstack[act_idx].idx_retval);

	tv1 = &resumer->valstack[resumer->callstack[act_idx].idx_retval];  /* return value from __duk__.resume() */
	DUK_TVAL_SET_TVAL(&tv_tmp, tv1);
	DUK_TVAL_SET_TVAL(tv1, &thr->heap->lj.value1);
	DUK_TVAL_INCREF(thr, tv1);
	DUK_TVAL_DECREF(thr, &tv_tmp);  /* side effects */

	duk_hthread_callstack_unwind(resumer, act_idx + 1);  /* unwind to 'resume' caller */

	/* no need to unwind catchstack */
	reconfig_valstack(resumer, act_idx, 1);  /* 1 = have retval */

	/* caller must change active thread, and set thr->resumer to NULL */
}

static int handle_longjmp(duk_hthread *thr,
                          duk_hthread *entry_thread,
                          int entry_callstack_top) {
	duk_tval tv_tmp;
	int entry_callstack_index;
	int retval = LONGJMP_RESTART;

	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(entry_thread != NULL);

	entry_callstack_index = entry_callstack_top - 1;

	/* FIXME: assert that 'thr' should be current thread, as no-one resumes
	 * except us, and we switch 'thr' in that case.
	 */

	/*
	 *  (Re)try handling the longjmp.
	 *
	 *  A longjmp handler may convert the longjmp to a different type and
	 *  "virtually" rethrow by goto'ing to 'check_longjmp'.  Before the goto,
	 *  the following must be updated:
	 *    - the heap 'lj' state
	 *    - 'thr' must reflect the "throwing" thread
	 */

 check_longjmp:

	DUK_DDPRINT("handling longjmp: type=%d, value1=%!T, value2=%!T, iserror=%d",
	            thr->heap->lj.type,
	            &thr->heap->lj.value1,
	            &thr->heap->lj.value2,
	            thr->heap->lj.iserror);

	switch (thr->heap->lj.type) {

	case DUK_LJ_TYPE_RESUME: {
		/*
		 *  Note: lj.value1 is 'value', lj.value2 is 'resumee'.
		 *  This differs from YIELD.
		 */

		duk_tval *tv;
		duk_tval *tv2;
		int act_idx;
		duk_hthread *resumee;

		/* duk_builtin_duk_object_yield() and duk_builtin_duk_object_resume() ensure all of these are met */

		DUK_ASSERT(thr->state == DUK_HTHREAD_STATE_RUNNING);                                                         /* unchanged by __duk__.resume() */
		DUK_ASSERT(thr->callstack_top >= 2);                                                                         /* Ecmascript activation + __duk__.resume() activation */
		DUK_ASSERT((thr->callstack + thr->callstack_top - 1)->func != NULL &&
		           DUK_HOBJECT_IS_NATIVEFUNCTION((thr->callstack + thr->callstack_top - 1)->func) &&
		           ((duk_hnativefunction *) (thr->callstack + thr->callstack_top - 1)->func)->func == duk_builtin_duk_object_resume);
		DUK_ASSERT((thr->callstack + thr->callstack_top - 2)->func != NULL &&
		           DUK_HOBJECT_IS_COMPILEDFUNCTION((thr->callstack + thr->callstack_top - 2)->func));                /* an Ecmascript function */
		DUK_ASSERT((thr->callstack + thr->callstack_top - 2)->idx_retval >= 0);                                      /* waiting for a value */

		tv = &thr->heap->lj.value2;  /* resumee */
		DUK_ASSERT(DUK_TVAL_IS_OBJECT(tv));
		DUK_ASSERT(DUK_TVAL_GET_OBJECT(tv) != NULL);
		DUK_ASSERT(DUK_HOBJECT_IS_THREAD(DUK_TVAL_GET_OBJECT(tv)));
		resumee = (duk_hthread *) DUK_TVAL_GET_OBJECT(tv);

		DUK_ASSERT(resumee != NULL);
		DUK_ASSERT(resumee->resumer == NULL);
		DUK_ASSERT(resumee->state == DUK_HTHREAD_STATE_INACTIVE ||
		           resumee->state == DUK_HTHREAD_STATE_YIELDED);                                                     /* checked by __duk__.resume() */
		DUK_ASSERT(resumee->state != DUK_HTHREAD_STATE_YIELDED ||
		           resumee->callstack_top >= 2);                                                                     /* YIELDED: Ecmascript activation + __duk__.yield() activation */
		DUK_ASSERT(resumee->state != DUK_HTHREAD_STATE_YIELDED ||
		           ((resumee->callstack + resumee->callstack_top - 1)->func != NULL &&
		            DUK_HOBJECT_IS_NATIVEFUNCTION((resumee->callstack + resumee->callstack_top - 1)->func) &&
		            ((duk_hnativefunction *) (resumee->callstack + resumee->callstack_top - 1)->func)->func == duk_builtin_duk_object_yield));
		DUK_ASSERT(resumee->state != DUK_HTHREAD_STATE_YIELDED ||
		           ((resumee->callstack + resumee->callstack_top - 2)->func != NULL &&
		            DUK_HOBJECT_IS_COMPILEDFUNCTION((resumee->callstack + resumee->callstack_top - 2)->func)));      /* an Ecmascript function */
		DUK_ASSERT(resumee->state != DUK_HTHREAD_STATE_YIELDED ||
		           (resumee->callstack + resumee->callstack_top - 2)->idx_retval >= 0);                              /* waiting for a value */
		DUK_ASSERT(resumee->state != DUK_HTHREAD_STATE_INACTIVE ||                                                   /* INACTIVE: no activation, single function value on valstack */
		           resumee->callstack_top == 0);
		DUK_ASSERT(resumee->state != DUK_HTHREAD_STATE_INACTIVE ||
		           (resumee->valstack_top == resumee->valstack + 1 &&
		            DUK_TVAL_IS_OBJECT(resumee->valstack_top - 1) &&
		            DUK_HOBJECT_IS_COMPILEDFUNCTION(DUK_TVAL_GET_OBJECT(resumee->valstack_top - 1))));

		if (thr->heap->lj.iserror) {
			/*
			 *  Throw the error in the resumed thread's context; the
			 *  error value is pushed onto the resumee valstack.
			 *
			 *  Note: the callstack of the target may empty in this case
			 *  too (i.e. the target thread has never been resumed).  The
			 *  value stack will contain the initial function in that case,
			 *  which we simply ignore.
			 */

			resumee->resumer = thr;
			resumee->state = DUK_HTHREAD_STATE_RUNNING;
			thr->state = DUK_HTHREAD_STATE_RESUMED;	
			thr->heap->curr_thread = resumee;
			thr = resumee;

			thr->heap->lj.type = DUK_LJ_TYPE_THROW;

			/* thr->heap->lj.value1 is already the value to throw */
			/* thr->heap->lj.value2 is 'thread', will be wiped out at the end */

			DUK_ASSERT(thr->heap->lj.iserror);  /* already set */

			DUK_DDPRINT("-> resume with an error, converted to a throw in the resumee, propagate");
			goto check_longjmp;
		} else if (resumee->state == DUK_HTHREAD_STATE_YIELDED) {
			act_idx = resumee->callstack_top - 2;  /* Ecmascript function */
			DUK_ASSERT(resumee->callstack[act_idx].idx_retval >= 0);

			tv = &resumee->valstack[resumee->callstack[act_idx].idx_retval];  /* return value from __duk__.yield() */
			DUK_ASSERT(tv >= resumee->valstack && tv < resumee->valstack_top);
			tv2 = &thr->heap->lj.value1;
			DUK_TVAL_SET_TVAL(&tv_tmp, tv);
			DUK_TVAL_SET_TVAL(tv, tv2);
			DUK_TVAL_INCREF(thr, tv);
			DUK_TVAL_DECREF(thr, &tv_tmp);  /* side effects */

			duk_hthread_callstack_unwind(resumee, act_idx + 1);  /* unwind to 'yield' caller */

			/* no need to unwind catchstack */

			reconfig_valstack(resumee, act_idx, 1);  /* 1 = have retval */

			resumee->resumer = thr;
			resumee->state = DUK_HTHREAD_STATE_RUNNING;
			thr->state = DUK_HTHREAD_STATE_RESUMED;
			thr->heap->curr_thread = resumee;
#if 0
			thr = resumee;  /* not needed, as we exit right away */
#endif
			DUK_DDPRINT("-> resume with a value, restart execution in resumee");	
			retval = LONGJMP_RESTART;
			goto wipe_and_return;
		} else {
			int call_flags;

			/* resumee: [... initial_func]  (currently actually: [initial_func]) */

			duk_push_undefined((duk_context *) resumee);
			tv = &thr->heap->lj.value1;
			duk_push_tval((duk_context *) resumee, tv);

			/* resumee: [... initial_func undefined(= this) resume_value ] */

			call_flags = DUK_CALL_FLAG_IS_RESUME;  /* is resume, not a tailcall */

			duk_handle_ecma_call_setup(resumee,
			                           1,              /* num_stack_args */
			                           call_flags);    /* call_flags */

			resumee->resumer = thr;
			resumee->state = DUK_HTHREAD_STATE_RUNNING;
			thr->state = DUK_HTHREAD_STATE_RESUMED;
			thr->heap->curr_thread = resumee;
#if 0
			thr = resumee;  /* not needed, as we exit right away */
#endif
			DUK_DDPRINT("-> resume with a value, restart execution in resumee");	
			retval = LONGJMP_RESTART;
			goto wipe_and_return;
		}
		DUK_NEVER_HERE();
		break;  /* never here */
	}

	case DUK_LJ_TYPE_YIELD: {
		/*
		 *  Currently only allowed only if yielding thread has only
		 *  Ecmascript activations (except for the __duk__.yield()
		 *  call at the callstack top) and none of them constructor
		 *  calls.
		 *
		 *  This excludes the 'entry' thread which will always have
		 *  a preventcount > 0.
		 */

		duk_hthread *resumer;

		/* duk_builtin_duk_object_yield() and duk_builtin_duk_object_resume() ensure all of these are met */

		DUK_ASSERT(thr != entry_thread);                                                                             /* __duk__.yield() should prevent */
		DUK_ASSERT(thr->state == DUK_HTHREAD_STATE_RUNNING);                                                         /* unchanged from __duk__.yield() */
		DUK_ASSERT(thr->callstack_top >= 2);                                                                         /* Ecmascript activation + __duk__.yield() activation */
		DUK_ASSERT((thr->callstack + thr->callstack_top - 1)->func != NULL &&
		           DUK_HOBJECT_IS_NATIVEFUNCTION((thr->callstack + thr->callstack_top - 1)->func) &&
		           ((duk_hnativefunction *) (thr->callstack + thr->callstack_top - 1)->func)->func == duk_builtin_duk_object_yield);
		DUK_ASSERT((thr->callstack + thr->callstack_top - 2)->func != NULL &&
		           DUK_HOBJECT_IS_COMPILEDFUNCTION((thr->callstack + thr->callstack_top - 2)->func));                /* an Ecmascript function */
		DUK_ASSERT((thr->callstack + thr->callstack_top - 2)->idx_retval >= 0);                                      /* waiting for a value */

		resumer = thr->resumer;

		DUK_ASSERT(resumer != NULL);
		DUK_ASSERT(resumer->state == DUK_HTHREAD_STATE_RESUMED);                                                     /* written by a previous RESUME handling */
		DUK_ASSERT(resumer->callstack_top >= 2);                                                                     /* Ecmascript activation + __duk__.resume() activation */
		DUK_ASSERT((resumer->callstack + resumer->callstack_top - 1)->func != NULL &&
		           DUK_HOBJECT_IS_NATIVEFUNCTION((resumer->callstack + resumer->callstack_top - 1)->func) &&
		           ((duk_hnativefunction *) (resumer->callstack + resumer->callstack_top - 1)->func)->func == duk_builtin_duk_object_resume);
		DUK_ASSERT((resumer->callstack + resumer->callstack_top - 2)->func != NULL &&
		           DUK_HOBJECT_IS_COMPILEDFUNCTION((resumer->callstack + resumer->callstack_top - 2)->func));        /* an Ecmascript function */
		DUK_ASSERT((resumer->callstack + resumer->callstack_top - 2)->idx_retval >= 0);                              /* waiting for a value */

		if (thr->heap->lj.iserror) {

			thr->state = DUK_HTHREAD_STATE_YIELDED;
			thr->resumer = NULL;
			resumer->state = DUK_HTHREAD_STATE_RUNNING;
			thr->heap->curr_thread = resumer;
			thr = resumer;

			thr->heap->lj.type = DUK_LJ_TYPE_THROW;
			/* lj.value1 is already set */
			DUK_ASSERT(thr->heap->lj.iserror);  /* already set */

			DUK_DDPRINT("-> yield an error, converted to a throw in the resumer, propagate");
			goto check_longjmp;
		} else {
			handle_yield(thr, resumer, resumer->callstack_top - 2);

			thr->state = DUK_HTHREAD_STATE_YIELDED;
			thr->resumer = NULL;
			resumer->state = DUK_HTHREAD_STATE_RUNNING;
			thr->heap->curr_thread = resumer;
#if 0
			thr = resumer;  /* not needed, as we exit right away */
#endif

			DUK_DDPRINT("-> yield a value, restart execution in resumer");
			retval = LONGJMP_RESTART;
			goto wipe_and_return;
		}
		DUK_NEVER_HERE();
		break;  /* never here */
	}

	case DUK_LJ_TYPE_RETURN: {
		/*
		 *  Four possible outcomes:
		 *    * A 'finally' in the same function catches the 'return'.
		 *      (or)
		 *    * The return happens at the entry level of the bytecode
		 *      executor, so return from the executor (in C stack).
		 *      (or)
		 *    * There is a calling (Ecmascript) activation in the call
		 *      stack => return to it.
		 *      (or)
		 *    * There is no calling activation, and the thread is
		 *      terminated.  There is always a resumer in this case,
		 *      which gets the return value similarly to a 'yield'
		 *      (except that the current thread can no longer be
		 *      resumed).
		 */

		duk_tval *tv1;
		duk_hthread *resumer;
		duk_catcher *cat;
		int orig_callstack_index;

		DUK_ASSERT(thr != NULL);
		DUK_ASSERT(thr->callstack_top >= 1);
		DUK_ASSERT(thr->catchstack != NULL);

		/* FIXME: does not work if thr->catchstack is NULL */
		/* FIXME: does not work if thr->catchstack is allocated but lowest pointer */

		cat = thr->catchstack + thr->catchstack_top - 1;  /* may be < thr->catchstack initially */
		orig_callstack_index = thr->callstack_top - 1;

		while (cat >= thr->catchstack) {
			if (cat->callstack_index != orig_callstack_index) {
				break;
			}
			if (DUK_CAT_GET_TYPE(cat) == DUK_CAT_TYPE_TCF &&
			    DUK_CAT_HAS_FINALLY_ENABLED(cat)) {
				/* 'finally' catches */
				handle_catch_or_finally(thr,
				                        cat - thr->catchstack,
				                        1); /* is_finally */

				DUK_DDPRINT("-> return caught by a finally (in the same function), restart execution");
				retval = LONGJMP_RESTART;
				goto wipe_and_return;
			}
			cat--;
		}
		/* if out of catchstack, cat = &thr->catchstack[-1] */

		/* return to calling activation (if any) */

		if (thr == entry_thread &&
		    thr->callstack_top == entry_callstack_top) {
			/* return to the bytecode executor caller */

			duk_push_tval((duk_context *) thr, &thr->heap->lj.value1);

			/* [ ... retval ] */

			DUK_DDPRINT("-> return propagated up to entry level, exit bytecode executor");
			retval = LONGJMP_FINISHED;
			goto wipe_and_return;
		}

		if (thr->callstack_top >= 2) {
			/* there is a caller; it MUST be an Ecmascript caller (otherwise it would
			 * match entry level check)
			 */

			DUK_DDDPRINT("slow return to Ecmascript caller, idx_retval=%d, lj_value1=%!T",
			             (thr->callstack + thr->callstack_top - 2)->idx_retval, &thr->heap->lj.value1);

			DUK_ASSERT(DUK_HOBJECT_IS_COMPILEDFUNCTION((thr->callstack + thr->callstack_top - 2)->func));   /* must be ecmascript */

			tv1 = thr->valstack + (thr->callstack + thr->callstack_top - 2)->idx_retval;
			DUK_TVAL_SET_TVAL(&tv_tmp, tv1);
			DUK_TVAL_SET_TVAL(tv1, &thr->heap->lj.value1);
			DUK_TVAL_INCREF(thr, tv1);
			DUK_TVAL_DECREF(thr, &tv_tmp);  /* side effects */

			DUK_DDDPRINT("return value at idx_retval=%d is %!T",
			             (thr->callstack + thr->callstack_top - 2)->idx_retval,
			             thr->valstack + (thr->callstack + thr->callstack_top - 2)->idx_retval);

			duk_hthread_catchstack_unwind(thr, (cat - thr->catchstack) + 1);  /* leave 'cat' as top catcher (also works if catchstack exhausted) */
			duk_hthread_callstack_unwind(thr, thr->callstack_top - 1);
			reconfig_valstack(thr, thr->callstack_top - 1, 1);    /* new top, i.e. callee */

			DUK_DDPRINT("-> return not caught, restart execution in caller");
			retval = LONGJMP_RESTART;
			goto wipe_and_return;
		}
	
		/* no calling activation, thread finishes (similar to yield) */

		DUK_ASSERT(thr->resumer != NULL);
		DUK_ASSERT(thr->resumer->callstack_top >= 2);  /* Ecmascript activation + __duk__.resume() activation */
		DUK_ASSERT((thr->resumer->callstack + thr->resumer->callstack_top - 1)->func != NULL &&
		           DUK_HOBJECT_IS_NATIVEFUNCTION((thr->resumer->callstack + thr->resumer->callstack_top - 1)->func) &&
		           ((duk_hnativefunction *) (thr->resumer->callstack + thr->resumer->callstack_top - 1)->func)->func == duk_builtin_duk_object_resume);  /* __duk__.resume() */
		DUK_ASSERT((thr->resumer->callstack + thr->resumer->callstack_top - 2)->func != NULL &&
		           DUK_HOBJECT_IS_COMPILEDFUNCTION((thr->resumer->callstack + thr->resumer->callstack_top - 2)->func));  /* an Ecmascript function */
		DUK_ASSERT((thr->resumer->callstack + thr->resumer->callstack_top - 2)->idx_retval >= 0);                        /* waiting for a value */
		DUK_ASSERT(thr->state == DUK_HTHREAD_STATE_RUNNING);
		DUK_ASSERT(thr->resumer->state == DUK_HTHREAD_STATE_RESUMED);

		resumer = thr->resumer;

		handle_yield(thr, resumer, resumer->callstack_top - 2);

		duk_hthread_terminate(thr);  /* updates thread state, minimizes its allocations */
		DUK_ASSERT(thr->state == DUK_HTHREAD_STATE_TERMINATED);

		thr->resumer = NULL;
		resumer->state = DUK_HTHREAD_STATE_RUNNING;
		thr->heap->curr_thread = resumer;
#if 0
		thr = resumer;  /* not needed */
#endif

		DUK_DDPRINT("-> return not caught, thread terminated; handle like yield, restart execution in resumer");
		retval = LONGJMP_RESTART;
		goto wipe_and_return;
	}

	case DUK_LJ_TYPE_BREAK:
	case DUK_LJ_TYPE_CONTINUE: {
		/*
		 *  Find a matching label catcher or 'finally' catcher in
		 *  the same function.
		 *  
		 *  A label catcher must always exist and will match unless
		 *  a 'finally' captures the break/continue first.  It is the
		 *  compiler's responsibility to ensure that labels are used
		 *  correctly.
		 */

		duk_catcher *cat;
		int orig_callstack_index;
		int lj_label;

		cat = thr->catchstack + thr->catchstack_top - 1;
		orig_callstack_index = cat->callstack_index;

		DUK_ASSERT(DUK_TVAL_IS_NUMBER(&thr->heap->lj.value1));
		lj_label = DUK_TVAL_GET_NUMBER(&thr->heap->lj.value1);

		DUK_DDDPRINT("handling break/continue with label=%d, callstack index=%d",
		             lj_label, cat->callstack_index);

		while (cat >= thr->catchstack) {
			if (cat->callstack_index != orig_callstack_index) {
				break;
			}
			DUK_DDDPRINT("considering catcher %d: type=%d label=%d",
			             (int) (cat - thr->catchstack),
			             DUK_CAT_GET_TYPE(cat), DUK_CAT_GET_LABEL(cat));

			if (DUK_CAT_GET_TYPE(cat) == DUK_CAT_TYPE_TCF &&
			    DUK_CAT_HAS_FINALLY_ENABLED(cat)) {
				/* finally catches */
				handle_catch_or_finally(thr,
				                        cat - thr->catchstack,
				                        1); /* is_finally */

				DUK_DDPRINT("-> break/continue caught by a finally (in the same function), restart execution");
				retval = LONGJMP_RESTART;
				goto wipe_and_return;
			}
			if (DUK_CAT_GET_TYPE(cat) == DUK_CAT_TYPE_LABEL &&
			    DUK_CAT_GET_LABEL(cat) == lj_label) {
				/* found label */
				handle_label(thr,
				             cat - thr->catchstack);

				/* FIXME: reset valstack to 'nregs' (or assert it) */

				DUK_DDPRINT("-> break/continue caught by a label catcher (in the same function), restart execution");	
				retval = LONGJMP_RESTART;
				goto wipe_and_return;
			}
			cat--;
		}

		/* should never happen, but be robust */
		DUK_DPRINT("break/continue not caught by anything in the current function (should never happen)");
		goto convert_to_internal_error;
	}

	case DUK_LJ_TYPE_THROW: {
		/*
		 *  Three possible outcomes:
		 *    * A try or finally catcher is found => resume there.
		 *      (or)
		 *    * The error propagates to the bytecode executor entry
		 *      level (and we're in the entry thread) => rethrow
		 *      with a new longjmp(), after restoring the previous
		 *      catchpoint.
		 *    * The error is not caught in the current thread, so
		 *      the thread finishes with an error.  This works like
		 *      a yielded error, except that the thread is finished
		 *      and can no longer be resumed.  (There is always a
		 *      resumer in this case.)
		 *
		 *  Note: until we hit the entry level, there can only be
		 *  Ecmascript activations.
		 */

		duk_catcher *cat;
		duk_hthread *resumer;

		cat = thr->catchstack + thr->catchstack_top - 1;
		while (cat >= thr->catchstack) {
			if (thr == entry_thread &&
			    cat->callstack_index < entry_callstack_index) {
				/* entry level reached */
				break;
			}

			if (DUK_CAT_HAS_CATCH_ENABLED(cat)) {
				/* try catches */
				DUK_ASSERT(DUK_CAT_GET_TYPE(cat) == DUK_CAT_TYPE_TCF);

				handle_catch_or_finally(thr,
				                        cat - thr->catchstack,
				                        0); /* is_finally */

				DUK_DDPRINT("-> throw caught by a 'catch' clause, restart execution");
				retval = LONGJMP_RESTART;
				goto wipe_and_return;
			}

			if (DUK_CAT_HAS_FINALLY_ENABLED(cat)) {
				DUK_ASSERT(DUK_CAT_GET_TYPE(cat) == DUK_CAT_TYPE_TCF);
				DUK_ASSERT(!DUK_CAT_HAS_CATCH_ENABLED(cat));

				handle_catch_or_finally(thr,
				                        cat - thr->catchstack,
				                        1); /* is_finally */

				/* FIXME: reset valstack to 'nregs' (or assert it) */

				DUK_DDPRINT("-> throw caught by a 'finally' clause, restart execution");
				retval = LONGJMP_RESTART;
				goto wipe_and_return;
			}

			cat--;
		}

		if (thr == entry_thread) {
			/* not caught by anything before entry level; rethrow and let the
			 * final catcher unwind everything
			 */
#if 0
			duk_hthread_catchstack_unwind(thr, (cat - thr->catchstack) + 1);  /* leave 'cat' as top catcher (also works if catchstack exhausted) */
			duk_hthread_callstack_unwind(thr, entry_callstack_index + 1);

#endif
			DUK_DDPRINT("-> throw propagated up to entry level, rethrow and exit bytecode executor");
			retval = LONGJMP_RETHROW;
			goto just_return;
			/* Note: MUST NOT wipe_and_return here, as heap->lj must remain intact */
		}

		/* not caught by current thread, thread terminates (yield error to resumer);
		 * note that this may cause a cascade if the resumer terminates with an uncaught
		 * exception etc (this is OK, but needs careful testing)
		 */

		DUK_ASSERT(thr->resumer != NULL);
		DUK_ASSERT(thr->resumer->callstack_top >= 2);  /* Ecmascript activation + __duk__.resume() activation */
		DUK_ASSERT((thr->resumer->callstack + thr->resumer->callstack_top - 1)->func != NULL &&
		           DUK_HOBJECT_IS_NATIVEFUNCTION((thr->resumer->callstack + thr->resumer->callstack_top - 1)->func) &&
		           ((duk_hnativefunction *) (thr->resumer->callstack + thr->resumer->callstack_top - 1)->func)->func == duk_builtin_duk_object_resume);  /* __duk__.resume() */
		DUK_ASSERT((thr->resumer->callstack + thr->resumer->callstack_top - 2)->func != NULL &&
		           DUK_HOBJECT_IS_COMPILEDFUNCTION((thr->resumer->callstack + thr->resumer->callstack_top - 2)->func));  /* an Ecmascript function */

		resumer = thr->resumer;

		/* reset longjmp */

		DUK_ASSERT(thr->heap->lj.type == DUK_LJ_TYPE_THROW);  /* already set */
		/* lj.value1 already set */

		duk_hthread_terminate(thr);  /* updates thread state, minimizes its allocations */
		DUK_ASSERT(thr->state == DUK_HTHREAD_STATE_TERMINATED);

		thr->resumer = NULL;
		resumer->state = DUK_HTHREAD_STATE_RUNNING;
		thr->heap->curr_thread = resumer;
		thr = resumer;
		goto check_longjmp;
	}

	case DUK_LJ_TYPE_NORMAL: {
		DUK_DPRINT("caught DUK_LJ_TYPE_NORMAL, should never happen, treat as internal error");
		goto convert_to_internal_error;
	}

	default: {
		/* should never happen, but be robust */
		DUK_DPRINT("caught unknown longjmp type %d, treat as internal error", (int) thr->heap->lj.type);
		goto convert_to_internal_error;
	}

	}  /* end switch */

	DUK_NEVER_HERE();

 wipe_and_return:
	/* this is not strictly necessary, but helps debugging */
	thr->heap->lj.type = DUK_LJ_TYPE_UNKNOWN;
	thr->heap->lj.iserror = 0;

	DUK_TVAL_SET_TVAL(&tv_tmp, &thr->heap->lj.value1);
	DUK_TVAL_SET_UNDEFINED_UNUSED(&thr->heap->lj.value1);
	DUK_TVAL_DECREF(thr, &tv_tmp);  /* side effects */

	DUK_TVAL_SET_TVAL(&tv_tmp, &thr->heap->lj.value2);
	DUK_TVAL_SET_UNDEFINED_UNUSED(&thr->heap->lj.value2);
	DUK_TVAL_DECREF(thr, &tv_tmp);  /* side effects */

 just_return:
	return retval;

 convert_to_internal_error:
	DUK_ERROR((duk_context *) thr, DUK_ERR_INTERNAL_ERROR, "internal error in bytecode executor longjmp handler");
#if 0
	/* FIXME: could also handle internally */
	thr->heap->lj.type = DUK_LJ_TYPE_THROW;
	goto check_longjmp;
#endif
	DUK_NEVER_HERE();
	return retval;
}

/*
 *  Ecmascript bytecode executor.
 *
 *  Resume execution for the current thread from its current activation.
 *  Returns when execution would return from the entry level activation,
 *  leaving a single return value on top of the stack.  Function calls
 *  and thread resumptions are handled internally.  If an error occurs,
 *  a longjmp() with type DUK_LJ_TYPE_THROW is called on the entry level
 *  setjmp() jmpbuf.
 *
 *  Ecmascript function calls and coroutine resumptions are handled
 *  internally without recursive C calls.  Other function calls are
 *  handled using duk_handle_call(), increasing C recursion depth.
 *
 *  There are many other tricky control flow situations, such as:
 *
 *    - Break and continue (fast and slow)
 *    - Return (fast and slow)
 *    - Error throwing
 *    - Thread resume and yield
 *
 *  For more detailed notes, see doc/execution.txt.
 *
 *  Note: setjmp() and local variables have a nasty interaction,
 *  see execution.txt; non-volatile locals modified after setjmp()
 *  call are not guaranteed to keep their value.
 */

#define  STRICT()       (DUK_HOBJECT_HAS_STRICT(&(fun)->obj))
#define  REG(x)         (thr->valstack_bottom[(x)])
#define  REGP(x)        (&thr->valstack_bottom[(x)])
#define  CONST(x)       (DUK_HCOMPILEDFUNCTION_GET_CONSTS_BASE(fun)[(x)])
#define  CONSTP(x)      (&DUK_HCOMPILEDFUNCTION_GET_CONSTS_BASE(fun)[(x)])
#define  REGCONST(x)    ((x) < DUK_BC_REGLIMIT ? REG((x)) : CONST((x) - DUK_BC_REGLIMIT))
#define  REGCONSTP(x)   ((x) < DUK_BC_REGLIMIT ? REGP((x)) : CONSTP((x) - DUK_BC_REGLIMIT))

#undef _COMPACT_ERRORS  /* FIXME: make this configurable */
                       
#ifdef _COMPACT_ERRORS
#define  INTERNAL_ERROR(msg)  do { \
		goto internal_error; \
	} while (0)
#else
#define  INTERNAL_ERROR(msg)  do { \
		DUK_ERROR(thr, DUK_ERR_INTERNAL_ERROR, (msg)); \
	} while (0)
#endif

void duk_js_execute_bytecode(duk_hthread *entry_thread) {
	/* entry level info */
	int entry_callstack_top;
	int entry_call_recursion_depth;
	duk_jmpbuf *entry_jmpbuf_ptr;

	/* "hot" variables for interpretation -- not volatile, value not guaranteed in setjmp error handling */
	duk_hthread *thr;             /* stable */
	duk_activation *act;          /* semi-stable (ok as long as callstack not resized) */
	duk_hcompiledfunction *fun;   /* stable */
	duk_instr *bcode;             /* stable */
	/* 'consts' is computed on-the-fly */
	/* 'funcs' is quite rarely used, so no local for it */

	/* "hot" temps for interpretation -- not volatile, value not guaranteed in setjmp error handling */
	duk_u32 ins;

	/* jmpbuf */
	duk_jmpbuf jmpbuf;

#ifdef DUK_USE_ASSERTIONS
	int valstack_top_base;    /* valstack top, should match before interpreting each op (no leftovers) */
#endif

	/* FIXME: document assumptions on setjmp and volatile variables
	 * (see duk_handle_call()).
	 */

	/*
	 *  Preliminaries
	 */

	DUK_ASSERT(entry_thread != NULL);
	DUK_ASSERT_REFCOUNT_NONZERO_HEAPHDR((duk_heaphdr *) entry_thread);
	DUK_ASSERT(entry_thread->callstack_top >= 1);  /* at least one activation, ours */
	DUK_ASSERT((entry_thread->callstack + entry_thread->callstack_top - 1)->func != NULL);
	DUK_ASSERT(DUK_HOBJECT_IS_COMPILEDFUNCTION((entry_thread->callstack + entry_thread->callstack_top - 1)->func));

	thr = entry_thread;

	entry_callstack_top = thr->callstack_top;
	entry_call_recursion_depth = thr->heap->call_recursion_depth;
	entry_jmpbuf_ptr = thr->heap->lj.jmpbuf_ptr;

	/* errhandler is kept as is, so no need to store / restore it */

	/*
	 *  Setjmp catchpoint setup.
	 *
	 *  Note: we currently assume that the setjmp() catchpoint is
	 *  not re-entrant (longjmp() cannot be called more than once
	 *  for a single setjmp()).
	 */

 reset_setjmp_catchpoint:

	DUK_ASSERT(thr != NULL);
	thr->heap->lj.jmpbuf_ptr = &jmpbuf;
	DUK_ASSERT(thr->heap->lj.jmpbuf_ptr != NULL);

	if (setjmp(thr->heap->lj.jmpbuf_ptr->jb)) {
		/*
		 *  Note: any local variables accessed here must have their value
		 *  assigned *before* the setjmp() call, OR they must be declared
		 *  volatile.  Otherwise their value is not guaranteed to be correct.
		 *
		 *  'thr' might seem to be a risky variable because it is changed
		 *  for yield and resume.  However, yield and resume are handled
		 *  using longjmp()s.
		 */

		int lj_ret;

		/* FIXME: signalling the need to shrink check (only if unwound) */

		DUK_DDDPRINT("longjmp caught by bytecode executor, thr=%p, curr_thread=%p",
		             thr, (thr && thr->heap) ? thr->heap->curr_thread : NULL);

		/* must be restored here to handle e.g. yields properly */
		thr->heap->call_recursion_depth = entry_call_recursion_depth;

		/* Longjmp callers should not switch threads, the longjmp handler
		 * does that (even for RESUME and YIELD).
		 */

		DUK_ASSERT(thr != NULL);
		DUK_ASSERT(thr == thr->heap->curr_thread);

		/* Switch to caller's setjmp() catcher so that if an error occurs
		 * during error handling, it is always propagated outwards instead
		 * of causing an infinite loop in our own handler.
		 */

		DUK_DDDPRINT("restore jmpbuf_ptr: %p -> %p",
		             (thr && thr->heap ? thr->heap->lj.jmpbuf_ptr : NULL),
		             entry_jmpbuf_ptr);
		thr->heap->lj.jmpbuf_ptr = entry_jmpbuf_ptr;
		/* errhandler not changed, so no restore */

		lj_ret = handle_longjmp(thr, entry_thread, entry_callstack_top);

		if (lj_ret == LONGJMP_RESTART) {
			/*
			 *  Restart bytecode execution, possibly with a changed thread.
			 */
			thr = thr->heap->curr_thread;
			goto reset_setjmp_catchpoint;
		} else if (lj_ret == LONGJMP_RETHROW) {
			/*
			 *  Rethrow error to calling state.
			 */

			/* thread may have changed (e.g. YIELD converted to THROW) */
			thr = thr->heap->curr_thread;

			DUK_ASSERT(thr->heap->lj.jmpbuf_ptr == entry_jmpbuf_ptr);
			/* errhandler is not changed, so no need to restore */

			duk_err_longjmp(thr);
			DUK_NEVER_HERE();
		} else {
			/*
			 *  Return from bytecode executor with a return value.
			 */
			DUK_ASSERT(lj_ret == LONGJMP_FINISHED);

			/* FIXME: return assertions for valstack, callstack, catchstack */

			DUK_ASSERT(thr->heap->lj.jmpbuf_ptr == entry_jmpbuf_ptr);
			/* errhandler is kept as is, so no need to store / restore it */
			return;
		}
		DUK_NEVER_HERE();
	}

	/*
	 *  Restart execution by reloading thread state.
	 *
	 *  Note that 'thr' and any thread configuration may have changed,
	 *  so all local variables are suspect.
	 *
	 *  The number of local variables should be kept to a minimum: if
	 *  the variables are spilled, they will need to be loaded from
	 *  memory anyway.
	 */

 restart_execution:

	/* Lookup current thread; note that we can use 'thr' for this even
	 * though it is not the current thread (any thread will do).
	 */
	thr = thr->heap->curr_thread;

	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(thr->callstack_top >= 1);
	DUK_ASSERT((thr->callstack + thr->callstack_top - 1)->func != NULL);
	DUK_ASSERT(DUK_HOBJECT_IS_COMPILEDFUNCTION((thr->callstack + thr->callstack_top - 1)->func));

	/* FIXME: shrink check flag? */

	/* assume that thr->valstack_bottom has been set-up before getting here */
	act = thr->callstack + thr->callstack_top - 1;
	fun = (duk_hcompiledfunction *) act->func;
	bcode = DUK_HCOMPILEDFUNCTION_GET_CODE_BASE(fun);

	DUK_ASSERT(thr->valstack_top - thr->valstack_bottom >= fun->nregs);
	DUK_ASSERT(thr->valstack_top - thr->valstack_bottom == fun->nregs);  /* XXX: correct? */

	/*
	 *  Bytecode interpreter.
	 *
	 *  The interpreter must be very careful with memory pointers, as
	 *  many pointers are not guaranteed to be 'stable' and may be
	 *  reallocated and relocated on-the-fly quite easily (e.g. by a
	 *  memory allocation or a property access).
	 *
	 *  The following are assumed to have stable pointers:
	 *    - the current thread
	 *    - the current function
	 *    - the bytecode, constant table, inner function table of the
	 *      current function (as they are a part of the function allocation)
	 *
	 *  The following are assumed to have semi-stable pointers:
	 *    - the current activation entry: stable as long as callstack
	 *      is not changed (reallocated by growing or shrinking), or
	 *      by any garbage collection invocation (through finalizers)
	 *    - Note in particular that ANY DECREF can invalidate the
	 *      activation pointer
	 *
	 *  The following are not assumed to have stable pointers at all:
	 *    - the value stack (registers) of the current thread
	 *    - the catch stack of the current thread
	 *
	 *  See execution.txt for discussion.
	 */

	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(act != NULL);
	DUK_ASSERT(fun != NULL);
	DUK_ASSERT(bcode != NULL);

	DUK_DDPRINT("restarting execution, thr %p, act %p (idx %d), fun %p, bcode %p, "
	            "consts %p, funcs %p, lev %d, regbot %d, regtop %d, catchstack_top=%d, "
	            "preventcount=%d",
	            (void *) thr,
	            (void *) act,
	            thr->callstack_top - 1,
	            (void *) fun,
	            (void *) bcode,
	            (void *) DUK_HCOMPILEDFUNCTION_GET_CONSTS_BASE(fun),
	            (void *) DUK_HCOMPILEDFUNCTION_GET_FUNCS_BASE(fun),
	            (int) (thr->callstack_top - 1),
	            (int) (thr->valstack_bottom - thr->valstack),
	            (int) (thr->valstack_top - thr->valstack),
	            (int) thr->catchstack_top,
	            (int) thr->callstack_preventcount);

#ifdef DUK_USE_ASSERTIONS
	valstack_top_base = (int) (thr->valstack_top - thr->valstack);
#endif

	for (;;) {
		DUK_ASSERT(thr->callstack_top >= 1);
		DUK_ASSERT(act == thr->callstack + thr->callstack_top - 1);  /* pointer stability */
		DUK_ASSERT(bcode + act->pc >= DUK_HCOMPILEDFUNCTION_GET_CODE_BASE(fun));
		DUK_ASSERT(bcode + act->pc < DUK_HCOMPILEDFUNCTION_GET_CODE_END(fun));

		DUK_ASSERT(thr->valstack_top - thr->valstack_bottom >= fun->nregs);  /* FIXME == nregs? */
		DUK_ASSERT((int) (thr->valstack_top - thr->valstack) == valstack_top_base);

		/* Because ANY DECREF potentially invalidates 'act' now (through
		 * finalization), we need to re-lookup 'act' in almost every case.
		 *
		 * FIXME:
		 * This is not nice; it would be nice if the program counter was a
		 * behind a stable pointer.  For instance, put a raw bytecode pointer
		 * into duk_hthread struct (not into the callstack); since bytecode
		 * has a stable pointer this would work nicely.  Whenever a call is
		 * made, the bytecode pointer could be backed up as an integer index
		 * to the calling activation.
		 */

		act = thr->callstack + thr->callstack_top - 1;

		DUK_DDDPRINT("executing bytecode: pc=%d ins=0x%08x, op=%d, valstack_top=%d/%d  -->  %!I",
		             act->pc, bcode[act->pc], DUK_DEC_OP(bcode[act->pc]),
		             (int) (thr->valstack_top - thr->valstack),
		             (int) (thr->valstack_end - thr->valstack),
		             bcode[act->pc]);

		ins = bcode[act->pc++];

		switch (DUK_DEC_OP(ins)) {

		case DUK_OP_LDREG: {
			int t;
			duk_tval tv_tmp;
			duk_tval *tv1, *tv2;

			t = DUK_DEC_A(ins); tv1 = REGP(t);
			t = DUK_DEC_BC(ins); tv2 = REGP(t);
			DUK_TVAL_SET_TVAL(&tv_tmp, tv1);
			DUK_TVAL_SET_TVAL(tv1, tv2);
			DUK_TVAL_INCREF(thr, tv1);
			DUK_TVAL_DECREF(thr, &tv_tmp);  /* side effects */
			break;
		}

		/* FIXME: this is not needed by the current compiler
		 * (no support for "spilling").
		 */
		case DUK_OP_STREG: {
			int t;
			duk_tval tv_tmp;
			duk_tval *tv1, *tv2;

			t = DUK_DEC_A(ins); tv1 = REGP(t);
			t = DUK_DEC_BC(ins); tv2 = REGP(t);
			DUK_TVAL_SET_TVAL(&tv_tmp, tv2);
			DUK_TVAL_SET_TVAL(tv2, tv1);
			DUK_TVAL_INCREF(thr, tv2);
			DUK_TVAL_DECREF(thr, &tv_tmp);  /* side effects */
			break;
		}

		case DUK_OP_LDCONST: {
			int t;
			duk_tval tv_tmp;
			duk_tval *tv1, *tv2;

			t = DUK_DEC_A(ins); tv1 = REGP(t);
			t = DUK_DEC_BC(ins); tv2 = CONSTP(t);
			DUK_TVAL_SET_TVAL(&tv_tmp, tv1);
			DUK_TVAL_SET_TVAL(tv1, tv2);
			DUK_TVAL_INCREF(thr, tv2);  /* may be e.g. string */
			DUK_TVAL_DECREF(thr, &tv_tmp);  /* side effects */
			break;
		}

		case DUK_OP_LDINT: {
			int t;
			duk_tval tv_tmp;
			duk_tval *tv1;
			double val;

			t = DUK_DEC_A(ins); tv1 = REGP(t);
			t = DUK_DEC_BC(ins); val = (double) (t - DUK_BC_LDINT_BIAS);
			DUK_TVAL_SET_TVAL(&tv_tmp, tv1);
			DUK_TVAL_SET_NUMBER(tv1, val);
			DUK_TVAL_DECREF(thr, &tv_tmp);  /* side effects */
			break;
		}

		case DUK_OP_LDINTX: {
			int t;
			duk_tval *tv1;
			double val;

			t = DUK_DEC_A(ins); tv1 = REGP(t);
			if (!DUK_TVAL_IS_NUMBER(tv1)) {
				INTERNAL_ERROR("LDINTX target not a number");
			}
			val = DUK_TVAL_GET_NUMBER(tv1) * ((double) (1 << DUK_BC_LDINTX_SHIFT)) +
			      (double) DUK_DEC_BC(ins);
			DUK_TVAL_SET_NUMBER(tv1, val);
			break;
		}

		case DUK_OP_MPUTOBJ: {
			duk_context *ctx = (duk_context *) thr;
			int t;
			duk_tval *tv1;
			duk_hobject *obj;
			int idx;
			int count;

			/* A -> register of target object
			 * B -> first register of key/value pair list
			 * C -> number of key/value pairs
			 */

			t = DUK_DEC_A(ins); tv1 = REGP(t);
			if (!DUK_TVAL_IS_OBJECT(tv1)) {
				INTERNAL_ERROR("MPUTOBJ target not an object");
			}
			obj = DUK_TVAL_GET_OBJECT(tv1);

			idx = (int) DUK_DEC_B(ins);

			count = (int) DUK_DEC_C(ins);

			if (idx < 0 || idx + count * 2 > duk_get_top(ctx)) {
				/* FIXME: improve check; check against nregs, not against top */
				INTERNAL_ERROR("MPUTOBJ out of bounds");
			}

			duk_push_hobject(ctx, obj);

			while (count > 0) {
				/* FIXME: faster initialization (direct access or better primitives) */
				/* FIXME: strictness for put? */

				duk_push_tval(ctx, REGP(idx));
				if (!duk_is_string(ctx, -1)) {
					INTERNAL_ERROR("MPUTOBJ key not a string");
				}
				duk_push_tval(ctx, REGP(idx + 1));  /* -> [... obj key value] */
				duk_put_prop(ctx, -3);  /* -> [... obj] */

				count--;
				idx += 2;
			}

			duk_pop(ctx);  /* [... obj] -> [...] */
			break;
		}

		case DUK_OP_MPUTARR: {
			duk_context *ctx = (duk_context *) thr;
			int t;
			duk_tval *tv1;
			duk_hobject *obj;
			int idx;
			int count;
			duk_u32 arr_idx;

			/* A -> register of target object
			 * B -> first register of value data (start_index, value1, value2, ..., valueN)
			 * C -> number of key/value pairs (N)
			 */

			t = DUK_DEC_A(ins); tv1 = REGP(t);
			if (!DUK_TVAL_IS_OBJECT(tv1)) {
				INTERNAL_ERROR("MPUTARR target not an object");
			}
			obj = DUK_TVAL_GET_OBJECT(tv1);

			idx = (int) DUK_DEC_B(ins);

			count = (int) DUK_DEC_C(ins);

			if (idx < 0 || idx + count + 1 > duk_get_top(ctx)) {
				/* FIXME: improve check; check against nregs, not against top */
				INTERNAL_ERROR("MPUTARR out of bounds");
			}

			tv1 = REGP(idx);
			if (!DUK_TVAL_IS_NUMBER(tv1)) {
				INTERNAL_ERROR("MPUTARR start index not a number");
			}
			arr_idx = (duk_u32) DUK_TVAL_GET_NUMBER(tv1);
			idx++;

			duk_push_hobject(ctx, obj);

			while (count > 0) {
				/* FIXME: faster initialization (direct access or better primitives);
				 * this is particularly bad now because array 'length' special behavior
				 * is invoked on every put.  It would be better to ignore array semantics
				 * and only update 'length' at the end.
				 */
				/* FIXME: strictness for put? */

				duk_push_tval(ctx, REGP(idx));  /* -> [... obj value] */
				duk_put_prop_index(ctx, -2, arr_idx);  /* -> [... obj] */

				count--;
				idx++;
				arr_idx++;
			}

			duk_pop(ctx);  /* [... obj] -> [...] */
			break;
		}

		case DUK_OP_NEW: {
			duk_context *ctx = (duk_context *) thr;
			int a = DUK_DEC_A(ins);
			int b = DUK_DEC_B(ins);
			int c = DUK_DEC_C(ins);
			int i;

			/* A -> target register
			 * B -> start reg: constructor, arg1, ..., argN
			 * C -> num args (N)
			 */

			/* Note: duk_new() will call the constuctor using duk_handle_call().
			 * A constructor call prevents a yield from inside the constructor,
			 * even if the constructor is an Ecmascript function.
			 */

			/* FIXME: unnecessary copying of values?  Just set 'top' to
			 * b + c, and let the return handling fix up the stack frame?
			 */

			duk_push_tval(ctx, REGP(b));
			for (i = 0; i < c; i++) {
				duk_push_tval(ctx, REGP(b + i + 1));
			}
			duk_new(ctx, c);  /* [... constructor arg1 ... argN] -> [retval] */
			DUK_DDDPRINT("NEW -> %!iT", duk_get_tval(ctx, -1));
			duk_replace(ctx, a);
			break;
		}

		case DUK_OP_REGEXP: {
#ifdef DUK_USE_REGEXP_SUPPORT
			duk_context *ctx = (duk_context *) thr;
			int a = DUK_DEC_A(ins);
			int b = DUK_DEC_B(ins);
			int c = DUK_DEC_C(ins);

			/* A -> target register
			 * B -> bytecode (also contains flags)
			 * C -> escaped source
			 */

			duk_push_tval(ctx, REGCONSTP(c));
			duk_push_tval(ctx, REGCONSTP(b));  /* -> [ ... escaped_source bytecode ] */
			duk_regexp_create_instance(ctx);   /* -> [ ... regexp_instance ] */
			DUK_DDDPRINT("regexp instance: %!iT", duk_get_tval(ctx, -1));
			duk_replace(ctx, a);
#else
			/* The compiler should never emit DUK_OP_REGEXP if there is no
			 * regexp support.
			 */
			INTERNAL_ERROR("no regexp support");
#endif

			break;
		}

		case DUK_OP_CSREG: {
			/*
			 *  Assuming a register binds to a variable declared within this
			 *  function (a declarative binding), the 'this' for the call
			 *  setup is always 'undefined'.  E5 Section 10.2.1.1.6.
			 */

			duk_context *ctx = (duk_context *) thr;
			int a = DUK_DEC_A(ins);
			int b = DUK_DEC_B(ins);  /* restricted to regs */

			/* A -> target register (A, A+1) for call setup
			 * B -> register containing target function (not type checked here)
			 */

			/* FIXME: direct manipulation, or duk_replace_tval() */

			/* Note: target registers a and a+1 may overlap with REGP(b).
			 * Careful here.
			 */

			duk_push_tval(ctx, REGP(b));
			duk_replace(ctx, a);
			duk_push_undefined(ctx);
			duk_replace(ctx, a+1);
			break;
		}

		case DUK_OP_GETVAR: {
			duk_context *ctx = (duk_context *) thr;
			int a = DUK_DEC_A(ins);
			int b = DUK_DEC_B(ins);
			duk_tval *tv1;
			duk_hstring *name;

			tv1 = REGCONSTP(b);
			if (!DUK_TVAL_IS_STRING(tv1)) {
				DUK_DDDPRINT("GETVAR not a string: %!T", tv1);
				INTERNAL_ERROR("GETVAR name not a string");
			}
			name = DUK_TVAL_GET_STRING(tv1);
			DUK_DDDPRINT("GETVAR: '%!O'", name);
			(void) duk_js_getvar_activation(thr, act, name, 1 /*throw*/);  /* -> [... val this] */

			duk_pop(ctx);  /* 'this' binding is not needed here */
			duk_replace(ctx, a);
			break;
		}

		case DUK_OP_PUTVAR: {
			int b = DUK_DEC_B(ins);
			int c = DUK_DEC_C(ins);
			duk_tval *tv1;
			duk_hstring *name;

			tv1 = REGCONSTP(b);
			if (!DUK_TVAL_IS_STRING(tv1)) {
				INTERNAL_ERROR("PUTVAR name not a string");
			}
			name = DUK_TVAL_GET_STRING(tv1);

			/* FIXME: putvar takes an duk_tval pointer, which is awkward and
			 * should be reworked.
			 */

			tv1 = REGCONSTP(c);  /* val */
			duk_js_putvar_activation(thr, act, name, tv1, STRICT());
			break;
		}

		case DUK_OP_DECLVAR: {
			duk_context *ctx = (duk_context *) thr;
			int a = DUK_DEC_A(ins);
			int b = DUK_DEC_B(ins);
			int c = DUK_DEC_C(ins);
			duk_tval *tv1;
			duk_hstring *name;
			int prop_flags;
			int is_func_decl;
			int flag_undef_value;
			int flag_func_decl;

			tv1 = REGCONSTP(b);
			if (!DUK_TVAL_IS_STRING(tv1)) {
				INTERNAL_ERROR("DECLVAR name not a string");
			}
			name = DUK_TVAL_GET_STRING(tv1);

			flag_undef_value = a & DUK_BC_DECLVAR_FLAG_UNDEF_VALUE;
			flag_func_decl = a & DUK_BC_DECLVAR_FLAG_FUNC_DECL;

			/* FIXME: declvar takes an duk_tval pointer, which is awkward and
			 * should be reworked.
			 */

			/* Compiler is responsible for selecting property flags (configurability,
			 * writability, etc).
			 */
			prop_flags = a & DUK_PROPDESC_FLAGS_MASK;
			is_func_decl = flag_func_decl;

			if (flag_undef_value) {
				duk_push_undefined(ctx);
			} else {
				duk_push_tval(ctx, REGCONSTP(c));
			}
			tv1 = duk_get_tval(ctx, -1);

			if (duk_js_declvar_activation(thr, act, name, tv1, prop_flags, is_func_decl)) {
				/* already declared, must update binding value */
				tv1 = duk_get_tval(ctx, -1);
				duk_js_putvar_activation(thr, act, name, tv1, STRICT());
			}

			duk_pop(ctx);
			break;
		}

		case DUK_OP_DELVAR: {
			duk_context *ctx = (duk_context *) thr;
			int a = DUK_DEC_A(ins);
			int b = DUK_DEC_B(ins);
			duk_tval *tv1;
			duk_hstring *name;
			int rc;

			tv1 = REGCONSTP(b);
			if (!DUK_TVAL_IS_STRING(tv1)) {
				INTERNAL_ERROR("DELVAR name not a string");
			}
			name = DUK_TVAL_GET_STRING(tv1);
			DUK_DDDPRINT("DELVAR '%!O'", name);
			rc = duk_js_delvar_activation(thr, act, name);

			duk_push_boolean(ctx, rc);
			duk_replace(ctx, a);
			break;
		}

		case DUK_OP_CSVAR: {
			/* 'this' value:
			 * E5 Section 6.b.i
			 *
			 * The only (standard) case where the 'this' binding is non-null is when
			 *   (1) the variable is found in an object environment record, and
			 *   (2) that object environment record is a 'with' block.
			 *
			 */

			duk_context *ctx = (duk_context *) thr;
			int a = DUK_DEC_A(ins);
			int b = DUK_DEC_B(ins);
			duk_tval *tv1;
			duk_hstring *name;

			tv1 = REGCONSTP(b);
			if (!DUK_TVAL_IS_STRING(tv1)) {
				INTERNAL_ERROR("CSVAR name not a string");
			}
			name = DUK_TVAL_GET_STRING(tv1);
			(void) duk_js_getvar_activation(thr, act, name, 1 /*throw*/);  /* -> [... val this] */

			/* Note: target registers a and a+1 may overlap with REGCONSTP(b)
			 * and REGCONSTP(c).  Careful here.
			 */

			duk_replace(ctx, a+1);  /* 'this' binding */
			duk_replace(ctx, a);    /* variable value (function, we hope, not checked here) */
			break;
		}

		case DUK_OP_CLOSURE: {
			duk_context *ctx = (duk_context *) thr;
			int a = DUK_DEC_A(ins);
			int bc = DUK_DEC_BC(ins);
			duk_hobject *fun_temp;

			/* A -> target reg
			 * BC -> inner function index
			 */

			DUK_DDDPRINT("CLOSURE to target register %d, fnum %d (count %d)",
			             a, bc, DUK_HCOMPILEDFUNCTION_GET_FUNCS_COUNT(fun));

			DUK_ASSERT(bc >= 0 && bc < DUK_HCOMPILEDFUNCTION_GET_FUNCS_COUNT(fun));
			fun_temp = DUK_HCOMPILEDFUNCTION_GET_FUNCS_BASE(fun)[bc];
			DUK_ASSERT(fun_temp != NULL);
			DUK_ASSERT(DUK_HOBJECT_IS_COMPILEDFUNCTION(fun_temp));

			DUK_DDDPRINT("CLOSURE: function template is: %p -> %!O", (void *) fun_temp, fun_temp);

			if (act->lex_env == NULL) {
				duk_js_init_activation_environment_records_delayed(thr, act);
			}
			DUK_ASSERT(act->var_env != NULL);
			DUK_ASSERT(act->lex_env != NULL);

			/* functions always have a NEWENV flag, i.e. they get a
			 * new variable declaration environment, so only lex_env
			 * matters here.
			 */
			duk_js_push_closure(thr,
			                    (duk_hcompiledfunction *) fun_temp,
			                    act->var_env,
			                    act->lex_env);
			duk_replace(ctx, a);

			break;
		}

		case DUK_OP_GETPROP: {
			duk_context *ctx = (duk_context *) thr;
			int a = DUK_DEC_A(ins);
			int b = DUK_DEC_B(ins);
			int c = DUK_DEC_C(ins);
			duk_tval *tv_obj;
			duk_tval *tv_key;
			int rc;

			/* A -> target reg
			 * B -> object reg/const (may be const e.g. in "'foo'[1]")
			 * C -> key reg/const
			 */

			tv_obj = REGCONSTP(b);
			tv_key = REGCONSTP(c);
			DUK_DDDPRINT("GETPROP: a=%d obj=%!T, key=%!T", a, REGCONSTP(b), REGCONSTP(c));
			rc = duk_hobject_getprop(thr, tv_obj, tv_key);  /* -> [val] */
			DUK_UNREF(rc);  /* ignore */
			DUK_DDDPRINT("GETPROP --> %!T", duk_get_tval(ctx, -1));
			tv_obj = NULL;  /* invalidated */
			tv_key = NULL;  /* invalidated */

			duk_replace(ctx, a);    /* val */
			break;
		}

		case DUK_OP_PUTPROP: {
			int a = DUK_DEC_A(ins);
			int b = DUK_DEC_B(ins);
			int c = DUK_DEC_C(ins);
			duk_tval *tv_obj;
			duk_tval *tv_key;
			duk_tval *tv_val;
			int rc;

			/* A -> object reg
			 * B -> key reg/const
			 * C -> value reg/const
			 *
			 * Note: intentional difference to register arrangement
			 * of e.g. GETPROP; 'A' must contain a register-only value.
			 */

			tv_obj = REGP(a);
			tv_key = REGCONSTP(b);
			tv_val = REGCONSTP(c);
			DUK_DDDPRINT("PUTPROP: obj=%!T, key=%!T, val=%!T", REGP(a), REGCONSTP(b), REGCONSTP(c));
			rc = duk_hobject_putprop(thr, tv_obj, tv_key, tv_val, STRICT());
			DUK_UNREF(rc);  /* ignore */
			DUK_DDDPRINT("PUTPROP --> obj=%!T, key=%!T, val=%!T", REGP(a), REGCONSTP(b), REGCONSTP(c));
			tv_obj = NULL;  /* invalidated */
			tv_key = NULL;  /* invalidated */
			tv_val = NULL;  /* invalidated */

			break;
		}

		case DUK_OP_DELPROP: {
			duk_context *ctx = (duk_context *) thr;
			int a = DUK_DEC_A(ins);
			int b = DUK_DEC_B(ins);
			int c = DUK_DEC_C(ins);
			duk_tval *tv_obj;
			duk_tval *tv_key;
			int rc;

			/* A -> result reg
			 * B -> object reg
			 * C -> key reg/const
			 */

			tv_obj = REGP(b);
			tv_key = REGCONSTP(c);
			rc = duk_hobject_delprop(thr, tv_obj, tv_key, STRICT());
			tv_obj = NULL;  /* invalidated */
			tv_key = NULL;  /* invalidated */

			duk_push_boolean(ctx, rc);
			duk_replace(ctx, a);    /* result */
			break;
		}

		case DUK_OP_CSPROP: {
			duk_context *ctx = (duk_context *) thr;
			int a = DUK_DEC_A(ins);
			int b = DUK_DEC_B(ins);
			int c = DUK_DEC_C(ins);
			duk_tval *tv_obj;
			duk_tval *tv_key;
			int rc;

			/* E5 Section 11.2.3, step 6.a.i */
			/* E5 Section 10.4.3 */

			/* FIXME: allow object to be a const, e.g. in 'foo'.toString() */

			tv_obj = REGP(b);
			tv_key = REGCONSTP(c);
			rc = duk_hobject_getprop(thr, tv_obj, tv_key);  /* -> [val] */
			DUK_UNREF(rc);  /* unused */
			tv_obj = NULL;  /* invalidated */
			tv_key = NULL;  /* invalidated */

			/* Note: target registers a and a+1 may overlap with REGP(b)
			 * and REGCONSTP(c).  Careful here.
			 */

			duk_push_tval(ctx, REGP(b));  /* [ ... val obj ] */
			duk_replace(ctx, a+1);        /* 'this' binding */
			duk_replace(ctx, a);          /* val */
			break;
		}

		case DUK_OP_ADD:
		case DUK_OP_SUB:
		case DUK_OP_MUL:
		case DUK_OP_DIV:
		case DUK_OP_MOD: {
			int a = DUK_DEC_A(ins);
			int b = DUK_DEC_B(ins);
			int c = DUK_DEC_C(ins);
			int op = DUK_DEC_OP(ins);

			if (op == DUK_OP_ADD) {
				/*
				 *  Handling DUK_OP_ADD this way is more compact (experimentally)
				 *  than a separate case with separate argument decoding.
				 */
				_vm_arith_add(thr, REGCONSTP(b), REGCONSTP(c), a);
			} else {
				_vm_arith_binary_op(thr, REGCONSTP(b), REGCONSTP(c), a, op);
			}
			break;
		}

		/* FIXME: move these into EXTRAOPs? */
		case DUK_OP_UNM:
		case DUK_OP_UNP:
		case DUK_OP_INC:
		case DUK_OP_DEC: {
			int a = DUK_DEC_A(ins);
			int b = DUK_DEC_B(ins);
			int op = DUK_DEC_OP(ins);

			_vm_arith_unary_op(thr, REGCONSTP(b), a, op);
			break;
		}

		case DUK_OP_BAND:
		case DUK_OP_BOR:
		case DUK_OP_BXOR:
		case DUK_OP_BASL:
		case DUK_OP_BLSR:
		case DUK_OP_BASR: {
			int a = DUK_DEC_A(ins);
			int b = DUK_DEC_B(ins);
			int c = DUK_DEC_C(ins);
			int op = DUK_DEC_OP(ins);

			_vm_bitwise_binary_op(thr, REGCONSTP(b), REGCONSTP(c), a, op);
			break;
		}

		case DUK_OP_BNOT: {
			int a = DUK_DEC_A(ins);
			int b = DUK_DEC_B(ins);

			_vm_bitwise_not(thr, REGCONSTP(b), a);
			break;
		}

		case DUK_OP_LNOT: {
			int a = DUK_DEC_A(ins);
			int b = DUK_DEC_B(ins);

			_vm_logical_not(thr, REGCONSTP(b), a);
			break;
		}

		case DUK_OP_EQ:
		case DUK_OP_NEQ: {
			duk_context *ctx = (duk_context *) thr;
			int a = DUK_DEC_A(ins);
			int b = DUK_DEC_B(ins);
			int c = DUK_DEC_C(ins);
			int tmp;

			/* E5 Sections 11.9.1, 11.9.3 */
			tmp = duk_js_equals(thr, REGCONSTP(b), REGCONSTP(c));
			if (DUK_DEC_OP(ins) == DUK_OP_NEQ) {
				tmp = !tmp;
			}
			duk_push_boolean(ctx, tmp);
			duk_replace(ctx, a);
			break;
		}

		case DUK_OP_SEQ:
		case DUK_OP_SNEQ: {
			duk_context *ctx = (duk_context *) thr;
			int a = DUK_DEC_A(ins);
			int b = DUK_DEC_B(ins);
			int c = DUK_DEC_C(ins);
			int tmp;

			/* E5 Sections 11.9.1, 11.9.3 */
			tmp = duk_js_strict_equals(REGCONSTP(b), REGCONSTP(c));
			if (DUK_DEC_OP(ins) == DUK_OP_SNEQ) {
				tmp = !tmp;
			}
			duk_push_boolean(ctx, tmp);
			duk_replace(ctx, a);
			break;
		}

		/* Note: combining comparison ops must be done carefully because
		 * of uncomparable values (NaN): it's not necessarily true that
		 * (x >= y) === !(x < y).  Also, evaluation order matters, and
		 * although it would only seem to affect the compiler this is
		 * actually not the case, because there are also run-time coercions
		 * of the arguments (with potential side effects).
		 *
		 * FIXME: can be combined; check code size.
		 */

		case DUK_OP_GT: {
			duk_context *ctx = (duk_context *) thr;
			int a = DUK_DEC_A(ins);
			int b = DUK_DEC_B(ins);
			int c = DUK_DEC_C(ins);
			int tmp;

			/* x > y  -->  y < x */
			tmp = duk_js_compare_helper(thr,
			                            REGCONSTP(c),  /* y */
			                            REGCONSTP(b),  /* x */
			                            0,             /* eval_left_first */
			                            0);            /* negate */

			duk_push_boolean(ctx, tmp);
			duk_replace(ctx, a);
			break;
		}

		case DUK_OP_GE: {
			duk_context *ctx = (duk_context *) thr;
			int a = DUK_DEC_A(ins);
			int b = DUK_DEC_B(ins);
			int c = DUK_DEC_C(ins);
			int tmp;

			/* x >= y  -->  not (x < y) */
			tmp = duk_js_compare_helper(thr,
			                            REGCONSTP(b),  /* x */
			                            REGCONSTP(c),  /* y */
			                            1,             /* eval_left_first */
			                            1);            /* negate */

			duk_push_boolean(ctx, tmp);
			duk_replace(ctx, a);
			break;
		}

		case DUK_OP_LT: {
			duk_context *ctx = (duk_context *) thr;
			int a = DUK_DEC_A(ins);
			int b = DUK_DEC_B(ins);
			int c = DUK_DEC_C(ins);
			int tmp;

			/* x < y */
			tmp = duk_js_compare_helper(thr,
			                            REGCONSTP(b),  /* x */
			                            REGCONSTP(c),  /* y */
			                            1,             /* eval_left_first */
			                            0);            /* negate */

			duk_push_boolean(ctx, tmp);
			duk_replace(ctx, a);
			break;
		}

		case DUK_OP_LE: {
			duk_context *ctx = (duk_context *) thr;
			int a = DUK_DEC_A(ins);
			int b = DUK_DEC_B(ins);
			int c = DUK_DEC_C(ins);
			int tmp;

			/* x <= y  -->  not (x > y)  -->  not (y < x) */
			tmp = duk_js_compare_helper(thr,
			                            REGCONSTP(c),  /* y */
			                            REGCONSTP(b),  /* x */
			                            0,             /* eval_left_first */
			                            1);            /* negate */

			duk_push_boolean(ctx, tmp);
			duk_replace(ctx, a);
			break;
		}

		case DUK_OP_IF: {
			int a = DUK_DEC_A(ins);
			int b = DUK_DEC_B(ins);
			int tmp;

			tmp = duk_js_toboolean(REGCONSTP(b));
			if (tmp == a) {
				/* if boolean matches A, skip next inst */
				act->pc++;
			} else {
				;
			}
			break;
		}

		case DUK_OP_INSTOF: {
			duk_context *ctx = (duk_context *) thr;
			int a = DUK_DEC_A(ins);
			int b = DUK_DEC_B(ins);
			int c = DUK_DEC_C(ins);
			int tmp;

			tmp = duk_js_instanceof(thr, REGCONSTP(b), REGCONSTP(c));
			duk_push_boolean(ctx, tmp);
			duk_replace(ctx, a);
			break;
		}

		case DUK_OP_IN: {
			duk_context *ctx = (duk_context *) thr;
			int a = DUK_DEC_A(ins);
			int b = DUK_DEC_B(ins);
			int c = DUK_DEC_C(ins);
			int tmp;

			tmp = duk_js_in(thr, REGCONSTP(b), REGCONSTP(c));
			duk_push_boolean(ctx, tmp);
			duk_replace(ctx, a);
			break;
		}

		case DUK_OP_JUMP: {
			int abc = DUK_DEC_ABC(ins);

			act->pc += abc - DUK_BC_JUMP_BIAS;
			break;
		}

		case DUK_OP_RETURN: {
			duk_context *ctx = (duk_context *) thr;
			int a = DUK_DEC_A(ins);
			int b = DUK_DEC_B(ins);
			/* int c = DUK_DEC_C(ins); */

			/* A -> flags
			 * B -> return value reg/const
			 * C -> currently unused: FIXME
			 */

			/* FIXME: fast return not implemented, always do a slow return now */
			if (a & DUK_BC_RETURN_FLAG_FAST && 0 /*FIXME*/) {
				/* fast return: no TCF catchers (but may have e.g. labels) */
				INTERNAL_ERROR("FIXME: fast return unimplemented");
			} else {
				/* slow return */

				DUK_DDDPRINT("SLOWRETURN a=%d b=%d", a, b);

				if (a & DUK_BC_RETURN_FLAG_HAVE_RETVAL) {
					duk_push_tval(ctx, REGCONSTP(b));
				} else {
					duk_push_undefined(ctx);
				}

				duk_err_setup_heap_ljstate(thr, DUK_LJ_TYPE_RETURN);

				DUK_ASSERT(thr->heap->lj.jmpbuf_ptr != NULL);  /* in bytecode executor, should always be set */
				duk_err_longjmp(thr);
				DUK_NEVER_HERE();
			}
			break;
		}

		case DUK_OP_CALL: {
			duk_context *ctx = (duk_context *) thr;
			int a = DUK_DEC_A(ins);
			int b = DUK_DEC_B(ins);
			int c = DUK_DEC_C(ins);
			int call_flags;
			int flag_tailcall;
			int flag_evalcall;
			duk_tval *tv_func;
			duk_hobject *obj_func;        /* target function, possibly a bound function */
			duk_hobject *obj_final_func;  /* final target function, non-bound function */

			/* A -> flags
			 * B -> base register for call (base -> func, base+1 -> this, base+2 -> arg1 ... base+2+N-1 -> argN)
			 * C -> nargs
			 */

			/* these are not necessarily 0 or 1 (may be other non-zero), that's ok */
			flag_tailcall = (a & DUK_BC_CALL_FLAG_TAILCALL);
			flag_evalcall = (a & DUK_BC_CALL_FLAG_EVALCALL);

			tv_func = REGP(b);
			if (!DUK_TVAL_IS_OBJECT(tv_func)) {
				DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, "call target not an object");
			}
			obj_func = DUK_TVAL_GET_OBJECT(tv_func);

			/*
			 *  To determine whether to use an optimized Ecmascript-to-Ecmascript
			 *  call, we need to know whether the final, non-bound function is an
			 *  Ecmascript function.  We need to follow the "bound" chain to do that;
			 *  the "bound" chain will be followed for the second time when calling.
			 *  This overhead only affects bound functions (in particular, helper
			 *  functions should not be called if the immediate target function is
			 *  not bound).
			 * 
			 *  Even so, this awkward solution could be avoided by e.g. replicating
			 *  final, non-bound target function flags to the bound function objects
			 *  (so that a bound function would e.g. have both a "BOUND" flag and
			 *  a "COMPILEDFUNCTION" flag).  Also, bound functions could also keep
			 *  a direct reference to the final non-bound function ("shortcut").
			 */

			if (DUK_HOBJECT_HAS_BOUND(obj_func)) {
				obj_final_func = find_nonbound_function(thr, obj_func);
			} else {
				obj_final_func = obj_func;
			}

			duk_set_top(ctx, b + c + 2);   /* [ ... func this arg1 ... argN ] */

			if (DUK_HOBJECT_IS_COMPILEDFUNCTION(obj_final_func)) {
				/*
				 *  Ecmascript-to-Ecmascript call: avoid C recursion
				 *  by being clever.
				 */

				/* Compared to duk_handle_call():
				 *   - protected call: never
				 *   - ignore recursion limit: never
				 *   - errhandler: never change current
				 */

				/* FIXME: optimize flag handling, by coordinating with bytecode */
				call_flags = 0;
				if (flag_tailcall) {
					call_flags |= DUK_CALL_FLAG_IS_TAILCALL;
				}

				duk_handle_ecma_call_setup(thr,
				                           c,              /* num_stack_args */
				                           call_flags);    /* call_flags */

				/* restart execution -> starts executing new function */
				goto restart_execution;
			} else {
				/*
				 *  Other cases, use C recursion.
				 *
				 *  If a tailcall was requested, we must handle it inline as there will
				 *  be no RETURN in the bytecode.  The RETURN is always a fast one, as
				 *  the compiler won't emit a tailcall otherwise.
				 *
				 *  Direct eval call: (1) call target (before following bound function
				 *  chain) is the built-in eval() function, and (2) call was made with
				 *  the identifier 'eval'.
				 */

				call_flags = 0;  /* not protected, respect reclimit, not constructor */

				if (DUK_HOBJECT_IS_NATIVEFUNCTION(obj_func) &&
				    ((duk_hnativefunction *) obj_func)->func == duk_builtin_global_object_eval) {
					if (flag_evalcall) {
						DUK_DDDPRINT("call target is eval, call identifier was 'eval' -> direct eval");
						call_flags |= DUK_CALL_FLAG_DIRECT_EVAL;
					} else {
						DUK_DDDPRINT("call target is eval, call identifier was not 'eval' -> indirect eval");
					}
				}

				duk_handle_call(thr,
				                c,           /* num_stack_args */
				                call_flags,  /* call_flags */
				                NULL);       /* errhandler (ignored because not protected) */

				/* FIXME: who should restore? */
				duk_set_top(ctx, fun->nregs);

				if (flag_tailcall) {
					DUK_DDDPRINT("tailcall requested but needed to do a recursive call "
					             "instead; now perform a fast return");
					DUK_DDDPRINT("FIXME: SLOW RETURN NOW");

					duk_dup(ctx, b);
					duk_err_setup_heap_ljstate(thr, DUK_LJ_TYPE_RETURN);

					DUK_ASSERT(thr->heap->lj.jmpbuf_ptr != NULL);  /* in bytecode executor, should always be set */
					duk_err_longjmp(thr);
					DUK_NEVER_HERE();
				}

				/* must reinit setjmp() catchpoint */  /* FIXME: why */
				goto reset_setjmp_catchpoint;
			}

			DUK_NEVER_HERE();
			break;
		}

		case DUK_OP_LABEL: {
			duk_catcher *cat;
			int abc = DUK_DEC_ABC(ins);

			/* allocate catcher and populate it (should be atomic) */

			duk_hthread_catchstack_grow(thr);
			cat = &thr->catchstack[thr->catchstack_top];
			thr->catchstack_top++;

			cat->flags = DUK_CAT_TYPE_LABEL | (abc << DUK_CAT_LABEL_SHIFT);
			cat->callstack_index = thr->callstack_top - 1;
			cat->pc_base = act->pc;  /* pre-incremented, points to first jump slot */
			cat->idx_base = 0;  /* unused for label */
			cat->h_varname = NULL;

			DUK_DDDPRINT("LABEL catcher: flags=0x%08x, callstack_index=%d, pc_base=%d, idx_base=%d, h_varname=%!O, label_id=%d",
			             cat->flags, cat->callstack_index, cat->pc_base, cat->idx_base, cat->h_varname,
			             DUK_CAT_GET_LABEL(cat));

			act->pc += 2;  /* skip jump slots */
			break;
		}

		case DUK_OP_ENDLABEL: {
			duk_catcher *cat;
#if defined(DUK_USE_DDDEBUG) || defined(DUK_USE_ASSERTIONS)
			int abc = DUK_DEC_ABC(ins);
			DUK_DDDPRINT("ENDLABEL %d", abc);
#endif

			DUK_ASSERT(thr->catchstack_top >= 1);

			cat = &thr->catchstack[thr->catchstack_top - 1];
			DUK_UNREF(cat);
			DUK_ASSERT(DUK_CAT_GET_TYPE(cat) == DUK_CAT_TYPE_LABEL);
			DUK_ASSERT(DUK_CAT_GET_LABEL(cat) == abc);

			duk_hthread_catchstack_unwind(thr, thr->catchstack_top - 1);
			break;
		}

		case DUK_OP_BREAK: {
			duk_context *ctx = (duk_context *) thr;
			int abc = DUK_DEC_ABC(ins);

			/* always the "slow break" variant (longjmp'ing); a "fast break" is
			 * simply an DUK_OP_JUMP.
			 */

			DUK_DDDPRINT("BREAK: %d", abc);

			duk_push_int(ctx, abc);
			duk_err_setup_heap_ljstate(thr, DUK_LJ_TYPE_BREAK);

			DUK_ASSERT(thr->heap->lj.jmpbuf_ptr != NULL);  /* always in executor */
			duk_err_longjmp(thr);

			DUK_NEVER_HERE();
			break;
		}

		case DUK_OP_CONTINUE: {
			duk_context *ctx = (duk_context *) thr;
			int abc = DUK_DEC_ABC(ins);

			/* always the "slow continue" variant (longjmp'ing); a "fast continue" is
			 * simply an DUK_OP_JUMP.
			 */

			DUK_DDDPRINT("CONTINUE: %d", abc);

			duk_push_int(ctx, abc);
			duk_err_setup_heap_ljstate(thr, DUK_LJ_TYPE_CONTINUE);

			DUK_ASSERT(thr->heap->lj.jmpbuf_ptr != NULL);  /* always in executor */
			duk_err_longjmp(thr);

			DUK_NEVER_HERE();
			break;
		}

		case DUK_OP_TRYCATCH: {
			duk_context *ctx = (duk_context *) thr;
			duk_catcher *cat;
			duk_tval *tv1;
			int a, b, c;

			/* A -> flags
			 * B -> reg_catch; base register for 2 regs
			 * C -> semantics depend on flags: var_name or with_target
			 *
			 *      If DUK_BC_TRYCATCH_FLAG_CATCH_BINDING set:
			 *          C is constant index for catch binding variable name.
			 *          Automatic declarative environment is established for
			 *          the duration of the 'catch' clause.
			 *
			 *      If DUK_BC_TRYCATCH_FLAG_WITH_BINDING set:
			 *          C is reg/const index for with 'target value', which
			 *          is coerced to an object and then used as a binding
			 *          object for an environment record.  The binding is
			 *          initialized here, for the 'try' clause.
			 *
			 * Note that a TRYCATCH generated for a 'with' statement has no
			 * catch or finally parts.
			 */

			/* FIXME: side effect handling is quite awkward here */

			DUK_DDDPRINT("TRYCATCH: reg_catch=%d, var_name/with_target=%d, have_catch=%d, have_finally=%d, catch_binding=%d, with_binding=%d (flags=0x%02x)",
			             DUK_DEC_B(ins),
			             DUK_DEC_C(ins),
			             (DUK_DEC_A(ins) & DUK_BC_TRYCATCH_FLAG_HAVE_CATCH ? 1 : 0),
			             (DUK_DEC_A(ins) & DUK_BC_TRYCATCH_FLAG_HAVE_FINALLY ? 1 : 0),
			             (DUK_DEC_A(ins) & DUK_BC_TRYCATCH_FLAG_CATCH_BINDING ? 1 : 0),
			             (DUK_DEC_A(ins) & DUK_BC_TRYCATCH_FLAG_WITH_BINDING ? 1 : 0),
			             DUK_DEC_A(ins));

			a = DUK_DEC_A(ins);
			b = DUK_DEC_B(ins);
			c = DUK_DEC_C(ins);

			DUK_ASSERT(thr->callstack_top >= 1);
			DUK_ASSERT(thr->catchstack_top + 1 <= thr->catchstack_size);

			/* with target must be created first, in case we run out of memory */
			/* FIXME: refactor out? */

			if (a & DUK_BC_TRYCATCH_FLAG_WITH_BINDING) {
				DUK_DDDPRINT("need to initialize a with binding object");

				if (act->lex_env == NULL) {
					DUK_DDDPRINT("delayed environment initialization");

					/* must relookup act in case of side effects */
					duk_js_init_activation_environment_records_delayed(thr, act);
					act = thr->callstack + thr->callstack_top - 1;
				}
				DUK_ASSERT(act->lex_env != NULL);

				(void) duk_push_object_helper(ctx,
				                              DUK_HOBJECT_FLAG_EXTENSIBLE |
				                              DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_OBJENV),
				                              -1);  /* no prototype, updated below */

				duk_push_tval(ctx, REGCONSTP(c));
				duk_to_object(ctx, -1);
				duk_dup(ctx, -1);

				/* [ ... env target ] */
				/* [ ... env target target ] */

				duk_def_prop_stridx(thr, -3, DUK_STRIDX_INT_TARGET, DUK_PROPDESC_FLAGS_NONE);
				duk_def_prop_stridx(thr, -2, DUK_STRIDX_INT_THIS, DUK_PROPDESC_FLAGS_NONE);  /* always provideThis=true */

				/* [ ... env ] */

				DUK_DDDPRINT("environment for with binding: %!iT", duk_get_tval(ctx, -1));
			}

			/* allocate catcher and populate it (should be atomic) */

			duk_hthread_catchstack_grow(thr);
			cat = &thr->catchstack[thr->catchstack_top];
			thr->catchstack_top++;

			cat->flags = DUK_CAT_TYPE_TCF;
			cat->h_varname = NULL;

			if (a & DUK_BC_TRYCATCH_FLAG_HAVE_CATCH) {
				cat->flags |= DUK_CAT_FLAG_CATCH_ENABLED;
			}
			if (a & DUK_BC_TRYCATCH_FLAG_HAVE_FINALLY) {
				cat->flags |= DUK_CAT_FLAG_FINALLY_ENABLED;
			}
			if (a & DUK_BC_TRYCATCH_FLAG_CATCH_BINDING) {
				DUK_DDDPRINT("catch binding flag set to catcher");
				cat->flags |= DUK_CAT_FLAG_CATCH_BINDING_ENABLED;
				tv1 = CONSTP(c);
				DUK_ASSERT(DUK_TVAL_IS_STRING(tv1));
				cat->h_varname = DUK_TVAL_GET_STRING(tv1);
			} else if (a & DUK_BC_TRYCATCH_FLAG_WITH_BINDING) {
				/* env created above to stack top */
				duk_hobject *new_env;

				DUK_DDDPRINT("lexenv active flag set to catcher");
				cat->flags |= DUK_CAT_FLAG_LEXENV_ACTIVE;

				DUK_DDDPRINT("activating object env: %!iT", duk_get_tval(ctx, -1));
				DUK_ASSERT(act->lex_env != NULL);
				new_env = duk_get_hobject(ctx, -1);
				DUK_ASSERT(new_env != NULL);

				act = thr->callstack + thr->callstack_top - 1;  /* FIXME: relookup, awkward */
				DUK_HOBJECT_SET_PROTOTYPE(thr, new_env, act->lex_env);  /* updates refcount */

				act = thr->callstack + thr->callstack_top - 1;  /* FIXME: relookup, awkward */
				act->lex_env = new_env;
				DUK_HOBJECT_INCREF(thr, new_env);
				duk_pop(ctx);
			} else {
				;
			}

			cat = thr->catchstack + thr->catchstack_top - 1;  /* FIXME: relookup, awkward */
			cat->callstack_index = thr->callstack_top - 1;
			cat->pc_base = act->pc;  /* pre-incremented, points to first jump slot */
			cat->idx_base = (int) (thr->valstack_bottom - thr->valstack) + b;

			DUK_DDDPRINT("TRYCATCH catcher: flags=0x%08x, callstack_index=%d, pc_base=%d, idx_base=%d, h_varname=%!O",
			             cat->flags, cat->callstack_index, cat->pc_base, cat->idx_base, cat->h_varname);

			act->pc += 2;  /* skip jump slots */
			break;
		}

		case DUK_OP_EXTRA: {
			/* FIXME: shared decoding of 'b' and 'c'? */

			switch (DUK_DEC_A(ins)) {

			case DUK_EXTRAOP_NOP: {
				/* nop */
				break;
			}

			case DUK_EXTRAOP_LDTHIS: {
				/* Note: 'this' may be bound to any value, not just an object */
				int b = DUK_DEC_B(ins);
				duk_tval tv_tmp;
				duk_tval *tv1, *tv2;

				tv1 = REGP(b);
				tv2 = thr->valstack_bottom - 1;  /* 'this binding' is just under bottom */
				DUK_ASSERT(tv2 >= thr->valstack);

				DUK_DDDPRINT("LDTHIS: %!T to r%d", tv2, b);

				DUK_TVAL_SET_TVAL(&tv_tmp, tv1);
				DUK_TVAL_SET_TVAL(tv1, tv2);
				DUK_TVAL_INCREF(thr, tv1);
				DUK_TVAL_DECREF(thr, &tv_tmp);  /* side effects */
				break;
			}

			case DUK_EXTRAOP_LDUNDEF: {
				int b = DUK_DEC_B(ins);
				duk_tval tv_tmp;
				duk_tval *tv1;

				tv1 = REGP(b);
				DUK_TVAL_SET_TVAL(&tv_tmp, tv1);
				DUK_TVAL_SET_UNDEFINED_ACTUAL(tv1);
				DUK_TVAL_DECREF(thr, &tv_tmp);  /* side effects */
				break;
			}

			case DUK_EXTRAOP_LDNULL: {
				int b = DUK_DEC_B(ins);
				duk_tval tv_tmp;
				duk_tval *tv1;

				tv1 = REGP(b);
				DUK_TVAL_SET_TVAL(&tv_tmp, tv1);
				DUK_TVAL_SET_NULL(tv1);
				DUK_TVAL_DECREF(thr, &tv_tmp);  /* side effects */
				break;
			}

			case DUK_EXTRAOP_LDBOOL: {
				int b = DUK_DEC_B(ins);
				int c = DUK_DEC_C(ins);
				duk_tval tv_tmp;
				duk_tval *tv1;

				DUK_ASSERT(c == 0 || c == 1);

				tv1 = REGP(b);
				DUK_TVAL_SET_TVAL(&tv_tmp, tv1);
				DUK_TVAL_SET_BOOLEAN(tv1, c);
				DUK_TVAL_DECREF(thr, &tv_tmp);  /* side effects */
				break;
			}

			case DUK_EXTRAOP_NEWOBJ: {
				duk_context *ctx = (duk_context *) thr;
				int b = DUK_DEC_B(ins);

				duk_push_object(ctx);
				duk_replace(ctx, b);
				break;
			}

			case DUK_EXTRAOP_NEWARR: {
				duk_context *ctx = (duk_context *) thr;
				int b = DUK_DEC_B(ins);

				duk_push_array(ctx);
				duk_replace(ctx, b);
				break;
			}

			case DUK_EXTRAOP_SETALEN: {
				int t;
				duk_tval *tv1;
				duk_hobject *h;
				duk_u32 len;

				t = DUK_DEC_B(ins); tv1 = REGP(t);
				DUK_ASSERT(DUK_TVAL_IS_OBJECT(tv1));
				h = DUK_TVAL_GET_OBJECT(tv1);

				t = DUK_DEC_C(ins); tv1 = REGP(t);
				DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv1));
				len = (duk_u32) DUK_TVAL_GET_NUMBER(tv1);

				duk_hobject_set_length(thr, h, len);

				break;
			}

			case DUK_EXTRAOP_TYPEOF: {
				duk_context *ctx = (duk_context *) thr;
				int b = DUK_DEC_B(ins);
				int c = DUK_DEC_C(ins);
				duk_push_hstring(ctx, duk_js_typeof(thr, REGCONSTP(c)));
				duk_replace(ctx, b);
				break;
			}

			case DUK_EXTRAOP_TYPEOFID: {
				duk_context *ctx = (duk_context *) thr;
				int b = DUK_DEC_B(ins);
				int c = DUK_DEC_C(ins);
				duk_hstring *name;
				duk_tval *tv;

				/* B -> target register
				 * C -> constant index of identifier name
				 */

				tv = REGCONSTP(c);  /* FIXME: this could be a CONSTP instead */
				DUK_ASSERT(DUK_TVAL_IS_STRING(tv));
				name = DUK_TVAL_GET_STRING(tv);
				if (duk_js_getvar_activation(thr, act, name, 0 /*throw*/)) {
					/* -> [... val this] */
					tv = duk_get_tval(ctx, -2);
					duk_push_hstring(ctx, duk_js_typeof(thr, tv));
					duk_replace(ctx, b);
					duk_pop_2(ctx);
				} else {
					/* unresolvable, no stack changes */
					duk_push_hstring_stridx(ctx, DUK_STRIDX_UNDEFINED);
					duk_replace(ctx, b);
				}

				break;
			}

			case DUK_EXTRAOP_TONUM: {
				duk_context *ctx = (duk_context *) thr;
				int b = DUK_DEC_B(ins);
				int c = DUK_DEC_C(ins);
				duk_dup(ctx, c);
				duk_to_number(ctx, -1);
				duk_replace(ctx, b);
				break;
			}

			case DUK_EXTRAOP_INITENUM: {
				duk_context *ctx = (duk_context *) thr;
				int b = DUK_DEC_B(ins);
				int c = DUK_DEC_C(ins);

				/*
				 *  Enumeration semantics come from for-in statement, E5 Section 12.6.4.
				 *  If called with 'null' or 'undefined', this opcode returns 'null' as
				 *  the enumerator, which is special cased in NEXTENUM.  This simplifies
				 *  the compiler part
				 */

				/* B -> register for writing enumerator object
				 * C -> value to be enumerated (expect a register)
				 */

				if (duk_is_null_or_undefined(ctx, c)) {
					duk_push_null(ctx);
					duk_replace(ctx, b);
				} else {
					duk_dup(ctx, c);
					duk_to_object(ctx, -1);
					duk_hobject_enumerator_create(ctx, 0 /*enum_flags*/);  /* [ ... val ] --> [ ... enum ] */
					duk_replace(ctx, b);
				}
				break;
			}

			case DUK_EXTRAOP_NEXTENUM: {
				duk_context *ctx = (duk_context *) thr;
				int b = DUK_DEC_B(ins);
				int c = DUK_DEC_C(ins);

				/*
				 *  NEXTENUM checks whether the enumerator still has unenumerated
				 *  keys.  If so, the next key is loaded to the target register
				 *  and the next instruction is skipped.  Otherwise the next instruction
				 *  will be executed, jumping out of the enumeration loop.
				 */

				/* B -> target register for next key
				 * C -> enum register
				 */

				DUK_DDDPRINT("NEXTENUM: b->%!T, c->%!T", duk_get_tval(ctx, b), duk_get_tval(ctx, c));

				if (duk_is_object(ctx, c)) {
					/* XXX: assert 'c' is an enumerator */
					duk_dup(ctx, c);
					if (duk_hobject_enumerator_next(ctx, 0 /*get_value*/)) {
						/* [ ... enum ] -> [ ... next_key ] */
						DUK_DDDPRINT("enum active, next key is %!T, skip jump slot ", duk_get_tval(ctx, -1));
						act->pc++;;
					} else {
						/* [ ... enum ] -> [ ... ] */
						DUK_DDDPRINT("enum finished, execute jump slot");
						duk_push_undefined(ctx);
					}
					duk_replace(ctx, b);
				} else {
					/* 'null' enumerator case -> behave as with an empty enumerator */
					DUK_ASSERT(duk_is_null(ctx, c));
					DUK_DDDPRINT("enum is null, execute jump slot");
				}
				break;				
			}

			case DUK_EXTRAOP_INITSET:
			case DUK_EXTRAOP_INITGET: {
				duk_context *ctx = (duk_context *) thr;
				int a = DUK_DEC_A(ins);  /* extraop */
				int b = DUK_DEC_B(ins);
				int c = DUK_DEC_C(ins);

				/* B -> object register
				 * C -> C+0 contains key, C+1 closure (value)
				 */

				/*
				 *  INITSET/INITGET are only used to initialize object literal keys.
				 *  The compiler ensures that there cannot be a previous data property
				 *  of the same name.  It also ensures that setter and getter can only
				 *  be initialized once (or not at all).
				 */

				/* FIXME: this is now a very unoptimal implementation -- this can be
				 * made very simple by direct manipulation of the object internals,
				 * given the guarantees above.
				 */

				duk_push_hobject(ctx, thr->builtins[DUK_BIDX_OBJECT_CONSTRUCTOR]);
				duk_get_prop_stridx(ctx, -1, DUK_STRIDX_DEFINE_PROPERTY);
				duk_push_undefined(ctx);
				duk_dup(ctx, b);
				duk_dup(ctx, c + 0);
				duk_push_object(ctx);  /* -> [ Object defineProperty undefined obj key desc ] */

				duk_push_true(ctx);
				duk_put_prop_stridx(ctx, -2, DUK_STRIDX_ENUMERABLE);
				duk_push_true(ctx);
				duk_put_prop_stridx(ctx, -2, DUK_STRIDX_CONFIGURABLE);
				duk_dup(ctx, c+1);
				duk_put_prop_stridx(ctx, -2, (a == DUK_EXTRAOP_INITSET ? DUK_STRIDX_SET : DUK_STRIDX_GET));

				DUK_DDDPRINT("INITGET/INITSET: obj=%!T, key=%!T, desc=%!T",
				             duk_get_tval(ctx, -3), duk_get_tval(ctx, -2), duk_get_tval(ctx, -1));

				duk_call_method(ctx, 3);  /* -> [ Object res ] */
				duk_pop_2(ctx);

				DUK_DDDPRINT("INITGET/INITSET AFTER: obj=%!T", duk_get_tval(ctx, b));
				break;
			}

			case DUK_EXTRAOP_ENDTRY: {
				duk_catcher *cat;
				duk_tval tv_tmp;
				duk_tval *tv1;

				DUK_ASSERT(thr->catchstack_top >= 1);
				DUK_ASSERT(thr->callstack_top >= 1);
				DUK_ASSERT(thr->catchstack[thr->catchstack_top - 1].callstack_index == thr->callstack_top - 1);

				cat = &thr->catchstack[thr->catchstack_top - 1];

				DUK_DDDPRINT("ENDTRY: clearing catch active flag (regardless of whether it was set or not)");
				DUK_CAT_CLEAR_CATCH_ENABLED(cat);

				if (DUK_CAT_HAS_FINALLY_ENABLED(cat)) {
					DUK_DDDPRINT("ENDTRY: finally part is active, jump through 2nd jump slot with 'normal continuation'");
			
					tv1 = &thr->valstack[cat->idx_base];
					DUK_ASSERT(tv1 >= thr->valstack && tv1 < thr->valstack_top);
					DUK_TVAL_SET_TVAL(&tv_tmp, tv1);
					DUK_TVAL_SET_UNDEFINED_ACTUAL(tv1);
					DUK_TVAL_DECREF(thr, &tv_tmp);     /* side effects */
					tv1 = NULL;

					tv1 = &thr->valstack[cat->idx_base + 1];
					DUK_ASSERT(tv1 >= thr->valstack && tv1 < thr->valstack_top);
					DUK_TVAL_SET_TVAL(&tv_tmp, tv1);
					DUK_TVAL_SET_NUMBER(tv1, (double) DUK_LJ_TYPE_NORMAL);
					DUK_TVAL_DECREF(thr, &tv_tmp);     /* side effects */
					tv1 = NULL;

					DUK_CAT_CLEAR_FINALLY_ENABLED(cat);
				} else {
					DUK_DDDPRINT("ENDTRY: no finally part, dismantle catcher, jump through 2nd jump slot (to end of statement)");
					duk_hthread_catchstack_unwind(thr, thr->catchstack_top - 1);
				}

				act->pc = cat->pc_base + 1;
				break;
			}

			case DUK_EXTRAOP_ENDCATCH: {
				duk_catcher *cat;
				duk_tval tv_tmp;
				duk_tval *tv1;

				DUK_ASSERT(thr->catchstack_top >= 1);
				DUK_ASSERT(thr->callstack_top >= 1);
				DUK_ASSERT(thr->catchstack[thr->catchstack_top - 1].callstack_index == thr->callstack_top - 1);

				cat = &thr->catchstack[thr->catchstack_top - 1];
				DUK_ASSERT(!DUK_CAT_HAS_CATCH_ENABLED(cat));  /* cleared before entering catch part */

				if (DUK_CAT_HAS_LEXENV_ACTIVE(cat)) {
					duk_hobject *prev_env;

					/* 'with' binding has no catch clause, so can't be here unless a normal try-catch */
					DUK_ASSERT(DUK_CAT_HAS_CATCH_BINDING_ENABLED(cat));
					DUK_ASSERT(act->lex_env != NULL);

					DUK_DDDPRINT("ENDCATCH: popping catcher part lexical environment");

					prev_env = act->lex_env;
					DUK_ASSERT(prev_env != NULL);
					act->lex_env = prev_env->prototype;
					DUK_CAT_CLEAR_LEXENV_ACTIVE(cat);
					DUK_HOBJECT_DECREF(thr, prev_env);  /* side effects */
				}

				if (DUK_CAT_HAS_FINALLY_ENABLED(cat)) {
					DUK_DDDPRINT("ENDCATCH: finally part is active, jump through 2nd jump slot with 'normal continuation'");
			
					tv1 = &thr->valstack[cat->idx_base];
					DUK_ASSERT(tv1 >= thr->valstack && tv1 < thr->valstack_top);
					DUK_TVAL_SET_TVAL(&tv_tmp, tv1);
					DUK_TVAL_SET_UNDEFINED_ACTUAL(tv1);
					DUK_TVAL_DECREF(thr, &tv_tmp);     /* side effects */
					tv1 = NULL;

					tv1 = &thr->valstack[cat->idx_base + 1];
					DUK_ASSERT(tv1 >= thr->valstack && tv1 < thr->valstack_top);
					DUK_TVAL_SET_TVAL(&tv_tmp, tv1);
					DUK_TVAL_SET_NUMBER(tv1, (double) DUK_LJ_TYPE_NORMAL);
					DUK_TVAL_DECREF(thr, &tv_tmp);     /* side effects */
					tv1 = NULL;

					DUK_CAT_CLEAR_FINALLY_ENABLED(cat);
				} else {
					DUK_DDDPRINT("ENDCATCH: no finally part, dismantle catcher, jump through 2nd jump slot (to end of statement)");
					duk_hthread_catchstack_unwind(thr, thr->catchstack_top - 1);
				}

				act->pc = cat->pc_base + 1;
				break;
			}

			case DUK_EXTRAOP_ENDFIN: {
				duk_context *ctx = (duk_context *) thr;
				duk_catcher *cat;
				duk_tval *tv1;
				int cont_type;

				DUK_ASSERT(thr->catchstack_top >= 1);
				DUK_ASSERT(thr->callstack_top >= 1);
				DUK_ASSERT(thr->catchstack[thr->catchstack_top - 1].callstack_index == thr->callstack_top - 1);

				cat = &thr->catchstack[thr->catchstack_top - 1];
				DUK_ASSERT(!DUK_CAT_HAS_CATCH_ENABLED(cat));
				DUK_ASSERT(!DUK_CAT_HAS_FINALLY_ENABLED(cat));  /* cleared before entering finally */
				/* FIXME: assert idx_base */

				DUK_DDDPRINT("ENDFIN: completion value=%!T, type=%!T",
				             &thr->valstack[cat->idx_base + 0],
				             &thr->valstack[cat->idx_base + 1]);

				tv1 = &thr->valstack[cat->idx_base + 1];  /* type */
				DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv1));
				cont_type = (int) DUK_TVAL_GET_NUMBER(tv1);

				if (cont_type == DUK_LJ_TYPE_NORMAL) {
					DUK_DDDPRINT("ENDFIN: finally part finishing with 'normal' (non-abrupt) completion -> "
					             "dismantle catcher, resume execution after ENDFIN");
					duk_hthread_catchstack_unwind(thr, thr->catchstack_top - 1);
				} else {
					DUK_DDDPRINT("ENDFIN: finally part finishing with abrupt completion, lj_type=%d -> "
					             "dismantle catcher, re-throw error",
					             cont_type);

					duk_push_tval(ctx, &thr->valstack[cat->idx_base]);

					/* FIXME: assert lj type valid */
					duk_err_setup_heap_ljstate(thr, cont_type);

					DUK_ASSERT(thr->heap->lj.jmpbuf_ptr != NULL);  /* always in executor */
					duk_err_longjmp(thr);
					DUK_NEVER_HERE();
				}

				/* continue execution after ENDFIN */
				break;
			}

			case DUK_EXTRAOP_THROW: {
				duk_context *ctx = (duk_context *) thr;
				int b = DUK_DEC_B(ins);

				/* Note: errors are augmented when they are created, not
				 * when they are thrown.  So, don't augment here, it would
				 * break re-throwing for instance.
				 */

				duk_dup(ctx, b);

				DUK_DDDPRINT("THROW ERROR (BYTECODE): %!dT", duk_get_tval(ctx, -1));

				duk_err_setup_heap_ljstate(thr, DUK_LJ_TYPE_THROW);

				DUK_ASSERT(thr->heap->lj.jmpbuf_ptr != NULL);  /* always in executor */
				duk_err_longjmp(thr);

				DUK_NEVER_HERE();
				break;
			}

			case DUK_EXTRAOP_INVLHS: {
				DUK_ERROR(thr, DUK_ERR_REFERENCE_ERROR, "invalid lvalue");

				DUK_NEVER_HERE();
				break;
			}

			default: {
				INTERNAL_ERROR("invalid extra opcode");
			}

			}  /* end switch */

			break;
		}

		case DUK_OP_DEBUG: {
#ifdef DUK_USE_DEBUG
			switch (DUK_DEC_A(ins)) {

			case DUK_DEBUGOP_DUMPREG: {
				DUK_DPRINT("DUMPREG: %d -> %!T",
				           DUK_DEC_BC(ins),
				           duk_get_tval((duk_context *) thr, DUK_DEC_BC(ins)));
				break;
			}

			case DUK_DEBUGOP_DUMPREGS: {
				int i, i_top;
				i_top = duk_get_top((duk_context *) thr);
				DUK_DPRINT("DUMPREGS: %d regs", i_top);
				for (i = 0; i < i_top; i++) {
					DUK_DPRINT("  r%d -> %!dT", i, duk_get_tval((duk_context *) thr, i));
				}
				break;
			}

			case DUK_DEBUGOP_DUMPTHREAD: {
				DUK_DEBUG_DUMP_HTHREAD(thr);
				break;
			}

			/*FIXME*/

			case DUK_DEBUGOP_LOGMARK: {
				DUK_DPRINT("LOGMARK: mark %d at pc %d", DUK_DEC_BC(ins), act->pc - 1);  /* -1, autoinc */
				break;
			}

			default: {
				INTERNAL_ERROR("invalid debug opcode");
			}

			}  /* end switch */
#endif
			break;
		}

		case DUK_OP_INVALID: {
			DUK_ERROR(thr, DUK_ERR_INTERNAL_ERROR, "INVALID opcode (%d)", DUK_DEC_ABC(ins));
			break;
		}

		default: {
			/* this should never be possible, because the switch-case is
			 * comprehensive
			 */
			INTERNAL_ERROR("invalid opcode");
			break;
		}

		}  /* end switch */
	}
	DUK_NEVER_HERE();

#ifdef _COMPACT_ERRORS  /*FIXME*/
 internal_error:
	DUK_ERROR(thr, DUK_ERR_INTERNAL_ERROR, "internal error in bytecode executor");
#endif
}

#line 1 "duk_js_ops.c"
/*
 *  Ecmascript specification algorithm and conversion helpers.
 *
 *  These helpers encapsulate the primitive Ecmascript operation
 *  semantics, and are used by the bytecode executor and the API
 *  (among other places).  Note that some primitives are only
 *  implemented as part of the API and have no "internal" helper.
 *  (This is the case when an internal helper would not really be
 *  useful; e.g. the operation is rare, uses value stack heavily,
 *  etc.)
 *
 *  The operation arguments depend on what is required to implement
 *  the operation:
 *
 *    - If an operation is simple and stateless, and has no side
 *      effects, it won't take an duk_hthread argument and its
 *      arguments may be duk_tval pointers (which are safe as long
 *      as no side effects take place).
 *
 *    - If complex coercions are required (e.g. a "ToNumber" coercion)
 *      or errors may be thrown, the operation takes an duk_hthread
 *      argument.  This also implies that the operation may have
 *      arbitrary side effects, invalidating any duk_tval pointers.
 *
 *    - For operations with potential side effects, arguments can be
 *      taken in several ways:
 *
 *      a) as duk_tval pointers, which makes sense if the "common case"
 *         can be resolved without side effects (e.g. coercion); the
 *         arguments are pushed to the valstack for coercion if
 *         necessary
 *
 *      b) as duk_tval values
 *
 *      c) implicitly on value stack top
 *
 *      d) as indices to the value stack
 *
 *  Future work:
 *
 *     - Argument styles may not be the most sensible in every case now.
 *
 *     - In-place coercions might be useful for several operations, if
 *       in-place coercion is OK for the bytecode executor and the API.
 */

/* include removed: duk_internal.h */

/*
 *  [[DefaultValue]]  (E5 Section 8.12.8)
 *
 *  ==> implemented in the API.
 */

/*
 *  ToPrimitive()  (E5 Section 9.1)
 *
 *  ==> implemented in the API.
 */

/*
 *  ToBoolean()  (E5 Section 9.2)
 */

int duk_js_toboolean(duk_tval *tv) {
	switch (DUK_TVAL_GET_TAG(tv)) {
	case DUK_TAG_UNDEFINED:
	case DUK_TAG_NULL:
		return 0;
	case DUK_TAG_BOOLEAN:
		return DUK_TVAL_GET_BOOLEAN(tv);
	case DUK_TAG_STRING: {
		duk_hstring *h = DUK_TVAL_GET_STRING(tv);
		DUK_ASSERT(h != NULL);
		return (h->blen > 0 ? 1 : 0);
	}
	case DUK_TAG_OBJECT: {
		return 1;
	}
	case DUK_TAG_BUFFER: {
		/* mimic semantics for strings */
		duk_hbuffer *h = DUK_TVAL_GET_BUFFER(tv);
		DUK_ASSERT(h != NULL);
		return (DUK_HBUFFER_GET_SIZE(h) > 0 ? 1 : 0);
	}
	case DUK_TAG_POINTER: {
		void *p = DUK_TVAL_GET_POINTER(tv);
		return (p != NULL ? 1 : 0);
	}
	default: {
		/* number */
		int c;
		DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv));
		c = fpclassify(DUK_TVAL_GET_NUMBER(tv));  /* FIXME: portability */
		if (c == FP_ZERO || c == FP_NAN) {
			return 0;
		} else {
			return 1;
		}
	}
	}
	DUK_NEVER_HERE();
}

/*
 *  ToNumber()  (E5 Section 9.3)
 *
 *  See: http://www.cs.indiana.edu/~burger/FP-Printing-PLDI96.pdf
 *       http://www.cs.indiana.edu/~burger/fp/index.html
 *
 *  Notes on the conversion:
 *
 *    - There are specific requirements on the accuracy of the conversion
 *      through a "Mathematical Value" (MV), so this conversion is not
 *      trivial.
 *
 *    - Quick rejects (e.g. based on first char) are difficult because
 *      the grammar allows leading and trailing white space.
 *
 *    - Quick reject based on string length is difficult even after
 *      accounting for white space; there may be arbitrarily many
 *      decimal digits.
 *
 *    - Standard grammar allows decimal values ("123"), hex values
 *      ("0x123") and infinities
 *
 *    - Unlike source code literals, ToNumber() coerces empty strings
 *      and strings with only whitespace to zero (not NaN).
 *
 *  FIXME: unify E5 Section 9.3.1 and main source literal syntax parsers
 *  into a shared helper, providing both (slightly different) semantics?
 */	

/* E5 Section 9.3.1 */
static double tonumber_string_raw(duk_hthread *thr, duk_hstring *h) {
	duk_context *ctx = (duk_context *) thr;
	int s2n_flags;
	double d;

	duk_push_hstring(ctx, h);

	/* Quite lenient, e.g. allow empty as zero, but don't allow trailing
	 * garbage.
	 */
	s2n_flags = DUK_S2N_FLAG_TRIM_WHITE |
	            DUK_S2N_FLAG_ALLOW_EXP |
	            DUK_S2N_FLAG_ALLOW_PLUS |
	            DUK_S2N_FLAG_ALLOW_MINUS |
	            DUK_S2N_FLAG_ALLOW_INF |
	            DUK_S2N_FLAG_ALLOW_FRAC |
	            DUK_S2N_FLAG_ALLOW_NAKED_FRAC |
	            DUK_S2N_FLAG_ALLOW_EMPTY_FRAC |
	            DUK_S2N_FLAG_ALLOW_EMPTY_AS_ZERO |
	            DUK_S2N_FLAG_ALLOW_LEADING_ZERO |
	            DUK_S2N_FLAG_ALLOW_AUTO_HEX_INT;

	duk_numconv_parse(ctx, 10 /*radix*/, s2n_flags);
	d = duk_get_number(ctx, -1);
	duk_pop(ctx);

	return d;
}

double duk_js_tonumber(duk_hthread *thr, duk_tval *tv) {
	duk_context *ctx = (duk_hthread *) thr;

	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(tv != NULL);

	switch (DUK_TVAL_GET_TAG(tv)) {
	case DUK_TAG_UNDEFINED: {
		/* return a specific NAN (although not strictly necessary) */
		double d;
		DUK_DOUBLE_SET_NAN(&d);
		DUK_ASSERT(DUK_DOUBLE_IS_NORMALIZED(&d));
		return d;
	}
	case DUK_TAG_NULL: {
		/* +0.0 */
		return 0.0;
	}
	case DUK_TAG_BOOLEAN: {
		if (DUK_TVAL_IS_BOOLEAN_TRUE(tv)) {
			return 1.0;
		}
		return 0.0;
	}
	case DUK_TAG_STRING: {
		duk_hstring *h = DUK_TVAL_GET_STRING(tv);
		return tonumber_string_raw(thr, h);
	}
	case DUK_TAG_OBJECT: {
		/* Note: ToPrimitive(object,hint) == [[DefaultValue]](object,hint),
		 * so use [[DefaultValue]] directly.
		 */
		double d;
		duk_push_tval(ctx, tv);
		duk_to_defaultvalue(ctx, -1, DUK_HINT_NUMBER);  /* 'tv' becomes invalid */

		/* recursive call for a primitive value (guaranteed not to cause second
		 * recursion).
		 */
		d = duk_js_tonumber(thr, duk_require_tval(ctx, -1));

		duk_pop(ctx);
		return d;
	}
	case DUK_TAG_BUFFER: {
		/* FIXME: what's a good conversion?  parse contents as a string (tonumber_string_raw)? */
		duk_hbuffer *h = DUK_TVAL_GET_BUFFER(tv);
		DUK_ASSERT(h != NULL);
		if (DUK_HBUFFER_GET_SIZE(h) > 0) {
			return 1.0;
		}
		return 0.0;
	}
	case DUK_TAG_POINTER: {
		/* FIXME: what's a good conversion?  pointer as a number?  NAN? */
		void *p = DUK_TVAL_GET_POINTER(tv);
		return (p != NULL ? 1.0 : 0.0);
	}
	default: {
		/* number */
		DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv));
		return DUK_TVAL_GET_NUMBER(tv);
	}
	}

	DUK_NEVER_HERE();
}

/*
 *  ToInteger()  (E5 Section 9.4)
 */

/* exposed, used by e.g. duk_builtin_date.c */
double duk_js_tointeger_number(double x) {
	int c = fpclassify(x);

	if (c == FP_NAN) {
		return 0.0;
	} else if (c == FP_ZERO || c == FP_INFINITE) {
		/* FIXME: FP_ZERO check can be removed, the else clause handles it
		 * correctly (preserving sign).
		 */
		return x;
	} else {
		int s = signbit(x);
		x = floor(fabs(x));  /* truncate towards zero */
		if (s) {
			x = -x;
		}
		return x;
	}
}

double duk_js_tointeger(duk_hthread *thr, duk_tval *tv) {
	double d = duk_js_tonumber(thr, tv);  /* invalidates tv */
	return duk_js_tointeger_number(d);
}

/*
 *  ToInt32(), ToUint32(), ToUint16()  (E5 Sections 9.5, 9.6, 9.7)
 */

/* combined algorithm matching E5 Sections 9.5 and 9.6 */	
static double toint32_or_touint32_helper(double x, int is_toint32) {
	int c = fpclassify(x);
	int s;

	if (c == FP_NAN || c == FP_ZERO || c == FP_INFINITE) {
		return 0.0;
	}


	/* x = sign(x) * floor(abs(x)), i.e. truncate towards zero, keep sign */
	s = signbit(x);
	x = floor(fabs(x));
	if (s) {
		x = -x;
	}
	
	/* NOTE: fmod(x) result sign is same as sign of x, which
	 * differs from what Javascript wants (see Section 9.6).
	 */

	x = fmod(x, DUK_DOUBLE_2TO32);    /* -> x in ]-2**32, 2**32[ */

	if (x < 0.0) {
		x += DUK_DOUBLE_2TO32;
	}
	/* -> x in [0, 2**32[ */

	if (is_toint32) {
		if (x >= DUK_DOUBLE_2TO31) {
			/* x in [2**31, 2**32[ */

			x -= DUK_DOUBLE_2TO32;  /* -> x in [-2**31,2**31[ */
		}
	}

	return x;
}

duk_i32 duk_js_toint32(duk_hthread *thr, duk_tval *tv) {
	double d = duk_js_tonumber(thr, tv);  /* invalidates tv */
	d = toint32_or_touint32_helper(d, 1);
	DUK_ASSERT(fpclassify(d) == FP_ZERO || fpclassify(d) == FP_NORMAL);
	DUK_ASSERT(d >= -2147483648.0 && d <= 2147483647.0);  /* [-0x80000000,0x7fffffff] */
	DUK_ASSERT(d == ((double) ((duk_i32) d)));  /* whole, won't clip */
	return (duk_i32) d;
}


duk_u32 duk_js_touint32(duk_hthread *thr, duk_tval *tv) {
	double d = duk_js_tonumber(thr, tv);  /* invalidates tv */
	d = toint32_or_touint32_helper(d, 0);
	DUK_ASSERT(fpclassify(d) == FP_ZERO || fpclassify(d) == FP_NORMAL);
	DUK_ASSERT(d >= 0.0 && d <= 4294967295.0);  /* [0x00000000, 0xffffffff] */
	DUK_ASSERT(d == ((double) ((duk_u32) d)));  /* whole, won't clip */
	return (duk_u32) d;

}

duk_u16 duk_js_touint16(duk_hthread *thr, duk_tval *tv) {
	/* should be a safe way to compute this */
	return (duk_u16) (duk_js_touint32(thr, tv) & 0x0000ffffU);
}

/*
 *  ToString()  (E5 Section 9.8)
 *
 *  ==> implemented in the API.
 */

/*
 *  ToObject()  (E5 Section 9.9)
 *
 *  ==> implemented in the API.
 */

/*
 *  CheckObjectCoercible()  (E5 Section 9.10)
 *
 *  Note: no API equivalent now.
 */

void duk_js_checkobjectcoercible(duk_hthread *thr, duk_tval *tv_x) {
	int tag = DUK_TVAL_GET_TAG(tv_x);

	/* Note: this must match ToObject() behavior */

	if (tag == DUK_TAG_UNDEFINED ||
	    tag == DUK_TAG_NULL ||
	    tag == DUK_TAG_POINTER ||
	    tag == DUK_TAG_BUFFER) {
		DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, "not object coercible");
	}
}

/*
 *  IsCallable()  (E5 Section 9.11)
 *
 *  FIXME: API equivalent is a separate implementation now, and this has
 *  currently no callers.
 */

int duk_js_iscallable(duk_tval *tv_x) {
	duk_hobject *obj;

	if (!DUK_TVAL_IS_OBJECT(tv_x)) {
		return 0;
	}
	obj = DUK_TVAL_GET_OBJECT(tv_x);
	DUK_ASSERT(obj != NULL);

	return DUK_HOBJECT_IS_CALLABLE(obj);
}

/*
 *  Loose equality, strict equality, and SameValue (E5 Sections 11.9.1, 11.9.4,
 *  9.12).  These have much in common so they can share some helpers.
 *
 *  FIXME notes:
 *
 *    - Current implementation (and spec definition) has recursion; this should
 *      be fixed if possible.
 *
 *    - String-to-number coercion should be possible without going through the
 *      value stack (and be more compact) if a shared helper is invoked.
 *
 *    - Non-standard coercion rules for internal types?  For instance:
 *
 *      + Pointer -> convert to number? or string?
 *      + Buffer vs. string -> compare contents
 *      + Buffer vs. buffer -> compare contents?  (not for strict mode)
 *
 *    - The beginning of loose and strict equality are identical: if the type
 *      tags are the same, comparison logic is the same -> implement a single
 *      helper with a strictness flag?
 *
 *    - SameValue and strict equals are identical except that zero signs are
 *      significant for SameValue but not for strict equals, so it can also go
 *      into the same helper.
 */

/* Note that this is the same operation for strict and loose equality:
 *  - E5 Section 11.9.3, step 1.c (loose)
 *  - E5 Section 11.9.6, step 4 (strict)
 */

int duk_js_equals_number(double x, double y) {
	int cx = fpclassify(x);
	int cy = fpclassify(y);

	if (cx == FP_NAN || cy == FP_NAN) {
		return 0;
	}

	/* FIXME: optimize */

	if (cx == FP_ZERO && cy == FP_ZERO) {
		return 1;
	}

	if (x == y) {
		return 1;
	}

	return 0;
}

/* E5 Section 11.9.3 */
int duk_js_equals(duk_hthread *thr, duk_tval *tv_x, duk_tval *tv_y) {
	duk_context *ctx = (duk_context *) thr;

	/*
	 *  FIXME: very direct translation now - should be made more
	 *  efficient, avoid recursion, etc.
	 */

	/*
	 *  Same type?
	 *
	 *  Note: since number values have no explicit tag, need the awkward
	 *  if + switch.
	 */

	if (DUK_TVAL_IS_NUMBER(tv_x) && DUK_TVAL_IS_NUMBER(tv_y)) {
		return duk_js_equals_number(DUK_TVAL_GET_NUMBER(tv_x),
		                            DUK_TVAL_GET_NUMBER(tv_y));
	} else if (DUK_TVAL_GET_TAG(tv_x) == DUK_TVAL_GET_TAG(tv_y)) {
		switch (DUK_TVAL_GET_TAG(tv_x)) {
		case DUK_TAG_UNDEFINED: {
			return 1;
		}
		case DUK_TAG_NULL: {
			return 1;
		}
		case DUK_TAG_BOOLEAN: {
			return DUK_TVAL_GET_BOOLEAN(tv_x) == DUK_TVAL_GET_BOOLEAN(tv_y);
		}
		case DUK_TAG_POINTER: {
			return DUK_TVAL_GET_POINTER(tv_x) == DUK_TVAL_GET_POINTER(tv_y);
		}
		case DUK_TAG_STRING:
		case DUK_TAG_OBJECT:
		case DUK_TAG_BUFFER: {
			/* heap pointer comparison suffices */
			return DUK_TVAL_GET_HEAPHDR(tv_x) == DUK_TVAL_GET_HEAPHDR(tv_y);
		}
		default: {
			DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv_x));
			DUK_NEVER_HERE();
			return 0;
		}
		}
	}

	/*
	 *  Types are different; various cases
	 */

	/* undefined/null are considered equal (e.g. "null == undefined" -> true) */
	if ((DUK_TVAL_IS_UNDEFINED(tv_x) && DUK_TVAL_IS_NULL(tv_y)) ||
	    (DUK_TVAL_IS_NULL(tv_x) && DUK_TVAL_IS_UNDEFINED(tv_y))) {
		return 1;
	}

	/* number/string -> coerce string to number (e.g. "'1.5' == 1.5" -> true) */
	if (DUK_TVAL_IS_NUMBER(tv_x) && DUK_TVAL_IS_STRING(tv_y)) {
		/* FIXME: this is possible without resorting to the value stack */
		double d1, d2;
		d1 = DUK_TVAL_GET_NUMBER(tv_x);
		duk_push_tval(ctx, tv_y);
		duk_to_number(ctx, -1);
		d2 = duk_require_number(ctx, -1);
		duk_pop(ctx);
		return duk_js_equals_number(d1, d2);
	}
	if (DUK_TVAL_IS_STRING(tv_x) && DUK_TVAL_IS_NUMBER(tv_y)) {
		/* FIXME: this is possible without resorting to the value stack */
		double d1, d2;
		d2 = DUK_TVAL_GET_NUMBER(tv_y);
		duk_push_tval(ctx, tv_x);
		duk_to_number(ctx, -1);
		d1 = duk_require_number(ctx, -1);
		duk_pop(ctx);
		return duk_js_equals_number(d1, d2);
	}

	/* boolean/(any) -> coerce boolean to number and try again */
	if (DUK_TVAL_IS_BOOLEAN(tv_x)) {
		/* FIXME: ToNumber(bool) is +1.0 or 0.0 -> make faster */
		int rc;
		duk_push_tval(ctx, tv_x);
		duk_push_tval(ctx, tv_y);
		duk_to_number(ctx, -2);
		rc = duk_js_equals(thr, duk_get_tval(ctx, -2), duk_get_tval(ctx, -1));
		duk_pop_2(ctx);
		return rc;
	}
	if (DUK_TVAL_IS_BOOLEAN(tv_y)) {
		/* FIXME: ToNumber(bool) is +1.0 or 0.0 -> make faster */
		int rc;
		duk_push_tval(ctx, tv_x);
		duk_push_tval(ctx, tv_y);
		duk_to_number(ctx, -1);
		rc = duk_js_equals(thr, duk_get_tval(ctx, -2), duk_get_tval(ctx, -1));
		duk_pop_2(ctx);
		return rc;
	}

	/* string-or-number/object -> coerce object to primitive (apparently without hint), then try again */
	if ((DUK_TVAL_IS_STRING(tv_x) || DUK_TVAL_IS_NUMBER(tv_x)) &&
	    DUK_TVAL_IS_OBJECT(tv_y)) {
		int rc;
		duk_push_tval(ctx, tv_x);
		duk_push_tval(ctx, tv_y);
		duk_to_primitive(ctx, -1, DUK_HINT_NONE);  /* apparently no hint? */
		rc = duk_js_equals(thr, duk_get_tval(ctx, -2), duk_get_tval(ctx, -1));
		duk_pop_2(ctx);
		return rc;
	}
	if (DUK_TVAL_IS_OBJECT(tv_x) &&
	    (DUK_TVAL_IS_STRING(tv_y) || DUK_TVAL_IS_NUMBER(tv_y))) {
		int rc;
		duk_push_tval(ctx, tv_x);
		duk_push_tval(ctx, tv_y);
		duk_to_primitive(ctx, -2, DUK_HINT_NONE);  /* apparently no hint? */
		rc = duk_js_equals(thr, duk_get_tval(ctx, -2), duk_get_tval(ctx, -1));
		duk_pop_2(ctx);
		return rc;
	}

	/* FIXME: coercion rules for internal types */

	/* nothing worked -> not equal */
	return 0;
}

/* E5 Section 11.9.4 */
int duk_js_strict_equals(duk_tval *tv_x, duk_tval *tv_y) {
	/*
	 *  Same type?
	 *
	 *  Note: since number values have no explicit tag, need the awkward
	 *  if + switch.
	 */

	if (DUK_TVAL_IS_NUMBER(tv_x) && DUK_TVAL_IS_NUMBER(tv_y)) {
		/* Note: comparison of numbers is identical for loose and strict
		 * equality, hence use loose helper.
		 */
		return duk_js_equals_number(DUK_TVAL_GET_NUMBER(tv_x),
		                            DUK_TVAL_GET_NUMBER(tv_y));
	} else if (DUK_TVAL_GET_TAG(tv_x) == DUK_TVAL_GET_TAG(tv_y)) {
		switch (DUK_TVAL_GET_TAG(tv_x)) {
		case DUK_TAG_UNDEFINED: {
			return 1;
		}
		case DUK_TAG_NULL: {
			return 1;
		}
		case DUK_TAG_BOOLEAN: {
			return DUK_TVAL_GET_BOOLEAN(tv_x) == DUK_TVAL_GET_BOOLEAN(tv_y);
		}
		case DUK_TAG_POINTER: {
			return DUK_TVAL_GET_POINTER(tv_x) == DUK_TVAL_GET_POINTER(tv_y);
		}
		case DUK_TAG_STRING:
		case DUK_TAG_OBJECT:
		case DUK_TAG_BUFFER: {
			/* heap pointer comparison suffices */
			return DUK_TVAL_GET_HEAPHDR(tv_x) == DUK_TVAL_GET_HEAPHDR(tv_y);
		}
		default: {
			DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv_x));
			DUK_NEVER_HERE();
			return 0;
		}
		}
	}

	/*
	 *  Types are different -> strict equality always false
	 */

	return 0;
}

/* E5 Section 9.12 */
int duk_js_samevalue_number(double x, double y) {
	int cx = fpclassify(x);
	int cy = fpclassify(y);

	if (cx == FP_NAN && cy == FP_NAN) {
		/* SameValue(NaN, NaN) = true, regardless of NaN sign or extra bits */
		return 1;
	}

	if (cx == FP_ZERO && cy == FP_ZERO) {
		/* Note: cannot assume that a non-zero return value of signbit() would
		 * always be the same -- hence cannot (portably) use something like:
		 *
		 *     signbit(x) == signbit(y)
		 */

		int sx = (signbit(x) ? 1 : 0);
		int sy = (signbit(y) ? 1 : 0);

		return (sx == sy);
	}

	/* normal comparison; known:
	 *   - both x and y are not NaNs (but one of them can be)
	 *   - both x and y are not zero (but one of them can be)
	 *   - x and y may be denormal or infinite
	 */

	return (x == y);
}

int duk_js_samevalue(duk_tval *tv_x, duk_tval *tv_y) {
	/*
	 *  Same type?
	 *
	 *  Note: since number values have no explicit tag, need the awkward
	 *  if + switch.
	 */

	if (DUK_TVAL_IS_NUMBER(tv_x) && DUK_TVAL_IS_NUMBER(tv_y)) {
		return duk_js_samevalue_number(DUK_TVAL_GET_NUMBER(tv_x),
		                               DUK_TVAL_GET_NUMBER(tv_y));
	} else if (DUK_TVAL_GET_TAG(tv_x) == DUK_TVAL_GET_TAG(tv_y)) {
		switch (DUK_TVAL_GET_TAG(tv_x)) {
		case DUK_TAG_UNDEFINED: {
			return 1;
		}
		case DUK_TAG_NULL: {
			return 1;
		}
		case DUK_TAG_BOOLEAN: {
			return DUK_TVAL_GET_BOOLEAN(tv_x) == DUK_TVAL_GET_BOOLEAN(tv_y);
		}
		case DUK_TAG_POINTER: {
			return DUK_TVAL_GET_POINTER(tv_x) == DUK_TVAL_GET_POINTER(tv_y);
		}
		case DUK_TAG_STRING:
		case DUK_TAG_OBJECT:
		case DUK_TAG_BUFFER: {
			/* heap pointer comparison suffices */
			return DUK_TVAL_GET_HEAPHDR(tv_x) == DUK_TVAL_GET_HEAPHDR(tv_y);
		}
		default: {
			DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv_x));
			DUK_NEVER_HERE();
			return 0;
		}
		}
	}

	/*
	 *  Types are different -> strict equality always false
	 */

	return 0;
}

/*
 *  Comparisons (x >= y, x > y, x <= y, x < y)
 *
 *  E5 Section 11.8.5: implement 'x < y' and then use negate and eval_left_first
 *  flags to get the rest.
 */

/* FIXME: join flags into one integer argument? */

/* FIXME: this should probably just operate on the stack top, because it
 * needs to push stuff on the stack anyway...
 */

int duk_js_string_compare(duk_hstring *h1, duk_hstring *h2) {
	/*
	 *  String comparison (E5 Section 11.8.5, step 4), which
	 *  needs to compare codepoint by codepoint.
	 *
	 *  However, UTF-8 allows us to use strcmp directly: the shared
	 *  prefix will be encoded identically (UTF-8 has unique encoding)
	 *  and the first differing character can be compared with a simple
	 *  unsigned byte comparison (which strcmp does).
	 *
	 *  This will not work properly for non-xutf-8 strings, but this
	 *  is not an issue for compliance.
	 */

	size_t h1_len, h2_len, prefix_len;
	int rc;

	DUK_ASSERT(h1 != NULL);
	DUK_ASSERT(h2 != NULL);
	h1_len = DUK_HSTRING_GET_BYTELEN(h1);
	h2_len = DUK_HSTRING_GET_BYTELEN(h2);
	prefix_len = (h1_len <= h2_len ? h1_len : h2_len);

	/* memcmp() should return zero (equal) for zero length, but avoid
	 * it because there are some platform specific bugs.  Don't use
	 * strncmp() because it stops comparing at a NUL.
	 */

	/* FIXME: use a wrapper utility memcmp() instead of doing the zero
	 * check everywhere separately.
	 */

	if (prefix_len == 0) {
		rc = 0;
	} else {
		rc = memcmp((const char *) DUK_HSTRING_GET_DATA(h1),
		            (const char *) DUK_HSTRING_GET_DATA(h2),
		            prefix_len);
	}

	if (rc < 0) {
		return -1;
	} else if (rc > 0) {
		return 1;
	}

	/* prefix matches, lengths matter now */
	if (h1_len < h2_len) {
		/* e.g. "x" < "xx" */
		return -1;
	} else if (h1_len > h2_len) {
		return 1;
	}

	return 0;
}

int duk_js_compare_helper(duk_hthread *thr, duk_tval *tv_x, duk_tval *tv_y, int eval_left_first, int negate) {
	duk_context *ctx = (duk_context *) thr;
	double d1, d2;
	int c1, c2;
	int s1, s2;
	int rc;
	int retval;

	duk_push_tval(ctx, tv_x);
	duk_push_tval(ctx, tv_y);

	if (eval_left_first) {
		duk_to_primitive(ctx, -2, DUK_HINT_NUMBER);
		duk_to_primitive(ctx, -1, DUK_HINT_NUMBER);
	} else {
		duk_to_primitive(ctx, -2, DUK_HINT_NUMBER);
		duk_to_primitive(ctx, -1, DUK_HINT_NUMBER);
	}

	/* Note: reuse variables */
	tv_x = duk_get_tval(ctx, -2);
	tv_y = duk_get_tval(ctx, -1);

	if (DUK_TVAL_IS_STRING(tv_x) && DUK_TVAL_IS_STRING(tv_y)) {
		duk_hstring *h1 = DUK_TVAL_GET_STRING(tv_x);
		duk_hstring *h2 = DUK_TVAL_GET_STRING(tv_y);
		DUK_ASSERT(h1 != NULL);
		DUK_ASSERT(h2 != NULL);

		rc = duk_js_string_compare(h1, h2);
		if (rc < 0) {
			goto lt_true;
		} else {
			goto lt_false;
		}
	} else {
		/* Ordering should not matter (E5 Section 11.8.5, step 3.a) but
		 * preserve it just in case.
		 */

		if (eval_left_first) {
			d1 = duk_to_number(ctx, -2);
			d2 = duk_to_number(ctx, -1);
		} else {
			d2 = duk_to_number(ctx, -1);
			d1 = duk_to_number(ctx, -2);
		}

		c1 = fpclassify(d1);
		s1 = signbit(d1);
		c2 = fpclassify(d2);
		s2 = signbit(d2);

		if (c1 == FP_NAN || c2 == FP_NAN) {
			goto lt_undefined;
		}

		if (c1 == FP_ZERO && c2 == FP_ZERO) {
			/* For all combinations: +0 < +0, +0 < -0, -0 < +0, -0 < -0,
			 * steps e, f, and g.
			 */
			goto lt_false;
		}

		if (d1 == d2) {
			goto lt_false;
		}

		if (c1 == FP_INFINITE && s1 == 0) {
			/* x == +Infinity */
			goto lt_false;
		}

		if (c2 == FP_INFINITE && s2 == 0) {
			/* y == +Infinity */
			goto lt_true;
		}

		if (c2 == FP_INFINITE && s2 != 0) {
			/* y == -Infinity */
			goto lt_false;
		}

		if (c1 == FP_INFINITE && s1 != 0) {
			/* x == -Infinity */
			goto lt_true;
		}

		if (d1 < d2) {
			goto lt_true;
		}

		goto lt_false;
	}

 lt_undefined:
	/* Note: undefined from Section 11.8.5 always results in false
	 * return (see e.g. Section 11.8.3) - hence special treatment here.
	 */
	retval = 0;
	goto cleanup;

 lt_true:
	if (negate) {
		retval = 0;
		goto cleanup;
	} else {
		retval = 1;
		goto cleanup;
	}
	/* never here */

 lt_false:
	if (negate) {
		retval = 1;
		goto cleanup;
	} else {
		retval = 0;
		goto cleanup;
	}
	/* never here */

 cleanup:
	duk_pop_2(ctx);
	return retval;
}

/* FIXME: remove these? or make them macros? */
#if 0  /* unused */
/* E5 Sections 11.8.1, 11.8.5 */
int duk_js_lessthan(duk_hthread *thr, duk_tval *tv_x, duk_tval *tv_y) {
	/* x < y */
	return duk_js_compare_helper(thr, tv_x, tv_y, 1, 0);
}

/* E5 Sections 11.8.2, 11.8.5 */
int duk_js_greaterthan(duk_hthread *thr, duk_tval *tv_x, duk_tval *tv_y) {
	/* x > y  -->  y < x */
	return duk_js_compare_helper(thr, tv_y, tv_x, 0, 0);
}

/* E5 Sections 11.8.3, 11.8.5 */
int duk_js_lessthanorequal(duk_hthread *thr, duk_tval *tv_x, duk_tval *tv_y) {
	/* x <= y  -->  not (x > y)  -->  not (y < x) */
	return duk_js_compare_helper(thr, tv_y, tv_x, 0, 1);
}

/* E5 Sections 11.8.4, 11.8.5 */
int duk_js_greaterthanorequal(duk_hthread *thr, duk_tval *tv_x, duk_tval *tv_y) {
	/* x >= y  -->  not (x < y) */
	return duk_js_compare_helper(thr, tv_x, tv_y, 1, 1);
}
#endif

/*
 *  instanceof
 */

/*
 *  E5 Section 11.8.6 describes the main algorithm, which uses
 *  [[HasInstance]].  [[HasInstance]] is defined for only
 *  function objects:
 *
 *    - Normal functions:
 *      E5 Section 15.3.5.3
 *    - Functions established with Function.prototype.bind():
 *      E5 Section 15.3.4.5.3
 *
 *  For other objects, a TypeError is thrown.
 *
 *  FIXME: TypeError descriptions are bad (automatic from API).
 *
 */

/* FIXME: refactoring -> helper to extract duk_hobject * from a tval would
 * be useful here; that function should throw TypeErrors if type expectations
 * are incorrect.
 */

int duk_js_instanceof(duk_hthread *thr, duk_tval *tv_x, duk_tval *tv_y) {
	duk_context *ctx = (duk_context *) thr;
	duk_hobject *func;
	duk_hobject *val;
	duk_hobject *proto;
	duk_u32 sanity;

	/*
	 *  Get the values onto the stack first.  It would be possible to cover
	 *  some normal cases without resorting to the value stack.
	 */

	duk_push_tval(ctx, tv_x);
	duk_push_tval(ctx, tv_y);
	func = duk_require_hobject(ctx, -1);

	/*
	 *  For bound objects, [[HasInstance]] just calls the target function
	 *  [[HasInstance]].  If that is again a bound object, repeat until
	 *  we find a non-bound Function object.
	 */

	/* FIXME: this bound function resolution also happens elsewhere,
	 * move into a shared helper.
	 */

	sanity = DUK_HOBJECT_BOUND_CHAIN_SANITY;
	do {
		DUK_ASSERT(DUK_HOBJECT_IS_CALLABLE(func));

		/* check func supports [[HasInstance]] (this is checked for every function
		 * in the bound chain, including the final one)
		 */

		if (!DUK_HOBJECT_IS_CALLABLE(func)) {
			/*
		 	 *  Note: of native Ecmascript objects, only Function instances
			 *  have a [[HasInstance]] internal property.  Custom objects might
			 *  also have it, but not in current implementation.
			 *
			 *  XXX: add a separate flag, DUK_HOBJECT_FLAG_ALLOW_INSTANCEOF?
			 */
			DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, "instanceof rval does not support [[HasInstance]]");
		}

		if (!DUK_HOBJECT_HAS_BOUND(func)) {
			break;
		}

		/* [ ... lval rval ] */

		duk_get_prop_stridx(ctx, -1, DUK_STRIDX_INT_TARGET);         /* -> [ ... lval rval new_rval ] */
		duk_replace(ctx, -1);                                        /* -> [ ... lval new_rval ] */
		func = duk_require_hobject(ctx, -1);

		/* func support for [[HasInstance]] checked in the beginning of the loop */
	} while (--sanity > 0);

	if (sanity == 0) {
		DUK_ERROR(thr, DUK_ERR_INTERNAL_ERROR, "instanceof bound chain sanity exceeded");
	}

	/*
	 *  'func' is now a non-bound object which supports [[HasInstance]]
	 *  (which here just means DUK_HOBJECT_FLAG_CALLABLE).  Move on
	 *  to execute E5 Section 15.3.5.3.
	 */

	DUK_ASSERT(!DUK_HOBJECT_HAS_BOUND(func));
	DUK_ASSERT(DUK_HOBJECT_IS_CALLABLE(func));

	/* [ ... lval rval(func) ] */

	val = duk_get_hobject(ctx, -2);
	if (!val) {
		goto pop_and_false;
	}

	duk_get_prop_stridx(ctx, -1, DUK_STRIDX_PROTOTYPE);  /* -> [ ... lval rval rval.prototype ] */
	proto = duk_require_hobject(ctx, -1);
	duk_pop(ctx);  /* -> [ ... lval rval ] */

	sanity = DUK_HOBJECT_PROTOTYPE_CHAIN_SANITY;
	do {
		/*
		 *  Note: prototype chain is followed BEFORE first comparison.  This
		 *  means that the instanceof lval is never itself compared to the
		 *  rval.prototype property.  This is apparently intentional, see E5
		 *  Section 15.3.5.3, step 4.a.
		 *
		 *  Also note:
		 *
		 *      js> (function() {}) instanceof Function
		 *      true
		 *      js> Function instanceof Function
		 *      true
		 *
		 *  For the latter, h_proto will be Function.prototype, which is the
		 *  built-in Function prototype.  Because Function.[[Prototype]] is
		 *  also the built-in Function prototype, the result is true.
		 */

		val = val->prototype;

		if (!val) {
			goto pop_and_false;
		} else if (val == proto) {
			goto pop_and_true;
		}

		/* follow prototype chain */
	} while (--sanity > 0);

	if (sanity == 0) {
		DUK_ERROR(thr, DUK_ERR_INTERNAL_ERROR, "instanceof prototype chain sanity exceeded");
	}
	DUK_NEVER_HERE();

 pop_and_false:
	duk_pop_2(ctx);
	return 0;

 pop_and_true:
	duk_pop_2(ctx);
	return 1;
}

/*
 *  in
 */

/*
 *  E5 Sections 11.8.7, 8.12.6.
 *
 *  Basically just a property existence check using [[HasProperty]].
 *
 *  FIXME: TypeError descriptions are bad (automatic from API).
 */
	
int duk_js_in(duk_hthread *thr, duk_tval *tv_x, duk_tval *tv_y) {
	duk_context *ctx = (duk_context *) thr;
	int retval;

	/*
	 *  Get the values onto the stack first.  It would be possible to cover
	 *  some normal cases without resorting to the value stack (e.g. if
	 *  lval is already a string).
	 */

	duk_push_tval(ctx, tv_x);
	duk_push_tval(ctx, tv_y);
	(void) duk_require_hobject(ctx, -1);  /* TypeError if rval not object */
	duk_to_string(ctx, -2);               /* coerce lval with ToString() */

	retval = duk_hobject_hasprop(thr, duk_get_tval(ctx, -1), duk_get_tval(ctx, -2));

	duk_pop_2(ctx);
	return retval;
}

/*
 *  typeof
 *
 *  E5 Section 11.4.3.
 *
 *  Very straightforward.  The only question is what to return for our
 *  non-standard tag / object types.
 *
 *  There is an unfortunate string constant define naming problem with
 *  typeof return values for e.g. "Object" and "object"; careful with
 *  the built-in string defines.  The LC_XXX defines are used for the
 *  lowercase variants now.
 */

duk_hstring *duk_js_typeof(duk_hthread *thr, duk_tval *tv_x) {
	int idx = 0;

	switch (DUK_TVAL_GET_TAG(tv_x)) {
	case DUK_TAG_UNDEFINED: {
		idx = DUK_STRIDX_UNDEFINED;
		break;
	}
	case DUK_TAG_NULL: {
		/* Note: not a typo, "object" is returned for a null value */
		idx = DUK_STRIDX_LC_OBJECT;
		break;
	}
	case DUK_TAG_BOOLEAN: {
		idx = DUK_STRIDX_LC_BOOLEAN;
		break;
	}
	case DUK_TAG_POINTER: {
		/* implementation specific */
		idx = DUK_STRIDX_POINTER;
		break;
	}
	case DUK_TAG_STRING: {
		idx = DUK_STRIDX_LC_STRING;
		break;
	}
	case DUK_TAG_OBJECT: {
		duk_hobject *obj = DUK_TVAL_GET_OBJECT(tv_x);
		DUK_ASSERT(obj != NULL);
		if (DUK_HOBJECT_IS_CALLABLE(obj)) {
			idx = DUK_STRIDX_LC_FUNCTION;
		} else {
			idx = DUK_STRIDX_LC_OBJECT;
		}
		break;
	}
	case DUK_TAG_BUFFER: {
		/* implementation specific */
		idx = DUK_STRIDX_BUFFER;
		break;
	}
	default: {
		/* number */
		DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv_x));
		idx = DUK_STRIDX_LC_NUMBER;
		break;
	}
	}

	DUK_ASSERT(idx >= 0 && idx < DUK_HEAP_NUM_STRINGS);
	return thr->strs[idx];
}

/*
 *  Array index and length
 *
 *  Array index: E5 Section 15.4
 *  Array length: E5 Section 15.4.5.1 steps 3.c - 3.d (array length write)
 *
 *  The DUK_HSTRING_GET_ARRIDX_SLOW() and DUK_HSTRING_GET_ARRIDX_FAST() macros
 *  duk_js_to_arrayindex_string_helper().
 */

static int raw_string_to_arrayindex(duk_u8 *str, duk_u32 blen, duk_u32 *out_idx) {
	char buf[16];

	/*
	 *  FIXME: placeholder, which is not even close.
	 */

	if (blen > 10) {
		return 0;
	}
	memcpy(buf, str, blen);
	buf[blen] = (char) 0;

	if (sscanf(buf, "%d", (int *) out_idx) == 1 && strstr(buf, ".") == NULL) {
		return 1;
	}
	return 0;
}

/* Called by duk_heap_stringtable.c for string interning */
int duk_js_is_arrayindex_raw_string(duk_u8 *str, duk_u32 blen) {
	duk_u32 dummy;

	/*
	 *  All array indexes must match [0-9]{1,10}.
	 *
	 *  Use a subset of this for a quick and short reject.
	 *  (Note: cannot require that first char be [1-9] because
	 *  '0' is a valid index.)
	 */

	if (blen == 0 || blen > 10) {
		return 0;
	}
	if (str[0] < (duk_u8) '0' || str[0] > (duk_u8) '9') {
		/* just check the first char; it's usually not a digit
		 * for non-numbers.
		 */
		return 0;
	}

	/*
	 *  Passed quick reject check, must parse and validate
	 */

	return raw_string_to_arrayindex(str, blen, &dummy);
}	

/* Called by duk_hstring.h macros */
duk_u32 duk_js_to_arrayindex_string_helper(duk_hstring *h) {
	duk_u32 res;
	int rc;

	if (!DUK_HSTRING_HAS_ARRIDX(h)) {
		return DUK_HSTRING_NO_ARRAY_INDEX;
	}
	rc = raw_string_to_arrayindex(DUK_HSTRING_GET_DATA(h),
	                              DUK_HSTRING_GET_BYTELEN(h),
	                              &res);
	DUK_UNREF(rc);
	DUK_ASSERT(rc != 0);
	return res;
}

#line 1 "duk_js_var.c"
/*
 *  Identifier access and function closure handling.
 *
 *  Provides the primitives for slow path identifier accesses: GETVAR,
 *  PUTVAR, DELVAR, etc.  The fast path, direct register accesses, should
 *  be used for most identifier accesses.  Consequently, these slow path
 *  primitives should be optimized for maximum compactness.
 *
 *  Ecmascript environment records (declarative and object) are represented
 *  as internal objects with control keys.  Environment records have a
 *  parent record ("outer environment reference") which is represented by
 *  the implicit prototype for technical reasons (in other words, it is a
 *  convenient field).  The prototype chain is not followed in the ordinary
 *  sense for variable lookups.
 *
 *  See identifier-design.txt and identifier-algorithms.txt for more details
 *  on identifier handling, and function-objects.txt for details on what
 *  function instances are expected to look like.
 *
 *  Care must be taken to avoid duk_tval pointer invalidation caused by
 *  e.g. value stack or object resizing.
 *
 *  FIXME: properties for function instances could be initialized much more
 *  efficiently by creating a property allocation for a certain size and
 *  filling in keys and values directly (and INCREFing both with "bulk incref"
 *  primitives.
 *
 *  FIXME: duk_hobject_getprop() and duk_hobject_putprop() calls are a bit
 *  awkward (especially because they follow the prototype chain); rework
 *  if "raw" own property helpers are added.
 */

/* include removed: duk_internal.h */

/*
 *  Local result type for get_identifier_reference() lookup.
 */

typedef struct {
	duk_hobject *holder;      /* for object-bound identifiers */
	duk_tval *value;          /* for register-bound identifiers */
	duk_tval *this_binding;
	duk_hobject *env;
} duk_id_lookup_result;

/*
 *  Create a new function object based on a "template function"
 *  which contains compiled bytecode, constants, etc, but lacks
 *  a lexical environment.
 *
 *  Ecmascript requires that each created closure is a separate
 *  object, with its own set of editable properties.  However,
 *  structured property values (such as the formal arguments
 *  list and the variable map) are shared.  Also the bytecode,
 *  constants, and inner functions are shared.
 *
 *  See E5 Section 13.2 for detailed requirements on the function
 *  objects; there are no similar requirements for function
 *  "templates" which are an implementation dependent feature.
 *  Also see function-objects.txt for a discussion on the function
 *  instance properties provided by this implementation.
 *
 *  Notes:
 *
 *   * Order of internal properties should match frequency of
 *     use, since the properties will be linearly scanned on
 *     lookup (functions usually don't have enough properties
 *     to warrant a hash part).
 *
 *   * The created closure is independent of its template;
 *     they do share the same 'data' buffer object, but the
 *     template object itself can be freed even if the closure
 *     object remains reachable.
 */

/* FIXME: need to recheck the resulting Function instance objects for
 * compliance to E5 Section 13.2.
 */

static void increase_data_inner_refcounts(duk_hthread *thr, duk_hcompiledfunction *f) {
	duk_tval *tv, *tv_end;
	duk_hobject **funcs, **funcs_end;

	DUK_ASSERT(f->data != NULL);  /* compiled functions must be created 'atomically' */

	tv = DUK_HCOMPILEDFUNCTION_GET_CONSTS_BASE(f);
	tv_end = DUK_HCOMPILEDFUNCTION_GET_CONSTS_END(f);
	while (tv < tv_end) {
		DUK_TVAL_INCREF(thr, tv);
		tv++;
	}

	funcs = DUK_HCOMPILEDFUNCTION_GET_FUNCS_BASE(f);
	funcs_end = DUK_HCOMPILEDFUNCTION_GET_FUNCS_END(f);
	while (funcs < funcs_end) {
		DUK_HEAPHDR_INCREF(thr, (duk_heaphdr *) *funcs);
		funcs++;
	}
}

/* Push a new closure on the stack.
 *
 * Note: if fun_temp has NEWENV, i.e. a new lexical and variable
 * declaration is created when the function is called, only
 * outer_lex_env matters (outer_var_env is ignored and may or
 * may not be same as outer_lex_env).
 */
void duk_js_push_closure(duk_hthread *thr,
                         duk_hcompiledfunction *fun_temp,
                         duk_hobject *outer_var_env,
                         duk_hobject *outer_lex_env) {
	duk_context *ctx = (duk_context *) thr;
	duk_hcompiledfunction *fun_clos;
	duk_u16 proplist[] = { DUK_STRIDX_INT_VARMAP,
	                       DUK_STRIDX_INT_FORMALS,
	                       DUK_STRIDX_INT_SOURCE,
	                       DUK_STRIDX_NAME,
	                       DUK_STRIDX_INT_PC2LINE,
	                       DUK_STRIDX_INT_FILENAME };  /* order: most frequent to least frequent */
	int i;
	duk_u32 len_value;

	DUK_ASSERT(fun_temp != NULL);
	DUK_ASSERT(fun_temp->data != NULL);
	DUK_ASSERT(fun_temp->funcs != NULL);
	DUK_ASSERT(fun_temp->bytecode != NULL);
	DUK_ASSERT(outer_var_env != NULL);
	DUK_ASSERT(outer_lex_env != NULL);

	duk_push_compiledfunction(ctx);
	duk_push_hobject(ctx, &fun_temp->obj);  /* -> [ ... closure template ] */

	fun_clos = (duk_hcompiledfunction *) duk_get_hobject(ctx, -2);  /* FIXME: duk_get_hcompiledfunction */
	DUK_ASSERT(DUK_HOBJECT_IS_COMPILEDFUNCTION((duk_hobject *) fun_clos));
	DUK_ASSERT(fun_clos != NULL);
	DUK_ASSERT(fun_clos->data == NULL);
	DUK_ASSERT(fun_clos->funcs == NULL);
	DUK_ASSERT(fun_clos->bytecode == NULL);

	fun_clos->data = fun_temp->data;
	fun_clos->funcs = fun_temp->funcs;
	fun_clos->bytecode = fun_temp->bytecode;

	/* Note: all references inside 'data' need to get their refcounts
	 * upped too.  This is the case because refcounts are decreased
	 * through every function referencing 'data' independently.
	 */

	DUK_HBUFFER_INCREF(thr, fun_clos->data);
	increase_data_inner_refcounts(thr, fun_temp);

	fun_clos->nregs = fun_temp->nregs;
	fun_clos->nargs = fun_temp->nargs;

	DUK_ASSERT(fun_clos->data != NULL);
	DUK_ASSERT(fun_clos->funcs != NULL);
	DUK_ASSERT(fun_clos->bytecode != NULL);

	/* XXX: or copy from template? */
	DUK_HOBJECT_SET_PROTOTYPE(thr, &fun_clos->obj, thr->builtins[DUK_BIDX_FUNCTION_PROTOTYPE]);  /* contains incref */

	/*
	 *  Init/assert flags, copying them where appropriate.
	 *  Some flags (like NEWENV) are processed separately below.
	 */

	/* FIXME: copy flags using a mask */

	DUK_ASSERT(DUK_HOBJECT_HAS_EXTENSIBLE(&fun_clos->obj));
	DUK_HOBJECT_SET_CONSTRUCTABLE(&fun_clos->obj);  /* Note: not set in template (has no "prototype") */
	DUK_ASSERT(DUK_HOBJECT_HAS_CONSTRUCTABLE(&fun_clos->obj));
	DUK_ASSERT(!DUK_HOBJECT_HAS_BOUND(&fun_clos->obj));
	DUK_ASSERT(DUK_HOBJECT_HAS_COMPILEDFUNCTION(&fun_clos->obj));
	DUK_ASSERT(!DUK_HOBJECT_HAS_NATIVEFUNCTION(&fun_clos->obj));
	DUK_ASSERT(!DUK_HOBJECT_HAS_THREAD(&fun_clos->obj));
	/* DUK_HOBJECT_FLAG_ARRAY_PART: don't care */
	if (DUK_HOBJECT_HAS_STRICT(&fun_temp->obj)) {
		DUK_HOBJECT_SET_STRICT(&fun_clos->obj);
	}
	/* DUK_HOBJECT_FLAG_NEWENV: handled below */
	DUK_ASSERT(!DUK_HOBJECT_HAS_NAMEBINDING(&fun_clos->obj));
	if (DUK_HOBJECT_HAS_CREATEARGS(&fun_temp->obj)) {
		DUK_HOBJECT_SET_CREATEARGS(&fun_clos->obj);
	}
	DUK_ASSERT(!DUK_HOBJECT_HAS_SPECIAL_ARRAY(&fun_clos->obj));
	DUK_ASSERT(!DUK_HOBJECT_HAS_SPECIAL_STRINGOBJ(&fun_clos->obj));
	DUK_ASSERT(!DUK_HOBJECT_HAS_SPECIAL_ARGUMENTS(&fun_clos->obj));

	/*
	 *  Setup environment record properties based on the template
	 *  and its flags.
	 *
	 *  If DUK_HOBJECT_HAS_NEWENV(fun_temp) is true, the environment
	 *  records represent identifiers "outside" the function; the
	 *  "inner" environment records are created on demand.  Otherwise,
	 *  the environment records are those that will be directly used
	 *  (e.g. for declarations).
	 *
	 *  _lexenv is always set; _varenv defaults to _lexenv if missing,
	 *  so _varenv is only set if _lexenv != _varenv.
	 *
	 *  This is relatively complex, see doc/identifier-handling.txt.
	 */

	if (DUK_HOBJECT_HAS_NEWENV(&fun_temp->obj)) {
		DUK_HOBJECT_SET_NEWENV(&fun_clos->obj);

		if (DUK_HOBJECT_HAS_NAMEBINDING(&fun_temp->obj)) {
			duk_hobject *proto;
			duk_hobject *env;

			/*
			 *  Named function expression, name needs to be bound
			 *  in an intermediate environment record.  The "outer"
			 *  lexical/variable environment will thus be:
			 *
			 *  a) { funcname: <func>, _prototype: outer_lex_env }
			 *  b) { funcname: <func>, _prototype:  <globalenv> }  (if outer_lex_env missing)
			 */

			DUK_ASSERT(duk_has_prop_stridx(ctx, -1, DUK_STRIDX_NAME));  /* required if NAMEBINDING set */

			if (outer_lex_env) {
				proto = outer_lex_env;
			} else {
				proto = thr->builtins[DUK_BIDX_GLOBAL_ENV];
			}

			/* -> [ ... closure template env ] */
			(void) duk_push_object_helper(ctx,
	   		                              DUK_HOBJECT_FLAG_EXTENSIBLE |
			                              DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_DECENV),
			                              -1);  /* no prototype, updated below */

			duk_get_prop_stridx(ctx, -2, DUK_STRIDX_NAME);       /* -> [ ... closure template env funcname ] */
			duk_dup(ctx, -4);                                    /* -> [ ... closure template env funcname closure ] */
			duk_def_prop(ctx, -3, DUK_PROPDESC_FLAGS_NONE);      /* -> [ ... closure template env ] */

			env = duk_get_hobject(ctx, -1);
			DUK_ASSERT(env != NULL);
			DUK_ASSERT(proto != NULL);
			DUK_HOBJECT_SET_PROTOTYPE(thr, env, proto);  /* increases 'proto' refcount */

			/* [ ... closure template env ] */

			duk_def_prop_stridx(ctx, -3, DUK_STRIDX_INT_LEXENV, DUK_PROPDESC_FLAGS_WC);
			/* since closure has NEWENV, never define DUK_STRIDX_INT_VARENV, as it
			 * will be ignored anyway
			 */

			/* [ ... closure template ] */
		} else {
			/*
			 *  Other cases (function declaration, anonymous function expression,
			 *  strict direct eval code).  The "outer" environment will be whatever
			 *  the caller gave us.
			 */

			duk_push_hobject(ctx, outer_lex_env);  /* -> [ ... closure template env ] */
			duk_def_prop_stridx(ctx, -3, DUK_STRIDX_INT_LEXENV, DUK_PROPDESC_FLAGS_WC);
			/* since closure has NEWENV, never define DUK_STRIDX_INT_VARENV, as it
			 * will be ignored anyway
			 */

			/* [ ... closure template ] */
		}
	} else {
		/*
		 *  Function gets no new environment when called.  This is the
		 *  case for global code, indirect eval code, and non-strict
		 *  direct eval code.  There is no direct correspondence to the
		 *  E5 specification, as global/eval code is not exposed as a
		 *  function.
		 */

		DUK_ASSERT(!DUK_HOBJECT_HAS_NAMEBINDING(&fun_temp->obj));

		duk_push_hobject(ctx, outer_lex_env);  /* -> [ ... closure template env ] */
		duk_def_prop_stridx(ctx, -3, DUK_STRIDX_INT_LEXENV, DUK_PROPDESC_FLAGS_WC);

		if (outer_var_env != outer_lex_env) {
			duk_push_hobject(ctx, outer_var_env);  /* -> [ ... closure template env ] */
			duk_def_prop_stridx(ctx, -3, DUK_STRIDX_INT_VARENV, DUK_PROPDESC_FLAGS_WC);
		}
	}
#ifdef DUK_USE_DDDEBUG
	duk_get_prop_stridx(ctx, -2, DUK_STRIDX_INT_VARENV);
	duk_get_prop_stridx(ctx, -3, DUK_STRIDX_INT_LEXENV);
	DUK_DDDPRINT("closure varenv -> %!ipT, lexenv -> %!ipT", duk_get_tval(ctx, -2), duk_get_tval(ctx, -1));
	duk_pop_2(ctx);
#endif

	/*
	 *  Copy some internal properties directly
	 */

	/* [ ... closure template ] */

	DUK_DDDPRINT("copying properties: closure=%!iT, template=%!iT", duk_get_tval(ctx, -2), duk_get_tval(ctx, -1));

	for (i = 0; i < sizeof(proplist) / sizeof(duk_u16); i++) {
		int stridx = (int) proplist[i];
		if (duk_get_prop_stridx(ctx, -1, stridx)) {
			/* [ ... closure template val ] */
			DUK_DDDPRINT("copying property, stridx=%d -> found", stridx);
			duk_def_prop_stridx(ctx, -3, stridx, DUK_PROPDESC_FLAGS_WC);
		} else {
			DUK_DDDPRINT("copying property, stridx=%d -> not found", stridx);
			duk_pop(ctx);
		}
	}

	/*
	 *  "length" maps to number of formals (E5 Section 13.2) for
	 *  function declarations/expressions (non-bound functions).
	 *  Note that 'nargs' is NOT necessarily equal to the number
	 *  of arguments.
	 */

	/* [ ... closure template ] */

	len_value = 0;

	/* FIXME: use helper for size optimization */
	if (duk_get_prop_stridx(ctx, -2, DUK_STRIDX_INT_FORMALS)) {
		/* [ ... closure template formals ] */
		DUK_ASSERT(duk_has_prop_stridx(ctx, -1, DUK_STRIDX_LENGTH));
		duk_get_prop_stridx(ctx, -1, DUK_STRIDX_LENGTH);
		DUK_ASSERT(duk_is_number(ctx, -1));
		len_value = duk_to_int(ctx, -1);
		duk_pop_2(ctx);
	} else {
		duk_pop(ctx);  /* FIXME: this is tedious.. another wrapper function? */
		/* FIXME: what to do if _formals is not empty but compiler has optimized
		 * it away -- read length from an explicit property then?
		 */
	}

	duk_push_int(ctx, len_value);  /* [ ... closure template len_value ] */
	duk_def_prop_stridx(ctx, -3, DUK_STRIDX_LENGTH, DUK_PROPDESC_FLAGS_NONE);

	/*
	 *  "prototype" is, by default, a fresh object with the "constructor"
	 *  property.
	 *
	 *  Note that this creates a circular reference for every function
	 *  instance (closure) which prevents refcount-based collection of
	 *  function instances.
	 *
	 *  FIXME: Try to avoid creating the default prototype object, because
	 *  many functions are not used as constructors and the default
	 *  prototype is unnecessary.  Perhaps it could be created on-demand
	 *  when it is first accessed?
	 */

	/* [ ... closure template ] */

	duk_push_object(ctx);  /* -> [ ... closure template newobj ] */
	duk_dup(ctx, -3);          /* -> [ ... closure template newobj closure ] */
	duk_def_prop_stridx(ctx, -2, DUK_STRIDX_CONSTRUCTOR, DUK_PROPDESC_FLAGS_WC);  /* -> [ ... closure template newobj ] */
	duk_def_prop_stridx(ctx, -3, DUK_STRIDX_PROTOTYPE, DUK_PROPDESC_FLAGS_W);     /* -> [ ... closure template ] */

	/*
	 *  "arguments" and "caller" must be mapped to throwers for
	 *  strict mode and bound functions (E5 Section 15.3.5).
	 *
	 *  FIXME: This is expensive to have for every strict function instance.
	 *  Try to implement as virtual properties or on-demand created properties.
	 */

	/* [ ... closure template ] */

	if (DUK_HOBJECT_HAS_STRICT(&fun_clos->obj)) {
		duk_def_prop_stridx_thrower(ctx, -2, DUK_STRIDX_CALLER, DUK_PROPDESC_FLAGS_NONE);
		duk_def_prop_stridx_thrower(ctx, -2, DUK_STRIDX_LC_ARGUMENTS, DUK_PROPDESC_FLAGS_NONE);
	}

	/*
	 *  "name" is a non-standard property found in at least V8, Rhino, smjs.
	 *  For Rhino and smjs it is non-writable, non-enumerable, and non-configurable;
	 *  for V8 it is writable, non-enumerable, non-configurable.  It is also defined
	 *  for an anonymous function expression in which case the value is an empty string.
	 *
	 *  FIXME: make optional?  costs something per function.
	 */

	/* [ ... closure template ] */

	if (duk_get_prop_stridx(ctx, -1, DUK_STRIDX_NAME)) {
		/* [ ... closure template name ] */
		DUK_ASSERT(duk_is_string(ctx, -1));
	} else {
		/* [ ... closure template undefined ] */
		duk_pop(ctx);
		duk_push_hstring_stridx(ctx, DUK_STRIDX_EMPTY_STRING);
	}
	duk_def_prop_stridx(ctx, -3, DUK_STRIDX_NAME, DUK_PROPDESC_FLAGS_NONE);  /* -> [ ... closure template ] */

	/*
	 *  Some assertions (E5 Section 13.2).
	 */

	DUK_ASSERT(DUK_HOBJECT_GET_CLASS_NUMBER(&fun_clos->obj) == DUK_HOBJECT_CLASS_FUNCTION);
	DUK_ASSERT(fun_clos->obj.prototype == thr->builtins[DUK_BIDX_FUNCTION_PROTOTYPE]);
	DUK_ASSERT(DUK_HOBJECT_HAS_EXTENSIBLE(&fun_clos->obj));
	DUK_ASSERT(duk_has_prop_stridx(ctx, -2, DUK_STRIDX_LENGTH) != 0);
	DUK_ASSERT(duk_has_prop_stridx(ctx, -2, DUK_STRIDX_PROTOTYPE) != 0);
	DUK_ASSERT(duk_has_prop_stridx(ctx, -2, DUK_STRIDX_NAME) != 0);  /* non-standard */
	DUK_ASSERT(!DUK_HOBJECT_HAS_STRICT(&fun_clos->obj) ||
	           duk_has_prop_stridx(ctx, -2, DUK_STRIDX_CALLER) != 0);
	DUK_ASSERT(!DUK_HOBJECT_HAS_STRICT(&fun_clos->obj) ||
	           duk_has_prop_stridx(ctx, -2, DUK_STRIDX_LC_ARGUMENTS) != 0);

	/*
	 *  Finish
	 */
	
	/* [ ... closure template ] */

	DUK_DDDPRINT("created function instance: template=%!iT -> closure=%!iT",
	             duk_get_tval(ctx, -1), duk_get_tval(ctx, -2));

	duk_pop(ctx);

	/* [ ... closure ] */
}

/*
 *  Delayed activation environment record initialization (for functions
 *  with NEWENV).
 *
 *  The non-delayed initialization is handled by duk_handle_call().
 */

/* shared helper */
duk_hobject *duk_create_activation_environment_record(duk_hthread *thr,
                                                      duk_hobject *func,
                                                      duk_u32 idx_bottom) {
	duk_context *ctx = (duk_context *) thr;
	duk_hobject *env;
	duk_hobject *parent;
	duk_tval *tv;

	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(func != NULL);

	tv = duk_hobject_find_existing_entry_tval_ptr(func, DUK_HEAP_STRING_INT_LEXENV(thr));
	if (tv) {
		DUK_ASSERT(DUK_TVAL_IS_OBJECT(tv));
		DUK_ASSERT(DUK_HOBJECT_IS_ENV(DUK_TVAL_GET_OBJECT(tv)));
		parent = DUK_TVAL_GET_OBJECT(tv);
	} else {
		parent = thr->builtins[DUK_BIDX_GLOBAL_ENV];
	}

	(void) duk_push_object_helper(ctx,
	                              DUK_HOBJECT_FLAG_EXTENSIBLE |
	                              DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_DECENV),
	                              -1);  /* no prototype, updated below */
	env = duk_require_hobject(ctx, -1);
	DUK_ASSERT(env != NULL);
	DUK_HOBJECT_SET_PROTOTYPE(thr, env, parent);  /* parent env is the prototype, updates refcounts */

	/* open scope information, for compiled functions only */

	if (DUK_HOBJECT_IS_COMPILEDFUNCTION(func)) {
		/* FIXME: duk_push_hthread etc -> macros at least */
		duk_push_hobject(ctx, (duk_hobject *) thr);
		duk_def_prop_stridx(ctx, -2, DUK_STRIDX_INT_THREAD, DUK_PROPDESC_FLAGS_WEC);
		duk_push_hobject(ctx, (duk_hobject *) func);
		duk_def_prop_stridx(ctx, -2, DUK_STRIDX_INT_CALLEE, DUK_PROPDESC_FLAGS_WEC);
		duk_push_int(ctx, idx_bottom);  /* FIXME: type */
		duk_def_prop_stridx(ctx, -2, DUK_STRIDX_INT_REGBASE, DUK_PROPDESC_FLAGS_WEC);
	}

	return env;
}

void duk_js_init_activation_environment_records_delayed(duk_hthread *thr,
                                                        duk_activation *act) {
	duk_context *ctx = (duk_context *) thr;
	duk_hobject *func;
	duk_hobject *env;

	func = act->func;
	DUK_ASSERT(func != NULL);
	DUK_ASSERT(!DUK_HOBJECT_HAS_BOUND(func));  /* bound functions are never in act->func */

	/*
	 *  Delayed initialization only occurs for 'NEWENV' functions.
	 */

	DUK_ASSERT(DUK_HOBJECT_HAS_NEWENV(func));
	DUK_ASSERT(act->lex_env == NULL);
	DUK_ASSERT(act->var_env == NULL);

	env = duk_create_activation_environment_record(thr, func, act->idx_bottom);
	DUK_ASSERT(env != NULL);

	DUK_DDDPRINT("created delayed fresh env: %!ipO", env);
#ifdef DUK_USE_DDDEBUG
	{
		duk_hobject *p = env;
		while (p) {
			DUK_DDDPRINT("  -> %!ipO", p);
			p = p->prototype;
		}
	}
#endif

	act->lex_env = env;
	act->var_env = env;
	DUK_HOBJECT_INCREF(thr, env);  /* FIXME: incref by count (here 2 times) */
	DUK_HOBJECT_INCREF(thr, env);

	duk_pop(ctx);
}

/*
 *  Closing environment records.
 *
 *  The environment record MUST be closed with the thread where its activation
 *  is.  In other words (if 'env' is open):
 *
 *    - 'thr' must match _env.thread
 *    - 'func' must match _env.callee
 *    - 'regbase' must match _env.regbase
 *
 *  These are not looked up from the env to minimize code size.
 *
 *  FIXME: should access the own properties directly instead of using the API
 */

void duk_js_close_environment_record(duk_hthread *thr, duk_hobject *env, duk_hobject *func, int regbase) {
	duk_context *ctx = (duk_context *) thr;
	int i;

	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(env != NULL);
	DUK_ASSERT(func != NULL);

	if (!DUK_HOBJECT_IS_DECENV(env) || DUK_HOBJECT_HAS_ENVRECCLOSED(env)) {
		DUK_DDDPRINT("environment record not a declarative record, or already closed: %!iO", env);
		return;
	}

	DUK_DDDPRINT("closing environment record: %!iO, func: %!iO, regbase: %d", env, func, regbase);

	duk_push_hobject(ctx, env);

	/* assertions: env must be closed in the same thread as where it runs */
#ifdef DUK_USE_ASSERTIONS
	{
		/* [... env] */

		if (duk_get_prop_stridx(ctx, -1, DUK_STRIDX_INT_CALLEE)) {
			DUK_ASSERT(duk_is_object(ctx, -1));
			DUK_ASSERT(duk_get_hobject(ctx, -1) == (duk_hobject *) func);
		}
		duk_pop(ctx);

		if (duk_get_prop_stridx(ctx, -1, DUK_STRIDX_INT_THREAD)) {
			DUK_ASSERT(duk_is_object(ctx, -1));
			DUK_ASSERT(duk_get_hobject(ctx, -1) == (duk_hobject *) thr);
		}
		duk_pop(ctx);

		if (duk_get_prop_stridx(ctx, -1, DUK_STRIDX_INT_REGBASE)) {
			DUK_ASSERT(duk_is_number(ctx, -1));
			DUK_ASSERT(duk_get_number(ctx, -1) == (double) regbase);
		}
		duk_pop(ctx);

		/* [... env] */
	}
#endif

	if (DUK_HOBJECT_IS_COMPILEDFUNCTION(func)) {
		duk_hobject *varmap;
		duk_hstring *key;
		duk_tval *tv;
		int regnum;

		/* FIXME: additional conditions when to close variables? we don't want to do it
		 * unless the environment may have "escaped" (referenced in a function closure).
		 * With delayed environments, the existence is probably good enough of a check.
		 */

		/* FIXME: any way to detect faster whether something needs to be closed?
		 * We now look up _callee and then skip the rest.
		 */

		/* Note: we rely on the _varmap having a bunch of nice properties, like:
		 *  - being compacted and unmodified during this process
		 *  - not containing an array part
		 *  - having correct value types
		 */

		/* [... env] */

		if (!duk_get_prop_stridx(ctx, -1, DUK_STRIDX_INT_CALLEE)) {
			DUK_DDDPRINT("env has no callee property, nothing to close; re-delete the control properties just in case");
			duk_pop(ctx);
			goto skip_varmap;
		}

		/* [... env callee] */

		if (!duk_get_prop_stridx(ctx, -1, DUK_STRIDX_INT_VARMAP)) {
			DUK_DDDPRINT("callee has no varmap property, nothing to close; delete the control properties");
			duk_pop_2(ctx);
			goto skip_varmap;
		}
		varmap = duk_require_hobject(ctx, -1);
		DUK_ASSERT(varmap != NULL);

		DUK_DDDPRINT("varmap: %!O", varmap);

		/* [... env callee varmap] */

		DUK_DDDPRINT("copying bound register values, %d bound regs", varmap->e_used);

		for (i = 0; i < varmap->e_used; i++) {
			key = DUK_HOBJECT_E_GET_KEY(varmap, i);
			DUK_ASSERT(key != NULL);   /* assume keys are compacted */

			DUK_ASSERT(!DUK_HOBJECT_E_SLOT_IS_ACCESSOR(varmap, i));  /* assume plain values */

			tv = DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(varmap, i);
			DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv));  /* assume value is a number */
			regnum = (int) DUK_TVAL_GET_NUMBER(tv);
			DUK_ASSERT(regnum >= 0 && regnum < ((duk_hcompiledfunction *) func)->nregs);  /* regnum is sane */
			DUK_ASSERT(thr->valstack + regbase + regnum >= thr->valstack);
			DUK_ASSERT(thr->valstack + regbase + regnum < thr->valstack_top);

			/* XXX: slightly awkward */
			duk_push_hstring(ctx, key);
			duk_push_tval(ctx, thr->valstack + regbase + regnum);
			DUK_DDDPRINT("closing identifier '%s' -> reg %d, value %!T",
			             duk_get_string(ctx, -2), regnum, duk_get_tval(ctx, -1));

			/* [... env callee varmap key val] */

			/* if property already exists, overwrites silently */
			duk_def_prop(ctx, -5, DUK_PROPDESC_FLAGS_WE);  /* writable but not deletable */
		}

		duk_pop_2(ctx);

		/* [... env] */
	}

 skip_varmap:

	/* [... env] */

	duk_del_prop_stridx(ctx, -1, DUK_STRIDX_INT_CALLEE);
	duk_del_prop_stridx(ctx, -1, DUK_STRIDX_INT_THREAD);
	duk_del_prop_stridx(ctx, -1, DUK_STRIDX_INT_REGBASE);

	duk_pop(ctx);

	DUK_HOBJECT_SET_ENVRECCLOSED(env);

	DUK_DDDPRINT("environment record after being closed: %!O", env);
}

/*
 *  GETIDREF: a GetIdentifierReference-like helper.
 *
 *  Provides a parent traversing lookup and a single level lookup
 *  (for HasBinding).
 *
 *  Instead of returning the value, returns a bunch of values allowing
 *  the caller to read, write, or delete the binding.  Value pointers
 *  are duk_tval pointers which can be mutated directly as long as
 *  refcounts are properly updated.  Note that any operation which may
 *  reallocate valstacks or compact objects may invalidate the returned
 *  duk_tval (but not object) pointers, so caller must be very careful.
 *
 *  If starting environment record 'env' is given, 'act' is ignored.
 *  However, if 'env' is NULL, the caller may identify, in 'act', an
 *  activation which hasn't had its declarative environment initialized
 *  yet.  The activation registers are then looked up, and its parent
 *  traversed normally.
 *
 *  The 'out' structure values are only valid if the function returns
 *  success (non-zero).
 */

/* lookup name from an open declarative record's registers */
static int get_identifier_open_decl_env_regs(duk_hthread *thr,
                                             duk_hstring *name,
                                             duk_hobject *env,
                                             duk_id_lookup_result *out) {
	duk_hthread *env_thr;
	duk_hobject *env_func;
	int env_regbase;
	duk_hobject *varmap;
	duk_tval *tv;
	int reg_rel;
	int idx;

	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(name != NULL);
	DUK_ASSERT(env != NULL);
	DUK_ASSERT(out != NULL);

	DUK_ASSERT(DUK_HOBJECT_IS_DECENV(env));

	tv = duk_hobject_find_existing_entry_tval_ptr(env, DUK_HEAP_STRING_INT_CALLEE(thr));
	if (!tv) {
		/* env is closed, should be missing _callee, _thread, _regbase */
		DUK_ASSERT(duk_hobject_find_existing_entry_tval_ptr(env, DUK_HEAP_STRING_INT_CALLEE(thr)) == NULL);
		DUK_ASSERT(duk_hobject_find_existing_entry_tval_ptr(env, DUK_HEAP_STRING_INT_THREAD(thr)) == NULL);
		DUK_ASSERT(duk_hobject_find_existing_entry_tval_ptr(env, DUK_HEAP_STRING_INT_REGBASE(thr)) == NULL);
		return 0;
	}

	DUK_ASSERT(DUK_TVAL_IS_OBJECT(tv));
	DUK_ASSERT(DUK_TVAL_GET_OBJECT(tv) != NULL);
	DUK_ASSERT(DUK_HOBJECT_IS_COMPILEDFUNCTION(DUK_TVAL_GET_OBJECT(tv)));
	env_func = DUK_TVAL_GET_OBJECT(tv);
	DUK_ASSERT(env_func != NULL);

	tv = duk_hobject_find_existing_entry_tval_ptr(env_func, DUK_HTHREAD_STRING_INT_VARMAP(thr));
	if (!tv) {
		return 0;
	}
	DUK_ASSERT(DUK_TVAL_IS_OBJECT(tv));
	varmap = DUK_TVAL_GET_OBJECT(tv);
	DUK_ASSERT(varmap != NULL);

	tv = duk_hobject_find_existing_entry_tval_ptr(varmap, name);
	if (!tv) {
		return 0;
	}
	DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv));
	reg_rel = DUK_TVAL_GET_NUMBER(tv);
	DUK_ASSERT(reg_rel >= 0 && reg_rel < ((duk_hcompiledfunction *) env_func)->nregs);

	tv = duk_hobject_find_existing_entry_tval_ptr(env, DUK_HEAP_STRING_INT_THREAD(thr));
	DUK_ASSERT(tv != NULL);
	DUK_ASSERT(DUK_TVAL_IS_OBJECT(tv));
	DUK_ASSERT(DUK_TVAL_GET_OBJECT(tv) != NULL);
	DUK_ASSERT(DUK_HOBJECT_IS_THREAD(DUK_TVAL_GET_OBJECT(tv)));
	env_thr = (duk_hthread *) DUK_TVAL_GET_OBJECT(tv);
	DUK_ASSERT(env_thr != NULL);

	/* Note: env_thr != thr is quite possible and normal, so careful
	 * with what thread is used for valstack lookup.
	 */

	tv = duk_hobject_find_existing_entry_tval_ptr(env, DUK_HEAP_STRING_INT_REGBASE(thr));
	DUK_ASSERT(tv != NULL);
	DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv));
	env_regbase = DUK_TVAL_GET_NUMBER(tv);

	idx = env_regbase + reg_rel;
	tv = &env_thr->valstack[idx];
	DUK_ASSERT(tv >= env_thr->valstack && tv < env_thr->valstack_end);  /* FIXME: more accurate? */

	out->value = tv;
	out->this_binding = NULL;  /* implicit this value always undefined for
	                            * declarative environment records.
	                            */
	out->env = env;
	out->holder = NULL;

	return 1;
}

/* lookup name from current activation record's functions' registers */
static int get_identifier_activation_regs(duk_hthread *thr,
                                          duk_hstring *name,
                                          duk_activation *act,
                                          duk_id_lookup_result *out) {
	duk_tval *tv;
	duk_hobject *func;
	duk_hobject *varmap;
	int reg_rel;
	int idx;

	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(name != NULL);
	DUK_ASSERT(act != NULL);
	DUK_ASSERT(out != NULL);

	func = act->func;
	DUK_ASSERT(func != NULL);
	DUK_ASSERT(DUK_HOBJECT_HAS_NEWENV(func));

	if (!DUK_HOBJECT_IS_COMPILEDFUNCTION(func)) {
		return 0;
	}

	tv = duk_hobject_find_existing_entry_tval_ptr(func, DUK_HTHREAD_STRING_INT_VARMAP(thr));
	if (!tv) {
		return 0;
	}
	DUK_ASSERT(DUK_TVAL_IS_OBJECT(tv));
	varmap = DUK_TVAL_GET_OBJECT(tv);
	DUK_ASSERT(varmap != NULL);

	tv = duk_hobject_find_existing_entry_tval_ptr(varmap, name);
	if (!tv) {
		return 0;
	}
	DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv));
	reg_rel = DUK_TVAL_GET_NUMBER(tv);
	DUK_ASSERT(reg_rel >= 0 && reg_rel < ((duk_hcompiledfunction *) func)->nregs);

	idx = act->idx_bottom + reg_rel;
	DUK_ASSERT(idx >= act->idx_bottom);
	tv = &thr->valstack[idx];

	out->value = tv;
	out->this_binding = NULL;  /* implicit this value always undefined for
	                            * declarative environment records.
	                            */
	out->env = NULL;
	out->holder = NULL;

	return 1;
}

static int get_identifier_reference(duk_hthread *thr,
                                    duk_hobject *env,
                                    duk_hstring *name,
                                    duk_activation *act,
                                    int parents,
                                    duk_id_lookup_result *out) {
	duk_tval *tv;
	duk_u32 sanity;

	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(env != NULL || act != NULL);
	DUK_ASSERT(name != NULL);
	DUK_ASSERT(out != NULL);

	DUK_ASSERT(!env || DUK_HOBJECT_IS_ENV(env));
	DUK_ASSERT(!env || !DUK_HOBJECT_HAS_ARRAY_PART(env));

	/*
	 *  Conceptually, we look for the identifier binding by starting from
	 *  'env' and following to chain of environment records (represented
	 *  by the prototype chain).
	 *
	 *  If 'env' is NULL, the current activation does not yet have an
	 *  allocated declarative environment record; this should be treated
	 *  exactly as if the environment record existed but had no bindings
	 *  other than register bindings.
	 *
	 *  Note: we assume that with the DUK_HOBJECT_FLAG_NEWENV cleared
	 *  the environment will always be initialized immediately; hence
	 *  a NULL 'env' should only happen with the flag set.  This is the
	 *  case for: (1) function calls, and (2) strict, direct eval calls.
	 */

	if (env == NULL && act != NULL) {
		duk_hobject *func;

		DUK_DDDPRINT("get_identifier_reference: env is NULL, activation is non-NULL -> "
		             "delayed env case, look up activation regs first");

		/*
		 *  Try registers
		 */

		if (get_identifier_activation_regs(thr, name, act, out)) {
			DUK_DDDPRINT("get_identifier_reference successful: "
			             "name=%!O -> value=%!T, this=%!T, env=%!O, holder=%!O "
			             "(found from register bindings when env=NULL)",
			             (duk_heaphdr *) name, out->value, out->this_binding,
			             out->env, out->holder);
			return 1;
		}

		DUK_DDDPRINT("not found in current activation regs");

		/*
		 *  Not found in registers, proceed to the parent record.
		 *  Here we need to determine what the parent would be,
		 *  if 'env' was not NULL (i.e. same logic as when initializing
		 *  the record).
		 *
		 *  Note that environment initialization is only deferred when
		 *  DUK_HOBJECT_HAS_NEWENV is set, and this only happens for:
		 *    - Function code
		 *    - Strict eval code
		 *
		 *  We only need to check _lexenv here; _varenv exists only if it
		 *  differs from _lexenv (and thus _lexenv will also be present).
		 */

		if (!parents) {
			DUK_DDDPRINT("get_identifier_reference failed, no parent traversal "
			             "(not found from register bindings when env=NULL)");
			goto fail_not_found;
		}

		func = act->func;
		DUK_ASSERT(func != NULL);
		DUK_ASSERT(DUK_HOBJECT_HAS_NEWENV(func));

		tv = duk_hobject_find_existing_entry_tval_ptr(func, DUK_HTHREAD_STRING_INT_LEXENV(thr));
		if (tv) {
			DUK_ASSERT(DUK_TVAL_IS_OBJECT(tv));
			env = DUK_TVAL_GET_OBJECT(tv);
		} else {
			DUK_ASSERT(duk_hobject_find_existing_entry_tval_ptr(func, DUK_HTHREAD_STRING_INT_VARENV(thr)) == NULL);
			env = thr->builtins[DUK_BIDX_GLOBAL_ENV];
		}

		DUK_DDDPRINT("continue lookup from env: %!iO", env);
	}

	/*
	 *  Prototype walking starting from 'env'.
	 *
	 *  ('act' is not needed anywhere here.)
	 */

	sanity = DUK_HOBJECT_PROTOTYPE_CHAIN_SANITY;
	while (env != NULL) {
		duk_tval *tv;
		int cl;

		DUK_DDDPRINT("get_identifier_reference, name=%!O, considering env=%p -> %!iO",
		             (duk_heaphdr *) name,
		             (void *) env,
		             (duk_heaphdr *) env);

		DUK_ASSERT(env != NULL);
		DUK_ASSERT(DUK_HOBJECT_IS_ENV(env));
		DUK_ASSERT(!DUK_HOBJECT_HAS_ARRAY_PART(env));

		cl = DUK_HOBJECT_GET_CLASS_NUMBER(env);
		DUK_ASSERT(cl == DUK_HOBJECT_CLASS_OBJENV || cl == DUK_HOBJECT_CLASS_DECENV);
		if (cl == DUK_HOBJECT_CLASS_DECENV) {
			/*
			 *  Declarative environment record.
			 *
			 *  Identifiers can never be stored in ancestors and are
			 *  always plain values, so we can use an internal helper
			 *  and access the value directly with an duk_tval ptr.
			 *
			 *  A closed environment is only indicated by it missing
			 *  the "book-keeping" properties required for accessing
			 *  register-bound variables.
			 */

			if (DUK_HOBJECT_HAS_ENVRECCLOSED(env)) {
				/* already closed */
				goto skip_regs;
			}

			if (get_identifier_open_decl_env_regs(thr, name, env, out)) {
				DUK_DDDPRINT("get_identifier_reference successful: "
				             "name=%!O -> value=%!T, this=%!T, env=%!O, holder=%!O "
				             "(declarative environment record, scope open, found in regs)",
				             (duk_heaphdr *) name, out->value, out->this_binding,
				             out->env, out->holder);
				return 1;
			}
		 skip_regs:

			tv = duk_hobject_find_existing_entry_tval_ptr(env, name);
			if (tv) {
				out->value = tv;
				out->this_binding = NULL;  /* implicit this value always undefined for
				                            * declarative environment records.
				                            */
				out->env = env;
				out->holder = env;

				DUK_DDDPRINT("get_identifier_reference successful: "
				             "name=%!O -> value=%!T, this=%!T, env=%!O, holder=%!O "
				             "(declarative environment record, found in properties)",
				             (duk_heaphdr *) name, out->value, out->this_binding,
				             out->env, out->holder);
				return 1;
			}
		} else {
			/*
			 *  Object environment record.
			 *
			 *  Binding (target) object is an external, uncontrolled object.
			 *  Identifier may be bound in an ancestor property, and may be
			 *  an accessor.
			 */

			/* FIXME: we could save space by using _target OR _this.  If _target, assume
			 * this binding is undefined.  If _this, assumes this binding is _this, and
			 * target is also _this.  One property would then be enough.
			 */

			duk_hobject *target;

			DUK_ASSERT(cl == DUK_HOBJECT_CLASS_OBJENV);

			tv = duk_hobject_find_existing_entry_tval_ptr(env, DUK_HEAP_STRING_INT_TARGET(thr));
			DUK_ASSERT(tv != NULL);
			DUK_ASSERT(DUK_TVAL_IS_OBJECT(tv));
			target = DUK_TVAL_GET_OBJECT(tv);
			DUK_ASSERT(target != NULL);

			/* Note: we must traverse the prototype chain, so use an actual
			 * hasprop call here.  The property may also be an accessor, so
			 * we can't get an duk_tval pointer here.
			 *
			 * out->holder is NOT set to the actual duk_hobject where the
			 * property is found, but rather the target object.
			 */

			if (duk_hobject_hasprop_raw(thr, target, name)) {
				out->value = NULL;  /* can't get value, may be accessor */

				tv = duk_hobject_find_existing_entry_tval_ptr(env, DUK_HEAP_STRING_INT_THIS(thr));
				out->this_binding = tv;  /* may be NULL */

				out->env = env;
				out->holder = target;

				DUK_DDDPRINT("get_identifier_reference successful: "
				             "name=%!O -> value=%!T, this=%!T, env=%!O, holder=%!O "
				             "(object environment record)",
				             (duk_heaphdr *) name, out->value, out->this_binding,
				             out->env, out->holder);
				return 1;
			}
		}

		if (!parents) {
			DUK_DDDPRINT("get_identifier_reference failed, no parent traversal "
			             "(not found from first traversed env)");
			goto fail_not_found;
		}

                if (sanity-- == 0) {
                        DUK_ERROR(thr, DUK_ERR_INTERNAL_ERROR, "prototype chain max depth reached (loop?)");
                }
		env = env->prototype;
	};

	/*
	 *  Not found (even in global object)
	 */

 fail_not_found:
	return 0;
}

/*
 *  HASVAR: check identifier binding from a given environment record
 *  without traversing its parents.
 *
 *  This primitive is not exposed to user code as such, but is used
 *  internally for e.g. declaration binding instantiation.
 *
 *  See E5 Sections:
 *    10.2.1.1.1 HasBinding(N)
 *    10.2.1.2.1 HasBinding(N)
 *
 *  Note: strictness has no bearing on this check.  Hence we don't take
 *  a 'strict' parameter.
 */

int duk_js_hasvar_envrec(duk_hthread *thr,
                         duk_hobject *env,
                         duk_hstring *name) {
	duk_id_lookup_result ref;
	int parents;

	DUK_DDDPRINT("hasvar: thr=%p, env=%p, name=%!O "
	             "(env -> %!dO)",
	             (void *) thr, (void *) env, (duk_heaphdr *) name,
	             (duk_heaphdr *) env);

	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(env != NULL);
	DUK_ASSERT(name != NULL);

        DUK_ASSERT_REFCOUNT_NONZERO_HEAPHDR(env);
        DUK_ASSERT_REFCOUNT_NONZERO_HEAPHDR(name);

	DUK_ASSERT(DUK_HOBJECT_IS_ENV(env));
	DUK_ASSERT(!DUK_HOBJECT_HAS_ARRAY_PART(env));

	/* lookup results is ignored */
	parents = 0;
	return get_identifier_reference(thr, env, name, NULL, parents, &ref);
}

/*
 *  GETVAR
 *
 *  See E5 Sections:
 *    11.1.2 Identifier Reference
 *    10.3.1 Identifier Resolution
 *    11.13.1 Simple Assignment  [example of where the Reference is GetValue'd]
 *    8.7.1 GetValue (V)
 *    8.12.1 [[GetOwnProperty]] (P)
 *    8.12.2 [[GetProperty]] (P)
 *    8.12.3 [[Get]] (P)
 *
 *  If 'throw' is true, always leaves two values on top of stack: [val this].
 *
 *  If 'throw' is false, returns 0 if identifier cannot be resolved, and the
 *  stack will be unaffected in this case.  If identifier is resolved, returns
 *  1 and leaves [val this] on top of stack.
 *
 *  Note: the 'strict' flag of a reference returned by GetIdentifierReference
 *  is ignored by GetValue.  Hence we don't take a 'strict' parameter.
 *
 *  The 'throw' flag is needed for implementing 'typeof' for an unreferenced
 *  identifier.  An unreference identifier in other contexts generates a
 *  ReferenceError.
 */

static int getvar_helper(duk_hthread *thr,
                         duk_hobject *env,
                         duk_activation *act,
                         duk_hstring *name,
                         int throw) {
	duk_context *ctx = (duk_context *) thr;
	duk_id_lookup_result ref;
	duk_tval tv_tmp_obj;
	duk_tval tv_tmp_key;
	int parents;

	DUK_DDDPRINT("getvar: thr=%p, env=%p, act=%p, name=%!O "
	             "(env -> %!dO)",
	             (void *) thr, (void *) env, (void *) act,
	             (duk_heaphdr *) name, (duk_heaphdr *) env);

	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(name != NULL);
	/* env and act may be NULL */

        DUK_ASSERT_REFCOUNT_NONZERO_HEAPHDR(env);
        DUK_ASSERT_REFCOUNT_NONZERO_HEAPHDR(name);

	parents = 1;     /* follow parent chain */
	if (get_identifier_reference(thr, env, name, act, parents, &ref)) {
		if (ref.value) {
			DUK_ASSERT(ref.this_binding == NULL);  /* always for register bindings */
			duk_push_tval(ctx, ref.value);
			duk_push_undefined(ctx);
		} else {
			DUK_ASSERT(ref.holder != NULL);

			/* Note: getprop may invoke any getter and invalidate any
			 * duk_tval pointers, so this must be done first.
			 */

			if (ref.this_binding) {
				duk_push_tval(ctx, ref.this_binding);
			} else {
				duk_push_undefined(ctx);
			}

			DUK_TVAL_SET_OBJECT(&tv_tmp_obj, ref.holder);
			DUK_TVAL_SET_STRING(&tv_tmp_key, name);
			(void) duk_hobject_getprop(thr, &tv_tmp_obj, &tv_tmp_key);  /* [this value] */

			/* ref.value, ref.this.binding invalidated here by getprop call */

			duk_insert(ctx, -2);  /* [this value] -> [value this] */
		}

		return 1;
	} else {
		if (throw) {
			DUK_ERROR(thr, DUK_ERR_REFERENCE_ERROR,
			          "identifier '%s' undefined",
			          (char *) DUK_HSTRING_GET_DATA(name));
		}

		return 0;
	}
}

int duk_js_getvar_envrec(duk_hthread *thr,
                         duk_hobject *env,
                         duk_hstring *name,
                         int throw) {
	return getvar_helper(thr, env, NULL, name, throw);
}

int duk_js_getvar_activation(duk_hthread *thr,
                             duk_activation *act,
                             duk_hstring *name,
                             int throw) {
	DUK_ASSERT(act != NULL);
	return getvar_helper(thr, act->lex_env, act, name, throw);
}

/*
 *  PUTVAR
 *
 *  See E5 Sections:
 *    11.1.2 Identifier Reference
 *    10.3.1 Identifier Resolution
 *    11.13.1 Simple Assignment  [example of where the Reference is PutValue'd]
 *    8.7.2 PutValue (V,W)  [see especially step 3.b, undefined -> automatic global in non-strict mode]
 *    8.12.4 [[CanPut]] (P)
 *    8.12.5 [[Put]] (P)
 *
 *  Note: may invalidate any valstack (or object) duk_tval pointers because
 *  putting a value may reallocate any object or any valstack.  Caller beware.
 */

static void putvar_helper(duk_hthread *thr,
                          duk_hobject *env,
                          duk_activation *act,
                          duk_hstring *name,
                          duk_tval *val,
                          int strict) {
	duk_id_lookup_result ref;
	duk_tval tv_tmp_obj;
	duk_tval tv_tmp_key;
	int parents;

	DUK_DDDPRINT("putvar: thr=%p, env=%p, act=%p, name=%!O, val=%p, strict=%d "
	             "(env -> %!dO, val -> %!T)",
	             (void *) thr, (void *) env, (void *) act,
	             (duk_heaphdr *) name, (void *) val, strict,
	             (duk_heaphdr *) env, val);

	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(name != NULL);
	DUK_ASSERT(val != NULL);
	/* env and act may be NULL */

        DUK_ASSERT_REFCOUNT_NONZERO_HEAPHDR(env);
        DUK_ASSERT_REFCOUNT_NONZERO_HEAPHDR(name);
	DUK_ASSERT_REFCOUNT_NONZERO_TVAL(val);

	/*
	 *  In strict mode E5 protects 'eval' and 'arguments' from being
	 *  assigned to (or even declared anywhere).  Attempt to do so
	 *  should result in a compile time SyntaxError.  See the internal
	 *  design documentation for details.
	 *
	 *  Thus, we should never come here, run-time, for strict code,
	 *  and name 'eval' or 'arguments'.
	 */

	DUK_ASSERT(!strict ||
	           (name != DUK_HTHREAD_STRING_EVAL(thr) &&
	            name != DUK_HTHREAD_STRING_LC_ARGUMENTS(thr)));

	/*
	 *  Lookup variable and update in-place if found.
	 */

	parents = 1;     /* follow parent chain */

	if (get_identifier_reference(thr, env, name, act, parents, &ref)) {
		if (ref.value) {
			duk_tval tv_tmp;
			duk_tval *tv_val;

			DUK_ASSERT(ref.this_binding == NULL);  /* always for register bindings */

 			tv_val = ref.value;
			DUK_ASSERT(tv_val != NULL);
			DUK_TVAL_SET_TVAL(&tv_tmp, tv_val);
			DUK_TVAL_SET_TVAL(tv_val, val);
			DUK_TVAL_INCREF(thr, val);
			DUK_TVAL_DECREF(thr, &tv_tmp);  /* must be last */

			/* ref.value and ref.this_binding invalidated here */
		} else {
			DUK_ASSERT(ref.holder != NULL);

			DUK_TVAL_SET_OBJECT(&tv_tmp_obj, ref.holder);
			DUK_TVAL_SET_STRING(&tv_tmp_key, name);
			(void) duk_hobject_putprop(thr, &tv_tmp_obj, &tv_tmp_key, val, strict);

			/* ref.value and ref.this_binding invalidated here */
		}

		return;
	}
	
	/*
	 *  Not found: write to global object (non-strict) or ReferenceError
	 *  (strict); see E5 Section 8.7.2, step 3.
	 */

	if (strict) {
		DUK_DDDPRINT("identifier binding not found, strict => reference error");
		DUK_ERROR(thr, DUK_ERR_REFERENCE_ERROR, "identifier not defined");
	}

	DUK_DDDPRINT("identifier binding not found, not strict => set to global");

	DUK_TVAL_SET_OBJECT(&tv_tmp_obj, thr->builtins[DUK_BIDX_GLOBAL]);
	DUK_TVAL_SET_STRING(&tv_tmp_key, name);
	(void) duk_hobject_putprop(thr, &tv_tmp_obj, &tv_tmp_key, val, 0);  /* 0 = no throw */

	/* NB: 'val' may be invalidated here because put_value may realloc valstack,
	 * caller beware.
	 */
}

void duk_js_putvar_envrec(duk_hthread *thr,
                          duk_hobject *env,
                          duk_hstring *name,
                          duk_tval *val,
                          int strict) {
	putvar_helper(thr, env, NULL, name, val, strict);
}

void duk_js_putvar_activation(duk_hthread *thr,
                              duk_activation *act,
                              duk_hstring *name,
                              duk_tval *val,
                              int strict) {
	DUK_ASSERT(act != NULL);
	putvar_helper(thr, act->lex_env, act, name, val, strict);
}

/*
 *  DELVAR
 *
 *  See E5 Sections:
 *    11.4.1 The delete operator
 *    10.2.1.1.5 DeleteBinding (N)  [declarative environment record]
 *    10.2.1.2.5 DeleteBinding (N)  [object environment record]
 *
 *  Variable bindings established inside eval() are deletable (configurable),
 *  other bindings are not, including variables declared in global level.
 *  Registers are always non-deletable, and the deletion of other bindings
 *  is controlled by the configurable flag.
 *
 *  For strict mode code, the 'delete' operator should fail with a compile
 *  time SyntaxError if applied to identifiers.  Hence, no strict mode
 *  run-time deletion of identifiers should ever happen.  This function
 *  should never be called from strict mode code!
 */

static int delvar_helper(duk_hthread *thr,
                         duk_hobject *env,
                         duk_activation *act,
                         duk_hstring *name) {
	duk_id_lookup_result ref;
	int parents;

	DUK_DDDPRINT("delvar: thr=%p, env=%p, act=%p, name=%!O "
	             "(env -> %!dO)",
	             (void *) thr, (void *) env, (void *) act,
	             (duk_heaphdr *) name, (duk_heaphdr *) env);

	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(name != NULL);
	/* env and act may be NULL */

        DUK_ASSERT_REFCOUNT_NONZERO_HEAPHDR(name);

	parents = 1;     /* follow parent chain */

	if (get_identifier_reference(thr, env, name, act, parents, &ref)) {
		if (ref.value) {
			/* value found, but in regs (not deletable) */
			return 0;
		}
		DUK_ASSERT(ref.holder != NULL);

		return duk_hobject_delprop_raw(thr, ref.holder, name, 0);
	}

	/*
	 *  Not found (even in global object).
	 *
	 *  In non-strict mode this is a silent SUCCESS (!), see E5 Section 11.4.1,
	 *  step 3.b.  In strict mode this case is a compile time SyntaxError so
	 *  we should not come here.
	 */

	DUK_DDDPRINT("identifier to be deleted not found: name=%!O "
	             "(treated as silent success)",
	             (duk_heaphdr *) name);
	return 1;
}

int duk_js_delvar_envrec(duk_hthread *thr,
                         duk_hobject *env,
                         duk_hstring *name) {
	return delvar_helper(thr, env, NULL, name);
}
	
int duk_js_delvar_activation(duk_hthread *thr,
                             duk_activation *act,
                             duk_hstring *name) {
	DUK_ASSERT(act != NULL);
	return delvar_helper(thr, act->lex_env, act, name);
}

/*
 *  DECLVAR
 *
 *  See E5 Sections:
 *    10.4.3 Entering Function Code
 *    10.5 Declaration Binding Instantion
 *    12.2 Variable Statement
 *    11.1.2 Identifier Reference
 *    10.3.1 Identifier Resolution
 *
 *  Variable declaration behavior is mainly discussed in Section 10.5,
 *  and is not discussed in the execution semantics (Sections 11-13).
 *
 *  Conceptually declarations happen when code (global, eval, function)
 *  is entered, before any user code is executed.  In practice, register-
 *  bound identifiers are 'declared' automatically (by virtue of being
 *  allocated to registers with the initial value 'undefined').  Other
 *  identifiers are declared in the function prologue with this primitive.
 *
 *  Since non-register bindings eventually back to an internal object's
 *  properties, the 'prop_flags' argument is used to specify binding
 *  type:
 *
 *    - Immutable binding: set DUK_PROPDESC_FLAG_WRITABLE to false
 *    - Non-deletable binding: set DUK_PROPDESC_FLAG_CONFIGURABLE to false
 *    - The flag DUK_PROPDESC_FLAG_ENUMERABLE should be set, although it
 *      doesn't really matter for internal objects
 *
 *  All bindings are non-deletable mutable bindings except:
 *
 *    - Declarations in eval code (mutable, deletable)
 *    - 'arguments' binding in strict function code (immutable)
 *    - Function name binding of a function expression (immutable)
 *
 *  Declarations may go to declarative environment records (always
 *  so for functions), but may also go to object environment records
 *  (e.g. global code).  The global object environment has special
 *  behavior when re-declaring a function (but not a variable); see
 *  E5.1 specification, Section 10.5, step 5.e.
 *
 *  Declarations always go to the 'top-most' environment record, i.e.
 *  we never check the record chain.  It's not an error even if a
 *  property (even an immutable or non-deletable one) of the same name
 *  already exists.
 *
 *  If a declared variable already exists, its value needs to be updated
 *  (if possible).  Returns 1 if a PUTVAR needs to be done by the caller;
 *  otherwise returns 0.
 */

static int declvar_helper(duk_hthread *thr,
                          duk_hobject *env,
                          duk_hstring *name,
                          duk_tval *tv_val,
                          int prop_flags,
                          int is_func_decl) {
	duk_context *ctx = (duk_context *) thr;
	duk_hobject *holder;
	int parents;
	duk_id_lookup_result ref;
	duk_tval *tv;

	DUK_DDDPRINT("declvar: thr=%p, env=%p, name=%!O, val=%!T, prop_flags=0x%08x, is_func_decl=%d "
	             "(env -> %!iO)",
	             (void *) thr, (void *) env, (duk_heaphdr *) name,
	             tv_val, prop_flags, is_func_decl, (duk_heaphdr *) env);

	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(env != NULL);
	DUK_ASSERT(name != NULL);
	DUK_ASSERT(tv_val != NULL);

	/* Note: in strict mode the compiler should reject explicit
	 * declaration of 'eval' or 'arguments'.  However, internal
	 * bytecode may declare 'arguments' in the function prologue.
	 * We don't bother checking (or asserting) for these now.
	 */

	/* Note: tv_val is a stable tv_val pointer.  The caller makes
	 * a value copy into its stack frame, so 'tv_val' is not subject
	 * to side effects here.
	 */

	/*
	 *  Check whether already declared.
	 *
	 *  We need to check whether the binding exists in the environment
	 *  without walking its parents.  However, we still need to check
	 *  register-bound identifiers and the prototype chain of an object
	 *  environment target object.
	 */

	parents = 0;  /* just check 'env' */
	if (get_identifier_reference(thr, env, name, NULL, parents, &ref)) {
		int e_idx;
		int h_idx;
		int flags;

		/*
		 *  Variable already declared, ignore re-declaration.
		 *  The only exception is the updated behavior of E5.1 for
		 *  global function declarations, E5.1 Section 10.5, step 5.e.
		 *  This behavior does not apply to global variable declarations.
		 */

		if (!(is_func_decl && env == thr->builtins[DUK_BIDX_GLOBAL_ENV])) {
			DUK_DDDPRINT("re-declare a binding, ignoring");
			return 1;  /* 1 -> needs a PUTVAR */
		}

		/*
		 *  Special behavior in E5.1.
		 *
		 *  Note that even though parents == 0, the conflicting property
		 *  may be an inherited property (currently our global object's
		 *  prototype is Object.prototype).  Step 5.e first operates on
		 *  the existing property (which is potentially in an ancestor)
		 *  and then defines a new property in the global object (and
		 *  never modifies the ancestor).
		 *
		 *  Also note that this logic would become even more complicated
		 *  if the conflicting property might be a virtual one.  Object
		 *  prototype has no virtual properties, though.
		 *
		 *  FIXME: this is now very awkward, rework.
		 */

		DUK_DDDPRINT("re-declare a function binding in global object, "
		             "updated E5.1 processing");

		DUK_ASSERT(ref.holder != NULL);
		holder = ref.holder;

		/* holder will be set to the target object, not the actual object
		 * where the property was found (see get_identifier_reference()).
		 */
		DUK_ASSERT(DUK_HOBJECT_GET_CLASS_NUMBER(holder) == DUK_HOBJECT_CLASS_GLOBAL);
		DUK_ASSERT(!DUK_HOBJECT_HAS_SPECIAL_ARRAY(holder));  /* global object doesn't have array part */

		/* FIXME: use a helper for prototype traversal; no loop check here */
		/* must be found: was found earlier, and cannot be inherited */
		for (;;) {
			DUK_ASSERT(holder != NULL);
			duk_hobject_find_existing_entry(holder, name, &e_idx, &h_idx);
			if (e_idx >= 0) {
				break;
			}
			holder = holder->prototype;
		}
		DUK_ASSERT(holder != NULL);
		DUK_ASSERT(e_idx >= 0);

		/* ref.holder is global object, holder is the object with the
		 * conflicting property.
		 */

		flags = DUK_HOBJECT_E_GET_FLAGS(holder, e_idx);
		if (!(flags & DUK_PROPDESC_FLAG_CONFIGURABLE)) {
			if (flags & DUK_PROPDESC_FLAG_ACCESSOR) {
				DUK_DDDPRINT("existing property is a non-configurable "
				             "accessor -> reject");
				goto fail_existing_attributes;
			}
			if (!((flags & DUK_PROPDESC_FLAG_WRITABLE) &&
			      (flags & DUK_PROPDESC_FLAG_ENUMERABLE))) {
				DUK_DDDPRINT("existing property is a non-configurable "
				             "plain property which is not writable and "
				             "enumerable -> reject");
				goto fail_existing_attributes;
			}

			DUK_DDDPRINT("existing property is not configurable but "
			             "is plain, enumerable, and writable -> "
			             "allow redeclaration");
		}

		if (holder == ref.holder) {
			/* FIXME: if duk_hobject_define_property_internal() was updated
			 * to handle a pre-existing accessor property, this would be
			 * a simple call (like for the ancestor case).
			 */
			DUK_DDDPRINT("redefine, offending property in global object itself");

			if (flags & DUK_PROPDESC_FLAG_ACCESSOR) {
				duk_hobject *tmp;

				tmp = DUK_HOBJECT_E_GET_VALUE_GETTER(holder, e_idx);
				DUK_HOBJECT_E_SET_VALUE_GETTER(holder, e_idx, NULL);
				DUK_HOBJECT_DECREF(thr, tmp);
				DUK_UNREF(tmp);
				tmp = DUK_HOBJECT_E_GET_VALUE_SETTER(holder, e_idx);
				DUK_HOBJECT_E_SET_VALUE_SETTER(holder, e_idx, NULL);
				DUK_HOBJECT_DECREF(thr, tmp);
				DUK_UNREF(tmp);
			} else {
				duk_tval tv_tmp;

				tv = DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(holder, e_idx);
				DUK_TVAL_SET_TVAL(&tv_tmp, tv);
				DUK_TVAL_SET_UNDEFINED_UNUSED(tv);
				DUK_TVAL_DECREF(thr, &tv_tmp);
			}

			/* Here tv_val would be potentially invalid if we didn't make
			 * a value copy at the caller.
			 */

			tv = DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(holder, e_idx);
			DUK_TVAL_SET_TVAL(tv, tv_val);
			DUK_TVAL_INCREF(thr, tv);
			DUK_HOBJECT_E_SET_FLAGS(holder, e_idx, prop_flags);

			DUK_DDDPRINT("updated global binding, final result: "
			             "value -> %!T, prop_flags=0x%08x",
			             DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(holder, e_idx),
			             prop_flags);
		} else {
			DUK_DDDPRINT("redefine, offending property in ancestor");

			DUK_ASSERT(ref.holder == thr->builtins[DUK_BIDX_GLOBAL]);
			duk_push_tval(ctx, tv_val);
			duk_hobject_define_property_internal(thr, ref.holder, name, prop_flags);
		}

		return 0;
	}

	/*
	 *  Not found (in registers or record objects).  Declare
	 *  to current variable environment.
	 */

	/*
	 *  Get holder object
	 */

	if (DUK_HOBJECT_IS_DECENV(env)) {
		holder = env;
	} else {
		DUK_ASSERT(DUK_HOBJECT_IS_OBJENV(env));

		tv = duk_hobject_find_existing_entry_tval_ptr(env, DUK_HEAP_STRING_INT_TARGET(thr));
		DUK_ASSERT(tv != NULL);
		DUK_ASSERT(DUK_TVAL_IS_OBJECT(tv));
		holder = DUK_TVAL_GET_OBJECT(tv);
		DUK_ASSERT(holder != NULL);
	}

	/*
	 *  Define new property
	 *
	 *  Note: this may fail if the holder is not extensible.
	 */

	/* FIXME: this is awkward as we use an internal method which doesn't handle
	 * extensibility etc correctly.  Basically we'd want to do a [[DefineOwnProperty]]
	 * or Object.defineProperty() here.
	 */

	if (!DUK_HOBJECT_HAS_EXTENSIBLE(holder)) {
		goto fail_not_extensible;
	}

	duk_push_hobject(ctx, holder);
	duk_push_hstring(ctx, name);
	duk_push_tval(ctx, tv_val);
	duk_def_prop(ctx, -3, prop_flags);  /* [holder name val] -> [holder] */
	duk_pop(ctx);

	return 0;

 fail_existing_attributes:
 fail_not_extensible:
	DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, "declaration failed");
	return 0;
}

int duk_js_declvar_activation(duk_hthread *thr,
                              duk_activation *act,
                              duk_hstring *name,
                              duk_tval *tv_val,
                              int prop_flags,
                              int is_func_decl) {
	duk_hobject *env;
	duk_tval tv_val_copy;

	/*
	 *  Make a value copy of the input tv_val.  This ensures that
	 *  side effects cannot invalidate the pointer.
	 */

	DUK_TVAL_SET_TVAL(&tv_val_copy, tv_val);
	tv_val = &tv_val_copy;

	/*
	 *  Delayed env creation check
	 */

	if (!act->var_env) {
		DUK_ASSERT(act->lex_env == NULL);
		duk_js_init_activation_environment_records_delayed(thr, act);
	}
	env = act->var_env;
	DUK_ASSERT(env != NULL);
	DUK_ASSERT(DUK_HOBJECT_IS_ENV(env));

	return declvar_helper(thr, env, name, tv_val, prop_flags, is_func_decl);
}

#line 1 "duk_lexer.c"
/*
 *  Lexer for source files, ToNumber() string conversions, RegExp expressions,
 *  and JSON.
 *
 *  Provides a stream of Ecmascript tokens from an UTF-8/CESU-8 buffer.  The
 *  caller can also rewind the token stream into a certain position which is
 *  needed by the compiler part for multi-pass scanning.  Tokens are
 *  represented as duk_token structures, and contain line number information.
 *  Token types are identified with DUK_TOK_* defines.
 *
 *  Characters are decoded into a fixed size lookup window consisting of
 *  decoded Unicode code points, with window positions past the end of the
 *  input filled with an invalid codepoint (-1).  The tokenizer can thus
 *  perform multiple character lookups efficiently and with few sanity
 *  checks (such as access outside the end of the input), which keeps the
 *  tokenization code small at the cost of performance.
 * 
 *  Character data in tokens (such as identifier names and string literals)
 *  is encoded into CESU-8 format on-the-fly while parsing the token in
 *  question.  The string data is made reachable to garbage collection by
 *  placing the token-related values in value stack entries allocated for
 *  this purpose by the caller.  The characters exist in Unicode code point
 *  form only in the fixed size lookup window, which keeps character data
 *  expansion (of especially ASCII data) low.
 *
 *  Token parsing supports the full range of Unicode characters as described
 *  in the E5 specification.  Parsing has been optimized for ASCII characters
 *  because ordinary Ecmascript code consists almost entirely of ASCII
 *  characters.  Matching of complex Unicode codepoint sets (such as in the
 *  IdentifierStart and IdentifierPart productions) is optimized for size,
 *  and is done using a linear scan of a bit-packed list of ranges.  This is
 *  very slow, but should never be entered unless the source code actually
 *  contains Unicode characters.
 *
 *  Ecmascript tokenization is partially context sensitive.  First,
 *  additional future reserved words are recognized in strict mode (see E5
 *  Section 7.6.1.2).  Second, a forward slash character ('/') can be
 *  recognized either as starting a RegExp literal or as a division operator,
 *  depending on context.  The caller must provide necessary context flags
 *  when requesting a new token.
 *
 *  Future work:
 *
 *    * Make the input window a circular array to avoid copying.  This would
 *      not necessarily complicate the tokenizer much, although it would make
 *      the window fetches more expensive (one AND).
 *
 *    * Make line number tracking optional, as it consumes space.  Also, is
 *      tracking end line really useful for tokens?
 *
 *    * Add a feature flag for disabling UTF-8 decoding of input, as most
 *      source code is ASCII.  Because of Unicode escapes written in ASCII,
 *      this does not allow Unicode support to be removed from e.g.
 *      duk_is_identifier_start() nor does it allow removal of CESU-8
 *      encoding of e.g. string literals.
 *
 *    * Add a feature flag for disabling Unicode compliance of e.g. identifier
 *      names.  This allows for a build more than a kilobyte smaller, because
 *      Unicode ranges needed by duk_is_identifier_start() and
 *      duk_is_identifier_part() can be dropped.  String literals should still
 *      be allowed to contain escaped Unicode, so this still does not allow
 *      removal of CESU-8 encoding of e.g. string literals.
 *
 *    * Character lookup tables for codepoints above BMP could be stripped.
 *
 *    * Strictly speaking, E5 specification requires that source code consists
 *      of 16-bit code units, and if not, must be conceptually converted to
 *      that format first.  The current lexer processes Unicode code points
 *      and allows characters outside the BMP.  These should be converted to
 *      surrogate pairs while reading the source characters into the window,
 *      not after tokens have been formed (as is done now).  However, the fix
 *      is not trivial because two characters are decoded from one codepoint.
 *
 *    * Optimize for speed as well as size.  Large if-else ladders are slow.
 */

/* include removed: duk_internal.h */

/* FIXME: check defines */

/*
 *  Various defines and file specific helper macros
 */

#define  MAX_REGEXP_DECIMAL_ESCAPE_DIGITS  9
#define  MAX_REGEXP_QUANTIFIER_DIGITS      9   /* FIXME: does not allow e.g. 2**31-1, but one more would allow overflows of u32 */

#define  LOOKUP(lex_ctx,index)    ((lex_ctx)->window[(index)])
#define  ADVANCE(lex_ctx,count)   advance_chars((lex_ctx), (count))
#define  INITBUFFER(lex_ctx)      initbuffer((lex_ctx))
#define  APPENDBUFFER(lex_ctx,x)  appendbuffer((lex_ctx), (int) (x))

/* whether to use macros or helper function depends on call count */
#define  ISDIGIT(x)          ((x) >= '0' && (x) <= '9')
#define  ISHEXDIGIT(x)       is_hex_digit((x))
#define  ISOCTDIGIT(x)       ((x) >= '0' && (x) <= '7')
#define  ISDIGIT03(x)        ((x) >= '0' && (x) <= '3')
#define  ISDIGIT47(x)        ((x) >= '4' && (x) <= '7')

/* lookup shorthands (note: assume context variable is named 'lex_ctx') */
#define  L0()  LOOKUP(lex_ctx, 0)
#define  L1()  LOOKUP(lex_ctx, 1)
#define  L2()  LOOKUP(lex_ctx, 2)
#define  L3()  LOOKUP(lex_ctx, 3)
#define  L4()  LOOKUP(lex_ctx, 4)
#define  L5()  LOOKUP(lex_ctx, 5)

/* packed advance/token number macro used by multiple functions */
#define  ADVTOK(adv,tok)  (((adv) << 8) + (tok))

/*
 *  Read a character from the window leading edge and update the line counter.
 *
 *  Decodes UTF-8/CESU-8 leniently with support for code points from U+0000 to
 *  U+10FFFF, causing an error if the input is unparseable.  Leniency means:
 *
 *    * Unicode code point validation is intentionally not performed,
 *      except to check that the codepoint does not exceed 0x10ffff.
 *
 *    * In particular, surrogate pairs are allowed and not combined, which
 *      allows source files to represent all SourceCharacters with CESU-8.
 *      Broken surrogate pairs are allowed, as Ecmascript does not mandate
 *      their validation.
 *
 *    * Allow non-shortest UTF-8 encodings.
 *
 *  Leniency here causes few security concerns because all character data is
 *  decoded into Unicode codepoints before lexer processing, and is then
 *  re-encoded into CESU-8.  The source can be parsed as strict UTF-8 with
 *  a compiler option.  However, Ecmascript source characters include -all-
 *  16-bit unsigned integer codepoints, so leniency seems to be appropriate.
 *
 *  Note that codepoints above the BMP are not strictly SourceCharacters,
 *  but the lexer still accepts them as such.  Before ending up in a string
 *  or an identifier name, codepoints above BMP are converted into surrogate
 *  pairs and then CESU-8 encoded, resulting in 16-bit Unicode data as
 *  expected by Ecmascript.
 *
 *  An alternative approach to dealing with invalid or partial sequences
 *  would be to skip them and replace them with e.g. the Unicode replacement
 *  character U+FFFD.  This has limited utility because a replacement character
 *  will most likely cause a parse error, unless it occurs inside a string.
 *  Further, Ecmascript source is typically pure ASCII.
 *
 *  See:
 *
 *     http://en.wikipedia.org/wiki/UTF-8
 *     http://en.wikipedia.org/wiki/CESU-8
 *     http://tools.ietf.org/html/rfc3629
 *     http://en.wikipedia.org/wiki/UTF-8#Invalid_byte_sequences
 *
 *  Future work:
 *
 *    * Reject other invalid Unicode sequences (see Wikipedia entry for examples)
 *      in strict UTF-8 mode.
 * 
 *    * Size optimize.  An attempt to use a 16-byte lookup table for the first
 *      byte resulted in a code increase though.
 *
 *    * Is checking against maximum 0x10ffff really useful?  4-byte encoding
 *      imposes a certain limit anyway.
 */

static int read_char(duk_lexer_ctx *lex_ctx) {
	/* attempting to reduce size of 'len' and/or 'i' resulted in larger code */
	int x;
	int len;
	int i;
	duk_u8 *p;
#ifdef DUK_USE_STRICT_UTF8_SOURCE
	int mincp;
#endif

	if (lex_ctx->input_offset >= lex_ctx->input_length) {
		return -1;
	}

	p = &lex_ctx->input[lex_ctx->input_offset];
	x = (int) *p++;

	if (x < 0x80) {
		/* 0xxx xxxx -> fast path */
		len = 1;
		goto fastpath;
	} else if (x < 0xc0) {
		/* 10xx xxxx -> invalid */
		goto error;
	} else if (x < 0xe0) {
		/* 110x xxxx   10xx xxxx  */
		len = 2;
#ifdef DUK_USE_STRICT_UTF8_SOURCE
		mincp = 0x80;
#endif
		x = x & 0x1f;
	} else if (x < 0xf0) {
		/* 1110 xxxx   10xx xxxx   10xx xxxx */
		len = 3;
#ifdef DUK_USE_STRICT_UTF8_SOURCE
		mincp = 0x800;
#endif
		x = x & 0x0f;
	} else if (x < 0xf8) {
		/* 1111 0xxx   10xx xxxx   10xx xxxx   10xx xxxx */
		len = 4;
#ifdef DUK_USE_STRICT_UTF8_SOURCE
		mincp = 0x10000;
#endif
		x = x & 0x07;
	} else {
		/* no point in supporting encodings of 5 or more bytes */
		goto error;
	}

	if (len > lex_ctx->input_length - lex_ctx->input_offset) {
		goto error;
	}

	for (i = 1; i < len; i++) {
		int y = *p++;
		if ((y & 0xc0) != 0x80) {
			/* check that byte has the form 10xx xxxx */
			goto error;
		}
		x = x << 6;
		x += y & 0x3f;
	}

	/* check final character validity */

	if (x > 0x10ffff) {
		goto error;
	}
#ifdef DUK_USE_STRICT_UTF8_SOURCE
	if (x < mincp || (x >= 0xd800 && x <= 0xdfff) || x == 0xfffe) {
		goto error;
	}
#endif

	/* fall through */

 fastpath:
	/* input offset tracking */
	lex_ctx->input_offset += len;

	/* line tracking */
	if ((x == 0x000a) ||
	    ((x == 0x000d) && (lex_ctx->input_offset >= lex_ctx->input_length ||
	                       lex_ctx->input[lex_ctx->input_offset] != 0x000a)) ||
	    (x == 0x2028) ||
	    (x == 0x2029)) {
		/* lookup for 0x000a above assumes shortest encoding now */

		/* E5 Section 7.3, treat the following as newlines:
		 *   LF
		 *   CR [not followed by LF]
		 *   LS
		 *   PS
		 *
		 * For CR LF, CR is ignored if it is followed by LF, and the LF will bump
		 * the line number.
		 */
		lex_ctx->input_line++;
	}

	return x;

 error:
	DUK_ERROR(lex_ctx->thr, DUK_ERR_SYNTAX_ERROR, "invalid char encoding in source");
	return 0;
}

/*
 *  Advance lookup window by N characters.  Also used to fill the window
 *  after position is changed (call with count == DUK_LEXER_WINDOW_SIZE).
 *
 *  Future work:
 *
 *    * A lot of copying now, perhaps change to circular array or at
 *      least use memcpy().  For memcpy(), putting all elements of the
 *      window (code point, offset, line) into a struct would allow one
 *      memcpy() to slide the window, instead of three separate copys.
 */

static void advance_chars(duk_lexer_ctx *lex_ctx, int count) {
	int i;

	DUK_ASSERT(count >= 0 && count <= DUK_LEXER_WINDOW_SIZE);

	if (count == 0) {
		/* allowing zero count makes some special caller flows easier */
		return;
	}

	for (i = 0; i < DUK_LEXER_WINDOW_SIZE - count; i++) {
		lex_ctx->offsets[i] = lex_ctx->offsets[i + count];
		lex_ctx->lines[i] = lex_ctx->lines[i + count];
		lex_ctx->window[i] = lex_ctx->window[i + count];
	}

	for (; i < DUK_LEXER_WINDOW_SIZE; i++) {
		lex_ctx->offsets[i] = lex_ctx->input_offset;
		lex_ctx->lines[i] = lex_ctx->input_line;
		lex_ctx->window[i] = read_char(lex_ctx);
	}
}

/*
 *  (Re)initialize the temporary byte buffer.  May be called extra times
 *  with little impact.
 */

static void initbuffer(duk_lexer_ctx *lex_ctx) {
	if (lex_ctx->buf->usable_size < DUK_LEXER_TEMP_BUF_LIMIT) {
		/* FIXME: resize (zero) without realloc -> API change */
		lex_ctx->buf->size = 0;
	} else {
		duk_hbuffer_resize(lex_ctx->thr, lex_ctx->buf, 0, DUK_LEXER_TEMP_BUF_LIMIT);
	}
}

/*
 *  Append a Unicode codepoint to the temporary byte buffer.  Performs
 *  CESU-8 surrogate pair encoding for codepoints above the BMP.
 *  Existing surrogate pairs are allowed and also encoded into CESU-8.
 */

static void appendbuffer(duk_lexer_ctx *lex_ctx, int x) {
	/*
	 *  Since character data is only generated by decoding the source or by
	 *  the compiler itself, we rely on the input codepoints being correct
	 *  and avoid a check here.
	 *
	 *  Character data can also come here through decoding of Unicode
	 *  escapes ("\udead\ubeef") so all 16-but unsigned values can be
	 *  present, even when the source file itself is strict UTF-8.
	 */

	DUK_ASSERT(x >= 0 && x <= 0x10ffff);

	duk_hbuffer_append_cesu8(lex_ctx->thr, lex_ctx->buf, x);
}

/*
 *  Intern the temporary byte buffer into a valstack slot
 *  (in practice, slot1 or slot2).
 */

static void internbuffer(duk_lexer_ctx *lex_ctx, int valstack_idx) {
	duk_context *ctx = (duk_context *) lex_ctx->thr;

	DUK_ASSERT(valstack_idx == lex_ctx->slot1_idx || valstack_idx == lex_ctx->slot2_idx);

	duk_dup(ctx, lex_ctx->buf_idx);
	duk_to_string(ctx, -1);
	duk_replace(ctx, valstack_idx);
}

/*
 *  Init lexer context
 */

void duk_lexer_initctx(duk_lexer_ctx *lex_ctx) {
	DUK_ASSERT(lex_ctx != NULL);

	memset(lex_ctx, 0, sizeof(*lex_ctx));
#ifdef DUK_USE_EXPLICIT_NULL_INIT
	lex_ctx->thr = NULL;
	lex_ctx->input = NULL;
	lex_ctx->buf = NULL;
#endif
}

/*
 *  Set lexer input position and reinitialize lookup window.
 */

/* NB: duk_lexer_getpoint() is a macro only */

void duk_lexer_setpoint(duk_lexer_ctx *lex_ctx, duk_lexer_point *pt) {
	DUK_ASSERT(pt->offset >= 0);
	DUK_ASSERT(pt->line >= 1);
	lex_ctx->input_offset = pt->offset;
	lex_ctx->input_line = pt->line;
	advance_chars(lex_ctx, DUK_LEXER_WINDOW_SIZE);  /* fill window */
}

/*
 *  Lexing helpers
 */

/* numeric value of a hex digit (also covers octal and decimal digits) */
static int hexval(duk_lexer_ctx *lex_ctx, int x) {
	if (x >= '0' && x <= '9') {
		return ((int) x) - ((int) '0');
	} else if (x >= 'a' && x <= 'f') {
		return ((int) x) - ((int) 'a') + 0x0a;
	} else if (x >= 'A' && x <= 'F') {
		return ((int) x) - ((int) 'A') + 0x0a;
	}

	/* Throwing an error this deep makes the error rather vague, but
	 * saves hundreds of bytes of code.
	 */
	DUK_ERROR(lex_ctx->thr, DUK_ERR_SYNTAX_ERROR, "decode error");
	return 0;
}

/* having this as a separate function provided a size benefit */
static int is_hex_digit(int x) {
	return (x >= '0' && x <= '9') ||
	       (x >= 'a' && x <= 'f') ||
	       (x >= 'A' && x <= 'F');
}

static int decode_hex_escape_from_window(duk_lexer_ctx *lex_ctx, int lookup_offset) {
	/* validation performed by hexval */
	return (hexval(lex_ctx, lex_ctx->window[lookup_offset]) << 4) |
	       (hexval(lex_ctx, lex_ctx->window[lookup_offset + 1]));
}

static int decode_unicode_escape_from_window(duk_lexer_ctx *lex_ctx, int lookup_offset) {
	/* validation performed by hexval */
	return (hexval(lex_ctx, lex_ctx->window[lookup_offset]) << 12) |
	       (hexval(lex_ctx, lex_ctx->window[lookup_offset + 1]) << 8) |
	       (hexval(lex_ctx, lex_ctx->window[lookup_offset + 2]) << 4) |
	       (hexval(lex_ctx, lex_ctx->window[lookup_offset + 3]));
}

/*
 *  Eat input characters until first character of window is not
 *  a white space (may be -1 if EOF encountered).
 */
static void eat_whitespace(duk_lexer_ctx *lex_ctx) {
	/* guaranteed to finish, as EOF (-1) is not a whitespace */
	while (duk_unicode_is_whitespace(LOOKUP(lex_ctx, 0))) {
		ADVANCE(lex_ctx, 1);
	}
}

/*
 *  Parse Ecmascript source InputElementDiv or InputElementRegExp
 *  (E5 Section 7).
 *
 *  Possible results are:
 *    (1) a token
 *    (2) a line terminator
 *    (3) a comment
 *    (4) EOF
 *
 *  White space is automatically skipped from the current position (but
 *  not after the input element).  If input has already ended, returns
 *  DUK_TOK_EOF indefinitely.  If a parse error occurs, uses an DUK_ERROR()
 *  macro call (and hence a longjmp through current heap longjmp context).
 *
 *  The input element being matched is determined by regexp_mode; if set,
 *  parses a InputElementRegExp, otherwise a InputElementDiv.  The
 *  difference between these are handling of productions starting with a
 *  forward slash.
 *
 *  If strict_mode is set, recognizes additional future reserved words
 *  specific to strict mode, and refuses to parse octal literals.
 *
 *  The matching strategy below is to (currently) use a six character
 *  lookup window to quickly determine which production is the -longest-
 *  matching one, and then parse that.  The top-level if-else clauses
 *  match the first character, and the code blocks for each clause
 *  handle -all- alternatives for that first character.  Ecmascript
 *  specification uses the "longest match wins" semantics, so the order
 *  of the if-clauses matters.
 *
 *  Misc notes:
 *
 *    * Ecmascript numeric literals do not accept a sign character.
 *      Consequently e.g. "-1.0" is parsed as two tokens: a negative
 *      sign and a positive numeric literal.  The compiler performs
 *      the negation during compilation, so this has no adverse impact.
 *
 *    * There is no token for "undefined": it is just a value available
 *      from the global object (or simply established by doing a reference
 *      to an undefined value).
 *
 *    * Some contexts want Identifier tokens, which are IdentifierNames
 *      excluding reserved words, while some contexts want IdentifierNames
 *      directly.  In the latter case e.g. "while" is interpreted as an
 *      identifier name, not a DUK_TOK_WHILE token.  The solution here is
 *      to provide both token types: DUK_TOK_WHILE goes to 't' while
 *      DUK_TOK_IDENTIFIER goes to 't_nores', and 'slot1' always contains
 *      the identifier / keyword name.
 *
 *    * Directive prologue needs to identify string literals such as
 *      "use strict" and 'use strict', which are sensitive to line
 *      continuations and escape sequences.  For instance, "use\u0020strict"
 *      is a valid directive but is distinct from "use strict".  The solution
 *      here is to decode escapes while tokenizing, but to keep track of the
 *      number of escapes.  Directive detection can then check that the
 *      number of escapes is zero.
 *
 *    * Comments are expressed as DUK_TOK_COMMENT tokens, with the type
 *      (single- or multi-line) and contents of the comments lost.
 *      Furthermore, multi-line comments with one or more internal
 *      LineTerminator are treated as DUK_TOK_LINETERM to comply with
 *      automatic semicolon insertion and to avoid complicating the
 *      tokenization process.  See E5 Section 7.4.
 */

static void parse_input_element_raw(duk_lexer_ctx *lex_ctx,
                                    duk_token *out_token,
                                    int strict_mode,
                                    int regexp_mode) {
	int x, y;               /* temporaries, must be 32-bit to hold Unicode code points */
	int advtok = 0;         /* (advance << 8) + token_type, updated at function end,
	                         * init is unnecessary but suppresses "may be used uninitialized warnings
	                         */
	eat_whitespace(lex_ctx);

	out_token->t = DUK_TOK_EOF;
	out_token->t_nores = -1;	/* marker: copy t if not changed */
	out_token->num = NAN;
	out_token->str1 = NULL;
	out_token->str2 = NULL;
	out_token->num_escapes = 0;
	out_token->start_line = lex_ctx->lines[0];
	/* out_token->end_line set at exit */
	/* out_token->lineterm set by caller */

	duk_to_undefined((duk_context *) lex_ctx->thr, lex_ctx->slot1_idx);
	duk_to_undefined((duk_context *) lex_ctx->thr, lex_ctx->slot2_idx);

	/* 'advtok' indicates how much to advance and which token id to assign
	 * at the end.  This shared functionality minimizes code size.  All
	 * code paths are required to set 'advtok' to some value, so no default
	 * init value is used.  Code paths calling DUK_ERROR() never return so
	 * they don't need to set advtok.
	 */

	/*
	 *  Matching order:
	 *
	 *    Punctuator first chars, also covers comments, regexps
	 *    LineTerminator
	 *    Identifier or reserved word, also covers null/true/false literals
	 *    NumericLiteral
	 *    StringLiteral
	 *    EOF
	 *
	 *  The order does not matter as long as the longest match is
	 *  always correctly identified.  There are order dependencies
	 *  in the clauses, so it's not trivial to convert to a switch.
	 *
	 *  Maybe change this to a switch which handles all single character
	 *  cases and a follow-up if-else chain.  Switch matches need to goto
	 *  to bypass the if-else chain.
	 */

	x = L0();
	y = L1();

	if (x == '/') {
		if (y == '/') {
			/*
			 *  E5 Section 7.4, allow SourceCharacter (which is any 16-bit
			 *  code point).
			 */

			/* ADVANCE(lex_ctx, 2) would be correct here, but it unnecessary */
			for (;;) {
				x = L0();
				if (x < 0 || duk_unicode_is_line_terminator(x)) {
					break;
				}
				ADVANCE(lex_ctx, 1);
			}
			advtok = ADVTOK(0, DUK_TOK_COMMENT);
		} else if (y == '*') {
			/*
			 *  E5 Section 7.4.  If the multi-line comment contains a newline,
			 *  it is treated like a single DUK_TOK_LINETERM to facilitate
			 *  automatic semicolon insertion.
			 */

			duk_u8 last_asterisk = 0;
			advtok = ADVTOK(0, DUK_TOK_COMMENT);
			ADVANCE(lex_ctx, 2);
			for (;;) {
				x = L0();
				if (x < 0) {
					DUK_ERROR(lex_ctx->thr, DUK_ERR_SYNTAX_ERROR,
					          "eof while parsing multiline comment");
				}
				ADVANCE(lex_ctx, 1);
				if (last_asterisk && x == '/') {
					break;
				}
				if (duk_unicode_is_line_terminator(x)) {
					advtok = ADVTOK(0, DUK_TOK_LINETERM);
				}
				last_asterisk = (x == (int) '*');
			}
		} else if (regexp_mode) {
#ifdef DUK_USE_REGEXP_SUPPORT
			/*
			 *  "/" followed by something in regexp mode.  See E5 Section 7.8.5.
			 *
			 *  RegExp parsing is a bit complex.  First, the regexp body is delimited
			 *  by forward slashes, but the body may also contain forward slashes as
			 *  part of an escape sequence or inside a character class (delimited by
			 *  square brackets).  A mini state machine is used to implement these.
			 *
			 *  Further, an early (parse time) error must be thrown if the regexp
			 *  would cause a run-time error when used in the expression new RegExp(...).
			 *  Parsing here simply extracts the (candidate) regexp, and also accepts
			 *  invalid regular expressions (which are delimited properly).  The caller
			 *  (compiler) must perform final validation and regexp compilation.
			 *
			 *  RegExp first char may not be '/' (single line comment) or '*' (multi-
			 *  line comment).  These have already been checked above, so there is no
			 *  need below for special handling of the first regexp character as in
			 *  the E5 productions.
			 *
			 *  About unicode escapes within regexp literals:
			 *
			 *      E5 Section 7.8.5 grammar does NOT accept \uHHHH escapes.
			 *      However, Section 6 states that regexps accept the escapes,
			 *      see paragraph starting with "In string literals...".
			 *      The regexp grammar, which sees the decoded regexp literal
			 *      (after lexical parsing) DOES have a \uHHHH unicode escape.
			 *      So, for instance:
			 *
			 *          /\u1234/
			 *
			 *      should first be parsed by the lexical grammar as:
			 *
			 *          '\' 'u'		RegularExpressionBackslashSequence
			 *          '1'			RegularExpressionNonTerminator
			 *          '2'			RegularExpressionNonTerminator
			 *          '3'			RegularExpressionNonTerminator
			 *          '4'			RegularExpressionNonTerminator
			 *
			 *      and the escape itself is then parsed by the regexp engine.
			 *      This is the current implementation. 
			 *
			 *  Minor spec inconsistency:
			 *
			 *      E5 Section 7.8.5 RegularExpressionBackslashSequence is:
			 *
			 *         \ RegularExpressionNonTerminator
			 *
			 *      while Section A.1 RegularExpressionBackslashSequence is:
			 *
			 *         \ NonTerminator
			 * 
			 *      The latter is not normative and a typo.
			 * 
			 */

			/* FIXME: needs to be checked carefully */

			/* FIXME: lexical parsing of regexps may be needed even without regexp
			 * support because regexp mode is the default in the compiler.
			 */

			/* first, parse regexp body roughly */

			duk_u8 state = 0;  /* 0=base, 1=esc, 2=class, 3=class+esc */

			INITBUFFER(lex_ctx);
			for (;;) {
				ADVANCE(lex_ctx, 1);	/* skip opening slash on first loop */
				x = L0();
				if (x < 0 || duk_unicode_is_line_terminator(x)) {
					DUK_ERROR(lex_ctx->thr, DUK_ERR_SYNTAX_ERROR,
					          "eof or line terminator while parsing regexp");
				}
				x = L0();	/* re-read to avoid spill / fetch */
				if (state == 0) {
					if (x == '/') {
						ADVANCE(lex_ctx, 1);	/* eat closing slash */
						break;
					} else if (x == '\\') {
						state = 1;
					} else if (x == '[') {
						state = 2;
					}
				} else if (state == 1) {
					state = 0;
				} else if (state == 2) {
					if (x == ']') {
						state = 0;
					} else if (x == '\\') {
						state = 3;
					}
				} else { /* state == 3 */
					state = 2;
				}
				APPENDBUFFER(lex_ctx, x);
			}
			internbuffer(lex_ctx, lex_ctx->slot1_idx);
			out_token->str1 = duk_get_hstring((duk_context *) lex_ctx->thr, lex_ctx->slot1_idx);

			/* second, parse flags */

			INITBUFFER(lex_ctx);
			for (;;) {
				x = L0();
				if (!duk_unicode_is_identifier_part(x)) {
					break;
				}
				x = L0();	/* re-read to avoid spill / fetch */
				APPENDBUFFER(lex_ctx, x);
				ADVANCE(lex_ctx, 1);
			}
			internbuffer(lex_ctx, lex_ctx->slot2_idx);
			out_token->str2 = duk_get_hstring((duk_context *) lex_ctx->thr, lex_ctx->slot2_idx);

			INITBUFFER(lex_ctx);	/* free some memory */

			/* validation of the regexp is caller's responsibility */

			advtok = ADVTOK(0, DUK_TOK_REGEXP);
#else
			DUK_ERROR(lex_ctx->thr, DUK_ERR_SYNTAX_ERROR, "regexp support not enabled");
#endif
		} else if (y == '=') {
			/* "/=" and not in regexp mode */
			advtok = ADVTOK(2, DUK_TOK_DIV_EQ);
		} else {
			/* "/" and not in regexp mode */
			advtok = ADVTOK(1, DUK_TOK_DIV);
		}
	} else if (x == '{') {
		advtok = ADVTOK(1, DUK_TOK_LCURLY);
	} else if (x == '}') {
		advtok = ADVTOK(1, DUK_TOK_RCURLY);
	} else if (x == '(') {
		advtok = ADVTOK(1, DUK_TOK_LPAREN);
	} else if (x == ')') {
		advtok = ADVTOK(1, DUK_TOK_RPAREN);
	} else if (x == '[') {
		advtok = ADVTOK(1, DUK_TOK_LBRACKET);
	} else if (x == ']') {
		advtok = ADVTOK(1, DUK_TOK_RBRACKET);
	} else if (x == '.' && !ISDIGIT(y)) {
		/* Note: period followed by a digit can only start DecimalLiteral (captured below) */
		advtok = ADVTOK(1, DUK_TOK_PERIOD);
	} else if (x == ';') {
		advtok = ADVTOK(1, DUK_TOK_SEMICOLON);
	} else if (x == ',') {
		advtok = ADVTOK(1, DUK_TOK_COMMA);
	} else if (x == '<') {
		if (y == '<' && L2() == '=') {
			advtok = ADVTOK(3, DUK_TOK_ALSHIFT_EQ);
		} else if (y == '=') {
			advtok = ADVTOK(2, DUK_TOK_LE);
		} else if (y == '<') {
			advtok = ADVTOK(2, DUK_TOK_ALSHIFT);
		} else {
			advtok = ADVTOK(1, DUK_TOK_LT);
		}
	} else if (x == '>') {
		if (y == '>' && L2() == '>' && L3() == '=') {
			advtok = ADVTOK(4, DUK_TOK_RSHIFT_EQ);
		} else if (y == '>' && L2() == '>') {
			advtok = ADVTOK(3, DUK_TOK_RSHIFT);
		} else if (y == '>' && L2() == '=') {
			advtok = ADVTOK(3, DUK_TOK_ARSHIFT_EQ);
		} else if (y == '=') {
			advtok = ADVTOK(2, DUK_TOK_GE);
		} else if (y == '>') {
			advtok = ADVTOK(2, DUK_TOK_ARSHIFT);
		} else {
			advtok = ADVTOK(1, DUK_TOK_GT);
		}
	} else if (x == '=') {
		if (y == '=' && L2() == '=') {
			advtok = ADVTOK(3, DUK_TOK_SEQ);
		} else if (y == '=') {
			advtok = ADVTOK(2, DUK_TOK_EQ);
		} else {
			advtok = ADVTOK(1, DUK_TOK_EQUALSIGN);
		}
	} else if (x == '!') {
		if (y == '=' && L2() == '=') {
			advtok = ADVTOK(3, DUK_TOK_SNEQ);
		} else if (y == '=') {
			advtok = ADVTOK(2, DUK_TOK_NEQ);
		} else {
			advtok = ADVTOK(1, DUK_TOK_LNOT);
		}
	} else if (x == '+') {
		if (y == '+') {
			advtok = ADVTOK(2, DUK_TOK_INCREMENT);
		} else if (y == '=') {
			advtok = ADVTOK(2, DUK_TOK_ADD_EQ);
		} else {
			advtok = ADVTOK(1, DUK_TOK_ADD);
		}
	} else if (x == '-') {
		if (y == '-') {
			advtok = ADVTOK(2, DUK_TOK_DECREMENT);
		} else if (y == '=') {
			advtok = ADVTOK(2, DUK_TOK_SUB_EQ);
		} else {
			advtok = ADVTOK(1, DUK_TOK_SUB);
		}
	} else if (x == '*') {
		if (y == '=') {
			advtok = ADVTOK(2, DUK_TOK_MUL_EQ);
		} else {
			advtok = ADVTOK(1, DUK_TOK_MUL);
		}
	} else if (x == '%') {
		if (y == '=') {
			advtok = ADVTOK(2, DUK_TOK_MOD_EQ);
		} else {
			advtok = ADVTOK(1, DUK_TOK_MOD);
		}
	} else if (x == '&') {
		if (y == '&') {
			advtok = ADVTOK(2, DUK_TOK_LAND);
		} else if (y == '=') {
			advtok = ADVTOK(2, DUK_TOK_BAND_EQ);
		} else {
			advtok = ADVTOK(1, DUK_TOK_BAND);
		}
	} else if (x == '|') {
		if (y == '|') {
			advtok = ADVTOK(2, DUK_TOK_LOR);
		} else if (y == '=') {
			advtok = ADVTOK(2, DUK_TOK_BOR_EQ);
		} else {
			advtok = ADVTOK(1, DUK_TOK_BOR);
		}
	} else if (x == '^') {
		if (y == '=') {
			advtok = ADVTOK(2, DUK_TOK_BXOR_EQ);
		} else {
			advtok = ADVTOK(1, DUK_TOK_BXOR);
		}
	} else if (x == '~') {
		advtok = ADVTOK(1, DUK_TOK_BNOT);
	} else if (x == '?') {
		advtok = ADVTOK(1, DUK_TOK_QUESTION);
	} else if (x == ':') {
		advtok = ADVTOK(1, DUK_TOK_COLON);
	} else if (duk_unicode_is_line_terminator(x)) {
		if (x == 0x000d && y == 0x000a) {
			/*
			 *  E5 Section 7.3: CR LF is detected as a single line terminator for
			 *  line numbers.  Here we also detect it as a single line terminator
			 *  token.
			 */
			advtok = ADVTOK(2, DUK_TOK_LINETERM);
		}
		advtok = ADVTOK(1, DUK_TOK_LINETERM);
	} else if (duk_unicode_is_identifier_start(x) || x == '\\') {
		/*
		 *  Parse an identifier and then check whether it is:
		 *    - reserved word (keyword or other reserved word)
		 *    - "null"  (NullLiteral)
		 *    - "true"  (BooleanLiteral)
		 *    - "false" (BooleanLiteral)
		 *    - anything else => identifier
		 *
		 *  This does not follow the E5 productions cleanly, but is
		 *  useful and compact.
		 *
		 *  Note that identifiers may contain Unicode escapes,
		 *  see E5 Sections 6 and 7.6.  They must be decoded first,
		 *  and the result checked against allowed characters.
		 *  The above if-clause accepts an identifier start and an
		 *  '\' character -- no other token can begin with a '\'.
		 *
		 *  Note that "get" and "set" are not reserved words in E5
		 *  specification, but we treat them as such.
		 *
		 *  Strictly speaking, following Ecmascript longest match
		 *  specification, an invalid escape for the first character
		 *  should cause a syntax error.  However, an invalid escape
		 *  for IdentifierParts should just terminate the identifier
		 *  early (longest match), and let the next tokenization
		 *  fail.  For instance Rhino croaks with 'foo\z' when
		 *  parsing the identifier.  This has little practical impact.
		 */

		int i, i_end;
		int first = 1;
		duk_hstring *str;

		INITBUFFER(lex_ctx);
		for (;;) {
			/* re-lookup first char on first loop */
			if (L0() == '\\') {
				int ch;
				if (L1() != 'u') {
					DUK_ERROR(lex_ctx->thr, DUK_ERR_SYNTAX_ERROR,
					          "invalid unicode escape while parsing identifier");
				}

				ch = decode_unicode_escape_from_window(lex_ctx, 2);

				/* IdentifierStart is stricter than IdentifierPart, so if the first
				 * character is escaped, must have a stricter check here.
				 */
				if (! (first ? duk_unicode_is_identifier_start(ch) : duk_unicode_is_identifier_part(ch))) {
					DUK_ERROR(lex_ctx->thr, DUK_ERR_SYNTAX_ERROR,
					          "invalid unicode escaped character while parsing identifier");
				}
				APPENDBUFFER(lex_ctx, ch);
				ADVANCE(lex_ctx, 6);

				/* Track number of escapes: necessary for proper keyword
				 * detection.
				 */
				out_token->num_escapes++;
			} else {
				/* Note: first character is checked against this.  But because
				 * IdentifierPart includes all IdentifierStart characters, and
				 * the first character (if unescaped) has already been checked
				 * in the if condition, this is OK.
				 */
				if (!duk_unicode_is_identifier_part(L0())) {
					break;
				}
				APPENDBUFFER(lex_ctx, L0());
				ADVANCE(lex_ctx, 1);
			}
			first = 0;
		}

		internbuffer(lex_ctx, lex_ctx->slot1_idx);
		out_token->str1 = duk_get_hstring((duk_context *) lex_ctx->thr, lex_ctx->slot1_idx);
		str = out_token->str1;
		DUK_ASSERT(str != NULL);

		out_token->t_nores = DUK_TOK_IDENTIFIER;

		INITBUFFER(lex_ctx);	/* free some memory */

		/*
		 *  Interned identifier is compared against reserved words, which are
		 *  currently interned into the heap context.  See genstrings.py.
		 *
		 *  Note that an escape in the identifier disables recognition of
		 *  keywords; e.g. "\u0069f = 1;" is a valid statement (assigns to
		 *  identifier named "if").  This is not necessarily compliant,
		 *  see test-dec-escaped-char-in-keyword.js.
		 */

		i_end = (strict_mode ? DUK_STRIDX_END_RESERVED : DUK_STRIDX_START_STRICT_RESERVED);

		advtok = ADVTOK(0, DUK_TOK_IDENTIFIER);
		if (out_token->num_escapes == 0) {
			for (i = DUK_STRIDX_START_RESERVED; i < i_end; i++) {
				DUK_ASSERT(i >= 0 && i < DUK_HEAP_NUM_STRINGS);
				if (lex_ctx->thr->strs[i] == str) {
					advtok = ADVTOK(0, DUK_STRIDX_TO_TOK(i));
					break;
				}
			}
		}
	} else if (ISDIGIT(x) || (x == '.')) {
		/* Note: decimal number may start with a period, but must be followed by a digit */

		/*
		 *  DecimalLiteral, HexIntegerLiteral, OctalIntegerLiteral
		 *  "pre-parsing", followed by an actual, accurate parser step.
		 *
		 *  Note: the leading sign character ('+' or '-') is -not- part of
		 *  the production in E5 grammar, and that the a DecimalLiteral
		 *  starting with a '0' must be followed by a non-digit.  Leading
		 *  zeroes are syntax errors and must be checked for.
		 *
		 *  FIXME: the two step parsing process is quite awkward, it would
		 *  be more straightforward to allow numconv to parse the longest
		 *  valid prefix (it already does that, it only needs to indicate
		 *  where the input ended).  However, the lexer decodes characters
		 *  using a lookup window, so this is not a trivial change.
		 */

		/* FIXME: because of the final check below (that the literal is not
		 * followed by a digit), this could maybe be simplified, if we bail
		 * out early from a leading zero (and if there are no periods etc).
		 * Maybe too complex.
		 */

		double val;
		int int_only = 0;
		int allow_hex = 0;
		int st;		/* 0=before period/exp,
		                 * 1=after period, before exp
		                 * 2=after exp, allow '+' or '-'
		                 * 3=after exp and exp sign
		                 */
		int s2n_flags;

		INITBUFFER(lex_ctx);
		if (x == '0' && (y == 'x' || y == 'X')) {
			APPENDBUFFER(lex_ctx, x);
			APPENDBUFFER(lex_ctx, y);
			ADVANCE(lex_ctx, 2);
			int_only = 1;
			allow_hex = 1;
#ifdef DUK_USE_OCTAL_SUPPORT
		} else if (!strict_mode && x == '0' && ISDIGIT(y)) {
			/* Note: if DecimalLiteral starts with a '0', it can only be
			 * followed by a period or an exponent indicator which starts
			 * with 'e' or 'E'.  Hence the if-check above ensures that
			 * OctalIntegerLiteral is the only valid NumericLiteral
			 * alternative at this point (even if y is, say, '9').
			 */
	
			APPENDBUFFER(lex_ctx, x);
			ADVANCE(lex_ctx, 1);
			int_only = 1;
		}
#endif

		st = 0;
		for (;;) {
			x = L0();	/* re-lookup curr char on first round */
			if (ISDIGIT(x)) {
				/* Note: intentionally allow leading zeroes here, as the
				 * actual parser will check for them.
				 */
				if (st == 2) {
					st = 3;
				}
			} else if (allow_hex && ISHEXDIGIT(x)) {
				/* Note: 'e' and 'E' are also accepted here. */
				;
			} else if (x == '.') {
				if (st >= 1 || int_only) {
					break;
				} else {
					st = 1;
				}
			} else if (x == 'e' || x == 'E') {
				if (st >= 2 || int_only) {
					break;
				} else {
					st = 2;
				}
			} else if (x == '-' || x == '+') {
				if (st != 2) {
					break;
				} else {
					st = 3;
				}
			} else {
				break;
			}
			APPENDBUFFER(lex_ctx, x);
			ADVANCE(lex_ctx, 1);
		}

		/* FIXME: better coercion */
		internbuffer(lex_ctx, lex_ctx->slot1_idx);

		s2n_flags = DUK_S2N_FLAG_ALLOW_EXP |
		            DUK_S2N_FLAG_ALLOW_FRAC |
		            DUK_S2N_FLAG_ALLOW_NAKED_FRAC |
		            DUK_S2N_FLAG_ALLOW_EMPTY_FRAC |
#ifdef DUK_USE_OCTAL_SUPPORT
		            (strict_mode ? 0 : DUK_S2N_FLAG_ALLOW_AUTO_OCT_INT) |
#endif
		            DUK_S2N_FLAG_ALLOW_AUTO_HEX_INT;

		duk_dup((duk_context *) lex_ctx->thr, lex_ctx->slot1_idx);
		duk_numconv_parse((duk_context *) lex_ctx->thr, 10 /*radix*/, s2n_flags);
		val = duk_to_number((duk_context *) lex_ctx->thr, -1);
		if (isnan(val)) {
			DUK_ERROR(lex_ctx->thr, DUK_ERR_SYNTAX_ERROR, "invalid numeric literal");
		}
		duk_replace((duk_context *) lex_ctx->thr, lex_ctx->slot1_idx);  /* FIXME: or pop? */

		INITBUFFER(lex_ctx);	/* free some memory */

		/* Section 7.8.3 (note): NumericLiteral must be followed by something other than
		 * IdentifierStart or DecimalDigit.
		 */

		if (ISDIGIT(L0()) || duk_unicode_is_identifier_start(L0())) {
			DUK_ERROR(lex_ctx->thr, DUK_ERR_SYNTAX_ERROR, "invalid numeric literal");
		}

		out_token->num = val;
		advtok = ADVTOK(0, DUK_TOK_NUMBER);
	} else if (x == '"' || x == '\'') {
		int quote = x;	/* duk_u8 type yields larger code */
		int adv;

		INITBUFFER(lex_ctx);
		for (;;) {
			ADVANCE(lex_ctx, 1);	/* eat opening quote on first loop */
			x = L0();
			if (x < 0 || duk_unicode_is_line_terminator(x)) {
				DUK_ERROR(lex_ctx->thr, DUK_ERR_SYNTAX_ERROR,
				          "eof or line terminator while parsing string literal");
			}
			if (x == quote) {
				ADVANCE(lex_ctx, 1);	/* eat closing quote */
				break;
			}
			if (x == '\\') {
				/* L0        -> '\' char
				 * L1 ... L5 -> more lookup
				 */

				x = L1();
				y = L2();

				/* How much to advance before next loop; note that next loop
				 * will advance by 1 anyway, so -1 from the total escape
				 * length (e.g. len('\uXXXX') - 1 = 6 - 1).  As a default,
				 * 1 is good.
				 */
				adv = 2 - 1;	/* note: long live range */

				if (x < 0) {
					DUK_ERROR(lex_ctx->thr, DUK_ERR_SYNTAX_ERROR,
					          "eof while parsing string literal");
				}
				if (duk_unicode_is_line_terminator(x)) {
					/* line continuation */
					if (x == 0x000d && y == 0x000a) {
						/* CR LF again a special case */
						adv = 3 - 1;
					}
				} else if (x == '\'') {
					APPENDBUFFER(lex_ctx, 0x0027);
				} else if (x == '"') {
					APPENDBUFFER(lex_ctx, 0x0022);
				} else if (x == '\\') {
					APPENDBUFFER(lex_ctx, 0x005c);
				} else if (x == 'b') {
					APPENDBUFFER(lex_ctx, 0x0008);
				} else if (x == 'f') {
					APPENDBUFFER(lex_ctx, 0x000c);
				} else if (x == 'n') {
					APPENDBUFFER(lex_ctx, 0x000a);
				} else if (x == 'r') {
					APPENDBUFFER(lex_ctx, 0x000d);
				} else if (x == 't') {
					APPENDBUFFER(lex_ctx, 0x0009);
				} else if (x == 'v') {
					APPENDBUFFER(lex_ctx, 0x000b);
				} else if (x == 'x') {
					adv = 4 - 1;
					APPENDBUFFER(lex_ctx, decode_hex_escape_from_window(lex_ctx, 2));
				} else if (x == 'u') {
					adv = 6 - 1;
					APPENDBUFFER(lex_ctx, decode_unicode_escape_from_window(lex_ctx, 2));
				} else if (ISDIGIT(x)) {
					int ch = 0;  /* initialized to avoid warnings of unused var */

					/*
					 *  Octal escape or zero escape:
					 *    \0                                     (lookahead not DecimalDigit)
					 *    \1 ... \7                              (lookahead not DecimalDigit)
					 *    \ZeroToThree OctalDigit                (lookahead not DecimalDigit)
					 *    \FourToSeven OctalDigit                (no lookahead restrictions)
					 *    \ZeroToThree OctalDigit OctalDigit     (no lookahead restrictions)
					 *
					 *  Zero escape is part of the standard syntax.  Octal escapes are
					 *  defined in E5 Section B.1.2, and are only allowed in non-strict mode.
					 *  Any other productions starting with a decimal digit are invalid.
					 */

					if (x == '0' && !ISDIGIT(y)) {
						/* Zero escape (also allowed in non-strict mode) */
						ch = 0;
						/* adv = 2 - 1 default OK */
#ifdef DUK_USE_OCTAL_SUPPORT
					} else if (strict_mode) {
						/* No other escape beginning with a digit in strict mode */
						DUK_ERROR(lex_ctx->thr, DUK_ERR_SYNTAX_ERROR,
						          "invalid escape while parsing string literal");
					} else if (ISDIGIT03(x) && ISOCTDIGIT(y) && ISOCTDIGIT(L3())) {
						/* Three digit octal escape, digits validated. */
						adv = 4 - 1;
						ch = (hexval(lex_ctx, x) << 6) +
						     (hexval(lex_ctx, y) << 3) +
						     hexval(lex_ctx, L3());
					} else if (((ISDIGIT03(x) && !ISDIGIT(L3())) || ISDIGIT47(x)) &&
					           ISOCTDIGIT(y)) {
						/* Two digit octal escape, digits validated.
						 * 
						 * The if-condition is a bit tricky.  We could catch e.g.
						 * '\039' in the three-digit escape and fail it there (by
					         * validating the digits), but we want to avoid extra
						 * additional validation code.
						 */
						adv = 3 - 1;
						ch = (hexval(lex_ctx, x) << 3) +
						     hexval(lex_ctx, y);
					} else if (ISDIGIT(x) && !ISDIGIT(y)) {
						/* One digit octal escape, digit validated. */
						/* adv = 2 default OK */
						ch = hexval(lex_ctx, x);
#else
					/* fall through to error */
#endif
					} else {
						DUK_ERROR(lex_ctx->thr, DUK_ERR_SYNTAX_ERROR,
						          "invalid escape while parsing string literal");
					}

					APPENDBUFFER(lex_ctx, ch);
				} else {
					/* escaped NonEscapeCharacter */
					APPENDBUFFER(lex_ctx, x);
				}
				ADVANCE(lex_ctx, adv);

				/* Track number of escapes; count not really needed but directive
				 * prologues need to detect whether there were any escapes or line
				 * continuations or not.
				 */
				out_token->num_escapes++;
			} else {
				/* part of string */
				APPENDBUFFER(lex_ctx, x);
			}
		}

		internbuffer(lex_ctx, lex_ctx->slot1_idx);
		out_token->str1 = duk_get_hstring((duk_context *) lex_ctx->thr, lex_ctx->slot1_idx);

		INITBUFFER(lex_ctx);	/* free some memory */

		advtok = ADVTOK(0, DUK_TOK_STRING);
	} else if (x < 0) {
		advtok = ADVTOK(0, DUK_TOK_EOF);
	} else {
		DUK_ERROR(lex_ctx->thr, DUK_ERR_SYNTAX_ERROR, "error parsing token");
	}

	/*
	 *  Shared exit path
	 */

	ADVANCE(lex_ctx, advtok >> 8);
	out_token->t = advtok & 0xff;
	if (out_token->t_nores < 0) {
		out_token->t_nores = out_token->t;
	}
	out_token->end_line = lex_ctx->lines[0];
}

/*
 *  Tokenize input until a non-whitespace, non-lineterm token is found.
 *  Note in the output token whether a lineterm token preceded the starting
 *  point (inclusive) and the result token.  This information is needed for
 *  automatic semicolon insertion.
 *
 *  Future work:
 *
 *    * Merge with parse_input_element_raw() because only this function is
 *      called in practice.
 */

/* FIXME: change mode flags into one flags argument? */

void duk_lexer_parse_js_input_element(duk_lexer_ctx *lex_ctx,
                                      duk_token *out_token,
                                      int strict_mode,
                                      int regexp_mode) {
	int tok;
	int got_lineterm = 0;  /* got lineterm preceding non-whitespace, non-lineterm token */

	for (;;) {
		parse_input_element_raw(lex_ctx, out_token, strict_mode, regexp_mode);
		tok = out_token->t;

		DUK_DDDPRINT("RAWTOKEN: %d (line %d-%d)",
		             tok, out_token->start_line, out_token->end_line);

		if (tok == DUK_TOK_COMMENT) {
			/* single-line comment or multi-line comment without an internal lineterm */
			continue;
		} else if (tok == DUK_TOK_LINETERM) {
			/* lineterm or multi-line comment with an internal lineterm */
			got_lineterm = 1;
			continue;
		} else {
			break;
		}
	}

	out_token->lineterm = got_lineterm;

	/* Automatic semicolon insertion is allowed if a token is preceded
	 * by line terminator(s), or terminates a statement list (right curly
	 * or EOF).
	 */
	if (got_lineterm || tok == DUK_TOK_RCURLY || tok == DUK_TOK_EOF) {
		out_token->allow_auto_semi = 1;
	} else {
		out_token->allow_auto_semi = 0;
	}
}

#ifdef DUK_USE_REGEXP_SUPPORT

/*
 *  Parse a RegExp token.  The grammar is described in E5 Section 15.10.
 *  Terminal constructions (such as quantifiers) are parsed directly here.
 *
 *  0xffffffffU is used as a marker for "infinity" in quantifiers.  Further,
 *  MAX_REGEXP_QUANTIFIER_DIGITS limits the maximum number of digits that
 *  will be accepted for a quantifier.
 */

void duk_lexer_parse_re_token(duk_lexer_ctx *lex_ctx, duk_re_token *out_token) {
	int advtok = 0;  /* init is unnecessary but suppresses "may be used uninitialized" warnings */
	int x, y;

	memset(out_token, 0, sizeof(*out_token));

	x = L0();
	y = L1();

	DUK_DDDPRINT("parsing regexp token, L0=%d, L1=%d", x, y);

	switch (x) {
	case '|': {
		advtok = ADVTOK(1, DUK_RETOK_DISJUNCTION);
		break;
	}
	case '^': {
		advtok = ADVTOK(1, DUK_RETOK_ASSERT_START);
		break;
	}
	case '$': {
		advtok = ADVTOK(1, DUK_RETOK_ASSERT_END);
		break;
	}
	case '?': {
		out_token->qmin = 0;
		out_token->qmax = 1;	
		if (y == '?') {
			advtok = ADVTOK(2, DUK_RETOK_QUANTIFIER);
			out_token->greedy = 0;
		} else {
			advtok = ADVTOK(1, DUK_RETOK_QUANTIFIER);
			out_token->greedy = 1;
		}
		break;
	}
	case '*': {
		out_token->qmin = 0;
		out_token->qmax = DUK_RE_QUANTIFIER_INFINITE;
		if (y == '?') {
			advtok = ADVTOK(2, DUK_RETOK_QUANTIFIER);
			out_token->greedy = 0;
		} else {
			advtok = ADVTOK(1, DUK_RETOK_QUANTIFIER);
			out_token->greedy = 1;
		}
		break;
	}
	case '+': {
		out_token->qmin = 1;
		out_token->qmax = DUK_RE_QUANTIFIER_INFINITE;
		if (y == '?') {
			advtok = ADVTOK(2, DUK_RETOK_QUANTIFIER);
			out_token->greedy = 0;
		} else {
			advtok = ADVTOK(1, DUK_RETOK_QUANTIFIER);
			out_token->greedy = 1;
		}
		break;
	}
	case '{': {
		/* Production allows 'DecimalDigits', including leading zeroes */
		duk_u32 val1 = 0;
		duk_u32 val2 = DUK_RE_QUANTIFIER_INFINITE;
		int digits = 0;
		for (;;) {
			ADVANCE(lex_ctx, 1);	/* eat '{' on entry */
			x = L0();
			if (ISDIGIT(x)) {
				if (digits >= MAX_REGEXP_QUANTIFIER_DIGITS) {
					DUK_ERROR(lex_ctx->thr, DUK_ERR_SYNTAX_ERROR,
					          "invalid regexp quantifier (too many digits)");
				}
				digits++;
				val1 = val1 * 10 + hexval(lex_ctx, x);
			} else if (x == ',') {
				if (val2 != DUK_RE_QUANTIFIER_INFINITE) {
					DUK_ERROR(lex_ctx->thr, DUK_ERR_SYNTAX_ERROR,
					          "invalid regexp quantifier (double comma)");
				}
				if (L1() == '}') {
					/* form: { DecimalDigits , }, val1 = min count */
					if (digits == 0) {
						DUK_ERROR(lex_ctx->thr, DUK_ERR_SYNTAX_ERROR,
						          "invalid regexp quantifier (missing digits)");
					}
					out_token->qmin = val1;
					out_token->qmax = DUK_RE_QUANTIFIER_INFINITE;
					ADVANCE(lex_ctx, 2);
					break;
				}
				val2 = val1;
				val1 = 0;
				digits = 0;	/* not strictly necessary because of lookahead '}' above */
			} else if (x == '}') {
				if (digits == 0) {
					DUK_ERROR(lex_ctx->thr, DUK_ERR_SYNTAX_ERROR,
					          "invalid regexp quantifier (missing digits)");
				}
				if (val2 != DUK_RE_QUANTIFIER_INFINITE) {
					/* val2 = min count, val1 = max count */
					out_token->qmin = val2;
					out_token->qmax = val1;
				} else {
					/* val1 = count */
					out_token->qmin = val1;
					out_token->qmax = val1;
				}
				ADVANCE(lex_ctx, 1);
				break;
			} else {
				DUK_ERROR(lex_ctx->thr, DUK_ERR_SYNTAX_ERROR,
				          "invalid regexp quantifier (unknown char)");
			}
		}
		if (L0() == '?') {
			out_token->greedy = 0;
			ADVANCE(lex_ctx, 1);
		} else {
			out_token->greedy = 1;
		}
		advtok = ADVTOK(0, DUK_RETOK_QUANTIFIER);
		break;
	}
	case '.': {
		advtok = ADVTOK(1, DUK_RETOK_ATOM_PERIOD);
		break;
	}
	case '\\': {
		/* The E5.1 specification does not seem to allow IdentifierPart characters
		 * to be used as identity escapes.  Unfortunately this includes '$', which
		 * cannot be escaped as '\$'; it needs to be escaped e.g. as '\u0024'.
		 * Many other implementations (including V8 and Rhino, for instance) do
		 * accept '\$' as a valid identity escape, which is quite pragmatic.
		 * See: test-regexp-identity-escape-dollar.js.
		 */

		advtok = ADVTOK(2, DUK_RETOK_ATOM_CHAR);	/* default: char escape (two chars) */
		if (y == 'b') {
			advtok = ADVTOK(2, DUK_RETOK_ASSERT_WORD_BOUNDARY);
		} else if (y == 'B') {
			advtok = ADVTOK(2, DUK_RETOK_ASSERT_NOT_WORD_BOUNDARY);
		} else if (y == 'f') {
			out_token->num = 0x000c;
		} else if (y == 'n') {
			out_token->num = 0x000a;
		} else if (y == 't') {
			out_token->num = 0x0009;
		} else if (y == 'r') {
			out_token->num = 0x000d;
		} else if (y == 'v') {
			out_token->num = 0x000b;
		} else if (y == 'c') {
			x = L2();
			if ((x >= 'a' && x <= 'z') ||
			    (x >= 'A' && x <= 'Z')) {
				out_token->num = (x % 32);
				advtok = ADVTOK(3, DUK_RETOK_ATOM_CHAR);
			} else {
				DUK_ERROR(lex_ctx->thr, DUK_ERR_SYNTAX_ERROR,
				          "invalid regexp control escape");
			}
		} else if (y == 'x') {
			out_token->num = decode_hex_escape_from_window(lex_ctx, 2);
			advtok = ADVTOK(4, DUK_RETOK_ATOM_CHAR);
		} else if (y == 'u') {
			out_token->num = decode_unicode_escape_from_window(lex_ctx, 2);
			advtok = ADVTOK(6, DUK_RETOK_ATOM_CHAR);
		} else if (y == 'd') {
			advtok = ADVTOK(2, DUK_RETOK_ATOM_DIGIT);
		} else if (y == 'D') {
			advtok = ADVTOK(2, DUK_RETOK_ATOM_NOT_DIGIT);
		} else if (y == 's') {
			advtok = ADVTOK(2, DUK_RETOK_ATOM_WHITE);
		} else if (y == 'S') {
			advtok = ADVTOK(2, DUK_RETOK_ATOM_NOT_WHITE);
		} else if (y == 'w') {
			advtok = ADVTOK(2, DUK_RETOK_ATOM_WORD_CHAR);
		} else if (y == 'W') {
			advtok = ADVTOK(2, DUK_RETOK_ATOM_NOT_WORD_CHAR);
		} else if (ISDIGIT(y)) {
			/* E5 Section 15.10.2.11 */
			if (y == '0') {
				if (ISDIGIT(L2())) {
					DUK_ERROR(lex_ctx->thr, DUK_ERR_SYNTAX_ERROR,
					          "invalid regexp escape");
				}
				out_token->num = 0x0000;
				advtok = ADVTOK(2, DUK_RETOK_ATOM_CHAR);
			} else {
				/* FIXME: shared parsing? */
				duk_u32 val = 0;
				int i;
				for (i = 0; ; i++) {
					if (i >= MAX_REGEXP_DECIMAL_ESCAPE_DIGITS) {
						DUK_ERROR(lex_ctx->thr, DUK_ERR_SYNTAX_ERROR,
						          "invalid regexp escape (decimal escape too long)");
					}
					ADVANCE(lex_ctx, 1);	/* eat backslash on entry */
					x = L0();
					if (!ISDIGIT(x)) {
						break;
					}
					val = val * 10 + hexval(lex_ctx, x);
				}
				/* L0() cannot be a digit, because the loop doesn't terminate if it is */
				advtok = ADVTOK(0, DUK_RETOK_ATOM_BACKREFERENCE);
				out_token->num = val;
			}
		} else if (!duk_unicode_is_identifier_part(y) ||
		           y == DUK_UNICODE_CP_ZWNJ ||
		           y == DUK_UNICODE_CP_ZWJ ||
		           y == '$') {
			/* IdentityEscape, with dollar added as a valid additional
			 * non-standard escape (see test-regexp-identity-escape-dollar.js).
			 */
			out_token->num = y;
		} else {
			DUK_ERROR(lex_ctx->thr, DUK_ERR_SYNTAX_ERROR,
			          "invalid regexp escape");
		}
		break;
	}
	case '(': {
		/* FIXME: naming is inconsistent: ATOM_END_GROUP ends an ASSERT_START_LOOKAHEAD */

		if (y == '?') {
			if (L2() == '=') {
				/* (?= */
				advtok = ADVTOK(3, DUK_RETOK_ASSERT_START_POS_LOOKAHEAD);
			} else if (L2() == '!') {
				/* (?! */
				advtok = ADVTOK(3, DUK_RETOK_ASSERT_START_NEG_LOOKAHEAD);
			} else if (L2() == ':') {
				/* (?: */
				advtok = ADVTOK(3, DUK_RETOK_ATOM_START_NONCAPTURE_GROUP);
			}
		} else {
			/* ( */
			advtok = ADVTOK(1, DUK_RETOK_ATOM_START_CAPTURE_GROUP);
		}
		break;
	}
	case ')': {
		advtok = ADVTOK(1, DUK_RETOK_ATOM_END_GROUP);
		break;
	}
	case '[': {
		/*
		 *  To avoid creating a heavy intermediate value for the list of ranges,
		 *  only the start token ('[' or '[^') is parsed here.  The regexp
		 *  compiler parses the ranges itself.
		 */
		advtok = ADVTOK(1, DUK_RETOK_ATOM_START_CHARCLASS);
		if (y == '^') {
			advtok = ADVTOK(2, DUK_RETOK_ATOM_START_CHARCLASS_INVERTED);
		}
		break;
	}
	case ']':
	case '}': {
		/* Although these could be parsed as PatternCharacters unambiguously (here),
		 * E5 Section 15.10.1 grammar explicitly forbids these as PatternCharacters.
		 */
		DUK_ERROR(lex_ctx->thr, DUK_ERR_SYNTAX_ERROR,
		          "invalid regexp character");
		break;
	}
	case -1: {
		/* EOF */
		advtok = ADVTOK(0, DUK_TOK_EOF);
		break;
	}
	default: {
		/* PatternCharacter, all excluded characters are matched by cases above */
		advtok = ADVTOK(1, DUK_RETOK_ATOM_CHAR);
		out_token->num = x;
		break;
	}
	}

	/*
	 *  Shared exit path
	 */

	ADVANCE(lex_ctx, advtok >> 8);
	out_token->t = advtok & 0xff;
}

/*
 *  Special parser for character classes; calls callback for every
 *  range parsed and returns the number of ranges present.
 */

/* FIXME: this duplicates functionality in duk_regexp.c where a similar loop is
 * required anyway.  We could use that BUT we need to update the regexp compiler
 * 'nranges' too.  Work this out a bit more cleanly to save space.
 */

/* FIXME: the handling of character range detection is a bit convoluted.
 * Try to simplify and make smaller.
 */

/* FIXME: logic for handling character ranges is now incorrect, it will accept
 * e.g. [\d-z] whereas it should croak from it?  SMJS accepts this too, though.
 *
 * Needs a read through and a lot of additional tests.
 */

static void emit_u16_direct_ranges(duk_lexer_ctx *lex_ctx,
                                   duk_re_range_callback gen_range,
                                   void *userdata,
                                   duk_u16 *ranges,
                                   int num) {
	duk_u16 *ranges_end = ranges + num;
	while (ranges < ranges_end) {
		/* mark range 'direct', bypass canonicalization (see Wiki) */
		gen_range(userdata, ranges[0], ranges[1], 1);
		ranges += 2;
	}
}

void duk_lexer_parse_re_ranges(duk_lexer_ctx *lex_ctx, duk_re_range_callback gen_range, void *userdata) {
	duk_i32 start = -1;
	int dash = 0;
	duk_i32 ch;

	DUK_DDPRINT("parsing regexp ranges");

	for (;;) {
		int x;

		x = L0();
		ADVANCE(lex_ctx, 1);

		ch = -1;  /* not strictly necessary, but avoids "uninitialized variable" warnings */

		if (x < 0) {
			DUK_ERROR(lex_ctx->thr, DUK_ERR_SYNTAX_ERROR,
			          "eof while parsing character class");
		} else if (x == ']') {
			DUK_ASSERT(!dash);	/* lookup should prevent this */
			if (start >= 0) {
				gen_range(userdata, start, start, 0);
			}
			break;
		} else if (x == '-') {
			if (start >= 0 && !dash && L0() != ']') {
				/* '-' as a range indicator */
				dash = 1;
				continue;
			} else {
				/* '-' verbatim */
				ch = x;
			}
		} else if (x == '\\') {
			/*
			 *  The escapes are same as outside a character class, except that \b has a
			 *  different meaning, and \B and backreferences are prohibited (see E5
			 *  Section 15.10.2.19).  However, it's difficult to share code because we
			 *  handle e.g. "\n" very differently: here we generate a single character
			 *  range for it.
			 */

			x = L0();
			ADVANCE(lex_ctx, 1);

			if (x == 'b') {
				/* Note: '\b' in char class is different than outside (assertion),
				 * '\B' is not allowed and is caught by the duk_unicode_is_identifier_part()
				 * check below.
				 */
				ch = 0x0008;
			} else if (x == 'f') {
				ch = 0x000c;
			} else if (x == 'n') {
				ch = 0x000a;
			} else if (x == 't') {
				ch = 0x0009;
			} else if (x == 'r') {
				ch = 0x000d;
			} else if (x == 'v') {
				ch = 0x000b;
			} else if (x == 'c') {
				x = L0();
				ADVANCE(lex_ctx, 1);
				if ((x >= 'a' && x <= 'z') ||
				    (x >= 'A' && x <= 'Z')) {
					ch = (x % 32);
				} else {
					DUK_ERROR(lex_ctx->thr, DUK_ERR_SYNTAX_ERROR,
					          "invalid regexp control escape");
					return;  /* never reached, but avoids warnings of
					          * potentially unused variables.
					          */
				}
			} else if (x == 'x') {
				ch = decode_hex_escape_from_window(lex_ctx, 0);
				ADVANCE(lex_ctx, 2);
			} else if (x == 'u') {
				ch = decode_unicode_escape_from_window(lex_ctx, 0);
				ADVANCE(lex_ctx, 4);
			} else if (x == 'd') {
				emit_u16_direct_ranges(lex_ctx,
				                       gen_range,
				                       userdata,
				                       duk_unicode_re_ranges_digit,
				                       sizeof(duk_unicode_re_ranges_digit) / sizeof(duk_u16));
				ch = -1;
			} else if (x == 'D') {
				emit_u16_direct_ranges(lex_ctx,
				                       gen_range,
				                       userdata,
				                       duk_unicode_re_ranges_not_digit,
				                       sizeof(duk_unicode_re_ranges_not_digit) / sizeof(duk_u16));
				ch = -1;
			} else if (x == 's') {
				emit_u16_direct_ranges(lex_ctx,
				                       gen_range,
				                       userdata,
				                       duk_unicode_re_ranges_white,
				                       sizeof(duk_unicode_re_ranges_white) / sizeof(duk_u16));
				ch = -1;
			} else if (x == 'S') {
				emit_u16_direct_ranges(lex_ctx,
				                       gen_range,
				                       userdata,
				                       duk_unicode_re_ranges_not_white,
				                       sizeof(duk_unicode_re_ranges_not_white) / sizeof(duk_u16));
				ch = -1;
			} else if (x == 'w') {
				emit_u16_direct_ranges(lex_ctx,
				                       gen_range,
				                       userdata,
				                       duk_unicode_re_ranges_wordchar,
				                       sizeof(duk_unicode_re_ranges_wordchar) / sizeof(duk_u16));
				ch = -1;
			} else if (x == 'W') {
				emit_u16_direct_ranges(lex_ctx,
				                       gen_range,
				                       userdata,
				                       duk_unicode_re_ranges_not_wordchar,
				                       sizeof(duk_unicode_re_ranges_not_wordchar) / sizeof(duk_u16));
				ch = -1;
			} else if (ISDIGIT(x)) {
				/* DecimalEscape, only \0 is allowed, no leading zeroes are allowed */
				if (x == 0 && !ISDIGIT(L0())) {
					ch = 0x0000;
				} else {
					DUK_ERROR(lex_ctx->thr, DUK_ERR_SYNTAX_ERROR,
					          "invalid decimal escape");
				}
			} else if (!duk_unicode_is_identifier_part(x)) {
				/* IdentityEscape */
				ch = x;
			} else {
				DUK_ERROR(lex_ctx->thr, DUK_ERR_SYNTAX_ERROR,
				          "invalid regexp escape");
			}
		} else {
			/* character represents itself */
			ch = x;
		}

		/* ch is a literal character here or -1 if parsed entity was
		 * an escape such as "\s".
		 */

		if (ch < 0) {
			/* multi-character sets not allowed as part of ranges, see
			 * E5 Section 15.10.2.15, abstract operation CharacterRange.
			 */
			if (start >= 0) {
				if (dash) {
					DUK_ERROR(lex_ctx->thr, DUK_ERR_SYNTAX_ERROR,
					          "invalid range");
				} else {
					gen_range(userdata, start, start, 0);
					start = -1;
					/* dash is already 0 */
				}
			}
		} else {
			if (start >= 0) {
				if (dash) {
					if (start > ch) {
						DUK_ERROR(lex_ctx->thr, DUK_ERR_SYNTAX_ERROR,
						          "invalid range");
					}
					gen_range(userdata, start, ch, 0);
					start = -1;
					dash = 0;
				} else {
					gen_range(userdata, start, start, 0);
					start = ch;
					/* dash is already 0 */
				}
			} else {
				start = ch;
			}
		}
	}

	return;
}

#endif  /* DUK_USE_REGEXP_SUPPORT */

#line 1 "duk_numconv.c"
/*
 *  Number-to-string and string-to-number conversions.
 *
 *  Slow path number-to-string and string-to-number conversion is based on
 *  a Dragon4 variant, with fast paths for small integers.  Big integer
 *  arithmetic is needed for guaranteeing that the conversion is correct
 *  and uses a minimum number of digits.  The big number arithmetic has a
 *  fixed maximum size and does not require dynamic allocations.
 *
 *  See: doc/number_conversion.txt.
 */

/* include removed: duk_internal.h */

#define  IEEE_DOUBLE_EXP_BIAS  1023
#define  IEEE_DOUBLE_EXP_MIN   (-1022)   /* biased exp == 0 -> denormal, exp -1022 */

#define  DIGITCHAR(x)  duk_lc_digits[(x)]

/*
 *  Tables generated with src/gennumdigits.py.
 *
 *  str2num_digits_for_radix indicates, for each radix, how many input
 *  digits should be considered significant for string-to-number conversion.
 *  The input is also padded to this many digits to give the Dragon4
 *  conversion enough (apparent) precision to work with.
 *
 *  str2num_exp_limits indicates, for each radix, the radix-specific
 *  minimum/maximum exponent values (for a Dragon4 integer mantissa)
 *  below and above which the number is guaranteed to underflow to zero
 *  or overflow to Infinity.  This allows parsing to keep bigint values
 *  bounded.
 */

static unsigned char str2num_digits_for_radix[] = {
	69, 44, 35, 30, 27, 25, 23, 22, 20, 20,    /* 2 to 11 */
	20, 19, 19, 18, 18, 17, 17, 17, 16, 16,    /* 12 to 21 */
	16, 16, 16, 15, 15, 15, 15, 15, 15, 14,    /* 22 to 31 */
	14, 14, 14, 14, 14                         /* 31 to 36 */
};

typedef struct {
	int16_t upper;
	int16_t lower;
} duk_exp_limits;

static duk_exp_limits str2num_exp_limits[] = {
	{ 957, -1147 }, { 605, -725 },  { 479, -575 },  { 414, -496 },
	{ 372, -446 },  { 342, -411 },  { 321, -384 },  { 304, -364 },
	{ 291, -346 },  { 279, -334 },  { 268, -323 },  { 260, -312 },
	{ 252, -304 },  { 247, -296 },  { 240, -289 },  { 236, -283 },
	{ 231, -278 },  { 227, -273 },  { 223, -267 },  { 220, -263 },
	{ 216, -260 },  { 213, -256 },  { 210, -253 },  { 208, -249 },
	{ 205, -246 },  { 203, -244 },  { 201, -241 },  { 198, -239 },
	{ 196, -237 },  { 195, -234 },  { 193, -232 },  { 191, -230 },
	{ 190, -228 },  { 188, -226 },  { 187, -225 },
};

/*
 *  Limited functionality bigint implementation.
 *
 *  Restricted to non-negative numbers with less than 32 * BI_MAX_PARTS bits,
 *  with the caller responsible for ensuring this is never exceeded.  No memory
 *  allocation (except stack) is needed for bigint computation.  Operations
 *  have been tailored for number conversion needs.
 *
 *  Argument order is "assignment order", i.e. target first, then arguments:
 *  x <- y * z  -->  bi_mul(x, y, z);
 */

/* This upper value has been experimentally determined; debug build will check
 * bigint size with assertions.
 */
#define  BI_MAX_PARTS  37  /* 37x32 = 1184 bits */

#ifdef DUK_USE_DDDEBUG
#define  BI_PRINT(name,x)  bi_print((name),(x))
#else
#define  BI_PRINT(name,x)
#endif

/* Current size is about 152 bytes. */
typedef struct {
	int n;
	uint32_t v[BI_MAX_PARTS];  /* low to high */
} duk_bigint;

#ifdef DUK_USE_DDDEBUG
static void bi_print(const char *name, duk_bigint *x) {
	char buf[1024];
	char *p = buf;
	int i;

	p += sprintf(p, "%p n=%d", (void *) x, x->n);
	if (x->n == 0) {
		p += sprintf(p, " 0");
	}
	for (i = x->n - 1; i >= 0; i--) {
		p += sprintf(p, " %08x", (unsigned int) x->v[i]);
	}

	DUK_DDDPRINT("%s: %s", name, buf);
}
#endif

#ifdef DUK_USE_ASSERTIONS
static int bi_is_valid(duk_bigint *x) {
	int is_normalized = (x->n == 0) || (x->v[x->n - 1] != 0);
	int is_valid_size = (x->n >= 0) && (x->n <= BI_MAX_PARTS);
	return is_normalized && is_valid_size;
}
#endif

static void bi_normalize(duk_bigint *x) {
	int i;

	for (i = x->n - 1; i >= 0; i--) {
		if (x->v[i] != 0) {
			break;
		}
	}

	/* Note: if 'x' is zero, x->n becomes 0 here */
	x->n = i + 1;
	DUK_ASSERT(bi_is_valid(x));
}

/* x <- y */
static void bi_copy(duk_bigint *x, duk_bigint *y) {
	int n;

	n = y->n;
	x->n = n;
	if (n == 0) {
		return;
	}
	memcpy((void *) x->v, (void *) y->v, (size_t) (sizeof(uint32_t) * n));
}

static void bi_set_small(duk_bigint *x, uint32_t v) {
	if (v == 0) {
		x->n = 0;
	} else {
		x->n = 1;
		x->v[0] = v;
	}
	DUK_ASSERT(bi_is_valid(x));
}

/* Return value: <0  <=>  x < y
 *                0  <=>  x == y
 *               >0  <=>  x > y
 */
static int bi_compare(duk_bigint *x, duk_bigint *y) {
	int i;
	int nx, ny;
	uint32_t tx, ty;

	DUK_ASSERT(bi_is_valid(x));
	DUK_ASSERT(bi_is_valid(y));

	nx = x->n;
	ny = y->n;
	if (nx > ny) {
		goto ret_gt;
	}
	if (nx < ny) {
		goto ret_lt;
	}
	for (i = nx - 1; i >= 0; i--) {
		tx = x->v[i];
		ty = y->v[i];

		if (tx > ty) {
			goto ret_gt;
		}
		if (tx < ty) {
			goto ret_lt;
		}
	}

	return 0;

 ret_gt:
	return 1;

 ret_lt:
	return -1;
}

/* x <- y + z */
static void bi_add(duk_bigint *x, duk_bigint *y, duk_bigint *z) {
	uint64_t tmp;
	int i, ny, nz;

	DUK_ASSERT(bi_is_valid(y));
	DUK_ASSERT(bi_is_valid(z));

	if (z->n > y->n) {
		duk_bigint *t;
		t = y; y = z; z = t;
	}
	DUK_ASSERT(y->n >= z->n);

	ny = y->n; nz = z->n;
	tmp = 0;
	for (i = 0; i < ny; i++) {
		DUK_ASSERT(i < BI_MAX_PARTS);
		tmp += y->v[i];
		if (i < nz) {
			tmp += z->v[i];
		}
		x->v[i] = (uint32_t) (tmp & 0xffffffffU);
		tmp = tmp >> 32;
	}
	if (tmp != 0) {
		DUK_ASSERT(i < BI_MAX_PARTS);
		x->v[i++] = (uint32_t) tmp;
	}
	x->n = i;
	DUK_ASSERT(x->n <= BI_MAX_PARTS);

	/* no need to normalize */
	DUK_ASSERT(bi_is_valid(x));
}

/* x <- y + z */
static void bi_add_small(duk_bigint *x, duk_bigint *y, uint32_t z) {
	duk_bigint tmp;

	DUK_ASSERT(bi_is_valid(y));

	/* FIXME: optimize, though only one caller now */
	bi_set_small(&tmp, z);
	bi_add(x, y, &tmp);

	DUK_ASSERT(bi_is_valid(x));
}

#if 0  /* unused */
/* x <- x + y, use t as temp */
static void bi_add_copy(duk_bigint *x, duk_bigint *y, duk_bigint *t) {
	bi_add(t, x, y);
	bi_copy(x, t);
}
#endif

/* x <- y - z, require x >= y => z >= 0, i.e. y >= z */
static void bi_sub(duk_bigint *x, duk_bigint *y, duk_bigint *z) {
	int ny, nz;
	int i;
	uint32_t ty, tz;
	int64_t tmp;

	DUK_ASSERT(bi_is_valid(y));
	DUK_ASSERT(bi_is_valid(z));
	DUK_ASSERT(bi_compare(y, z) >= 0);
	DUK_ASSERT(y->n >= z->n);

	ny = y->n; nz = z->n;
	tmp = 0;
	for (i = 0; i < ny; i++) {
		ty = y->v[i];
		if (i < nz) {
			tz = z->v[i];
		} else {
			tz = 0;
		}
		tmp = (int64_t) ty - (int64_t) tz + tmp;
		x->v[i] = (uint32_t) (tmp & 0xffffffffU);
		tmp = tmp >> 32;  /* 0 or -1 */
	}
	DUK_ASSERT(tmp == 0);

	x->n = i;
	bi_normalize(x);  /* need to normalize, may even cancel to 0 */
	DUK_ASSERT(bi_is_valid(x));
}

#if 0  /* unused */
/* x <- y - z */
static void bi_sub_small(duk_bigint *x, duk_bigint *y, uint32_t z) {
	duk_bigint tmp;

	DUK_ASSERT(bi_is_valid(y));

	/* FIXME: optimize */
	bi_set_small(&tmp, z);
	bi_sub(x, y, &tmp);

	DUK_ASSERT(bi_is_valid(x));
}
#endif

/* x <- x - y, use t as temp */
static void bi_sub_copy(duk_bigint *x, duk_bigint *y, duk_bigint *t) {
	bi_sub(t, x, y);
	bi_copy(x, t);
}

/* x <- y * z */
static void bi_mul(duk_bigint *x, duk_bigint *y, duk_bigint *z) {
	int i, j, nx, nz;
	uint64_t tmp;

	DUK_ASSERT(bi_is_valid(y));
	DUK_ASSERT(bi_is_valid(z));

	nx = y->n + z->n;  /* max possible */
	DUK_ASSERT(nx <= BI_MAX_PARTS);

	if (nx == 0) {
		/* Both inputs are zero; cases where only one is zero can go
		 * through main algorithm.
		 */
		x->n = 0;
		return;
	}

	memset((void *) x->v, 0, (size_t) (sizeof(uint32_t) * nx));
	x->n = nx;

	nz = z->n;
	for (i = 0; i < y->n; i++) {
		tmp = 0;
		for (j = 0; j < nz; j++) {
			tmp += (uint64_t) y->v[i] * (uint64_t) z->v[j] + x->v[i+j];
			x->v[i+j] = (uint32_t) (tmp & 0xffffffffU);
			tmp = tmp >> 32;
		}
		if (tmp > 0) {
			DUK_ASSERT(i + j < nx);
			DUK_ASSERT(i + j < BI_MAX_PARTS);
			x->v[i+j] += (uint32_t) tmp;
		}
	}

	bi_normalize(x);
	DUK_ASSERT(bi_is_valid(x));
}

/* x <- y * z */
static void bi_mul_small(duk_bigint *x, duk_bigint *y, uint32_t z) {
	duk_bigint tmp;

	DUK_ASSERT(bi_is_valid(y));

	/* FIXME: optimize */
	bi_set_small(&tmp, z);
	bi_mul(x, y, &tmp);

	DUK_ASSERT(bi_is_valid(x));
}

/* x <- x * y, use t as temp */
static void bi_mul_copy(duk_bigint *x, duk_bigint *y, duk_bigint *t) {
	bi_mul(t, x, y);
	bi_copy(x, t);
}

/* x <- x * y, use t as temp */
static void bi_mul_small_copy(duk_bigint *x, uint32_t y, duk_bigint *t) {
	bi_mul_small(t, x, y);
	bi_copy(x, t);
}

static int bi_is_even(duk_bigint *x) {
	DUK_ASSERT(bi_is_valid(x));
	return (x->n == 0) || ((x->v[0] & 0x01) == 0);
}

static int bi_is_zero(duk_bigint *x) {
	DUK_ASSERT(bi_is_valid(x));
	return (x->n == 0);  /* this is the case for normalized numbers */
}

/* Bigint is 2^52.  Used to detect normalized IEEE double mantissa values
 * which are at the lowest edge (next floating point value downwards has
 * a different exponent).  The lowest mantissa has the form:
 *
 *     1000........000    (52 zeroes; only "hidden bit" is set)
 */
static int bi_is_2to52(duk_bigint *x) {
	DUK_ASSERT(bi_is_valid(x));
	return (x->n == 2) && (x->v[0] == 0) && (x->v[1] == (1 << (52-32)));
}

/* x <- (1<<y) */
static void bi_twoexp(duk_bigint *x, int y) {
	int n, r;

	n = (y / 32) + 1;
	DUK_ASSERT(n > 0);
	r = y % 32;
	memset((void *) x->v, 0, sizeof(uint32_t) * n);
	x->n = n;
	x->v[n - 1] = (((uint32_t) 1) << r);
}

/* x <- b^y; use t1 and t2 as temps */
static void bi_exp_small(duk_bigint *x, int b, int y, duk_bigint *t1, duk_bigint *t2) {
	/* Fast path the binary case */

	DUK_ASSERT(x != t1 && x != t2 && t1 != t2);  /* distinct bignums, easy mistake to make */
	DUK_ASSERT(b >= 0);
	DUK_ASSERT(y >= 0);

	if (b == 2) {
		bi_twoexp(x, y);
		return;
	}

	/* http://en.wikipedia.org/wiki/Exponentiation_by_squaring */

	DUK_DDDPRINT("exp_small: b=%d, y=%d", b, y);

	bi_set_small(x, 1);
	bi_set_small(t1, b);
	for (;;) {
		/* Loop structure ensures that we don't compute t1^2 unnecessarily
		 * on the final round, as that might create a bignum exceeding the
		 * current BI_MAX_PARTS limit.
		 */
		if (y & 0x01) {
			bi_mul_copy(x, t1, t2);
		}
		y = y >> 1;
		if (y == 0) {
			break;
		}
		bi_mul_copy(t1, t1, t2);
	}

	BI_PRINT("exp_small result", x);
}

/*
 *  A Dragon4 number-to-string variant, based on:
 *
 *    Guy L. Steele Jr., Jon L. White: "How to Print Floating-Point Numbers
 *    Accurately"
 *
 *    Robert G. Burger, R. Kent Dybvig: "Printing Floating-Point Numbers
 *    Quickly and Accurately"
 *
 *  The current algorithm is based on Figure 1 of the Burger-Dybvig paper,
 *  i.e. the base implementation without logarithm estimation speedups
 *  (these would increase code footprint considerably).  Fixed-format output
 *  does not follow the suggestions in the paper; instead, we generate an
 *  extra digit and round-with-carry.
 *
 *  The same algorithm is used for number parsing (with b=10 and B=2)
 *  by generating one extra digit and doing rounding manually.
 *
 *  See doc/number_conversion.txt for limitations.
 */

/* Maximum number of digits generated. */
#define  MAX_OUTPUT_DIGITS          1040  /* (Number.MAX_VALUE).toString(2).length == 1024, + spare */

/* Maximum number of characters in formatted value. */
#define  MAX_FORMATTED_LENGTH       1040  /* (-Number.MAX_VALUE).toString(2).length == 1025, + spare */

/* Number and (minimum) size of bigints in the nc_ctx structure. */
#define  NUMCONV_CTX_NUM_BIGINTS    7
#define  NUMCONV_CTX_BIGINTS_SIZE   (sizeof(duk_bigint) * NUMCONV_CTX_NUM_BIGINTS)

typedef struct {
	/* Currently about 7*152 = 1064 bytes.  The space for these
	 * duk_bigints is used also as a temporary buffer for generating
	 * the final string.  This is a bit awkard; a union would be
	 * more correct.
	 */
	duk_bigint f, r, s, mp, mm, t1, t2;

	int is_s2n;        /* if 1, doing a string-to-number; else doing a number-to-string */
	int is_fixed;      /* if 1, doing a fixed format output (not free format) */
	int req_digits;    /* request number of output digits; 0 = free-format */
	int abs_pos;       /* digit position is absolute, not relative */
	int e;             /* exponent for 'f' */
	int b;             /* input radix */
	int B;             /* output radix */
	int k;             /* see algorithm */
	int low_ok;        /* see algorithm */
	int high_ok;       /* see algorithm */
	int unequal_gaps;  /* m+ != m- (very rarely) */

	/* Buffer used for generated digits, values are in the range [0,B-1]. */
	char digits[MAX_OUTPUT_DIGITS];
	int count;  /* digit count */
} duk_numconv_stringify_ctx;

/* Note: computes with 'idx' in assertions, so caller beware.
 * 'idx' is preincremented, i.e. '1' on first call, because it
 * is more convenient for the caller.
 */
#define  DRAGON4_OUTPUT(nc_ctx,preinc_idx,x)  do { \
		DUK_ASSERT((preinc_idx) - 1 >= 0); \
		DUK_ASSERT((preinc_idx) - 1 < MAX_OUTPUT_DIGITS); \
		((nc_ctx)->digits[(preinc_idx) - 1]) = (x); \
	} while(0)

size_t dragon4_format_uint32(char *buf, unsigned int x, int radix) {
	char *p;
	size_t len;
	int dig;
	int t;

	DUK_ASSERT(radix >= 2 && radix <= 36);

	/* A 32-bit unsigned integer formats to at most 32 digits (the
	 * worst case happens with radix == 2).  Output the digits backwards,
	 * and use a memmove() to get them in the right place.
	 */

	p = buf + 32;
	for (;;) {
		t = x / radix;
		dig = x - t * radix;
		x = t;

		DUK_ASSERT(dig >= 0 && dig < 36);
		*(--p) = DIGITCHAR(dig);

		if (x == 0) {
			break;
		}
	}
	len = (buf + 32) - p;

	memmove((void *) buf, (void *) p, len);

	return len;
}

static void dragon4_prepare(duk_numconv_stringify_ctx *nc_ctx) {
	int lowest_mantissa;

#if 1
	/* Assume IEEE round-to-even, so that shorter encoding can be used
	 * when round-to-even would produce correct result.  By removing
	 * this check (and having low_ok == high_ok == 0) the results would
	 * still be accurate but in some cases longer than necessary.
	 */
	if (bi_is_even(&nc_ctx->f)) {
		DUK_DDDPRINT("f is even");
		nc_ctx->low_ok = 1;
		nc_ctx->high_ok = 1;
	} else {
		DUK_DDDPRINT("f is odd");
		nc_ctx->low_ok = 0;
		nc_ctx->high_ok = 0;
	}
#else
	/* Note: not honoring round-to-even should work but now generates incorrect
	 * results.  For instance, 1e23 serializes to "a000...", i.e. the first digit
	 * equals the radix (10).  Scaling stops one step too early in this case.
	 * Don't know why this is the case, but since this code path is unused, it
	 * doesn't matter.
	 */
	nc_ctx->low_ok = 0;
	nc_ctx->high_ok = 0;
#endif

	/* For string-to-number, pretend we never have the lowest mantissa as there
	 * is no natural "precision" for inputs.  Having lowest_mantissa == 0, we'll
	 * fall into the base cases for both e >= 0 and e < 0.
	 */
	if (nc_ctx->is_s2n) {
		lowest_mantissa = 0;
	} else {
		lowest_mantissa = bi_is_2to52(&nc_ctx->f);
	}

	nc_ctx->unequal_gaps = 0;
	if (nc_ctx->e >= 0) {
		/* exponent non-negative (and thus not minimum exponent) */

		if (lowest_mantissa) {
			/* (>= e 0) AND (= f (expt b (- p 1)))
			 *
			 * be <- (expt b e) == b^e
			 * be1 <- (* be b) == (expt b (+ e 1)) == b^(e+1)
			 * r <- (* f be1 2) == 2 * f * b^(e+1)    [if b==2 -> f * b^(e+2)]
			 * s <- (* b 2)                           [if b==2 -> 4]
			 * m+ <- be1 == b^(e+1)
			 * m- <- be == b^e
			 * k <- 0
			 * B <- B
			 * low_ok <- round
			 * high_ok <- round
			 */

			DUK_DDDPRINT("non-negative exponent (not smallest exponent); "
			             "lowest mantissa value for this exponent -> "
			             "unequal gaps");

			bi_exp_small(&nc_ctx->mm, nc_ctx->b, nc_ctx->e, &nc_ctx->t1, &nc_ctx->t2);  /* mm <- b^e */
			bi_mul_small(&nc_ctx->mp, &nc_ctx->mm, nc_ctx->b);  /* mp <- b^(e+1) */
			bi_mul_small(&nc_ctx->t1, &nc_ctx->f, 2);
			bi_mul(&nc_ctx->r, &nc_ctx->t1, &nc_ctx->mp);       /* r <- (2 * f) * b^(e+1) */
			bi_set_small(&nc_ctx->s, nc_ctx->b * 2);            /* s <- 2 * b */
			nc_ctx->unequal_gaps = 1;
		} else {
			/* (>= e 0) AND (not (= f (expt b (- p 1))))
			 *
			 * be <- (expt b e) == b^e
			 * r <- (* f be 2) == 2 * f * b^e    [if b==2 -> f * b^(e+1)]
			 * s <- 2
			 * m+ <- be == b^e
			 * m- <- be == b^e
			 * k <- 0
			 * B <- B
			 * low_ok <- round
			 * high_ok <- round
			 */

			DUK_DDDPRINT("non-negative exponent (not smallest exponent); "
			             "not lowest mantissa for this exponent -> "
			             "equal gaps");

			bi_exp_small(&nc_ctx->mm, nc_ctx->b, nc_ctx->e, &nc_ctx->t1, &nc_ctx->t2);  /* mm <- b^e */
			bi_copy(&nc_ctx->mp, &nc_ctx->mm);                /* mp <- b^e */
			bi_mul_small(&nc_ctx->t1, &nc_ctx->f, 2);
			bi_mul(&nc_ctx->r, &nc_ctx->t1, &nc_ctx->mp);     /* r <- (2 * f) * b^e */
			bi_set_small(&nc_ctx->s, 2);                      /* s <- 2 */
		}
	} else {
		/* When doing string-to-number, lowest_mantissa is always 0 so
		 * the exponent check, while incorrect, won't matter.
		 */
		if (nc_ctx->e > IEEE_DOUBLE_EXP_MIN /*not minimum exponent*/ &&
		    lowest_mantissa /* lowest mantissa for this exponent*/) {
			/* r <- (* f b 2)                                [if b==2 -> (* f 4)]
			 * s <- (* (expt b (- 1 e)) 2) == b^(1-e) * 2    [if b==2 -> b^(2-e)]
			 * m+ <- b == 2
			 * m- <- 1
			 * k <- 0
			 * B <- B
			 * low_ok <- round
			 * high_ok <- round
			 */

			DUK_DDDPRINT("negative exponent; not minimum exponent and "
			             "lowest mantissa for this exponent -> "
			             "unequal gaps");

			bi_mul_small(&nc_ctx->r, &nc_ctx->f, nc_ctx->b * 2);  /* r <- (2 * b) * f */
			bi_exp_small(&nc_ctx->t1, nc_ctx->b, 1 - nc_ctx->e, &nc_ctx->s, &nc_ctx->t2);  /* NB: use 's' as temp on purpose */
			bi_mul_small(&nc_ctx->s, &nc_ctx->t1, 2);             /* s <- b^(1-e) * 2 */
			bi_set_small(&nc_ctx->mp, 2);
			bi_set_small(&nc_ctx->mm, 1);
			nc_ctx->unequal_gaps = 1;
		} else {
			/* r <- (* f 2)
			 * s <- (* (expt b (- e)) 2) == b^(-e) * 2    [if b==2 -> b^(1-e)]
			 * m+ <- 1
			 * m- <- 1
			 * k <- 0
			 * B <- B
			 * low_ok <- round
			 * high_ok <- round
			 */

			DUK_DDDPRINT("negative exponent; minimum exponent or not "
			             "lowest mantissa for this exponent -> "
			             "equal gaps");

			bi_mul_small(&nc_ctx->r, &nc_ctx->f, 2);            /* r <- 2 * f */
			bi_exp_small(&nc_ctx->t1, nc_ctx->b, -nc_ctx->e, &nc_ctx->s, &nc_ctx->t2);  /* NB: use 's' as temp on purpose */
			bi_mul_small(&nc_ctx->s, &nc_ctx->t1, 2);           /* s <- b^(-e) * 2 */
			bi_set_small(&nc_ctx->mp, 1);
			bi_set_small(&nc_ctx->mm, 1);
		}
	}
}

static void dragon4_scale(duk_numconv_stringify_ctx *nc_ctx) {
	int k = 0;

	/* This is essentially the 'scale' algorithm, with recursion removed.
	 * Note that 'k' is either correct immediately, or will move in one
	 * direction in the loop.  There's no need to do the low/high checks
	 * on every round (like the Scheme algorithm does).
	 *
	 * The scheme algorithm finds 'k' and updates 's' simultaneously,
	 * while the logical algorithm finds 'k' with 's' having its initial
	 * value, after which 's' is updated separately (see the Burger-Dybvig
	 * paper, Section 3.1, steps 2 and 3).
	 *
	 * The case where m+ == m- (almost always) is optimized for, because
	 * it reduces the bigint operations considerably and almost always
	 * applies.  The scale loop only needs to work with m+, so this works.
	 */

	/* XXX: this algorithm could be optimized quite a lot by using e.g.
	 * a logarithm based estimator for 'k' and performing B^n multiplication
	 * using a lookup table or using some bit-representation based exp
	 * algorithm.  Currently we just loop, with significant performance
	 * impact for very large and very small numbers.
	 */

	DUK_DDDPRINT("scale: B=%d, low_ok=%d, high_ok=%d", nc_ctx->B, nc_ctx->low_ok, nc_ctx->high_ok);
	BI_PRINT("r(init)", &nc_ctx->r);
	BI_PRINT("s(init)", &nc_ctx->s);
	BI_PRINT("mp(init)", &nc_ctx->mp);
	BI_PRINT("mm(init)", &nc_ctx->mm);

	for (;;) {
		DUK_DDDPRINT("scale loop (inc k), k=%d", k);
		BI_PRINT("r", &nc_ctx->r);
		BI_PRINT("s", &nc_ctx->s);
		BI_PRINT("m+", &nc_ctx->mp);
		BI_PRINT("m-", &nc_ctx->mm);

		bi_add(&nc_ctx->t1, &nc_ctx->r, &nc_ctx->mp);  /* t1 = (+ r m+) */
		if (bi_compare(&nc_ctx->t1, &nc_ctx->s) >= (nc_ctx->high_ok ? 0 : 1)) {
			DUK_DDDPRINT("k is too low");
			/* r <- r
			 * s <- (* s B)
			 * m+ <- m+
			 * m- <- m-
			 * k <- (+ k 1)
			 */

			bi_mul_small_copy(&nc_ctx->s, nc_ctx->B, &nc_ctx->t1);
			k++;
		} else {
			break;
		}
	}

	/* k > 0 -> k was too low, and cannot be too high */
	if (k > 0) {
		goto skip_dec_k;
	}

	for (;;) {
		DUK_DDDPRINT("scale loop (dec k), k=%d", k);
		BI_PRINT("r", &nc_ctx->r);
		BI_PRINT("s", &nc_ctx->s);
		BI_PRINT("m+", &nc_ctx->mp);
		BI_PRINT("m-", &nc_ctx->mm);

		bi_add(&nc_ctx->t1, &nc_ctx->r, &nc_ctx->mp);  /* t1 = (+ r m+) */
		bi_mul_small(&nc_ctx->t2, &nc_ctx->t1, nc_ctx->B);   /* t2 = (* (+ r m+) B) */
		if (bi_compare(&nc_ctx->t2, &nc_ctx->s) <= (nc_ctx->high_ok ? -1 : 0)) {
			DUK_DDDPRINT("k is too high");
			/* r <- (* r B)
			 * s <- s
			 * m+ <- (* m+ B)
			 * m- <- (* m- B)
			 * k <- (- k 1)
			 */
			bi_mul_small_copy(&nc_ctx->r, nc_ctx->B, &nc_ctx->t1);
			bi_mul_small_copy(&nc_ctx->mp, nc_ctx->B, &nc_ctx->t1);
			if (nc_ctx->unequal_gaps) {
				DUK_DDDPRINT("m+ != m- -> need to update m- too");
				bi_mul_small_copy(&nc_ctx->mm, nc_ctx->B, &nc_ctx->t1);
			}
			k--;
		} else {
			break;
		}
	}

 skip_dec_k:

	if (!nc_ctx->unequal_gaps) {
		DUK_DDDPRINT("equal gaps, copy m- from m+");
		bi_copy(&nc_ctx->mm, &nc_ctx->mp);  /* mm <- mp */
	}
	nc_ctx->k = k;

	DUK_DDDPRINT("final k: %d", k);
	BI_PRINT("r(final)", &nc_ctx->r);
	BI_PRINT("s(final)", &nc_ctx->s);
	BI_PRINT("mp(final)", &nc_ctx->mp);
	BI_PRINT("mm(final)", &nc_ctx->mm);
}

static void dragon4_generate(duk_numconv_stringify_ctx *nc_ctx) {
	int tc1, tc2;
	int d;
	int count = 0;

	/*
	 *  Digit generation loop.
	 *
	 *  Different termination conditions:
	 *
	 *    1. Free format output.  Terminate when shortest accurate
	 *       representation found.
	 *
	 *    2. Fixed format output, with specific number of digits.
	 *       Ignore termination conditions, terminate when digits
	 *       generated.  Caller requests an extra digit and rounds.
	 *
	 *    3. Fixed format output, with a specific absolute cut-off
	 *       position (e.g. 10 digits after decimal point).  Note
	 *       that we always generate at least one digit, even if
	 *       the digit is below the cut-off point already.
	 */

	for (;;) {
		DUK_DDDPRINT("generate loop, count=%d, k=%d, B=%d, low_ok=%d, high_ok=%d",
		             count, nc_ctx->k, nc_ctx->B, nc_ctx->low_ok, nc_ctx->high_ok);
		BI_PRINT("r", &nc_ctx->r);
		BI_PRINT("s", &nc_ctx->s);
		BI_PRINT("m+", &nc_ctx->mp);
		BI_PRINT("m-", &nc_ctx->mm);

		/* (quotient-remainder (* r B) s) using a dummy subtraction loop */
		bi_mul_small(&nc_ctx->t1, &nc_ctx->r, nc_ctx->B);       /* t1 <- (* r B) */
		d = 0;
		for (;;) {
			if (bi_compare(&nc_ctx->t1, &nc_ctx->s) < 0) {
				break;
			}
			bi_sub_copy(&nc_ctx->t1, &nc_ctx->s, &nc_ctx->t2);  /* t1 <- t1 - s */
			d++;
		}
		bi_copy(&nc_ctx->r, &nc_ctx->t1);  /* r <- (remainder (* r B) s) */
		                                   /* d <- (quotient (* r B) s)   (in range 0...B-1) */
		DUK_DDDPRINT("-> d(quot)=%d", d);
		BI_PRINT("r(rem)", &nc_ctx->r);

		bi_mul_small_copy(&nc_ctx->mp, nc_ctx->B, &nc_ctx->t2); /* m+ <- (* m+ B) */
		bi_mul_small_copy(&nc_ctx->mm, nc_ctx->B, &nc_ctx->t2); /* m- <- (* m- B) */
		BI_PRINT("mp(upd)", &nc_ctx->mp);
		BI_PRINT("mm(upd)", &nc_ctx->mm);

		/* Terminating conditions.  For fixed width output, we just ignore the
		 * terminating conditions (and pretend that tc1 == tc2 == false).  The
		 * the current shortcut for fixed-format output is to generate a few
		 * extra digits and use rounding (with carry) to finish the output.
		 */

		if (nc_ctx->is_fixed == 0) {
			/* free-form */
			tc1 = (bi_compare(&nc_ctx->r, &nc_ctx->mm) <= (nc_ctx->low_ok ? 0 : -1));

			bi_add(&nc_ctx->t1, &nc_ctx->r, &nc_ctx->mp);  /* t1 <- (+ r m+) */
			tc2 = (bi_compare(&nc_ctx->t1, &nc_ctx->s) >= (&nc_ctx->high_ok ? 0 : 1));

			DUK_DDDPRINT("tc1=%d, tc2=%d", tc1, tc2);
		} else {
			/* fixed-format */
			tc1 = 0;
			tc2 = 0;
		}

		/* Count is incremented before DRAGON4_OUTPUT() call on purpose.  This
		 * is taken into account by DRAGON4_OUTPUT() macro.
		 */
		count++;

		if (tc1) {
			if (tc2) {
				/* tc1 = true, tc2 = true */
				bi_mul_small(&nc_ctx->t1, &nc_ctx->r, 2);
				if (bi_compare(&nc_ctx->t1, &nc_ctx->s) < 0) {  /* (< (* r 2) s) */
					DUK_DDDPRINT("tc1=true, tc2=true, 2r > s: output d --> %d (k=%d)", d, nc_ctx->k);
					DRAGON4_OUTPUT(nc_ctx, count, d);
				} else {
					DUK_DDDPRINT("tc1=true, tc2=true, 2r <= s: output d+1 --> %d (k=%d)", d + 1, nc_ctx->k);
					DRAGON4_OUTPUT(nc_ctx, count, d + 1);
				}
				break;
			} else {
				/* tc1 = true, tc2 = false */
				DUK_DDDPRINT("tc1=true, tc2=false: output d --> %d (k=%d)", d, nc_ctx->k);
				DRAGON4_OUTPUT(nc_ctx, count, d);
				break;
			}
		} else {
			if (tc2) {
				/* tc1 = false, tc2 = true */
				DUK_DDDPRINT("tc1=false, tc2=true: output d+1 --> %d (k=%d)", d + 1, nc_ctx->k);
				DRAGON4_OUTPUT(nc_ctx, count, d + 1);
				break;
			} else {
				/* tc1 = false, tc2 = false */

				DUK_DDDPRINT("tc1=false, tc2=false: output d --> %d (k=%d)", d, nc_ctx->k);
				DRAGON4_OUTPUT(nc_ctx, count, d);

				/* r <- r    (updated above: r <- (remainder (* r B) s)
				 * s <- s
				 * m+ <- m+  (updated above: m+ <- (* m+ B)
				 * m- <- m-  (updated above: m- <- (* m- B)
				 * B, low_ok, high_ok are fixed
				 */

				/* fall through and continue for-loop */
			}
		}

		/* fixed-format termination conditions */
		if (nc_ctx->is_fixed) {
			if (nc_ctx->abs_pos) {
				int pos = nc_ctx->k - count + 1;  /* count is already incremented, take into account */
				DUK_DDDPRINT("fixed format, absolute: abs pos=%d, k=%d, count=%d, req=%d", pos, nc_ctx->k, count, nc_ctx->req_digits);
				if (pos <= nc_ctx->req_digits) {
					DUK_DDDPRINT("digit position reached req_digits, end generate loop");
					break;
				}
			} else {
				DUK_DDDPRINT("fixed format, relative: k=%d, count=%d, req=%d", nc_ctx->k, count, nc_ctx->req_digits);
				if (count >= nc_ctx->req_digits) {
					DUK_DDDPRINT("digit count reached req_digits, end generate loop");
					break;
				}
			}
		}
	}  /* for */

	nc_ctx->count = count;

	DUK_DDDPRINT("generate finished");

#ifdef DUK_USE_DDDEBUG
	{
		char buf[2048];
		int i, t;
		memset(buf, 0, sizeof(buf));
		for (i = 0; i < nc_ctx->count; i++) {
			t = nc_ctx->digits[i];
			if (t < 0 || t > 36) {
				buf[i] = '?';
			} else {
				buf[i] = DIGITCHAR(t);
			}
		}
		DUK_DDDPRINT("-> generated digits; k=%d, digits='%s'", nc_ctx->k, buf);
	}
#endif
}

/* Round up digits to a given position.  If position is out-of-bounds,
 * does nothing.  If carry propagates over the first digit, a '1' is
 * prepended to digits and 'k' will be updated.  Return value indicates
 * whether carry propagated over the first digit.
 *
 * Note that nc_ctx->count is NOT updated based on the rounding position
 * (it is updated only if carry overflows over the first digit and an
 * extra digit is prepended).
 */
static int dragon4_fixed_format_round(duk_numconv_stringify_ctx *nc_ctx, int round_idx) {
	int t;
	char *p;
	int roundup_limit;
	int ret = 0;

	/*
	 *  round_idx points to the digit which is considered for rounding; the
	 *  digit to its left is the final digit of the rounded value.  If round_idx
	 *  is zero, rounding will be performed; the result will either be an empty
	 *  rounded value or if carry happens a '1' digit is generated.
	 */

	if (round_idx >= nc_ctx->count) {
		DUK_DDDPRINT("round_idx out of bounds (%d >= %d (count)) -> no rounding",
		             round_idx, nc_ctx->count);
		return 0;
	} else if (round_idx < 0) {
		DUK_DDDPRINT("round_idx out of bounds (%d < 0) -> no rounding", round_idx);
		return 0;
	}

	/*
	 *  Round-up limit.
	 *
	 *  For even values, divides evenly, e.g. 10 -> roundup_limit=5.
	 *
	 *  For odd values, rounds up, e.g. 3 -> roundup_limit=2.
	 *  If radix is 3, 0/3 -> down, 1/3 -> down, 2/3 -> up.
	 */
	roundup_limit = (nc_ctx->B + 1) / 2;

	p = &nc_ctx->digits[round_idx];
	if (*p >= roundup_limit) {
		DUK_DDDPRINT("fixed-format rounding carry required");
		/* carry */
		for (;;) {
			*p = 0;
			if (p == &nc_ctx->digits[0]) {
				DUK_DDDPRINT("carry propagated to first digit -> special case handling");
				memmove((void *) (&nc_ctx->digits[1]),
				        (void *) (&nc_ctx->digits[0]),
				        (size_t) (sizeof(char) * nc_ctx->count));
				nc_ctx->digits[0] = 1;  /* don't increase 'count' */
				nc_ctx->k++;  /* position of highest digit changed */
				nc_ctx->count++;  /* number of digits changed */
				ret = 1;
				break;
			}

			DUK_DDDPRINT("fixed-format rounding carry: B=%d, roundup_limit=%d, p=%p, digits=%p",
			             (int) nc_ctx->B, roundup_limit, (void *) p, (void *) nc_ctx->digits);
			p--;
			t = *p;
			DUK_DDDPRINT("digit before carry: %d", t);
			if (++t < nc_ctx->B) {
				DUK_DDDPRINT("rounding carry terminated");
				*p = t;
				break;
			}

			DUK_DDDPRINT("wraps, carry to next digit");
		}
	}

	return ret;
}

#define  NO_EXP  (65536)  /* arbitrary marker, outside valid exp range */

static void dragon4_convert_and_push(duk_numconv_stringify_ctx *nc_ctx, duk_context *ctx, int radix, int digits, int flags, int neg) {
	int k;
	int pos, pos_end;
	int exp;
	int dig;
	char *q;
	char *buf;

	/*
	 *  The string conversion here incorporates all the necessary Ecmascript
	 *  semantics without attempting to be generic.  nc_ctx->digits contains
	 *  nc_ctx->count digits (>= 1), with the topmost digit's 'position'
	 *  indicated by nc_ctx->k as follows:
	 *
	 *    digits="123" count=3 k=0   -->   0.123
	 *    digits="123" count=3 k=1   -->   1.23
	 *    digits="123" count=3 k=5   -->   12300
	 *    digits="123" count=3 k=-1  -->   0.0123
	 *
	 *  Note that the identifier names used for format selection are different
	 *  in Burger-Dybvig paper and Ecmascript specification (quite confusingly
	 *  so, because e.g. 'k' has a totally different meaning in each).  See
	 *  documentation for discussion.
	 *
	 *  Ecmascript doesn't specify any specific behavior for format selection
	 *  (e.g. when to use exponent notation) for non-base-10 numbers.
	 *
	 *  The bigint space in the context is reused for string output, as there
	 *  is more than enough space for that (>1kB at the moment), and we avoid
	 *  allocating even more stack.
	 */

	DUK_ASSERT(NUMCONV_CTX_BIGINTS_SIZE >= MAX_FORMATTED_LENGTH);
	DUK_ASSERT(nc_ctx->count >= 1);

	k = nc_ctx->k;
	buf = (char *) &nc_ctx->f;  /* XXX: union would be more correct */
	q = buf;

	/* Exponent handling: if exponent format is used, record exponent value and
	 * fake k such that one leading digit is generated (e.g. digits=123 -> "1.23").
	 *
	 * toFixed() prevents exponent use; otherwise apply a set of criteria to
	 * match the other API calls (toString(), toPrecision, etc).
	 */

	exp = NO_EXP;
	if (!nc_ctx->abs_pos /* toFixed() */) {
		if ((flags & DUK_N2S_FLAG_FORCE_EXP) ||             /* exponential notation forced */
		    ((flags & DUK_N2S_FLAG_NO_ZERO_PAD) &&          /* fixed precision and zero padding would be required */
	             (k - digits >= 1)) ||                          /* (e.g. k=3, digits=2 -> "12X") */
		    ((k > 21 || k <= -6) && (radix == 10))) {       /* toString() conditions */
			DUK_DDDPRINT("use exponential notation: k=%d -> exp=%d", k, k - 1);
			exp = k - 1;  /* e.g. 12.3 -> digits="123" k=2 -> 1.23e1 */
			k = 1;  /* generate mantissa with a single leading whole number digit */
		}
	}

	if (neg) {
		*q++ = '-';
	}

	/* Start position (inclusive) and end position (exclusive) */
	pos = (k >= 1 ? k : 1);
	if (nc_ctx->is_fixed) {
		if (nc_ctx->abs_pos) {
			/* toFixed() */
			pos_end = -digits;
		} else {
			pos_end = k - digits;
		}
	} else {
		pos_end = k - nc_ctx->count;
	}
	if (pos_end > 0) {
		pos_end = 0;
	}

	DUK_DDDPRINT("exp=%d, k=%d, count=%d, pos=%d, pos_end=%d, is_fixed=%d, "
	             "digits=%d, abs_pos=%d",
	             exp, k, nc_ctx->count, pos, pos_end, nc_ctx->is_fixed,
	             digits, nc_ctx->abs_pos);

	/* Digit generation */
	while (pos > pos_end) {
		DUK_DDDPRINT("digit generation: pos=%d, pos_end=%d", pos, pos_end);
		if (pos == 0) {
			*q++ = '.';
		}
		if (pos > k) {
			*q++ = '0';
		} else if (pos <= k - nc_ctx->count) {
			*q++ = '0';
		} else {
			dig = nc_ctx->digits[k - pos];
			DUK_ASSERT(dig >= 0 && dig < nc_ctx->B);
			*q++ = DIGITCHAR(dig);
		} 

		pos--;
	}
	DUK_ASSERT(pos <= 1);

	/* Exponent */
	if (exp != NO_EXP) {
		/*
		 *  Exponent notation for non-base-10 numbers isn't specified in Ecmascript
		 *  specification, as it never explicitly turns up: non-decimal numbers can
		 *  only be formatted with Number.prototype.toString([radix]) and for that,
		 *  behavior is not explicitly specified.
		 *
		 *  Logical choices include formatting the exponent as decimal (e.g. binary
		 *  100000 as 1e+5) or in current radix (e.g. binary 100000 as 1e+101).
		 *  The Dragon4 algorithm (in the original paper) prints the exponent value
		 *  in the target radix B.  However, for radix values 15 and above, the
		 *  exponent separator 'e' is no longer easily parseable.  Consider, for
		 *  instance, the number "1.faecee+1c".
		 */

		size_t len;
		char exp_sign;

		*q++ = 'e';
		if (exp >= 0) {
			exp_sign = '+';
		} else {
			exp_sign = '-';
			exp = -exp;
		}
		*q++ = exp_sign;
		len = dragon4_format_uint32(q, (unsigned int) exp, radix);
		q += len;
	}

	duk_push_lstring(ctx, buf, q - buf);
}

/*
 *  Conversion helpers
 */

static void dragon4_double_to_ctx(duk_numconv_stringify_ctx *nc_ctx, double x) {
	volatile duk_double_union u;
	uint32_t tmp;
	int exp;

	/*
	 *    seeeeeee eeeeffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff
	 *       A        B        C        D        E        F        G        H
	 *
	 *    s       sign bit
	 *    eee...  exponent field
	 *    fff...  fraction
	 *
	 *    ieee value = 1.ffff... * 2^(e - 1023)  (normal)
	 *               = 0.ffff... * 2^(-1022)     (denormal)
	 *
	 *    algorithm v = f * b^e
	 */

	DUK_DBLUNION_SET_DOUBLE(&u, x);

	nc_ctx->f.n = 2;

	tmp = (uint32_t) DUK_DBLUNION_GET_LOW32(&u);
	nc_ctx->f.v[0] = tmp;
	tmp = (uint32_t) DUK_DBLUNION_GET_HIGH32(&u);
	nc_ctx->f.v[1] = tmp & 0x000fffffU;
	exp = (tmp >> 20) & 0x07ffU;

	if (exp == 0) {
		/* denormal */
		exp = IEEE_DOUBLE_EXP_MIN - 52;
		bi_normalize(&nc_ctx->f);
	} else {
		/* normal: implicit leading 1-bit */
		nc_ctx->f.v[1] |= 0x00100000U;
		exp = exp - IEEE_DOUBLE_EXP_BIAS - 52;
		DUK_ASSERT(bi_is_valid(&nc_ctx->f));  /* true, because v[1] has at least one bit set */
	}

	DUK_ASSERT(bi_is_valid(&nc_ctx->f));

	nc_ctx->e = exp;
}

void dragon4_ctx_to_double(duk_numconv_stringify_ctx *nc_ctx, double *x) {
	volatile duk_double_union u;
	int exp;
	int i;
	int bitstart;
	int bitround;
	int bitidx;
	int skip_round;
	uint32_t t, v;

	DUK_ASSERT(nc_ctx->count == 53 + 1);
	DUK_ASSERT(nc_ctx->digits[0] == 1);  /* zero handled by caller */

	/*
	 *  Figure out how generated digits match up with the mantissa,
	 *  and then perform rounding.  If mantissa overflows, need to
	 *  recompute the exponent (it is bumped and may overflow to
	 *  infinity).
	 *
	 *  For normal numbers the leading '1' is hidden and ignored,
	 *  and the last bit is used for rounding:
	 *
	 *                          rounding pt
	 *       <--------52------->|
	 *     1 x x x x ... x x x x|y  ==>  x x x x ... x x x x
	 *
	 *  For denormals, the leading '1' is included in the number,
	 *  and the rounding point is different:
	 *
	 *                      rounding pt
	 *     <--52 or less--->|
	 *     1 x x x x ... x x|x x y  ==>  0 0 ... 1 x x ... x x
	 *
	 *  The largest denormals will have a mantissa beginning with
	 *  a '1' (the explicit leading bit); smaller denormals will
	 *  have leading zero bits.
	 *
	 *  If the exponent would become too high, the result becomes
	 *  Infinity.  If the exponent is so small that the entire
	 *  mantissa becomes zero, the result becomes zero.
	 *
	 *  Note: the Dragon4 'k' is off-by-one with respect to the IEEE
	 *  exponent.  For instance, k==0 indicates that the leading '1'
	 *  digit is at the first binary fraction position (0.1xxx...);
	 *  the corresponding IEEE exponent would be -1.
	 */

	skip_round = 0;

 recheck_exp:

	exp = nc_ctx->k - 1;   /* IEEE exp without bias */
	if (exp > 1023) {
		/* Infinity */
		bitstart = -255;  /* needed for inf: causes mantissa to become zero,
		                   * and rounding to be skipped.
		                   */
		exp = 2047;
	} else if (exp >= -1022) {
		/* normal */
		bitstart = 1;  /* skip leading digit */
		exp += IEEE_DOUBLE_EXP_BIAS;
		DUK_ASSERT(exp >= 1 && exp <= 2046);
	} else {
		/* denormal or zero */
		bitstart = 1023 + exp;  /* exp==-1023 -> bitstart=0 (leading 1);
		                         * exp==-1024 -> bitstart=-1 (one left of leading 1), etc
		                         */
		exp = 0;
	}
	bitround = bitstart + 52;

	DUK_DDDPRINT("ieee exp=%d, bitstart=%d, bitround=%d", exp, bitstart, bitround);

	if (!skip_round) {
		if (dragon4_fixed_format_round(nc_ctx, bitround)) {
			/* Corner case: see test-numconv-parse-mant-carry.js.  We could
			 * just bump the exponent and update bitstart, but it's more robust
			 * to recompute (but avoid rounding twice).
			 */
			DUK_DDDPRINT("rounding caused exponent to be bumped, recheck exponent");
			skip_round = 1;
			goto recheck_exp;
		}
	}

	/*
	 *  Create mantissa
	 */

	t = 0;
	for (i = 0; i < 52; i++) {
		bitidx = bitstart + 52 - 1 - i;
		if (bitidx >= nc_ctx->count) {
			v = 0;
		} else if (bitidx < 0) {
			v = 0;
		} else {
			v = nc_ctx->digits[bitidx];
		}
		DUK_ASSERT(v == 0 || v == 1);
		t += v << (i % 32);
		if (i == 31) {
			/* low 32 bits is complete */
			DUK_DBLUNION_SET_LOW32(&u, t);
			t = 0;
		}
	}
	/* t has high mantissa */

	DUK_DDDPRINT("mantissa is complete: %08x %08x",
	             t,
	             (unsigned int) DUK_DBLUNION_GET_LOW32(&u));

	DUK_ASSERT(exp >= 0 && exp <= 0x7ff);
	t += exp << 20;
#if 0  /* caller handles sign change */
	if (negative) {
		t |= 0x80000000U;
	}
#endif
	DUK_DBLUNION_SET_HIGH32(&u, t);

	DUK_DDDPRINT("number is complete: %08x %08x",
	             (unsigned int) DUK_DBLUNION_GET_HIGH32(&u),
	             (unsigned int) DUK_DBLUNION_GET_LOW32(&u));

	*x = DUK_DBLUNION_GET_DOUBLE(&u);
}

/*
 *  Exposed number-to-string API
 *
 *  Input: [ number ]
 *  Output: [ string ]
 */

void duk_numconv_stringify(duk_context *ctx, int radix, int digits, int flags) {
	double x;
	int c;
	int neg;
	unsigned int uval;
	duk_numconv_stringify_ctx nc_ctx_alloc;  /* large context; around 2kB now */
	duk_numconv_stringify_ctx *nc_ctx = &nc_ctx_alloc;

	x = duk_require_number(ctx, -1);
	duk_pop(ctx);

	/*
	 *  Handle special cases (NaN, infinity, zero).
	 */

	c = fpclassify(x);
	if (signbit(x)) {
		x = -x;
		neg = 1;
	} else {
		neg = 0;
	}
	DUK_ASSERT(signbit(x) == 0);

	if (c == FP_NAN) {
		duk_push_hstring_stridx(ctx, DUK_STRIDX_NAN);
		return;
	} else if (c == FP_INFINITE) {
		if (neg) {
			/* -Infinity */
			duk_push_hstring_stridx(ctx, DUK_STRIDX_MINUS_INFINITY);
		} else {
			/* Infinity */
			duk_push_hstring_stridx(ctx, DUK_STRIDX_INFINITY);
		}
		return;
	} else if (c == FP_ZERO) {
		/* We can't shortcut zero here if it goes through special formatting
		 * (such as forced exponential notation).
		 */
		;
	}

	/*
	 *  Handle integers in 32-bit range (that is, [-(2**32-1),2**32-1])
	 *  specially, as they're very likely for embedded programs.  This
	 *  is now done for all radix values.  We must be careful not to use
	 *  the fast path when special formatting (e.g. forced exponential)
	 *  is in force.
	 *
	 *  XXX: could save space by supporting radix 10 only and using
	 *  sprintf "%u" for the fast path and for exponent formatting.
	 */

	uval = (unsigned int) x;
	if (((double) uval) == x &&  /* integer number in range */
	    flags == 0) {            /* no special formatting */
		/* use bigint area as a temp */
		char *buf = (char *) (&nc_ctx->f);
		char *p = buf;

		DUK_ASSERT(NUMCONV_CTX_BIGINTS_SIZE >= 32 + 1);  /* max size: radix=2 + sign */
		if (neg && uval != 0) {
			/* no negative sign for zero */
			*p++ = '-';
		}
		p += dragon4_format_uint32(p, uval, radix);
		duk_push_lstring(ctx, buf, (size_t) (p - buf));
		return;
	}

	/*
	 *  Dragon4 setup.
	 *
	 *  Convert double from IEEE representation for conversion;
	 *  normal finite values have an implicit leading 1-bit.  The
	 *  slow path algorithm doesn't handle zero, so zero is special
	 *  cased here but still creates a valid nc_ctx, and goes
	 *  through normal formatting in case special formatting has
	 *  been requested (e.g. forced exponential format: 0 -> "0e+0").
	 */

	/* Would be nice to bulk clear the allocation, but the context
	 * is 1-2 kilobytes and nothing should rely on it being zeroed.
	 */
#if 0
	memset((void *) nc_ctx, 0, sizeof(*nc_ctx));  /* slow init, do only for slow path cases */
#endif

	nc_ctx->is_s2n = 0;
	nc_ctx->b = 2;
	nc_ctx->B = radix;
	nc_ctx->abs_pos = 0;
	if (flags & DUK_N2S_FLAG_FIXED_FORMAT) {
		nc_ctx->is_fixed = 1;
		if (flags & DUK_N2S_FLAG_FRACTION_DIGITS) {
			/* absolute req_digits; e.g. digits = 1 -> last digit is 0,
			 * but add an extra digit for rounding.
			 */
			nc_ctx->abs_pos = 1;
			nc_ctx->req_digits = (-digits + 1) - 1;
		} else {
			nc_ctx->req_digits = digits + 1;
		}
	} else {
		nc_ctx->is_fixed = 0;
		nc_ctx->req_digits = 0;
	}

	if (c == FP_ZERO) {
		/* Zero special case: fake requested number of zero digits; ensure
		 * no sign bit is printed.  Relative and absolute fixed format
		 * require separate handling.
		 */
		int count;
		if (nc_ctx->is_fixed) {
			if (nc_ctx->abs_pos) {
				count = digits + 2;  /* lead zero + 'digits' fractions + 1 for rounding */
			} else {
				count = digits + 1;  /* + 1 for rounding */
			}
		} else {
			count = 1;
		}
		DUK_DDDPRINT("count=%d", count);
		DUK_ASSERT(count >= 1);
		memset((void *) nc_ctx->digits, 0, count);
		nc_ctx->count = count;
		nc_ctx->k = 1;  /* 0.000... */
		neg = 0;
		goto zero_skip;
	}

	dragon4_double_to_ctx(nc_ctx, x);   /* -> sets 'f' and 'e' */
	BI_PRINT("f", &nc_ctx->f);
	DUK_DDDPRINT("e=%d", nc_ctx->e);

	/*
	 *  Dragon4 slow path digit generation.
	 */

	dragon4_prepare(nc_ctx);  /* setup many variables in nc_ctx */

	DUK_DDDPRINT("after prepare:");
	BI_PRINT("r", &nc_ctx->r);
	BI_PRINT("s", &nc_ctx->s);
	BI_PRINT("mp", &nc_ctx->mp);
	BI_PRINT("mm", &nc_ctx->mm);

	dragon4_scale(nc_ctx);

	DUK_DDDPRINT("after scale; k=%d", nc_ctx->k);
	BI_PRINT("r", &nc_ctx->r);
	BI_PRINT("s", &nc_ctx->s);
	BI_PRINT("mp", &nc_ctx->mp);
	BI_PRINT("mm", &nc_ctx->mm);

	dragon4_generate(nc_ctx);

	/*
	 *  Convert and push final string.
	 */

 zero_skip:

	if (flags & DUK_N2S_FLAG_FIXED_FORMAT) {
		/* Perform fixed-format rounding. */
		int roundpos;
		if (flags & DUK_N2S_FLAG_FRACTION_DIGITS) {
			/* 'roundpos' is relative to nc_ctx->k and increases to the right
			 * (opposite of how 'k' changes).
			 */
			roundpos = -digits;  /* absolute position for digit considered for rounding */
			roundpos = nc_ctx->k - roundpos;
			
		} else {
			roundpos = digits;
		}
		DUK_DDDPRINT("rounding: k=%d, count=%d, digits=%d, roundpos=%d",
		             nc_ctx->k, nc_ctx->count, digits, roundpos);
		(void) dragon4_fixed_format_round(nc_ctx, roundpos);

		/* Note: 'count' is currently not adjusted by rounding (i.e. the
		 * digits are not "chopped off".  That shouldn't matter because
		 * the digit position (absolute or relative) is passed on to the
		 * convert-and-push function.
		 */
	}

	dragon4_convert_and_push(nc_ctx, ctx, radix, digits, flags, neg);
}

/*
 *  Exposed string-to-number API
 *
 *  Input: [ string ]
 *  Output: [ number ]
 *
 *  If number parsing fails, a NaN is pushed as the result.  If number parsing
 *  fails due to an internal error, an InternalError is thrown.
 *
 *  FIXME: is this reasonable? should caller have the assurance that no error
 *  can be thrown?  Comment in documentation.
 */

void duk_numconv_parse(duk_context *ctx, int radix, int flags) {
	duk_hthread *thr = (duk_hthread *) ctx;
	duk_numconv_stringify_ctx nc_ctx_alloc;  /* large context; around 2kB now */
	duk_numconv_stringify_ctx *nc_ctx = &nc_ctx_alloc;
	double res;
	duk_hstring *h_str;
	int exp;
	int exp_neg;
	int exp_adj;
	int neg;
	int dig;
	int dig_whole;
	int dig_lzero;
	int dig_frac;
	int dig_exp;
	int dig_prec;
	duk_exp_limits *explim;
	const unsigned char *p;
	int ch;

	DUK_DDDPRINT("parse number: %!T, radix=%d, flags=0x%08x", duk_get_tval(ctx, -1), radix, flags);

	/* FIXME: macros or explicit flag checks - check impact on code size */
	int trim_white = (flags & DUK_S2N_FLAG_TRIM_WHITE);
	int allow_exp = (flags & DUK_S2N_FLAG_ALLOW_EXP);
	int allow_garbage = (flags & DUK_S2N_FLAG_ALLOW_GARBAGE);
	int allow_plus = (flags & DUK_S2N_FLAG_ALLOW_PLUS);
	int allow_minus = (flags & DUK_S2N_FLAG_ALLOW_MINUS);
	int allow_infinity = (flags & DUK_S2N_FLAG_ALLOW_INF);
	int allow_frac = (flags & DUK_S2N_FLAG_ALLOW_FRAC);
	int allow_naked_frac = (flags & DUK_S2N_FLAG_ALLOW_NAKED_FRAC);
	int allow_empty_frac = (flags & DUK_S2N_FLAG_ALLOW_EMPTY_FRAC);
	int allow_empty = (flags & DUK_S2N_FLAG_ALLOW_EMPTY_AS_ZERO);
	int allow_leading_zero = (flags & DUK_S2N_FLAG_ALLOW_LEADING_ZERO);
	int allow_auto_hex_int = (flags & DUK_S2N_FLAG_ALLOW_AUTO_HEX_INT);
	int allow_auto_oct_int = (flags & DUK_S2N_FLAG_ALLOW_AUTO_OCT_INT);

	DUK_ASSERT(radix >= 2 && radix <= 36);
	DUK_ASSERT(radix - 2 < sizeof(str2num_digits_for_radix));

	/*
	 *  Preliminaries: trim, sign, Infinity check
	 *
	 *  We rely on the interned string having a NUL terminator, which will
	 *  cause a parse failure wherever it is encountered.  As a result, we
	 *  don't need separate pointer checks.
	 *
	 *  There is no special parsing for 'NaN' in the specification although
	 *  'Infinity' (with an optional sign) is allowed in some contexts.
	 *  Some contexts allow plus/minus sign, while others only allow the
	 *  minus sign (like JSON.parse()).
	 *
	 *  Automatic hex number detection (leading '0x' or '0X') and octal
	 *  number detection (leading '0' followed by at least one octal digit)
	 *  is done here too.
	 */

	if (trim_white) {
		/* Leading / trailing whitespace is sometimes accepted and
		 * sometimes not.  After white space trimming, all valid input
		 * characters are pure ASCII.
		 */
		duk_trim(ctx, -1);
	}
	h_str = duk_require_hstring(ctx, -1);
	DUK_ASSERT(h_str != NULL);
	p = (const unsigned char *) DUK_HSTRING_GET_DATA(h_str);

	neg = 0;
	ch = *p;
	if (ch == '+') {
		if (!allow_plus) {
			DUK_DDDPRINT("parse failed: leading plus sign not allowed");
			goto parse_fail;
		}
		p++;
	} else if (ch == '-') {
		if (!allow_minus) {
			DUK_DDDPRINT("parse failed: leading minus sign not allowed");
			goto parse_fail;
		}
		p++;
		neg = 1;
	}

	ch = *p;
	if (allow_infinity && ch == 'I') {
		/* Don't check for Infinity unless the context allows it.
		 * 'Infinity' is a valid integer literal in e.g. base-36:
		 *
		 *   parseInt('Infinity', 36)
		 *   1461559270678
		 */

		const unsigned char *q;

		/* borrow literal Infinity from builtin string */
		q = (const unsigned char *) DUK_HSTRING_GET_DATA(DUK_HTHREAD_STRING_INFINITY(thr));
		if (strcmp((const char *) p, (const char *) q) == 0) {
			if (!allow_garbage && (p[8] != (unsigned char) 0)) {
				DUK_DDDPRINT("parse failed: trailing garbage after matching 'Infinity' not allowed");
				goto parse_fail;
			} else {
				/* FIXME: compile warning here on gcc-4.0, floating constant exceeds range of 'float' */
				res = INFINITY;
				goto neg_and_ret;
			}
		}
	}
	if (ch == '0') {
		int detect_radix = 0;
		ch = p[1];
		if (allow_auto_hex_int && (ch == 'x' || ch == 'X')) {
			DUK_DDDPRINT("detected 0x/0X hex prefix, changing radix and preventing fractions and exponent");
			detect_radix = 16;
			allow_empty = 0;  /* interpret e.g. '0x' and '0xg' as a NaN (= parse error) */
			p += 2;
		} else if (allow_auto_oct_int && (ch >= '0' && ch <= '9')) {
			DUK_DDDPRINT("detected 0n oct prefix, changing radix and preventing fractions and exponent");
			detect_radix = 8;
			allow_empty = 1;  /* interpret e.g. '09' as '0', not NaN */
			p += 1;
		}
		if (detect_radix > 0) {
			radix = detect_radix;
			allow_exp = 0;
			allow_frac = 0;
			allow_naked_frac = 0;
			allow_empty_frac = 0;
			allow_leading_zero = 1;  /* allow e.g. '0x0009' and '00077' */
		}
	}

	/*
	 *  Scan number and setup for Dragon4.
	 *
	 *  The fast path case is detected during setup: an integer which
	 *  can be converted without rounding, no net exponent.  The fast
	 *  path could be implemented as a separate scan, but may not really
	 *  be worth it: the multiplications for building 'f' are not
	 *  expensive when 'f' is small.
	 *
	 *  The significand ('f') must contain enough bits of (apparent)
	 *  accuracy, so that Dragon4 will generate enough binary output digits.
	 *  For decimal numbers, this means generating a 20-digit significand,
	 *  which should yield enough practical accuracy to parse IEEE doubles.
	 *  In fact, the Ecmascript specification explicitly allows an
	 *  implementation to treat digits beyond 20 as zeroes (and even
	 *  to round the 20th digit upwards).  For non-decimal numbers, the
	 *  appropriate number of digits has been precomputed for comparable
	 *  accuracy.
	 *
	 *  Digit counts:
	 *
	 *    [ dig_lzero ]
	 *      |
	 *     .+-..---[ dig_prec ]----.
	 *     |  ||                   |
	 *     0000123.456789012345678901234567890e+123456
	 *     |     | |                         |  |    |
	 *     `--+--' `------[ dig_frac ]-------'  `-+--'
	 *        |                                   |
	 *    [ dig_whole ]                       [ dig_exp ]
	 *
	 *    dig_frac and dig_exp are -1 if not present
	 *    dig_lzero is only computed for whole number part
	 *
	 *  Parsing state
	 *
	 *     Parsing whole part      dig_frac < 0 AND dig_exp < 0
	 *     Parsing fraction part   dig_frac >= 0 AND dig_exp < 0
	 *     Parsing exponent part   dig_exp >= 0   (dig_frac may be < 0 or >= 0)
	 * 
	 *  Note: in case we hit an implementation limit (like exponent range),
	 *  we should throw an error, NOT return NaN or Infinity.  Even with
	 *  very large exponent (or significand) values the final result may be
	 *  finite, so NaN/Infinity would be incorrect.
	 */

	bi_set_small(&nc_ctx->f, 0);
	dig_prec = 0;
	dig_lzero = 0;
	dig_whole = 0;
	dig_frac = -1;
	dig_exp = -1;
	exp = 0;
	exp_adj = 0;  /* essentially tracks digit position of lowest 'f' digit */
	exp_neg = 0;
	for (;;) {
		ch = *p++;

		DUK_DDDPRINT("parse digits: p=%p, ch='%c' (%d), exp=%d, exp_adj=%d, "
		             "dig_whole=%d, dig_frac=%d, dig_exp=%d, dig_lzero=%d, dig_prec=%d",
		             (void *) p, (ch >= 0x20 && ch <= 0x7e) ? ch : '?', ch,
		             exp, exp_adj, dig_whole, dig_frac, dig_exp, dig_lzero, dig_prec);
		BI_PRINT("f", &nc_ctx->f);

		/* Most common cases first. */
		if (ch >= '0' && ch <= '9') {
			dig = (int) ch - '0' + 0;
		} else if (ch == '.') {
			/* A leading digit is not required in some cases, e.g. accept ".123".
			 * In other cases (JSON.parse()) a leading digit is required.  This
			 * is checked for after the loop.
			 */
			if (dig_frac >= 0 || dig_exp >= 0) {
				if (allow_garbage) {
					DUK_DDDPRINT("garbage termination (invalid period)");
					break;
				} else {
					DUK_DDDPRINT("parse failed: period not allowed");
					goto parse_fail;
				}
			}

			if (!allow_frac) {
				/* Some contexts don't allow fractions at all; this can't be a
				 * post-check because the state ('f' and exp) would be incorrect.
				 */
				if (allow_garbage) {
					DUK_DDDPRINT("garbage termination (invalid first period)");
					break;
				} else {
					DUK_DDDPRINT("parse failed: fraction part not allowed");
				}
			}

			DUK_DDDPRINT("start fraction part");
			dig_frac = 0;
			continue;
		} else if (ch == (char) 0) {
			DUK_DDDPRINT("NUL termination");
			break;
		} else if (allow_exp && dig_exp < 0 && (ch == 'e' || ch == 'E')) {
			/* Note: we don't parse back exponent notation for anything else
			 * than radix 10, so this is not an ambiguous check (e.g. hex
			 * exponent values may have 'e' either as a significand digit
			 * or as an exponent separator).
			 *
			 * If the exponent separator occurs twice, 'e' will be interpreted
			 * as a digit (= 14) and will be rejected as an invalid decimal
			 * digit.
			 */

			DUK_DDDPRINT("start exponent part");

			/* Exponent without a sign or with a +/- sign is accepted
			 * by all call sites (even JSON.parse()).
			 */
			ch = *p;
			if (ch == '-') {
				exp_neg = 1;
				p++;
			} else if (ch == '+') {
				p++;
			}
			dig_exp = 0;
			continue;
		} else if (ch >= 'a' && ch <= 'z') {
			dig = (int) ch - 'a' + 0x0a;
		} else if (ch >= 'A' && ch <= 'Z') {
			dig = (int) ch - 'A' + 0x0a;
		} else {
			dig = 255;  /* triggers garbage digit check below */
		}
		DUK_ASSERT((dig >= 0 && dig <= 35) || dig == 255);

		if (dig >= radix) {
			if (allow_garbage) {
				DUK_DDDPRINT("garbage termination");
				break;
			} else {
				DUK_DDDPRINT("parse failed: trailing garbage or invalid digit");
				goto parse_fail;
			}
		}

		if (dig_exp < 0) {
			/* whole or fraction digit */

			if (dig_prec < str2num_digits_for_radix[radix - 2]) {
				/* significant from precision perspective */

				int f_zero = bi_is_zero(&nc_ctx->f);
				if (f_zero && dig == 0) {
					/* Leading zero is not counted towards precision digits; not
					 * in the integer part, nor in the fraction part.
					 */
					if (dig_frac < 0) {
						dig_lzero++;
					}
				} else {
					/* FIXME: join these ops */
					bi_mul_small(&nc_ctx->t1, &nc_ctx->f, radix);
					bi_add_small(&nc_ctx->f, &nc_ctx->t1, dig);
					dig_prec++;
				}
			} else {
				/* Ignore digits beyond a radix-specific limit, but note them
				 * in exp_adj.
				 */
				exp_adj++;
			}
	
			if (dig_frac >= 0) {
				dig_frac++;
				exp_adj--;
			} else {
				dig_whole++;
			}
		} else {
			/* exponent digit */

			exp = exp * radix + dig;
			if (exp > DUK_S2N_MAX_EXPONENT) {
				/* impose a reasonable exponent limit, so that exp
				 * doesn't need to get tracked using a bigint.
				 */
				DUK_DDDPRINT("parse failed: exponent too large");
				goto parse_int_error;
			}
			dig_exp++;
		}
	}

	/* Leading zero. */

	if (dig_lzero > 0 && dig_whole > 1) {
		if (!allow_leading_zero) {
			DUK_DDDPRINT("parse failed: leading zeroes not allowed in integer part");
			goto parse_fail;
		}
	}

	/* Validity checks for various fraction formats ("0.1", ".1", "1.", "."). */

	if (dig_whole == 0) {
		if (dig_frac == 0) {
			/* "." is not accepted in any format */
			DUK_DDDPRINT("parse failed: plain period without leading or trailing digits");
			goto parse_fail;
		} else if (dig_frac > 0) {
			/* ".123" */
			if (!allow_naked_frac) {
				DUK_DDDPRINT("parse failed: fraction part not allowed without "
				             "leading integer digit(s)");
				goto parse_fail;
			}
		} else {
			/* empty ("") is allowed in some formats (e.g. Number(''), as zero */
			if (!allow_empty) {
				DUK_DDDPRINT("parse failed: empty string not allowed (as zero)");
				goto parse_fail;
			}
		}
	} else {
		if (dig_frac == 0) {
			/* "123." is allowed in some formats */
			if (!allow_empty_frac) {
				DUK_DDDPRINT("parse failed: empty fractions");
				goto parse_fail;
			}
		} else if (dig_frac > 0) {
			/* "123.456" */
			;
		} else {
			/* "123" */
			;
		}
	}

	/* Exponent without digits (e.g. "1e" or "1e+").  If trailing garbage is
	 * allowed, ignore exponent part as garbage (= parse as "1", i.e. exp 0).
	 */

	if (dig_exp == 0) {
		if (!allow_garbage) {
			DUK_DDDPRINT("parse failed: empty exponent");
			goto parse_fail;
		}
		DUK_ASSERT(exp == 0);
	}

	if (exp_neg) {
		exp = -exp;
	}
	DUK_DDDPRINT("exp=%d, exp_adj=%d, net exponent -> %d", exp, exp_adj, exp + exp_adj);
	exp += exp_adj;

	/* Fast path check. */

	if (nc_ctx->f.n <= 1 &&   /* 32-bit value */
	    exp == 0    /* no net exponent */) {
		/* Fast path is triggered for no exponent and also for balanced exponent
		 * and fraction parts, e.g. for "1.23e2" == "123".  Remember to respect
		 * zero sign.
		 */

		/* XXX: could accept numbers larger than 32 bits, e.g. up to 53 bits? */
		DUK_DDDPRINT("fast path number parse");
		if (nc_ctx->f.n == 1) {
			res = (double) nc_ctx->f.v[0];
		} else {
			res = 0.0;
		}
		goto neg_and_ret;
	}

	/* Significand ('f') padding. */

	while (dig_prec < str2num_digits_for_radix[radix - 2]) {
		/* Pad significand with "virtual" zero digits so that Dragon4 will
		 * have enough (apparent) precision to work with.
		 */
		DUK_DDDPRINT("dig_prec=%d, pad significand with zero", dig_prec);
		bi_mul_small_copy(&nc_ctx->f, radix, &nc_ctx->t1);
		BI_PRINT("f", &nc_ctx->f);
		exp--;
		dig_prec++;
	}

	DUK_DDDPRINT("final exponent: %d", exp);

	/* Detect zero special case. */

	if (nc_ctx->f.n == 0) {
		/* This may happen even after the fast path check, if exponent is
		 * not balanced (e.g. "0e1").  Remember to respect zero sign.
		 */
		DUK_DDDPRINT("significand is zero");
		res = 0.0;
		goto neg_and_ret;
	}


	/* Quick reject of too large or too small exponents.  This check
	 * would be incorrect for zero (e.g. "0e1000" is zero, not Infinity)
	 * so zero check must be above.
	 */

	explim = &str2num_exp_limits[radix - 2];
	if (exp > explim->upper) {
		DUK_DDDPRINT("exponent too large -> infinite");
		/* FIXME: compile warning here on gcc-4.0, floating constant exceeds range of 'float' */
		res = INFINITY;
		goto neg_and_ret;
	} else if (exp < explim->lower) {
		DUK_DDDPRINT("exponent too small -> zero");
		res = 0.0;
		goto neg_and_ret;
	}

	nc_ctx->is_s2n = 1;
	nc_ctx->e = exp;
	nc_ctx->b = radix;
	nc_ctx->B = 2;
	nc_ctx->is_fixed = 1;
	nc_ctx->abs_pos = 0;
	nc_ctx->req_digits = 53 + 1;

	BI_PRINT("f", &nc_ctx->f);
	DUK_DDDPRINT("e=%d", nc_ctx->e);

	/*
	 *  Dragon4 slow path (binary) digit generation.
	 *  An extra digit is generated for rounding.
	 */

	dragon4_prepare(nc_ctx);  /* setup many variables in nc_ctx */

	DUK_DDDPRINT("after prepare:");
	BI_PRINT("r", &nc_ctx->r);
	BI_PRINT("s", &nc_ctx->s);
	BI_PRINT("mp", &nc_ctx->mp);
	BI_PRINT("mm", &nc_ctx->mm);

	dragon4_scale(nc_ctx);

	DUK_DDDPRINT("after scale; k=%d", nc_ctx->k);
	BI_PRINT("r", &nc_ctx->r);
	BI_PRINT("s", &nc_ctx->s);
	BI_PRINT("mp", &nc_ctx->mp);
	BI_PRINT("mm", &nc_ctx->mm);

	dragon4_generate(nc_ctx);

	DUK_ASSERT(nc_ctx->count == 53 + 1);

	/*
	 *  Convert binary digits into an IEEE double.  Need to handle
	 *  denormals and rounding correctly.
	 */

	dragon4_ctx_to_double(nc_ctx, &res);
	goto neg_and_ret;

 neg_and_ret:
	if (neg) {
		res = -res;
	}
	duk_pop(ctx);
	duk_push_number(ctx, res);
	DUK_DDDPRINT("result: %!T", duk_get_tval(ctx, -1));
	return;

 parse_fail:
	DUK_DDDPRINT("parse failed");
	duk_pop(ctx);
	duk_push_nan(ctx);
	return;

 parse_int_error:
	DUK_DDDPRINT("parse failed, internal error, can't return a value");
	DUK_ERROR(thr, DUK_ERR_INTERNAL_ERROR, "number parse error");
	return;
}

#line 1 "duk_regexp_compiler.c"
/*
 *  Regexp compilation.
 *
 *  See doc/regexp.txt for a discussion of the compilation approach and
 *  current limitations.
 */

/* include removed: duk_internal.h */

#ifdef  DUK_USE_REGEXP_SUPPORT

/*
 *  Helper macros
 */

#ifdef BUFLEN
#undef BUFLEN
#endif

#define  BUFLEN(re_ctx)   DUK_HBUFFER_GET_SIZE((duk_hbuffer *) re_ctx->buf)

/*
 *  Encoding helpers
 */

static duk_u32 encode_i32(duk_i32 x) {
	if (x < 0) {
		return ((duk_u32) (-x)) * 2 + 1;
	} else {
		return ((duk_u32) x) * 2;
	}
}

static duk_u32 insert_u32(duk_re_compiler_ctx *re_ctx, duk_u32 offset, duk_u32 x) {
	return duk_hbuffer_insert_xutf8(re_ctx->thr, re_ctx->buf, offset, x);
}

static duk_u32 append_u32(duk_re_compiler_ctx *re_ctx, duk_u32 x) {
	return duk_hbuffer_append_xutf8(re_ctx->thr, re_ctx->buf, x);
}

static duk_u32 insert_i32(duk_re_compiler_ctx *re_ctx, duk_u32 offset, duk_i32 x) {
	return duk_hbuffer_insert_xutf8(re_ctx->thr, re_ctx->buf, offset, encode_i32(x));
}

#if 0  /* unused */
static duk_u32 append_i32(duk_re_compiler_ctx *re_ctx, duk_i32 x) {
	return duk_hbuffer_append_xutf8(re_ctx->thr, re_ctx->buf, encode_i32(x));
}
#endif

/* special helper for emitting u16 lists (used for character ranges for built-in char classes) */
static duk_u32 append_u16_list(duk_re_compiler_ctx *re_ctx, duk_u16 *values, duk_u32 num) {
	duk_u32 len = 0;
	while (num > 0) {
		len += append_u32(re_ctx, (duk_u32) (*values++));
		num--;
	}
	return len;
}

static void insert_slice(duk_re_compiler_ctx *re_ctx, duk_u32 offset, duk_u32 data_offset, duk_u32 data_length) {
	duk_hbuffer_insert_slice(re_ctx->thr, re_ctx->buf, offset, data_offset, data_length);
}

static void append_slice(duk_re_compiler_ctx *re_ctx, duk_u32 data_offset, duk_u32 data_length) {
	duk_hbuffer_append_slice(re_ctx->thr, re_ctx->buf, data_offset, data_length);
}

static void remove_slice(duk_re_compiler_ctx *re_ctx, duk_u32 offset, duk_u32 length) {
	duk_hbuffer_remove_slice(re_ctx->thr, re_ctx->buf, offset, length);
}

/*
 *  Insert a jump offset at 'offset' to complete an instruction
 *  (the jump offset is always the last component of an instruction).
 *  The 'skip' argument must be computed relative to 'offset',
 *  -without- taking into account the skip field being inserted.
 *
 *       ... A B C ins X Y Z ...   (ins may be a JUMP, SPLIT1/SPLIT2, etc)
 *   =>  ... A B C ins SKIP X Y Z
 */

static duk_u32 insert_jump_offset(duk_re_compiler_ctx *re_ctx, duk_u32 offset, duk_i32 skip) {
	duk_u32 len;

	/*
	 *  Computing the final (adjusted) skip value, which is relative
	 *  to the first byte of the next instruction, is a bit tricky
	 *  because of the variable length UTF-8 encoding.
	 *
	 *  See doc/regexp.txt for discussion.
	 */

	/* FIXME: solve into closed form (smaller code) */

	if (skip < 0) {
		/* two encoding attempts suffices */
		len = duk_unicode_get_xutf8_length(encode_i32(skip));
		len = duk_unicode_get_xutf8_length(encode_i32(skip - len));
		DUK_ASSERT(duk_unicode_get_xutf8_length(encode_i32(skip - len)) == len);  /* no change */
		skip -= len;
	}
	return insert_i32(re_ctx, offset, skip);
}

static duk_u32 append_jump_offset(duk_re_compiler_ctx *re_ctx, duk_i32 skip) {
	return insert_jump_offset(re_ctx, BUFLEN(re_ctx), skip);
}

/*
 *  duk_re_range_callback for generating character class ranges.
 *
 *  When ignoreCase is false, the range is simply emitted as is.
 *  We don't, for instance, eliminate duplicates or overlapping
 *  ranges in a character class.
 *
 *  When ignoreCase is true, the range needs to be normalized through
 *  canonicalization.  Unfortunately a canonicalized version of a
 *  continuous range is not necessarily continuous (e.g. [x-{] is
 *  continuous but [X-{] is not).  The current algorithm creates the
 *  canonicalized range(s) space efficiently at the cost of compile
 *  time execution time (see doc/regexp.txt for discussion).
 *
 *  Note that the ctx->nranges is a context-wide temporary value
 *  (this is OK because there cannot be multiple character classes
 *  being parsed simultaneously).
 */

static void generate_ranges(void *userdata, duk_u32 r1, duk_u32 r2, int direct) {
	duk_re_compiler_ctx *re_ctx = (duk_re_compiler_ctx *) userdata;

	DUK_DDPRINT("generate_ranges(): re_ctx=%p, range=[%d,%d] direct=%d", re_ctx, r1, r2, direct);

	if (!direct && (re_ctx->re_flags & DUK_RE_FLAG_IGNORE_CASE)) {
		/*
		 *  Canonicalize a range, generating result ranges as necessary.
		 *  Needs to exhaustively scan the entire range (at most 65536
		 *  code points).  If 'direct' is set, caller (lexer) has ensured
		 *  that the range is already canonicalization compatible (this
		 *  is used to avoid unnecessary canonicalization of built-in
		 *  ranges like \W, which are not affected by canonicalization).
		 *
		 *  NOTE: here is one place where we don't want to support chars
		 *  outside the BMP, because the exhaustive search would be
		 *  massively larger.
		 */

		duk_u32 i;
		duk_u32 t;
		duk_u32 r_start, r_end;

		r_start = duk_unicode_re_canonicalize_char(re_ctx->thr, r1);
		r_end = r_start;
		for (i = r1 + 1; i <= r2; i++) {
			t = duk_unicode_re_canonicalize_char(re_ctx->thr, i);
			if (t == r_end + 1) {
				r_end = t;
			} else {
				DUK_DDPRINT("canonicalized, emit range: [%d,%d]", r_start, r_end);
				append_u32(re_ctx, r_start);
				append_u32(re_ctx, r_end);
				re_ctx->nranges++;
				r_start = t;
				r_end = t;
			}
		}
		DUK_DDPRINT("canonicalized, emit range: [%d,%d]", r_start, r_end);
		append_u32(re_ctx, r_start);
		append_u32(re_ctx, r_end);
		re_ctx->nranges++;
	} else {
		DUK_DDPRINT("direct, emit range: [%d,%d]", r1, r2);
		append_u32(re_ctx, r1);
		append_u32(re_ctx, r2);
		re_ctx->nranges++;
	}
}

/*
 *  Parse regexp Disjunction.  Most of regexp compilation happens here.
 *
 *  Handles Disjunction, Alternative, and Term productions directly without
 *  recursion.  The only constructs requiring recursion are positive/negative
 *  lookaheads, capturing parentheses, and non-capturing parentheses.
 *
 *  The function determines whether the entire disjunction is a 'simple atom'
 *  (see doc/regexp.txt discussion on 'simple quantifiers') and if so,
 *  returns the atom character length which is needed by the caller to keep
 *  track of its own atom character length.  A disjunction with more than one
 *  alternative is never considered a simple atom (although in some cases
 *  that might be the case).
 *
 *  Return value: simple atom character length or < 0 if not a simple atom.
 *  Appends the bytecode for the disjunction matcher to the end of the temp
 *  buffer.
 *
 *  Regexp top level structure is:
 *
 *    Disjunction = Term*
 *                | Term* | Disjunction
 *
 *    Term = Assertion
 *         | Atom
 *         | Atom Quantifier
 *
 *  An empty Term sequence is a valid disjunction alternative (e.g. /|||c||/).
 *
 *  Notes:
 *
 *    * Tracking of the 'simple-ness' of the current atom vs. the entire
 *      disjunction are separate matters.  For instance, the disjunction
 *      may be complex, but individual atoms may be simple.  Furthermore,
 *      simple quantifiers are used whenever possible, even if the
 *      disjunction as a whole is complex.
 *
 *    * The estimate of whether an atom is simple is conservative now,
 *      and it would be possible to expand it.  For instance, captures
 *      cause the disjunction to be marked complex, even though captures
 *      -can- be handled by simple quantifiers with some minor modifications.
 *
 *    * Disjunction 'tainting' as 'complex' is handled at the end of the
 *      main for loop collectively for atoms.  Assertions, quantifiers,
 *      and '|' tokens need to taint the result manually if necessary.
 *      Assertions cannot add to result char length, only atoms (and
 *      quantifiers) can; currently quantifiers will taint the result
 *      as complex though.
 */

static duk_i32 parse_disjunction(duk_re_compiler_ctx *re_ctx, int expect_eof) {
	duk_i32 atom_start_offset = -1;
	duk_i32 atom_char_length = 0;   /* negative -> complex atom */
	duk_i32 unpatched_disjunction_split = -1;
	duk_i32 unpatched_disjunction_jump = -1;
	duk_u32 entry_offset = BUFLEN(re_ctx);
	duk_i32 res = 0;	/* -1 if disjunction is complex, char length if simple */

	if (re_ctx->recursion_depth >= re_ctx->recursion_limit) {
		DUK_ERROR(re_ctx->thr, DUK_ERR_INTERNAL_ERROR,
		          "regexp compiler recursion limit reached");
	}
	re_ctx->recursion_depth++;

	for (;;) {
		duk_i32 new_atom_char_length;   /* char length of the atom parsed in this loop */
		duk_i32 new_atom_start_offset;  /* bytecode start offset of the atom parsed in this loop
		                                 * (allows quantifiers to copy the atom bytecode)
		                                 */

		duk_lexer_parse_re_token(&re_ctx->lex, &re_ctx->curr_token);

		DUK_DDPRINT("re token: %d (num=%d, char=%c)",
		            re_ctx->curr_token.t,
		            re_ctx->curr_token.num,
		            (re_ctx->curr_token.num >= 0x20 && re_ctx->curr_token.num <= 0x7e) ?
		            (char) re_ctx->curr_token.num : '?');

		/* set by atom case clauses */
		new_atom_start_offset = -1;
		new_atom_char_length = -1;

		switch (re_ctx->curr_token.t) {
		case DUK_RETOK_DISJUNCTION: {
			/*
			 *  The handling here is a bit tricky.  If a previous '|' has been processed,
			 *  we have a pending split1 and a pending jump (for a previous match).  These
			 *  need to be back-patched carefully.  See docs for a detailed example.
			 */

			/* patch pending jump and split */
			if (unpatched_disjunction_jump >= 0) {
				duk_u32 offset;

				DUK_ASSERT(unpatched_disjunction_split >= 0);
				offset = unpatched_disjunction_jump;
				offset += insert_jump_offset(re_ctx,
				                             offset,
				                             BUFLEN(re_ctx) - offset);
				/* offset is now target of the pending split (right after jump) */
				insert_jump_offset(re_ctx,
				                   unpatched_disjunction_split,
				                   offset - unpatched_disjunction_split);
			}

			/* add a new pending split to the beginning of the entire disjunction */
			(void) insert_u32(re_ctx,
			                  entry_offset,
			                  DUK_REOP_SPLIT1);   /* prefer direct execution */
			unpatched_disjunction_split = entry_offset + 1;   /* +1 for opcode */

			/* add a new pending match jump for latest finished alternative */
			append_u32(re_ctx, DUK_REOP_JUMP);
			unpatched_disjunction_jump = BUFLEN(re_ctx);

			/* 'taint' result as complex */
			res = -1;
			break;
		}
		case DUK_RETOK_QUANTIFIER: {
			if (atom_start_offset < 0) {
				DUK_ERROR(re_ctx->thr, DUK_ERR_SYNTAX_ERROR,
				          "quantifier without preceding atom");
			}
			if (re_ctx->curr_token.qmin > re_ctx->curr_token.qmax) {
				DUK_ERROR(re_ctx->thr, DUK_ERR_SYNTAX_ERROR,
				          "quantifier values invalid (qmin > qmax)");
			}
			if (atom_char_length >= 0) {
				/*
				 *  Simple atom
				 *
				 *  If atom_char_length is zero, we'll have unbounded execution time for e.g.
			 	 *  /()*x/.exec('x').  We can't just skip the match because it might have some
				 *  side effects (for instance, if we allowed captures in simple atoms, the
				 *  capture needs to happen).  The simple solution below is to force the
				 *  quantifier to match at most once, since the additional matches have no effect.
				 */
				int atom_code_length;
				duk_u32 offset;
				duk_u32 qmin, qmax;

				qmin = re_ctx->curr_token.qmin;
				qmax = re_ctx->curr_token.qmax;
				if (atom_char_length == 0) {
					/* qmin and qmax will be 0 or 1 */
					if (qmin > 1) {
						qmin = 1;
					}
					if (qmax > 1) {
						qmax = 1;
					}
				}

				append_u32(re_ctx, DUK_REOP_MATCH);   /* complete 'sub atom' */
				atom_code_length = BUFLEN(re_ctx) - atom_start_offset;

				offset = atom_start_offset;
				if (re_ctx->curr_token.greedy) {
					offset += insert_u32(re_ctx, offset, DUK_REOP_SQGREEDY);
					offset += insert_u32(re_ctx, offset, qmin);
					offset += insert_u32(re_ctx, offset, qmax);
					offset += insert_u32(re_ctx, offset, atom_char_length);
					offset += insert_jump_offset(re_ctx, offset, atom_code_length);
				} else {
					offset += insert_u32(re_ctx, offset, DUK_REOP_SQMINIMAL);
					offset += insert_u32(re_ctx, offset, qmin);
					offset += insert_u32(re_ctx, offset, qmax);
					offset += insert_jump_offset(re_ctx, offset, atom_code_length);
				}
			} else {
				/*
				 *  Complex atom
				 *
				 *  The original code is used as a template, and removed at the end
				 *  (this differs from the handling of simple quantifiers).
				 *
				 *  NOTE: there is no current solution for empty atoms in complex
				 *  quantifiers.  This would need some sort of a 'progress' instruction.
				 *
				 *  XXX: impose limit on maximum result size, i.e. atom_code_len * atom_copies?
				 */
				duk_i32 atom_code_length;
				int atom_copies;
				duk_u32 tmp_qmin, tmp_qmax;

				/* pre-check how many atom copies we're willing to make (atom_copies not needed below) */
				atom_copies = (re_ctx->curr_token.qmax == DUK_RE_QUANTIFIER_INFINITE) ?
				              re_ctx->curr_token.qmin : re_ctx->curr_token.qmax;
				if (atom_copies > DUK_RE_MAX_ATOM_COPIES) {
					DUK_ERROR(re_ctx->thr, DUK_ERR_INTERNAL_ERROR,
					          "quantifier expansion requires too many atom copies");
				}

				atom_code_length = BUFLEN(re_ctx) - atom_start_offset;

				/* insert the required matches (qmin) by copying the atom */
				tmp_qmin = re_ctx->curr_token.qmin;
				tmp_qmax = re_ctx->curr_token.qmax;
				while (tmp_qmin > 0) {
					append_slice(re_ctx, atom_start_offset, atom_code_length);
					tmp_qmin--;
					if (tmp_qmax != DUK_RE_QUANTIFIER_INFINITE) {
						tmp_qmax--;
					}
				}
				DUK_ASSERT(tmp_qmin == 0);

				/* insert code for matching the remainder - infinite or finite */
				if (tmp_qmax == DUK_RE_QUANTIFIER_INFINITE) {
					/* reuse last emitted atom for remaining 'infinite' quantifier */

					if (re_ctx->curr_token.qmin == 0) {
						/* Special case: original qmin was zero so there is nothing
						 * to repeat.  Emit an atom copy but jump over it here.
						 */
						append_u32(re_ctx, DUK_REOP_JUMP);
						append_jump_offset(re_ctx, atom_code_length);
						append_slice(re_ctx, atom_start_offset, atom_code_length);
					}
					if (re_ctx->curr_token.greedy) {
						append_u32(re_ctx, DUK_REOP_SPLIT2);   /* prefer jump */
					} else {
						append_u32(re_ctx, DUK_REOP_SPLIT1);   /* prefer direct */
					}
					append_jump_offset(re_ctx, -atom_code_length - 1);  /* -1 for opcode */
				} else {
					/*
					 *  The remaining matches are emitted as sequence of SPLITs and atom
					 *  copies; the SPLITs skip the remaining copies and match the sequel.
					 *  This sequence needs to be emitted starting from the last copy
					 *  because the SPLITs are variable length due to the variable length
					 *  skip offset.  This causes a lot of memory copying now.
					 *
					 *  Example structure (greedy, match maximum # atoms):
					 *
					 *      SPLIT1 LSEQ
					 *      (atom)
					 *      SPLIT1 LSEQ    ; <- the byte length of this instruction is needed
					 *      (atom)	       ; to encode the above SPLIT1 correctly
					 *      ...
					 *   LSEQ:
					 */
					duk_u32 offset = BUFLEN(re_ctx);
					while (tmp_qmax > 0) {
						insert_slice(re_ctx, offset, atom_start_offset, atom_code_length);
						if (re_ctx->curr_token.greedy) {
							insert_u32(re_ctx, offset, DUK_REOP_SPLIT1);   /* prefer direct */
						} else {
							insert_u32(re_ctx, offset, DUK_REOP_SPLIT2);   /* prefer jump */
						}
						insert_jump_offset(re_ctx,
						                   offset + 1,   /* +1 for opcode */
						                   BUFLEN(re_ctx) - (offset + 1));
						tmp_qmax--;
					}
				}

				/* remove the original 'template' atom */
				remove_slice(re_ctx, atom_start_offset, atom_code_length);
			}

			/* 'taint' result as complex */
			res = -1;
			break;
		}
		case DUK_RETOK_ASSERT_START: {
			append_u32(re_ctx, DUK_REOP_ASSERT_START);
			break;
		}
		case DUK_RETOK_ASSERT_END: {
			append_u32(re_ctx, DUK_REOP_ASSERT_END);
			break;
		}
		case DUK_RETOK_ASSERT_WORD_BOUNDARY: {
			append_u32(re_ctx, DUK_REOP_ASSERT_WORD_BOUNDARY);
			break;
		}
		case DUK_RETOK_ASSERT_NOT_WORD_BOUNDARY: {
			append_u32(re_ctx, DUK_REOP_ASSERT_NOT_WORD_BOUNDARY);
			break;
		}
		case DUK_RETOK_ASSERT_START_POS_LOOKAHEAD:
		case DUK_RETOK_ASSERT_START_NEG_LOOKAHEAD: {
			duk_u32 offset;
			duk_u32 opcode = (re_ctx->curr_token.t == DUK_RETOK_ASSERT_START_POS_LOOKAHEAD) ?
			                 DUK_REOP_LOOKPOS : DUK_REOP_LOOKNEG;

			offset = BUFLEN(re_ctx);
			(void) parse_disjunction(re_ctx, 0);
			append_u32(re_ctx, DUK_REOP_MATCH);

			(void) insert_u32(re_ctx, offset, opcode);
			(void) insert_jump_offset(re_ctx,
			                          offset + 1,   /* +1 for opcode */
			                          BUFLEN(re_ctx) - (offset + 1));

			/* 'taint' result as complex -- this is conservative,
			 * as lookaheads do not backtrack.
			 */
			res = -1;
			break;
		}
		case DUK_RETOK_ATOM_PERIOD: {
			new_atom_char_length = 1;
			new_atom_start_offset = BUFLEN(re_ctx);
			append_u32(re_ctx, DUK_REOP_PERIOD);
			break;
		}
		case DUK_RETOK_ATOM_CHAR: {
			/* Note: successive characters could be joined into string matches
			 * but this is not trivial (consider e.g. '/xyz+/); see docs for
			 * more discussion.
			 */
			duk_u32 ch;

			new_atom_char_length = 1;
			new_atom_start_offset = BUFLEN(re_ctx);
			append_u32(re_ctx, DUK_REOP_CHAR);
			ch = re_ctx->curr_token.num;
			if (re_ctx->re_flags & DUK_RE_FLAG_IGNORE_CASE) {
				ch = duk_unicode_re_canonicalize_char(re_ctx->thr, ch);
			}
			append_u32(re_ctx, ch);
			break;
		}
		case DUK_RETOK_ATOM_DIGIT:
		case DUK_RETOK_ATOM_NOT_DIGIT: {
			new_atom_char_length = 1;
			new_atom_start_offset = BUFLEN(re_ctx);
			append_u32(re_ctx,
			           (re_ctx->curr_token.t == DUK_RETOK_ATOM_DIGIT) ?
			           DUK_REOP_RANGES : DUK_REOP_INVRANGES);
			append_u32(re_ctx, sizeof(duk_unicode_re_ranges_digit) / (2 * sizeof(duk_u16)));
			append_u16_list(re_ctx, duk_unicode_re_ranges_digit, sizeof(duk_unicode_re_ranges_digit) / sizeof(duk_u16));
			break;
		}
		case DUK_RETOK_ATOM_WHITE:
		case DUK_RETOK_ATOM_NOT_WHITE: {
			new_atom_char_length = 1;
			new_atom_start_offset = BUFLEN(re_ctx);
			append_u32(re_ctx,
			           (re_ctx->curr_token.t == DUK_RETOK_ATOM_WHITE) ?
			           DUK_REOP_RANGES : DUK_REOP_INVRANGES);
			append_u32(re_ctx, sizeof(duk_unicode_re_ranges_white) / (2 * sizeof(duk_u16)));
			append_u16_list(re_ctx, duk_unicode_re_ranges_white, sizeof(duk_unicode_re_ranges_white) / sizeof(duk_u16));
			break;
		}
		case DUK_RETOK_ATOM_WORD_CHAR:
		case DUK_RETOK_ATOM_NOT_WORD_CHAR: {
			new_atom_char_length = 1;
			new_atom_start_offset = BUFLEN(re_ctx);
			append_u32(re_ctx,
			           (re_ctx->curr_token.t == DUK_RETOK_ATOM_WORD_CHAR) ?
			           DUK_REOP_RANGES : DUK_REOP_INVRANGES);
			append_u32(re_ctx, sizeof(duk_unicode_re_ranges_wordchar) / (2 * sizeof(duk_u16)));
			append_u16_list(re_ctx, duk_unicode_re_ranges_wordchar, sizeof(duk_unicode_re_ranges_wordchar) / sizeof(duk_u16));
			break;
		}
		case DUK_RETOK_ATOM_BACKREFERENCE: {
			duk_u32 backref = (duk_u32) re_ctx->curr_token.num;
			if (backref > re_ctx->highest_backref) {
				re_ctx->highest_backref = backref;
			}
			new_atom_char_length = -1;   /* mark as complex */
			new_atom_start_offset = BUFLEN(re_ctx);
			append_u32(re_ctx, DUK_REOP_BACKREFERENCE);
			append_u32(re_ctx, backref);
			break;
		}
		case DUK_RETOK_ATOM_START_CAPTURE_GROUP: {
			duk_u32 cap;

			new_atom_char_length = -1;   /* mark as complex (capture handling) */
			new_atom_start_offset = BUFLEN(re_ctx);
			cap = ++re_ctx->captures;
			append_u32(re_ctx, DUK_REOP_SAVE);
			append_u32(re_ctx, cap * 2);
			(void) parse_disjunction(re_ctx, 0);  /* retval (sub-atom char length) unused, tainted as complex above */
			append_u32(re_ctx, DUK_REOP_SAVE);
			append_u32(re_ctx, cap * 2 + 1);
			break;
		}
		case DUK_RETOK_ATOM_START_NONCAPTURE_GROUP: {
			new_atom_char_length = parse_disjunction(re_ctx, 0);
			new_atom_start_offset = BUFLEN(re_ctx);
			break;
		}
		case DUK_RETOK_ATOM_START_CHARCLASS:
		case DUK_RETOK_ATOM_START_CHARCLASS_INVERTED: {
			/*
			 *  Range parsing is done with a special lexer function which calls
			 *  us for every range parsed.  This is different from how rest of
			 *  the parsing works, but avoids a heavy, arbitrary size intermediate
			 *  value type to hold the ranges.
			 *
			 *  Another complication is the handling of character ranges when
			 *  case insensitive matching is used (see docs for discussion).
			 *  The range handler callback given to the lexer takes care of this
			 *  as well.
			 *
			 *  Note that duplicate ranges are not eliminated when parsing character
			 *  classes, so that canonicalization of
			 *
			 *    [0-9a-fA-Fx-{]
			 *
			 *  creates the result (note the duplicate ranges):
			 *
			 *    [0-9A-FA-FX-Z{-{]
			 *
			 *  where [x-{] is split as a result of canonicalization.  The duplicate
			 *  ranges are not a semantics issue: they work correctly.
			 */

			duk_u32 offset;

			DUK_DDPRINT("character class");

			/* insert ranges instruction, range count patched in later */
			new_atom_char_length = 1;
			new_atom_start_offset = BUFLEN(re_ctx);
			append_u32(re_ctx,
			           (re_ctx->curr_token.t == DUK_RETOK_ATOM_START_CHARCLASS) ?
			           DUK_REOP_RANGES : DUK_REOP_INVRANGES);
			offset = BUFLEN(re_ctx);    /* patch in range count later */

			/* parse ranges until character class ends */
			re_ctx->nranges = 0;    /* note: ctx-wide temporary */
			duk_lexer_parse_re_ranges(&re_ctx->lex, generate_ranges, (void *) re_ctx);

			/* insert range count */
			insert_u32(re_ctx, offset, re_ctx->nranges);
			break;
		}
		case DUK_RETOK_ATOM_END_GROUP: {
			if (expect_eof) {
				DUK_ERROR(re_ctx->thr, DUK_ERR_SYNTAX_ERROR,
				          "unexpected closing parenthesis");
			}
			goto done;
		}
		case DUK_RETOK_EOF: {
			if (!expect_eof) {
				DUK_ERROR(re_ctx->thr, DUK_ERR_SYNTAX_ERROR,
				          "unexpected end of pattern");
			}
			goto done;
		}
		default: {
			DUK_ERROR(re_ctx->thr, DUK_ERR_SYNTAX_ERROR,
			          "unexpected token in regexp");
		}
		}

		/* a complex (new) atom taints the result */
		if (new_atom_start_offset >= 0) {
			if (new_atom_char_length < 0) {
				res = -1;
			} else if (res >= 0) {
				/* only advance if not tainted */
				res += new_atom_char_length;
			}
		}

		/* record previous atom info in case next token is a quantifier */
		atom_start_offset = new_atom_start_offset;
		atom_char_length = new_atom_char_length;
	}

 done:

	/* finish up pending jump and split for last alternative */
	if (unpatched_disjunction_jump >= 0) {
		duk_u32 offset;

		DUK_ASSERT(unpatched_disjunction_split >= 0);
		offset = unpatched_disjunction_jump;
		offset += insert_jump_offset(re_ctx,
		                             offset,
		                             BUFLEN(re_ctx) - offset);
		/* offset is now target of the pending split (right after jump) */
		insert_jump_offset(re_ctx,
		                   unpatched_disjunction_split,
		                   offset - unpatched_disjunction_split);
	}

	re_ctx->recursion_depth--;

	return res;
}

/*
 *  Flags parsing (see E5 Section 15.10.4.1).
 */

static duk_u32 parse_regexp_flags(duk_hthread *thr, duk_hstring *h) {
	duk_u8 *p;
	duk_u8 *p_end;
	duk_u32 flags = 0;

	p = DUK_HSTRING_GET_DATA(h);
	p_end = p + DUK_HSTRING_GET_BYTELEN(h);

	/* Note: can be safely scanned as bytes (undecoded) */

	while (p < p_end) {
		duk_u8 c = *p++;
		switch ((int) c) {
		case (int) 'g': {
			if (flags & DUK_RE_FLAG_GLOBAL) {
				goto error;
			}
			flags |= DUK_RE_FLAG_GLOBAL;
			break;
		}
		case (int) 'i': {
			if (flags & DUK_RE_FLAG_IGNORE_CASE) {
				goto error;
			}
			flags |= DUK_RE_FLAG_IGNORE_CASE;
			break;
		}
		case (int) 'm': {
			if (flags & DUK_RE_FLAG_MULTILINE) {
				goto error;
			}
			flags |= DUK_RE_FLAG_MULTILINE;
			break;
		}
		default: {
			goto error;
		}
		}
	}

	return flags;

 error:
	DUK_ERROR(thr, DUK_ERR_SYNTAX_ERROR, "invalid regexp flags");
	return 0;  /* never here */
}

/*
 *  Create escaped RegExp source (E5 Section 15.10.3).
 *
 *  The current approach is to special case the empty RegExp
 *  ('' -> '(?:)') and otherwise replace unescaped '/' characters
 *  with '\/' regardless of where they occur in the regexp.
 *
 *  Note that normalization does not seem to be necessary for
 *  RegExp literals (e.g. '/foo/') because to be acceptable as
 *  a RegExp literal, the text between forward slashes must
 *  already match the escaping requirements (e.g. must not contain
 *  unescaped forward slashes or be empty).  Escaping IS needed
 *  for expressions like 'new Regexp("...", "")' however.
 *  Currently, we re-escape in either case.
 *
 *  Also note that we process the source here in UTF-8 encoded
 *  form.  This is correct, because any non-ASCII characters are
 *  passed through without change.
 */

static void create_escaped_source(duk_hthread *thr, int idx_pattern) {
	duk_context *ctx = (duk_context *) thr;
	duk_hstring *h;
	duk_hbuffer_dynamic *buf;
	const char *p;
	int i, n;
	char c_prev, c;

	h = duk_get_hstring(ctx, idx_pattern);
	DUK_ASSERT(h != NULL);
	p = (const char *) DUK_HSTRING_GET_DATA(h);
	n = DUK_HSTRING_GET_BYTELEN(h);

	if (n == 0) {
		/* return '(?:)' */
		duk_push_hstring_stridx(ctx, DUK_STRIDX_ESCAPED_EMPTY_REGEXP);
		return;
	}

	duk_push_dynamic_buffer(ctx, 0);
	buf = (duk_hbuffer_dynamic *) duk_get_hbuffer(ctx, -1);
	DUK_ASSERT(buf != NULL);

	c_prev = (char) 0;

	for (i = 0; i < n; i++) {
		c = p[i];

		if (c == '/' && c_prev != '\\') {
			/* Unescaped '/' ANYWHERE in the regexp (in disjunction,
			 * inside a character class, ...) => same escape works.
			 */
			duk_hbuffer_append_byte(thr, buf, (duk_u8) '\\');
		}
		duk_hbuffer_append_byte(thr, buf, (duk_u8) c);

		c_prev = c;
	}

	duk_to_string(ctx, -1);  /* -> [ ... escaped_source ] */
}

/*
 *  Exposed regexp compilation primitive.
 *
 *  Sets up a regexp compilation context, and calls parse_disjunction() to do the
 *  actual parsing.  Handles generation of the compiled regexp header and the
 *  "boilerplate" capture of the matching substring (save 0 and 1).  Also does some
 *  global level regexp checks after recursive compilation has finished.
 *
 *  An escaped version of the regexp source, suitable for use as a RegExp instance
 *  'source' property (see E5 Section 15.10.3), is also left on the stack.
 *
 *  Input stack:  [ pattern flags ]
 *  Output stack: [ bytecode escaped_source ]  (both as strings)
 */

void duk_regexp_compile(duk_hthread *thr) {
	duk_context *ctx = (duk_context *) thr;
	duk_re_compiler_ctx re_ctx;
	duk_lexer_point lex_point;
	duk_hstring *h_pattern;
	duk_hstring *h_flags;
	duk_hbuffer_dynamic *h_buffer;

	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(ctx != NULL);

	/*
	 *  Args validation
	 */

	/* TypeError if fails */
	h_pattern = duk_require_hstring(ctx, -2);
	h_flags = duk_require_hstring(ctx, -1);

	/*
	 *  Create normalized 'source' property (E5 Section 15.10.3).
	 */

	/* [ ... pattern flags ] */

	create_escaped_source(thr, -2);

	/* [ ... pattern flags escaped_source ] */

	/*
	 *  Init compilation context
	 */

	duk_push_dynamic_buffer(ctx, 0);
	h_buffer = (duk_hbuffer_dynamic *) duk_require_hbuffer(ctx, -1);
	DUK_ASSERT(DUK_HBUFFER_HAS_DYNAMIC(h_buffer));

	/* [ ... pattern flags escaped_source buffer ] */

	memset(&re_ctx, 0, sizeof(re_ctx));
	DUK_LEXER_INITCTX(&re_ctx.lex);  /* duplicate zeroing, expect for (possible) NULL inits */
	re_ctx.thr = thr;
	re_ctx.lex.thr = thr;
	re_ctx.lex.input = DUK_HSTRING_GET_DATA(h_pattern);
	re_ctx.lex.input_length = DUK_HSTRING_GET_BYTELEN(h_pattern);
	re_ctx.buf = h_buffer;
	re_ctx.recursion_limit = DUK_RE_COMPILE_RECURSION_LIMIT;
	re_ctx.re_flags = parse_regexp_flags(thr, h_flags);

	DUK_DDPRINT("regexp compiler ctx initialized, flags=0x%08x, recursion_limit=%d",
	            re_ctx.re_flags, re_ctx.recursion_limit);

	/*
	 *  Init lexer
	 */

	lex_point.offset = 0;		/* expensive init, just want to fill window */
	lex_point.line = 1;
	DUK_LEXER_SETPOINT(&re_ctx.lex, &lex_point);

	/*
	 *  Compilation
	 */

	DUK_DPRINT("starting regexp compilation");

	append_u32(&re_ctx, DUK_REOP_SAVE);
	append_u32(&re_ctx, 0);
	(void) parse_disjunction(&re_ctx, 1);  /* 1 = expect eof */
	append_u32(&re_ctx, DUK_REOP_SAVE);
	append_u32(&re_ctx, 1);
	append_u32(&re_ctx, DUK_REOP_MATCH);

	DUK_DPRINT("regexp bytecode size (before header) is %d bytes",
	           DUK_HBUFFER_GET_SIZE(re_ctx.buf));

	/*
	 *  Check for invalid backreferences; note that it is NOT an error
	 *  to back-reference a capture group which has not yet been introduced
	 *  in the pattern (as in /\1(foo)/); in fact, the backreference will
	 *  always match!  It IS an error to back-reference a capture group
	 *  which will never be introduced in the pattern.  Thus, we can check
	 *  for such references only after parsing is complete.
	 */

	if (re_ctx.highest_backref > re_ctx.captures) {
		DUK_ERROR(thr, DUK_ERR_SYNTAX_ERROR, "invalid backreference(s)");
	}

	/*
	 *  Emit compiled regexp header: flags, ncaptures
	 *  (insertion order inverted on purpose)
	 */

	insert_u32(&re_ctx, 0, (re_ctx.captures + 1) * 2);
	insert_u32(&re_ctx, 0, re_ctx.re_flags);

	DUK_DPRINT("regexp bytecode size (after header) is %d bytes",
	           DUK_HBUFFER_GET_SIZE(re_ctx.buf));
	DUK_DDDPRINT("compiled regexp: %!xO", re_ctx.buf);

	/* [ ... pattern flags escaped_source buffer ] */

	duk_to_string(ctx, -1);  /* coerce to string */

	/* [ ... pattern flags escaped_source bytecode ] */

	/*
	 *  Finalize stack
	 */

	duk_remove(ctx, -4);     /* -> [ ... flags escaped_source bytecode ] */
	duk_remove(ctx, -3);     /* -> [ ... escaped_source bytecode ] */

	DUK_DPRINT("regexp compilation successful, bytecode: %!T, escaped source: %!T",
	           duk_get_tval(ctx, -1), duk_get_tval(ctx, -2));
}

/*
 *  Create a RegExp instance (E5 Section 15.10.7).
 *
 *  Note: the output stack left by duk_regexp_compile() is directly compatible
 *  with the input here.
 *
 *  Input stack:  [ escaped_source bytecode ]  (both as strings)
 *  Output stack: [ RegExp ]
 */
 
void duk_regexp_create_instance(duk_hthread *thr) {
	duk_context *ctx = (duk_context *) thr;
	duk_hobject *h;
	duk_hstring *h_bc;
	int re_flags;

	/* [ ... escape_source bytecode ] */

	h_bc = duk_get_hstring(ctx, -1);
	DUK_ASSERT(h_bc != NULL);
	DUK_ASSERT(DUK_HSTRING_GET_BYTELEN(h_bc) >= 1);          /* always at least the header */
	DUK_ASSERT(DUK_HSTRING_GET_CHARLEN(h_bc) >= 1);
	DUK_ASSERT((int) DUK_HSTRING_GET_DATA(h_bc)[0] < 0x80);  /* flags always encodes to 1 byte */
	re_flags = (int) DUK_HSTRING_GET_DATA(h_bc)[0];

	/* [ ... escaped_source bytecode ] */

	duk_push_object(ctx);
	h = duk_get_hobject(ctx, -1);
	DUK_ASSERT(h != NULL);
	duk_insert(ctx, -3);

	/* [ ... regexp_object escaped_source bytecode ] */

	DUK_HOBJECT_SET_CLASS_NUMBER(h, DUK_HOBJECT_CLASS_REGEXP);
	DUK_HOBJECT_SET_PROTOTYPE(thr, h, thr->builtins[DUK_BIDX_REGEXP_PROTOTYPE]);

	duk_def_prop_stridx(ctx, -3, DUK_STRIDX_INT_BYTECODE, DUK_PROPDESC_FLAGS_NONE);

	/* [ ... regexp_object escaped_source ] */

	duk_def_prop_stridx(ctx, -2, DUK_STRIDX_SOURCE, DUK_PROPDESC_FLAGS_NONE);

	/* [ ... regexp_object ] */

	duk_push_boolean(ctx, (re_flags & DUK_RE_FLAG_GLOBAL));
	duk_def_prop_stridx(ctx, -2, DUK_STRIDX_GLOBAL, DUK_PROPDESC_FLAGS_NONE);

	duk_push_boolean(ctx, (re_flags & DUK_RE_FLAG_IGNORE_CASE));
	duk_def_prop_stridx(ctx, -2, DUK_STRIDX_IGNORE_CASE, DUK_PROPDESC_FLAGS_NONE);

	duk_push_boolean(ctx, (re_flags & DUK_RE_FLAG_MULTILINE));
	duk_def_prop_stridx(ctx, -2, DUK_STRIDX_MULTILINE, DUK_PROPDESC_FLAGS_NONE);

	duk_push_int(ctx, 0);
	duk_def_prop_stridx(ctx, -2, DUK_STRIDX_LAST_INDEX, DUK_PROPDESC_FLAGS_W);

	/* [ ... regexp_object ] */
}

#endif  /* DUK_USE_REGEXP_SUPPORT */

#line 1 "duk_regexp_executor.c"
/*
 *  Regexp executor.
 *
 *  Safety: the Ecmascript executor should prevent user from reading and
 *  replacing regexp bytecode.  Even so, the executor must validate all
 *  memory accesses etc.  When an invalid access is detected (e.g. a 'save'
 *  opcode to invalid, unallocated index) it should fail with an internal
 *  error but not cause a segmentation fault.
 */

/* include removed: duk_internal.h */

#ifdef DUK_USE_REGEXP_SUPPORT

/*
 *  Helpers for UTF-8 handling
 */

static duk_u32 bc_get_u32(duk_re_matcher_ctx *re_ctx, duk_u8 **pc) {
	return duk_unicode_xutf8_get_u32_checked(re_ctx->thr, pc, re_ctx->bytecode, re_ctx->bytecode_end);
}

static duk_i32 bc_get_i32(duk_re_matcher_ctx *re_ctx, duk_u8 **pc) {
	duk_u32 t;

	/* signed integer encoding needed to work with UTF-8 */
	t = duk_unicode_xutf8_get_u32_checked(re_ctx->thr, pc, re_ctx->bytecode, re_ctx->bytecode_end);
	if (t & 1) {
		return -(t >> 1);
	} else {
		return (t >> 1);
	}
}

static duk_u8 *utf8_backtrack(duk_hthread *thr, duk_u8 **ptr, duk_u8 *ptr_start, duk_u8 *ptr_end, duk_u32 count) {
	duk_u8 *p;

	/* Note: allow backtracking from p == ptr_end */
	p = *ptr;
	if (p < ptr_start || p > ptr_end) {
		goto fail;
	}

	while (count > 0) {
		for (;;) {
			p--;
			if (p < ptr_start) {
				goto fail;
			}
			if ((*p & 0xc0) != 0x80) {
				/* utf-8 continuation bytes have the form 10xx xxxx */
				break;
			}
		}
		count--;
	}
	*ptr = p;
	return p;

 fail:
	DUK_ERROR(thr, DUK_ERR_INTERNAL_ERROR, "regexp backtrack failed");
	return NULL;  /* never here */
}

static duk_u8 *utf8_advance(duk_hthread *thr, duk_u8 **ptr, duk_u8 *ptr_start, duk_u8 *ptr_end, duk_u32 count) {
	duk_u8 *p;

	p = *ptr;
	if (p < ptr_start || p >= ptr_end) {
		goto fail;
	}

	while (count > 0) {
		for (;;) {
			p++;

			/* Note: if encoding ends by hitting end of input, we don't check that
			 * the encoding is valid, we just assume it is.
			 */
			if (p >= ptr_end || ((*p & 0xc0) != 0x80)) {
				/* utf-8 continuation bytes have the form 10xx xxxx */
				break;
			}
		}
		count--;
	}

	*ptr = p;
	return p;

 fail:
	DUK_ERROR(thr, DUK_ERR_INTERNAL_ERROR, "regexp advance failed");
	return NULL;  /* never here */
}

/*
 *  Helpers for dealing with the input string
 */

/* Get a (possibly canonicalized) input character from current sp.
 * Note that the input itself is never modified, and captures
 * always record non-canonicalized strings even in case-insensitive
 * matching.
 */
static duk_u32 inp_get_u32(duk_re_matcher_ctx *re_ctx, duk_u8 **sp) {
	duk_u32 res = duk_unicode_xutf8_get_u32_checked(re_ctx->thr, sp, re_ctx->input, re_ctx->input_end);
	if (re_ctx->re_flags & DUK_RE_FLAG_IGNORE_CASE) {
		res = duk_unicode_re_canonicalize_char(re_ctx->thr, res);
	}
	return res;
}

static duk_u8 *inp_backtrack(duk_re_matcher_ctx *re_ctx, duk_u8 **sp, duk_u32 count) {
	return utf8_backtrack(re_ctx->thr, sp, re_ctx->input, re_ctx->input_end, count);
}

/* Backtrack utf-8 input and return a (possibly canonicalized) input character. */
static int inp_getprev(duk_re_matcher_ctx *re_ctx, duk_u8 *sp) {
	/* note: caller 'sp' is intentionally not updated here */
	(void) inp_backtrack(re_ctx, &sp, 1);
	return inp_get_u32(re_ctx, &sp);
}
	
/*
 *  Regexp recursive matching function.
 *
 *  Returns 'sp' on successful match (points to character after last matched one),
 *  NULL otherwise.
 *
 *  The C recursion depth limit check is only performed in this function, this
 *  suffices because the function is present in all true recursion required by
 *  regexp execution.
 */

static duk_u8 *match_regexp(duk_re_matcher_ctx *re_ctx, duk_u8 *pc, duk_u8 *sp) {
	if (re_ctx->recursion_depth >= re_ctx->recursion_limit) {
		DUK_ERROR(re_ctx->thr, DUK_ERR_INTERNAL_ERROR, "regexp executor recursion limit reached");
	}
	re_ctx->recursion_depth++;

	for (;;) {
		int op;

		if (re_ctx->steps_count >= re_ctx->steps_limit) {
			DUK_ERROR(re_ctx->thr, DUK_ERR_INTERNAL_ERROR, "regexp step limit reached");
		}
		re_ctx->steps_count++;

		op = bc_get_u32(re_ctx, &pc);

		DUK_DDDPRINT("match: rec=%d, steps=%d, pc (after op)=%d, sp=%d, op=%d",
		             re_ctx->recursion_depth,
		             re_ctx->steps_count,
		             (int) (pc - re_ctx->bytecode),
		             (int) (sp - re_ctx->input),
		             op);

		switch (op) {
		case DUK_REOP_MATCH: {
			goto match;
		}
		case DUK_REOP_CHAR: {
			/*
			 *  Byte-based matching would be possible for case-sensitive
			 *  matching but not for case-insensitive matching, but we
			 *  match by decoding the input and bytecode character normally.
			 *
			 *  Bytecode characters are assumed to be already canonicalized.
			 *  Input characters are canonicalized automatically by
			 *  inp_get_u32() if necessary.
			 *
			 *  There is no opcode for matching multiple characters.  The
			 *  regexp compiler has trouble joining strings efficiently
			 *  during compilation.  See doc/regexp.txt for more discussion.
			 */
			unsigned int c1, c2;

			c1 = bc_get_u32(re_ctx, &pc);
			DUK_ASSERT(!(re_ctx->re_flags & DUK_RE_FLAG_IGNORE_CASE) ||
			           c1 == duk_unicode_re_canonicalize_char(re_ctx->thr, c1));  /* canonicalized by compiler */
			if (sp >= re_ctx->input_end) {
				goto fail;
			}
			c2 = inp_get_u32(re_ctx, &sp);
			DUK_DDDPRINT("char match, c1=%d, c2=%d", c1, c2);
			if (c1 != c2) {
				goto fail;
			}
			break;
		}
		case DUK_REOP_PERIOD: {
			unsigned int c;

			if (sp >= re_ctx->input_end) {
				goto fail;
			}
			c = inp_get_u32(re_ctx, &sp);
			if (duk_unicode_is_line_terminator(c)) {
				/* E5 Sections 15.10.2.8, 7.3 */
				goto fail;
			}
			break;
		}
		case DUK_REOP_RANGES:
		case DUK_REOP_INVRANGES: {
			unsigned int n;
			unsigned int c;
			int match;
	
			n = bc_get_u32(re_ctx, &pc);
			if (sp >= re_ctx->input_end) {
				goto fail;
			}
			c = inp_get_u32(re_ctx, &sp);

			match = 0;
			while (n) {
				unsigned int r1, r2;
				r1 = bc_get_u32(re_ctx, &pc);
				r2 = bc_get_u32(re_ctx, &pc);
				DUK_DDDPRINT("matching ranges/invranges, n=%d, r1=%d, r2=%d, c=%d",
				             n, r1, r2, c);
				if (c >= r1 && c <= r2) {
					/* Note: don't bail out early, we must read all the ranges from
					 * bytecode.  Another option is to skip them efficiently after
					 * breaking out of here.  Prefer smallest code.
					 */
					match = 1;
				}
				n--;
			}

			if (op == DUK_REOP_RANGES) {
				if (!match) {
					goto fail;
				}
			} else {
				/* op == DUK_REOP_INVRANGES */
				if (match) {
					goto fail;
				}
			}
			break;
		}
		case DUK_REOP_ASSERT_START: {
			unsigned int c;

			if (sp <= re_ctx->input) {
				break;
			}
			if (!(re_ctx->re_flags & DUK_RE_FLAG_MULTILINE)) {
				goto fail;
			}
			c = inp_getprev(re_ctx, sp);
			if (duk_unicode_is_line_terminator(c)) {
				/* E5 Sections 15.10.2.8, 7.3 */
				break;
			}
			goto fail;
		}
		case DUK_REOP_ASSERT_END: {
			unsigned int c;
			duk_u8 *temp_sp;

			if (sp >= re_ctx->input_end) {
				break;
			}
			if (!(re_ctx->re_flags & DUK_RE_FLAG_MULTILINE)) {
				goto fail;
			}
			temp_sp = sp;
			c = inp_get_u32(re_ctx, &temp_sp);
			if (duk_unicode_is_line_terminator(c)) {
				/* E5 Sections 15.10.2.8, 7.3 */
				break;
			}
			goto fail;
		}
		case DUK_REOP_ASSERT_WORD_BOUNDARY:
		case DUK_REOP_ASSERT_NOT_WORD_BOUNDARY: {
			/*
			 *  E5 Section 15.10.2.6.  The previous and current character
			 *  should -not- be canonicalized as they are now.  However,
			 *  canonicalization does not affect the result of IsWordChar()
			 *  (which depends on Unicode characters never canonicalizing
			 *  into ASCII characters) so this does not matter.
			 */
			int c1, c2;  /* Note: negative value used as 'out of bounds' marker */

			if (sp <= re_ctx->input) {
				c1 = -1;
			} else {
				c1 = inp_getprev(re_ctx, sp);
			}
			c1 = duk_unicode_re_is_wordchar(c1);
			if (sp >= re_ctx->input_end) {
				c2 = -1;
			} else {
				duk_u8 *tmp_sp = sp;
				c2 = inp_get_u32(re_ctx, &tmp_sp);
			}
			c2 = duk_unicode_re_is_wordchar(c2);

			if (op == DUK_REOP_ASSERT_WORD_BOUNDARY) {
				if (c1 == c2) {
					goto fail;
				}
			} else {
				DUK_ASSERT(op == DUK_REOP_ASSERT_NOT_WORD_BOUNDARY);
				if (c1 != c2) {
					goto fail;
				}
			}
			break;
		}
		case DUK_REOP_JUMP: {
			duk_i32 skip;

			skip = bc_get_i32(re_ctx, &pc);
			pc += skip;
			break;
		}
		case DUK_REOP_SPLIT1: {
			/* split1: prefer direct execution (no jump) */
			duk_u8 *sub_sp;
			duk_i32 skip;

			skip = bc_get_i32(re_ctx, &pc);
			sub_sp = match_regexp(re_ctx, pc, sp);
			if (sub_sp) {
				sp = sub_sp;
				goto match;
			}
			pc += skip;
			break;
		}
		case DUK_REOP_SPLIT2: {
			/* split2: prefer jump execution (not direct) */
			duk_u8 *sub_sp;
			duk_i32 skip;

			skip = bc_get_i32(re_ctx, &pc);
			sub_sp = match_regexp(re_ctx, pc + skip, sp);
			if (sub_sp) {
				sp = sub_sp;
				goto match;
			}
			break;
		}
		case DUK_REOP_SQMINIMAL: {
			duk_u32 q, qmin, qmax;
			duk_i32 skip;
			duk_u8 *sub_sp;

			qmin = bc_get_u32(re_ctx, &pc);
			qmax = bc_get_u32(re_ctx, &pc);
			skip = bc_get_i32(re_ctx, &pc);
			DUK_DDDPRINT("minimal quantifier, qmin=%u, qmax=%u, skip=%d",
			             (unsigned int) qmin, (unsigned int) qmax, (int) skip);

			q = 0;
			while (q <= qmax) {
				if (q >= qmin) {
					sub_sp = match_regexp(re_ctx, pc + skip, sp);
					if (sub_sp) {
						sp = sub_sp;
						goto match;
					}
				}
				sub_sp = match_regexp(re_ctx, pc, sp);
				if (!sub_sp) {
					break;
				}
				sp = sub_sp;
				q++;
			}
			goto fail;
		}
		case DUK_REOP_SQGREEDY: {
			duk_u32 q, qmin, qmax, atomlen;
			duk_i32 skip;
			duk_u8 *sub_sp;

			qmin = bc_get_u32(re_ctx, &pc);
			qmax = bc_get_u32(re_ctx, &pc);
			atomlen = bc_get_u32(re_ctx, &pc);
			skip = bc_get_i32(re_ctx, &pc);
			DUK_DDDPRINT("greedy quantifier, qmin=%u, qmax=%u, atomlen=%u, skip=%d",
			             (unsigned int) qmin, (unsigned int) qmax, (unsigned int) atomlen, (int) skip);

			q = 0;
			while (q < qmax) {
				sub_sp = match_regexp(re_ctx, pc, sp);
				if (!sub_sp) {
					break;
				}
				sp = sub_sp;
				q++;
			}
			while (q >= qmin) {
				sub_sp = match_regexp(re_ctx, pc + skip, sp);
				if (sub_sp) {
					sp = sub_sp;
					goto match;
				}
				if (q == qmin) {
					break;
				}

				/* Note: if atom were to contain e.g. captures, we would need to
				 * re-match the atom to get correct captures.  Simply quantifiers
				 * do not allow captures in their atom now, so this is not an issue.
				 */

				DUK_DDDPRINT("greedy quantifier, backtrack %d characters (atomlen)",
				             atomlen);
				sp = inp_backtrack(re_ctx, &sp, atomlen);
				q--;
			}
			goto fail;
		}
		case DUK_REOP_SAVE: {
			duk_u32 idx;
			duk_u8 *old;
			duk_u8 *sub_sp;

			idx = bc_get_u32(re_ctx, &pc);
			if (idx >= re_ctx->nsaved) {
				/* idx is unsigned, < 0 check is not necessary */
				DUK_DPRINT("internal error, regexp save index insane");
				goto internal_error;
			}
			old = re_ctx->saved[idx];
			re_ctx->saved[idx] = sp;
			sub_sp = match_regexp(re_ctx, pc, sp);
			if (sub_sp) {
				sp = sub_sp;
				goto match;
			}
			re_ctx->saved[idx] = old;
			goto fail;
		}
		case DUK_REOP_LOOKPOS:
		case DUK_REOP_LOOKNEG: {
			/*
			 *  Needs a save of multiple saved[] entries depending on what range
			 *  may be overwritten.  Because the regexp parser does no such analysis,
			 *  we currently save the entire saved array here.  Lookaheads are thus
			 *  a bit expensive.  Note that the saved array is not needed for just
			 *  the lookahead sub-match, but for the matching of the entire sequel.
			 *
			 *  The temporary save buffer is pushed on to the valstack to handle
			 *  errors correctly.  Each lookahead causes a C recursion and pushes
			 *  more stuff on the value stack.  If the C recursion limit is less
			 *  than the value stack spare, there is no need to check the stack.
			 *  We do so regardless, just in case.
			 */

			duk_i32 skip;
			duk_u8 **full_save;
			duk_u8 *sub_sp;

			DUK_ASSERT(re_ctx->nsaved > 0);

			duk_require_stack((duk_context *) re_ctx->thr, 1);
			full_save = duk_push_fixed_buffer((duk_context *) re_ctx->thr,
			                                   sizeof(duk_u8 *) * re_ctx->nsaved);
			DUK_ASSERT(full_save != NULL);
			memcpy(full_save, re_ctx->saved, sizeof(duk_u8 *) * re_ctx->nsaved);

			skip = bc_get_i32(re_ctx, &pc);
			sub_sp = match_regexp(re_ctx, pc, sp);
			if (op == DUK_REOP_LOOKPOS) {
				if (!sub_sp) {
					goto lookahead_fail;
				}
			} else {
				if (sub_sp) {
					goto lookahead_fail;
				}
			}
			sub_sp = match_regexp(re_ctx, pc + skip, sp);
			if (sub_sp) {
				/* match: keep saves */
				duk_pop((duk_context *) re_ctx->thr);
				sp = sub_sp;
				goto match;
			}

			/* fall through */

		 lookahead_fail:
			/* fail: restore saves */
			memcpy(re_ctx->saved, full_save, sizeof(duk_u8 *) * re_ctx->nsaved);
			duk_pop((duk_context *) re_ctx->thr);
			goto fail;
		}
		case DUK_REOP_BACKREFERENCE: {
			/*
			 *  Byte matching for back-references would be OK in case-
			 *  sensitive matching.  In case-insensitive matching we need
			 *  to canonicalize characters, so back-reference matching needs
			 *  to be done with codepoints instead.  So, we just decode
			 *  everything normally here, too.
			 *
			 *  Note: back-reference index which is 0 or higher than
			 *  NCapturingParens (= number of capturing parens in the
			 *  -entire- regexp) is a compile time error.  However, a
			 *  backreference referring to a valid capture which has
			 *  not matched anything always succeeds!  See E5 Section
			 *  15.10.2.9, step 5, sub-step 3.
			 */
			duk_u32 idx;
			duk_u8 *p;

			idx = bc_get_u32(re_ctx, &pc);
			idx = idx << 1;		/* backref n -> saved indices [n*2, n*2+1] */
			if (idx < 2 || idx + 1 >= re_ctx->nsaved) {
				/* regexp compiler should catch these */
				DUK_DPRINT("internal error, backreference index insane");
				goto internal_error;
			}
			if (!re_ctx->saved[idx] || !re_ctx->saved[idx+1]) {
				/* capture is 'undefined', always matches! */
				DUK_DDDPRINT("backreference: saved[%d,%d] not complete, always match",
				             idx, idx+1);
				break;
			}
			DUK_DDDPRINT("backreference: match saved[%d,%d]", idx, idx+1);

			p = re_ctx->saved[idx];
			while (p < re_ctx->saved[idx+1]) {
				unsigned int c1, c2;

				/* Note: not necessary to check p against re_ctx->input_end:
				 * the memory access is checked by inp_get_u32(), while
				 * valid compiled regexps cannot write a saved[] entry
				 * which points to outside the string.
				 */
				if (sp >= re_ctx->input_end) {
					goto fail;
				}
				c1 = inp_get_u32(re_ctx, &p);
				c2 = inp_get_u32(re_ctx, &sp);
				if (c1 != c2) {
					goto fail;
				}
			}
			break;
		}
		default: {
			DUK_DPRINT("internal error, regexp opcode error: %d", op);
			goto internal_error;
		}
		}
	}

 match:
	re_ctx->recursion_depth--;
	return sp;

 fail:
	re_ctx->recursion_depth--;
	return NULL;

 internal_error:
	DUK_ERROR(re_ctx->thr, DUK_ERR_INTERNAL_ERROR, "regexp internal error");
	return NULL;  /* never here */
}

/*
 *  Exposed matcher function which provides the semantics of RegExp.prototype.exec().
 *
 *  RegExp.prototype.test() has the same semantics as exec() but does not return the
 *  result object (which contains the matching string and capture groups).  Currently
 *  there is no separate test() helper, so a temporary result object is created and
 *  discarded if test() is needed.  This is intentional, to save code space.
 *
 *  Input stack:  [ ... re_obj input ]
 *  Output stack: [ ... result ]
 */

static void regexp_match_helper(duk_hthread *thr, int force_global) {
	duk_context *ctx = (duk_context *) thr;
	duk_re_matcher_ctx re_ctx;
	duk_hobject *h_regexp;
	duk_hstring *h_bytecode;
	duk_hstring *h_input;
	duk_u8 *pc;
	duk_u8 *sp;
	int match = 0;
	int global;
	int i;
	double d;
	duk_u32 char_offset;

	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(ctx != NULL);

	DUK_DDPRINT("regexp match: regexp=%!T, input=%!T", duk_get_tval(ctx, -2), duk_get_tval(ctx, -1));

	/*
	 *  Regexp instance check, bytecode check, input coercion.
	 *
	 *  See E5 Section 15.10.6.
	 */

	/* TypeError if wrong; class check, see E5 Section 15.10.6 */
	h_regexp = duk_require_hobject_with_class(ctx, -2, DUK_HOBJECT_CLASS_REGEXP);
	DUK_ASSERT(h_regexp != NULL);
	DUK_ASSERT(DUK_HOBJECT_GET_CLASS_NUMBER(h_regexp) == DUK_HOBJECT_CLASS_REGEXP);
	DUK_UNREF(h_regexp);

	duk_to_string(ctx, -1);
	h_input = duk_get_hstring(ctx, -1);
	DUK_ASSERT(h_input != NULL);

	duk_get_prop_stridx(ctx, -2, DUK_STRIDX_INT_BYTECODE);  /* [ ... re_obj input ] -> [ ... re_obj input bc ] */
	h_bytecode = duk_require_hstring(ctx, -1);  /* no regexp instance should exist without a non-configurable bytecode property */
	DUK_ASSERT(h_bytecode != NULL);

	/*
	 *  Basic context initialization.
	 *
	 *  Some init values are read from the bytecode header
	 *  whose format is (UTF-8 codepoints):
	 *
	 *    uint   flags
	 *    uint   nsaved (even, 2n+2 where n = num captures)
	 */

	/* [ ... re_obj input bc ] */

	memset(&re_ctx, 0, sizeof(re_ctx));

	re_ctx.thr = thr;
	re_ctx.input = (duk_u8 *) DUK_HSTRING_GET_DATA(h_input);
	re_ctx.input_end = re_ctx.input + DUK_HSTRING_GET_BYTELEN(h_input);
	re_ctx.bytecode = (duk_u8 *) DUK_HSTRING_GET_DATA(h_bytecode);
	re_ctx.bytecode_end = re_ctx.bytecode + DUK_HSTRING_GET_BYTELEN(h_bytecode);
	re_ctx.saved = NULL;
	re_ctx.recursion_limit = DUK_RE_EXECUTE_RECURSION_LIMIT;
	re_ctx.steps_limit = DUK_RE_EXECUTE_STEPS_LIMIT;

	/* read header */
	pc = re_ctx.bytecode;
	re_ctx.re_flags = bc_get_u32(&re_ctx, &pc);
	re_ctx.nsaved = bc_get_u32(&re_ctx, &pc);
	re_ctx.bytecode = pc;

	global = (force_global || (re_ctx.re_flags & DUK_RE_FLAG_GLOBAL));

	DUK_ASSERT(re_ctx.nsaved >= 2);
	DUK_ASSERT((re_ctx.nsaved % 2) == 0);

	duk_push_fixed_buffer(ctx, sizeof(duk_u8 *) * re_ctx.nsaved);
	re_ctx.saved = duk_get_buffer(ctx, -1, NULL);
	DUK_ASSERT(re_ctx.saved != NULL);

	/* [ ... re_obj input bc saved_buf ] */

	/* buffer is automatically zeroed */
#ifdef DUK_USE_EXPLICIT_NULL_INIT
	for (i = 0; i < re_ctx.nsaved; i++) {
		re_ctx.saved[i] = (duk_u8 *) NULL;
	}
#endif

	DUK_DDDPRINT("regexp ctx initialized, flags=0x%08x, nsaved=%d, recursion_limit=%d, steps_limit=%d",
	             re_ctx.re_flags, re_ctx.nsaved, re_ctx.recursion_limit, re_ctx.steps_limit);

	/*
	 *  Get starting character offset for match, and initialize 'sp' based on it.
	 *
	 *  Note: lastIndex is non-configurable so it must be present (we check the
	 *  internal class of the object above, so we know it is).  User code can set
	 *  its value to an arbitrary (garbage) value though; E5 requires that lastIndex
	 *  be coerced to a number before using.  The code below works even if the
	 *  property is missing: the value will then be coerced to zero.
	 *
	 *  Note: lastIndex may be outside Uint32 range even after ToInteger() coercion.
	 *  For instance, ToInteger(+Infinity) = +Infinity.  We track the match offset
	 *  as an integer, but pre-check it to be inside the 32-bit range before the loop.
	 *  If not, the check in E5 Section 15.10.6.2, step 9.a applies.
	 */

	/* XXX: lastIndex handling produces a lot of asm */

	/* [ ... re_obj input bc saved_buf ] */

	duk_get_prop_stridx(ctx, -4, DUK_STRIDX_LAST_INDEX);  /* -> [ ... re_obj input bc saved_buf lastIndex ] */
	(void) duk_to_int(ctx, -1);  /* ToInteger(lastIndex) */
	d = duk_get_number(ctx, -1);  /* integer, but may be +/- Infinite, +/- zero (not NaN, though) */
	duk_pop(ctx);

	if (global) {
		if (d < 0.0 || d > (double) DUK_HSTRING_GET_CHARLEN(h_input)) {
			/* match fail */
			char_offset = 0;   /* not really necessary */
			DUK_ASSERT(match == 0);
			goto match_over;
		}
		char_offset = (duk_u32) d;
	} else {
		/* lastIndex must be ignored for non-global regexps, but get the
		 * value for (theoretical) side effects.  No side effects can
		 * really occur, because lastIndex is a normal property and is
		 * always non-configurable for RegExp instances.
		 */
		char_offset = (duk_u32) 0;
	}

	sp = re_ctx.input + duk_heap_strcache_offset_char2byte(thr, h_input, char_offset);

	/*
	 *  Match loop.
	 *
	 *  Try matching at different offsets until match found or input exhausted.
	 */

	/* [ ... re_obj input bc saved_buf ] */

	DUK_ASSERT(match == 0);

	for (;;) {
		/* char offset in [0, h_input->clen] (both ends inclusive), checked before entry */
		DUK_ASSERT(char_offset >= 0 && char_offset <= DUK_HSTRING_GET_CHARLEN(h_input));

		/* Note: ctx.steps is intentionally not reset, it applies to the entire unanchored match */
		DUK_ASSERT(re_ctx.recursion_depth == 0);

		DUK_DDDPRINT("attempt match at char offset %d; %p [%p,%p]",
		             char_offset, sp, re_ctx.input, re_ctx.input_end);

		/*
		 *  Note:
		 *
		 *    - match_regexp() is required not to longjmp() in ordinary "non-match"
		 *      conditions; a longjmp() will terminate the entire matching process.
		 *
		 *    - Clearing saved[] is not necessary because backtracking does it
		 *
		 *    - Backtracking also rewinds ctx.recursion back to zero, unless an
		 *      internal/limit error occurs (which causes a longjmp())
		 *
		 *    - If we supported anchored matches, we would break out here
		 *      unconditionally; however, Ecmascript regexps don't have anchored
		 *      matches.  It might make sense to implement a fast bail-out if
		 *      the regexp begins with '^' and sp is not 0: currently we'll just
		 *      run through the entire input string, trivially failing the match
		 *      at every non-zero offset.
		 */

		if (match_regexp(&re_ctx, re_ctx.bytecode, sp) != NULL) {
			DUK_DDDPRINT("match at offset %d", char_offset);
			match = 1;
			break;
		}

		/* advance by one character (code point) and one char_offset */
		char_offset++;
		if (char_offset > (double) DUK_HSTRING_GET_CHARLEN(h_input)) {
			/*
			 *  Note:
			 *
			 *    - Intentionally attempt (empty) match at char_offset == k_input->clen
			 *
			 *    - Negative char_offsets have been eliminated and char_offset is duk_u32
			 *      -> no need or use for a negative check
			 */

			DUK_DDDPRINT("no match after trying all sp offsets");
			break;
		}

		(void) utf8_advance(thr, &sp, re_ctx.input, re_ctx.input_end, 1);  /* avoid calling at end of input, will DUK_ERROR (above check suffices) */
	}

 match_over:

	/*
	 *  Matching complete, create result array or return a 'null'.  Update lastIndex
	 *  if necessary.  See E5 Section 15.10.6.2.
	 *
	 *  Because lastIndex is a character (not byte) offset, we need the character
	 *  length of the match which we conveniently get as a side effect of interning
	 *  the matching substring (0th index of result array).
	 *
	 *  saved[0]         start pointer (~ byte offset) of current match
	 *  saved[1]         end pointer (~ byte offset) of current match (exclusive)
	 *  char_offset      start character offset of current match (-> .index of result)
	 *  char_end_offset  end character offset (computed below)
	 */

	/* [ ... re_obj input bc saved_buf ] */

	if (match) {
#ifdef DUK_USE_ASSERTIONS
		duk_hobject *h_res;
#endif
		duk_u32 char_end_offset = 0;

		DUK_DDDPRINT("regexp matches at char_offset %d", (int) char_offset);

		DUK_ASSERT(re_ctx.nsaved >= 2);        /* must have start and end */
		DUK_ASSERT((re_ctx.nsaved % 2) == 0);  /* and even number */

		/* XXX: Array size is known before and (2 * re_ctx.nsaved) but not taken advantage
		 * of now.  The array is not compacted either, as regexp match objects are usually
		 * short lived.
		 */

		duk_push_array(ctx);

#ifdef DUK_USE_ASSERTIONS
		h_res = duk_require_hobject(ctx, -1);
		DUK_ASSERT(DUK_HOBJECT_HAS_EXTENSIBLE(h_res));
		DUK_ASSERT(DUK_HOBJECT_HAS_SPECIAL_ARRAY(h_res));
		DUK_ASSERT(DUK_HOBJECT_GET_CLASS_NUMBER(h_res) == DUK_HOBJECT_CLASS_ARRAY);
#endif

		/* [ ... re_obj input bc saved_buf res_obj ] */

		duk_push_number(ctx, (double) char_offset);
		duk_def_prop_stridx(ctx, -2, DUK_STRIDX_INDEX, DUK_PROPDESC_FLAGS_WEC);

		duk_dup(ctx, -4);
		duk_def_prop_stridx(ctx, -2, DUK_STRIDX_INPUT, DUK_PROPDESC_FLAGS_WEC);

		for (i = 0; i < re_ctx.nsaved; i += 2) {
			/* If saved[] pointers are insane, we just ignore them instead of
			 * throwing an internal error; the captures are returned as 'undefined'.
			 * This should, of course, never happen in practice.
			 */
			if (re_ctx.saved[i] && re_ctx.saved[i+1] && re_ctx.saved[i+1] >= re_ctx.saved[i]) {
				duk_hstring *h_saved;

				duk_push_lstring(ctx,
				                 (char *) re_ctx.saved[i],
				                 (size_t) (re_ctx.saved[i+1] - re_ctx.saved[i]));
				h_saved = duk_get_hstring(ctx, -1);
				DUK_ASSERT(h_saved != NULL);

				if (i == 0) {
					/* Assumes that saved[0] and saved[1] are always
					 * set by regexp bytecode (if not, char_end_offset
					 * will be zero).  Also assumes clen reflects the
					 * correct char length.
					 */
					char_end_offset = char_offset + DUK_HSTRING_GET_CHARLEN(h_saved);
				}
			} else {
				duk_push_undefined(ctx);
			}

			/* [ ... re_obj input bc saved_buf res_obj val ] */
			duk_put_prop_index(ctx, -2, i / 2);
		}

		/* [ ... re_obj input bc saved_buf res_obj ] */

		/* NB: 'length' property is automatically updated by the array setup loop */

		if (global) {
			/* global regexp: lastIndex updated on match */
			duk_push_number(ctx, (double) char_end_offset);
			duk_put_prop_stridx(ctx, -6, DUK_STRIDX_LAST_INDEX);
		} else {
			/* non-global regexp: lastIndex never updated on match */
			;
		}
	} else {
		/*
		 *  No match, E5 Section 15.10.6.2, step 9.a.i - 9.a.ii apply, regardless
		 *  of 'global' flag of the RegExp.  In particular, if lastIndex is invalid
		 *  initially, it is reset to zero.
		 */

		DUK_DDDPRINT("regexp does not match");

		duk_push_null(ctx);

		/* [ ... re_obj input bc saved_buf res_obj ] */

		duk_push_int(ctx, 0);
		duk_put_prop_stridx(ctx, -6, DUK_STRIDX_LAST_INDEX);
	}

	/* [ ... re_obj input bc saved_buf res_obj ] */

	duk_insert(ctx, -5);

	/* [ ... res_obj re_obj input bc saved_buf ] */

	duk_pop_n(ctx, 4);

	/* [ ... res_obj ] */

	/* XXX: these last tricks are unnecessary if the function is made
	 * a genuine native function.
	 */
}

void duk_regexp_match(duk_hthread *thr) {
	regexp_match_helper(thr, 0 /*force_global*/);
}

/* This variant is needed by String.prototype.split(); it needs to perform
 * global-style matching on a cloned RegExp which is potentially non-global.
 */
void duk_regexp_match_force_global(duk_hthread *thr) {
	regexp_match_helper(thr, 1 /*force_global*/);
}

#endif  /* DUK_USE_REGEXP_SUPPORT */

#line 1 "duk_strings.c"
/*
 *  Automatically generated by genstrings.py, do not edit!
 */

char duk_strings_data[] = {
(char)'\x01', (char)'\x8c', (char)'\xb7', (char)'\x04', (char)'\x0b', 
(char)'\x4f', (char)'\x41', (char)'\x13', (char)'\x51', (char)'\x84', 
(char)'\x6c', (char)'\xe4', (char)'\x4f', (char)'\x26', (char)'\x4e', 
(char)'\x69', (char)'\x3a', (char)'\xc0', (char)'\x4c', (char)'\xe5', 
(char)'\xe9', (char)'\x23', (char)'\x17', (char)'\x44', (char)'\xdd', 
(char)'\x89', (char)'\x0d', (char)'\xd2', (char)'\x5d', (char)'\xe4', 
(char)'\xe8', (char)'\xc1', (char)'\x32', (char)'\x1b', (char)'\xad', 
(char)'\xa3', (char)'\x02', (char)'\x48', (char)'\x9f', (char)'\xa1', 
(char)'\x73', (char)'\x96', (char)'\x40', (char)'\x34', (char)'\xdd', 
(char)'\x94', (char)'\xe2', (char)'\x86', (char)'\x98', (char)'\xbd', 
(char)'\x04', (char)'\x62', (char)'\x0c', (char)'\x23', (char)'\xa5', 
(char)'\xa3', (char)'\x45', (char)'\x34', (char)'\x39', (char)'\xa6', 
(char)'\xeb', (char)'\x82', (char)'\x92', (char)'\x0a', (char)'\x6e', 
(char)'\xff', (char)'\xbf', (char)'\xa2', (char)'\xd3', (char)'\x50', 
(char)'\xd2', (char)'\xfd', (char)'\x17', (char)'\xdd', (char)'\x4e', 
(char)'\x34', (char)'\x27', (char)'\xfe', (char)'\xb7', (char)'\xfe', 
(char)'\xfe', (char)'\x8b', (char)'\x48', (char)'\x69', (char)'\x7e', 
(char)'\x8b', (char)'\xee', (char)'\xa7', (char)'\x1a', (char)'\x13', 
(char)'\xff', (char)'\x5b', (char)'\xff', (char)'\x7f', (char)'\x45', 
(char)'\xa6', (char)'\x81', (char)'\xbf', (char)'\x45', (char)'\xf7', 
(char)'\x53', (char)'\x8d', (char)'\x09', (char)'\xff', (char)'\xb3', 
(char)'\xff', (char)'\xbf', (char)'\xa2', (char)'\xd5', (char)'\x1a', 
(char)'\x32', (char)'\x15', (char)'\x0d', (char)'\x20', (char)'\xfe', 
(char)'\x8b', (char)'\xee', (char)'\xa7', (char)'\x1a', (char)'\x13', 
(char)'\xff', (char)'\x4b', (char)'\x25', (char)'\x90', (char)'\x8f', 
(char)'\x30', (char)'\x41', (char)'\x22', (char)'\x7d', (char)'\xb7', 
(char)'\xda', (char)'\x0c', (char)'\xe4', (char)'\xb8', (char)'\xc6', 
(char)'\x41', (char)'\x0c', (char)'\x8d', (char)'\x13', (char)'\x0c', 
(char)'\x49', (char)'\xf4', (char)'\xa8', (char)'\x68', (char)'\x16', 
(char)'\x8c', (char)'\x92', (char)'\x2c', (char)'\x91', (char)'\x27', 
(char)'\xd2', (char)'\xa1', (char)'\xa0', (char)'\x5a', (char)'\x32', 
(char)'\x48', (char)'\x92', (char)'\x68', (char)'\x61', (char)'\x04', 
(char)'\x61', (char)'\x10', (char)'\x54', (char)'\x8c', (char)'\x4d', 
(char)'\x12', (char)'\x4a', (char)'\x8c', (char)'\x21', (char)'\x64', 
(char)'\xf0', (char)'\x59', (char)'\xa4', (char)'\x89', (char)'\x0a', 
(char)'\x22', (char)'\x00', (char)'\x63', (char)'\x89', (char)'\x43', 
(char)'\x44', (char)'\x2c', (char)'\x67', (char)'\xa9', (char)'\x23', 
(char)'\x24', (char)'\x39', (char)'\xa7', (char)'\xd6', (char)'\x87', 
(char)'\x45', (char)'\x6b', (char)'\x48', (char)'\xdc', (char)'\x48', 
(char)'\x60', (char)'\x82', (char)'\x44', (char)'\x3e', (char)'\xe6', 
(char)'\x78', (char)'\x90', (char)'\x03', (char)'\x56', (char)'\xca', 
(char)'\x86', (char)'\x81', (char)'\x68', (char)'\xc9', (char)'\x22', 
(char)'\xad', (char)'\xb0', (char)'\x93', (char)'\x04', (char)'\xc0', 
(char)'\x15', (char)'\x90', (char)'\xfb', (char)'\x10', (char)'\x16', 
(char)'\xb2', (char)'\x10', (char)'\x9b', (char)'\x60', (char)'\x1e', 
(char)'\x5d', (char)'\x82', (char)'\x26', (char)'\x91', (char)'\x77', 
(char)'\x33', (char)'\xa2', (char)'\x49', (char)'\xd9', (char)'\x50', 
(char)'\xb2', (char)'\x34', (char)'\x0c', (char)'\x22', (char)'\x36', 
(char)'\xf1', (char)'\x7d', (char)'\x92', (char)'\xd0', (char)'\xd2', 
(char)'\x1f', (char)'\x72', (char)'\x75', (char)'\x22', (char)'\x22', 
(char)'\x1f', (char)'\x75', (char)'\x04', (char)'\x48', (char)'\xda', 
(char)'\x9f', (char)'\x6b', (char)'\x25', (char)'\xc8', (char)'\xda', 
(char)'\x9f', (char)'\x75', (char)'\x04', (char)'\x58', (char)'\x07', 
(char)'\xa3', (char)'\x65', (char)'\x74', (char)'\x58', (char)'\x05', 
(char)'\xc9', (char)'\x3b', (char)'\x0e', (char)'\x26', (char)'\x41', 
(char)'\x38', (char)'\x64', (char)'\x2e', (char)'\xda', (char)'\x4b', 
(char)'\xcc', (char)'\xfb', (char)'\x98', (char)'\x22', (char)'\x62', 
(char)'\x4c', (char)'\xdb', (char)'\xa8', (char)'\x17', (char)'\x42', 
(char)'\x1d', (char)'\xee', (char)'\x43', (char)'\x66', (char)'\x48', 
(char)'\x98', (char)'\x34', (char)'\x29', (char)'\x49', (char)'\x12', 
(char)'\xce', (char)'\x34', (char)'\x68', (char)'\x93', (char)'\x38', 
(char)'\x80', (char)'\x44', (char)'\x08', (char)'\x04', (char)'\xa2', 
(char)'\x88', (char)'\x14', (char)'\x91', (char)'\x08', (char)'\x27', 
(char)'\x0c', (char)'\x88', (char)'\x44', (char)'\xba', (char)'\xd0', 
(char)'\x4d', (char)'\x15', (char)'\x22', (char)'\x96', (char)'\x86', 
(char)'\x93', (char)'\xad', (char)'\xa3', (char)'\x02', (char)'\x48', 
(char)'\xa0', (char)'\xa8', (char)'\x59', (char)'\x3a', (char)'\xd0', 
(char)'\x30', (char)'\x82', (char)'\x78', (char)'\x9d', (char)'\xd9', 
(char)'\x9e', (char)'\x2e', (char)'\xb7', (char)'\x67', (char)'\x87', 
(char)'\x93', (char)'\xa4', (char)'\x8c', (char)'\x5d', (char)'\x13', 
(char)'\x08', (char)'\x0b', (char)'\x59', (char)'\x08', (char)'\xce', 
(char)'\xa1', (char)'\xb5', (char)'\x02', (char)'\xd0', (char)'\x3f', 
(char)'\xa0', (char)'\xe8', (char)'\xc1', (char)'\x32', (char)'\x07', 
(char)'\xe8', (char)'\x03', (char)'\xf5', (char)'\x82', (char)'\xfa', 
(char)'\xdf', (char)'\xb0', (char)'\x17', (char)'\xd5', (char)'\xfd', 
(char)'\x80', (char)'\x7e', (char)'\xc1', (char)'\x3f', (char)'\x5b', 
(char)'\xd4', (char)'\x34', (char)'\xa8', (char)'\x6a', (char)'\x27', 
(char)'\x84', (char)'\xfd', (char)'\x5f', (char)'\x50', (char)'\xd2', 
(char)'\xa1', (char)'\xa8', (char)'\x9e', (char)'\x11', (char)'\xd4', 
(char)'\x34', (char)'\xa8', (char)'\x6a', (char)'\x27', (char)'\x83', 
(char)'\x38', (char)'\x29', (char)'\x20', (char)'\xa6', (char)'\x69', 
(char)'\x4e', (char)'\x28', (char)'\x69', (char)'\x8c', (char)'\xda', 
(char)'\x30', (char)'\x24', (char)'\x89', (char)'\xc2', (char)'\xe7', 
(char)'\x2c', (char)'\x80', (char)'\x6a', (char)'\x68', (char)'\xd1', 
(char)'\x90', (char)'\xa8', (char)'\x69', (char)'\x06', (char)'\x99', 
(char)'\x4e', (char)'\x28', (char)'\x69', (char)'\x90', (char)'\x5c', 
(char)'\x0e', (char)'\x60', (char)'\x69', (char)'\x25', (char)'\x08', 
(char)'\xcc', (char)'\x72', (char)'\x43', (char)'\x4b', (char)'\x17', 
(char)'\x51', (char)'\xa3', (char)'\x34', (char)'\x40', (char)'\xd1', 
(char)'\xb9', (char)'\x83', (char)'\x7b', (char)'\xac', (char)'\x36', 
(char)'\x21', (char)'\xa3', (char)'\x60', (char)'\x2e', (char)'\x35', 
(char)'\xb8', (char)'\xc5', (char)'\x2a', (char)'\xdc', (char)'\xe8', 
(char)'\x8c', (char)'\x97', (char)'\x78', (char)'\xc4', (char)'\xe9', 
(char)'\x10', (char)'\x44', (char)'\x42', (char)'\xca', (char)'\x09', 
(char)'\x81', (char)'\xbf', (char)'\x64', (char)'\x40', (char)'\x4c', 
(char)'\x0d', (char)'\x20', (char)'\x24', (char)'\x86', (char)'\x90', 
(char)'\x02', (char)'\x74', (char)'\x86', (char)'\x00', (char)'\xc8', 
(char)'\xbe', (char)'\x94', (char)'\x23', (char)'\x3f', (char)'\xb2', 
(char)'\x3f', (char)'\xa5', (char)'\x08', (char)'\xcf', (char)'\xec', 
(char)'\x75', (char)'\xf6', (char)'\x42', (char)'\xf3', (char)'\xd0', 
(char)'\x6f', (char)'\x2d', (char)'\xc6', (char)'\xfb', (char)'\x1f', 
(char)'\xb0', (char)'\x21', (char)'\x7c', (char)'\xb7', (char)'\x1b', 
(char)'\xec', (char)'\x88', (char)'\x3f', (char)'\x2d', (char)'\xbf', 
(char)'\x64', (char)'\x4f', (char)'\x2d', (char)'\xbf', (char)'\x63', 
(char)'\xf6', (char)'\x01', (char)'\xe9', (char)'\x0e', (char)'\xc2', 
(char)'\x4a', (char)'\x40', (char)'\x31', (char)'\x08', (char)'\xd0', 
(char)'\x30', (char)'\x85', (char)'\x43', (char)'\x5f', (char)'\x49', 
(char)'\x95', (char)'\x0d', (char)'\x19', (char)'\x2e', (char)'\x4f', 
(char)'\xa8', (char)'\xfb', (char)'\xff', (char)'\xba', (char)'\xfa', 
(char)'\x94', (char)'\xac', (char)'\x12', (char)'\x9f', (char)'\x50', 
(char)'\xd1', (char)'\x92', (char)'\xe9', (char)'\x65', (char)'\x17', 
(char)'\x34', (char)'\x2d', (char)'\x0d', (char)'\x22', (char)'\x90', 
(char)'\x66', (char)'\xba', (char)'\x24', (char)'\xe8', (char)'\x81', 
(char)'\x22', (char)'\x1a', (char)'\x4e', (char)'\xa4', (char)'\x44', 
(char)'\x42', (char)'\x4c', (char)'\x92', (char)'\x99', (char)'\x09', 
(char)'\x72', (char)'\x09', (char)'\x73', (char)'\x77', (char)'\x8c', 
(char)'\xc9', (char)'\xcb', (char)'\xd3', (char)'\x8a', (char)'\x1a', 
(char)'\x63', (char)'\xc8', (char)'\x93', (char)'\xee', (char)'\x08', 
(char)'\x08', (char)'\x9c', (char)'\xc4', (char)'\x9f', (char)'\x70', 
(char)'\x40', (char)'\x44', (char)'\xd3', (char)'\x77', (char)'\x93', 
(char)'\x27', (char)'\x35', (char)'\x73', (char)'\x77', (char)'\x91', 
(char)'\x27', (char)'\x4b', (char)'\xd3', (char)'\x8a', (char)'\x1a', 
(char)'\x65', (char)'\xcd', (char)'\xde', (char)'\xa4', (char)'\xc5', 
(char)'\x2f', (char)'\x4e', (char)'\x28', (char)'\x69', (char)'\x9d', 
(char)'\x22', (char)'\x4f', (char)'\xd4', (char)'\x98', (char)'\x8b', 
(char)'\xea', (char)'\x2d', (char)'\x7d', (char)'\xc1', (char)'\x01', 
(char)'\x15', (char)'\xc8', (char)'\x93', (char)'\xe9', (char)'\x68', 
(char)'\xb5', (char)'\xf7', (char)'\x04', (char)'\x04', (char)'\x57', 
(char)'\x22', (char)'\x4f', (char)'\xd4', (char)'\x98', (char)'\x99', 
(char)'\xe7', (char)'\x36', (char)'\x67', (char)'\x44', (char)'\x89', 
(char)'\x3e', (char)'\xb1', (char)'\xcd', (char)'\x99', (char)'\xd5', 
(char)'\x22', (char)'\x4f', (char)'\xd4', (char)'\x98', (char)'\x87', 
(char)'\xe0', (char)'\x4c', (char)'\x87', (char)'\x91', (char)'\x27', 
(char)'\xd1', (char)'\x82', (char)'\x64', (char)'\x5c', (char)'\x89', 
(char)'\x3f', (char)'\x52', (char)'\x62', (char)'\x3f', (char)'\x9d', 
(char)'\x48', (char)'\xc9', (char)'\x12', (char)'\x24', (char)'\xfa', 
(char)'\x77', (char)'\x52', (char)'\x32', (char)'\x6c', (char)'\x89', 
(char)'\x3f', (char)'\x52', (char)'\x62', (char)'\x67', (char)'\x90', 
(char)'\xda', (char)'\x4c', (char)'\x92', (char)'\x54', (char)'\x89', 
(char)'\x3e', (char)'\xb1', (char)'\x0d', (char)'\xa4', (char)'\xc9', 
(char)'\x26', (char)'\xc8', (char)'\x93', (char)'\xf5', (char)'\x26', 
(char)'\x29', (char)'\x78', (char)'\x82', (char)'\x73', (char)'\x47', 
(char)'\x25', (char)'\x48', (char)'\x93', (char)'\xec', (char)'\x88', 
(char)'\x27', (char)'\x34', (char)'\x72', (char)'\x94', (char)'\x89', 
(char)'\x3f', (char)'\x52', (char)'\x62', (char)'\x67', (char)'\x90', 
(char)'\xb5', (char)'\xa2', (char)'\x44', (char)'\x13', (char)'\x9a', 
(char)'\x39', (char)'\x3e', (char)'\x44', (char)'\x9f', (char)'\x58', 
(char)'\x85', (char)'\xad', (char)'\x12', (char)'\x20', (char)'\x9c', 
(char)'\xd1', (char)'\xc8', (char)'\xf2', (char)'\x24', (char)'\xfb', 
(char)'\x34', (char)'\x30', (char)'\x91', (char)'\x31', (char)'\x27', 
(char)'\xd9', (char)'\xa1', (char)'\x84', (char)'\xcb', (char)'\x9a', 
(char)'\x4e', (char)'\xb8', (char)'\xa5', (char)'\x91', (char)'\x27', 
(char)'\x23', (char)'\x12', (char)'\x7e', (char)'\xa4', (char)'\xc4', 
(char)'\xcf', (char)'\x21', (char)'\x6b', (char)'\x44', (char)'\x88', 
(char)'\x27', (char)'\x34', (char)'\x72', (char)'\x79', (char)'\x89', 
(char)'\x3e', (char)'\xb1', (char)'\x0b', (char)'\x5a', (char)'\x24', 
(char)'\x41', (char)'\x39', (char)'\xa3', (char)'\x93', (char)'\x4c', 
(char)'\x49', (char)'\xfa', (char)'\x93', (char)'\x14', (char)'\xbc', 
(char)'\x41', (char)'\x39', (char)'\xa3', (char)'\x92', (char)'\x8c', 
(char)'\x49', (char)'\xf6', (char)'\x44', (char)'\x13', (char)'\x9a', 
(char)'\x39', (char)'\x34', (char)'\xc4', (char)'\x9f', (char)'\xa9', 
(char)'\x31', (char)'\x33', (char)'\xc8', (char)'\x6d', (char)'\x26', 
(char)'\x49', (char)'\x28', (char)'\xc4', (char)'\x9f', (char)'\x58', 
(char)'\x86', (char)'\xd2', (char)'\x64', (char)'\x92', (char)'\xcc', 
(char)'\x49', (char)'\xfa', (char)'\x93', (char)'\x11', (char)'\xfc', 
(char)'\xea', (char)'\x46', (char)'\x48', (char)'\x31', (char)'\x27', 
(char)'\xd3', (char)'\xba', (char)'\x91', (char)'\x92', (char)'\x4c', 
(char)'\x49', (char)'\xfa', (char)'\x93', (char)'\x10', (char)'\xfc', 
(char)'\x0c', (char)'\x18', (char)'\xc4', (char)'\x9f', (char)'\x46', 
(char)'\x0c', (char)'\x28', (char)'\xc4', (char)'\x9f', (char)'\xa9', 
(char)'\x31', (char)'\x0f', (char)'\xc0', (char)'\x99', (char)'\x0e', 
(char)'\x62', (char)'\x4f', (char)'\xa3', (char)'\x04', (char)'\xc8', 
(char)'\xb3', (char)'\x12', (char)'\x7e', (char)'\xa4', (char)'\xc4', 
(char)'\xcf', (char)'\x39', (char)'\xb3', (char)'\x3a', (char)'\x0c', 
(char)'\x49', (char)'\xf5', (char)'\x8e', (char)'\x6c', (char)'\xce', 
(char)'\xe3', (char)'\x12', (char)'\x7e', (char)'\xa4', (char)'\xc4', 
(char)'\x5f', (char)'\x51', (char)'\x6b', (char)'\xee', (char)'\x08', 
(char)'\x08', (char)'\xac', (char)'\xc4', (char)'\x9f', (char)'\x4b', 
(char)'\x45', (char)'\xaf', (char)'\xb8', (char)'\x20', (char)'\x22', 
(char)'\x73', (char)'\x12', (char)'\x7d', (char)'\x9a', (char)'\x18', 
(char)'\x49', (char)'\x4d', (char)'\xdd', (char)'\x5b', (char)'\x84', 
(char)'\x05', (char)'\x93', (char)'\xb3', (char)'\x43', (char)'\x09', 
(char)'\xd9', (char)'\x4e', (char)'\x28', (char)'\x69', (char)'\xa5', 
(char)'\x37', (char)'\x75', (char)'\x6e', (char)'\x10', (char)'\x16', 
(char)'\x4e', (char)'\x8c', (char)'\x13', (char)'\x27', (char)'\x65', 
(char)'\x38', (char)'\xa1', (char)'\xa6', (char)'\x64', (char)'\xdd', 
(char)'\xd9', (char)'\xa1', (char)'\x84', (char)'\xec', (char)'\xa7', 
(char)'\x14', (char)'\x34', (char)'\xcc', (char)'\x9b', (char)'\xba', 
(char)'\x30', (char)'\x4c', (char)'\x9d', (char)'\x94', (char)'\xe2', 
(char)'\x86', (char)'\x98', (char)'\x6d', (char)'\x75', (char)'\x87', 
(char)'\xea', (char)'\x4c', (char)'\x45', (char)'\x78', (char)'\x23', 
(char)'\x22', (char)'\x2e', (char)'\x6e', (char)'\xeb', (char)'\xe2', 
(char)'\x41', (char)'\x22', (char)'\x48', (char)'\x73', (char)'\x5b', 
(char)'\x37', (char)'\x74', (char)'\x97', (char)'\x7b', (char)'\x9a', 
(char)'\x46', (char)'\xcd', (char)'\x00', (char)'\x59', (char)'\xe6', 
(char)'\xee', (char)'\x95', (char)'\x17', (char)'\x20', (char)'\xe3', 
(char)'\xe7', (char)'\xba', (char)'\x48', (char)'\x9a', (char)'\x2a', 
(char)'\x4d', (char)'\x21', (char)'\xa5', (char)'\x43', (char)'\x51', 
(char)'\x3c', (char)'\x47', (char)'\xcd', (char)'\x21', (char)'\x81', 
(char)'\x34', (char)'\x54', (char)'\x9a', (char)'\x43', (char)'\x4a', 
(char)'\x86', (char)'\xa2', (char)'\x78', (char)'\x1f', (char)'\x5a', 
(char)'\x0e', (char)'\xb5', (char)'\x3e', (char)'\x62', (char)'\x1b', 
(char)'\xaa', (char)'\x81', (char)'\x74', (char)'\x22', (char)'\x7c', 
(char)'\xc0', (char)'\x5f', (char)'\x55', (char)'\x02', (char)'\xe8', 
(char)'\x43', (char)'\x4a', (char)'\x81', (char)'\x94', (char)'\xe2', 
(char)'\x49', (char)'\xc5', (char)'\x0c', (char)'\x8c', (char)'\xdd', 
(char)'\xd5', (char)'\xb8', (char)'\x40', (char)'\x59', (char)'\x3b', 
(char)'\x47', (char)'\xbc', (char)'\x91', (char)'\xe8', (char)'\x81', 
(char)'\x22', (char)'\x2e', (char)'\x6e', (char)'\xed', (char)'\x1e', 
(char)'\xf2', (char)'\x47', (char)'\xa2', (char)'\x04', (char)'\x89', 
(char)'\x19', (char)'\xbb', (char)'\xab', (char)'\x70', (char)'\x80', 
(char)'\xb2', (char)'\x75', (char)'\x6e', (char)'\xb1', (char)'\x23', 
(char)'\xd1', (char)'\x02', (char)'\x44', (char)'\x5c', (char)'\xdd', 
(char)'\xd5', (char)'\xba', (char)'\xc4', (char)'\x8f', (char)'\x44', 
(char)'\x09', (char)'\x11', (char)'\x32', (char)'\xa0', (char)'\x65', 
(char)'\x38', (char)'\xa1', (char)'\xa6', (char)'\x2c', (char)'\x9e', 
(char)'\xb4', (char)'\x4c', (char)'\xd2', (char)'\x20', (char)'\x22', 
(char)'\x23', (char)'\x9e', (char)'\x24', (char)'\x7a', (char)'\xc0', 
(char)'\x22', (char)'\x15', (char)'\x80', (char)'\x98', (char)'\x8e', 
(char)'\xd5', (char)'\xb8', (char)'\x40', (char)'\x59', (char)'\x3a', 
(char)'\x27', (char)'\x31', (char)'\xe0', (char)'\x89', (char)'\x14', 
(char)'\x23', (char)'\x82', (char)'\x3d', (char)'\x13', (char)'\x86', 
(char)'\x4e', (char)'\x82', (char)'\x66', (char)'\x11', (char)'\xc1', 
(char)'\x1e', (char)'\x82', (char)'\x6c', (char)'\x2c', (char)'\x5c', 
(char)'\xce', (char)'\x88', (char)'\xe0', (char)'\x8f', (char)'\x44', 
(char)'\xe1', (char)'\x91', (char)'\x71', (char)'\x20', (char)'\xe8', 
(char)'\x22', (char)'\x76', (char)'\x28', (char)'\x31', (char)'\xe6', 
(char)'\x68', (char)'\x90', (char)'\x74', (char)'\x11', (char)'\x0c', 
(char)'\x54', (char)'\x2e', (char)'\x64', (char)'\x88', (char)'\xd8', 
(char)'\x07', (char)'\x9c', (char)'\xae', (char)'\x8f', (char)'\x48', 
(char)'\x01', (char)'\x1c', (char)'\x92', (char)'\x73', (char)'\x08', 
(char)'\x52', (char)'\x54', (char)'\x91', (char)'\xc2', (char)'\xd6', 
(char)'\x09', (char)'\x4f', (char)'\xa8', (char)'\x68', (char)'\xc9', 
(char)'\x7e', (char)'\xb8', (char)'\xa7', (char)'\x43', (char)'\x46', 
(char)'\x4b', (char)'\xf5', (char)'\xc5', (char)'\x3d', (char)'\x1b', 
(char)'\x23', (char)'\xa0', (char)'\xb3', (char)'\x34', (char)'\x9e', 
(char)'\xb4', (char)'\x08', (char)'\x84', (char)'\x93', (char)'\xa3', 
(char)'\x32', (char)'\xc9', (char)'\x68', (char)'\x11', (char)'\x0b', 
(char)'\x23', (char)'\xa0', (char)'\xb3', (char)'\x3c', (char)'\x49', 
(char)'\x52', (char)'\x46', (char)'\x44', (char)'\x23', (char)'\xe9', 
(char)'\x23', (char)'\x8d', (char)'\xee', (char)'\x79', (char)'\x12', 
(char)'\xe4', (char)'\x34', (char)'\xc2', (char)'\x73', (char)'\x44', 
(char)'\x09', (char)'\x9d', (char)'\x12', (char)'\xe8', (char)'\x23', 
(char)'\x10', (char)'\x61', (char)'\x20', (char)'\x89', (char)'\xa8', 
(char)'\xc2', (char)'\x36', (char)'\x72', (char)'\x30', (char)'\x80', 
(char)'\xb5', (char)'\x92', (char)'\x24', (char)'\x0a', (char)'\x1a', 
(char)'\x32', (char)'\x08', (char)'\x0b', (char)'\x59', (char)'\x40', 
(char)'\xf7', (char)'\xaf', (char)'\x14', (char)'\x7c', (char)'\x5c', 
(char)'\xf2', (char)'\x46', (char)'\x78', (char)'\xea', (char)'\x25', 
(char)'\xd2', (char)'\x36', (char)'\x8c', (char)'\x24', (char)'\x40', 
(char)'\x15', (char)'\x91', (char)'\xa8', (char)'\x97', (char)'\x5f', 
(char)'\x17', (char)'\x4d', (char)'\xd3', (char)'\xc3', (char)'\xc9', 
(char)'\xd7', (char)'\x15', (char)'\xc7', (char)'\x04', (char)'\xba', 
(char)'\xeb', (char)'\x37', (char)'\xaf', (char)'\x8b', (char)'\x9e', 
(char)'\x48', (char)'\xcf', (char)'\x07', (char)'\xa8', (char)'\x17', 
(char)'\x42', (char)'\x75', (char)'\xc5', (char)'\x74', (char)'\xdd', 
(char)'\xd5', (char)'\xb8', (char)'\x40', (char)'\x59', (char)'\x3b', 
(char)'\x29', (char)'\xc5', (char)'\x0d', (char)'\x32', (char)'\x26', 
(char)'\xee', (char)'\xca', (char)'\x71', (char)'\x43', (char)'\x4c', 
(char)'\xb1', (char)'\x39', (char)'\xb2', (char)'\x9c', (char)'\x68', 
(char)'\x29', (char)'\xba', (char)'\x2a', (char)'\x23', (char)'\x68', 
(char)'\xc2', (char)'\x44', (char)'\x01', (char)'\x59', (char)'\x18', 
(char)'\x27', (char)'\x34', (char)'\xa8', (char)'\x35', (char)'\x22', 
(char)'\x00', (char)'\xac', (char)'\x88', (char)'\xb4', (char)'\x51', 
(char)'\x30', (char)'\x05', (char)'\x64', (char)'\x2d', (char)'\x40', 
(char)'\xba', (char)'\x10', (char)'\x8a', (char)'\x26', (char)'\x24', 
(char)'\xc4', (char)'\x4b', (char)'\xa4', (char)'\xbc', (char)'\xc8', 
(char)'\xd9', (char)'\x20', (char)'\x2b', (char)'\x22', (char)'\x11', 
(char)'\x2e', (char)'\x96', (char)'\x2e', (char)'\xc9', (char)'\x1a', 
(char)'\x84', (char)'\x4b', (char)'\xb2', (char)'\x20', (char)'\x16', 
(char)'\x41', (char)'\xc5', (char)'\xf1', (char)'\x25', (char)'\x48', 
(char)'\xd9', (char)'\xf4', (char)'\x97', (char)'\x99', (char)'\x1b', 
(char)'\x24', (char)'\x39', (char)'\xb2', (char)'\x31', (char)'\x62', 
(char)'\x42', (char)'\x64', (char)'\x84', (char)'\x91', (char)'\x00', 
(char)'\xb8', (char)'\x0c', (char)'\x85', (char)'\x43', (char)'\x49', 
(char)'\xd7', (char)'\xc5', (char)'\xcf', (char)'\x24', (char)'\x66', 
(char)'\x82', (char)'\x49', (char)'\xc3', (char)'\x21', (char)'\x50', 
(char)'\xd2', (char)'\x75', (char)'\xf1', (char)'\x73', (char)'\xc9', 
(char)'\x19', (char)'\xe0', (char)'\xc2', (char)'\x89', (char)'\x01', 
(char)'\x32', (char)'\x4c', (char)'\xc4', (char)'\x9f', (char)'\x5d', 
(char)'\x66', (char)'\xf5', (char)'\xf1', (char)'\x73', (char)'\xc9', 
(char)'\x19', (char)'\xe3', (char)'\xad', (char)'\x03', (char)'\x09', 
(char)'\x2c', (char)'\x18', (char)'\x93', (char)'\xeb', (char)'\xac', 
(char)'\xde', (char)'\xbe', (char)'\x2e', (char)'\x79', (char)'\x23', 
(char)'\x3c', (char)'\x74', (char)'\x64', (char)'\x90', (char)'\xa2', 
(char)'\x87', (char)'\xcd', (char)'\xd1', (char)'\x71', (char)'\x89', 
(char)'\x3e', (char)'\xbe', (char)'\x2e', (char)'\x9b', (char)'\xa7', 
(char)'\x87', (char)'\x93', (char)'\xae', (char)'\x2a', (char)'\x5f', 
(char)'\x17', (char)'\x4d', (char)'\xd3', (char)'\xc3', (char)'\xc8', 
(char)'\x65', (char)'\x91', (char)'\xa6', (char)'\x99', (char)'\xca', 
(char)'\x05', (char)'\x92', (char)'\x33', (char)'\x2b', (char)'\xe2', 
(char)'\x86', (char)'\xcd', (char)'\x14', (char)'\x69', (char)'\x24', 
(char)'\x20', (char)'\x3c', (char)'\x86', (char)'\x24', (char)'\x84', 
(char)'\x07', (char)'\x92', (char)'\x44', (char)'\x68', (char)'\x9c', 
(char)'\x32', (char)'\x7a', (char)'\x91', (char)'\x40', (char)'\xbc', 
(char)'\xe6', (char)'\x3d', (char)'\xcd', (char)'\x23', (char)'\x66', 
(char)'\x92', (char)'\x34', (char)'\x4e', (char)'\x19', (char)'\x3d', 
(char)'\x48', (char)'\xa2', (char)'\x43', (char)'\x20', (char)'\x9c', 
(char)'\x32', (char)'\x7a', (char)'\x91', (char)'\x40', (char)'\xbc', 
(char)'\xe6', (char)'\x3d', (char)'\xcd', (char)'\x23', (char)'\x66', 
(char)'\x91', (char)'\x90', (char)'\x4e', (char)'\x19', (char)'\x3d', 
(char)'\x48', (char)'\xa1', (char)'\x08', (char)'\x97', (char)'\x4a', 
(char)'\x86', (char)'\xa2', (char)'\x64', (char)'\x2a', (char)'\x25', 
(char)'\xd6', (char)'\x83', (char)'\xad', (char)'\x53', (char)'\xc1', 
(char)'\x19', (char)'\x13', (char)'\xa5', (char)'\x5b', (char)'\x81', 
(char)'\x34', (char)'\x3c', (char)'\x11', (char)'\x91', (char)'\x3a', 
(char)'\x86', (char)'\xcc', (char)'\x84', (char)'\xa8', (char)'\x16', 
(char)'\x8f', (char)'\x52', (char)'\x28', (char)'\x27', (char)'\xa3', 
(char)'\x17', (char)'\x45', (char)'\x3d', (char)'\x9e', (char)'\x1e', 
(char)'\x4e', (char)'\x92', (char)'\x31', (char)'\x74', (char)'\x57', 
(char)'\xd9', (char)'\x61', (char)'\xb3', (char)'\x05', (char)'\xfa', 
(char)'\x48', (char)'\xc5', (char)'\xd1', (char)'\x77', (char)'\x62', 
(char)'\x42', (char)'\x92', (char)'\x24', (char)'\x68', (char)'\x89', 
(char)'\xd2', (char)'\x46', (char)'\x2e', (char)'\x8a', (char)'\xbb', 
(char)'\x10', (char)'\x34', (char)'\xc4', (char)'\xe9', (char)'\x23', 
(char)'\x17', (char)'\x45', (char)'\x3d', (char)'\x25', (char)'\x40', 
(char)'\xbe', (char)'\x92', (char)'\x31', (char)'\x74', (char)'\x4a', 
(char)'\x18', (char)'\x90', (char)'\x0a', (char)'\x20', (char)'\x81', 
(char)'\x22', (char)'\x14', (char)'\x40', (char)'\x98', (char)'\x8e', 
(char)'\x81', (char)'\x39', (char)'\xb3', (char)'\x43', (char)'\x68', 
(char)'\x44', (char)'\x0c', (char)'\x81', (char)'\xa1', (char)'\x8c', 
(char)'\x48', (char)'\x9c', (char)'\x64', (char)'\x28', (char)'\x28', 
(char)'\xb9', (char)'\x98', (char)'\x64', (char)'\x59', (char)'\x26', 
(char)'\x41', (char)'\x0d', (char)'\xc4', (char)'\x22', (char)'\xe4', 
(char)'\x43', (char)'\x95', (char)'\x0d', (char)'\x02', (char)'\xd7', 
(char)'\x81', (char)'\x95', (char)'\xd1', (char)'\x41', (char)'\x68', 
(char)'\xd1', (char)'\x4d', (char)'\x0e', (char)'\x68', (char)'\x90', 
(char)'\x51', (char)'\x21', (char)'\xaa', (char)'\x43', (char)'\x65', 
(char)'\x30', (char)'\x34', (char)'\x44', (char)'\x71', (char)'\x46', 
(char)'\xd2', (char)'\x58', (char)'\xd1', (char)'\x24', (char)'\xe9', 
(char)'\x16', (char)'\x9a', (char)'\x56', (char)'\x44', (char)'\xc4', 
(char)'\x72', (char)'\x4c', (char)'\xe8', (char)'\x91', (char)'\x66', 
(char)'\x78', (char)'\xba', (char)'\xc3', (char)'\x9c', (char)'\x70', 
(char)'\x69', (char)'\xe1', (char)'\xe4', (char)'\x71', (char)'\x47', 
(char)'\x50', (char)'\x44', (char)'\x95', (char)'\x72', (char)'\x06', 
(char)'\x5b', (char)'\x1d', (char)'\x0b', (char)'\x21', (char)'\x2c', 
(char)'\x89', (char)'\x9c', (char)'\xa2', (char)'\x58', (char)'\x25', 
(char)'\x22', (char)'\x89', (char)'\xcd', (char)'\x94', (char)'\xc8', 
(char)'\x46', (char)'\xd1', (char)'\x86', (char)'\x25', (char)'\xde', 
(char)'\xe8', (char)'\xcc', (char)'\xe4', (char)'\xbc', (char)'\xc8', 
(char)'\xd1', (char)'\xc8', (char)'\xc8', (char)'\x63', (char)'\xdd', 
(char)'\x19', (char)'\x96', (char)'\x54', (char)'\x79', (char)'\x22', 
(char)'\x46', (char)'\xd1', (char)'\x6b', (char)'\x24', (char)'\xe3', 
(char)'\x42', (char)'\x14', (char)'\xa0', (char)'\x5c', (char)'\x88', 
(char)'\x33', (char)'\x12', (char)'\x63', (char)'\x91', (char)'\x26', 
(char)'\xa4', (char)'\x31', (char)'\xeb', (char)'\x23', (char)'\x08', 
(char)'\xd9', (char)'\xc9', (char)'\x28', (char)'\x6c', (char)'\xc9', 
(char)'\x12', (char)'\x80', (char)'\x44', (char)'\x1a', (char)'\xc9', 
(char)'\x33', (char)'\xbc', (char)'\x02', (char)'\x50', (char)'\x0c', 
(char)'\x43', (char)'\xbe', (char)'\x28', (char)'\xa8', (char)'\x26', 
(char)'\x44', (char)'\xbe', (char)'\x2e', (char)'\x99', (char)'\x05', 
(char)'\x32', (char)'\x0c', (char)'\xcf', (char)'\xa0', (char)'\x56', 
(char)'\x81', (char)'\x1a', (char)'\x53', (char)'\x04', (char)'\xd0', 
(char)'\x22', (char)'\xe1', (char)'\x04', (char)'\x58', (char)'\xc0', 
};

/* to convert a heap stridx to a token number, subtract
 * DUK_STRIDX_START_RESERVED and add DUK_TOK_START_RESERVED.
 */
#line 1 "duk_unicode_support.c"
/*
 *  Various Unicode help functions for character classification predicates,
 *  case conversion, decoding, etc.
 */

/* include removed: duk_internal.h */

/*
 *  XUTF-8 and CESU-8 encoding/decoding
 */

int duk_unicode_get_xutf8_length(duk_u32 x) {
	if (x < 0x80) {
		/* 7 bits */
		return 1;
	} else if (x < 0x800) {
		/* 11 bits */
		return 2;
	} else if (x < 0x10000) {
		/* 16 bits */
		return 3;
	} else if (x < 0x200000) {
		/* 21 bits */
		return 4;
	} else if (x < 0x4000000) {
		/* 26 bits */
		return 5;
	} else if (x < (duk_u32) 0x80000000L) {
		/* 31 bits */
		return 6;
	} else {
		/* 36 bits */
		return 7;
	}
}

duk_u8 duk_unicode_xutf8_markers[7] = {
	0x00, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe
};

/* Encode to extended UTF-8; 'out' must have space for at least
 * DUK_UNICODE_MAX_XUTF8_LENGTH bytes.  Allows encoding of any
 * 32-bit (unsigned) codepoint.
 */
size_t duk_unicode_encode_xutf8(duk_u32 x, duk_u8 *out) {
	size_t len;
	duk_u8 marker;
	size_t i;

	len = duk_unicode_get_xutf8_length(x);
	DUK_ASSERT(len > 0);

	marker = duk_unicode_xutf8_markers[len - 1];  /* 64-bit OK because always >= 0 */

	i = len;
	DUK_ASSERT(i > 0);
	do {
		i--;
		if (i > 0) {
			out[i] = 0x80 + (x & 0x3f);
			x >>= 6;
		} else {
			/* Note: masking of 'x' is not necessary because of
			 * range check and shifting -> no bits overlapping
			 * the marker should be set.
			 */
			out[0] = marker + x;
		}
	} while(i > 0);

	return len;
}

/* Encode to CESU-8; 'out' must have space for at least
 * DUK_UNICODE_MAX_CESU8_LENGTH bytes; codepoints above U+10FFFF
 * will encode to garbage but won't overwrite the output buffer.
 */
size_t duk_unicode_encode_cesu8(duk_u32 x, duk_u8 *out) {
	size_t len;

	if (x < 0x80) {
		out[0] = x;
		len = 1;
	} else if (x < 0x800) {
		out[0] = 0xc0 + ((x >> 6) & 0x1f);
		out[1] = 0x80 + (x & 0x3f);
		len = 2;
	} else if (x < 0x10000) {
		/* surrogate pairs get encoded here */
		out[0] = 0xe0 + ((x >> 12) & 0x0f);
		out[1] = 0x80 + ((x >> 6) & 0x3f);
		out[2] = 0x80 + (x & 0x3f);
		len = 3;
	} else {
		/*
		 *  Unicode codepoints above U+FFFF are encoded as surrogate
		 *  pairs here.  This ensures that all CESU-8 codepoints are
		 *  16-bit values as expected in Ecmascript.  The surrogate
		 *  pairs always get a 3-byte encoding (each) in CESU-8.
		 *  See: http://en.wikipedia.org/wiki/Surrogate_pair
		 *
		 *  20-bit codepoint, 10 bits (A and B) per surrogate pair:
		 * 
		 *    x = 0b00000000 0000AAAA AAAAAABB BBBBBBBB
		 *  sp1 = 0b110110AA AAAAAAAA  (0xd800 + ((x >> 10) & 0x3ff))
		 *  sp2 = 0b110111BB BBBBBBBB  (0xdc00 + (x & 0x3ff))
		 *
		 *  Encoded into CESU-8:
		 *
		 *  sp1 -> 0b11101101  (0xe0 + ((sp1 >> 12) & 0x0f))
		 *      -> 0b1010AAAA  (0x80 + ((sp1 >> 6) & 0x3f))
		 *      -> 0b10AAAAAA  (0x80 + (sp1 & 0x3f))
		 *  sp2 -> 0b11101101  (0xe0 + ((sp2 >> 12) & 0x0f))
		 *      -> 0b1011BBBB  (0x80 + ((sp2 >> 6) & 0x3f))
		 *      -> 0b10BBBBBB  (0x80 + (sp2 & 0x3f))
		 *
		 *  Note that 0x10000 must be subtracted first.  The code below
		 *  avoids the sp1, sp2 temporaries which saves around 20 bytes
		 *  of code.
		 */

		x -= 0x10000;

		out[0] = 0xed;
		out[1] = 0xa0 + ((x >> 16) & 0x0f);
		out[2] = 0x80 + ((x >> 10) & 0x3f);
		out[3] = 0xed;
		out[4] = 0xb0 + ((x >> 6) & 0x0f);
		out[5] = 0x80 + (x & 0x3f);
		len = 6;
	}

	return len;
}

/* Decode helper.  Return zero on error. */
int duk_unicode_xutf8_get_u32(duk_hthread *thr, duk_u8 **ptr, duk_u8 *ptr_start, duk_u8 *ptr_end, duk_u32 *out_cp) {
	duk_u8 *p;
	duk_u32 res;
	int ch;
	int n;

	p = *ptr;
	if (p < ptr_start || p >= ptr_end) {
		goto fail;
	}

	/*
	 *  UTF-8 decoder which accepts longer than standard byte sequences.
	 *  This allows full 32-bit code points to be used.
	 */

	ch = *p++;
	if (ch < 0x80) {
		/* 0xxx xxxx   [7 bits] */
		res = ch & 0x7f;
		n = 0;
	} else if (ch < 0xc0) {
		/* 10xx xxxx -> invalid */
		goto fail;
	} else if (ch < 0xe0) {
		/* 110x xxxx   10xx xxxx   [11 bits] */
		res = ch & 0x1f;
		n = 1;
	} else if (ch < 0xf0) {
		/* 1110 xxxx   10xx xxxx   10xx xxxx   [16 bits] */
		res = ch & 0x0f;
		n = 2;
	} else if (ch < 0xf8) {
		/* 1111 0xxx   10xx xxxx   10xx xxxx   10xx xxxx   [21 bits] */
		res = ch & 0x07;
		n = 3;
	} else if (ch < 0xfc) {
		/* 1111 10xx   10xx xxxx   10xx xxxx   10xx xxxx   10xx xxxx   [26 bits] */
		res = ch & 0x03;
		n = 4;
	} else if (ch < 0xfe) {
		/* 1111 110x   10xx xxxx   10xx xxxx   10xx xxxx   10xx xxxx   10xx xxxx   [31 bits] */
		res = ch & 0x01;
		n = 5;
	} else if (ch < 0xff) {
		/* 1111 1110   10xx xxxx   10xx xxxx   10xx xxxx   10xx xxxx   10xx xxxx   10xx xxxx   [36 bits] */
		res = 0;
		n = 6;
	} else {
		/* 8-byte format could be:
		 * 1111 1111   10xx xxxx   10xx xxxx   10xx xxxx   10xx xxxx   10xx xxxx   10xx xxxx   10xx xxxx   [41 bits]
		 *
		 * However, this format would not have a zero bit following the
		 * leading one bits and would not allow 0xFF to be used as an
		 * "invalid xutf-8" marker for internal keys.  Further, 8-byte
		 * encodings (up to 41 bit code points) are not currently needed.
		 */
		goto fail;
	}

	DUK_ASSERT(p >= ptr_start);  /* verified at beginning */
	if (p + n > ptr_end) {
		/* check pointer at end */
		goto fail;
	}

	while (n > 0) {
		DUK_ASSERT(p >= ptr_start && p < ptr_end);
		res = res << 6;
		res += (*p++) & 0x3f;
		n--;
	}

	*ptr = p;
	*out_cp = res;
	return 1;

 fail:
	return 0;
}

/* used by e.g. duk_regexp_executor.c, string built-ins */
duk_u32 duk_unicode_xutf8_get_u32_checked(duk_hthread *thr, duk_u8 **ptr, duk_u8 *ptr_start, duk_u8 *ptr_end) {
	duk_u32 cp;

	if (duk_unicode_xutf8_get_u32(thr, ptr, ptr_start, ptr_end, &cp)) {
		return cp;
	}
	DUK_ERROR(thr, DUK_ERR_INTERNAL_ERROR, "utf-8 decode failed");
	return 0;  /* never here */
}

/* (extended) utf-8 length without codepoint encoding validation, used
 * for string interning (should probably be inlined).
 */
duk_u32 duk_unicode_unvalidated_utf8_length(duk_u8 *data, duk_u32 blen) {
        duk_u8 *p = data;
        duk_u8 *p_end = data + blen;
        duk_u32 clen = 0;

        while (p < p_end) {
                duk_u8 x = *p++;
                if (x < 0x80) {
                        clen++;
                } else if (x >= 0xc0 ) {
                        /* 10xxxxxx = continuation chars (0x80...0xbf), above that
                         * initial bytes.
                         */
                        clen++;
                }
        }

        return clen;
}

/*
 *  Unicode range matcher
 *
 *  Matches a codepoint against a packed bitstream of character ranges.
 *  Used for slow path Unicode matching.
 */

/* Must match src/extract_chars.py, generate_match_table3(). */
static int uni_decode_value(duk_bitdecoder_ctx *bd_ctx) {
	int t;

	t = duk_bd_decode(bd_ctx, 4);
	if (t <= 0x0e) {
		return t;
	}
	t = duk_bd_decode(bd_ctx, 8);
	if (t <= 0xfd) {
		return t + 0x0f;
	}
	if (t == 0xfe) {
		t = duk_bd_decode(bd_ctx, 12);
		return t + 0x0f + 0xfe;
	} else {
		t = duk_bd_decode(bd_ctx, 24);
		return t + 0x0f + 0xfe + 0x1000;
	}
}

static int uni_range_match(char *unitab, int unilen, int x) {
	duk_bitdecoder_ctx bd_ctx;

	bd_ctx.data = (duk_u8 *) unitab;
	bd_ctx.offset = (duk_u32) 0;
	bd_ctx.length = (duk_u32) unilen;
	bd_ctx.currval = (duk_u32) 0;
	bd_ctx.currbits = (duk_u32) 0;

	int prev_re = 0;
	for (;;) {
		int r1, r2;
		r1 = uni_decode_value(&bd_ctx);
		if (r1 == 0) {
			break;
		}
		r2 = uni_decode_value(&bd_ctx);

		r1 = prev_re + r1;
		r2 = r1 + r2;
		prev_re = r2;

		/* [r1,r2] is the range */

		DUK_DDDPRINT("uni_range_match: range=[0x%06x,0x%06x]", r1, r2);
		if (x >= r1 && x <= r2) {
			return 1;
		}
	}

	return 0;
}

/*
 *  "WhiteSpace" production check.
 */

int duk_unicode_is_whitespace(int x) {
	/*
	 *  E5 Section 7.2 specifies six characters specifically as
	 *  white space:
	 *
	 *    0009;<control>;Cc;0;S;;;;;N;CHARACTER TABULATION;;;;
	 *    000B;<control>;Cc;0;S;;;;;N;LINE TABULATION;;;;
	 *    000C;<control>;Cc;0;WS;;;;;N;FORM FEED (FF);;;;
	 *    0020;SPACE;Zs;0;WS;;;;;N;;;;;
	 *    00A0;NO-BREAK SPACE;Zs;0;CS;<noBreak> 0020;;;;N;NON-BREAKING SPACE;;;;
	 *    FEFF;ZERO WIDTH NO-BREAK SPACE;Cf;0;BN;;;;;N;BYTE ORDER MARK;;;;
	 *
	 *  It also specifies any Unicode category 'Z' characters as white
	 *  space.  These can be extracted with the "src/extract_chars.py" script,
	 *  see src/SConscript t_uni_ws target.
	 *
	 *  Current result (built as WhiteSpace-Z.txt).
	 *
	 *    RAW OUTPUT:
	 *    ===========
	 *    0020;SPACE;Zs;0;WS;;;;;N;;;;;
	 *    00A0;NO-BREAK SPACE;Zs;0;CS;<noBreak> 0020;;;;N;NON-BREAKING SPACE;;;;
	 *    1680;OGHAM SPACE MARK;Zs;0;WS;;;;;N;;;;;
	 *    180E;MONGOLIAN VOWEL SEPARATOR;Zs;0;WS;;;;;N;;;;;
	 *    2000;EN QUAD;Zs;0;WS;2002;;;;N;;;;;
	 *    2001;EM QUAD;Zs;0;WS;2003;;;;N;;;;;
	 *    2002;EN SPACE;Zs;0;WS;<compat> 0020;;;;N;;;;;
	 *    2003;EM SPACE;Zs;0;WS;<compat> 0020;;;;N;;;;;
	 *    2004;THREE-PER-EM SPACE;Zs;0;WS;<compat> 0020;;;;N;;;;;
	 *    2005;FOUR-PER-EM SPACE;Zs;0;WS;<compat> 0020;;;;N;;;;;
	 *    2006;SIX-PER-EM SPACE;Zs;0;WS;<compat> 0020;;;;N;;;;;
	 *    2007;FIGURE SPACE;Zs;0;WS;<noBreak> 0020;;;;N;;;;;
	 *    2008;PUNCTUATION SPACE;Zs;0;WS;<compat> 0020;;;;N;;;;;
	 *    2009;THIN SPACE;Zs;0;WS;<compat> 0020;;;;N;;;;;
	 *    200A;HAIR SPACE;Zs;0;WS;<compat> 0020;;;;N;;;;;
	 *    2028;LINE SEPARATOR;Zl;0;WS;;;;;N;;;;;
	 *    2029;PARAGRAPH SEPARATOR;Zp;0;B;;;;;N;;;;;
	 *    202F;NARROW NO-BREAK SPACE;Zs;0;CS;<noBreak> 0020;;;;N;;;;;
	 *    205F;MEDIUM MATHEMATICAL SPACE;Zs;0;WS;<compat> 0020;;;;N;;;;;
	 *    3000;IDEOGRAPHIC SPACE;Zs;0;WS;<wide> 0020;;;;N;;;;;
	 *    
	 *    RANGES:
	 *    =======
	 *    0x0020
	 *    0x00a0
	 *    0x1680
	 *    0x180e
	 *    0x2000 ... 0x200a
	 *    0x2028 ... 0x2029
	 *    0x202f
	 *    0x205f
	 *    0x3000
	 *
	 *  A manual decoder (below) is probably most compact for this.
	 */

	unsigned char lo;
	int hi;

	lo = (unsigned char) (x & 0xff);
	hi = (int) (x >> 8);  /* does not fit into an uchar */

	if (hi == 0x0000) {
		if (lo == 0x09 || lo == 0x0b || lo == 0x0c ||
	            lo == 0x20 || lo == 0xa0) {
			return 1;
		}
	} else if (hi == 0x0020) {
		if (lo <= 0x0a || lo == 0x28 || lo == 0x29 ||
		    lo == 0x2f || lo == 0x5f) {
			return 1;
		}
	} else if (x == 0x1680 || x == 0x180e || x == 0x3000 ||
	           x == 0xfeff) {
		return 1;
	}

	return 0;
}

/*
 *  "LineTerminator" production check.
 */

int duk_unicode_is_line_terminator(int x) {
	/*
	 *  E5 Section 7.3
	 *
	 *  A LineTerminatorSequence essentially merges <CR> <LF> sequences
	 *  into a single line terminator.  This must be handled by the caller.
	 */

	if (x == 0x000a || x == 0x000d || x == 0x2028 ||
	    x == 0x2029) {
		return 1;
	}

	return 0;
}

/*
 *  "IdentifierStart" production check.
 */

int duk_unicode_is_identifier_start(int x) {
	/*
	 *  E5 Section 7.6:
	 *
	 *    IdentifierStart:
	 *      UnicodeLetter
	 *      $
	 *      _
	 *      \ UnicodeEscapeSequence
	 *
	 *  IdentifierStart production has one multi-character production:
	 *
	 *    \ UnicodeEscapeSequence
	 *
	 *  The '\' character is -not- matched by this function.  Rather, the caller
	 *  should decode the escape and then call this function to check whether the
	 *  decoded character is acceptable (see discussion in E5 Section 7.6).
	 *
	 *  The "UnicodeLetter" alternative of the production allows letters
	 *  from various Unicode categories.  These can be extracted with the
	 *  "src/extract_chars.py" script, see src/SConscript t_uni_idstart* targets.
	 *
	 *  Because the result has hundreds of Unicode codepoint ranges, matching
	 *  for any values >= 0x80 are done using a very slow range-by-range scan
	 *  and a packed range format.
	 *
	 *  The ASCII portion (codepoints 0x00 ... 0x7f) is fast-pathed below because
	 *  it matters the most.  The ASCII related ranges of IdentifierStart are:
	 *
	 *    0x0041 ... 0x005a		['A' ... 'Z']
	 *    0x0061 ... 0x007a		['a' ... 'z']
	 *    0x0024			['$']
	 *    0x005f			['_']
	 */

	/* ASCII fast path -- quick accept and reject */
	if (x <= 0x7f) {
		if ((x >= 'a' && x <= 'z') ||
		    (x >= 'A' && x <= 'Z') ||
		    x == '_' || x == '$') {
			return 1;
		}
		return 0;
	}

	/* Non-ASCII slow path (range-by-range linear comparison), very slow */

#ifdef DUK_USE_SOURCE_NONBMP
	if (uni_range_match(duk_unicode_identifier_start_noascii,
	                    sizeof(duk_unicode_identifier_start_noascii),
	                    x)) {
		return 1;
	}
	return 0;
#else
	if (x < 0x10000) {
		if (uni_range_match(duk_unicode_identifier_start_noascii_bmponly,
	                    sizeof(duk_unicode_identifier_start_noascii_bmponly),
	                    x)) {
			return 1;
		}
		return 0;
	} else {
		/* without explicit non-BMP support, assume non-BMP characters
		 * are always accepted as identifier characters.
		 */
		return 1;
	}
#endif
}

/*
 *  "IdentifierPart" production check.
 */

int duk_unicode_is_identifier_part(int x) {
	/*
	 *  E5 Section 7.6:
	 *
	 *    IdentifierPart:
	 *      IdentifierStart
	 *      UnicodeCombiningMark
	 *      UnicodeDigit
	 *      UnicodeConnectorPunctuation
	 *      <ZWNJ>	[U+200C]
	 *      <ZWJ>	[U+200D]
	 *
	 *  IdentifierPart production has one multi-character production
	 *  as part of its IdentifierStart alternative.  The '\' character
	 *  of an escape sequence is not matched here, see discussion in
	 *  duk_unicode_is_identifier_start().
	 *
	 *  To match non-ASCII characters (codepoints >= 0x80), a very slow
	 *  linear range-by-range scan is used.  The codepoint is first compared
	 *  to the IdentifierStart ranges, and if it doesn't match, then to a
	 *  set consisting of code points in IdentifierPart but not in
	 *  IdentifierStart.  This is done to keep the unicode range data small,
	 *  at the expense of speed.
	 *
	 *  The ASCII fast path consists of:
	 *
	 *    0x0030 ... 0x0039		['0' ... '9', UnicodeDigit]
	 *    0x0041 ... 0x005a		['A' ... 'Z', IdentifierStart]
	 *    0x0061 ... 0x007a		['a' ... 'z', IdentifierStart]
	 *    0x0024			['$', IdentifierStart]
	 *    0x005f			['_', IdentifierStart and
	 *                               UnicodeConnectorPunctuation]
	 *
	 *  UnicodeCombiningMark has no code points <= 0x7f.
	 *
	 *  The matching code reuses the "identifier start" tables, and then
	 *  consults a separate range set for characters in "identifier part"
	 *  but not in "identifier start".  These can be extracted with the
	 *  "src/extract_chars.py" script, see src/SConscript
	 *  t_uni_idpart_minus_idstart* targets.
	 *
	 *  UnicodeCombiningMark -> categories Mn, Mc
	 *  UnicodeDigit -> categories Nd
	 *  UnicodeConnectorPunctuation -> categories Pc
	 */

	/* ASCII fast path -- quick accept and reject */
	if (x <= 0x7f) {
		if ((x >= 'a' && x <= 'z') ||
		    (x >= 'A' && x <= 'Z') ||
		    (x >= '0' && x <= '9') ||
		    x == '_' || x == '$') {
			return 1;
		}
		return 0;
	}

	/* Non-ASCII slow path (range-by-range linear comparison), very slow */

#ifdef DUK_USE_SOURCE_NONBMP
	if (uni_range_match(duk_unicode_identifier_start_noascii,
	                    sizeof(duk_unicode_identifier_start_noascii),
	                    x) ||
	    uni_range_match(duk_unicode_identifier_part_minus_identifier_start_noascii,
	                    sizeof(duk_unicode_identifier_part_minus_identifier_start_noascii),
	                    x)) {
		return 1;
	}
	return 0;
#else
	if (x < 0x10000) {
		if (uni_range_match(duk_unicode_identifier_start_noascii_bmponly,
		                    sizeof(duk_unicode_identifier_start_noascii_bmponly),
		                    x) ||
		    uni_range_match(duk_unicode_identifier_part_minus_identifier_start_noascii_bmponly,
		                    sizeof(duk_unicode_identifier_part_minus_identifier_start_noascii_bmponly),
		                    x)) {
			return 1;
		}
		return 0;
	} else {
		/* without explicit non-BMP support, assume non-BMP characters
		 * are always accepted as identifier characters.
		 */
		return 1;
	}
#endif
}

/*
 *  Complex case conversion helper which decodes a bit-packed conversion
 *  control stream generated by unicode/extract_caseconv.py.  The conversion
 *  is very slow because it runs through the conversion data in a linear
 *  fashion to save space (which is why ASCII characters have a special
 *  fast path before arriving here).
 * 
 *  The particular bit counts etc have been determined experimentally to
 *  be small but still sufficient, and must match the Python script
 *  (src/extract_caseconv.py).
 *
 *  The return value is the case converted codepoint or -1 if the conversion
 *  results in multiple characters (this is useful for regexp Canonicalization
 *  operation).  If 'buf' is not NULL, the result codepoint(s) are also
 *  appended to the hbuffer.
 *
 *  Context and locale specific rules must be checked before consulting
 *  this function.
 */

static int slow_case_conversion(duk_hthread *thr,
                                duk_hbuffer_dynamic *buf,
                                int x,
                                duk_bitdecoder_ctx *bd_ctx) {
	int skip = 0;
	int n, t;
	int start_i, start_o, count;

	DUK_DDDPRINT("slow case conversion for codepoint: %d", x);

	/* range conversion with a "skip" */
	DUK_DDDPRINT("checking ranges");
	for (;;) {
		skip++;
		n = duk_bd_decode(bd_ctx, 6);
		if (n == 0x3f) {
			/* end marker */
			break;
		}
		DUK_DDDPRINT("skip=%d, n=%d", skip, n);

		while (n--) {
			start_i = duk_bd_decode(bd_ctx, 16);
			start_o = duk_bd_decode(bd_ctx, 16);
			count = duk_bd_decode(bd_ctx, 7);
			DUK_DDDPRINT("range: start_i=%d, start_o=%d, count=%d, skip=%d",
			             start_i, start_o, count, skip);

			t = x - start_i;
			if (t >= 0 && t < count * skip && (t % skip) == 0) {
				DUK_DDDPRINT("range matches input codepoint");
				x = start_o + t;
				goto single;
			}
		}
	}

	/* 1:1 conversion */
	n = duk_bd_decode(bd_ctx, 6);
	DUK_DDDPRINT("checking 1:1 conversions (count %d)", n);
	while (n--) {
		start_i = duk_bd_decode(bd_ctx, 16);
		start_o = duk_bd_decode(bd_ctx, 16);
		DUK_DDDPRINT("1:1 conversion %d -> %d", start_i, start_o);
		if (x == start_i) {
			DUK_DDDPRINT("1:1 matches input codepoint");
			x = start_o;
			goto single;
		}
	}

	/* complex, multicharacter conversion */
	n = duk_bd_decode(bd_ctx, 7);
	DUK_DDDPRINT("checking 1:n conversions (count %d)", n);
	while (n--) {
		start_i = duk_bd_decode(bd_ctx, 16);
		t = duk_bd_decode(bd_ctx, 2);
		DUK_DDDPRINT("1:n conversion %d -> %d chars", start_i, t);
		if (x == start_i) {
			DUK_DDDPRINT("1:n matches input codepoint");
			if (buf) {
				while (t--) {
					int tmp = duk_bd_decode(bd_ctx, 16);
					DUK_ASSERT(buf != NULL);
					duk_hbuffer_append_xutf8(thr, buf, tmp);
				}
			}
			return -1;
		} else {
			while (t--) {
				(void) duk_bd_decode(bd_ctx, 16);
			}
		}
	}

	/* default: no change */
	DUK_DDDPRINT("no rule matches, output is same as input");
	/* fall through */

 single:
	if (buf) {
		duk_hbuffer_append_xutf8(thr, buf, x);
	}
	return x;
}

/*
 *  Case conversion helper, with context/local sensitivity.
 *  For proper case conversion, one needs to know the character
 *  and the preceding and following characters, as well as
 *  locale/language.
 */

static int case_transform_helper(duk_hthread *thr,
                                 duk_hbuffer_dynamic *buf,
                                 int x,
                                 int prev,
                                 int next,
                                 int uppercase,
                                 int language) {
	duk_bitdecoder_ctx bd_ctx;

	/* fast path for ASCII */
	if (x < 0x80) {
		/* FIXME: context sensitive rules exist for ASCII range too.
		 * Need to add them here.
		 */

		if (uppercase) {
			if (x >= 'a' && x <= 'z') {
				x = x - 'a' + 'A';
			}
		} else {
			if (x >= 'A' && x <= 'Z') {
				x = x - 'A' + 'a';
			}
		}
		goto singlechar;
	}

	/* context and locale specific rules which cannot currently be represented
	 * in the caseconv bitstream: hardcoded rules in C
	 */
	if (uppercase) {
		/* FIXME: turkish / azeri */
	} else {
		/* final sigma context specific rule */
		if (x == 0x03a3 &&   /* U+03A3 = GREEK CAPITAL LETTER SIGMA */
		    prev >= 0 &&     /* prev is letter */
		    next < 0) {      /* next is not letter */
			/* FIXME: fix conditions */
			x = 0x03c2;
			goto singlechar;
		}

		/* FIXME: lithuanian */
		if (0 /* language == 'lt' */ &&
		    x == 0x0307) {               /* U+0307 = COMBINING DOT ABOVE */
			goto nochar;
		}

		/* FIXME: lithuanian, explicit dot rules */
		/* FIXME: turkish / azeri, lowercase rules */
	}

	/* 1:1 or special conversions, but not locale/context specific: script generated rules */
	memset(&bd_ctx, 0, sizeof(bd_ctx));
	if (uppercase) {
		bd_ctx.data = (duk_u8 *) duk_unicode_caseconv_uc;
		bd_ctx.length = (duk_u32) sizeof(duk_unicode_caseconv_uc);
	} else {
		bd_ctx.data = (duk_u8 *) duk_unicode_caseconv_lc;
		bd_ctx.length = (duk_u32) sizeof(duk_unicode_caseconv_lc);
	}
	return slow_case_conversion(thr, buf, x, &bd_ctx);

 singlechar:
	if (buf) {
		duk_hbuffer_append_xutf8(thr, buf, x);
	}
	return x;

 nochar:
	return -1;
}

/*
 *  Replace valstack top with case converted version.
 */

void duk_unicode_case_convert_string(duk_hthread *thr, int uppercase) {
	duk_context *ctx = (duk_context *) thr;
	duk_hstring *h_input;
	duk_hbuffer_dynamic *h_buf;
	duk_u8 *p, *p_start, *p_end;
	int prev, curr, next;

	h_input = duk_require_hstring(ctx, -1);
	DUK_ASSERT(h_input != NULL);

	/* FIXME: should init with a spare of at least h_input->blen? */
	duk_push_dynamic_buffer(ctx, 0);
	h_buf = (duk_hbuffer_dynamic *) duk_get_hbuffer(ctx, -1);
	DUK_ASSERT(h_buf != NULL);
	DUK_ASSERT(DUK_HBUFFER_HAS_DYNAMIC(h_buf));

	/* [ ... input buffer ] */

	p_start = (duk_u8 *) DUK_HSTRING_GET_DATA(h_input);
	p_end = p_start + DUK_HSTRING_GET_BYTELEN(h_input);
	p = p_start;

	prev = -1;
	curr = -1;
	next = -1;
	for (;;) {
		prev = curr;
		curr = next;
		next = -1;
		if (p < p_end) {
			next = (int) duk_unicode_xutf8_get_u32_checked(thr, &p, p_start, p_end);
		} else {
			/* end of input and last char has been processed */
			if (curr < 0) {
				break;
			}
		}

		/* on first round, skip */
		if (curr >= 0) {
			/* may generate any number of output codepoints */
			case_transform_helper(thr,
			                      h_buf,
			                      curr,
			                      prev,
			                      next,
			                      uppercase,
			                      0);  /* FIXME: language */
		}
	}

	duk_to_string(ctx, -1);  /* invalidates h_buf pointer */
	duk_remove(ctx, -2);
}

#ifdef DUK_USE_REGEXP_SUPPORT

/*
 *  Canonicalize() abstract operation needed for canonicalization of individual
 *  codepoints during regexp compilation and execution, see E5 Section 15.10.2.8.
 *  Note that codepoints are canonicalized one character at a time, so no context
 *  specific rules can apply.  Locale specific rules can apply, though.
 */

int duk_unicode_re_canonicalize_char(duk_hthread *thr, int x) {
	int y;

	y = case_transform_helper(thr,
	                          NULL,    /* buf */
	                          x,       /* curr char */
	                          -1,      /* prev char */
	                          -1,      /* next char */
	                          1,       /* uppercase */
	                          0);      /* FIXME: language */

	if ((y < 0) || (x >= 0x80 && y < 0x80)) {
		/* multiple codepoint conversion or non-ASCII mapped to ASCII
		 * --> leave as is.
		 */
		return x;
	}

	return y;
}

/*
 *  E5 Section 15.10.2.6 "IsWordChar" abstract operation.  Assume
 *  x < 0 for characters read outside the string.
 */

int duk_unicode_re_is_wordchar(int x) {
	/*
	 *  Note: the description in E5 Section 15.10.2.6 has a typo, it
	 *  contains 'A' twice and lacks 'a'; the intent is [0-9a-zA-Z_].
	 */
	if ((x >= '0' && x <= '9') ||
	    (x >= 'a' && x <= 'z') ||
	    (x >= 'A' && x <= 'Z') ||
	    (x == '_')) {
		return 1;
	}
	return 0;
}

/*
 *  Regexp range tables
 */

/* exposed because lexer needs these too */
duk_u16 duk_unicode_re_ranges_digit[2] = {
	(duk_u16) 0x0030, (duk_u16) 0x0039,
};
duk_u16 duk_unicode_re_ranges_white[22] = {
	(duk_u16) 0x0009, (duk_u16) 0x000D,
	(duk_u16) 0x0020, (duk_u16) 0x0020,
	(duk_u16) 0x00A0, (duk_u16) 0x00A0,
	(duk_u16) 0x1680, (duk_u16) 0x1680,
	(duk_u16) 0x180E, (duk_u16) 0x180E,
	(duk_u16) 0x2000, (duk_u16) 0x200A,
	(duk_u16) 0x2028, (duk_u16) 0x2029,
	(duk_u16) 0x202F, (duk_u16) 0x202F,
	(duk_u16) 0x205F, (duk_u16) 0x205F,
	(duk_u16) 0x3000, (duk_u16) 0x3000,
	(duk_u16) 0xFEFF, (duk_u16) 0xFEFF,
};
duk_u16 duk_unicode_re_ranges_wordchar[8] = {
	(duk_u16) 0x0030, (duk_u16) 0x0039,
	(duk_u16) 0x0041, (duk_u16) 0x005A,
	(duk_u16) 0x005F, (duk_u16) 0x005F,
	(duk_u16) 0x0061, (duk_u16) 0x007A,
};
duk_u16 duk_unicode_re_ranges_not_digit[4] = {
	(duk_u16) 0x0000, (duk_u16) 0x002F,
	(duk_u16) 0x003A, (duk_u16) 0xFFFF,
};
duk_u16 duk_unicode_re_ranges_not_white[24] = {
	(duk_u16) 0x0000, (duk_u16) 0x0008,
	(duk_u16) 0x000E, (duk_u16) 0x001F,
	(duk_u16) 0x0021, (duk_u16) 0x009F,
	(duk_u16) 0x00A1, (duk_u16) 0x167F,
	(duk_u16) 0x1681, (duk_u16) 0x180D,
	(duk_u16) 0x180F, (duk_u16) 0x1FFF,
	(duk_u16) 0x200B, (duk_u16) 0x2027,
	(duk_u16) 0x202A, (duk_u16) 0x202E,
	(duk_u16) 0x2030, (duk_u16) 0x205E,
	(duk_u16) 0x2060, (duk_u16) 0x2FFF,
	(duk_u16) 0x3001, (duk_u16) 0xFEFE,
	(duk_u16) 0xFF00, (duk_u16) 0xFFFF,
};
duk_u16 duk_unicode_re_ranges_not_wordchar[10] = {
	(duk_u16) 0x0000, (duk_u16) 0x002F,
	(duk_u16) 0x003A, (duk_u16) 0x0040,
	(duk_u16) 0x005B, (duk_u16) 0x005E,
	(duk_u16) 0x0060, (duk_u16) 0x0060,
	(duk_u16) 0x007B, (duk_u16) 0xFFFF,
};

#endif  /* DUK_USE_REGEXP_SUPPORT */

#line 1 "duk_unicode_tables.c"
/*
 *  Unicode support tables automatically generated during build.
 */

/* include removed: duk_internal.h */

/*
 *  Unicode tables containing ranges of Unicode characters in a
 *  packed format.  These tables are used to match non-ASCII
 *  characters of complex productions by resorting to a linear
 *  range-by-range comparison.  This is very slow, but is expected
 *  to be very rare in practical Ecmascript source code, and thus
 *  compactness is most important.
 *
 *  The tables are matched using uni_range_match() and the format
 *  is described in src/extract_chars.py.
 */

#ifdef DUK_USE_SOURCE_NONBMP
/* IdentifierStart production with ASCII excluded */
/* duk_unicode_identifier_start_noascii[] */
/*
 *  Automatically generated by extract_chars.py, do not edit!
 */

char duk_unicode_identifier_start_noascii[797] = {
(char)'\xf9', (char)'\xb0', (char)'\xb0', (char)'\x50', (char)'\x6f', 
(char)'\x07', (char)'\x2f', (char)'\x0f', (char)'\x2f', (char)'\xfe', 
(char)'\x0b', (char)'\xc5', (char)'\xbf', (char)'\x00', (char)'\x48', 
(char)'\x02', (char)'\x0f', (char)'\x73', (char)'\x42', (char)'\x13', 
(char)'\x39', (char)'\x02', (char)'\x22', (char)'\x02', (char)'\xf0', 
(char)'\x42', (char)'\xf4', (char)'\x32', (char)'\xf7', (char)'\xb9', 
(char)'\xf8', (char)'\xea', (char)'\xf1', (char)'\x63', (char)'\x08', 
(char)'\xf1', (char)'\x7f', (char)'\x3a', (char)'\xf0', (char)'\xb6', 
(char)'\x2f', (char)'\x1f', (char)'\xf1', (char)'\xbf', (char)'\x15', 
(char)'\x12', (char)'\xf5', (char)'\x32', (char)'\x0f', (char)'\x01', 
(char)'\x18', (char)'\x1b', (char)'\x23', (char)'\x0f', (char)'\x02', 
(char)'\x02', (char)'\xf0', (char)'\xef', (char)'\x0f', (char)'\xf4', 
(char)'\x9c', (char)'\x0f', (char)'\x0a', (char)'\xf1', (char)'\x1a', 
(char)'\x15', (char)'\x06', (char)'\xf0', (char)'\x65', (char)'\x0a', 
(char)'\x04', (char)'\x0f', (char)'\x09', (char)'\xf0', (char)'\x9f', 
(char)'\x9d', (char)'\xf2', (char)'\x64', (char)'\x0f', (char)'\x04', 
(char)'\x08', (char)'\x9f', (char)'\x01', (char)'\x62', (char)'\x66', 
(char)'\x73', (char)'\x13', (char)'\xf0', (char)'\x62', (char)'\x62', 
(char)'\x04', (char)'\x34', (char)'\x0f', (char)'\x02', (char)'\x0e', 
(char)'\x12', (char)'\x2f', (char)'\x00', (char)'\x1f', (char)'\x05', 
(char)'\x55', (char)'\x13', (char)'\xf0', (char)'\x62', (char)'\x62', 
(char)'\x12', (char)'\x12', (char)'\x1f', (char)'\x11', (char)'\x32', 
(char)'\x0f', (char)'\x05', (char)'\x2f', (char)'\x02', (char)'\x82', 
(char)'\x22', (char)'\xf0', (char)'\x62', (char)'\x62', (char)'\x12', 
(char)'\x44', (char)'\x0f', (char)'\x04', (char)'\x0f', (char)'\x01', 
(char)'\x1f', (char)'\x15', (char)'\x73', (char)'\x13', (char)'\xf0', 
(char)'\x62', (char)'\x62', (char)'\x12', (char)'\x44', (char)'\x0f', 
(char)'\x10', (char)'\x12', (char)'\x2f', (char)'\x01', (char)'\x0f', 
(char)'\x03', (char)'\x02', (char)'\x54', (char)'\x22', (char)'\x34', 
(char)'\x12', (char)'\x02', (char)'\x14', (char)'\x14', (char)'\x24', 
(char)'\xbf', (char)'\x08', (char)'\x0f', (char)'\x26', (char)'\x72', 
(char)'\x22', (char)'\xf0', (char)'\x72', (char)'\x92', (char)'\x44', 
(char)'\x0f', (char)'\x0c', (char)'\x17', (char)'\x1f', (char)'\x15', 
(char)'\x72', (char)'\x22', (char)'\xf0', (char)'\x72', (char)'\x92', 
(char)'\x44', (char)'\x0f', (char)'\x12', (char)'\x02', (char)'\x1f', 
(char)'\x01', (char)'\x1f', (char)'\x04', (char)'\x72', (char)'\x22', 
(char)'\xf1', (char)'\x93', (char)'\x0f', (char)'\x02', (char)'\x0f', 
(char)'\x03', (char)'\x1f', (char)'\x0a', (char)'\x56', (char)'\xf0', 
(char)'\x24', (char)'\xf0', (char)'\x82', (char)'\x82', (char)'\x03', 
(char)'\x6f', (char)'\x2c', (char)'\xf2', (char)'\x02', (char)'\x1d', 
(char)'\x6f', (char)'\x2c', (char)'\x12', (char)'\x03', (char)'\x12', 
(char)'\x03', (char)'\x07', (char)'\x32', (char)'\x62', (char)'\x22', 
(char)'\x02', (char)'\x03', (char)'\x12', (char)'\x32', (char)'\x1a', 
(char)'\x03', (char)'\x42', (char)'\x0f', (char)'\x07', (char)'\x1f', 
(char)'\x14', (char)'\x0f', (char)'\x31', (char)'\x72', (char)'\xf1', 
(char)'\x4f', (char)'\x0d', (char)'\x4f', (char)'\x65', (char)'\xf1', 
(char)'\xbf', (char)'\x06', (char)'\x0f', (char)'\x02', (char)'\x55', 
(char)'\x34', (char)'\x04', (char)'\x18', (char)'\x25', (char)'\xcd', 
(char)'\x0f', (char)'\x03', (char)'\xf1', (char)'\x6b', (char)'\xf1', 
(char)'\xb2', (char)'\x04', (char)'\xff', (char)'\xe0', (char)'\x3b', 
(char)'\x23', (char)'\x36', (char)'\x20', (char)'\x23', (char)'\x3f', 
(char)'\x19', (char)'\x23', (char)'\x3f', (char)'\x11', (char)'\x23', 
(char)'\x36', (char)'\x20', (char)'\x23', (char)'\x3e', (char)'\x2f', 
(char)'\x29', (char)'\x23', (char)'\x3f', (char)'\x33', (char)'\xf1', 
(char)'\x7f', (char)'\x00', (char)'\xf0', (char)'\x2f', (char)'\x45', 
(char)'\xdf', (char)'\xfe', (char)'\x15', (char)'\xe3', (char)'\xf0', 
(char)'\x12', (char)'\xf0', (char)'\xa6', (char)'\xf3', (char)'\xb4', 
(char)'\x2f', (char)'\x01', (char)'\xc2', (char)'\x3f', (char)'\x00', 
(char)'\xf0', (char)'\x2f', (char)'\x00', (char)'\xf0', (char)'\x2f', 
(char)'\x00', (char)'\xc2', (char)'\x2f', (char)'\x01', (char)'\xf2', 
(char)'\x4f', (char)'\x15', (char)'\x05', (char)'\x0f', (char)'\x35', 
(char)'\xf4', (char)'\x89', (char)'\xf1', (char)'\x92', (char)'\x06', 
(char)'\xf3', (char)'\x6b', (char)'\xf0', (char)'\xdf', (char)'\x25', 
(char)'\xf0', (char)'\xe3', (char)'\x4c', (char)'\xf1', (char)'\xcf', 
(char)'\x07', (char)'\x6f', (char)'\x2a', (char)'\xf0', (char)'\x7a', 
(char)'\xf2', (char)'\x5f', (char)'\x44', (char)'\x0f', (char)'\x4f', 
(char)'\xf1', (char)'\xff', (char)'\x03', (char)'\x6f', (char)'\x29', 
(char)'\xf0', (char)'\xee', (char)'\x1f', (char)'\x02', (char)'\xf1', 
(char)'\x6f', (char)'\x0c', (char)'\xf1', (char)'\x4f', (char)'\x1b', 
(char)'\x2b', (char)'\xf1', (char)'\x4f', (char)'\x5d', (char)'\x32', 
(char)'\x3f', (char)'\x00', (char)'\xfb', (char)'\x0f', (char)'\x32', 
(char)'\xff', (char)'\xe0', (char)'\x08', (char)'\x35', (char)'\x3f', 
(char)'\x16', (char)'\x35', (char)'\x37', (char)'\x20', (char)'\x20', 
(char)'\x20', (char)'\x2f', (char)'\x0f', (char)'\x3f', (char)'\x25', 
(char)'\x26', (char)'\x20', (char)'\x42', (char)'\x26', (char)'\x43', 
(char)'\x35', (char)'\x5c', (char)'\x62', (char)'\x26', (char)'\xf6', 
(char)'\x60', (char)'\xe0', (char)'\xf0', (char)'\x2c', (char)'\xf5', 
(char)'\x70', (char)'\x50', (char)'\x39', (char)'\x20', (char)'\x44', 
(char)'\x70', (char)'\x20', (char)'\x20', (char)'\x23', (char)'\x2a', 
(char)'\x33', (char)'\x64', (char)'\x50', (char)'\xf0', (char)'\x3f', 
(char)'\x19', (char)'\xff', (char)'\xe9', (char)'\x6b', (char)'\xf1', 
(char)'\xf2', (char)'\xf1', (char)'\xf2', (char)'\xf7', (char)'\x57', 
(char)'\x3f', (char)'\x03', (char)'\xf1', (char)'\x6b', (char)'\xf2', 
(char)'\x6a', (char)'\x0f', (char)'\x02', (char)'\xf0', (char)'\x7a', 
(char)'\x62', (char)'\x62', (char)'\x62', (char)'\x62', (char)'\x62', 
(char)'\x62', (char)'\x62', (char)'\x6f', (char)'\x42', (char)'\x0f', 
(char)'\xfe', (char)'\x0c', (char)'\x92', (char)'\xf0', (char)'\xb8', 
(char)'\x84', (char)'\x34', (char)'\x5f', (char)'\x46', (char)'\x72', 
(char)'\x2f', (char)'\x4a', (char)'\x23', (char)'\x6f', (char)'\x19', 
(char)'\x4f', (char)'\x4e', (char)'\xf0', (char)'\x3f', (char)'\x0b', 
(char)'\xf2', (char)'\x7f', (char)'\x00', (char)'\xff', (char)'\xe0', 
(char)'\xf4', (char)'\x0f', (char)'\xff', (char)'\x00', (char)'\x08', 
(char)'\xa8', (char)'\x0f', (char)'\x3c', (char)'\x0f', (char)'\xff', 
(char)'\x00', (char)'\x40', (char)'\xbe', (char)'\x0f', (char)'\x26', 
(char)'\xff', (char)'\xe3', (char)'\x7f', (char)'\xf3', (char)'\x5f', 
(char)'\x1e', (char)'\x3f', (char)'\xfd', (char)'\x4f', (char)'\x00', 
(char)'\xb1', (char)'\xf0', (char)'\x6f', (char)'\x1f', (char)'\xf0', 
(char)'\x2f', (char)'\x09', (char)'\x9f', (char)'\x40', (char)'\xf1', 
(char)'\x98', (char)'\x3f', (char)'\x57', (char)'\x33', (char)'\x21', 
(char)'\xf0', (char)'\x09', (char)'\xf4', (char)'\x27', (char)'\x22', 
(char)'\x23', (char)'\x2f', (char)'\x07', (char)'\xf0', (char)'\xff', 
(char)'\x24', (char)'\xf0', (char)'\x0f', (char)'\x22', (char)'\xf3', 
(char)'\x05', (char)'\x40', (char)'\xf0', (char)'\x0f', (char)'\x0c', 
(char)'\xbf', (char)'\x07', (char)'\xf0', (char)'\xbf', (char)'\x0d', 
(char)'\x8f', (char)'\x1f', (char)'\xf0', (char)'\xe0', (char)'\xf2', 
(char)'\x2f', (char)'\x19', (char)'\xf0', (char)'\x92', (char)'\x27', 
(char)'\xf0', (char)'\x6f', (char)'\x07', (char)'\x40', (char)'\x6f', 
(char)'\x20', (char)'\x20', (char)'\x41', (char)'\x34', (char)'\x30', 
(char)'\x20', (char)'\xf0', (char)'\xa2', (char)'\xf1', (char)'\x55', 
(char)'\x35', (char)'\x35', (char)'\xa6', (char)'\x26', (char)'\xf8', 
(char)'\x3f', (char)'\x13', (char)'\xf0', (char)'\xf0', (char)'\xff', 
(char)'\xf0', (char)'\x01', (char)'\xa9', (char)'\x60', (char)'\xdf', 
(char)'\x07', (char)'\x5f', (char)'\x21', (char)'\xff', (char)'\xf0', 
(char)'\x00', (char)'\xff', (char)'\x8f', (char)'\xfe', (char)'\x02', 
(char)'\x03', (char)'\xf2', (char)'\xe3', (char)'\xf5', (char)'\xaf', 
(char)'\x18', (char)'\x6d', (char)'\x46', (char)'\x02', (char)'\x92', 
(char)'\xc2', (char)'\x42', (char)'\x02', (char)'\x12', (char)'\x12', 
(char)'\xf5', (char)'\xcf', (char)'\x13', (char)'\xff', (char)'\xe0', 
(char)'\x5d', (char)'\xf0', (char)'\x4f', (char)'\x30', (char)'\x3f', 
(char)'\x26', (char)'\xf1', (char)'\xab', (char)'\xf6', (char)'\x64', 
(char)'\x2f', (char)'\x77', (char)'\xf1', (char)'\x6f', (char)'\x0a', 
(char)'\x7f', (char)'\x0a', (char)'\xcf', (char)'\x49', (char)'\x45', 
(char)'\x35', (char)'\x35', (char)'\x32', (char)'\xf1', (char)'\x5b', 
(char)'\x2f', (char)'\x0a', (char)'\x2f', (char)'\x03', (char)'\x21', 
(char)'\x2e', (char)'\x3d', (char)'\xf1', (char)'\x4f', (char)'\x6b', 
(char)'\xf3', (char)'\x7f', (char)'\x25', (char)'\xff', (char)'\xdf', 
(char)'\x0d', (char)'\x4f', (char)'\x21', (char)'\xf2', (char)'\x1f', 
(char)'\x0f', (char)'\xf0', (char)'\x3f', (char)'\x0b', (char)'\xf2', 
(char)'\x7f', (char)'\x0e', (char)'\x3f', (char)'\x14', (char)'\x57', 
(char)'\x24', (char)'\xf1', (char)'\xcf', (char)'\x8e', (char)'\xff', 
(char)'\xe2', (char)'\x56', (char)'\x53', (char)'\x02', (char)'\xf1', 
(char)'\xc2', (char)'\x14', (char)'\x03', (char)'\xf0', (char)'\x7f', 
(char)'\x9c', (char)'\xf0', (char)'\x6b', (char)'\xf0', (char)'\xaf', 
(char)'\xb8', (char)'\x0f', (char)'\x01', (char)'\x32', (char)'\x22', 
(char)'\xf0', (char)'\xbf', (char)'\x1e', (char)'\xf0', (char)'\xdf', 
(char)'\x75', (char)'\xf2', (char)'\x6b', (char)'\xf0', (char)'\x6b', 
(char)'\xf0', (char)'\x3f', (char)'\x7f', (char)'\xf3', (char)'\x9f', 
(char)'\xfe', (char)'\x2a', (char)'\xef', (char)'\x25', (char)'\xf3', 
(char)'\xdf', (char)'\x1d', (char)'\xff', (char)'\xee', (char)'\x44', 
(char)'\xff', (char)'\xe2', (char)'\x61', (char)'\xf8', (char)'\x3f', 
(char)'\x53', (char)'\xff', (char)'\xea', (char)'\x91', (char)'\xff', 
(char)'\xe3', (char)'\x21', (char)'\xff', (char)'\xf0', (char)'\x02', 
(char)'\x2c', (char)'\x5f', (char)'\xfe', (char)'\x12', (char)'\xbf', 
(char)'\xff', (char)'\x00', (char)'\x34', (char)'\xbb', (char)'\x1f', 
(char)'\xff', (char)'\x00', (char)'\x12', (char)'\xf2', (char)'\xf4', 
(char)'\x52', (char)'\xf3', (char)'\x72', (char)'\x13', (char)'\x03', 
(char)'\x13', (char)'\x32', (char)'\xb2', (char)'\x02', (char)'\x62', 
(char)'\xf3', (char)'\x12', (char)'\x33', (char)'\x72', (char)'\x62', 
(char)'\xf0', (char)'\xc2', (char)'\x32', (char)'\x42', (char)'\x04', 
(char)'\x62', (char)'\xff', (char)'\xe0', (char)'\x46', (char)'\x3f', 
(char)'\x09', (char)'\x2f', (char)'\x09', (char)'\x2f', (char)'\x0f', 
(char)'\x2f', (char)'\x09', (char)'\x2f', (char)'\x0f', (char)'\x2f', 
(char)'\x09', (char)'\x2f', (char)'\x0f', (char)'\x2f', (char)'\x09', 
(char)'\x2f', (char)'\x0f', (char)'\x2f', (char)'\x09', (char)'\x27', 
(char)'\xff', (char)'\xf0', (char)'\x01', (char)'\x72', (char)'\x80', 
(char)'\xff', (char)'\xf0', (char)'\x09', (char)'\x5c', (char)'\x90', 
(char)'\xf1', (char)'\xb0', (char)'\xff', (char)'\xef', (char)'\x27', 
(char)'\x0c', (char)'\x0f', (char)'\xce', (char)'\x0f', (char)'\xff', 
(char)'\x00', (char)'\x2e', (char)'\xd6', (char)'\xff', (char)'\xe1', 
(char)'\x10', (char)'\x00', 
};
#else
/* IdentifierStart production with ASCII and non-BMP excluded */
/* duk_unicode_identifier_start_noascii_bmponly[] */
/*
 *  Automatically generated by extract_chars.py, do not edit!
 */

char duk_unicode_identifier_start_noascii_bmponly[614] = {
(char)'\xf9', (char)'\xb0', (char)'\xb0', (char)'\x50', (char)'\x6f', 
(char)'\x07', (char)'\x2f', (char)'\x0f', (char)'\x2f', (char)'\xfe', 
(char)'\x0b', (char)'\xc5', (char)'\xbf', (char)'\x00', (char)'\x48', 
(char)'\x02', (char)'\x0f', (char)'\x73', (char)'\x42', (char)'\x13', 
(char)'\x39', (char)'\x02', (char)'\x22', (char)'\x02', (char)'\xf0', 
(char)'\x42', (char)'\xf4', (char)'\x32', (char)'\xf7', (char)'\xb9', 
(char)'\xf8', (char)'\xea', (char)'\xf1', (char)'\x63', (char)'\x08', 
(char)'\xf1', (char)'\x7f', (char)'\x3a', (char)'\xf0', (char)'\xb6', 
(char)'\x2f', (char)'\x1f', (char)'\xf1', (char)'\xbf', (char)'\x15', 
(char)'\x12', (char)'\xf5', (char)'\x32', (char)'\x0f', (char)'\x01', 
(char)'\x18', (char)'\x1b', (char)'\x23', (char)'\x0f', (char)'\x02', 
(char)'\x02', (char)'\xf0', (char)'\xef', (char)'\x0f', (char)'\xf4', 
(char)'\x9c', (char)'\x0f', (char)'\x0a', (char)'\xf1', (char)'\x1a', 
(char)'\x15', (char)'\x06', (char)'\xf0', (char)'\x65', (char)'\x0a', 
(char)'\x04', (char)'\x0f', (char)'\x09', (char)'\xf0', (char)'\x9f', 
(char)'\x9d', (char)'\xf2', (char)'\x64', (char)'\x0f', (char)'\x04', 
(char)'\x08', (char)'\x9f', (char)'\x01', (char)'\x62', (char)'\x66', 
(char)'\x73', (char)'\x13', (char)'\xf0', (char)'\x62', (char)'\x62', 
(char)'\x04', (char)'\x34', (char)'\x0f', (char)'\x02', (char)'\x0e', 
(char)'\x12', (char)'\x2f', (char)'\x00', (char)'\x1f', (char)'\x05', 
(char)'\x55', (char)'\x13', (char)'\xf0', (char)'\x62', (char)'\x62', 
(char)'\x12', (char)'\x12', (char)'\x1f', (char)'\x11', (char)'\x32', 
(char)'\x0f', (char)'\x05', (char)'\x2f', (char)'\x02', (char)'\x82', 
(char)'\x22', (char)'\xf0', (char)'\x62', (char)'\x62', (char)'\x12', 
(char)'\x44', (char)'\x0f', (char)'\x04', (char)'\x0f', (char)'\x01', 
(char)'\x1f', (char)'\x15', (char)'\x73', (char)'\x13', (char)'\xf0', 
(char)'\x62', (char)'\x62', (char)'\x12', (char)'\x44', (char)'\x0f', 
(char)'\x10', (char)'\x12', (char)'\x2f', (char)'\x01', (char)'\x0f', 
(char)'\x03', (char)'\x02', (char)'\x54', (char)'\x22', (char)'\x34', 
(char)'\x12', (char)'\x02', (char)'\x14', (char)'\x14', (char)'\x24', 
(char)'\xbf', (char)'\x08', (char)'\x0f', (char)'\x26', (char)'\x72', 
(char)'\x22', (char)'\xf0', (char)'\x72', (char)'\x92', (char)'\x44', 
(char)'\x0f', (char)'\x0c', (char)'\x17', (char)'\x1f', (char)'\x15', 
(char)'\x72', (char)'\x22', (char)'\xf0', (char)'\x72', (char)'\x92', 
(char)'\x44', (char)'\x0f', (char)'\x12', (char)'\x02', (char)'\x1f', 
(char)'\x01', (char)'\x1f', (char)'\x04', (char)'\x72', (char)'\x22', 
(char)'\xf1', (char)'\x93', (char)'\x0f', (char)'\x02', (char)'\x0f', 
(char)'\x03', (char)'\x1f', (char)'\x0a', (char)'\x56', (char)'\xf0', 
(char)'\x24', (char)'\xf0', (char)'\x82', (char)'\x82', (char)'\x03', 
(char)'\x6f', (char)'\x2c', (char)'\xf2', (char)'\x02', (char)'\x1d', 
(char)'\x6f', (char)'\x2c', (char)'\x12', (char)'\x03', (char)'\x12', 
(char)'\x03', (char)'\x07', (char)'\x32', (char)'\x62', (char)'\x22', 
(char)'\x02', (char)'\x03', (char)'\x12', (char)'\x32', (char)'\x1a', 
(char)'\x03', (char)'\x42', (char)'\x0f', (char)'\x07', (char)'\x1f', 
(char)'\x14', (char)'\x0f', (char)'\x31', (char)'\x72', (char)'\xf1', 
(char)'\x4f', (char)'\x0d', (char)'\x4f', (char)'\x65', (char)'\xf1', 
(char)'\xbf', (char)'\x06', (char)'\x0f', (char)'\x02', (char)'\x55', 
(char)'\x34', (char)'\x04', (char)'\x18', (char)'\x25', (char)'\xcd', 
(char)'\x0f', (char)'\x03', (char)'\xf1', (char)'\x6b', (char)'\xf1', 
(char)'\xb2', (char)'\x04', (char)'\xff', (char)'\xe0', (char)'\x3b', 
(char)'\x23', (char)'\x36', (char)'\x20', (char)'\x23', (char)'\x3f', 
(char)'\x19', (char)'\x23', (char)'\x3f', (char)'\x11', (char)'\x23', 
(char)'\x36', (char)'\x20', (char)'\x23', (char)'\x3e', (char)'\x2f', 
(char)'\x29', (char)'\x23', (char)'\x3f', (char)'\x33', (char)'\xf1', 
(char)'\x7f', (char)'\x00', (char)'\xf0', (char)'\x2f', (char)'\x45', 
(char)'\xdf', (char)'\xfe', (char)'\x15', (char)'\xe3', (char)'\xf0', 
(char)'\x12', (char)'\xf0', (char)'\xa6', (char)'\xf3', (char)'\xb4', 
(char)'\x2f', (char)'\x01', (char)'\xc2', (char)'\x3f', (char)'\x00', 
(char)'\xf0', (char)'\x2f', (char)'\x00', (char)'\xf0', (char)'\x2f', 
(char)'\x00', (char)'\xc2', (char)'\x2f', (char)'\x01', (char)'\xf2', 
(char)'\x4f', (char)'\x15', (char)'\x05', (char)'\x0f', (char)'\x35', 
(char)'\xf4', (char)'\x89', (char)'\xf1', (char)'\x92', (char)'\x06', 
(char)'\xf3', (char)'\x6b', (char)'\xf0', (char)'\xdf', (char)'\x25', 
(char)'\xf0', (char)'\xe3', (char)'\x4c', (char)'\xf1', (char)'\xcf', 
(char)'\x07', (char)'\x6f', (char)'\x2a', (char)'\xf0', (char)'\x7a', 
(char)'\xf2', (char)'\x5f', (char)'\x44', (char)'\x0f', (char)'\x4f', 
(char)'\xf1', (char)'\xff', (char)'\x03', (char)'\x6f', (char)'\x29', 
(char)'\xf0', (char)'\xee', (char)'\x1f', (char)'\x02', (char)'\xf1', 
(char)'\x6f', (char)'\x0c', (char)'\xf1', (char)'\x4f', (char)'\x1b', 
(char)'\x2b', (char)'\xf1', (char)'\x4f', (char)'\x5d', (char)'\x32', 
(char)'\x3f', (char)'\x00', (char)'\xfb', (char)'\x0f', (char)'\x32', 
(char)'\xff', (char)'\xe0', (char)'\x08', (char)'\x35', (char)'\x3f', 
(char)'\x16', (char)'\x35', (char)'\x37', (char)'\x20', (char)'\x20', 
(char)'\x20', (char)'\x2f', (char)'\x0f', (char)'\x3f', (char)'\x25', 
(char)'\x26', (char)'\x20', (char)'\x42', (char)'\x26', (char)'\x43', 
(char)'\x35', (char)'\x5c', (char)'\x62', (char)'\x26', (char)'\xf6', 
(char)'\x60', (char)'\xe0', (char)'\xf0', (char)'\x2c', (char)'\xf5', 
(char)'\x70', (char)'\x50', (char)'\x39', (char)'\x20', (char)'\x44', 
(char)'\x70', (char)'\x20', (char)'\x20', (char)'\x23', (char)'\x2a', 
(char)'\x33', (char)'\x64', (char)'\x50', (char)'\xf0', (char)'\x3f', 
(char)'\x19', (char)'\xff', (char)'\xe9', (char)'\x6b', (char)'\xf1', 
(char)'\xf2', (char)'\xf1', (char)'\xf2', (char)'\xf7', (char)'\x57', 
(char)'\x3f', (char)'\x03', (char)'\xf1', (char)'\x6b', (char)'\xf2', 
(char)'\x6a', (char)'\x0f', (char)'\x02', (char)'\xf0', (char)'\x7a', 
(char)'\x62', (char)'\x62', (char)'\x62', (char)'\x62', (char)'\x62', 
(char)'\x62', (char)'\x62', (char)'\x6f', (char)'\x42', (char)'\x0f', 
(char)'\xfe', (char)'\x0c', (char)'\x92', (char)'\xf0', (char)'\xb8', 
(char)'\x84', (char)'\x34', (char)'\x5f', (char)'\x46', (char)'\x72', 
(char)'\x2f', (char)'\x4a', (char)'\x23', (char)'\x6f', (char)'\x19', 
(char)'\x4f', (char)'\x4e', (char)'\xf0', (char)'\x3f', (char)'\x0b', 
(char)'\xf2', (char)'\x7f', (char)'\x00', (char)'\xff', (char)'\xe0', 
(char)'\xf4', (char)'\x0f', (char)'\xff', (char)'\x00', (char)'\x08', 
(char)'\xa8', (char)'\x0f', (char)'\x3c', (char)'\x0f', (char)'\xff', 
(char)'\x00', (char)'\x40', (char)'\xbe', (char)'\x0f', (char)'\x26', 
(char)'\xff', (char)'\xe3', (char)'\x7f', (char)'\xf3', (char)'\x5f', 
(char)'\x1e', (char)'\x3f', (char)'\xfd', (char)'\x4f', (char)'\x00', 
(char)'\xb1', (char)'\xf0', (char)'\x6f', (char)'\x1f', (char)'\xf0', 
(char)'\x2f', (char)'\x09', (char)'\x9f', (char)'\x40', (char)'\xf1', 
(char)'\x98', (char)'\x3f', (char)'\x57', (char)'\x33', (char)'\x21', 
(char)'\xf0', (char)'\x09', (char)'\xf4', (char)'\x27', (char)'\x22', 
(char)'\x23', (char)'\x2f', (char)'\x07', (char)'\xf0', (char)'\xff', 
(char)'\x24', (char)'\xf0', (char)'\x0f', (char)'\x22', (char)'\xf3', 
(char)'\x05', (char)'\x40', (char)'\xf0', (char)'\x0f', (char)'\x0c', 
(char)'\xbf', (char)'\x07', (char)'\xf0', (char)'\xbf', (char)'\x0d', 
(char)'\x8f', (char)'\x1f', (char)'\xf0', (char)'\xe0', (char)'\xf2', 
(char)'\x2f', (char)'\x19', (char)'\xf0', (char)'\x92', (char)'\x27', 
(char)'\xf0', (char)'\x6f', (char)'\x07', (char)'\x40', (char)'\x6f', 
(char)'\x20', (char)'\x20', (char)'\x41', (char)'\x34', (char)'\x30', 
(char)'\x20', (char)'\xf0', (char)'\xa2', (char)'\xf1', (char)'\x55', 
(char)'\x35', (char)'\x35', (char)'\xa6', (char)'\x26', (char)'\xf8', 
(char)'\x3f', (char)'\x13', (char)'\xf0', (char)'\xf0', (char)'\xff', 
(char)'\xf0', (char)'\x01', (char)'\xa9', (char)'\x60', (char)'\xdf', 
(char)'\x07', (char)'\x5f', (char)'\x21', (char)'\xff', (char)'\xf0', 
(char)'\x00', (char)'\xff', (char)'\x8f', (char)'\xfe', (char)'\x02', 
(char)'\x03', (char)'\xf2', (char)'\xe3', (char)'\xf5', (char)'\xaf', 
(char)'\x18', (char)'\x6d', (char)'\x46', (char)'\x02', (char)'\x92', 
(char)'\xc2', (char)'\x42', (char)'\x02', (char)'\x12', (char)'\x12', 
(char)'\xf5', (char)'\xcf', (char)'\x13', (char)'\xff', (char)'\xe0', 
(char)'\x5d', (char)'\xf0', (char)'\x4f', (char)'\x30', (char)'\x3f', 
(char)'\x26', (char)'\xf1', (char)'\xab', (char)'\xf6', (char)'\x64', 
(char)'\x2f', (char)'\x77', (char)'\xf1', (char)'\x6f', (char)'\x0a', 
(char)'\x7f', (char)'\x0a', (char)'\xcf', (char)'\x49', (char)'\x45', 
(char)'\x35', (char)'\x35', (char)'\x32', (char)'\x00', 
};
#endif

#ifdef DUK_USE_SOURCE_NONBMP
/* IdentifierPart production with IdentifierStart and ASCII excluded */
/* duk_unicode_identifier_part_minus_identifier_start_noascii[] */
/*
 *  Automatically generated by extract_chars.py, do not edit!
 */

char duk_unicode_identifier_part_minus_identifier_start_noascii[397] = {
(char)'\xff', (char)'\xe1', (char)'\xf3', (char)'\xf6', (char)'\x0f', 
(char)'\xfe', (char)'\x00', (char)'\x74', (char)'\xff', (char)'\xbf', 
(char)'\x1d', (char)'\x20', (char)'\x21', (char)'\x21', (char)'\x20', 
(char)'\xf3', (char)'\xaa', (char)'\xf2', (char)'\x2f', (char)'\x0f', 
(char)'\x70', (char)'\xf5', (char)'\x76', (char)'\x35', (char)'\x31', 
(char)'\x23', (char)'\x39', (char)'\xf0', (char)'\x90', (char)'\xf1', 
(char)'\x0f', (char)'\x0b', (char)'\xf4', (char)'\xda', (char)'\xf0', 
(char)'\x19', (char)'\xf1', (char)'\x38', (char)'\xf1', (char)'\x43', 
(char)'\x28', (char)'\x22', (char)'\x24', (char)'\xf1', (char)'\xd2', 
(char)'\xf9', (char)'\x63', (char)'\xf2', (char)'\x82', (char)'\x2f', 
(char)'\x02', (char)'\x26', (char)'\xb1', (char)'\x39', (char)'\xf0', 
(char)'\x32', (char)'\xf2', (char)'\xa0', (char)'\x26', (char)'\x31', 
(char)'\x32', (char)'\xa0', (char)'\xb1', (char)'\x39', (char)'\xf0', 
(char)'\x32', (char)'\xf2', (char)'\xa0', (char)'\x24', (char)'\x51', 
(char)'\x32', (char)'\x40', (char)'\xf0', (char)'\x6b', (char)'\x40', 
(char)'\xc2', (char)'\xf2', (char)'\xa0', (char)'\x27', (char)'\x22', 
(char)'\x22', (char)'\xf0', (char)'\x61', (char)'\x39', (char)'\xf0', 
(char)'\x32', (char)'\xf2', (char)'\xa0', (char)'\x26', (char)'\x31', 
(char)'\x32', (char)'\x91', (char)'\xb1', (char)'\x39', (char)'\xf0', 
(char)'\x40', (char)'\xf2', (char)'\xd4', (char)'\x42', (char)'\x23', 
(char)'\xa0', (char)'\xf0', (char)'\x09', (char)'\xf0', (char)'\x32', 
(char)'\xf2', (char)'\xc6', (char)'\x22', (char)'\x23', (char)'\x81', 
(char)'\xc1', (char)'\x39', (char)'\xf0', (char)'\x41', (char)'\xf2', 
(char)'\xa0', (char)'\x26', (char)'\x22', (char)'\x23', (char)'\x81', 
(char)'\xc1', (char)'\x39', (char)'\xf0', (char)'\x41', (char)'\xf2', 
(char)'\xc6', (char)'\x22', (char)'\x23', (char)'\xa0', (char)'\xb1', 
(char)'\x39', (char)'\xf0', (char)'\x41', (char)'\xf3', (char)'\x80', 
(char)'\x55', (char)'\x20', (char)'\x27', (char)'\xf0', (char)'\x41', 
(char)'\xf2', (char)'\xf0', (char)'\x36', (char)'\xd7', (char)'\x29', 
(char)'\xf4', (char)'\x90', (char)'\x35', (char)'\x21', (char)'\xc5', 
(char)'\x39', (char)'\xf3', (char)'\x01', (char)'\x79', (char)'\xc0', 
(char)'\x20', (char)'\x20', (char)'\x51', (char)'\xf2', (char)'\x3f', 
(char)'\x04', (char)'\x21', (char)'\x6a', (char)'\x2f', (char)'\x14', 
(char)'\xa0', (char)'\xf5', (char)'\x6f', (char)'\x04', (char)'\x29', 
(char)'\xd3', (char)'\x52', (char)'\x22', (char)'\x36', (char)'\x43', 
(char)'\xeb', (char)'\x2e', (char)'\xff', (char)'\xe1', (char)'\xb3', 
(char)'\x2f', (char)'\xfe', (char)'\x2a', (char)'\x62', (char)'\xf0', 
(char)'\xf2', (char)'\xf0', (char)'\xf1', (char)'\xf1', (char)'\x01', 
(char)'\xf3', (char)'\x4f', (char)'\x0e', (char)'\xa0', (char)'\x39', 
(char)'\xf1', (char)'\x32', (char)'\x39', (char)'\xf8', (char)'\x10', 
(char)'\xf6', (char)'\x8b', (char)'\x5b', (char)'\xb9', (char)'\xf5', 
(char)'\x2f', (char)'\x01', (char)'\x81', (char)'\x79', (char)'\xf2', 
(char)'\xf4', (char)'\xf2', (char)'\xb9', (char)'\x2f', (char)'\x0d', 
(char)'\x3a', (char)'\x79', (char)'\xf5', (char)'\x84', (char)'\xf2', 
(char)'\x1f', (char)'\x01', (char)'\xc9', (char)'\xf0', (char)'\x38', 
(char)'\xd2', (char)'\xf1', (char)'\x09', (char)'\x69', (char)'\xf1', 
(char)'\xed', (char)'\xf2', (char)'\x2f', (char)'\x04', (char)'\x99', 
(char)'\x79', (char)'\xf6', (char)'\x82', (char)'\x2f', (char)'\x05', 
(char)'\x50', (char)'\x50', (char)'\xfb', (char)'\xff', (char)'\x17', 
(char)'\xf0', (char)'\x73', (char)'\xff', (char)'\xe1', (char)'\x00', 
(char)'\x1f', (char)'\x23', (char)'\x1f', (char)'\x05', (char)'\x0f', 
(char)'\x6d', (char)'\xc5', (char)'\x04', (char)'\xbf', (char)'\xfe', 
(char)'\xaf', (char)'\x22', (char)'\xf7', (char)'\xf0', (char)'\xf5', 
(char)'\x2f', (char)'\x10', (char)'\xff', (char)'\xe1', (char)'\x1e', 
(char)'\x5f', (char)'\x5b', (char)'\x1f', (char)'\xff', (char)'\x00', 
(char)'\x64', (char)'\x79', (char)'\x9f', (char)'\x37', (char)'\x0d', 
(char)'\x1f', (char)'\x64', (char)'\x1f', (char)'\xfe', (char)'\x00', 
(char)'\x40', (char)'\x40', (char)'\x50', (char)'\xf0', (char)'\x94', 
(char)'\xf4', (char)'\xa1', (char)'\xf2', (char)'\x4f', (char)'\x01', 
(char)'\xc9', (char)'\x7f', (char)'\x02', (char)'\xf0', (char)'\x09', 
(char)'\xf0', (char)'\xe7', (char)'\xf0', (char)'\xbc', (char)'\xf1', 
(char)'\xe3', (char)'\xf2', (char)'\x1d', (char)'\xf0', (char)'\x19', 
(char)'\xf4', (char)'\x1d', (char)'\xd0', (char)'\x91', (char)'\x39', 
(char)'\xf1', (char)'\x30', (char)'\xf2', (char)'\x60', (char)'\x22', 
(char)'\x31', (char)'\x61', (char)'\x20', (char)'\xff', (char)'\xe0', 
(char)'\x15', (char)'\x72', (char)'\x13', (char)'\x9f', (char)'\xff', 
(char)'\x00', (char)'\x3e', (char)'\x18', (char)'\x0f', (char)'\xfe', 
(char)'\x1d', (char)'\x5f', (char)'\x00', (char)'\xf0', (char)'\x26', 
(char)'\xd1', (char)'\xf0', (char)'\xa2', (char)'\xfb', (char)'\x29', 
(char)'\xf1', (char)'\x70', (char)'\xff', (char)'\xe1', (char)'\xb1', 
(char)'\x0f', (char)'\xfe', (char)'\x19', (char)'\x69', (char)'\xff', 
(char)'\xe4', (char)'\x4b', (char)'\x22', (char)'\x16', (char)'\x3f', 
(char)'\x1a', (char)'\x25', (char)'\x0f', (char)'\xfe', (char)'\x4b', 
(char)'\x42', (char)'\xf2', (char)'\x7e', (char)'\xf1', (char)'\x19', 
(char)'\xf0', (char)'\x22', (char)'\xf1', (char)'\xfa', (char)'\xff', 
(char)'\xf0', (char)'\x0a', (char)'\xf9', (char)'\xe4', (char)'\x45', 
(char)'\x97', (char)'\x36', (char)'\xf1', (char)'\x03', (char)'\xf8', 
(char)'\x62', (char)'\xff', (char)'\xe4', (char)'\x7d', (char)'\xf2', 
(char)'\x2f', (char)'\xff', (char)'\x0c', (char)'\x17', (char)'\xf4', 
(char)'\xfe', (char)'\x00', 
};
#else
/* IdentifierPart production with IdentifierStart, ASCII, and non-BMP excluded */
/* duk_unicode_identifier_part_minus_identifier_start_noascii_bmponly[] */
/*
 *  Automatically generated by extract_chars.py, do not edit!
 */

char duk_unicode_identifier_part_minus_identifier_start_noascii_bmponly[348] = {
(char)'\xff', (char)'\xe1', (char)'\xf3', (char)'\xf6', (char)'\x0f', 
(char)'\xfe', (char)'\x00', (char)'\x74', (char)'\xff', (char)'\xbf', 
(char)'\x1d', (char)'\x20', (char)'\x21', (char)'\x21', (char)'\x20', 
(char)'\xf3', (char)'\xaa', (char)'\xf2', (char)'\x2f', (char)'\x0f', 
(char)'\x70', (char)'\xf5', (char)'\x76', (char)'\x35', (char)'\x31', 
(char)'\x23', (char)'\x39', (char)'\xf0', (char)'\x90', (char)'\xf1', 
(char)'\x0f', (char)'\x0b', (char)'\xf4', (char)'\xda', (char)'\xf0', 
(char)'\x19', (char)'\xf1', (char)'\x38', (char)'\xf1', (char)'\x43', 
(char)'\x28', (char)'\x22', (char)'\x24', (char)'\xf1', (char)'\xd2', 
(char)'\xf9', (char)'\x63', (char)'\xf2', (char)'\x82', (char)'\x2f', 
(char)'\x02', (char)'\x26', (char)'\xb1', (char)'\x39', (char)'\xf0', 
(char)'\x32', (char)'\xf2', (char)'\xa0', (char)'\x26', (char)'\x31', 
(char)'\x32', (char)'\xa0', (char)'\xb1', (char)'\x39', (char)'\xf0', 
(char)'\x32', (char)'\xf2', (char)'\xa0', (char)'\x24', (char)'\x51', 
(char)'\x32', (char)'\x40', (char)'\xf0', (char)'\x6b', (char)'\x40', 
(char)'\xc2', (char)'\xf2', (char)'\xa0', (char)'\x27', (char)'\x22', 
(char)'\x22', (char)'\xf0', (char)'\x61', (char)'\x39', (char)'\xf0', 
(char)'\x32', (char)'\xf2', (char)'\xa0', (char)'\x26', (char)'\x31', 
(char)'\x32', (char)'\x91', (char)'\xb1', (char)'\x39', (char)'\xf0', 
(char)'\x40', (char)'\xf2', (char)'\xd4', (char)'\x42', (char)'\x23', 
(char)'\xa0', (char)'\xf0', (char)'\x09', (char)'\xf0', (char)'\x32', 
(char)'\xf2', (char)'\xc6', (char)'\x22', (char)'\x23', (char)'\x81', 
(char)'\xc1', (char)'\x39', (char)'\xf0', (char)'\x41', (char)'\xf2', 
(char)'\xa0', (char)'\x26', (char)'\x22', (char)'\x23', (char)'\x81', 
(char)'\xc1', (char)'\x39', (char)'\xf0', (char)'\x41', (char)'\xf2', 
(char)'\xc6', (char)'\x22', (char)'\x23', (char)'\xa0', (char)'\xb1', 
(char)'\x39', (char)'\xf0', (char)'\x41', (char)'\xf3', (char)'\x80', 
(char)'\x55', (char)'\x20', (char)'\x27', (char)'\xf0', (char)'\x41', 
(char)'\xf2', (char)'\xf0', (char)'\x36', (char)'\xd7', (char)'\x29', 
(char)'\xf4', (char)'\x90', (char)'\x35', (char)'\x21', (char)'\xc5', 
(char)'\x39', (char)'\xf3', (char)'\x01', (char)'\x79', (char)'\xc0', 
(char)'\x20', (char)'\x20', (char)'\x51', (char)'\xf2', (char)'\x3f', 
(char)'\x04', (char)'\x21', (char)'\x6a', (char)'\x2f', (char)'\x14', 
(char)'\xa0', (char)'\xf5', (char)'\x6f', (char)'\x04', (char)'\x29', 
(char)'\xd3', (char)'\x52', (char)'\x22', (char)'\x36', (char)'\x43', 
(char)'\xeb', (char)'\x2e', (char)'\xff', (char)'\xe1', (char)'\xb3', 
(char)'\x2f', (char)'\xfe', (char)'\x2a', (char)'\x62', (char)'\xf0', 
(char)'\xf2', (char)'\xf0', (char)'\xf1', (char)'\xf1', (char)'\x01', 
(char)'\xf3', (char)'\x4f', (char)'\x0e', (char)'\xa0', (char)'\x39', 
(char)'\xf1', (char)'\x32', (char)'\x39', (char)'\xf8', (char)'\x10', 
(char)'\xf6', (char)'\x8b', (char)'\x5b', (char)'\xb9', (char)'\xf5', 
(char)'\x2f', (char)'\x01', (char)'\x81', (char)'\x79', (char)'\xf2', 
(char)'\xf4', (char)'\xf2', (char)'\xb9', (char)'\x2f', (char)'\x0d', 
(char)'\x3a', (char)'\x79', (char)'\xf5', (char)'\x84', (char)'\xf2', 
(char)'\x1f', (char)'\x01', (char)'\xc9', (char)'\xf0', (char)'\x38', 
(char)'\xd2', (char)'\xf1', (char)'\x09', (char)'\x69', (char)'\xf1', 
(char)'\xed', (char)'\xf2', (char)'\x2f', (char)'\x04', (char)'\x99', 
(char)'\x79', (char)'\xf6', (char)'\x82', (char)'\x2f', (char)'\x05', 
(char)'\x50', (char)'\x50', (char)'\xfb', (char)'\xff', (char)'\x17', 
(char)'\xf0', (char)'\x73', (char)'\xff', (char)'\xe1', (char)'\x00', 
(char)'\x1f', (char)'\x23', (char)'\x1f', (char)'\x05', (char)'\x0f', 
(char)'\x6d', (char)'\xc5', (char)'\x04', (char)'\xbf', (char)'\xfe', 
(char)'\xaf', (char)'\x22', (char)'\xf7', (char)'\xf0', (char)'\xf5', 
(char)'\x2f', (char)'\x10', (char)'\xff', (char)'\xe1', (char)'\x1e', 
(char)'\x5f', (char)'\x5b', (char)'\x1f', (char)'\xff', (char)'\x00', 
(char)'\x64', (char)'\x79', (char)'\x9f', (char)'\x37', (char)'\x0d', 
(char)'\x1f', (char)'\x64', (char)'\x1f', (char)'\xfe', (char)'\x00', 
(char)'\x40', (char)'\x40', (char)'\x50', (char)'\xf0', (char)'\x94', 
(char)'\xf4', (char)'\xa1', (char)'\xf2', (char)'\x4f', (char)'\x01', 
(char)'\xc9', (char)'\x7f', (char)'\x02', (char)'\xf0', (char)'\x09', 
(char)'\xf0', (char)'\xe7', (char)'\xf0', (char)'\xbc', (char)'\xf1', 
(char)'\xe3', (char)'\xf2', (char)'\x1d', (char)'\xf0', (char)'\x19', 
(char)'\xf4', (char)'\x1d', (char)'\xd0', (char)'\x91', (char)'\x39', 
(char)'\xf1', (char)'\x30', (char)'\xf2', (char)'\x60', (char)'\x22', 
(char)'\x31', (char)'\x61', (char)'\x20', (char)'\xff', (char)'\xe0', 
(char)'\x15', (char)'\x72', (char)'\x13', (char)'\x9f', (char)'\xff', 
(char)'\x00', (char)'\x3e', (char)'\x18', (char)'\x0f', (char)'\xfe', 
(char)'\x1d', (char)'\x5f', (char)'\x00', (char)'\xf0', (char)'\x26', 
(char)'\xd1', (char)'\xf0', (char)'\xa2', (char)'\xfb', (char)'\x29', 
(char)'\xf1', (char)'\x70', (char)'\x00', 
};
#endif

/*
 *  Case conversion tables generated using src/extract_caseconv.py.
 */

/* duk_unicode_caseconv_uc[] */
/* duk_unicode_caseconv_lc[] */

/*
 *  Automatically generated by extract_caseconv.py, do not edit!
 */

char duk_unicode_caseconv_uc[1288] = {
(char)'\x84', (char)'\x03', (char)'\x80', (char)'\x03', (char)'\x00', 
(char)'\xb8', (char)'\x07', (char)'\xc0', (char)'\x06', (char)'\xc0', 
(char)'\x70', (char)'\x23', (char)'\xf2', (char)'\xc7', (char)'\xe0', 
(char)'\x40', (char)'\x4a', (char)'\xc0', (char)'\x31', (char)'\x20', 
(char)'\x80', (char)'\xa2', (char)'\x80', (char)'\x6c', (char)'\x41', 
(char)'\x01', (char)'\xbd', (char)'\x81', (char)'\xfe', (char)'\x83', 
(char)'\x03', (char)'\xad', (char)'\x03', (char)'\x88', (char)'\x06', 
(char)'\x07', (char)'\x62', (char)'\x07', (char)'\x22', (char)'\x44', 
(char)'\x0f', (char)'\x0c', (char)'\x0e', (char)'\x8c', (char)'\x48', 
(char)'\x1e', (char)'\x68', (char)'\x1c', (char)'\x70', (char)'\x20', 
(char)'\x43', (char)'\x00', (char)'\x41', (char)'\x04', (char)'\x00', 
(char)'\x8a', (char)'\x00', (char)'\x80', (char)'\x04', (char)'\x01', 
(char)'\x58', (char)'\x41', (char)'\x4c', (char)'\x53', (char)'\x0f', 
(char)'\x80', (char)'\x0f', (char)'\x84', (char)'\x08', (char)'\x1f', 
(char)'\x10', (char)'\x1f', (char)'\x18', (char)'\x0c', (char)'\x3e', 
(char)'\x40', (char)'\x3e', (char)'\x50', (char)'\x20', (char)'\x7c', 
(char)'\xc0', (char)'\x7c', (char)'\xe0', (char)'\x40', (char)'\xfa', 
(char)'\x00', (char)'\xfa', (char)'\x40', (char)'\x61', (char)'\xf6', 
(char)'\x01', (char)'\xf6', (char)'\x81', (char)'\x03', (char)'\xee', 
(char)'\x03', (char)'\xf7', (char)'\x40', (char)'\x87', (char)'\xdc', 
(char)'\x87', (char)'\xf2', (char)'\x02', (char)'\x0f', (char)'\xbb', 
(char)'\x0f', (char)'\xed', (char)'\x02', (char)'\x1f', (char)'\x78', 
(char)'\x1f', (char)'\xf8', (char)'\x04', (char)'\x3e', (char)'\xf4', 
(char)'\x3f', (char)'\xd4', (char)'\x08', (char)'\x7d', (char)'\xf0', 
(char)'\x7f', (char)'\xe8', (char)'\x10', (char)'\xfd', (char)'\x80', 
(char)'\xfd', (char)'\xc0', (char)'\x21', (char)'\xfd', (char)'\x01', 
(char)'\xfd', (char)'\x80', (char)'\x43', (char)'\xfc', (char)'\x03', 
(char)'\xfd', (char)'\x00', (char)'\x88', (char)'\x5c', (char)'\x08', 
(char)'\x58', (char)'\x08', (char)'\x12', (char)'\x68', (char)'\x12', 
(char)'\x5b', (char)'\x1a', (char)'\x2c', (char)'\x30', (char)'\x2c', 
(char)'\x00', (char)'\x5e', (char)'\x5a', (char)'\x00', (char)'\x21', 
(char)'\x40', (char)'\x9b', (char)'\xfd', (char)'\x07', (char)'\xfc', 
(char)'\x84', (char)'\xd4', (char)'\x00', (char)'\x20', (char)'\x20', 
(char)'\x20', (char)'\x06', (char)'\x00', (char)'\x4c', (char)'\xc0', 
(char)'\x4c', (char)'\x81', (char)'\x80', (char)'\x9d', (char)'\x00', 
(char)'\x9c', (char)'\x88', (char)'\x01', (char)'\x4b', (char)'\x01', 
(char)'\x4a', (char)'\x2e', (char)'\x02', (char)'\xf4', (char)'\x02', 
(char)'\xf2', (char)'\x0c', (char)'\x06', (char)'\x0c', (char)'\x06', 
(char)'\x08', (char)'\x10', (char)'\x0d', (char)'\x08', (char)'\x0d', 
(char)'\x00', (char)'\x30', (char)'\x1b', (char)'\x40', (char)'\x1b', 
(char)'\x30', (char)'\x40', (char)'\x39', (char)'\xc0', (char)'\x39', 
(char)'\xa2', (char)'\x00', (char)'\x77', (char)'\xc0', (char)'\x77', 
(char)'\x84', (char)'\x80', (char)'\xfc', (char)'\x80', (char)'\xfc', 
(char)'\x14', (char)'\x02', (char)'\x23', (char)'\x02', (char)'\x22', 
(char)'\x12', (char)'\x04', (char)'\x8e', (char)'\x04', (char)'\x8c', 
(char)'\x14', (char)'\x0d', (char)'\xc4', (char)'\x0d', (char)'\xc0', 
(char)'\x10', (char)'\x1e', (char)'\xc8', (char)'\x1e', (char)'\xc0', 
(char)'\xc0', (char)'\x46', (char)'\x10', (char)'\x46', (char)'\x02', 
(char)'\x20', (char)'\x91', (char)'\x60', (char)'\x91', (char)'\x46', 
(char)'\xc1', (char)'\x30', (char)'\x81', (char)'\x30', (char)'\x43', 
(char)'\x82', (char)'\x68', (char)'\x82', (char)'\x68', (char)'\x2c', 
(char)'\x1e', (char)'\x01', (char)'\x1e', (char)'\x00', (char)'\x96', 
(char)'\x3d', (char)'\x42', (char)'\x3d', (char)'\x40', (char)'\xc0', 
(char)'\x7d', (char)'\x44', (char)'\x7d', (char)'\x64', (char)'\x21', 
(char)'\x63', (char)'\x41', (char)'\x63', (char)'\x38', (char)'\x32', 
(char)'\xc8', (char)'\x12', (char)'\xc8', (char)'\x06', (char)'\x45', 
(char)'\x9d', (char)'\x85', (char)'\x9d', (char)'\x60', (char)'\xa9', 
(char)'\x90', (char)'\x69', (char)'\x90', (char)'\x0b', (char)'\xd3', 
(char)'\x40', (char)'\xd3', (char)'\x40', (char)'\x0c', (char)'\xa7', 
(char)'\x23', (char)'\xa7', (char)'\x22', (char)'\x0f', (char)'\x4e', 
(char)'\x67', (char)'\x4e', (char)'\x64', (char)'\x7e', (char)'\x9d', 
(char)'\xea', (char)'\x9d', (char)'\xe4', (char)'\x15', (char)'\x3b', 
(char)'\xfd', (char)'\x3b', (char)'\xf0', (char)'\x5a', (char)'\x7a', 
(char)'\x1a', (char)'\x7a', (char)'\x00', (char)'\xa3', (char)'\x80', 
(char)'\xd6', (char)'\x80', (char)'\xd6', (char)'\x02', (char)'\x01', 
(char)'\xc5', (char)'\x01', (char)'\xc4', (char)'\x06', (char)'\x03', 
(char)'\x8c', (char)'\x03', (char)'\x88', (char)'\x0c', (char)'\x07', 
(char)'\xc8', (char)'\x07', (char)'\xc4', (char)'\x10', (char)'\x14', 
(char)'\x00', (char)'\x0d', (char)'\x30', (char)'\x20', (char)'\x3f', 
(char)'\x80', (char)'\x3f', (char)'\x70', (char)'\x45', (char)'\x8e', 
(char)'\x65', (char)'\x8e', (char)'\x40', (char)'\x82', (char)'\x01', 
(char)'\x88', (char)'\x01', (char)'\x87', (char)'\x04', (char)'\x03', 
(char)'\x72', (char)'\x03', (char)'\x70', (char)'\x08', (char)'\x1a', 
(char)'\x78', (char)'\xca', (char)'\x78', (char)'\xb0', (char)'\x41', 
(char)'\x01', (char)'\x1e', (char)'\x01', (char)'\x1d', (char)'\x82', 
(char)'\x02', (char)'\x69', (char)'\x01', (char)'\x96', (char)'\x05', 
(char)'\xff', (char)'\x60', (char)'\x16', (char)'\xa0', (char)'\x73', 
(char)'\x80', (char)'\x1f', (char)'\xe0', (char)'\x2f', (char)'\x00', 
(char)'\x26', (char)'\x20', (char)'\x09', (char)'\x20', (char)'\x2f', 
(char)'\xe0', (char)'\x0a', (char)'\x60', (char)'\x30', (char)'\x00', 
(char)'\x48', (char)'\x60', (char)'\x32', (char)'\x40', (char)'\x32', 
(char)'\x20', (char)'\x32', (char)'\xa0', (char)'\x3e', (char)'\xc0', 
(char)'\x33', (char)'\x20', (char)'\x33', (char)'\x00', (char)'\x33', 
(char)'\x40', (char)'\x47', (char)'\xa0', (char)'\x33', (char)'\xc0', 
(char)'\x44', (char)'\x00', (char)'\x35', (char)'\x00', (char)'\x34', 
(char)'\xe0', (char)'\x37', (char)'\xe0', (char)'\x3e', (char)'\xe0', 
(char)'\x3b', (char)'\xa0', (char)'\x31', (char)'\xc0', (char)'\x3e', 
(char)'\x60', (char)'\x3e', (char)'\x20', (char)'\x4a', (char)'\x05', 
(char)'\x8d', (char)'\xe0', (char)'\x4a', (char)'\x25', (char)'\x8d', 
(char)'\xa0', (char)'\x4a', (char)'\x45', (char)'\x8e', (char)'\x00', 
(char)'\x4a', (char)'\x60', (char)'\x30', (char)'\x20', (char)'\x4a', 
(char)'\x80', (char)'\x30', (char)'\xc0', (char)'\x4b', (char)'\x20', 
(char)'\x31', (char)'\xe0', (char)'\x4b', (char)'\x60', (char)'\x32', 
(char)'\x00', (char)'\x4c', (char)'\x00', (char)'\x32', (char)'\x60', 
(char)'\x4c', (char)'\x60', (char)'\x32', (char)'\x80', (char)'\x4c', 
(char)'\xb4', (char)'\xf1', (char)'\xa0', (char)'\x4d', (char)'\x00', 
(char)'\x32', (char)'\xe0', (char)'\x4d', (char)'\x65', (char)'\x8c', 
(char)'\x40', (char)'\x4e', (char)'\x25', (char)'\x8d', (char)'\xc0', 
(char)'\x4e', (char)'\x40', (char)'\x33', (char)'\xa0', (char)'\x4e', 
(char)'\xa0', (char)'\x33', (char)'\xe0', (char)'\x4f', (char)'\xa5', 
(char)'\x8c', (char)'\x80', (char)'\x51', (char)'\x00', (char)'\x35', 
(char)'\xc0', (char)'\x51', (char)'\x20', (char)'\x48', (char)'\x80', 
(char)'\x51', (char)'\x80', (char)'\x48', (char)'\xa0', (char)'\x52', 
(char)'\x40', (char)'\x36', (char)'\xe0', (char)'\x68', (char)'\xa0', 
(char)'\x73', (char)'\x20', (char)'\x6e', (char)'\xe0', (char)'\x6e', 
(char)'\xc0', (char)'\x75', (char)'\x80', (char)'\x70', (char)'\xc0', 
(char)'\x78', (char)'\x40', (char)'\x74', (char)'\x60', (char)'\x79', 
(char)'\x80', (char)'\x71', (char)'\x80', (char)'\x7a', (char)'\x00', 
(char)'\x72', (char)'\x40', (char)'\x7a', (char)'\x20', (char)'\x73', 
(char)'\x00', (char)'\x7a', (char)'\xa0', (char)'\x74', (char)'\xc0', 
(char)'\x7a', (char)'\xc0', (char)'\x74', (char)'\x00', (char)'\x7a', 
(char)'\xe0', (char)'\x79', (char)'\xe0', (char)'\x7e', (char)'\x00', 
(char)'\x73', (char)'\x40', (char)'\x7e', (char)'\x20', (char)'\x74', 
(char)'\x20', (char)'\x7e', (char)'\x40', (char)'\x7f', (char)'\x20', 
(char)'\x7e', (char)'\xa0', (char)'\x72', (char)'\xa0', (char)'\x99', 
(char)'\xe0', (char)'\x98', (char)'\x03', (char)'\xaf', (char)'\x34', 
(char)'\xef', (char)'\xa3', (char)'\xaf', (char)'\xa5', (char)'\x8c', 
(char)'\x63', (char)'\xd3', (char)'\x63', (char)'\xcc', (char)'\x03', 
(char)'\xf7', (char)'\xc0', (char)'\x73', (char)'\x23', (char)'\xfc', 
(char)'\xa3', (char)'\xfd', (char)'\x84', (char)'\x29', (char)'\xc4', 
(char)'\x26', (char)'\x44', (char)'\x30', (char)'\x84', (char)'\x30', 
(char)'\x65', (char)'\x8c', (char)'\x25', (char)'\x8c', (char)'\x05', 
(char)'\x8c', (char)'\xa0', (char)'\x47', (char)'\x45', (char)'\x8c', 
(char)'\xc0', (char)'\x47', (char)'\xd9', (char)'\x80', (char)'\x37', 
(char)'\xe0', (char)'\x05', (char)'\x30', (char)'\x05', (char)'\x30', 
(char)'\x14', (char)'\x98', (char)'\x0a', (char)'\xf0', (char)'\x01', 
(char)'\x38', (char)'\x07', (char)'\xc2', (char)'\x00', (char)'\x4a', 
(char)'\x03', (char)'\x0c', (char)'\x03', (char)'\x90', (char)'\xc0', 
(char)'\xe6', (char)'\x40', (char)'\xc2', (char)'\x00', (char)'\xc0', 
(char)'\x40', (char)'\xec', (char)'\x30', (char)'\x3a', (char)'\x50', 
(char)'\x30', (char)'\x80', (char)'\x30', (char)'\x10', (char)'\x58', 
(char)'\x78', (char)'\x14', (char)'\xd4', (char)'\x15', (char)'\x48', 
(char)'\x7a', (char)'\x5a', (char)'\x00', (char)'\x48', (char)'\x03', 
(char)'\x31', (char)'\x1e', (char)'\x97', (char)'\x80', (char)'\x15', 
(char)'\x00', (char)'\xc2', (char)'\x07', (char)'\xa6', (char)'\x20', 
(char)'\x05', (char)'\x70', (char)'\x30', (char)'\xa1', (char)'\xe9', 
(char)'\x98', (char)'\x01', (char)'\x64', (char)'\x0c', (char)'\x28', 
(char)'\x7a', (char)'\x6a', (char)'\x00', (char)'\x41', (char)'\x02', 
(char)'\xbe', (char)'\x1f', (char)'\x50', (char)'\x80', (char)'\xe9', 
(char)'\x40', (char)'\xc4', (char)'\xc7', (char)'\xd4', (char)'\xb0', 
(char)'\x3a', (char)'\x50', (char)'\x31', (char)'\x30', (char)'\x30', 
(char)'\x01', (char)'\xf5', (char)'\x4c', (char)'\x0e', (char)'\x94', 
(char)'\x0c', (char)'\x4c', (char)'\x0c', (char)'\x04', (char)'\x7d', 
(char)'\x5b', (char)'\x03', (char)'\xa5', (char)'\x03', (char)'\x13', 
(char)'\x03', (char)'\x42', (char)'\x1f', (char)'\x80', (char)'\x87', 
(char)'\xc2', (char)'\x00', (char)'\xe6', (char)'\x47', (char)'\xe0', 
(char)'\x61', (char)'\xf0', (char)'\x90', (char)'\x39', (char)'\x91', 
(char)'\xf8', (char)'\x28', (char)'\x7c', (char)'\x28', (char)'\x0e', 
(char)'\x64', (char)'\x7e', (char)'\x0e', (char)'\x1f', (char)'\x0b', 
(char)'\x03', (char)'\x99', (char)'\x1f', (char)'\x84', (char)'\x87', 
(char)'\xc3', (char)'\x00', (char)'\xe6', (char)'\x47', (char)'\xe1', 
(char)'\x61', (char)'\xf0', (char)'\xd0', (char)'\x39', (char)'\x91', 
(char)'\xf8', (char)'\x68', (char)'\x7c', (char)'\x38', (char)'\x0e', 
(char)'\x64', (char)'\x7e', (char)'\x1e', (char)'\x1f', (char)'\x0f', 
(char)'\x03', (char)'\x99', (char)'\x1f', (char)'\x88', (char)'\x87', 
(char)'\xc2', (char)'\x00', (char)'\xe6', (char)'\x47', (char)'\xe2', 
(char)'\x61', (char)'\xf0', (char)'\x90', (char)'\x39', (char)'\x91', 
(char)'\xf8', (char)'\xa8', (char)'\x7c', (char)'\x28', (char)'\x0e', 
(char)'\x64', (char)'\x7e', (char)'\x2e', (char)'\x1f', (char)'\x0b', 
(char)'\x03', (char)'\x99', (char)'\x1f', (char)'\x8c', (char)'\x87', 
(char)'\xc3', (char)'\x00', (char)'\xe6', (char)'\x47', (char)'\xe3', 
(char)'\x61', (char)'\xf0', (char)'\xd0', (char)'\x39', (char)'\x91', 
(char)'\xf8', (char)'\xe8', (char)'\x7c', (char)'\x38', (char)'\x0e', 
(char)'\x64', (char)'\x7e', (char)'\x3e', (char)'\x1f', (char)'\x0f', 
(char)'\x03', (char)'\x99', (char)'\x1f', (char)'\x90', (char)'\x87', 
(char)'\xca', (char)'\x00', (char)'\xe6', (char)'\x47', (char)'\xe4', 
(char)'\x61', (char)'\xf2', (char)'\x90', (char)'\x39', (char)'\x91', 
(char)'\xf9', (char)'\x28', (char)'\x7c', (char)'\xa8', (char)'\x0e', 
(char)'\x64', (char)'\x7e', (char)'\x4e', (char)'\x1f', (char)'\x2b', 
(char)'\x03', (char)'\x99', (char)'\x1f', (char)'\x94', (char)'\x87', 
(char)'\xcb', (char)'\x00', (char)'\xe6', (char)'\x47', (char)'\xe5', 
(char)'\x61', (char)'\xf2', (char)'\xd0', (char)'\x39', (char)'\x91', 
(char)'\xf9', (char)'\x68', (char)'\x7c', (char)'\xb8', (char)'\x0e', 
(char)'\x64', (char)'\x7e', (char)'\x5e', (char)'\x1f', (char)'\x2f', 
(char)'\x03', (char)'\x99', (char)'\x1f', (char)'\x98', (char)'\x87', 
(char)'\xca', (char)'\x00', (char)'\xe6', (char)'\x47', (char)'\xe6', 
(char)'\x61', (char)'\xf2', (char)'\x90', (char)'\x39', (char)'\x91', 
(char)'\xf9', (char)'\xa8', (char)'\x7c', (char)'\xa8', (char)'\x0e', 
(char)'\x64', (char)'\x7e', (char)'\x6e', (char)'\x1f', (char)'\x2b', 
(char)'\x03', (char)'\x99', (char)'\x1f', (char)'\x9c', (char)'\x87', 
(char)'\xcb', (char)'\x00', (char)'\xe6', (char)'\x47', (char)'\xe7', 
(char)'\x61', (char)'\xf2', (char)'\xd0', (char)'\x39', (char)'\x91', 
(char)'\xf9', (char)'\xe8', (char)'\x7c', (char)'\xb8', (char)'\x0e', 
(char)'\x64', (char)'\x7e', (char)'\x7e', (char)'\x1f', (char)'\x2f', 
(char)'\x03', (char)'\x99', (char)'\x1f', (char)'\xa0', (char)'\x87', 
(char)'\xda', (char)'\x00', (char)'\xe6', (char)'\x47', (char)'\xe8', 
(char)'\x61', (char)'\xf6', (char)'\x90', (char)'\x39', (char)'\x91', 
(char)'\xfa', (char)'\x28', (char)'\x7d', (char)'\xa8', (char)'\x0e', 
(char)'\x64', (char)'\x7e', (char)'\x8e', (char)'\x1f', (char)'\x6b', 
(char)'\x03', (char)'\x99', (char)'\x1f', (char)'\xa4', (char)'\x87', 
(char)'\xdb', (char)'\x00', (char)'\xe6', (char)'\x47', (char)'\xe9', 
(char)'\x61', (char)'\xf6', (char)'\xd0', (char)'\x39', (char)'\x91', 
(char)'\xfa', (char)'\x68', (char)'\x7d', (char)'\xb8', (char)'\x0e', 
(char)'\x64', (char)'\x7e', (char)'\x9e', (char)'\x1f', (char)'\x6f', 
(char)'\x03', (char)'\x99', (char)'\x1f', (char)'\xa8', (char)'\x87', 
(char)'\xda', (char)'\x00', (char)'\xe6', (char)'\x47', (char)'\xea', 
(char)'\x61', (char)'\xf6', (char)'\x90', (char)'\x39', (char)'\x91', 
(char)'\xfa', (char)'\xa8', (char)'\x7d', (char)'\xa8', (char)'\x0e', 
(char)'\x64', (char)'\x7e', (char)'\xae', (char)'\x1f', (char)'\x6b', 
(char)'\x03', (char)'\x99', (char)'\x1f', (char)'\xac', (char)'\x87', 
(char)'\xdb', (char)'\x00', (char)'\xe6', (char)'\x47', (char)'\xeb', 
(char)'\x61', (char)'\xf6', (char)'\xd0', (char)'\x39', (char)'\x91', 
(char)'\xfa', (char)'\xe8', (char)'\x7d', (char)'\xb8', (char)'\x0e', 
(char)'\x64', (char)'\x7e', (char)'\xbe', (char)'\x1f', (char)'\x6f', 
(char)'\x03', (char)'\x99', (char)'\x1f', (char)'\xb2', (char)'\x87', 
(char)'\xee', (char)'\x80', (char)'\xe6', (char)'\x47', (char)'\xec', 
(char)'\xe0', (char)'\x39', (char)'\x10', (char)'\x39', (char)'\x91', 
(char)'\xfb', (char)'\x48', (char)'\x0e', (char)'\x18', (char)'\x0e', 
(char)'\x64', (char)'\x7e', (char)'\xda', (char)'\x03', (char)'\x91', 
(char)'\x03', (char)'\x42', (char)'\x1f', (char)'\xb7', (char)'\xc0', 
(char)'\xe4', (char)'\x40', (char)'\xd0', (char)'\x80', (char)'\xe6', 
(char)'\x47', (char)'\xef', (char)'\x20', (char)'\x39', (char)'\x10', 
(char)'\x39', (char)'\x91', (char)'\xfc', (char)'\x28', (char)'\x7f', 
(char)'\x28', (char)'\x0e', (char)'\x64', (char)'\x7f', (char)'\x0e', 
(char)'\x03', (char)'\x97', (char)'\x03', (char)'\x99', (char)'\x1f', 
(char)'\xc4', (char)'\x80', (char)'\xe2', (char)'\x40', (char)'\xe6', 
(char)'\x47', (char)'\xf1', (char)'\xa0', (char)'\x39', (char)'\x70', 
(char)'\x34', (char)'\x21', (char)'\xfc', (char)'\x7c', (char)'\x0e', 
(char)'\x5c', (char)'\x0d', (char)'\x08', (char)'\x0e', (char)'\x64', 
(char)'\x7f', (char)'\x32', (char)'\x03', (char)'\x97', (char)'\x03', 
(char)'\x99', (char)'\x1f', (char)'\xd2', (char)'\xc0', (char)'\xe6', 
(char)'\x40', (char)'\xc2', (char)'\x00', (char)'\xc0', (char)'\x07', 
(char)'\xf4', (char)'\xf0', (char)'\x39', (char)'\x90', (char)'\x30', 
(char)'\x80', (char)'\x30', (char)'\x11', (char)'\xfd', (char)'\x68', 
(char)'\x0e', (char)'\x64', (char)'\x0d', (char)'\x08', (char)'\x7f', 
(char)'\x5f', (char)'\x03', (char)'\x99', (char)'\x03', (char)'\x08', 
(char)'\x03', (char)'\x42', (char)'\x1f', (char)'\xe2', (char)'\xc0', 
(char)'\xe9', (char)'\x40', (char)'\xc2', (char)'\x00', (char)'\xc0', 
(char)'\x07', (char)'\xf8', (char)'\xf0', (char)'\x3a', (char)'\x50', 
(char)'\x30', (char)'\x80', (char)'\x30', (char)'\x11', (char)'\xfe', 
(char)'\x48', (char)'\x0e', (char)'\x84', (char)'\x0c', (char)'\x4c', 
(char)'\x7f', (char)'\x9a', (char)'\x03', (char)'\xa5', (char)'\x03', 
(char)'\x42', (char)'\x1f', (char)'\xe7', (char)'\xc0', (char)'\xe9', 
(char)'\x40', (char)'\xc2', (char)'\x00', (char)'\xd0', (char)'\x87', 
(char)'\xfc', (char)'\xa1', (char)'\xff', (char)'\xa0', (char)'\x39', 
(char)'\x91', (char)'\xff', (char)'\x38', (char)'\x0e', (char)'\xa4', 
(char)'\x0e', (char)'\x64', (char)'\x7f', (char)'\xd2', (char)'\x03', 
(char)'\x8f', (char)'\x03', (char)'\x99', (char)'\x1f', (char)'\xf6', 
(char)'\x80', (char)'\xea', (char)'\x40', (char)'\xd0', (char)'\x87', 
(char)'\xfd', (char)'\xf0', (char)'\x3a', (char)'\x90', (char)'\x34', 
(char)'\x20', (char)'\x39', (char)'\x91', (char)'\xff', (char)'\xc8', 
(char)'\x0e', (char)'\xa4', (char)'\x0e', (char)'\x67', (char)'\xec', 
(char)'\x02', (char)'\x00', (char)'\x46', (char)'\x00', (char)'\x46', 
(char)'\xfb', (char)'\x01', (char)'\x80', (char)'\x11', (char)'\x80', 
(char)'\x12', (char)'\x7e', (char)'\xc0', (char)'\xa0', (char)'\x04', 
(char)'\x60', (char)'\x04', (char)'\xcf', (char)'\xb0', (char)'\x3c', 
(char)'\x01', (char)'\x18', (char)'\x01', (char)'\x18', (char)'\x01', 
(char)'\x27', (char)'\xec', (char)'\x13', (char)'\x00', (char)'\x46', 
(char)'\x00', (char)'\x46', (char)'\x00', (char)'\x4c', (char)'\xfb', 
(char)'\x05', (char)'\x80', (char)'\x14', (char)'\xc0', (char)'\x15', 
(char)'\x3e', (char)'\xc1', (char)'\xa0', (char)'\x05', (char)'\x30', 
(char)'\x05', (char)'\x4f', (char)'\xb1', (char)'\x38', (char)'\x15', 
(char)'\x10', (char)'\x15', (char)'\x1b', (char)'\xec', (char)'\x52', 
(char)'\x05', (char)'\x44', (char)'\x05', (char)'\x35', (char)'\xfb', 
(char)'\x15', (char)'\x81', (char)'\x51', (char)'\x01', (char)'\x4e', 
(char)'\xfe', (char)'\xc5', (char)'\xa0', (char)'\x54', (char)'\xe0', 
(char)'\x54', (char)'\x6f', (char)'\xb1', (char)'\x78', (char)'\x15', 
(char)'\x10', (char)'\x14', (char)'\xf4', 
};
char duk_unicode_caseconv_lc[616] = {
(char)'\x90', (char)'\x03', (char)'\x00', (char)'\x03', (char)'\x80', 
(char)'\xb8', (char)'\x06', (char)'\xc0', (char)'\x07', (char)'\xc0', 
(char)'\x70', (char)'\x18', (char)'\x90', (char)'\x25', (char)'\x60', 
(char)'\x40', (char)'\x36', (char)'\x20', (char)'\x51', (char)'\x40', 
(char)'\x80', (char)'\xe2', (char)'\x00', (char)'\xeb', (char)'\x41', 
(char)'\x81', (char)'\xc7', (char)'\x01', (char)'\xe6', (char)'\x82', 
(char)'\x03', (char)'\x91', (char)'\x03', (char)'\xb1', (char)'\x22', 
(char)'\x07', (char)'\x46', (char)'\x07', (char)'\x86', (char)'\x24', 
(char)'\x0f', (char)'\xf4', (char)'\x0d', (char)'\xec', (char)'\x18', 
(char)'\x20', (char)'\x00', (char)'\x22', (char)'\x81', (char)'\x00', 
(char)'\x41', (char)'\x00', (char)'\x43', (char)'\x04', (char)'\x00', 
(char)'\xa6', (char)'\x20', (char)'\xac', (char)'\x29', (char)'\x84', 
(char)'\x28', (char)'\x0b', (char)'\x40', (char)'\x13', (char)'\x0f', 
(char)'\x84', (char)'\x0f', (char)'\x80', (char)'\x08', (char)'\x1f', 
(char)'\x18', (char)'\x1f', (char)'\x10', (char)'\x0c', (char)'\x3e', 
(char)'\x50', (char)'\x3e', (char)'\x40', (char)'\x20', (char)'\x7c', 
(char)'\xe0', (char)'\x7c', (char)'\xc0', (char)'\x40', (char)'\xfa', 
(char)'\x40', (char)'\xfa', (char)'\x00', (char)'\x61', (char)'\xf6', 
(char)'\x81', (char)'\xf6', (char)'\x01', (char)'\x03', (char)'\xf1', 
(char)'\x03', (char)'\xf0', (char)'\x02', (char)'\x07', (char)'\xe6', 
(char)'\x07', (char)'\xe4', (char)'\x04', (char)'\x0f', (char)'\xd4', 
(char)'\x0f', (char)'\xd0', (char)'\x08', (char)'\x1f', (char)'\xb8', 
(char)'\x1f', (char)'\xb0', (char)'\x04', (char)'\x3f', (char)'\x74', 
(char)'\x3e', (char)'\xe0', (char)'\x08', (char)'\x7f', (char)'\x20', 
(char)'\x7d', (char)'\xc8', (char)'\x20', (char)'\xfe', (char)'\xc0', 
(char)'\xfe', (char)'\x80', (char)'\x21', (char)'\xfd', (char)'\xa1', 
(char)'\xf7', (char)'\x60', (char)'\x43', (char)'\xfd', (char)'\x03', 
(char)'\xfc', (char)'\x00', (char)'\x87', (char)'\xfa', (char)'\x87', 
(char)'\xde', (char)'\x81', (char)'\x0f', (char)'\xfc', (char)'\x0f', 
(char)'\xbc', (char)'\x02', (char)'\x1f', (char)'\xfa', (char)'\x1f', 
(char)'\x7c', (char)'\x04', (char)'\x42', (char)'\xc0', (char)'\x42', 
(char)'\xe0', (char)'\x40', (char)'\x92', (char)'\xd8', (char)'\x93', 
(char)'\x40', (char)'\xd1', (char)'\x60', (char)'\x01', (char)'\x61', 
(char)'\x82', (char)'\xf2', (char)'\xc7', (char)'\xe0', (char)'\x23', 
(char)'\xf0', (char)'\x5f', (char)'\xe4', (char)'\x3f', (char)'\xe8', 
(char)'\x26', (char)'\xa1', (char)'\x01', (char)'\x00', (char)'\x01', 
(char)'\x01', (char)'\x30', (char)'\x02', (char)'\x64', (char)'\x02', 
(char)'\x66', (char)'\x0c', (char)'\x04', (char)'\xe4', (char)'\x04', 
(char)'\xe8', (char)'\x40', (char)'\x0a', (char)'\x50', (char)'\x0a', 
(char)'\x59', (char)'\x70', (char)'\x17', (char)'\x90', (char)'\x17', 
(char)'\xa0', (char)'\x60', (char)'\x30', (char)'\x40', (char)'\x30', 
(char)'\x60', (char)'\x80', (char)'\x68', (char)'\x00', (char)'\x68', 
(char)'\x41', (char)'\x80', (char)'\xd9', (char)'\x80', (char)'\xda', 
(char)'\x02', (char)'\x01', (char)'\xcb', (char)'\x01', (char)'\xcc', 
(char)'\x12', (char)'\x03', (char)'\xbc', (char)'\x03', (char)'\xbe', 
(char)'\x24', (char)'\x07', (char)'\xc8', (char)'\x07', (char)'\xcc', 
(char)'\x10', (char)'\x0f', (char)'\xc0', (char)'\x0f', (char)'\xc9', 
(char)'\x40', (char)'\x22', (char)'\x20', (char)'\x22', (char)'\x31', 
(char)'\x20', (char)'\x48', (char)'\xc0', (char)'\x48', (char)'\xe1', 
(char)'\x40', (char)'\xdc', (char)'\x00', (char)'\xdc', (char)'\x41', 
(char)'\x01', (char)'\xec', (char)'\x01', (char)'\xec', (char)'\x8c', 
(char)'\x04', (char)'\x60', (char)'\x04', (char)'\x61', (char)'\x22', 
(char)'\x09', (char)'\x14', (char)'\x09', (char)'\x16', (char)'\x6c', 
(char)'\x13', (char)'\x04', (char)'\x13', (char)'\x08', (char)'\x38', 
(char)'\x26', (char)'\x80', (char)'\x26', (char)'\x8a', (char)'\xc1', 
(char)'\xe0', (char)'\x01', (char)'\xe0', (char)'\x19', (char)'\x63', 
(char)'\xd4', (char)'\x03', (char)'\xd4', (char)'\x2c', (char)'\x07', 
(char)'\xd6', (char)'\x47', (char)'\xd4', (char)'\x42', (char)'\x16', 
(char)'\x33', (char)'\x96', (char)'\x34', (char)'\x03', (char)'\x2c', 
(char)'\x80', (char)'\x2c', (char)'\x81', (char)'\x64', (char)'\x59', 
(char)'\xd6', (char)'\x59', (char)'\xd8', (char)'\x0a', (char)'\x99', 
(char)'\x02', (char)'\x99', (char)'\x04', (char)'\xbd', (char)'\x34', 
(char)'\x05', (char)'\x34', (char)'\x08', (char)'\xca', (char)'\x72', 
(char)'\x2a', (char)'\x72', (char)'\x30', (char)'\xf4', (char)'\xe6', 
(char)'\x54', (char)'\xe6', (char)'\x67', (char)'\xe9', (char)'\xde', 
(char)'\x69', (char)'\xde', (char)'\x81', (char)'\x53', (char)'\xbf', 
(char)'\x53', (char)'\xbf', (char)'\x85', (char)'\xa7', (char)'\xa0', 
(char)'\xa7', (char)'\xa1', (char)'\x0a', (char)'\x30', (char)'\x0d', 
(char)'\x30', (char)'\x14', (char)'\x00', (char)'\x20', (char)'\x1a', 
(char)'\xc0', (char)'\x1a', (char)'\xd0', (char)'\x40', (char)'\x38', 
(char)'\x80', (char)'\x38', (char)'\xc0', (char)'\xc0', (char)'\x71', 
(char)'\x40', (char)'\x71', (char)'\x81', (char)'\x01', (char)'\xfb', 
(char)'\x81', (char)'\xfc', (char)'\x02', (char)'\x2c', (char)'\x72', 
(char)'\x2c', (char)'\x73', (char)'\x04', (char)'\x10', (char)'\x0c', 
(char)'\x38', (char)'\x0c', (char)'\x40', (char)'\x20', (char)'\x1b', 
(char)'\x80', (char)'\x1b', (char)'\x90', (char)'\x40', (char)'\xd3', 
(char)'\xc5', (char)'\xd3', (char)'\xc6', (char)'\x02', (char)'\x08', 
(char)'\x06', (char)'\x58', (char)'\x09', (char)'\xa4', (char)'\x10', 
(char)'\x11', (char)'\xd8', (char)'\x11', (char)'\xe0', (char)'\x2f', 
(char)'\xf5', (char)'\x01', (char)'\x78', (char)'\x00', (char)'\xff', 
(char)'\x01', (char)'\x81', (char)'\x02', (char)'\x53', (char)'\x01', 
(char)'\x86', (char)'\x02', (char)'\x54', (char)'\x01', (char)'\x8e', 
(char)'\x01', (char)'\xdd', (char)'\x01', (char)'\x8f', (char)'\x02', 
(char)'\x59', (char)'\x01', (char)'\x90', (char)'\x02', (char)'\x5b', 
(char)'\x01', (char)'\x91', (char)'\x01', (char)'\x92', (char)'\x01', 
(char)'\x93', (char)'\x02', (char)'\x60', (char)'\x01', (char)'\x94', 
(char)'\x02', (char)'\x63', (char)'\x01', (char)'\x97', (char)'\x02', 
(char)'\x68', (char)'\x01', (char)'\x98', (char)'\x01', (char)'\x99', 
(char)'\x01', (char)'\x9d', (char)'\x02', (char)'\x72', (char)'\x01', 
(char)'\x9f', (char)'\x02', (char)'\x75', (char)'\x01', (char)'\xa7', 
(char)'\x01', (char)'\xa8', (char)'\x01', (char)'\xae', (char)'\x02', 
(char)'\x88', (char)'\x01', (char)'\xb7', (char)'\x02', (char)'\x92', 
(char)'\x01', (char)'\xf1', (char)'\x01', (char)'\xf3', (char)'\x01', 
(char)'\xf6', (char)'\x01', (char)'\x95', (char)'\x01', (char)'\xf7', 
(char)'\x01', (char)'\xbf', (char)'\x02', (char)'\x20', (char)'\x01', 
(char)'\x9e', (char)'\x02', (char)'\x3a', (char)'\x2c', (char)'\x65', 
(char)'\x02', (char)'\x3d', (char)'\x01', (char)'\x9a', (char)'\x02', 
(char)'\x3e', (char)'\x2c', (char)'\x66', (char)'\x02', (char)'\x43', 
(char)'\x01', (char)'\x80', (char)'\x02', (char)'\x44', (char)'\x02', 
(char)'\x89', (char)'\x02', (char)'\x45', (char)'\x02', (char)'\x8c', 
(char)'\x03', (char)'\x76', (char)'\x03', (char)'\x77', (char)'\x03', 
(char)'\x86', (char)'\x03', (char)'\xac', (char)'\x03', (char)'\x8c', 
(char)'\x03', (char)'\xcc', (char)'\x03', (char)'\xcf', (char)'\x03', 
(char)'\xd7', (char)'\x03', (char)'\xf4', (char)'\x03', (char)'\xb8', 
(char)'\x03', (char)'\xf9', (char)'\x03', (char)'\xf2', (char)'\x04', 
(char)'\xc0', (char)'\x04', (char)'\xcf', (char)'\x1e', (char)'\x9e', 
(char)'\x00', (char)'\xdf', (char)'\x1f', (char)'\xbc', (char)'\x1f', 
(char)'\xb3', (char)'\x1f', (char)'\xcc', (char)'\x1f', (char)'\xc3', 
(char)'\x1f', (char)'\xec', (char)'\x1f', (char)'\xe5', (char)'\x1f', 
(char)'\xfc', (char)'\x1f', (char)'\xf3', (char)'\x21', (char)'\x26', 
(char)'\x03', (char)'\xc9', (char)'\x21', (char)'\x2a', (char)'\x00', 
(char)'\x6b', (char)'\x21', (char)'\x2b', (char)'\x00', (char)'\xe5', 
(char)'\x21', (char)'\x32', (char)'\x21', (char)'\x4e', (char)'\x21', 
(char)'\x83', (char)'\x21', (char)'\x84', (char)'\x2c', (char)'\x60', 
(char)'\x2c', (char)'\x61', (char)'\x2c', (char)'\x62', (char)'\x02', 
(char)'\x6b', (char)'\x2c', (char)'\x63', (char)'\x1d', (char)'\x7d', 
(char)'\x2c', (char)'\x64', (char)'\x02', (char)'\x7d', (char)'\x2c', 
(char)'\x6d', (char)'\x02', (char)'\x51', (char)'\x2c', (char)'\x6e', 
(char)'\x02', (char)'\x71', (char)'\x2c', (char)'\x6f', (char)'\x02', 
(char)'\x50', (char)'\x2c', (char)'\x70', (char)'\x02', (char)'\x52', 
(char)'\xa7', (char)'\x7d', (char)'\x1d', (char)'\x79', (char)'\xa7', 
(char)'\x8d', (char)'\x02', (char)'\x65', (char)'\x02', (char)'\x02', 
(char)'\x61', (char)'\x00', (char)'\x34', (char)'\x81', (char)'\x83', 
(char)'\x80', 
};

#line 1 "duk_util_bitdecoder.c"
/*
 *  Bitstream decoder.
 */

/* include removed: duk_internal.h */

duk_u32 duk_bd_decode(duk_bitdecoder_ctx *ctx, int bits) {
	int shift;
	int mask;
	int tmp;

	/* Note: cannot read more than 24 bits without possibly shifting top bits out.
	 * Fixable, but adds complexity.
	 */
	DUK_ASSERT(bits >= 1 && bits <= 24);

	while (ctx->currbits < bits) {
#if 0
		DUK_DDDPRINT("decode_bits: shift more data (bits=%d, currbits=%d)", bits, ctx->currbits);
#endif
		ctx->currval <<= 8;
		if (ctx->offset < ctx->length) {
			/* If ctx->offset >= ctx->length, we "shift zeroes in"
			 * instead of croaking.
			 */
			ctx->currval |= ctx->data[ctx->offset++];
		}
		ctx->currbits += 8;
	}
#if 0
	DUK_DDDPRINT("decode_bits: bits=%d, currbits=%d, currval=0x%08x", bits, ctx->currbits, ctx->currval);
#endif

	/* Extract 'top' bits of currval; note that the extracted bits do not need
	 * to be cleared, we just ignore them on next round.
	 */
	shift = ctx->currbits - bits;
	mask = (1 << bits) - 1;
	tmp = (ctx->currval >> shift) & mask;
	ctx->currbits = shift;  /* remaining */

#if 0
	DUK_DDDPRINT("decode_bits: %d bits -> 0x%08x (%d), currbits=%d, currval=0x%08x",
	             bits, tmp, tmp, ctx->currbits, ctx->currval);
#endif

	return tmp;
}

int duk_bd_decode_flag(duk_bitdecoder_ctx *ctx) {
	return (int) duk_bd_decode(ctx, 1);
}

#line 1 "duk_util_bitencoder.c"
/*
 *  Bitstream encoder.
 */

/* include removed: duk_internal.h */

void duk_be_encode(duk_bitencoder_ctx *ctx, duk_u32 data, int bits) {
	int tmp;

	DUK_ASSERT(ctx != NULL);
	DUK_ASSERT(ctx->currbits < 8);

	/* This limitation would be fixable but adds unnecessary complexity. */
	DUK_ASSERT(bits >= 1 && bits <= 24);

	ctx->currval = (ctx->currval << bits) | data;
	ctx->currbits += bits;

	while (ctx->currbits >= 8) {
		tmp = (ctx->currval >> (ctx->currbits - 8)) & 0xff;

		if (ctx->offset < ctx->length) {
			ctx->data[ctx->offset++] = (duk_u8) tmp;
		} else {
			/* If buffer has been exhausted, truncate bitstream */
			ctx->truncated = 1;
		}

		ctx->currbits -= 8;
	}
}

void duk_be_finish(duk_bitencoder_ctx *ctx) {
	int npad;

	DUK_ASSERT(ctx != NULL);
	DUK_ASSERT(ctx->currbits < 8);

	npad = 8 - ctx->currbits;
	if (npad > 0) {
		duk_be_encode(ctx, 0, npad);
	}
	DUK_ASSERT(ctx->currbits == 0);
}

#line 1 "duk_util_hashbytes.c"
/*
 *  Hash function duk_util_hashbytes().
 *
 *  Currently, 32-bit MurmurHash2.
 *
 *  Don't rely on specific hash values; hash function may be endianness
 *  dependent, for instance.
 */

/* include removed: duk_internal.h */

/* 'magic' constants for Murmurhash2 */
#define  MAGIC_M  ((duk_u32) 0x5bd1e995)
#define  MAGIC_R  24

duk_u32 duk_util_hashbytes(duk_u8 *data, duk_u32 len, duk_u32 seed) {
	duk_u32 h = seed ^ len;

	while (len >= 4) {
		/* Portability workaround is required for platforms without
		 * unaligned access.  The replacement code emulates little
		 * endian access even on big endian architectures, which is
		 * OK as long as it is consistent for a build.
		 */
#ifdef DUK_USE_HASHBYTES_UNALIGNED_U32_ACCESS
		duk_u32 k = *((duk_u32 *) data);
#else
		duk_u32 k = ((duk_u32) data[0]) |
		            (((duk_u32) data[1]) << 8) |
		            (((duk_u32) data[2]) << 16) |
		            (((duk_u32) data[3]) << 24);
#endif

		k *= MAGIC_M;
		k ^= k >> MAGIC_R;
		k *= MAGIC_M;
		h *= MAGIC_M;
		h ^= k;
		data += 4;
		len -= 4;
	}

	switch (len) {
		case 3:	h ^= data[2] << 16;
		case 2:	h ^= data[1] << 8;
		case 1:	h ^= data[0];
			h *= MAGIC_M;
        }

	h ^= h >> 13;
	h *= MAGIC_M;
	h ^= h >> 15;

	return h;
}

#line 1 "duk_util_hashprime.c"
/*
 *  Round a number upwards to a prime (not usually the nearest one).
 *
 *  Uses a table of successive 32-bit primes whose ratio is roughly
 *  constant.  This keeps the relative upwards 'rounding error' bounded
 *  and the data size small.  A simple 'predict-correct' compression is
 *  used to compress primes to one byte per prime.  See genhashsizes.py
 *  for details.
 *
 *  The minimum prime returned here must be coordinated with the possible
 *  probe sequence steps in duk_hobject and duk_heap stringtable.
 */

/* include removed: duk_internal.h */

/* hash size ratio goal, must match genhashsizes.py */
#define  HASH_SIZE_RATIO   1177  /* floor(1.15 * (1 << 10)) */

/* prediction corrections for prime list (see genhashsizes.py) */
static duk_i8 hash_size_corrections[] = {
	17,  /* minimum prime */
	4, 3, 4, 1, 4, 1, 1, 2, 2, 2, 2, 1, 6, 6, 9, 5, 1, 2, 2, 5, 1, 3, 3, 3,
	5, 4, 4, 2, 4, 8, 3, 4, 23, 2, 4, 7, 8, 11, 2, 12, 15, 10, 1, 1, 5, 1, 5,
	8, 9, 17, 14, 10, 7, 5, 2, 46, 21, 1, 9, 9, 4, 4, 10, 23, 36, 6, 20, 29,
	18, 6, 19, 21, 16, 11, 5, 5, 48, 9, 1, 39, 14, 8, 4, 29, 9, 1, 15, 48, 12,
	22, 6, 15, 27, 4, 2, 17, 28, 8, 9, 4, 5, 8, 3, 3, 8, 37, 11, 15, 8, 30,
	43, 6, 33, 41, 5, 20, 32, 41, 38, 24, 77, 14, 19, 11, 4, 35, 18, 19, 41,
	10, 23, 16, 9, 2,
	-1
};

/* probe steps (see genhashsizes.py), currently assumed to be 32 entries long
 * (DUK_UTIL_GET_HASH_PROBE_STEP macro).
 */
duk_u8 duk_util_probe_steps[32] = {
	2, 3, 5, 7, 11, 13, 19, 31, 41, 47, 59, 67, 73, 79, 89, 101, 103, 107,
	109, 127, 137, 139, 149, 157, 163, 167, 173, 181, 191, 193, 197, 199
};

duk_u32 duk_util_get_hash_prime(duk_u32 size) {
	duk_i8 *p = hash_size_corrections;
	duk_u32 curr;

	curr = (duk_u32) *p++;
	for (;;) {
		int t = (int) *p++;
		if (t < 0) {
			/* may happen if size is very close to 2^32-1 */
			break;
		}

		/* FIXME: portability: perhaps use double instead? */

		/* prediction */
		curr = (duk_u32) ((((unsigned long long) curr) * ((unsigned long long) HASH_SIZE_RATIO)) >> 10);

		/* correction */
		curr += t;

		DUK_DDDPRINT("size=%d, curr=%d", size, curr);

		if (curr >= size) {
			return curr;
		}
	}
	return 0;
}

#line 1 "duk_util_misc.c"
/*
 *  Misc util stuff
 */

/* include removed: duk_internal.h */

/* Lowercase digits for radix values 2 to 36.  Also doubles as lowercase
 * hex nybble table.
 */
char duk_lc_digits[36] = { '0', '1', '2', '3', '4', '5', '6', '7',
                           '8', '9', 'a', 'b', 'c', 'd', 'e', 'f',
                           'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
                           'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
                           'w', 'x', 'y', 'z' };

char duk_uc_nybbles[16] = { '0', '1', '2', '3', '4', '5', '6', '7',
                            '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };

#line 1 "duk_util_tinyrandom.c"
/*
 *  A tiny random number generator.
 *
 *  Currently used for Math.random().
 *
 *  http://www.woodmann.com/forum/archive/index.php/t-3100.html
 */

/* include removed: duk_internal.h */

#define  UPDATE_RND(rnd) do { \
		(rnd) += ((rnd) * (rnd)) | 0x05; \
		(rnd) = ((rnd) & 0xffffffffU);       /* if duk_u32 is exactly 32 bits, this is a NOP */ \
	} while (0)

#define  RND_BIT(rnd)  ((rnd) >> 31)  /* only use the highest bit */

duk_u32 duk_util_tinyrandom_get_bits(duk_hthread *thr, int n) {
	int i;
	duk_u32 res = 0;
	duk_u32 rnd;

	rnd = thr->heap->rnd_state;

	for (i = 0; i < n; i++) {
		UPDATE_RND(rnd);
		res <<= 1;
		res += RND_BIT(rnd);
	}

	thr->heap->rnd_state = rnd;

	return res;
}

double duk_util_tinyrandom_get_double(duk_hthread *thr) {
	double t;
	int n;
	duk_u32 rnd;

	/*
	 *  XXX: could make this a lot faster if we create the double memory
	 *  representation directly.  Feasible easily (must be uniform random).
	 */

	rnd = thr->heap->rnd_state;

	n = 53;  /* enough to cover the whole mantissa */
	t = 0.0;

	do {
		UPDATE_RND(rnd);
		t += RND_BIT(rnd);
		t /= 2.0;
	} while(--n);

	thr->heap->rnd_state = rnd;

	DUK_ASSERT(t >= (double) 0.0);
	DUK_ASSERT(t < (double) 1.0);

	return t;
}

