Cesanta / v7 Featured

Embedded JavaScript virtual machine

Dependents:   DISCO-F469NI_javascript_blinker

v7.c

Committer:
Marko Mikulicic
Date:
2016-10-11
Revision:
3:2bce515c5f0b
Parent:
2:7762b98d31c7

File content as of revision 3:2bce515c5f0b:

#include "v7.h"
#ifdef V7_MODULE_LINES
#line 1 "v7/src/license.h"
#endif
/*
 * Copyright (c) 2013-2014 Cesanta Software Limited
 * All rights reserved
 *
 * This software is dual-licensed: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation. For the terms of this
 * license, see <http://www.gnu.org/licenses/>.
 *
 * You are free to use this software under the terms of the GNU General
 * Public License, but WITHOUT ANY WARRANTY; without even the implied
 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU General Public License for more details.
 *
 * Alternatively, you can license this software under a commercial
 * license, as set out in <https://www.cesanta.com/license>.
 */

#ifdef V7_EXPOSE_PRIVATE
#define V7_PRIVATE
#define V7_EXTERN extern
#else
#define V7_PRIVATE static
#define V7_EXTERN static
#endif /* CS_V7_SRC_LICENSE_H_ */
#ifdef V7_MODULE_LINES
#line 1 "common/platform.h"
#endif
#ifndef CS_COMMON_PLATFORM_H_
#define CS_COMMON_PLATFORM_H_

/*
 * For the "custom" platform, includes and dependencies can be
 * provided through mg_locals.h.
 */
#define CS_P_CUSTOM 0
#define CS_P_UNIX 1
#define CS_P_WINDOWS 2
#define CS_P_ESP_LWIP 3
#define CS_P_CC3200 4
#define CS_P_MSP432 5
#define CS_P_CC3100 6
#define CS_P_MBED 7

/* If not specified explicitly, we guess platform by defines. */
#ifndef CS_PLATFORM

#if defined(TARGET_IS_MSP432P4XX) || defined(__MSP432P401R__)

#define CS_PLATFORM CS_P_MSP432
#elif defined(cc3200)
#define CS_PLATFORM CS_P_CC3200
#elif defined(__unix__) || defined(__APPLE__)
#define CS_PLATFORM CS_P_UNIX
#elif defined(_WIN32)
#define CS_PLATFORM CS_P_WINDOWS
#elif defined(__MBED__)
#define CS_PLATFORM CS_P_MBED
#endif

#ifndef CS_PLATFORM
#error "CS_PLATFORM is not specified and we couldn't guess it."
#endif

#endif /* !defined(CS_PLATFORM) */

/* Amalgamated: #include "common/platforms/platform_unix.h" */
/* Amalgamated: #include "common/platforms/platform_windows.h" */
/* Amalgamated: #include "common/platforms/platform_esp_lwip.h" */
/* Amalgamated: #include "common/platforms/platform_cc3200.h" */
/* Amalgamated: #include "common/platforms/platform_cc3100.h" */
/* Amalgamated: #include "common/platforms/platform_mbed.h" */

/* Common stuff */

#ifdef __GNUC__
#define NORETURN __attribute__((noreturn))
#define NOINLINE __attribute__((noinline))
#define WARN_UNUSED_RESULT __attribute__((warn_unused_result))
#define NOINSTR __attribute__((no_instrument_function))
#else
#define NORETURN
#define NOINLINE
#define WARN_UNUSED_RESULT
#define NOINSTR
#endif /* __GNUC__ */

#ifndef ARRAY_SIZE
#define ARRAY_SIZE(array) (sizeof(array) / sizeof(array[0]))
#endif

#endif /* CS_COMMON_PLATFORM_H_ */
#ifdef V7_MODULE_LINES
#line 1 "common/platforms/platform_windows.h"
#endif
#ifndef CS_COMMON_PLATFORMS_PLATFORM_WINDOWS_H_
#define CS_COMMON_PLATFORMS_PLATFORM_WINDOWS_H_
#if CS_PLATFORM == CS_P_WINDOWS

/*
 * MSVC++ 14.0 _MSC_VER == 1900 (Visual Studio 2015)
 * MSVC++ 12.0 _MSC_VER == 1800 (Visual Studio 2013)
 * MSVC++ 11.0 _MSC_VER == 1700 (Visual Studio 2012)
 * MSVC++ 10.0 _MSC_VER == 1600 (Visual Studio 2010)
 * MSVC++ 9.0  _MSC_VER == 1500 (Visual Studio 2008)
 * MSVC++ 8.0  _MSC_VER == 1400 (Visual Studio 2005)
 * MSVC++ 7.1  _MSC_VER == 1310 (Visual Studio 2003)
 * MSVC++ 7.0  _MSC_VER == 1300
 * MSVC++ 6.0  _MSC_VER == 1200
 * MSVC++ 5.0  _MSC_VER == 1100
 */
#ifdef _MSC_VER
#pragma warning(disable : 4127) /* FD_SET() emits warning, disable it */
#pragma warning(disable : 4204) /* missing c99 support */
#endif

#define _WINSOCK_DEPRECATED_NO_WARNINGS 1
#define _CRT_SECURE_NO_WARNINGS

#include <assert.h>
#include <direct.h>
#include <errno.h>
#include <fcntl.h>
#include <io.h>
#include <limits.h>
#include <signal.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <time.h>

#ifdef _MSC_VER
#pragma comment(lib, "ws2_32.lib") /* Linking with winsock library */
#endif

#include <winsock2.h>
#include <ws2tcpip.h>
#include <windows.h>
#include <process.h>

#if defined(_MSC_VER) && _MSC_VER >= 1800
#define strdup _strdup
#endif

#ifndef EINPROGRESS
#define EINPROGRESS WSAEINPROGRESS
#endif
#ifndef EWOULDBLOCK
#define EWOULDBLOCK WSAEWOULDBLOCK
#endif
#ifndef __func__
#define STRX(x) #x
#define STR(x) STRX(x)
#define __func__ __FILE__ ":" STR(__LINE__)
#endif
#define snprintf _snprintf
#define fileno _fileno
#define vsnprintf _vsnprintf
#define sleep(x) Sleep((x) *1000)
#define to64(x) _atoi64(x)
#if !defined(__MINGW32__) && !defined(__MINGW64__)
#define popen(x, y) _popen((x), (y))
#define pclose(x) _pclose(x)
#endif
#define rmdir _rmdir
#if defined(_MSC_VER) && _MSC_VER >= 1400
#define fseeko(x, y, z) _fseeki64((x), (y), (z))
#else
#define fseeko(x, y, z) fseek((x), (y), (z))
#endif
#if defined(_MSC_VER) && _MSC_VER <= 1200
typedef unsigned long uintptr_t;
typedef long intptr_t;
#endif
typedef int socklen_t;
#if _MSC_VER >= 1700
#include <stdint.h>
#else
typedef signed char int8_t;
typedef unsigned char uint8_t;
typedef int int32_t;
typedef unsigned int uint32_t;
typedef short int16_t;
typedef unsigned short uint16_t;
typedef __int64 int64_t;
typedef unsigned __int64 uint64_t;
#endif
typedef SOCKET sock_t;
typedef uint32_t in_addr_t;
#ifndef UINT16_MAX
#define UINT16_MAX 65535
#endif
#ifndef UINT32_MAX
#define UINT32_MAX 4294967295
#endif
#ifndef pid_t
#define pid_t HANDLE
#endif
#define INT64_FMT "I64d"
#define INT64_X_FMT "I64x"
#define SIZE_T_FMT "Iu"
#if defined(__MINGW32__) && !defined(__MINGW64_VERSION_MAJOR)
typedef struct stat cs_stat_t;
#else
typedef struct _stati64 cs_stat_t;
#endif
#ifndef S_ISDIR
#define S_ISDIR(x) (((x) &_S_IFMT) == _S_IFDIR)
#endif
#ifndef S_ISREG
#define S_ISREG(x) (((x) &_S_IFMT) == _S_IFREG)
#endif
#define DIRSEP '\\'

#ifndef va_copy
#ifdef __va_copy
#define va_copy __va_copy
#else
#define va_copy(x, y) (x) = (y)
#endif
#endif

#ifndef MG_MAX_HTTP_REQUEST_SIZE
#define MG_MAX_HTTP_REQUEST_SIZE 8192
#endif

#ifndef MG_MAX_HTTP_SEND_MBUF
#define MG_MAX_HTTP_SEND_MBUF 4096
#endif

#ifndef MG_MAX_HTTP_HEADERS
#define MG_MAX_HTTP_HEADERS 40
#endif

#endif /* CS_PLATFORM == CS_P_WINDOWS */
#endif /* CS_COMMON_PLATFORMS_PLATFORM_WINDOWS_H_ */
#ifdef V7_MODULE_LINES
#line 1 "common/platforms/platform_unix.h"
#endif
#ifndef CS_COMMON_PLATFORMS_PLATFORM_UNIX_H_
#define CS_COMMON_PLATFORMS_PLATFORM_UNIX_H_
#if CS_PLATFORM == CS_P_UNIX

#ifndef _XOPEN_SOURCE
#define _XOPEN_SOURCE 600
#endif

/* <inttypes.h> wants this for C++ */
#ifndef __STDC_FORMAT_MACROS
#define __STDC_FORMAT_MACROS
#endif

/* C++ wants that for INT64_MAX */
#ifndef __STDC_LIMIT_MACROS
#define __STDC_LIMIT_MACROS
#endif

/* Enable fseeko() and ftello() functions */
#ifndef _LARGEFILE_SOURCE
#define _LARGEFILE_SOURCE
#endif

/* Enable 64-bit file offsets */
#ifndef _FILE_OFFSET_BITS
#define _FILE_OFFSET_BITS 64
#endif

#include <arpa/inet.h>
#include <assert.h>
#include <ctype.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <stdint.h>
#include <limits.h>
#include <math.h>
#include <netdb.h>
#include <netinet/in.h>
#include <pthread.h>
#include <signal.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>

/*
 * osx correctly avoids defining strtoll when compiling in strict ansi mode.
 * We require strtoll, and if your embedded pre-c99 compiler lacks one, please
 * implement a shim.
 */
#if !(defined(__DARWIN_C_LEVEL) && __DARWIN_C_LEVEL >= 200809L)
long long strtoll(const char *, char **, int);
#endif

typedef int sock_t;
#define INVALID_SOCKET (-1)
#define SIZE_T_FMT "zu"
typedef struct stat cs_stat_t;
#define DIRSEP '/'
#define to64(x) strtoll(x, NULL, 10)
#define INT64_FMT PRId64
#define INT64_X_FMT PRIx64

#ifndef __cdecl
#define __cdecl
#endif

#ifndef va_copy
#ifdef __va_copy
#define va_copy __va_copy
#else
#define va_copy(x, y) (x) = (y)
#endif
#endif

#define closesocket(x) close(x)

#ifndef MG_MAX_HTTP_REQUEST_SIZE
#define MG_MAX_HTTP_REQUEST_SIZE 8192
#endif

#ifndef MG_MAX_HTTP_SEND_MBUF
#define MG_MAX_HTTP_SEND_MBUF 4096
#endif

#ifndef MG_MAX_HTTP_HEADERS
#define MG_MAX_HTTP_HEADERS 40
#endif

#endif /* CS_PLATFORM == CS_P_UNIX */
#endif /* CS_COMMON_PLATFORMS_PLATFORM_UNIX_H_ */
#ifdef V7_MODULE_LINES
#line 1 "common/platforms/platform_esp_lwip.h"
#endif
#ifndef CS_COMMON_PLATFORMS_PLATFORM_ESP_LWIP_H_
#define CS_COMMON_PLATFORMS_PLATFORM_ESP_LWIP_H_
#if CS_PLATFORM == CS_P_ESP_LWIP

#include <assert.h>
#include <ctype.h>
#include <fcntl.h>
#include <inttypes.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/time.h>

#include <lwip/err.h>
#include <lwip/ip_addr.h>
#include <lwip/inet.h>
#include <lwip/netdb.h>
#include <lwip/dns.h>

#ifndef LWIP_PROVIDE_ERRNO
#include <errno.h>
#endif

#define LWIP_TIMEVAL_PRIVATE 0

#if LWIP_SOCKET
#include <lwip/sockets.h>
#define SOMAXCONN 10
#else
/* We really need the definitions from sockets.h. */
#undef LWIP_SOCKET
#define LWIP_SOCKET 1
#include <lwip/sockets.h>
#undef LWIP_SOCKET
#define LWIP_SOCKET 0
#endif

typedef int sock_t;
#define INVALID_SOCKET (-1)
#define SIZE_T_FMT "u"
typedef struct stat cs_stat_t;
#define DIRSEP '/'
#define to64(x) strtoll(x, NULL, 10)
#define INT64_FMT PRId64
#define INT64_X_FMT PRIx64
#define __cdecl

unsigned long os_random(void);
#define random os_random

#endif /* CS_PLATFORM == CS_P_ESP_LWIP */
#endif /* CS_COMMON_PLATFORMS_PLATFORM_ESP_LWIP_H_ */
#ifdef V7_MODULE_LINES
#line 1 "common/mbuf.h"
#endif
/*
 * Copyright (c) 2015 Cesanta Software Limited
 * All rights reserved
 */

/*
 * === Memory Buffers
 *
 * Mbufs are mutable/growing memory buffers, like C++ strings.
 * Mbuf can append data to the end of a buffer or insert data into arbitrary
 * position in the middle of a buffer. The buffer grows automatically when
 * needed.
 */

#ifndef CS_COMMON_MBUF_H_
#define CS_COMMON_MBUF_H_

#if defined(__cplusplus)
extern "C" {
#endif

#include <stdlib.h>

#ifndef MBUF_SIZE_MULTIPLIER
#define MBUF_SIZE_MULTIPLIER 1.5
#endif

/* Memory buffer descriptor */
struct mbuf {
  char *buf;   /* Buffer pointer */
  size_t len;  /* Data length. Data is located between offset 0 and len. */
  size_t size; /* Buffer size allocated by realloc(1). Must be >= len */
};

/*
 * Initialises an Mbuf.
 * `initial_capacity` specifies the initial capacity of the mbuf.
 */
void mbuf_init(struct mbuf *, size_t initial_capacity);

/* Frees the space allocated for the mbuffer and resets the mbuf structure. */
void mbuf_free(struct mbuf *);

/*
 * Appends data to the Mbuf.
 *
 * Returns the number of bytes appended or 0 if out of memory.
 */
size_t mbuf_append(struct mbuf *, const void *data, size_t data_size);

/*
 * Inserts data at a specified offset in the Mbuf.
 *
 * Existing data will be shifted forwards and the buffer will
 * be grown if necessary.
 * Returns the number of bytes inserted.
 */
size_t mbuf_insert(struct mbuf *, size_t, const void *, size_t);

/* Removes `data_size` bytes from the beginning of the buffer. */
void mbuf_remove(struct mbuf *, size_t data_size);

/*
 * Resizes an Mbuf.
 *
 * If `new_size` is smaller than buffer's `len`, the
 * resize is not performed.
 */
void mbuf_resize(struct mbuf *, size_t new_size);

/* Shrinks an Mbuf by resizing its `size` to `len`. */
void mbuf_trim(struct mbuf *);

#if defined(__cplusplus)
}
#endif /* __cplusplus */

#endif /* CS_COMMON_MBUF_H_ */
#ifdef V7_MODULE_LINES
#line 1 "common/platforms/simplelink/cs_simplelink.h"
#endif
/*
 * Copyright (c) 2014-2016 Cesanta Software Limited
 * All rights reserved
 */

#ifndef CS_COMMON_PLATFORMS_SIMPLELINK_CS_SIMPLELINK_H_
#define CS_COMMON_PLATFORMS_SIMPLELINK_CS_SIMPLELINK_H_

/* If simplelink.h is already included, all bets are off. */
#if defined(MG_SOCKET_SIMPLELINK) && !defined(__SIMPLELINK_H__)

#include <stdbool.h>

#ifndef __TI_COMPILER_VERSION__
#undef __CONCAT
#undef FD_CLR
#undef FD_ISSET
#undef FD_SET
#undef FD_SETSIZE
#undef FD_ZERO
#undef fd_set
#endif

/* We want to disable SL_INC_STD_BSD_API_NAMING, so we include user.h ourselves
 * and undef it. */
#define PROVISIONING_API_H_
#include <simplelink/user.h>
#undef PROVISIONING_API_H_
#undef SL_INC_STD_BSD_API_NAMING

#include <simplelink/include/simplelink.h>
#include <simplelink/include/netapp.h>

/* Now define only the subset of the BSD API that we use.
 * Notably, close(), read() and write() are not defined. */
#define AF_INET SL_AF_INET

#define socklen_t SlSocklen_t
#define sockaddr SlSockAddr_t
#define sockaddr_in SlSockAddrIn_t
#define in_addr SlInAddr_t

#define SOCK_STREAM SL_SOCK_STREAM
#define SOCK_DGRAM SL_SOCK_DGRAM

#define htonl sl_Htonl
#define ntohl sl_Ntohl
#define htons sl_Htons
#define ntohs sl_Ntohs

#ifndef EACCES
#define EACCES SL_EACCES
#endif
#ifndef EAFNOSUPPORT
#define EAFNOSUPPORT SL_EAFNOSUPPORT
#endif
#ifndef EAGAIN
#define EAGAIN SL_EAGAIN
#endif
#ifndef EBADF
#define EBADF SL_EBADF
#endif
#ifndef EINVAL
#define EINVAL SL_EINVAL
#endif
#ifndef ENOMEM
#define ENOMEM SL_ENOMEM
#endif
#ifndef EWOULDBLOCK
#define EWOULDBLOCK SL_EWOULDBLOCK
#endif

#define SOMAXCONN 8

#ifdef __cplusplus
extern "C" {
#endif

const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
char *inet_ntoa(struct in_addr in);
int inet_pton(int af, const char *src, void *dst);

struct mg_mgr;
struct mg_connection;

typedef void (*mg_init_cb)(struct mg_mgr *mgr);
bool mg_start_task(int priority, int stack_size, mg_init_cb mg_init);

void mg_run_in_task(void (*cb)(struct mg_mgr *mgr, void *arg), void *cb_arg);

int sl_fs_init(void);

void sl_restart_cb(struct mg_mgr *mgr);

int sl_set_ssl_opts(struct mg_connection *nc);

#ifdef __cplusplus
}
#endif

#endif /* defined(MG_SOCKET_SIMPLELINK) && !defined(__SIMPLELINK_H__) */

#endif /* CS_COMMON_PLATFORMS_SIMPLELINK_CS_SIMPLELINK_H_ */
#ifdef V7_MODULE_LINES
#line 1 "common/platforms/platform_cc3200.h"
#endif
/*
 * Copyright (c) 2014-2016 Cesanta Software Limited
 * All rights reserved
 */

#ifndef CS_COMMON_PLATFORMS_PLATFORM_CC3200_H_
#define CS_COMMON_PLATFORMS_PLATFORM_CC3200_H_
#if CS_PLATFORM == CS_P_CC3200

#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <inttypes.h>
#include <stdint.h>
#include <string.h>
#include <time.h>

#ifndef __TI_COMPILER_VERSION__
#include <fcntl.h>
#include <sys/time.h>
#endif

#define MG_SOCKET_SIMPLELINK 1
#define MG_DISABLE_SOCKETPAIR 1
#define MG_DISABLE_SYNC_RESOLVER 1
#define MG_DISABLE_POPEN 1
#define MG_DISABLE_CGI 1
/* Only SPIFFS supports directories, SLFS does not. */
#ifndef CC3200_FS_SPIFFS
#define MG_DISABLE_DAV 1
#define MG_DISABLE_DIRECTORY_LISTING 1
#endif

/* Amalgamated: #include "common/platforms/simplelink/cs_simplelink.h" */

typedef int sock_t;
#define INVALID_SOCKET (-1)
#define SIZE_T_FMT "u"
typedef struct stat cs_stat_t;
#define DIRSEP '/'
#define to64(x) strtoll(x, NULL, 10)
#define INT64_FMT PRId64
#define INT64_X_FMT PRIx64
#define __cdecl

#define fileno(x) -1

/* Some functions we implement for Mongoose. */

#ifdef __cplusplus
extern "C" {
#endif

#ifdef __TI_COMPILER_VERSION__
struct SlTimeval_t;
#define timeval SlTimeval_t
int gettimeofday(struct timeval *t, void *tz);

int asprintf(char **strp, const char *fmt, ...);

#endif

/* TI's libc does not have stat & friends, add them. */
#ifdef __TI_COMPILER_VERSION__

#include <file.h>

typedef unsigned int mode_t;
typedef size_t _off_t;
typedef long ssize_t;

struct stat {
  int st_ino;
  mode_t st_mode;
  int st_nlink;
  time_t st_mtime;
  off_t st_size;
};

int _stat(const char *pathname, struct stat *st);
#define stat(a, b) _stat(a, b)

#define __S_IFMT 0170000

#define __S_IFDIR 0040000
#define __S_IFCHR 0020000
#define __S_IFREG 0100000

#define __S_ISTYPE(mode, mask) (((mode) &__S_IFMT) == (mask))

#define S_IFDIR __S_IFDIR
#define S_IFCHR __S_IFCHR
#define S_IFREG __S_IFREG
#define S_ISDIR(mode) __S_ISTYPE((mode), __S_IFDIR)
#define S_ISREG(mode) __S_ISTYPE((mode), __S_IFREG)

/* As of 5.2.7, TI compiler does not support va_copy() yet. */
#define va_copy(apc, ap) ((apc) = (ap))

#endif /* __TI_COMPILER_VERSION__ */

#ifdef CC3200_FS_SPIFFS
#include <common/spiffs/spiffs.h>

typedef struct {
  spiffs_DIR dh;
  struct spiffs_dirent de;
} DIR;

#define d_name name
#define dirent spiffs_dirent

DIR *opendir(const char *dir_name);
int closedir(DIR *dir);
struct dirent *readdir(DIR *dir);
#endif /* CC3200_FS_SPIFFS */

#ifdef CC3200_FS_SLFS
#define MG_FS_SLFS
#endif

#ifdef __cplusplus
}
#endif

#endif /* CS_PLATFORM == CS_P_CC3200 */
#endif /* CS_COMMON_PLATFORMS_PLATFORM_CC3200_H_ */
#ifdef V7_MODULE_LINES
#line 1 "common/platforms/platform_cc3100.h"
#endif
/*
 * Copyright (c) 2014-2016 Cesanta Software Limited
 * All rights reserved
 */

#ifndef CS_COMMON_PLATFORMS_PLATFORM_CC3100_H_
#define CS_COMMON_PLATFORMS_PLATFORM_CC3100_H_
#if CS_PLATFORM == CS_P_CC3100

#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <inttypes.h>
#include <stdint.h>
#include <string.h>
#include <time.h>

#define MG_SOCKET_SIMPLELINK 1
#define MG_DISABLE_SOCKETPAIR 1
#define MG_DISABLE_SYNC_RESOLVER 1
#define MG_DISABLE_POPEN 1
#define MG_DISABLE_CGI 1
#define MG_DISABLE_DAV 1
#define MG_DISABLE_DIRECTORY_LISTING 1
#define MG_DISABLE_FILESYSTEM 1

/*
 * CC3100 SDK and STM32 SDK include headers w/out path, just like
 * #include "simplelink.h". As result, we have to add all required directories
 * into Makefile IPATH and do the same thing (include w/out path)
 */

#include <simplelink.h>
#include <netapp.h>
#undef timeval 

typedef int sock_t;
#define INVALID_SOCKET (-1)

#define to64(x) strtoll(x, NULL, 10)
#define INT64_FMT PRId64
#define INT64_X_FMT PRIx64
#define SIZE_T_FMT "u"

#define SOMAXCONN 8

const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
char *inet_ntoa(struct in_addr in);
int inet_pton(int af, const char *src, void *dst);

#endif /* CS_PLATFORM == CS_P_CC3100 */
#endif /* CS_COMMON_PLATFORMS_PLATFORM_CC3100_H_ */
#ifdef V7_MODULE_LINES
#line 1 "common/platforms/platform_mbed.h"
#endif
/*
 * Copyright (c) 2014-2016 Cesanta Software Limited
 * All rights reserved
 */

#ifndef CS_COMMON_PLATFORMS_PLATFORM_MBED_H_
#define CS_COMMON_PLATFORMS_PLATFORM_MBED_H_
#if CS_PLATFORM == CS_P_MBED

/* Amalgamated: #include "mbed.h" */

#endif /* CS_PLATFORM == CS_P_MBED */
#endif /* CS_COMMON_PLATFORMS_PLATFORM_MBED_H_ */
#ifdef V7_MODULE_LINES
#line 1 "common/str_util.h"
#endif
/*
 * Copyright (c) 2015 Cesanta Software Limited
 * All rights reserved
 */

#ifndef CS_COMMON_STR_UTIL_H_
#define CS_COMMON_STR_UTIL_H_

#include <stdarg.h>
#include <stdlib.h>

#ifdef __cplusplus
extern "C" {
#endif

size_t c_strnlen(const char *s, size_t maxlen);
int c_snprintf(char *buf, size_t buf_size, const char *format, ...);
int c_vsnprintf(char *buf, size_t buf_size, const char *format, va_list ap);
/*
 * Find the first occurrence of find in s, where the search is limited to the
 * first slen characters of s.
 */
const char *c_strnstr(const char *s, const char *find, size_t slen);

#ifdef __cplusplus
}
#endif

#endif /* CS_COMMON_STR_UTIL_H_ */
#ifdef V7_MODULE_LINES
#line 1 "common/utf.h"
#endif
/*
 * Copyright (c) 2014 Cesanta Software Limited
 * All rights reserved
 */

#ifndef CS_COMMON_UTF_H_
#define CS_COMMON_UTF_H_

#if defined(__cplusplus)
extern "C" {
#endif /* __cplusplus */

typedef unsigned char uchar;

typedef unsigned short Rune; /* 16 bits */

#define nelem(a) (sizeof(a) / sizeof(a)[0])

enum {
  UTFmax = 3,               /* maximum bytes per rune */
  Runesync = 0x80,          /* cannot represent part of a UTF sequence (<) */
  Runeself = 0x80,          /* rune and UTF sequences are the same (<) */
  Runeerror = 0xFFFD        /* decoding error in UTF */
  /* Runemax    = 0xFFFC */ /* maximum rune value */
};

/* Edit .+1,/^$/ | cfn $PLAN9/src/lib9/utf/?*.c | grep -v static |grep -v __ */
int chartorune(Rune *rune, const char *str);
int fullrune(const char *str, int n);
int isdigitrune(Rune c);
int isnewline(Rune c);
int iswordchar(Rune c);
int isalpharune(Rune c);
int islowerrune(Rune c);
int isspacerune(Rune c);
int isupperrune(Rune c);
int runetochar(char *str, Rune *rune);
Rune tolowerrune(Rune c);
Rune toupperrune(Rune c);
int utfnlen(const char *s, long m);
const char *utfnshift(const char *s, long m);

#if 0 /* Not implemented. */
int istitlerune(Rune c);
int runelen(Rune c);
int runenlen(Rune *r, int nrune);
Rune *runestrcat(Rune *s1, Rune *s2);
Rune *runestrchr(Rune *s, Rune c);
Rune *runestrcpy(Rune *s1, Rune *s2);
Rune *runestrdup(Rune *s);
Rune *runestrecpy(Rune *s1, Rune *es1, Rune *s2);
int runestrcmp(Rune *s1, Rune *s2);
long runestrlen(Rune *s);
Rune *runestrncat(Rune *s1, Rune *s2, long n);
int runestrncmp(Rune *s1, Rune *s2, long n);
Rune *runestrncpy(Rune *s1, Rune *s2, long n);
Rune *runestrrchr(Rune *s, Rune c);
Rune *runestrstr(Rune *s1, Rune *s2);
Rune totitlerune(Rune c);
char *utfecpy(char *to, char *e, char *from);
int utflen(char *s);
char *utfrrune(char *s, long c);
char *utfrune(char *s, long c);
char *utfutf(char *s1, char *s2);
#endif

#if defined(__cplusplus)
}
#endif /* __cplusplus */
#endif /* CS_COMMON_UTF_H_ */
#ifdef V7_MODULE_LINES
#line 1 "common/base64.h"
#endif
/*
 * Copyright (c) 2014 Cesanta Software Limited
 * All rights reserved
 */

#ifndef CS_COMMON_BASE64_H_
#define CS_COMMON_BASE64_H_

#ifndef DISABLE_BASE64

#include <stdio.h>

#ifdef __cplusplus
extern "C" {
#endif

typedef void (*cs_base64_putc_t)(char, void *);

struct cs_base64_ctx {
  /* cannot call it putc because it's a macro on some environments */
  cs_base64_putc_t b64_putc;
  unsigned char chunk[3];
  int chunk_size;
  void *user_data;
};

void cs_base64_init(struct cs_base64_ctx *ctx, cs_base64_putc_t putc,
                    void *user_data);
void cs_base64_update(struct cs_base64_ctx *ctx, const char *str, size_t len);
void cs_base64_finish(struct cs_base64_ctx *ctx);

void cs_base64_encode(const unsigned char *src, int src_len, char *dst);
void cs_fprint_base64(FILE *f, const unsigned char *src, int src_len);
int cs_base64_decode(const unsigned char *s, int len, char *dst);

#ifdef __cplusplus
}
#endif

#endif /* DISABLE_BASE64 */

#endif /* CS_COMMON_BASE64_H_ */
#ifdef V7_MODULE_LINES
#line 1 "common/md5.h"
#endif
/*
 * Copyright (c) 2014 Cesanta Software Limited
 * All rights reserved
 */

#ifndef CS_COMMON_MD5_H_
#define CS_COMMON_MD5_H_

/* Amalgamated: #include "common/platform.h" */

#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */

typedef struct MD5Context {
  uint32_t buf[4];
  uint32_t bits[2];
  unsigned char in[64];
} MD5_CTX;

void MD5_Init(MD5_CTX *c);
void MD5_Update(MD5_CTX *c, const unsigned char *data, size_t len);
void MD5_Final(unsigned char *md, MD5_CTX *c);

/*
 * Return stringified MD5 hash for NULL terminated list of pointer/length pairs.
 * A length should be specified as size_t variable.
 * Example:
 *
 *    char buf[33];
 *    cs_md5(buf, "foo", (size_t) 3, "bar", (size_t) 3, NULL);
 */
char *cs_md5(char buf[33], ...);

/*
 * Stringify binary data. Output buffer size must be 2 * size_of_input + 1
 * because each byte of input takes 2 bytes in string representation
 * plus 1 byte for the terminating \0 character.
 */
void cs_to_hex(char *to, const unsigned char *p, size_t len);

#ifdef __cplusplus
}
#endif /* __cplusplus */

#endif /* CS_COMMON_MD5_H_ */
#ifdef V7_MODULE_LINES
#line 1 "common/sha1.h"
#endif
/*
 * Copyright (c) 2014 Cesanta Software Limited
 * All rights reserved
 */

#ifndef CS_COMMON_SHA1_H_
#define CS_COMMON_SHA1_H_

#ifndef DISABLE_SHA1

/* Amalgamated: #include "common/platform.h" */

#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */

typedef struct {
  uint32_t state[5];
  uint32_t count[2];
  unsigned char buffer[64];
} cs_sha1_ctx;

void cs_sha1_init(cs_sha1_ctx *);
void cs_sha1_update(cs_sha1_ctx *, const unsigned char *data, uint32_t len);
void cs_sha1_final(unsigned char digest[20], cs_sha1_ctx *);
void cs_hmac_sha1(const unsigned char *key, size_t key_len,
                  const unsigned char *text, size_t text_len,
                  unsigned char out[20]);
#ifdef __cplusplus
}
#endif /* __cplusplus */

#endif /* DISABLE_SHA1 */

#endif /* CS_COMMON_SHA1_H_ */
#ifdef V7_MODULE_LINES
#line 1 "common/cs_dirent.h"
#endif
/*
 * Copyright (c) 2014-2016 Cesanta Software Limited
 * All rights reserved
 */

#ifndef CS_COMMON_CS_DIRENT_H_
#define CS_COMMON_CS_DIRENT_H_

#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */

#ifdef CS_ENABLE_SPIFFS

#include <spiffs.h>

typedef struct {
  spiffs_DIR dh;
  struct spiffs_dirent de;
} DIR;

#define d_name name
#define dirent spiffs_dirent

int rmdir(const char *path);
int mkdir(const char *path, mode_t mode);

#endif

#if defined(_WIN32)
struct dirent {
  char d_name[MAX_PATH];
};

typedef struct DIR {
  HANDLE handle;
  WIN32_FIND_DATAW info;
  struct dirent result;
} DIR;
#endif

#if defined(_WIN32) || defined(CS_ENABLE_SPIFFS)
DIR *opendir(const char *dir_name);
int closedir(DIR *dir);
struct dirent *readdir(DIR *dir);
#endif

#ifdef __cplusplus
}
#endif /* __cplusplus */

#endif /* CS_COMMON_CS_DIRENT_H_ */
#ifdef V7_MODULE_LINES
#line 1 "common/cs_file.h"
#endif
/*
 * Copyright (c) 2015 Cesanta Software Limited
 * All rights reserved
 */

#ifndef CS_COMMON_CS_FILE_H_
#define CS_COMMON_CS_FILE_H_

/* Amalgamated: #include "common/platform.h" */

#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */

/*
 * Read whole file `path` in memory. It is responsibility of the caller
 * to `free()` allocated memory. File content is guaranteed to be
 * '\0'-terminated. File size is returned in `size` variable, which does not
 * count terminating `\0`.
 * Return: allocated memory, or NULL on error.
 */
char *cs_read_file(const char *path, size_t *size);

#ifdef CS_MMAP
char *cs_mmap_file(const char *path, size_t *size);
#endif

#ifdef __cplusplus
}
#endif /* __cplusplus */

#endif /* CS_COMMON_CS_FILE_H_ */
#ifdef V7_MODULE_LINES
#line 1 "common/coroutine.h"
#endif
/*
 * Copyright (c) 2015 Cesanta Software Limited
 * All rights reserved
 */

/*
 * Module that provides generic macros and functions to implement "coroutines",
 * i.e. C code that uses `mbuf` as a stack for function calls.
 *
 * More info: see the design doc: https://goo.gl/kfcG61
 */

#ifndef CS_COMMON_COROUTINE_H_
#define CS_COMMON_COROUTINE_H_

/* Amalgamated: #include "common/mbuf.h" */
/* Amalgamated: #include "common/platform.h" */

/* user-defined union, this module only operates on the pointer */
union user_arg_ret;

/*
 * Type that represents size of local function variables. We assume we'll never
 * need more than 255 bytes of stack frame.
 */
typedef uint8_t cr_locals_size_t;

/*
 * Descriptor of a single function; const array of such descriptors should
 * be given to `cr_context_init()`
 */
struct cr_func_desc {
  /*
   * Size of the function's data that should be stored on stack.
   *
   * NOTE: you should use `CR_LOCALS_SIZEOF(your_type)` instead of `sizeof()`,
   * since this value should be aligned by the word boundary, and
   * `CR_LOCALS_SIZEOF()` takes care of this.
   */
  cr_locals_size_t locals_size;
};

enum cr_status {
  CR_RES__OK,
  CR_RES__OK_YIELDED,

  CR_RES__ERR_STACK_OVERFLOW,

  /* Underflow can only be caused by memory corruption or bug in CR */
  CR_RES__ERR_STACK_DATA_UNDERFLOW,
  /* Underflow can only be caused by memory corruption or bug in CR */
  CR_RES__ERR_STACK_CALL_UNDERFLOW,

  CR_RES__ERR_UNCAUGHT_EXCEPTION,
};

/* Context of the coroutine engine */
struct cr_ctx {
  /*
   * id of the next "function" to call. If no function is going to be called,
   * it's CR_FID__NONE.
   */
  uint8_t called_fid;

  /*
   * when `called_fid` is not `CR_FID__NONE`, this field holds called
   * function's stack frame size
   */
  size_t call_locals_size;

  /*
   * when `called_fid` is not `CR_FID__NONE`, this field holds called
   * function's arguments size
   */
  size_t call_arg_size;

  /*
   * pointer to the current function's locals.
   * Needed to make `CR_CUR_LOCALS_PT()` fast.
   */
  uint8_t *p_cur_func_locals;

  /* data stack */
  struct mbuf stack_data;

  /* return stack */
  struct mbuf stack_ret;

  /* index of the current fid + 1 in return stack */
  size_t cur_fid_idx;

  /* pointer to the array of function descriptors */
  const struct cr_func_desc *p_func_descrs;

  /* thrown exception. If nothing is currently thrown, it's `CR_EXC_ID__NONE` */
  uint8_t thrown_exc;

  /* status: normally, it's `CR_RES__OK` */
  enum cr_status status;

  /*
   * pointer to user-dependent union of arguments for all functions, as well as
   * return values, yielded and resumed values.
   */
  union user_arg_ret *p_arg_retval;

  /* true if currently running function returns */
  unsigned need_return : 1;

  /* true if currently running function yields */
  unsigned need_yield : 1;

#if defined(CR_TRACK_MAX_STACK_LEN)
  size_t stack_data_max_len;
  size_t stack_ret_max_len;
#endif
};

/*
 * User's enum with function ids should use items of this one like this:
 *
 *   enum my_func_id {
 *     my_func_none = CR_FID__NONE,
 *
 *     my_foo = CR_FID__USER,
 *     my_foo1,
 *     my_foo2,
 *
 *     my_bar,
 *     my_bar1,
 *   };
 *
 */
enum cr_fid {
  CR_FID__NONE,
  CR_FID__USER,

  /* for internal usage only */
  CR_FID__TRY_MARKER = 0xff,
};

/*
 * User's enum with exception ids should use items of this one like this:
 *
 *   enum my_exc_id {
 *     MY_EXC_ID__FIRST = CR_EXC_ID__USER,
 *     MY_EXC_ID__SECOND,
 *     MY_EXC_ID__THIRD,
 *   };
 */
enum cr_exc_id {
  CR_EXC_ID__NONE,
  CR_EXC_ID__USER,
};

/*
 * A type whose size is a special case for macros `CR_LOCALS_SIZEOF()` and
 * `CR_ARG_SIZEOF()` : it is assumed as zero size.
 *
 * This hackery is needed because empty structs (that would yield sizeof 0) are
 * illegal in plain C.
 */
typedef struct { uint8_t _dummy[((cr_locals_size_t) -1)]; } cr_zero_size_type_t;

/*
 * To be used in dispatcher switch: depending on the "fid" (function id), we
 * jump to the appropriate label.
 */
#define CR_DEFINE_ENTRY_POINT(fid) \
  case fid:                        \
    goto fid

/*
 * Returns lvalue: id of the currently active "function". It just takes the id
 * from the appropriate position of the "stack".
 *
 * Client code only needs it in dispatcher switch.
 */
#define CR_CURR_FUNC_C(p_ctx) \
  *(((cr_locals_size_t *) (p_ctx)->stack_ret.buf) + (p_ctx)->cur_fid_idx - 1)

/*
 * Prepare context for calling first function.
 *
 * Should be used outside of the exec loop, right after initializing
 * context with `cr_context_init()`
 *
 * `call_fid`: id of the function to be called
 */
#define CR_FIRST_CALL_PREPARE_C(p_ctx, call_fid)                           \
  _CR_CALL_PREPARE(p_ctx, call_fid, CR_LOCALS_SIZEOF(call_fid##_locals_t), \
                   CR_ARG_SIZEOF(call_fid##_arg_t), CR_FID__NONE)

/*
 * Call "function" with id `call_fid`: uses `_CR_CALL_PREPARE()` to prepare
 * stuff, and then jumps to the `_cr_iter_begin`, which will perform all
 * necessary bookkeeping.
 *
 * Should be used from eval loop only.
 *
 * `local_ret_fid`: id of the label right after the function call (where
 * currently running function will be resumed later)
 */
#define CR_CALL_C(p_ctx, call_fid, local_ret_fid)                            \
  do {                                                                       \
    _CR_CALL_PREPARE(p_ctx, call_fid, CR_LOCALS_SIZEOF(call_fid##_locals_t), \
                     CR_ARG_SIZEOF(call_fid##_arg_t), local_ret_fid);        \
    goto _cr_iter_begin;                                                     \
  local_ret_fid:                                                             \
    /* we'll get here when called function returns */                        \
    ;                                                                        \
  } while (0)

/*
 * "Return" the value `retval` from the current "function" with id `cur_fid`.
 * You have to specify `cur_fid` since different functions may have different
 * return types.
 *
 * Should be used from eval loop only.
 */
#define CR_RETURN_C(p_ctx, cur_fid, retval)         \
  do {                                              \
    /* copy ret to arg_retval */                    \
    CR_ARG_RET_PT_C(p_ctx)->ret.cur_fid = (retval); \
    /* set need_return flag */                      \
    (p_ctx)->need_return = 1;                       \
    goto _cr_iter_begin;                            \
  } while (0)

/*
 * Same as `CR_RETURN_C`, but without any return value
 */
#define CR_RETURN_VOID_C(p_ctx) \
  do {                          \
    /* set need_return flag */  \
    (p_ctx)->need_return = 1;   \
    goto _cr_iter_begin;        \
  } while (0)

/*
 * Yield with the value `value`. It will be set just by the assigment operator
 * in the `yielded` field of the `union user_arg_ret`.
 *
 * `local_ret_fid`: id of the label right after the yielding (where currently
 * running function will be resumed later)
 *
 */
#define CR_YIELD_C(p_ctx, value, local_ret_fid)           \
  do {                                                    \
    /* copy ret to arg_retval */                          \
    CR_ARG_RET_PT_C(p_ctx)->yielded = (value);            \
    /* set need_yield flag */                             \
    (p_ctx)->need_yield = 1;                              \
                                                          \
    /* adjust return func id */                           \
    CR_CURR_FUNC_C(p_ctx) = (local_ret_fid);              \
                                                          \
    goto _cr_iter_begin;                                  \
  local_ret_fid:                                          \
    /* we'll get here when the machine will be resumed */ \
    ;                                                     \
  } while (0)

/*
 * Prepare context for resuming with the given value. After using this
 * macro, you need to call your user-dependent exec function.
 */
#define CR_RESUME_C(p_ctx, value)                \
  do {                                           \
    if ((p_ctx)->status == CR_RES__OK_YIELDED) { \
      CR_ARG_RET_PT_C(p_ctx)->resumed = (value); \
      (p_ctx)->status = CR_RES__OK;              \
    }                                            \
  } while (0)

/*
 * Evaluates to the yielded value (value given to `CR_YIELD_C()`)
 */
#define CR_YIELDED_C(p_ctx) (CR_ARG_RET_PT_C(p_ctx)->yielded)

/*
 * Evaluates to the value given to `CR_RESUME_C()`
 */
#define CR_RESUMED_C(p_ctx) (CR_ARG_RET_PT_C(p_ctx)->resumed)

/*
 * Beginning of the try-catch block.
 *
 * Should be used in eval loop only.
 *
 * `first_catch_fid`: function id of the first catch block.
 */
#define CR_TRY_C(p_ctx, first_catch_fid)                                   \
  do {                                                                     \
    _CR_STACK_RET_ALLOC((p_ctx), _CR_TRY_SIZE);                            \
    /* update pointer to current function's locals (may be invalidated) */ \
    _CR_CUR_FUNC_LOCALS_UPD(p_ctx);                                        \
    /*  */                                                                 \
    _CR_TRY_MARKER(p_ctx) = CR_FID__TRY_MARKER;                            \
    _CR_TRY_CATCH_FID(p_ctx) = (first_catch_fid);                          \
  } while (0)

/*
 * Beginning of the individual catch block (and the end of the previous one, if
 * any)
 *
 * Should be used in eval loop only.
 *
 * `exc_id`: exception id to catch
 *
 * `catch_fid`: function id of this catch block.
 *
 * `next_catch_fid`: function id of the next catch block (or of the
 * `CR_ENDCATCH()`)
 */
#define CR_CATCH_C(p_ctx, exc_id, catch_fid, next_catch_fid) \
  catch_fid:                                                 \
  do {                                                       \
    if ((p_ctx)->thrown_exc != (exc_id)) {                   \
      goto next_catch_fid;                                   \
    }                                                        \
    (p_ctx)->thrown_exc = CR_EXC_ID__NONE;                   \
  } while (0)

/*
 * End of all catch blocks.
 *
 * Should be used in eval loop only.
 *
 * `endcatch_fid`: function id of this endcatch.
 */
#define CR_ENDCATCH_C(p_ctx, endcatch_fid)                                   \
  endcatch_fid:                                                              \
  do {                                                                       \
    (p_ctx)->stack_ret.len -= _CR_TRY_SIZE;                                  \
    /* if we still have non-handled exception, continue unwinding "stack" */ \
    if ((p_ctx)->thrown_exc != CR_EXC_ID__NONE) {                            \
      goto _cr_iter_begin;                                                   \
    }                                                                        \
  } while (0)

/*
 * Throw exception.
 *
 * Should be used from eval loop only.
 *
 * `exc_id`: exception id to throw
 */
#define CR_THROW_C(p_ctx, exc_id)                        \
  do {                                                   \
    assert((enum cr_exc_id)(exc_id) != CR_EXC_ID__NONE); \
    /* clear need_return flag */                         \
    (p_ctx)->thrown_exc = (exc_id);                      \
    goto _cr_iter_begin;                                 \
  } while (0)

/*
 * Get latest returned value from the given "function".
 *
 * `fid`: id of the function which returned value. Needed to ret value value
 * from the right field in the `(p_ctx)->arg_retval.ret` (different functions
 * may have different return types)
 */
#define CR_RETURNED_C(p_ctx, fid) (CR_ARG_RET_PT_C(p_ctx)->ret.fid)

/*
 * Get currently thrown exception id. If nothing is being thrown at the moment,
 * `CR_EXC_ID__NONE` is returned
 */
#define CR_THROWN_C(p_ctx) ((p_ctx)->thrown_exc)

/*
 * Like `sizeof()`, but it always evaluates to the multiple of `sizeof(void *)`
 *
 * It should be used for (struct cr_func_desc)::locals_size
 *
 * NOTE: instead of checking `sizeof(type) <= ((cr_locals_size_t) -1)`, I'd
 * better put the calculated value as it is, and if it overflows, then compiler
 * will generate warning, and this would help us to reveal our mistake. But
 * unfortunately, clang *always* generates this warning (even if the whole
 * expression yields 0), so we have to apply a bit more of dirty hacks here.
 */
#define CR_LOCALS_SIZEOF(type)                                                \
  ((sizeof(type) == sizeof(cr_zero_size_type_t))                              \
       ? 0                                                                    \
       : (sizeof(type) <= ((cr_locals_size_t) -1)                             \
              ? ((cr_locals_size_t)(((sizeof(type)) + (sizeof(void *) - 1)) & \
                                    (~(sizeof(void *) - 1))))                 \
              : ((cr_locals_size_t) -1)))

#define CR_ARG_SIZEOF(type) \
  ((sizeof(type) == sizeof(cr_zero_size_type_t)) ? 0 : sizeof(type))

/*
 * Returns pointer to the current function's stack locals, and casts to given
 * type.
 *
 * Typical usage might look as follows:
 *
 *    #undef L
 *    #define L CR_CUR_LOCALS_PT(p_ctx, struct my_foo_locals)
 *
 * Then, assuming `struct my_foo_locals` has the field `bar`, we can access it
 * like this:
 *
 *    L->bar
 */
#define CR_CUR_LOCALS_PT_C(p_ctx, type) ((type *) ((p_ctx)->p_cur_func_locals))

/*
 * Returns pointer to the user-defined union of arguments and return values:
 * `union user_arg_ret`
 */
#define CR_ARG_RET_PT_C(p_ctx) ((p_ctx)->p_arg_retval)

#define CR_ARG_RET_PT() CR_ARG_RET_PT_C(p_ctx)

#define CR_CUR_LOCALS_PT(type) CR_CUR_LOCALS_PT_C(p_ctx, type)

#define CR_CURR_FUNC() CR_CURR_FUNC_C(p_ctx)

#define CR_CALL(call_fid, local_ret_fid) \
  CR_CALL_C(p_ctx, call_fid, local_ret_fid)

#define CR_RETURN(cur_fid, retval) CR_RETURN_C(p_ctx, cur_fid, retval)

#define CR_RETURN_VOID() CR_RETURN_VOID_C(p_ctx)

#define CR_RETURNED(fid) CR_RETURNED_C(p_ctx, fid)

#define CR_YIELD(value, local_ret_fid) CR_YIELD_C(p_ctx, value, local_ret_fid)

#define CR_YIELDED() CR_YIELDED_C(p_ctx)

#define CR_RESUME(value) CR_RESUME_C(p_ctx, value)

#define CR_RESUMED() CR_RESUMED_C(p_ctx)

#define CR_TRY(catch_name) CR_TRY_C(p_ctx, catch_name)

#define CR_CATCH(exc_id, catch_name, next_catch_name) \
  CR_CATCH_C(p_ctx, exc_id, catch_name, next_catch_name)

#define CR_ENDCATCH(endcatch_name) CR_ENDCATCH_C(p_ctx, endcatch_name)

#define CR_THROW(exc_id) CR_THROW_C(p_ctx, exc_id)

/* Private macros {{{ */

#define _CR_CUR_FUNC_LOCALS_UPD(p_ctx)                                 \
  do {                                                                 \
    (p_ctx)->p_cur_func_locals = (uint8_t *) (p_ctx)->stack_data.buf + \
                                 (p_ctx)->stack_data.len -             \
                                 _CR_CURR_FUNC_LOCALS_SIZE(p_ctx);     \
  } while (0)

/*
 * Size of the stack needed for each try-catch block.
 * Use `_CR_TRY_MARKER()` and `_CR_TRY_CATCH_FID()` to get/set parts.
 */
#define _CR_TRY_SIZE 2 /*CR_FID__TRY_MARKER, catch_fid*/

/*
 * Evaluates to lvalue where `CR_FID__TRY_MARKER` should be stored
 */
#define _CR_TRY_MARKER(p_ctx) \
  *(((uint8_t *) (p_ctx)->stack_ret.buf) + (p_ctx)->stack_ret.len - 1)

/*
 * Evaluates to lvalue where `catch_fid` should be stored
 */
#define _CR_TRY_CATCH_FID(p_ctx) \
  *(((uint8_t *) (p_ctx)->stack_ret.buf) + (p_ctx)->stack_ret.len - 2)

#define _CR_CURR_FUNC_LOCALS_SIZE(p_ctx) \
  ((p_ctx)->p_func_descrs[CR_CURR_FUNC_C(p_ctx)].locals_size)

/*
 * Prepare context for calling next function.
 *
 * See comments for `CR_CALL()` macro.
 */
#define _CR_CALL_PREPARE(p_ctx, _call_fid, _locals_size, _arg_size, \
                         local_ret_fid)                             \
  do {                                                              \
    /* adjust return func id */                                     \
    CR_CURR_FUNC_C(p_ctx) = (local_ret_fid);                        \
                                                                    \
    /* set called_fid */                                            \
    (p_ctx)->called_fid = (_call_fid);                              \
                                                                    \
    /* set sizes: locals and arg */                                 \
    (p_ctx)->call_locals_size = (_locals_size);                     \
    (p_ctx)->call_arg_size = (_arg_size);                           \
  } while (0)

#define _CR_STACK_DATA_OVF_CHECK(p_ctx, inc) (0)

#define _CR_STACK_DATA_UND_CHECK(p_ctx, dec) ((p_ctx)->stack_data.len < (dec))

#define _CR_STACK_RET_OVF_CHECK(p_ctx, inc) (0)

#define _CR_STACK_RET_UND_CHECK(p_ctx, dec) ((p_ctx)->stack_ret.len < (dec))

#define _CR_STACK_FID_OVF_CHECK(p_ctx, inc) (0)

#define _CR_STACK_FID_UND_CHECK(p_ctx, dec) ((p_ctx)->cur_fid_idx < (dec))

#if defined(CR_TRACK_MAX_STACK_LEN)

#define _CR_STACK_DATA_ALLOC(p_ctx, inc)                         \
  do {                                                           \
    mbuf_append(&((p_ctx)->stack_data), NULL, (inc));            \
    if ((p_ctx)->stack_data_max_len < (p_ctx)->stack_data.len) { \
      (p_ctx)->stack_data_max_len = (p_ctx)->stack_data.len;     \
    }                                                            \
  } while (0)

#define _CR_STACK_RET_ALLOC(p_ctx, inc)                        \
  do {                                                         \
    mbuf_append(&((p_ctx)->stack_ret), NULL, (inc));           \
    if ((p_ctx)->stack_ret_max_len < (p_ctx)->stack_ret.len) { \
      (p_ctx)->stack_ret_max_len = (p_ctx)->stack_ret.len;     \
    }                                                          \
  } while (0)

#else

#define _CR_STACK_DATA_ALLOC(p_ctx, inc)              \
  do {                                                \
    mbuf_append(&((p_ctx)->stack_data), NULL, (inc)); \
  } while (0)

#define _CR_STACK_RET_ALLOC(p_ctx, inc)              \
  do {                                               \
    mbuf_append(&((p_ctx)->stack_ret), NULL, (inc)); \
  } while (0)

#endif

#define _CR_STACK_DATA_FREE(p_ctx, dec) \
  do {                                  \
    (p_ctx)->stack_data.len -= (dec);   \
  } while (0)

#define _CR_STACK_RET_FREE(p_ctx, dec) \
  do {                                 \
    (p_ctx)->stack_ret.len -= (dec);   \
  } while (0)

#define _CR_STACK_FID_ALLOC(p_ctx, inc) \
  do {                                  \
    (p_ctx)->cur_fid_idx += (inc);      \
  } while (0)

#define _CR_STACK_FID_FREE(p_ctx, dec) \
  do {                                 \
    (p_ctx)->cur_fid_idx -= (dec);     \
  } while (0)

/* }}} */

/*
 * Should be used in eval loop right after `_cr_iter_begin:` label
 */
enum cr_status cr_on_iter_begin(struct cr_ctx *p_ctx);

/*
 * Initialize context `p_ctx`.
 *
 * `p_arg_retval`: pointer to the user-defined `union user_arg_ret`
 *
 * `p_func_descrs`: array of all user function descriptors
 */
void cr_context_init(struct cr_ctx *p_ctx, union user_arg_ret *p_arg_retval,
                     size_t arg_retval_size,
                     const struct cr_func_desc *p_func_descrs);

/*
 * free resources occupied by context (at least, "stack" arrays)
 */
void cr_context_free(struct cr_ctx *p_ctx);

#endif /* CS_COMMON_COROUTINE_H_ */
#ifdef V7_MODULE_LINES
#line 1 "v7/src/features_profiles.h"
#endif
/*
 * Copyright (c) 2014 Cesanta Software Limited
 * All rights reserved
 */

#ifndef CS_V7_SRC_FEATURES_PROFILES_H_
#define CS_V7_SRC_FEATURES_PROFILES_H_

#define V7_BUILD_PROFILE_MINIMAL 1
#define V7_BUILD_PROFILE_MEDIUM 2
#define V7_BUILD_PROFILE_FULL 3

#ifndef V7_BUILD_PROFILE
#define V7_BUILD_PROFILE V7_BUILD_PROFILE_FULL
#endif

#endif /* CS_V7_SRC_FEATURES_PROFILES_H_ */
#ifdef V7_MODULE_LINES
#line 1 "v7/src/features_minimal.h"
#endif
/*
 * Copyright (c) 2014 Cesanta Software Limited
 * All rights reserved
 */

/* Amalgamated: #include "v7/src/features_profiles.h" */

#if V7_BUILD_PROFILE == V7_BUILD_PROFILE_MINIMAL

/* This space is intentionally left blank. */

#endif /* CS_V7_SRC_FEATURES_MINIMAL_H_ */
#ifdef V7_MODULE_LINES
#line 1 "v7/src/features_medium.h"
#endif
/*
 * Copyright (c) 2014 Cesanta Software Limited
 * All rights reserved
 */

/* Amalgamated: #include "v7/src/features_profiles.h" */

#if V7_BUILD_PROFILE == V7_BUILD_PROFILE_MEDIUM

#define V7_ENABLE__Date 1
#define V7_ENABLE__Date__now 1
#define V7_ENABLE__Date__UTC 1
#define V7_ENABLE__Math 1
#define V7_ENABLE__Math__atan2 1
#define V7_ENABLE__RegExp 1

#endif /* CS_V7_SRC_FEATURES_MEDIUM_H_ */
#ifdef V7_MODULE_LINES
#line 1 "v7/src/features_full.h"
#endif
/*
 * Copyright (c) 2014 Cesanta Software Limited
 * All rights reserved
 */

#ifndef CS_V7_SRC_FEATURES_FULL_H_
#define CS_V7_SRC_FEATURES_FULL_H_

/* Amalgamated: #include "v7/src/features_profiles.h" */

#if V7_BUILD_PROFILE == V7_BUILD_PROFILE_FULL
/*
 * DO NOT EDIT.
 * This file is generated by scripts/gen-features-full.pl.
 */
#ifndef CS_ENABLE_UTF8
#define CS_ENABLE_UTF8 1
#endif

#define V7_ENABLE__Array__reduce 1
#define V7_ENABLE__Blob 1
#define V7_ENABLE__Date 1
#define V7_ENABLE__Date__UTC 1
#define V7_ENABLE__Date__getters 1
#define V7_ENABLE__Date__now 1
#define V7_ENABLE__Date__parse 1
#define V7_ENABLE__Date__setters 1
#define V7_ENABLE__Date__toJSON 1
#define V7_ENABLE__Date__toLocaleString 1
#define V7_ENABLE__Date__toString 1
#define V7_ENABLE__File__list 1
#define V7_ENABLE__File__require 1
#define V7_ENABLE__Function__bind 1
#define V7_ENABLE__Function__call 1
#define V7_ENABLE__Math 1
#define V7_ENABLE__Math__abs 1
#define V7_ENABLE__Math__acos 1
#define V7_ENABLE__Math__asin 1
#define V7_ENABLE__Math__atan 1
#define V7_ENABLE__Math__atan2 1
#define V7_ENABLE__Math__ceil 1
#define V7_ENABLE__Math__constants 1
#define V7_ENABLE__Math__cos 1
#define V7_ENABLE__Math__exp 1
#define V7_ENABLE__Math__floor 1
#define V7_ENABLE__Math__log 1
#define V7_ENABLE__Math__max 1
#define V7_ENABLE__Math__min 1
#define V7_ENABLE__Math__pow 1
#define V7_ENABLE__Math__random 1
#define V7_ENABLE__Math__round 1
#define V7_ENABLE__Math__sin 1
#define V7_ENABLE__Math__sqrt 1
#define V7_ENABLE__Math__tan 1
#define V7_ENABLE__Memory__stats 1
#define V7_ENABLE__NUMBER__NEGATIVE_INFINITY 1
#define V7_ENABLE__NUMBER__POSITIVE_INFINITY 1
#define V7_ENABLE__Object__create 1
#define V7_ENABLE__Object__defineProperties 1
#define V7_ENABLE__Object__getOwnPropertyDescriptor 1
#define V7_ENABLE__Object__getOwnPropertyNames 1
#define V7_ENABLE__Object__getPrototypeOf 1
#define V7_ENABLE__Object__hasOwnProperty 1
#define V7_ENABLE__Object__isExtensible 1
#define V7_ENABLE__Object__isFrozen 1
#define V7_ENABLE__Object__isPrototypeOf 1
#define V7_ENABLE__Object__isSealed 1
#define V7_ENABLE__Object__keys 1
#define V7_ENABLE__Object__preventExtensions 1
#define V7_ENABLE__Object__propertyIsEnumerable 1
#define V7_ENABLE__Proxy 1
#define V7_ENABLE__RegExp 1
#define V7_ENABLE__StackTrace 1
#define V7_ENABLE__String__localeCompare 1
#define V7_ENABLE__String__localeLowerCase 1
#define V7_ENABLE__String__localeUpperCase 1

#endif /* V7_BUILD_PROFILE == V7_BUILD_PROFILE_FULL */

#endif /* CS_V7_SRC_FEATURES_FULL_H_ */
#ifdef V7_MODULE_LINES
#line 1 "v7/src/v7_features.h"
#endif
/*
 * Copyright (c) 2014 Cesanta Software Limited
 * All rights reserved
 */

#ifndef CS_V7_SRC_V7_FEATURES_H_
#define CS_V7_SRC_V7_FEATURES_H_

/* Only one will actually be used based on V7_BUILD_PROFILE. */
/* Amalgamated: #include "v7/src/features_minimal.h" */
/* Amalgamated: #include "v7/src/features_medium.h" */
/* Amalgamated: #include "v7/src/features_full.h" */

#endif /* CS_V7_SRC_V7_FEATURES_H_ */
#ifdef V7_MODULE_LINES
#line 1 "v7/src/platform.h"
#endif
/*
 * Copyright (c) 2014 Cesanta Software Limited
 * All rights reserved
 */

#ifndef CS_V7_SRC_PLATFORM_H_
#define CS_V7_SRC_PLATFORM_H_

#ifdef __arm
#undef V7_ENABLE__Date
#define V7_ENABLE__Date 0
#endif

#endif /* CS_V7_SRC_PLATFORM_H_ */
#ifdef V7_MODULE_LINES
#line 1 "v7/src/internal.h"
#endif
/*
 * Copyright (c) 2014 Cesanta Software Limited
 * All rights reserved
 */

#ifndef CS_V7_SRC_INTERNAL_H_
#define CS_V7_SRC_INTERNAL_H_

/* Amalgamated: #include "v7/src/license.h" */

/* Check whether we're compiling in an environment with no filesystem */
#if defined(ARDUINO) && (ARDUINO == 106)
#define V7_NO_FS
#endif

#ifndef FAST
#define FAST
#endif

#ifndef STATIC
#define STATIC
#endif

#ifndef ENDL
#define ENDL "\n"
#endif

/*
 * In some compilers (watcom) NAN == NAN (and other comparisons) don't follow
 * the rules of IEEE 754. Since we don't know a priori which compilers
 * will generate correct code, we disable the fallback on selected platforms.
 * TODO(mkm): selectively disable on clang/gcc once we test this out.
 */
#define V7_BROKEN_NAN

#undef _POSIX_C_SOURCE
#define _POSIX_C_SOURCE 200809L

#include <assert.h>
#ifndef NO_LIBC
#include <ctype.h>
#endif
#include <errno.h>
#include <float.h>
#include <limits.h>
#include <math.h>
#include <stdarg.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <setjmp.h>

/* Public API. Implemented in api.c */
/* Amalgamated: #include "common/platform.h" */

#ifdef V7_WINDOWS
#define vsnprintf _vsnprintf
#define snprintf _snprintf

/* VS2015 Update 1 has ISO C99 `isnan` and `isinf` defined in math.h */
#if _MSC_FULL_VER < 190023506
#define isnan(x) _isnan(x)
#define isinf(x) (!_finite(x))
#endif

#define __unused __pragma(warning(suppress : 4100))
typedef __int64 int64_t;
typedef int int32_t;
typedef unsigned int uint32_t;
typedef unsigned short uint16_t;
typedef unsigned char uint8_t;

/* For 64bit VisualStudio 2010 */
#ifndef _UINTPTR_T_DEFINED
typedef unsigned long uintptr_t;
#endif

#ifndef __func__
#define __func__ ""
#endif

#else
#include <stdint.h>
#endif

/* Amalgamated: #include "v7/src/v7_features.h" */
/* Amalgamated: #include "v7/src/platform.h" */

/* MSVC6 doesn't have standard C math constants defined */
#ifndef M_E
#define M_E 2.71828182845904523536028747135266250
#endif

#ifndef M_LOG2E
#define M_LOG2E 1.44269504088896340735992468100189214
#endif

#ifndef M_LOG10E
#define M_LOG10E 0.434294481903251827651128918916605082
#endif

#ifndef M_LN2
#define M_LN2 0.693147180559945309417232121458176568
#endif

#ifndef M_LN10
#define M_LN10 2.30258509299404568401799145468436421
#endif

#ifndef M_PI
#define M_PI 3.14159265358979323846264338327950288
#endif

#ifndef M_SQRT2
#define M_SQRT2 1.41421356237309504880168872420969808
#endif

#ifndef M_SQRT1_2
#define M_SQRT1_2 0.707106781186547524400844362104849039
#endif

#ifndef NAN
extern double _v7_nan;
#define HAS_V7_NAN
#define NAN (_v7_nan)
#endif

#ifndef INFINITY
extern double _v7_infinity;
#define HAS_V7_INFINITY
#define INFINITY (_v7_infinity)
#endif

#ifndef EXIT_SUCCESS
#define EXIT_SUCCESS 0
#endif

#ifndef EXIT_FAILURE
#define EXIT_FAILURE 1
#endif

#if defined(V7_ENABLE_GC_CHECK) || defined(V7_STACK_GUARD_MIN_SIZE) || \
    defined(V7_ENABLE_STACK_TRACKING) || defined(V7_ENABLE_CALL_TRACE)
/* Need to enable GCC/clang instrumentation */
#define V7_CYG_PROFILE_ON
#endif

#if defined(V7_CYG_PROFILE_ON)
extern struct v7 *v7_head;

#if defined(V7_STACK_GUARD_MIN_SIZE)
extern void *v7_sp_limit;
#endif
#endif

#ifndef ARRAY_SIZE
#define ARRAY_SIZE(array) (sizeof(array) / sizeof(array[0]))
#endif

#define V7_STATIC_ASSERT(COND, MSG) \
  typedef char static_assertion_##MSG[2 * (!!(COND)) - 1]

#define BUF_LEFT(size, used) (((size_t)(used) < (size)) ? ((size) - (used)) : 0)

#endif /* CS_V7_SRC_INTERNAL_H_ */
#ifdef V7_MODULE_LINES
#line 1 "v7/src/core_public.h"
#endif
/*
 * Copyright (c) 2014 Cesanta Software Limited
 * All rights reserved
 */

/*
 * === Core
 */

#ifndef CS_V7_SRC_CORE_PUBLIC_H_
#define CS_V7_SRC_CORE_PUBLIC_H_

#ifndef _POSIX_C_SOURCE
#define _POSIX_C_SOURCE 200809L
#endif

/* Amalgamated: #include "v7/src/license.h" */
/* Amalgamated: #include "v7/src/v7_features.h" */
/* Amalgamated: #include "v7/src/platform.h" */

#include <stddef.h> /* For size_t */
#include <stdio.h>  /* For FILE */

#if defined(__cplusplus)
extern "C" {
#endif /* __cplusplus */

/*
 * TODO(dfrank) : improve amalgamation, so that we'll be able to include
 * files here, and include common/platform.h
 *
 * For now, copy-pasting `WARN_UNUSED_RESULT` here
 */
#ifdef __GNUC__
#define WARN_UNUSED_RESULT __attribute__((warn_unused_result))
#define NOINSTR __attribute__((no_instrument_function))
#else
#define WARN_UNUSED_RESULT
#define NOINSTR
#endif

#define V7_VERSION "1.0"

#if (defined(_WIN32) && !defined(__MINGW32__) && !defined(__MINGW64__)) || \
    (defined(_MSC_VER) && _MSC_VER <= 1200)
#define V7_WINDOWS
#endif

#ifdef V7_WINDOWS
typedef unsigned __int64 uint64_t;
#else
#include <inttypes.h>
#endif
/* 64-bit value, used to store JS values */
typedef uint64_t v7_val_t;

/* JavaScript `null` value */
#define V7_NULL ((uint64_t) 0xfffe << 48)

/* JavaScript `undefined` value */
#define V7_UNDEFINED ((uint64_t) 0xfffd << 48)

/* This if-0 is a dirty workaround to force etags to pick `struct v7` */
#if 0
/* Opaque structure. V7 engine context. */
struct v7 {
  /* ... */
};
#endif

struct v7;

/*
 * Code which is returned by some of the v7 functions. If something other than
 * `V7_OK` is returned from some function, the caller function typically should
 * either immediately cleanup and return the code further, or handle the error.
 */
enum v7_err {
  V7_OK,
  V7_SYNTAX_ERROR,
  V7_EXEC_EXCEPTION,
  V7_AST_TOO_LARGE,
  V7_INTERNAL_ERROR,
};

/* JavaScript -> C call interface */
WARN_UNUSED_RESULT
typedef enum v7_err(v7_cfunction_t)(struct v7 *v7, v7_val_t *res);

/* Create V7 instance */
struct v7 *v7_create(void);

/*
 * Customizations of initial V7 state; used by `v7_create_opt()`.
 */
struct v7_create_opts {
  size_t object_arena_size;
  size_t function_arena_size;
  size_t property_arena_size;
#ifdef V7_STACK_SIZE
  void *c_stack_base;
#endif
#ifdef V7_FREEZE
  /* if not NULL, dump JS heap after init */
  char *freeze_file;
#endif
};

/*
 * Like `v7_create()`, but allows to customize initial v7 state, see `struct
 * v7_create_opts`.
 */
struct v7 *v7_create_opt(struct v7_create_opts opts);

/* Destroy V7 instance */
void v7_destroy(struct v7 *v7);

/* Return root level (`global`) object of the given V7 instance. */
v7_val_t v7_get_global(struct v7 *v);

/* Return current `this` object. */
v7_val_t v7_get_this(struct v7 *v);

/* Return current `arguments` array */
v7_val_t v7_get_arguments(struct v7 *v);

/* Return i-th argument */
v7_val_t v7_arg(struct v7 *v, unsigned long i);

/* Return the length of `arguments` */
unsigned long v7_argc(struct v7 *v7);

/*
 * Tells the GC about a JS value variable/field owned
 * by C code.
 *
 * User C code should own v7_val_t variables
 * if the value's lifetime crosses any invocation
 * to the v7 runtime that creates new objects or new
 * properties and thus can potentially trigger GC.
 *
 * The registration of the variable prevents the GC from mistakenly treat
 * the object as garbage. The GC might be triggered potentially
 * allows the GC to update pointers
 *
 * User code should also explicitly disown the variables with v7_disown once
 * it goes out of scope or the structure containing the v7_val_t field is freed.
 *
 * Example:
 *
 *  ```
 *    struct v7_val cb;
 *    v7_own(v7, &cb);
 *    cb = v7_array_get(v7, args, 0);
 *    // do something with cb
 *    v7_disown(v7, &cb);
 *  ```
 */
void v7_own(struct v7 *v7, v7_val_t *v);

/*
 * Returns 1 if value is found, 0 otherwise
 */
int v7_disown(struct v7 *v7, v7_val_t *v);

/*
 * Enable or disable GC.
 *
 * Must be called before invoking v7_exec or v7_apply
 * from within a cfunction unless you know what you're doing.
 *
 * GC is disabled during execution of cfunctions in order to simplify
 * memory management of simple cfunctions.
 * However executing even small snippets of JS code causes a lot of memory
 * pressure. Enabling GC solves that but forces you to take care of the
 * reachability of your temporary V7 v7_val_t variables, as the GC needs
 * to know where they are since objects and strings can be either reclaimed
 * or relocated during a GC pass.
 */
void v7_set_gc_enabled(struct v7 *v7, int enabled);

/*
 * Set an optional C stack limit.
 *
 * It sets a flag that will cause the interpreter
 * to throw an InterruptedError.
 * It's safe to call it from signal handlers and ISRs
 * on single threaded environments.
 */
void v7_interrupt(struct v7 *v7);

/* Returns last parser error message. TODO: rename it to `v7_get_error()` */
const char *v7_get_parser_error(struct v7 *v7);

#if defined(V7_ENABLE_STACK_TRACKING)
/*
 * Available if only `V7_ENABLE_STACK_TRACKING` is defined.
 *
 * Stack metric id. See `v7_stack_stat()`
 */
enum v7_stack_stat_what {
  /* max stack size consumed by `i_exec()` */
  V7_STACK_STAT_EXEC,
  /* max stack size consumed by `parse()` (which is called from `i_exec()`) */
  V7_STACK_STAT_PARSER,

  V7_STACK_STATS_CNT
};

/*
 * Available if only `V7_ENABLE_STACK_TRACKING` is defined.
 *
 * Returns stack metric specified by the metric id `what`. See
 * `v7_stack_stat_clean()`
 */
int v7_stack_stat(struct v7 *v7, enum v7_stack_stat_what what);

/*
 * Available if only `V7_ENABLE_STACK_TRACKING` is defined.
 *
 * Clean all stack statistics gathered so far. See `v7_stack_stat()`
 */
void v7_stack_stat_clean(struct v7 *v7);
#endif

#if defined(__cplusplus)
}
#endif /* __cplusplus */

#endif /* CS_V7_SRC_CORE_PUBLIC_H_ */
#ifdef V7_MODULE_LINES
#line 1 "v7/src/std_error.h"
#endif
/*
 * Copyright (c) 2014 Cesanta Software Limited
 * All rights reserved
 */

#ifndef CS_V7_SRC_STD_ERROR_H_
#define CS_V7_SRC_STD_ERROR_H_

/* Amalgamated: #include "v7/src/license.h" */

struct v7;

/*
 * JavaScript error types
 */
#define TYPE_ERROR "TypeError"
#define SYNTAX_ERROR "SyntaxError"
#define REFERENCE_ERROR "ReferenceError"
#define INTERNAL_ERROR "InternalError"
#define RANGE_ERROR "RangeError"
#define EVAL_ERROR "EvalError"
#define ERROR_CTOR_MAX 6
/*
 * TODO(mkm): EvalError is not so important, we should guard it behind
 * something like `V7_ENABLE__EvalError`. However doing so makes it hard to
 * keep ERROR_CTOR_MAX up to date; perhaps let's find a better way of doing it.
 *
 * EvalError is useful mostly because we now have ecma tests failing:
 *
 * 8129 FAIL ch15/15.4/15.4.4/15.4.4.16/15.4.4.16-7-c-iii-24.js (tail -c
 * +7600043 tests/ecmac.db|head -c 496): [{"message":"[EvalError] is not
 * defined"}]
 *
 * Those tests are not EvalError specific, and they do test that the exception
 * handling machinery works as intended.
 */

#if defined(__cplusplus)
extern "C" {
#endif /* __cplusplus */

V7_PRIVATE void init_error(struct v7 *v7);

#if defined(__cplusplus)
}
#endif /* __cplusplus */

#endif /* CS_V7_SRC_STD_ERROR_H_ */
#ifdef V7_MODULE_LINES
#line 1 "v7/src/mm.h"
#endif
/*
 * Copyright (c) 2014 Cesanta Software Limited
 * All rights reserved
 */

#ifndef CS_V7_SRC_MM_H_
#define CS_V7_SRC_MM_H_

/* Amalgamated: #include "v7/src/internal.h" */

typedef void (*gc_cell_destructor_t)(struct v7 *v7, void *);

struct gc_block {
  struct gc_block *next;
  struct gc_cell *base;
  size_t size;
};

struct gc_arena {
  struct gc_block *blocks;
  size_t size_increment;
  struct gc_cell *free; /* head of free list */
  size_t cell_size;

#if V7_ENABLE__Memory__stats
  unsigned long allocations; /* cumulative counter of allocations */
  unsigned long garbage;     /* cumulative counter of garbage */
  unsigned long alive;       /* number of living cells */
#endif

  gc_cell_destructor_t destructor;

  int verbose;
  const char *name; /* for debugging purposes */
};

#endif /* CS_V7_SRC_MM_H_ */
#ifdef V7_MODULE_LINES
#line 1 "v7/src/parser.h"
#endif
/*
 * Copyright (c) 2014 Cesanta Software Limited
 * All rights reserved
 */

#ifndef CS_V7_SRC_PARSER_H_
#define CS_V7_SRC_PARSER_H_

/* Amalgamated: #include "v7/src/internal.h" */
/* Amalgamated: #include "v7/src/core_public.h" */

#if !defined(V7_NO_COMPILER)

struct v7;
struct ast;

#if defined(__cplusplus)
extern "C" {
#endif /* __cplusplus */

struct v7_pstate {
  const char *file_name;
  const char *source_code;
  const char *pc;      /* Current parsing position */
  const char *src_end; /* End of source code */
  int line_no;         /* Line number */
  int prev_line_no;    /* Line number of previous token */
  int inhibit_in;      /* True while `in` expressions are inhibited */
  int in_function;     /* True if in a function */
  int in_loop;         /* True if in a loop */
  int in_switch;       /* True if in a switch block */
  int in_strict;       /* True if in strict mode */
};

V7_PRIVATE enum v7_err parse(struct v7 *v7, struct ast *a, const char *src,
                             size_t src_len, int is_json);

#if defined(__cplusplus)
}
#endif /* __cplusplus */

#endif /* V7_NO_COMPILER */

#endif /* CS_V7_SRC_PARSER_H_ */
#ifdef V7_MODULE_LINES
#line 1 "v7/src/object_public.h"
#endif
/*
 * Copyright (c) 2014 Cesanta Software Limited
 * All rights reserved
 */

/*
 * === Objects
 */

#ifndef CS_V7_SRC_OBJECT_PUBLIC_H_
#define CS_V7_SRC_OBJECT_PUBLIC_H_

/* Amalgamated: #include "v7/src/core_public.h" */

#if defined(__cplusplus)
extern "C" {
#endif /* __cplusplus */

/*
 * Property attributes bitmask
 */
typedef unsigned short v7_prop_attr_t;
#define V7_PROPERTY_NON_WRITABLE (1 << 0)
#define V7_PROPERTY_NON_ENUMERABLE (1 << 1)
#define V7_PROPERTY_NON_CONFIGURABLE (1 << 2)
#define V7_PROPERTY_GETTER (1 << 3)
#define V7_PROPERTY_SETTER (1 << 4)
#define _V7_PROPERTY_HIDDEN (1 << 5)
/* property not managed by V7 HEAP */
#define _V7_PROPERTY_OFF_HEAP (1 << 6)
/* special property holding user data and destructor cb */
#define _V7_PROPERTY_USER_DATA_AND_DESTRUCTOR (1 << 7)
/*
 * not a property attribute, but a flag for `v7_def()`. It's here in order to
 * keep all offsets in one place
 */
#define _V7_DESC_PRESERVE_VALUE (1 << 8)

#define V7_PROP_ATTR_IS_WRITABLE(a) (!(a & V7_PROPERTY_NON_WRITABLE))
#define V7_PROP_ATTR_IS_ENUMERABLE(a) (!(a & V7_PROPERTY_NON_ENUMERABLE))
#define V7_PROP_ATTR_IS_CONFIGURABLE(a) (!(a & V7_PROPERTY_NON_CONFIGURABLE))

/*
 * Internal helpers for `V7_DESC_...` macros
 */
#define _V7_DESC_SHIFT 16
#define _V7_DESC_MASK ((1 << _V7_DESC_SHIFT) - 1)
#define _V7_MK_DESC(v, n) \
  (((v7_prop_attr_desc_t)(n)) << _V7_DESC_SHIFT | ((v) ? (n) : 0))
#define _V7_MK_DESC_INV(v, n) _V7_MK_DESC(!(v), (n))

/*
 * Property attribute descriptors that may be given to `v7_def()`: for each
 * attribute (`v7_prop_attr_t`), there is a corresponding macro, which takes
 * param: either 1 (set attribute) or 0 (clear attribute). If some particular
 * attribute isn't mentioned at all, it's left unchanged (or default, if the
 * property is being created)
 *
 * There is additional flag: `V7_DESC_PRESERVE_VALUE`. If it is set, the
 * property value isn't changed (or set to `undefined` if the property is being
 * created)
 */
typedef unsigned long v7_prop_attr_desc_t;
#define V7_DESC_WRITABLE(v) _V7_MK_DESC_INV(v, V7_PROPERTY_NON_WRITABLE)
#define V7_DESC_ENUMERABLE(v) _V7_MK_DESC_INV(v, V7_PROPERTY_NON_ENUMERABLE)
#define V7_DESC_CONFIGURABLE(v) _V7_MK_DESC_INV(v, V7_PROPERTY_NON_CONFIGURABLE)
#define V7_DESC_GETTER(v) _V7_MK_DESC(v, V7_PROPERTY_GETTER)
#define V7_DESC_SETTER(v) _V7_MK_DESC(v, V7_PROPERTY_SETTER)
#define V7_DESC_PRESERVE_VALUE _V7_DESC_PRESERVE_VALUE

#define _V7_DESC_HIDDEN(v) _V7_MK_DESC(v, _V7_PROPERTY_HIDDEN)
#define _V7_DESC_OFF_HEAP(v) _V7_MK_DESC(v, _V7_PROPERTY_OFF_HEAP)

/* See `v7_set_destructor_cb` */
typedef void(v7_destructor_cb_t)(struct v7 *v7, void *ud);

/* Make an empty object */
v7_val_t v7_mk_object(struct v7 *v7);

/*
 * Returns true if the given value is an object or function.
 * i.e. it returns true if the value holds properties and can be
 * used as argument to `v7_get`, `v7_set` and `v7_def`.
 */
int v7_is_object(v7_val_t v);

/* Set object's prototype. Return old prototype or undefined on error. */
v7_val_t v7_set_proto(struct v7 *v7, v7_val_t obj, v7_val_t proto);

/* Get object's prototype. */
v7_val_t v7_get_proto(struct v7 *v7, v7_val_t obj);

/*
 * Lookup property `name` in object `obj`. If `obj` holds no such property,
 * an `undefined` value is returned.
 *
 * If `name_len` is ~0, `name` is assumed to be NUL-terminated and
 * `strlen(name)` is used.
 */
v7_val_t v7_get(struct v7 *v7, v7_val_t obj, const char *name, size_t name_len);

/*
 * Like `v7_get()`, but "returns" value through `res` pointer argument.
 * `res` must not be `NULL`.
 *
 * Caller should check the error code returned, and if it's something other
 * than `V7_OK`, perform cleanup and return this code further.
 */
WARN_UNUSED_RESULT
enum v7_err v7_get_throwing(struct v7 *v7, v7_val_t obj, const char *name,
                            size_t name_len, v7_val_t *res);

/*
 * Define object property, similar to JavaScript `Object.defineProperty()`.
 *
 * `name`, `name_len` specify property name, `val` is a property value.
 * `attrs_desc` is a set of flags which can affect property's attributes,
 * see comment of `v7_prop_attr_desc_t` for details.
 *
 * If `name_len` is ~0, `name` is assumed to be NUL-terminated and
 * `strlen(name)` is used.
 *
 * Returns non-zero on success, 0 on error (e.g. out of memory).
 *
 * See also `v7_set()`.
 */
int v7_def(struct v7 *v7, v7_val_t obj, const char *name, size_t name_len,
           v7_prop_attr_desc_t attrs_desc, v7_val_t v);

/*
 * Set object property. Behaves just like JavaScript assignment.
 *
 * See also `v7_def()`.
 */
int v7_set(struct v7 *v7, v7_val_t obj, const char *name, size_t len,
           v7_val_t val);

/*
 * A helper function to define object's method backed by a C function `func`.
 * `name` must be NUL-terminated.
 *
 * Return value is the same as for `v7_set()`.
 */
int v7_set_method(struct v7 *, v7_val_t obj, const char *name,
                  v7_cfunction_t *func);

/*
 * Delete own property `name` of the object `obj`. Does not follow the
 * prototype chain.
 *
 * If `name_len` is ~0, `name` is assumed to be NUL-terminated and
 * `strlen(name)` is used.
 *
 * Returns 0 on success, -1 on error.
 */
int v7_del(struct v7 *v7, v7_val_t obj, const char *name, size_t name_len);

#if V7_ENABLE__Proxy
struct prop_iter_proxy_ctx;
#endif

/*
 * Context for property iteration, see `v7_next_prop()`.
 *
 * Clients should not interpret contents of this structure, it's here merely to
 * allow clients to allocate it not from the heap.
 */
struct prop_iter_ctx {
#if V7_ENABLE__Proxy
  struct prop_iter_proxy_ctx *proxy_ctx;
#endif
  struct v7_property *cur_prop;

  unsigned init : 1;
};

/*
 * Initialize the property iteration context `ctx`, see `v7_next_prop()` for
 * usage example.
 */
enum v7_err v7_init_prop_iter_ctx(struct v7 *v7, v7_val_t obj,
                                  struct prop_iter_ctx *ctx);

/*
 * Destruct the property iteration context `ctx`, see `v7_next_prop()` for
 * usage example
 */
void v7_destruct_prop_iter_ctx(struct v7 *v7, struct prop_iter_ctx *ctx);

/*
 * Iterate over the `obj`'s properties.
 *
 * Usage example (here we assume we have some `v7_val_t obj`):
 *
 *     struct prop_iter_ctx ctx;
 *     v7_val_t name, val;
 *     v7_prop_attr_t attrs;
 *
 *     v7_init_prop_iter_ctx(v7, obj, &ctx);
 *     while (v7_next_prop(v7, &ctx, &name, &val, &attrs)) {
 *       if (V7_PROP_ATTR_IS_ENUMERABLE(attrs)) continue;
 *       ...
 *     }
 *     v7_destruct_prop_iter_ctx(v7, &ctx);
 *
 * As you see, v7_next_prop will iterate through all properties, including
 * non-enumerable ones, and it's your responsibility to test the attributes
 * with the provided `V7_PROP_ATTR_*` macros and proceed as you see fit.
 */
int v7_next_prop(struct v7 *v7, struct prop_iter_ctx *ctx, v7_val_t *name,
                 v7_val_t *value, v7_prop_attr_t *attrs);

/* Returns true if the object is an instance of a given constructor. */
int v7_is_instanceof(struct v7 *v7, v7_val_t o, const char *c);

/* Returns true if the object is an instance of a given constructor. */
int v7_is_instanceof_v(struct v7 *v7, v7_val_t o, v7_val_t c);

/*
 * Associates an opaque C value (anything that can be casted to a `void * )
 * with an object.
 *
 * You can achieve a similar effect by just setting a special property with
 * a foreign value (see `v7_mk_foreign`), except user data offers the following
 * advantages:
 *
 * 1. You don't have to come up with some arbitrary "special" property name.
 * 2. JS scripts cannot access user data by mistake via property lookup.
 * 3. The user data is available to the destructor. When the desctructor is
 *    invoked you cannot access any of its properties.
 * 4. Allows the implementation to use a more compact encoding
 *
 * Does nothing if `obj` is not a mutable object.
 */
void v7_set_user_data(struct v7 *v7, v7_val_t obj, void *ud);

/*
 * Get the opaque user data set with `v7_set_user_data`.
 *
 * Returns NULL if there is no user data set or if `obj` is not an object.
 */
void *v7_get_user_data(struct v7 *v7, v7_val_t obj);

/*
 * Register a callback which will be invoked when a given object gets
 * reclaimed by the garbage collector.
 *
 * The callback will be invoked while garbage collection is still in progress
 * and hence the internal state of the JS heap is in an undefined state.
 *
 * The only v7 API which is safe to use in this callback is `v7_disown()`,
 * that's why `v7` pointer is given to it. *Calls to any other v7 functions are
 * illegal here*.
 *
 * The intended use case is to reclaim resources allocated by C code.
 */
void v7_set_destructor_cb(struct v7 *v7, v7_val_t obj, v7_destructor_cb_t *d);

#if defined(__cplusplus)
}
#endif /* __cplusplus */

#endif /* CS_V7_SRC_OBJECT_PUBLIC_H_ */
#ifdef V7_MODULE_LINES
#line 1 "v7/src/tokenizer.h"
#endif
/*
 * Copyright (c) 2014 Cesanta Software Limited
 * All rights reserved
 */

#ifndef CS_V7_SRC_TOKENIZER_H_
#define CS_V7_SRC_TOKENIZER_H_

/* Amalgamated: #include "v7/src/internal.h" */

#if !defined(V7_NO_COMPILER)

enum v7_tok {
  TOK_END_OF_INPUT,
  TOK_NUMBER,
  TOK_STRING_LITERAL,
  TOK_REGEX_LITERAL,
  TOK_IDENTIFIER,

  /* Punctuators */
  TOK_OPEN_CURLY,
  TOK_CLOSE_CURLY,
  TOK_OPEN_PAREN,
  TOK_CLOSE_PAREN,
  TOK_COMMA,
  TOK_OPEN_BRACKET,
  TOK_CLOSE_BRACKET,
  TOK_DOT,
  TOK_COLON,
  TOK_SEMICOLON,

  /* Equality ops, in this order */
  TOK_EQ,
  TOK_EQ_EQ,
  TOK_NE,
  TOK_NE_NE,

  /* Assigns */
  TOK_ASSIGN,
  TOK_REM_ASSIGN,
  TOK_MUL_ASSIGN,
  TOK_DIV_ASSIGN,
  TOK_XOR_ASSIGN,
  TOK_PLUS_ASSIGN,
  TOK_MINUS_ASSIGN,
  TOK_OR_ASSIGN,
  TOK_AND_ASSIGN,
  TOK_LSHIFT_ASSIGN,
  TOK_RSHIFT_ASSIGN,
  TOK_URSHIFT_ASSIGN,
  TOK_AND,
  TOK_LOGICAL_OR,
  TOK_PLUS,
  TOK_MINUS,
  TOK_PLUS_PLUS,
  TOK_MINUS_MINUS,
  TOK_LOGICAL_AND,
  TOK_OR,
  TOK_QUESTION,
  TOK_TILDA,
  TOK_REM,
  TOK_MUL,
  TOK_DIV,
  TOK_XOR,

  /* Relational ops, must go in this order */
  TOK_LE,
  TOK_LT,
  TOK_GE,
  TOK_GT,
  TOK_LSHIFT,
  TOK_RSHIFT,
  TOK_URSHIFT,
  TOK_NOT,

  /* Keywords. must be in the same order as tokenizer.c::s_keywords array */
  TOK_BREAK,
  TOK_CASE,
  TOK_CATCH,
  TOK_CONTINUE,
  TOK_DEBUGGER,
  TOK_DEFAULT,
  TOK_DELETE,
  TOK_DO,
  TOK_ELSE,
  TOK_FALSE,
  TOK_FINALLY,
  TOK_FOR,
  TOK_FUNCTION,
  TOK_IF,
  TOK_IN,
  TOK_INSTANCEOF,
  TOK_NEW,
  TOK_NULL,
  TOK_RETURN,
  TOK_SWITCH,
  TOK_THIS,
  TOK_THROW,
  TOK_TRUE,
  TOK_TRY,
  TOK_TYPEOF,
  TOK_VAR,
  TOK_VOID,
  TOK_WHILE,
  TOK_WITH,

  /* TODO(lsm): process these reserved words too */
  TOK_CLASS,
  TOK_ENUM,
  TOK_EXTENDS,
  TOK_SUPER,
  TOK_CONST,
  TOK_EXPORT,
  TOK_IMPORT,
  TOK_IMPLEMENTS,
  TOK_LET,
  TOK_PRIVATE,
  TOK_PUBLIC,
  TOK_INTERFACE,
  TOK_PACKAGE,
  TOK_PROTECTED,
  TOK_STATIC,
  TOK_YIELD,

  NUM_TOKENS
};

#if defined(__cplusplus)
extern "C" {
#endif /* __cplusplus */

V7_PRIVATE int skip_to_next_tok(const char **ptr, const char *src_end);
V7_PRIVATE enum v7_tok get_tok(const char **s, const char *src_end, double *n,
                               enum v7_tok prev_tok);
V7_PRIVATE int is_reserved_word_token(enum v7_tok tok);

#if defined(__cplusplus)
}
#endif /* __cplusplus */

#endif /* V7_NO_COMPILER */

#endif /* CS_V7_SRC_TOKENIZER_H_ */
#ifdef V7_MODULE_LINES
#line 1 "v7/src/opcodes.h"
#endif
/*
 * Copyright (c) 2014 Cesanta Software Limited
 * All rights reserved
 */

#ifndef CS_V7_SRC_OPCODES_H_
#define CS_V7_SRC_OPCODES_H_

/*
 * ==== Instructions
 *
 * Bytecode instructions consist of 1-byte opcode, optionally followed by N
 * bytes of arguments.
 *
 * Opcodes that accept an index in the literal table (PUSH_LIT, GET_VAR,
 * SET_VAR, ...) also accept inline literals. In order to distinguish indices in
 * the literals table and the inline literals, indices 0 and 1 are reserved as
 * type tags for inline literals:
 *
 * if 0, the following bytes encode a string literal
 * if 1, they encode a number (textual, like in the AST)
 *
 * (see enum bcode_inline_lit_type_tag)
 *
 *
 * Stack diagrams follow the syntax and semantics of:
 *
 * http://everything2.com/title/Forth+stack+diagrams[Forth stack diagrams].
 *
 * We use the following extension in the terminology:
 *
 * `T`: "Try stack".
 * `A`: opcode arguments.
 * `S`: stash register (one element stack).
 *
 */
enum opcode {
  /*
   * Removes an item from the top of the stack. It is undefined what happens if
   * the stack is empty.
   *
   * `( a -- )`
  */
  OP_DROP,
  /*
   * Duplicates a value on top of the stack.
   *
   * `( a -- a a)`
  */
  OP_DUP,
  /*
   * Duplicates 2 values from the top of the stack in the same order.
   *
   * `( a b -- a b a b)`
  */
  OP_2DUP,
  /*
   * Swap the top two items on the stack.
   *
   * `( a b -- b a )`
   */
  OP_SWAP,
  /*
   * Copy current top of the stack to the temporary stash register.
   *
   * The content of the stash register will be cleared in the event of an
   * exception.
   *
   * `( a S: b -- a S: a)` saves TOS to stash reg
   */
  OP_STASH,
  /*
   * Replace the top of the stack with the content of the temporary stash
   * register.
   *
   * The stash register is cleared afterwards.
   *
   * `( a S: b -- b S: nil )` replaces tos with stash reg
   */
  OP_UNSTASH,

  /*
   * Effectively drops the last-but-one element from stack
   *
   * `( a b -- b )`
   */
  OP_SWAP_DROP,

  /*
   * Pushes `undefined` onto the stack.
   *
   * `( -- undefined )`
   */
  OP_PUSH_UNDEFINED,
  /*
   * Pushes `null` onto the stack.
   *
   * `( -- null )`
   */
  OP_PUSH_NULL,
  /*
   * Pushes current value of `this` onto the stack.
   *
   * `( -- this )`
   */
  OP_PUSH_THIS,
  /*
   * Pushes `true` onto the stack.
   *
   * `( -- true )`
   */
  OP_PUSH_TRUE,
  /*
   * Pushes `false` onto the stack.
   *
   * `( -- false )`
   */
  OP_PUSH_FALSE,
  /*
   * Pushes `0` onto the stack.
   *
   * `( -- 0 )`
   */
  OP_PUSH_ZERO,
  /*
   * Pushes `1` onto the stack.
   *
   * `( -- 1 )`
   */
  OP_PUSH_ONE,

  /*
   * Pushes a value from literals table onto the stack.
   *
   * The opcode takes a varint operand interpreted as an index in the current
   * literal table (see lit table).
   *
   * ( -- a )
   */
  OP_PUSH_LIT,

  OP_NOT,
  OP_LOGICAL_NOT,

  /*
   * Takes a number from the top of the stack, inverts the sign and pushes it
   * back.
   *
   * `( a -- -a )`
   */
  OP_NEG,
  /*
   * Takes a number from the top of the stack pushes the evaluation of
   * `Number()`.
   *
   * `( a -- Number(a) )`
   */
  OP_POS,

  /*
   * Takes 2 values from the top of the stack and performs addition operation:
   * If any of the two values is not `undefined`, number or boolean, both values
   * are converted into strings and concatenated.
   * Otherwise, both values are treated as numbers:
   * * `undefined` is converted into NaN
   * * `true` is converted into 1
   * * `false` is converted into 0
   *
   * Result is pushed back onto the stack.
   *
   * TODO: make it behave exactly like JavaScript's `+` operator.
   *
   * `( a b -- a+b )`
   */
  OP_ADD,
  OP_SUB,     /* ( a b -- a-b ) */
  OP_REM,     /* ( a b -- a%b ) */
  OP_MUL,     /* ( a b -- a*b ) */
  OP_DIV,     /* ( a b -- a/b ) */
  OP_LSHIFT,  /* ( a b -- a<<b ) */
  OP_RSHIFT,  /* ( a b -- a>>b ) */
  OP_URSHIFT, /* ( a b -- a>>>b ) */
  OP_OR,      /* ( a b -- a|b ) */
  OP_XOR,     /* ( a b -- a^b ) */
  OP_AND,     /* ( a b -- a&b ) */

  /*
   * Takes two numbers form the top of the stack and pushes `true` if they are
   * equal, or `false` if they are not equal.
   *
   * ( a b -- a===b )
   */
  OP_EQ_EQ,
  OP_EQ,    /* ( a b -- a==b ) */
  OP_NE,    /* ( a b -- a!=b ) */
  OP_NE_NE, /* ( a b -- a!==b ) */
  OP_LT,    /* ( a b -- a<b ) */
  OP_LE,    /* ( a b -- a<=b ) */
  OP_GT,    /* ( a b -- a>b ) */
  OP_GE,    /* ( a b -- a>=b ) */
  OP_INSTANCEOF,

  OP_TYPEOF,

  OP_IN,
  /*
   * Takes 2 values from the stack, treats the top of the stack as property name
   * and the next value must be an object, an array or a string.
   * If it's an object, pushes the value of its named property onto the stack.
   * If it's an array or a string, returns a value at a given position.
  */
  OP_GET,
  /*
   * Takes 3 items from the stack: value, property name, object. Sets the given
   * property of a given object to a given value, pushes value back onto the
   *stack.
   *
   * `( a b c -- a[b]=c )`
  */
  OP_SET,
  /*
   * Takes 1 value from the stack and a varint argument -- index of the var name
   * in the literals table. Tries to find the variable in the current scope
   * chain and assign the value to it. If the varialble is not found -- creates
   * a new one in the global scope. Pushes the value back to the stack.
   *
   * `( a -- a )`
   */
  OP_SET_VAR,
  /*
   * Takes a varint argument -- index of the var name in the literals table.
   * Looks up that variable in the scope chain and pushes its value onto the
   * stack.
   *
   * `( -- a )`
   */
  OP_GET_VAR,

  /*
   * Like OP_GET_VAR but returns undefined
   * instead of throwing reference error.
   *
   * `( -- a )`
   */
  OP_SAFE_GET_VAR,

  /*
   * ==== Jumps
   *
   * All jump instructions take one 4-byte argument: offset to jump to. Offset
   *is a
   * index of the byte in the instruction stream, starting with 0. No byte order
   * conversion is applied.
   *
   * TODO: specify byte order for the offset.
   */

  /*
   * Unconditiona jump.
   */
  OP_JMP,
  /*
   * Takes one value from the stack and performs a jump if conversion of that
   * value to boolean results in `true`.
   *
   * `( a -- )`
  */
  OP_JMP_TRUE,
  /*
   * Takes one value from the stack and performs a jump if conversion of that
   * value to boolean results in `false`.
   *
   * `( a -- )`
   */
  OP_JMP_FALSE,
  /*
   * Like OP_JMP_TRUE but if the branch
   * is taken it also drops another stack element:
   *
   * if `b` is true: `( a b -- )`
   * if `b` is false: `( a b -- a )`
   */
  OP_JMP_TRUE_DROP,

  /*
   * Conditional jump on the v7->is_continuing flag.
   * Clears the flag once executed.
   *
   * `( -- )`
   */
  OP_JMP_IF_CONTINUE,

  /*
   * Constructs a new empty object and pushes it onto the stack.
   *
   * `( -- {} )`
   */
  OP_CREATE_OBJ,
  /*
   * Constructs a new empty array and pushes it onto the stack.
   *
   * `( -- [] )`
   */
  OP_CREATE_ARR,

  /*
   * Allocates the iteration context (for `OP_NEXT_PROP`) from heap and pushes
   * a foreign pointer to it on stack. The allocated data is stored as "user
   * data" of the object, and it will be reclaimed automatically when the
   * object gets garbage-collected.
   *
   * `( -- ctx )`
   */
  OP_PUSH_PROP_ITER_CTX,

  /*
   * Yields the next property name.
   * Used in the for..in construct.
   *
   * The first evaluation must receive `null` as handle.
   * Subsequent evaluations will either:
   *
   * a) produce a new handle, the key and true value:
   *
   * `( o h -- o h' key true)`
   *
   * b) produce a false value only, indicating no more properties:
   *
   * `( o h -- false)`
   */
  OP_NEXT_PROP,

  /*
   * Copies the function object at TOS and assigns current scope
   * in func->scope.
   *
   * `( a -- a )`
   */
  OP_FUNC_LIT,
  /*
   * Takes the number of arguments as parameter.
   *
   * Pops N function arguments from stack, then pops function, then pops `this`.
   * Calls a function and populates TOS with the returned value.
   *
   * `( this f a0 a1 ... aN -- f(a0,a1,...) )`
   */
  OP_CALL,
  OP_NEW,
  /*
   * Checks that TOS is a callable and if not saves an exception
   * that will will be thrown by CALL after all arguments have been evaluated.
   */
  OP_CHECK_CALL,
  /*
   * Returns the current function.
   *
   * It has no stack side effects. The function upon return will leave the
   * return value on the stack. The return value must be pushed on the stack
   * prior to invoking a RET.
   *
   * `( -- )`
   */
  OP_RET,

  /*
   * Deletes the property of given name `p` from the given object `o`. Returns
   * boolean value `a`.
   *
   * `( o p -- a )`
   */
  OP_DELETE,

  /*
   * Like `OP_DELETE`, but uses the current scope as an object to delete
   * a property from.
   *
   * `( p -- a )`
   */
  OP_DELETE_VAR,

  /*
   * Pushes a value (bcode offset of `catch` block) from opcode argument to
   * "try stack".
   *
   * Used in the beginning of the `try` block.
   *
   * `( A: a -- T: a )`
   */
  OP_TRY_PUSH_CATCH,

  /*
   * Pushes a value (bcode offset of `finally` block) from opcode argument to
   * "try stack".
   *
   * Used in the beginning of the `try` block.
   *
   * `( A: a -- T: a )`
   *
   * TODO: implement me
   */
  OP_TRY_PUSH_FINALLY,

  /*
   * Pushes a value (bcode offset of a label) from opcode argument to
   * "try stack".
   *
   * Used at the beginning of loops that contain break or continue.
   * Possible optimisation: don't emit if we can ensure that no break or
   * continue statement is used.
   *
   * `( A: a -- T: a )`
   */
  OP_TRY_PUSH_LOOP,

  /*
   * Pushes a value (bcode offset of a label) from opcode argument to
   * "try stack".
   *
   * Used at the beginning of switch statements.
   *
   * `( A: a -- T: a )`
   */
  OP_TRY_PUSH_SWITCH,

  /*
   * Pops a value (bcode offset of `finally` or `catch` block) from "try
   * stack", and discards it
   *
   * Used in the end of the `try` block, as well as in the beginning of the
   * `catch` and `finally` blocks
   *
   * `( T: a -- T: )`
   */
  OP_TRY_POP,

  /*
   * Used in the end of the `finally` block:
   *
   * - if some value is currently being thrown, keep throwing it.
   *   If eventually we encounter `catch` block, the thrown value gets
   *   populated on TOS:
   *
   *   `( -- a )`
   *
   * - if there is some pending value to return, keep returning it.
   *   If we encounter no further `finally` blocks, then the returned value
   *   gets populated on TOS:
   *
   *   `( -- a )`
   *
   *   And return is performed.
   *
   * - otherwise, do nothing
   */
  OP_AFTER_FINALLY,

  /*
   * Throw value from TOS. First of all, it pops the value and saves it into
   * `v7->vals.thrown_error`:
   *
   * `( a -- )`
   *
   * Then unwinds stack looking for the first `catch` or `finally` blocks.
   *
   * - if `finally` is found, thrown value is kept into `v7->vals.thrown_error`.
   * - if `catch` is found, thrown value is pushed back to the stack:
   *   `( -- a )`
   * - otherwise, thrown value is kept into `v7->vals.thrown_error`
   */
  OP_THROW,

  /*
   * Unwind to next break entry in the try stack, evaluating
   * all finally blocks on its way up.
   *
   * `( -- )`
   */
  OP_BREAK,

  /*
   * Like OP_BREAK, but sets the v7->is_continuing flag
   * which will cause OP_JMP_IF_CONTINUE to restart the loop.
   *
   * `( -- )`
   */
  OP_CONTINUE,

  /*
   * Used when we enter the `catch` block. Takes a varint argument -- index of
   * the exception variable name in the literals table.
   *
   * Pops the exception value from the stack, creates a private frame,
   * sets exception property on it with the given name. pushes this
   * private frame to call stack.
   *
   * `( e -- )`
   */
  OP_ENTER_CATCH,

  /*
   * Ued when we exit from the `catch` block. Merely pops the private frame
   * from the call stack.
   *
   * `( -- )`
   */
  OP_EXIT_CATCH,

  OP_MAX,
};

#define _OP_LINE_NO 0x80

#endif /* CS_V7_SRC_OPCODES_H_ */
#ifdef V7_MODULE_LINES
#line 1 "v7/src/core.h"
#endif
/*
 * Copyright (c) 2014 Cesanta Software Limited
 * All rights reserved
 */

#ifndef CS_V7_SRC_CORE_H_
#define CS_V7_SRC_CORE_H_

/* Amalgamated: #include "v7/src/core_public.h" */

/* Amalgamated: #include "common/mbuf.h" */
/* Amalgamated: #include "v7/src/std_error.h" */
/* Amalgamated: #include "v7/src/mm.h" */
/* Amalgamated: #include "v7/src/parser.h" */
/* Amalgamated: #include "v7/src/object_public.h" */
/* Amalgamated: #include "v7/src/tokenizer.h" */
/* Amalgamated: #include "v7/src/opcodes.h" */

#if defined(__cplusplus)
extern "C" {
#endif /* __cplusplus */

typedef uint64_t val_t;

#if defined(V7_ENABLE_ENTITY_IDS)

typedef unsigned short entity_id_t;
typedef unsigned char entity_id_part_t;

/*
 * Magic numbers that are stored in various objects in order to identify their
 * type in runtime
 */
#define V7_ENTITY_ID_PROP 0xe9a1
#define V7_ENTITY_ID_PART_OBJ 0x57
#define V7_ENTITY_ID_PART_GEN_OBJ 0x31
#define V7_ENTITY_ID_PART_JS_FUNC 0x0d

#define V7_ENTITY_ID_NONE 0xa5a5
#define V7_ENTITY_ID_PART_NONE 0xa5

#endif

/*
 *  Double-precision floating-point number, IEEE 754
 *
 *  64 bit (8 bytes) in total
 *  1  bit sign
 *  11 bits exponent
 *  52 bits mantissa
 *      7         6        5        4        3        2        1        0
 *  seeeeeee|eeeemmmm|mmmmmmmm|mmmmmmmm|mmmmmmmm|mmmmmmmm|mmmmmmmm|mmmmmmmm
 *
 * If an exponent is all-1 and mantissa is all-0, then it is an INFINITY:
 *  11111111|11110000|00000000|00000000|00000000|00000000|00000000|00000000
 *
 * If an exponent is all-1 and mantissa's MSB is 1, it is a quiet NaN:
 *  11111111|11111000|00000000|00000000|00000000|00000000|00000000|00000000
 *
 *  V7 NaN-packing:
 *    sign and exponent is 0xfff
 *    4 bits specify type (tag), must be non-zero
 *    48 bits specify value
 *
 *  11111111|1111tttt|vvvvvvvv|vvvvvvvv|vvvvvvvv|vvvvvvvv|vvvvvvvv|vvvvvvvv
 *   NaN marker |type|  48-bit placeholder for values: pointers, strings
 *
 * On 64-bit platforms, pointers are really 48 bit only, so they can fit,
 * provided they are sign extended
 */

/*
 * A tag is made of the sign bit and the 4 lower order bits of byte 6.
 * So in total we have 32 possible tags.
 *
 * Tag (1,0) however cannot hold a zero payload otherwise it's interpreted as an
 * INFINITY; for simplicity we're just not going to use that combination.
 */
#define MAKE_TAG(s, t) \
  ((uint64_t)(s) << 63 | (uint64_t) 0x7ff0 << 48 | (uint64_t)(t) << 48)

#define V7_TAG_OBJECT MAKE_TAG(1, 0xF)
#define V7_TAG_FOREIGN MAKE_TAG(1, 0xE)
#define V7_TAG_UNDEFINED MAKE_TAG(1, 0xD)
#define V7_TAG_BOOLEAN MAKE_TAG(1, 0xC)
#define V7_TAG_NAN MAKE_TAG(1, 0xB)
#define V7_TAG_STRING_I MAKE_TAG(1, 0xA)  /* Inlined string len < 5 */
#define V7_TAG_STRING_5 MAKE_TAG(1, 0x9)  /* Inlined string len 5 */
#define V7_TAG_STRING_O MAKE_TAG(1, 0x8)  /* Owned string */
#define V7_TAG_STRING_F MAKE_TAG(1, 0x7)  /* Foreign string */
#define V7_TAG_STRING_C MAKE_TAG(1, 0x6)  /* String chunk */
#define V7_TAG_FUNCTION MAKE_TAG(1, 0x5)  /* JavaScript function */
#define V7_TAG_CFUNCTION MAKE_TAG(1, 0x4) /* C function */
#define V7_TAG_STRING_D MAKE_TAG(1, 0x3)  /* Dictionary string  */
#define V7_TAG_REGEXP MAKE_TAG(1, 0x2)    /* Regex */
#define V7_TAG_NOVALUE MAKE_TAG(1, 0x1)   /* Sentinel for no value */
#define V7_TAG_MASK MAKE_TAG(1, 0xF)

#define _V7_NULL V7_TAG_FOREIGN
#define _V7_UNDEFINED V7_TAG_UNDEFINED

V7_STATIC_ASSERT(_V7_NULL == V7_NULL, public_V7_NULL_is_wrong);
V7_STATIC_ASSERT(_V7_UNDEFINED == V7_UNDEFINED, public_V7_UNDEFINED_is_wrong);

/*
 * Object attributes bitmask
 */
typedef unsigned char v7_obj_attr_t;
#define V7_OBJ_NOT_EXTENSIBLE (1 << 0) /* TODO(lsm): store this in LSB */
#define V7_OBJ_DENSE_ARRAY (1 << 1)    /* TODO(mkm): store in some tag */
#define V7_OBJ_FUNCTION (1 << 2)       /* function object */
#define V7_OBJ_OFF_HEAP (1 << 3)       /* object not managed by V7 HEAP */
#define V7_OBJ_HAS_DESTRUCTOR (1 << 4) /* has user data */
#define V7_OBJ_PROXY (1 << 5)          /* it's a Proxy object */

/*
 * JavaScript value is either a primitive, or an object.
 * There are 5 primitive types: Undefined, Null, Boolean, Number, String.
 * Non-primitive type is an Object type. There are several classes of Objects,
 * see description of `struct v7_generic_object` below for more details.
 * This enumeration combines types and object classes in one enumeration.
 * NOTE(lsm): compile with `-fshort-enums` to reduce sizeof(enum v7_type) to 1.
 */
enum v7_type {
  /* Primitive types */
  V7_TYPE_UNDEFINED,
  V7_TYPE_NULL,
  V7_TYPE_BOOLEAN,
  V7_TYPE_NUMBER,
  V7_TYPE_STRING,
  V7_TYPE_FOREIGN,
  V7_TYPE_CFUNCTION,

  /* Different classes of Object type */
  V7_TYPE_GENERIC_OBJECT,
  V7_TYPE_BOOLEAN_OBJECT,
  V7_TYPE_STRING_OBJECT,
  V7_TYPE_NUMBER_OBJECT,
  V7_TYPE_FUNCTION_OBJECT,
  V7_TYPE_CFUNCTION_OBJECT,
  V7_TYPE_REGEXP_OBJECT,
  V7_TYPE_ARRAY_OBJECT,
  V7_TYPE_DATE_OBJECT,
  V7_TYPE_ERROR_OBJECT,
  V7_TYPE_MAX_OBJECT_TYPE,
  V7_NUM_TYPES
};

/*
 * Call frame type mask: we have a "class hierarchy" of the call frames, see
 * `struct v7_call_frame_base`, and the `type_mask` field represents the exact
 * frame type.
 *
 * Possible values are:
 *
 * - `V7_CALL_FRAME_MASK_PRIVATE | V7_CALL_FRAME_MASK_BCODE`: the most popular
 *   frame type: call frame for bcode execution, either top-level code or JS
 *   function.
 * - `V7_CALL_FRAME_MASK_PRIVATE`: used for `catch` clauses only: the variables
 *   we create in `catch` clause should not be visible from the outside of the
 *   clause, so we have to create a separate scope object for it.
 * - `V7_CALL_FRAME_MASK_CFUNC`: call frame for C function.
 */
typedef uint8_t v7_call_frame_mask_t;
#define V7_CALL_FRAME_MASK_BCODE (1 << 0)
#define V7_CALL_FRAME_MASK_PRIVATE (1 << 1)
#define V7_CALL_FRAME_MASK_CFUNC (1 << 2)

/*
 * Base of the call frame; includes the pointer to the previous frame,
 * and the frame type.
 *
 * In order to save memory, it also contains some bitfields which actually
 * belong to some "sub-structures".
 *
 * The hierarchy is as follows:
 *
 *   - v7_call_frame_base
 *     - v7_call_frame_private
 *       - v7_call_frame_bcode
 *     - v7_call_frame_cfunc
 */
struct v7_call_frame_base {
  struct v7_call_frame_base *prev;

  /* See comment for `v7_call_frame_mask_t` */
  v7_call_frame_mask_t type_mask : 3;

  /* Belongs to `struct v7_call_frame_private` */
  unsigned int line_no : 16;

  /* Belongs to `struct v7_call_frame_bcode` */
  unsigned is_constructor : 1;

  /* Belongs to `struct v7_call_frame_bcode` */
  unsigned int is_thrown : 1;
};

/*
 * "private" call frame, used in `catch` blocks, merely for using a separate
 * scope object there. It is also a "base class" for the bcode call frame,
 * see `struct v7_call_frame_bcode`.
 *
 * TODO(dfrank): probably implement it differently, so that we can get rid of
 * the separate "private" frames whatsoever (and just include it into struct
 * v7_call_frame_bcode )
 */
struct v7_call_frame_private {
  struct v7_call_frame_base base;
  size_t stack_size;
  struct {
    /*
     * Current execution scope. Initially, it is equal to the `global_object`;
     * and at each function call, it is augmented by the new scope object, which
     * has the previous value as a prototype.
     */
    val_t scope;

    val_t try_stack;
  } vals;
};

/*
 * "bcode" call frame, augments "private" frame with `bcode` and the position
 * in it, and `this` object. It is the primary frame type, used when executing
 * a bcode script or calling a function.
 */
struct v7_call_frame_bcode {
  struct v7_call_frame_private base;
  struct {
    val_t this_obj;
    val_t thrown_error;
  } vals;
  struct bcode *bcode;
  char *bcode_ops;
};

/*
 * "cfunc" call frame, used when calling cfunctions.
 */
struct v7_call_frame_cfunc {
  struct v7_call_frame_base base;

  struct {
    val_t this_obj;
  } vals;

  v7_cfunction_t *cfunc;
};

/*
 * This structure groups together all val_t logical members
 * of struct v7 so that GC and freeze logic can easily access all
 * of them together. This structure must contain only val_t members.
 */
struct v7_vals {
  val_t global_object;

  val_t arguments; /* arguments of current call */

  val_t object_prototype;
  val_t array_prototype;
  val_t boolean_prototype;
  val_t error_prototype;
  val_t string_prototype;
  val_t regexp_prototype;
  val_t number_prototype;
  val_t date_prototype;
  val_t function_prototype;
  val_t proxy_prototype;

  /*
   * temporary register for `OP_STASH` and `OP_UNSTASH` instructions. Valid if
   * `v7->is_stashed` is non-zero
   */
  val_t stash;

  val_t error_objects[ERROR_CTOR_MAX];

  /*
   * Value that is being thrown. Valid if `is_thrown` is non-zero (see below)
   */
  val_t thrown_error;

  /*
   * value that is going to be returned. Needed when some `finally` block needs
   * to be executed after `return my_value;` was issued. Used in bcode.
   * See also `is_returned` below
   */
  val_t returned_value;

  val_t last_name[2]; /* used for error reporting */
  /* most recent OP_CHECK_CALL exceptions, to be thrown by OP_CALL|OP_NEW */
  val_t call_check_ex;
};

struct v7 {
  struct v7_vals vals;

  /*
   * Stack of call frames.
   *
   * Execution contexts are contained in two chains:
   * - Stack of call frames: to allow returning, throwing, and stack trace
   *   generation;
   * - In the lexical scope via their prototype chain (to allow variable
   *   lookup), see `struct v7_call_frame_private::scope`.
   *
   * Execution contexts should be allocated on heap, because they might not be
   * on a call stack but still referenced (closures).
   *
   * New call frame is created every time some top-level code is evaluated,
   * or some code is being `eval`-d, or some function is called, either JS
   * function or C function (although the call frame types are different for
   * JS functions and cfunctions, see `struct v7_call_frame_base` and its
   * sub-structures)
   *
   * When no code is being evaluated at the moment, `call_stack` is `NULL`.
   *
   * See comment for `struct v7_call_frame_base` for some more details.
   */
  struct v7_call_frame_base *call_stack;

  /*
   * Bcode executes until it reaches `bottom_call_frame`. When some top-level
   * or `eval`-d code starts execution, the `bottom_call_frame` is set to the
   * call frame which was just created for the execution.
   */
  struct v7_call_frame_base *bottom_call_frame;

  struct mbuf stack; /* value stack for bcode interpreter */

  struct mbuf owned_strings;   /* Sequence of (varint len, char data[]) */
  struct mbuf foreign_strings; /* Sequence of (varint len, char *data) */

  struct mbuf tmp_stack; /* Stack of val_t* elements, used as root set */
  int need_gc;           /* Set to true to trigger GC when safe */

  struct gc_arena generic_object_arena;
  struct gc_arena function_arena;
  struct gc_arena property_arena;
#if V7_ENABLE__Memory__stats
  size_t function_arena_ast_size;
  size_t bcode_ops_size;
  size_t bcode_lit_total_size;
  size_t bcode_lit_deser_size;
#endif
  struct mbuf owned_values; /* buffer for GC roots owned by C code */

  /*
   * Stack of the root bcodes being executed at the moment. Note that when some
   * regular JS function is called inside `eval_bcode()`, the function's bcode
   * is NOT added here. Buf if some cfunction is called, which in turn calls
   * `b_exec()` (or `b_apply()`) recursively, the new bcode is added to this
   * stack.
   */
  struct mbuf act_bcodes;

  char error_msg[80]; /* Exception message */

  struct mbuf json_visited_stack; /* Detecting cycle in to_json */

/* Parser state */
#if !defined(V7_NO_COMPILER)
  struct v7_pstate pstate; /* Parsing state */
  enum v7_tok cur_tok;     /* Current token */
  const char *tok;         /* Parsed terminal token (ident, number, string) */
  unsigned long tok_len;   /* Length of the parsed terminal token */
  size_t last_var_node;    /* Offset of last var node or function/script node */
  int after_newline;       /* True if the cur_tok starts a new line */
  double cur_tok_dbl;      /* When tokenizing, parser stores TOK_NUMBER here */

  /*
   * Current linenumber. Currently it is used by parser, compiler and bcode
   * evaluator.
   *
   * - Parser: it's the last line_no emitted to AST
   * - Compiler: it's the last line_no emitted to bcode
   */
  int line_no;
#endif /* V7_NO_COMPILER */

  /* singleton, pointer because of amalgamation */
  struct v7_property *cur_dense_prop;

  volatile int interrupted;
#ifdef V7_STACK_SIZE
  void *sp_limit;
  void *sp_lwm;
#endif

#if defined(V7_CYG_PROFILE_ON)
  /* linked list of v7 contexts, needed by cyg_profile hooks */
  struct v7 *next_v7;

#if defined(V7_ENABLE_STACK_TRACKING)
  /* linked list of stack tracking contexts */
  struct stack_track_ctx *stack_track_ctx;

  int stack_stat[V7_STACK_STATS_CNT];
#endif

#endif

#ifdef V7_MALLOC_GC
  struct mbuf malloc_trace;
#endif

/*
 * TODO(imax): remove V7_DISABLE_STR_ALLOC_SEQ knob after 2015/12/01 if there
 * are no issues.
 */
#ifndef V7_DISABLE_STR_ALLOC_SEQ
  uint16_t gc_next_asn; /* Next sequence number to use. */
  uint16_t gc_min_asn;  /* Minimal sequence number currently in use. */
#endif

#if defined(V7_TRACK_MAX_PARSER_STACK_SIZE)
  size_t parser_stack_data_max_size;
  size_t parser_stack_ret_max_size;
  size_t parser_stack_data_max_len;
  size_t parser_stack_ret_max_len;
#endif

#ifdef V7_FREEZE
  FILE *freeze_file;
#endif

  /*
   * true if exception is currently being created. Needed to avoid recursive
   * exception creation
   */
  unsigned int creating_exception : 1;
  /* while true, GC is inhibited */
  unsigned int inhibit_gc : 1;
  /* true if `thrown_error` is valid */
  unsigned int is_thrown : 1;
  /* true if `returned_value` is valid */
  unsigned int is_returned : 1;
  /* true if a finally block is executing while breaking */
  unsigned int is_breaking : 1;
  /* true when a continue OP is executed, reset by `OP_JMP_IF_CONTINUE` */
  unsigned int is_continuing : 1;
  /* true if some value is currently stashed (`v7->vals.stash`) */
  unsigned int is_stashed : 1;
  /* true if last emitted statement does not affect data stack */
  unsigned int is_stack_neutral : 1;
  /* true if precompiling; affects compiler bcode choices */
  unsigned int is_precompiling : 1;

  enum opcode last_ops[2]; /* trace of last ops, used for error reporting */
};

struct v7_property {
  struct v7_property *
      next; /* Linkage in struct v7_generic_object::properties */
  v7_prop_attr_t attributes;
#if defined(V7_ENABLE_ENTITY_IDS)
  entity_id_t entity_id;
#endif
  val_t name;  /* Property name (a string) */
  val_t value; /* Property value */
};

/*
 * "base object": structure which is shared between objects and functions.
 */
struct v7_object {
  /* First HIDDEN property in a chain is an internal object value */
  struct v7_property *properties;
  v7_obj_attr_t attributes;
#if defined(V7_ENABLE_ENTITY_IDS)
  entity_id_part_t entity_id_base;
  entity_id_part_t entity_id_spec;
#endif
};

/*
 * An object is an unordered collection of properties.
 * A function stored in a property of an object is called a method.
 * A property has a name, a value, and set of attributes.
 * Attributes are: ReadOnly, DontEnum, DontDelete, Internal.
 *
 * A constructor is a function that creates and initializes objects.
 * Each constructor has an associated prototype object that is used for
 * inheritance and shared properties. When a constructor creates an object,
 * the new object references the constructor’s prototype.
 *
 * Objects could be a "generic objects" which is a collection of properties,
 * or a "typed object" which also hold an internal value like String or Number.
 * Those values are implicit, unnamed properties of the respective types,
 * and can be coerced into primitive types by calling a respective constructor
 * as a function:
 *    var a = new Number(123);
 *    typeof(a) == 'object';
 *    typeof(Number(a)) == 'number';
 */
struct v7_generic_object {
  /*
   * This has to be the first field so that objects can be managed by the GC.
   */
  struct v7_object base;
  struct v7_object *prototype;
};

/*
 * Variables are function-scoped and are hoisted.
 * Lexical scoping & closures: each function has a chain of scopes, defined
 * by the lexicographic order of function definitions.
 * Scope is different from the execution context.
 * Execution context carries "variable object" which is variable/value
 * mapping for all variables defined in a function, and `this` object.
 * If function is not called as a method, then `this` is a global object.
 * Otherwise, `this` is an object that contains called method.
 * New execution context is created each time a function call is performed.
 * Passing arguments through recursion is done using execution context, e.g.
 *
 *    var factorial = function(num) {
 *      return num < 2 ? 1 : num * factorial(num - 1);
 *    };
 *
 * Here, recursion calls the same function `factorial` several times. Execution
 * contexts for each call form a stack. Each context has different variable
 * object, `vars`, with different values of `num`.
 */

struct v7_js_function {
  /*
   * Functions are objects. This has to be the first field so that function
   * objects can be managed by the GC.
   */
  struct v7_object base;
  struct v7_generic_object *scope; /* lexical scope of the closure */

  /* bytecode, might be shared between functions */
  struct bcode *bcode;
};

struct v7_regexp {
  val_t regexp_string;
  struct slre_prog *compiled_regexp;
  long lastIndex;
};

/* Vector, describes some memory location pointed by `p` with length `len` */
struct v7_vec {
  char *p;
  size_t len;
};

/*
 * Constant vector, describes some const memory location pointed by `p` with
 * length `len`
 */
struct v7_vec_const {
  const char *p;
  size_t len;
};

#define V7_VEC(str) \
  { (str), sizeof(str) - 1 }

/*
 * Returns current execution scope.
 *
 * See comment for `struct v7_call_frame_private::vals::scope`
 */
V7_PRIVATE v7_val_t get_scope(struct v7 *v7);

/*
 * Returns 1 if currently executing bcode in the "strict mode", 0 otherwise
 */
V7_PRIVATE uint8_t is_strict_mode(struct v7 *v7);

#if defined(__cplusplus)
}
#endif /* __cplusplus */

#endif /* CS_V7_SRC_CORE_H_ */
#ifdef V7_MODULE_LINES
#line 1 "v7/src/primitive_public.h"
#endif
/*
 * Copyright (c) 2014 Cesanta Software Limited
 * All rights reserved
 */

/*
 * === Primitives
 *
 * All primitive values but strings.
 *
 * "foreign" values are also here, see `v7_mk_foreign()`.
 */

#ifndef CS_V7_SRC_PRIMITIVE_PUBLIC_H_
#define CS_V7_SRC_PRIMITIVE_PUBLIC_H_

/* Amalgamated: #include "v7/src/core_public.h" */

#if defined(__cplusplus)
extern "C" {
#endif /* __cplusplus */

/* Make numeric primitive value */
NOINSTR v7_val_t v7_mk_number(struct v7 *v7, double num);

/*
 * Returns number value stored in `v7_val_t` as `double`.
 *
 * Returns NaN for non-numbers.
 */
NOINSTR double v7_get_double(struct v7 *v7, v7_val_t v);

/*
 * Returns number value stored in `v7_val_t` as `int`. If the number value is
 * not an integer, the fraction part will be discarded.
 *
 * If the given value is a non-number, or NaN, the result is undefined.
 */
NOINSTR int v7_get_int(struct v7 *v7, v7_val_t v);

/* Returns true if given value is a primitive number value */
int v7_is_number(v7_val_t v);

/* Make boolean primitive value (either `true` or `false`) */
NOINSTR v7_val_t v7_mk_boolean(struct v7 *v7, int is_true);

/*
 * Returns boolean stored in `v7_val_t`:
 *  0 for `false` or non-boolean, non-0 for `true`
 */
NOINSTR int v7_get_bool(struct v7 *v7, v7_val_t v);

/* Returns true if given value is a primitive boolean value */
int v7_is_boolean(v7_val_t v);

/*
 * Make `null` primitive value.
 *
 * NOTE: this function is deprecated and will be removed in future releases.
 * Use `V7_NULL` instead.
 */
NOINSTR v7_val_t v7_mk_null(void);

/* Returns true if given value is a primitive `null` value */
int v7_is_null(v7_val_t v);

/*
 * Make `undefined` primitive value.
 *
 * NOTE: this function is deprecated and will be removed in future releases.
 * Use `V7_UNDEFINED` instead.
 */
NOINSTR v7_val_t v7_mk_undefined(void);

/* Returns true if given value is a primitive `undefined` value */
int v7_is_undefined(v7_val_t v);

/*
 * Make JavaScript value that holds C/C++ `void *` pointer.
 *
 * A foreign value is completely opaque and JS code cannot do anything useful
 * with it except holding it in properties and passing it around.
 * It behaves like a sealed object with no properties.
 *
 * NOTE:
 * Only valid pointers (as defined by each supported architecture) will fully
 * preserved. In particular, all supported 64-bit architectures (x86_64, ARM-64)
 * actually define a 48-bit virtual address space.
 * Foreign values will be sign-extended as required, i.e creating a foreign
 * value of something like `(void *) -1` will work as expected. This is
 * important because in some 64-bit OSs (e.g. Solaris) the user stack grows
 * downwards from the end of the address space.
 *
 * If you need to store exactly sizeof(void*) bytes of raw data where
 * `sizeof(void*)` >= 8, please use byte arrays instead.
 */
NOINSTR v7_val_t v7_mk_foreign(struct v7 *v7, void *ptr);

/*
 * Returns `void *` pointer stored in `v7_val_t`.
 *
 * Returns NULL if the value is not a foreign pointer.
 */
NOINSTR void *v7_get_ptr(struct v7 *v7, v7_val_t v);

/* Returns true if given value holds `void *` pointer */
int v7_is_foreign(v7_val_t v);

#if defined(__cplusplus)
}
#endif /* __cplusplus */

#endif /* CS_V7_SRC_PRIMITIVE_PUBLIC_H_ */
#ifdef V7_MODULE_LINES
#line 1 "v7/src/primitive.h"
#endif
/*
 * Copyright (c) 2014 Cesanta Software Limited
 * All rights reserved
 */

#ifndef CS_V7_SRC_PRIMITIVE_H_
#define CS_V7_SRC_PRIMITIVE_H_

/* Amalgamated: #include "v7/src/primitive_public.h" */

/* Amalgamated: #include "v7/src/core.h" */

/* Returns true if given value is a number, not NaN and not Infinity. */
V7_PRIVATE int is_finite(struct v7 *v7, v7_val_t v);

V7_PRIVATE val_t pointer_to_value(void *p);
V7_PRIVATE void *get_ptr(val_t v);

#endif /* CS_V7_SRC_PRIMITIVE_H_ */
#ifdef V7_MODULE_LINES
#line 1 "v7/src/string_public.h"
#endif
/*
 * Copyright (c) 2014 Cesanta Software Limited
 * All rights reserved
 */

/*
 * === Strings
 */

#ifndef CS_V7_SRC_STRING_PUBLIC_H_
#define CS_V7_SRC_STRING_PUBLIC_H_

/* Amalgamated: #include "v7/src/core_public.h" */

#if defined(__cplusplus)
extern "C" {
#endif /* __cplusplus */

/*
 * Creates a string primitive value.
 * `str` must point to the utf8 string of length `len`.
 * If `len` is ~0, `str` is assumed to be NUL-terminated and `strlen(str)` is
 * used.
 *
 * If `copy` is non-zero, the string data is copied and owned by the GC. The
 * caller can free the string data afterwards. Otherwise (`copy` is zero), the
 * caller owns the string data, and is responsible for not freeing it while it
 * is used.
 */
v7_val_t v7_mk_string(struct v7 *v7, const char *str, size_t len, int copy);

/* Returns true if given value is a primitive string value */
int v7_is_string(v7_val_t v);

/*
 * Returns a pointer to the string stored in `v7_val_t`.
 *
 * String length returned in `len`, which is allowed to be NULL. Returns NULL
 * if the value is not a string.
 *
 * JS strings can contain embedded NUL chars and may or may not be NUL
 * terminated.
 *
 * CAUTION: creating new JavaScript object, array, or string may kick in a
 * garbage collector, which in turn may relocate string data and invalidate
 * pointer returned by `v7_get_string()`.
 *
 * Short JS strings are embedded inside the `v7_val_t` value itself. This is why
 * a pointer to a `v7_val_t` is required. It also means that the string data
 * will become invalid once that `v7_val_t` value goes out of scope.
 */
const char *v7_get_string(struct v7 *v7, v7_val_t *v, size_t *len);

/*
 * Returns a pointer to the string stored in `v7_val_t`.
 *
 * Returns NULL if the value is not a string or if the string is not compatible
 * with a C string.
 *
 * C compatible strings contain exactly one NUL char, in terminal position.
 *
 * All strings owned by the V7 engine (see `v7_mk_string()`) are guaranteed to
 * be NUL terminated. Out of these, those that don't include embedded NUL chars
 * are guaranteed to be C compatible.
 */
const char *v7_get_cstring(struct v7 *v7, v7_val_t *v);

#if defined(__cplusplus)
}
#endif /* __cplusplus */

#endif /* CS_V7_SRC_STRING_PUBLIC_H_ */
#ifdef V7_MODULE_LINES
#line 1 "v7/src/string.h"
#endif
/*
 * Copyright (c) 2014 Cesanta Software Limited
 * All rights reserved
 */

#ifndef CS_V7_SRC_STRING_H_
#define CS_V7_SRC_STRING_H_

/* Amalgamated: #include "v7/src/string_public.h" */

/* Amalgamated: #include "v7/src/core.h" */

/*
 * Size of the extra space for strings mbuf that is needed to avoid frequent
 * reallocations
 */
#define _V7_STRING_BUF_RESERVE 500

#if defined(__cplusplus)
extern "C" {
#endif /* __cplusplus */

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err v7_char_code_at(struct v7 *v7, v7_val_t s, v7_val_t at,
                                       double *res);
V7_PRIVATE int s_cmp(struct v7 *, val_t a, val_t b);
V7_PRIVATE val_t s_concat(struct v7 *, val_t, val_t);

/*
 * Convert a C string to to an unsigned integer.
 * `ok` will be set to true if the string conforms to
 * an unsigned long.
 */
WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err str_to_ulong(struct v7 *v7, val_t v, int *ok,
                                    unsigned long *res);

/*
 * Convert a V7 string to to an unsigned integer.
 * `ok` will be set to true if the string conforms to
 * an unsigned long.
 *
 * Use it if only you need strong conformity of the value to an integer;
 * otherwise, use `to_long()` or `to_number_v()` instead.
 */
V7_PRIVATE unsigned long cstr_to_ulong(const char *s, size_t len, int *ok);

enum embstr_flags {
  EMBSTR_ZERO_TERM = (1 << 0),
  EMBSTR_UNESCAPE = (1 << 1),
};

V7_PRIVATE void embed_string(struct mbuf *m, size_t offset, const char *p,
                             size_t len, uint8_t /*enum embstr_flags*/ flags);

V7_PRIVATE size_t unescape(const char *s, size_t len, char *to);

#if defined(__cplusplus)
}
#endif /* __cplusplus */

#endif /* CS_V7_SRC_STRING_H_ */
#ifdef V7_MODULE_LINES
#line 1 "v7/src/exceptions_public.h"
#endif
/*
 * Copyright (c) 2014 Cesanta Software Limited
 * All rights reserved
 */

/*
 * === Exceptions
 */

#ifndef CS_V7_SRC_EXCEPTIONS_PUBLIC_H_
#define CS_V7_SRC_EXCEPTIONS_PUBLIC_H_

/* Amalgamated: #include "v7/src/core_public.h" */

#if defined(__cplusplus)
extern "C" {
#endif /* __cplusplus */

/* Throw an exception with an already existing value. */
WARN_UNUSED_RESULT
enum v7_err v7_throw(struct v7 *v7, v7_val_t v);

/*
 * Throw an exception with given formatted message.
 *
 * Pass "Error" as typ for a generic error.
 */
WARN_UNUSED_RESULT
enum v7_err v7_throwf(struct v7 *v7, const char *typ, const char *err_fmt, ...);

/*
 * Rethrow the currently thrown object. In fact, it just returns
 * V7_EXEC_EXCEPTION.
 */
WARN_UNUSED_RESULT
enum v7_err v7_rethrow(struct v7 *v7);

/*
 * Returns the value that is being thrown at the moment, or `undefined` if
 * nothing is being thrown. If `is_thrown` is not `NULL`, it will be set
 * to either 0 or 1, depending on whether something is thrown at the moment.
 */
v7_val_t v7_get_thrown_value(struct v7 *v7, unsigned char *is_thrown);

/* Clears currently thrown value, if any. */
void v7_clear_thrown_value(struct v7 *v7);

#if defined(__cplusplus)
}
#endif /* __cplusplus */

#endif /* CS_V7_SRC_EXCEPTIONS_PUBLIC_H_ */
#ifdef V7_MODULE_LINES
#line 1 "v7/src/exceptions.h"
#endif
/*
 * Copyright (c) 2014 Cesanta Software Limited
 * All rights reserved
 */

#ifndef CS_V7_SRC_EXCEPTIONS_H_
#define CS_V7_SRC_EXCEPTIONS_H_

/* Amalgamated: #include "v7/src/exceptions_public.h" */

/* Amalgamated: #include "v7/src/core.h" */

/*
 * Try to perform some arbitrary call, and if the result is other than `V7_OK`,
 * "throws" an error with `V7_THROW()`
 */
#define V7_TRY2(call, clean_label)           \
  do {                                       \
    enum v7_err _e = call;                   \
    V7_CHECK2(_e == V7_OK, _e, clean_label); \
  } while (0)

/*
 * Sets return value to the provided one, and `goto`s `clean`.
 *
 * For this to work, you should have local `enum v7_err rcode` variable,
 * and a `clean` label.
 */
#define V7_THROW2(err_code, clean_label)                              \
  do {                                                                \
    (void) v7;                                                        \
    rcode = (err_code);                                               \
    assert(rcode != V7_OK);                                           \
    assert(!v7_is_undefined(v7->vals.thrown_error) && v7->is_thrown); \
    goto clean_label;                                                 \
  } while (0)

/*
 * Checks provided condition `cond`, and if it's false, then "throws"
 * provided `err_code` (see `V7_THROW()`)
 */
#define V7_CHECK2(cond, err_code, clean_label) \
  do {                                         \
    if (!(cond)) {                             \
      V7_THROW2(err_code, clean_label);        \
    }                                          \
  } while (0)

/*
 * Checks provided condition `cond`, and if it's false, then "throws"
 * internal error
 *
 * TODO(dfrank): it would be good to have formatted string: then, we can
 * specify file and line.
 */
#define V7_CHECK_INTERNAL2(cond, clean_label)                         \
  do {                                                                \
    if (!(cond)) {                                                    \
      enum v7_err __rcode = v7_throwf(v7, "Error", "Internal error"); \
      (void) __rcode;                                                 \
      V7_THROW2(V7_INTERNAL_ERROR, clean_label);                      \
    }                                                                 \
  } while (0)

/*
 * Shortcuts for the macros above, but they assume the clean label `clean`.
 */

#define V7_TRY(call) V7_TRY2(call, clean)
#define V7_THROW(err_code) V7_THROW2(err_code, clean)
#define V7_CHECK(cond, err_code) V7_CHECK2(cond, err_code, clean)
#define V7_CHECK_INTERNAL(cond) V7_CHECK_INTERNAL2(cond, clean)

#if defined(__cplusplus)
extern "C" {
#endif /* __cplusplus */

/*
 * At the moment, most of the exception-related functions are public, and are
 * declared in `exceptions_public.h`
 */

/*
 * Create an instance of the exception with type `typ` (see `TYPE_ERROR`,
 * `SYNTAX_ERROR`, etc), and message `msg`.
 */
V7_PRIVATE enum v7_err create_exception(struct v7 *v7, const char *typ,
                                        const char *msg, val_t *res);

#if defined(__cplusplus)
}
#endif /* __cplusplus */

#endif /* CS_V7_SRC_EXCEPTIONS_H_ */
#ifdef V7_MODULE_LINES
#line 1 "v7/src/object.h"
#endif
/*
 * Copyright (c) 2014 Cesanta Software Limited
 * All rights reserved
 */

#ifndef CS_V7_SRC_OBJECT_H_
#define CS_V7_SRC_OBJECT_H_

/* Amalgamated: #include "v7/src/object_public.h" */

/* Amalgamated: #include "v7/src/internal.h" */
/* Amalgamated: #include "v7/src/core.h" */

V7_PRIVATE val_t mk_object(struct v7 *v7, val_t prototype);
V7_PRIVATE val_t v7_object_to_value(struct v7_object *o);
V7_PRIVATE struct v7_generic_object *get_generic_object_struct(val_t v);

/*
 * Returns pointer to the struct representing an object.
 * Given value must be an object (the caller can verify it
 * by calling `v7_is_object()`)
 */
V7_PRIVATE struct v7_object *get_object_struct(v7_val_t v);

/*
 * Return true if given value is a JavaScript object (will return
 * false for function)
 */
V7_PRIVATE int v7_is_generic_object(v7_val_t v);

V7_PRIVATE struct v7_property *v7_mk_property(struct v7 *v7);

V7_PRIVATE struct v7_property *v7_get_own_property2(struct v7 *v7, val_t obj,
                                                    const char *name,
                                                    size_t len,
                                                    v7_prop_attr_t attrs);

V7_PRIVATE struct v7_property *v7_get_own_property(struct v7 *v7, val_t obj,
                                                   const char *name,
                                                   size_t len);

/*
 * If `len` is -1/MAXUINT/~0, then `name` must be 0-terminated
 *
 * Returns a pointer to the property structure, given an object and a name of
 * the property as a pointer to string buffer and length.
 *
 * See also `v7_get_property_v`
 */
V7_PRIVATE struct v7_property *v7_get_property(struct v7 *v7, val_t obj,
                                               const char *name, size_t len);

/*
 * Just like `v7_get_property`, but takes name as a `v7_val_t`
 */
V7_PRIVATE enum v7_err v7_get_property_v(struct v7 *v7, val_t obj,
                                         v7_val_t name,
                                         struct v7_property **res);

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err v7_get_throwing_v(struct v7 *v7, v7_val_t obj,
                                         v7_val_t name, v7_val_t *res);

V7_PRIVATE void v7_destroy_property(struct v7_property **p);

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err v7_invoke_setter(struct v7 *v7, struct v7_property *prop,
                                        val_t obj, val_t val);

/*
 * Like `set_property`, but takes property name as a `val_t`
 */
WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err set_property_v(struct v7 *v7, val_t obj, val_t name,
                                      val_t val, struct v7_property **res);

/*
 * Like JavaScript assignment: set a property with given `name` + `len` at
 * the object `obj` to value `val`. Returns a property through the `res`
 * (which may be `NULL` if return value is not required)
 */
WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err set_property(struct v7 *v7, val_t obj, const char *name,
                                    size_t len, v7_val_t val,
                                    struct v7_property **res);

/*
 * Like `def_property()`, but takes property name as a `val_t`
 */
WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err def_property_v(struct v7 *v7, val_t obj, val_t name,
                                      v7_prop_attr_desc_t attrs_desc, val_t val,
                                      uint8_t as_assign,
                                      struct v7_property **res);

/*
 * Define object property, similar to JavaScript `Object.defineProperty()`.
 *
 * Just like public `v7_def()`, but returns `enum v7_err`, and therefore can
 * throw.
 *
 * Additionally, takes param `as_assign`: if it is non-zero, it behaves
 * similarly to plain JavaScript assignment in terms of some exception-related
 * corner cases.
 *
 * `res` may be `NULL`.
 */
WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err def_property(struct v7 *v7, val_t obj, const char *name,
                                    size_t len, v7_prop_attr_desc_t attrs_desc,
                                    v7_val_t val, uint8_t as_assign,
                                    struct v7_property **res);

V7_PRIVATE int set_method(struct v7 *v7, val_t obj, const char *name,
                          v7_cfunction_t *func, int num_args);

V7_PRIVATE int set_cfunc_prop(struct v7 *v7, val_t o, const char *name,
                              v7_cfunction_t *func);

/* Return address of property value or NULL if the passed property is NULL */
WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err v7_property_value(struct v7 *v7, val_t obj,
                                         struct v7_property *p, val_t *res);

#if V7_ENABLE__Proxy
/*
 * Additional context for property iteration of a proxied object, see
 * `v7_next_prop()`.
 */
struct prop_iter_proxy_ctx {
  /* Proxy target object */
  v7_val_t target_obj;
  /* Proxy handler object */
  v7_val_t handler_obj;

  /*
   * array returned by the `ownKeys` callback, valid if only `has_own_keys` is
   * set
   */
  v7_val_t own_keys;
  /*
   * callback to get property descriptor, one of these:
   *   - a JS or cfunction `getOwnPropertyDescriptor`
   *     (if `has_get_own_prop_desc_C` is not set);
   *   - a C callback `v7_get_own_prop_desc_cb_t`.
   *     (if `has_get_own_prop_desc_C` is set);
   */
  v7_val_t get_own_prop_desc;

  /*
   * if `has_own_keys` is set, `own_key_idx` represents next index in the
   * `own_keys` array
   */
  unsigned own_key_idx : 29;

  /* if set, `own_keys` is valid */
  unsigned has_own_keys : 1;
  /* if set, `get_own_prop_desc` is valid */
  unsigned has_get_own_prop_desc : 1;
  /*
   * if set, `get_own_prop_desc` is a C callback `has_get_own_prop_desc_C`, not
   * a JS callback
   */
  unsigned has_get_own_prop_desc_C : 1;
};
#endif

/*
 * Like public function `v7_init_prop_iter_ctx()`, but it takes additional
 * argument `proxy_transp`; if it is zero, and the given `obj` is a Proxy, it
 * will iterate the properties of the proxy itself, not the Proxy's target.
 */
WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err init_prop_iter_ctx(struct v7 *v7, v7_val_t obj,
                                          int proxy_transp,
                                          struct prop_iter_ctx *ctx);

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err next_prop(struct v7 *v7, struct prop_iter_ctx *ctx,
                                 v7_val_t *name, v7_val_t *value,
                                 v7_prop_attr_t *attrs, int *ok);

/*
 * Set new prototype `proto` for the given object `obj`. Returns `0` at
 * success, `-1` at failure (it may fail if given `obj` is a function object:
 * it's impossible to change function object's prototype)
 */
V7_PRIVATE int obj_prototype_set(struct v7 *v7, struct v7_object *obj,
                                 struct v7_object *proto);

/*
 * Given a pointer to the object structure, returns a
 * pointer to the prototype object, or `NULL` if there is
 * no prototype.
 */
V7_PRIVATE struct v7_object *obj_prototype(struct v7 *v7,
                                           struct v7_object *obj);

V7_PRIVATE int is_prototype_of(struct v7 *v7, val_t o, val_t p);

/* Get the property holding user data and destructor, or NULL */
V7_PRIVATE struct v7_property *get_user_data_property(v7_val_t obj);

#endif /* CS_V7_SRC_OBJECT_H_ */
#ifdef V7_MODULE_LINES
#line 1 "v7/src/exec_public.h"
#endif
/*
 * Copyright (c) 2014 Cesanta Software Limited
 * All rights reserved
 */

/*
 * === Execution of JavaScript code
 */

#ifndef CS_V7_SRC_EXEC_PUBLIC_H_
#define CS_V7_SRC_EXEC_PUBLIC_H_

/* Amalgamated: #include "v7/src/core_public.h" */

#if defined(__cplusplus)
extern "C" {
#endif /* __cplusplus */

/*
 * Execute JavaScript `js_code`. The result of evaluation is stored in
 * the `result` variable.
 *
 * Return:
 *
 *  - V7_OK on success. `result` contains the result of execution.
 *  - V7_SYNTAX_ERROR if `js_code` in not a valid code. `result` is undefined.
 *  - V7_EXEC_EXCEPTION if `js_code` threw an exception. `result` stores
 *    an exception object.
 *  - V7_AST_TOO_LARGE if `js_code` contains an AST segment longer than 16 bit.
 *    `result` is undefined. To avoid this error, build V7 with V7_LARGE_AST.
 */
WARN_UNUSED_RESULT
enum v7_err v7_exec(struct v7 *v7, const char *js_code, v7_val_t *result);

/*
 * Options for `v7_exec_opt()`. To get default options, like `v7_exec()` uses,
 * just zero out this struct.
 */
struct v7_exec_opts {
  /* Filename, used for stack traces only */
  const char *filename;

  /*
   * Object to be used as `this`. Note: when it is zeroed out, i.e. it's a
   * number `0`, the `undefined` value is assumed. It means that it's
   * impossible to actually use the number `0` as `this` object, but it makes
   * little sense anyway.
   */
  v7_val_t this_obj;

  /* Whether the given `js_code` should be interpreted as JSON, not JS code */
  unsigned is_json : 1;
};

/*
 * Customizable version of `v7_exec()`: allows to specify various options, see
 * `struct v7_exec_opts`.
 */
enum v7_err v7_exec_opt(struct v7 *v7, const char *js_code,
                        const struct v7_exec_opts *opts, v7_val_t *res);

/*
 * Same as `v7_exec()`, but loads source code from `path` file.
 */
WARN_UNUSED_RESULT
enum v7_err v7_exec_file(struct v7 *v7, const char *path, v7_val_t *result);

/*
 * Parse `str` and store corresponding JavaScript object in `res` variable.
 * String `str` should be '\0'-terminated.
 * Return value and semantic is the same as for `v7_exec()`.
 */
WARN_UNUSED_RESULT
enum v7_err v7_parse_json(struct v7 *v7, const char *str, v7_val_t *res);

/*
 * Same as `v7_parse_json()`, but loads JSON string from `path`.
 */
WARN_UNUSED_RESULT
enum v7_err v7_parse_json_file(struct v7 *v7, const char *path, v7_val_t *res);

#if !defined(V7_NO_COMPILER)

/*
 * Compile JavaScript code `js_code` into the byte code and write generated
 * byte code into opened file stream `fp`. If `generate_binary_output` is 0,
 * then generated byte code is in human-readable text format. Otherwise, it is
 * in the binary format, suitable for execution by V7 instance.
 * NOTE: `fp` must be a valid, opened, writable file stream.
 */
WARN_UNUSED_RESULT
enum v7_err v7_compile(const char *js_code, int generate_binary_output,
                       int use_bcode, FILE *fp);

#endif /* V7_NO_COMPILER */

/*
 * Call function `func` with arguments `args`, using `this_obj` as `this`.
 * `args` should be an array containing arguments or `undefined`.
 *
 * `res` can be `NULL` if return value is not required.
 */
WARN_UNUSED_RESULT
enum v7_err v7_apply(struct v7 *v7, v7_val_t func, v7_val_t this_obj,
                     v7_val_t args, v7_val_t *res);

#if defined(__cplusplus)
}
#endif /* __cplusplus */

#endif /* CS_V7_SRC_EXEC_PUBLIC_H_ */
#ifdef V7_MODULE_LINES
#line 1 "v7/src/exec.h"
#endif
/*
 * Copyright (c) 2014 Cesanta Software Limited
 * All rights reserved
 */

#ifndef CS_V7_SRC_EXEC_H_
#define CS_V7_SRC_EXEC_H_

/* Amalgamated: #include "v7/src/exec_public.h" */

/* Amalgamated: #include "v7/src/core.h" */

#if !defined(V7_NO_COMPILER)

#if defined(__cplusplus)
extern "C" {
#endif /* __cplusplus */

/*
 * At the moment, all exec-related functions are public, and are declared in
 * `exec_public.h`
 */

WARN_UNUSED_RESULT
enum v7_err _v7_compile(const char *js_code, size_t js_code_size,
                        int generate_binary_output, int use_bcode, FILE *fp);

#if defined(__cplusplus)
}
#endif /* __cplusplus */

#endif /* V7_NO_COMPILER */

#endif /* CS_V7_SRC_EXEC_H_ */
#ifdef V7_MODULE_LINES
#line 1 "v7/src/array_public.h"
#endif
/*
 * Copyright (c) 2014 Cesanta Software Limited
 * All rights reserved
 */

/*
 * === Arrays
 */

#ifndef CS_V7_SRC_ARRAY_PUBLIC_H_
#define CS_V7_SRC_ARRAY_PUBLIC_H_

/* Amalgamated: #include "v7/src/core_public.h" */

#if defined(__cplusplus)
extern "C" {
#endif /* __cplusplus */

/* Make an empty array object */
v7_val_t v7_mk_array(struct v7 *v7);

/* Returns true if given value is an array object */
int v7_is_array(struct v7 *v7, v7_val_t v);

/* Returns length on an array. If `arr` is not an array, 0 is returned. */
unsigned long v7_array_length(struct v7 *v7, v7_val_t arr);

/* Insert value `v` in array `arr` at the end of the array. */
int v7_array_push(struct v7 *, v7_val_t arr, v7_val_t v);

/*
 * Like `v7_array_push()`, but "returns" value through the `res` pointer
 * argument. `res` is allowed to be `NULL`.
 *
 * Caller should check the error code returned, and if it's something other
 * than `V7_OK`, perform cleanup and return this code further.
 */
WARN_UNUSED_RESULT
enum v7_err v7_array_push_throwing(struct v7 *v7, v7_val_t arr, v7_val_t v,
                                   int *res);

/*
 * Return array member at index `index`. If `index` is out of bounds, undefined
 * is returned.
 */
v7_val_t v7_array_get(struct v7 *, v7_val_t arr, unsigned long index);

/* Insert value `v` into `arr` at index `index`. */
int v7_array_set(struct v7 *v7, v7_val_t arr, unsigned long index, v7_val_t v);

/*
 * Like `v7_array_set()`, but "returns" value through the `res` pointer
 * argument. `res` is allowed to be `NULL`.
 *
 * Caller should check the error code returned, and if it's something other
 * than `V7_OK`, perform cleanup and return this code further.
 */
WARN_UNUSED_RESULT
enum v7_err v7_array_set_throwing(struct v7 *v7, v7_val_t arr,
                                  unsigned long index, v7_val_t v, int *res);

/* Delete value in array `arr` at index `index`, if it exists. */
void v7_array_del(struct v7 *v7, v7_val_t arr, unsigned long index);

#if defined(__cplusplus)
}
#endif /* __cplusplus */

#endif /* CS_V7_SRC_ARRAY_PUBLIC_H_ */
#ifdef V7_MODULE_LINES
#line 1 "v7/src/array.h"
#endif
/*
 * Copyright (c) 2014 Cesanta Software Limited
 * All rights reserved
 */

#ifndef CS_V7_SRC_ARRAY_H_
#define CS_V7_SRC_ARRAY_H_

/* Amalgamated: #include "v7/src/array_public.h" */

/* Amalgamated: #include "v7/src/core.h" */

#if defined(__cplusplus)
extern "C" {
#endif /* __cplusplus */

V7_PRIVATE v7_val_t v7_mk_dense_array(struct v7 *v7);
V7_PRIVATE val_t
v7_array_get2(struct v7 *v7, v7_val_t arr, unsigned long index, int *has);

#if defined(__cplusplus)
}
#endif /* __cplusplus */

#endif /* CS_V7_SRC_ARRAY_H_ */
#ifdef V7_MODULE_LINES
#line 1 "v7/src/conversion_public.h"
#endif
/*
 * Copyright (c) 2014 Cesanta Software Limited
 * All rights reserved
 */

/*
 * === Conversion
 */

#ifndef CS_V7_SRC_CONVERSION_PUBLIC_H_
#define CS_V7_SRC_CONVERSION_PUBLIC_H_

/* Amalgamated: #include "v7/src/core_public.h" */

#if defined(__cplusplus)
extern "C" {
#endif /* __cplusplus */

/* Stringify mode, see `v7_stringify()` and `v7_stringify_throwing()` */
enum v7_stringify_mode {
  V7_STRINGIFY_DEFAULT,
  V7_STRINGIFY_JSON,
  V7_STRINGIFY_DEBUG,
};

/*
 * Generate string representation of the JavaScript value `val` into a buffer
 * `buf`, `len`. If `len` is too small to hold a generated string,
 * `v7_stringify()` allocates required memory. In that case, it is caller's
 * responsibility to free the allocated buffer. Generated string is guaranteed
 * to be 0-terminated.
 *
 * Available stringification modes are:
 *
 * - `V7_STRINGIFY_DEFAULT`:
 *   Convert JS value to string, using common JavaScript semantics:
 *   - If value is an object:
 *     - call `toString()`;
 *     - If `toString()` returned non-primitive value, call `valueOf()`;
 *     - If `valueOf()` returned non-primitive value, throw `TypeError`.
 *   - Now we have a primitive, and if it's not a string, then stringify it.
 *
 * - `V7_STRINGIFY_JSON`:
 *   Generate JSON output
 *
 * - `V7_STRINGIFY_DEBUG`:
 *   Mostly like JSON, but will not omit non-JSON objects like functions.
 *
 * Example code:
 *
 *     char buf[100], *p;
 *     p = v7_stringify(v7, obj, buf, sizeof(buf), V7_STRINGIFY_DEFAULT);
 *     printf("JSON string: [%s]\n", p);
 *     if (p != buf) {
 *       free(p);
 *     }
 */
char *v7_stringify(struct v7 *v7, v7_val_t v, char *buf, size_t len,
                   enum v7_stringify_mode mode);

/*
 * Like `v7_stringify()`, but "returns" value through the `res` pointer
 * argument. `res` must not be `NULL`.
 *
 * Caller should check the error code returned, and if it's something other
 * than `V7_OK`, perform cleanup and return this code further.
 */
WARN_UNUSED_RESULT
enum v7_err v7_stringify_throwing(struct v7 *v7, v7_val_t v, char *buf,
                                  size_t size, enum v7_stringify_mode mode,
                                  char **res);

/*
 * A shortcut for `v7_stringify()` with `V7_STRINGIFY_JSON`
 */
#define v7_to_json(a, b, c, d) v7_stringify(a, b, c, d, V7_STRINGIFY_JSON)

/* Returns true if given value evaluates to true, as in `if (v)` statement. */
int v7_is_truthy(struct v7 *v7, v7_val_t v);

#if defined(__cplusplus)
}
#endif /* __cplusplus */

#endif /* CS_V7_SRC_CONVERSION_PUBLIC_H_ */
#ifdef V7_MODULE_LINES
#line 1 "v7/src/conversion.h"
#endif
/*
 * Copyright (c) 2014 Cesanta Software Limited
 * All rights reserved
 */

#ifndef CS_V7_SRC_CONVERSION_H_
#define CS_V7_SRC_CONVERSION_H_

/* Amalgamated: #include "v7/src/conversion_public.h" */

/* Amalgamated: #include "v7/src/core.h" */

#if defined(__cplusplus)
extern "C" {
#endif /* __cplusplus */

/*
 * Conversion API
 * ==============
 *
 * - If you need to convert any JS value to string using common JavaScript
 *   semantics, use `to_string()`, which can convert to both `v7_val_t` or your
 *   C buffer.
 *
 * - If you need to convert any JS value to number using common JavaScript
 *   semantics, use `to_number_v()`;
 *
 * - If you need to convert any JS value to primitive, without forcing it to
 *   string or number, use `to_primitive()` (see comments for this function for
 *   details);
 *
 * - If you have a primitive value, and you want to convert it to either string
 *   or number, you can still use functions above: `to_string()` and
 *   `to_number_v()`. But if you want to save a bit of work, use:
 *   - `primitive_to_str()`
 *   - `primitive_to_number()`
 *
 *   In fact, these are a bit lower level functions, which are used by
 *   `to_string()` and `to_number_v()` after converting value to
 *   primitive.
 *
 * - If you want to call `valueOf()` on the object, use `obj_value_of()`;
 * - If you want to call `toString()` on the object, use `obj_to_string()`;
 *
 * - If you need to convert any JS value to boolean using common JavaScript
 *   semantics (as in the expression `if (v)` or `Boolean(v)`), use
 *   `to_boolean_v()`.
 *
 * - If you want to get the JSON representation of a value, use
 *   `to_json_or_debug()`, passing `0` as `is_debug` : writes data to your C
 *   buffer;
 *
 * - There is one more kind of representation: `DEBUG`. It's very similar to
 *   JSON, but it will not omit non-JSON values, such as functions. Again, use
 *   `to_json_or_debug()`, but pass `1` as `is_debug` this time: writes data to
 *   your C buffer;
 *
 * Additionally, for any kind of to-string conversion into C buffer, you can
 * use a convenience wrapper function (mostly for public API), which can
 * allocate the buffer for you:
 *
 *   - `v7_stringify_throwing()`;
 *   - `v7_stringify()` : the same as above, but doesn't throw.
 *
 * There are a couple of more specific conversions, which I'd like to probably
 * refactor or remove in the future:
 *
 * - `to_long()` : if given value is `undefined`, returns provided default
 *   value; otherwise, converts value to number, and then truncates to `long`.
 * - `str_to_ulong()` : converts the value to string, and tries to parse it as
 *   an integer. Use it if only you need strong conformity ov the value to an
 *   integer (currently, it's used only when examining keys of array object)
 *
 * ----------------------------------------------------------------------------
 *
 * TODO(dfrank):
 *   - Rename functions like `v7_get_double(v7, )`, `get_object_struct()` to
 *something
 *     that will clearly identify that they convert to some C entity, not
 *     `v7_val_t`
 *   - Maybe make `to_string()` private? But then, there will be no way
 *     in public API to convert value to `v7_val_t` string, so, for now
 *     it's here.
 *   - When we agree on what goes to public API, and what does not, write
 *     similar conversion guide for public API (in `conversion_public.h`)
 */

/*
 * Convert any JS value to number, using common JavaScript semantics:
 *
 * - If value is an object:
 *   - call `valueOf()`;
 *   - If `valueOf()` returned non-primitive value, call `toString()`;
 *   - If `toString()` returned non-primitive value, throw `TypeError`.
 * - Now we have a primitive, and if it's not a number, then:
 *   - If `undefined`, return `NaN`
 *   - If `null`, return 0.0
 *   - If boolean, return either 1 or 0
 *   - If string, try to parse it.
 */
WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err to_number_v(struct v7 *v7, v7_val_t v, v7_val_t *res);

/*
 * Convert any JS value to string, using common JavaScript semantics,
 * see `v7_stringify()` and `V7_STRINGIFY_DEFAULT`.
 *
 * This function can return multiple things:
 *
 * - String as a `v7_val_t` (if `res` is not `NULL`)
 * - String copied to buffer `buf` with max size `buf_size` (if `buf` is not
 *   `NULL`)
 * - Length of actual string, independently of `buf_size` (if `res_len` is not
 *   `NULL`)
 *
 * The rationale of having multiple formats of returned value is the following:
 *
 * Initially, to-string conversion always returned `v7_val_t`. But it turned
 * out that there are situations where such an approach adds useless pressure
 * on GC: e.g. when converting `undefined` to string, and the caller actually
 * needs a C buffer, not a `v7_val_t`.
 *
 * Always returning string through `buf`+`buf_size` is bad as well: if we
 * convert from object to string, and either `toString()` or `valueOf()`
 * returned string, then we'd have to get string data from it, write to buffer,
 * and if caller actually need `v7_val_t`, then it will have to create new
 * instance of the same string: again, useless GC pressure.
 *
 * So, we have to use the combined approach. This function will make minimal
 * work depending on give `res` and `buf`.
 */
WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err to_string(struct v7 *v7, v7_val_t v, v7_val_t *res,
                                 char *buf, size_t buf_size, size_t *res_len);

/*
 * Convert value to primitive, if it's not already.
 *
 * For object-to-primitive conversion, each object in JavaScript has two
 * methods: `toString()` and `valueOf()`.
 *
 * When converting object to string, JavaScript does the following:
 *   - call `toString()`;
 *   - If `toString()` returned non-primitive value, call `valueOf()`;
 *   - If `valueOf()` returned non-primitive value, throw `TypeError`.
 *
 * When converting object to number, JavaScript calls the same functions,
 * but in reverse:
 *   - call `valueOf()`;
 *   - If `valueOf()` returned non-primitive value, call `toString()`;
 *   - If `toString()` returned non-primitive value, throw `TypeError`.
 *
 * This function `to_primitive()` performs either type of conversion,
 * depending on the `hint` argument (see `enum to_primitive_hint`).
 */
enum to_primitive_hint {
  /* Call `valueOf()` first, then `toString()` if needed */
  V7_TO_PRIMITIVE_HINT_NUMBER,

  /* Call `toString()` first, then `valueOf()` if needed */
  V7_TO_PRIMITIVE_HINT_STRING,

  /* STRING for Date, NUMBER for everything else */
  V7_TO_PRIMITIVE_HINT_AUTO,
};
WARN_UNUSED_RESULT
enum v7_err to_primitive(struct v7 *v7, v7_val_t v, enum to_primitive_hint hint,
                         v7_val_t *res);

/*
 * Convert primitive value to string, using common JavaScript semantics. If
 * you need to convert any value to string (either object or primitive),
 * see `to_string()` or `v7_stringify_throwing()`.
 *
 * This function can return multiple things:
 *
 * - String as a `v7_val_t` (if `res` is not `NULL`)
 * - String copied to buffer `buf` with max size `buf_size` (if `buf` is not
 *   `NULL`)
 * - Length of actual string, independently of `buf_size` (if `res_len` is not
 *   `NULL`)
 */
WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err primitive_to_str(struct v7 *v7, val_t v, val_t *res,
                                        char *buf, size_t buf_size,
                                        size_t *res_len);

/*
 * Convert primitive value to number, using common JavaScript semantics. If you
 * need to convert any value to number (either object or primitive), see
 * `to_number_v()`
 */
WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err primitive_to_number(struct v7 *v7, val_t v, val_t *res);

/*
 * Convert value to JSON or "debug" representation, depending on whether
 * `is_debug` is non-zero. The "debug" is the same as JSON, but non-JSON values
 * (functions, `undefined`, etc) will not be omitted.
 *
 * See also `v7_stringify()`, `v7_stringify_throwing()`.
 */
WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err to_json_or_debug(struct v7 *v7, val_t v, char *buf,
                                        size_t size, size_t *res_len,
                                        uint8_t is_debug);

/*
 * Calls `valueOf()` on given object `v`
 */
WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err obj_value_of(struct v7 *v7, val_t v, val_t *res);

/*
 * Calls `toString()` on given object `v`
 */
WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err obj_to_string(struct v7 *v7, val_t v, val_t *res);

/*
 * If given value is `undefined`, returns `default_value`; otherwise,
 * converts value to number, and then truncates to `long`.
 */
WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err to_long(struct v7 *v7, val_t v, long default_value,
                               long *res);

/*
 * Converts value to boolean as in the expression `if (v)` or `Boolean(v)`.
 *
 * NOTE: it can't throw (even if the given value is an object with `valueOf()`
 * that throws), so it returns `val_t` directly.
 */
WARN_UNUSED_RESULT
V7_PRIVATE val_t to_boolean_v(struct v7 *v7, val_t v);

#if defined(__cplusplus)
}
#endif /* __cplusplus */

#endif /* CS_V7_SRC_CONVERSION_H_ */
#ifdef V7_MODULE_LINES
#line 1 "v7/src/varint.h"
#endif
/*
 * Copyright (c) 2014 Cesanta Software Limited
 * All rights reserved
 */

#ifndef CS_V7_SRC_VARINT_H_
#define CS_V7_SRC_VARINT_H_

/* Amalgamated: #include "v7/src/internal.h" */

#if defined(__cplusplus)
extern "C" {
#endif /* __cplusplus */

V7_PRIVATE int encode_varint(size_t len, unsigned char *p);
V7_PRIVATE size_t decode_varint(const unsigned char *p, int *llen);
V7_PRIVATE int calc_llen(size_t len);

#if defined(__cplusplus)
}
#endif /* __cplusplus */

#endif /* CS_V7_SRC_VARINT_H_ */
#ifdef V7_MODULE_LINES
#line 1 "common/cs_strtod.h"
#endif
/*
 * Copyright (c) 2014-2016 Cesanta Software Limited
 * All rights reserved
 */

#ifndef CS_COMMON_CS_STRTOD_H_
#define CS_COMMON_CS_STRTOD_H_

double cs_strtod(const char *str, char **endptr);

#endif /* CS_COMMON_CS_STRTOD_H_ */
#ifdef V7_MODULE_LINES
#line 1 "v7/src/ast.h"
#endif
/*
 * Copyright (c) 2014 Cesanta Software Limited
 * All rights reserved
 */

#ifndef CS_V7_SRC_AST_H_
#define CS_V7_SRC_AST_H_

#include <stdio.h>
/* Amalgamated: #include "common/mbuf.h" */
/* Amalgamated: #include "v7/src/internal.h" */
/* Amalgamated: #include "v7/src/core.h" */

#if !defined(V7_NO_COMPILER)

#if defined(__cplusplus)
extern "C" {
#endif /* __cplusplus */

#define BIN_AST_SIGNATURE "V\007ASTV10"

enum ast_tag {
  AST_NOP,
  AST_SCRIPT,
  AST_VAR,
  AST_VAR_DECL,
  AST_FUNC_DECL,
  AST_IF,
  AST_FUNC,

  AST_ASSIGN,
  AST_REM_ASSIGN,
  AST_MUL_ASSIGN,
  AST_DIV_ASSIGN,
  AST_XOR_ASSIGN,
  AST_PLUS_ASSIGN,
  AST_MINUS_ASSIGN,
  AST_OR_ASSIGN,
  AST_AND_ASSIGN,
  AST_LSHIFT_ASSIGN,
  AST_RSHIFT_ASSIGN,
  AST_URSHIFT_ASSIGN,

  AST_NUM,
  AST_IDENT,
  AST_STRING,
  AST_REGEX,
  AST_LABEL,

  AST_SEQ,
  AST_WHILE,
  AST_DOWHILE,
  AST_FOR,
  AST_FOR_IN,
  AST_COND,

  AST_DEBUGGER,
  AST_BREAK,
  AST_LABELED_BREAK,
  AST_CONTINUE,
  AST_LABELED_CONTINUE,
  AST_RETURN,
  AST_VALUE_RETURN,
  AST_THROW,

  AST_TRY,
  AST_SWITCH,
  AST_CASE,
  AST_DEFAULT,
  AST_WITH,

  AST_LOGICAL_OR,
  AST_LOGICAL_AND,
  AST_OR,
  AST_XOR,
  AST_AND,

  AST_EQ,
  AST_EQ_EQ,
  AST_NE,
  AST_NE_NE,

  AST_LE,
  AST_LT,
  AST_GE,
  AST_GT,
  AST_IN,
  AST_INSTANCEOF,

  AST_LSHIFT,
  AST_RSHIFT,
  AST_URSHIFT,

  AST_ADD,
  AST_SUB,

  AST_REM,
  AST_MUL,
  AST_DIV,

  AST_POSITIVE,
  AST_NEGATIVE,
  AST_NOT,
  AST_LOGICAL_NOT,
  AST_VOID,
  AST_DELETE,
  AST_TYPEOF,
  AST_PREINC,
  AST_PREDEC,

  AST_POSTINC,
  AST_POSTDEC,

  AST_MEMBER,
  AST_INDEX,
  AST_CALL,

  AST_NEW,

  AST_ARRAY,
  AST_OBJECT,
  AST_PROP,
  AST_GETTER,
  AST_SETTER,

  AST_THIS,
  AST_TRUE,
  AST_FALSE,
  AST_NULL,
  AST_UNDEFINED,

  AST_USE_STRICT,

  AST_MAX_TAG
};

struct ast {
  struct mbuf mbuf;
  int refcnt;
  int has_overflow;
};

typedef unsigned long ast_off_t;

#if __GNUC__ >= 4 && __GNUC_MINOR__ >= 8
#define GCC_HAS_PRAGMA_DIAGNOSTIC
#endif

#ifdef GCC_HAS_PRAGMA_DIAGNOSTIC
/*
 * TODO(mkm): GCC complains that bitfields on char are not standard
 */
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wpedantic"
#endif
struct ast_node_def {
#ifndef V7_DISABLE_AST_TAG_NAMES
  const char *name; /* tag name, for debugging and serialization */
#endif
  unsigned char has_varint : 1;   /* has a varint body */
  unsigned char has_inlined : 1;  /* inlined data whose size is in varint fld */
  unsigned char num_skips : 3;    /* number of skips */
  unsigned char num_subtrees : 3; /* number of fixed subtrees */
};
extern const struct ast_node_def ast_node_defs[];
#if V7_ENABLE_FOOTPRINT_REPORT
extern const size_t ast_node_defs_size;
extern const size_t ast_node_defs_count;
#endif
#ifdef GCC_HAS_PRAGMA_DIAGNOSTIC
#pragma GCC diagnostic pop
#endif

enum ast_which_skip {
  AST_END_SKIP = 0,
  AST_VAR_NEXT_SKIP = 1,
  AST_SCRIPT_FIRST_VAR_SKIP = AST_VAR_NEXT_SKIP,
  AST_FOR_BODY_SKIP = 1,
  AST_DO_WHILE_COND_SKIP = 1,
  AST_END_IF_TRUE_SKIP = 1,
  AST_TRY_CATCH_SKIP = 1,
  AST_TRY_FINALLY_SKIP = 2,
  AST_FUNC_FIRST_VAR_SKIP = AST_VAR_NEXT_SKIP,
  AST_FUNC_BODY_SKIP = 2,
  AST_SWITCH_DEFAULT_SKIP = 1
};

V7_PRIVATE void ast_init(struct ast *, size_t);
V7_PRIVATE void ast_optimize(struct ast *);
V7_PRIVATE void ast_free(struct ast *);

/*
 * Begins an AST node by inserting a tag to the AST at the given offset.
 *
 * It also allocates space for the fixed_size payload and the space for
 * the skips.
 *
 * The caller is responsible for appending children.
 *
 * Returns the offset of the node payload (one byte after the tag).
 * This offset can be passed to `ast_set_skip`.
 */
V7_PRIVATE ast_off_t
ast_insert_node(struct ast *a, ast_off_t pos, enum ast_tag tag);

/*
 * Modify tag which is already added to buffer. Keeps `AST_TAG_LINENO_PRESENT`
 * flag.
 */
V7_PRIVATE void ast_modify_tag(struct ast *a, ast_off_t tag_off,
                               enum ast_tag tag);

#ifndef V7_DISABLE_LINE_NUMBERS
/*
 * Add line_no varint after all skips of the tag at the offset `tag_off`, and
 * marks the tag byte.
 *
 * Byte at the offset `tag_off` should be a valid tag.
 */
V7_PRIVATE void ast_add_line_no(struct ast *a, ast_off_t tag_off, int line_no);
#endif

/*
 * Patches a given skip slot for an already emitted node with the
 * current write cursor position (e.g. AST length).
 *
 * This is intended to be invoked when a node with a variable number
 * of child subtrees is closed, or when the consumers need a shortcut
 * to the next sibling.
 *
 * Each node type has a different number and semantic for skips,
 * all of them defined in the `ast_which_skip` enum.
 * All nodes having a variable number of child subtrees must define
 * at least the `AST_END_SKIP` skip, which effectively skips a node
 * boundary.
 *
 * Every tree reader can assume this and safely skip unknown nodes.
 *
 * `pos` should be an offset of the byte right after a tag.
 */
V7_PRIVATE ast_off_t
ast_set_skip(struct ast *a, ast_off_t pos, enum ast_which_skip skip);

/*
 * Patches a given skip slot for an already emitted node with the value
 * (stored as delta relative to the `pos` node) of the `where` argument.
 */
V7_PRIVATE ast_off_t ast_modify_skip(struct ast *a, ast_off_t pos,
                                     ast_off_t where, enum ast_which_skip skip);

/*
 * Returns the offset in AST to which the given `skip` points.
 *
 * `pos` should be an offset of the byte right after a tag.
 */
V7_PRIVATE ast_off_t
ast_get_skip(struct ast *a, ast_off_t pos, enum ast_which_skip skip);

/*
 * Returns the tag from the offset `ppos`, and shifts `ppos` by 1.
 */
V7_PRIVATE enum ast_tag ast_fetch_tag(struct ast *a, ast_off_t *ppos);

/*
 * Moves the cursor to the tag's varint and inlined data (if there are any, see
 * `struct ast_node_def::has_varint` and `struct ast_node_def::has_inlined`).
 *
 * Technically, it skips node's "skips" and line number data, if either is
 * present.
 *
 * Assumes a cursor (`ppos`) positioned right after a tag.
 */
V7_PRIVATE void ast_move_to_inlined_data(struct ast *a, ast_off_t *ppos);

/*
 * Moves the cursor to the tag's subtrees (if there are any,
 * see `struct ast_node_def::num_subtrees`), or to the next node in case the
 * current node has no subtrees.
 *
 * Technically, it skips node's "skips", line number data, and inlined data, if
 * either is present.
 *
 * Assumes a cursor (`ppos`) positioned right after a tag.
 */
V7_PRIVATE void ast_move_to_children(struct ast *a, ast_off_t *ppos);

/* Helper to add a node with inlined data. */
V7_PRIVATE ast_off_t ast_insert_inlined_node(struct ast *a, ast_off_t pos,
                                             enum ast_tag tag, const char *name,
                                             size_t len);

/*
 * Returns the line number encoded in the node, or `0` in case of none is
 * encoded.
 *
 * `pos` should be an offset of the byte right after a tag.
 */
V7_PRIVATE int ast_get_line_no(struct ast *a, ast_off_t pos);

/*
 * `pos` should be an offset of the byte right after a tag
 */
V7_PRIVATE char *ast_get_inlined_data(struct ast *a, ast_off_t pos, size_t *n);

/*
 * Returns the `double` number inlined in the node
 */
V7_PRIVATE double ast_get_num(struct ast *a, ast_off_t pos);

/*
 * Skips the node and all its subnodes.
 *
 * Cursor (`ppos`) should be at the tag byte
 */
V7_PRIVATE void ast_skip_tree(struct ast *a, ast_off_t *ppos);

V7_PRIVATE void ast_dump_tree(FILE *fp, struct ast *a, ast_off_t *ppos,
                              int depth);

V7_PRIVATE void release_ast(struct v7 *v7, struct ast *a);

#if defined(__cplusplus)
}
#endif /* __cplusplus */

#endif /* V7_NO_COMPILER */

#endif /* CS_V7_SRC_AST_H_ */
#ifdef V7_MODULE_LINES
#line 1 "v7/src/bcode.h"
#endif
/*
 * Copyright (c) 2014 Cesanta Software Limited
 * All rights reserved
 */

#ifndef CS_V7_SRC_BCODE_H_
#define CS_V7_SRC_BCODE_H_

#define BIN_BCODE_SIGNATURE "V\007BCODE:"

#if !defined(V7_NAMES_CNT_WIDTH)
#define V7_NAMES_CNT_WIDTH 10
#endif

#if !defined(V7_ARGS_CNT_WIDTH)
#define V7_ARGS_CNT_WIDTH 8
#endif

#define V7_NAMES_CNT_MAX ((1 << V7_NAMES_CNT_WIDTH) - 1)
#define V7_ARGS_CNT_MAX ((1 << V7_ARGS_CNT_WIDTH) - 1)

/* Amalgamated: #include "v7/src/internal.h" */
/* Amalgamated: #include "v7/src/core.h" */
/* Amalgamated: #include "v7/src/opcodes.h" */
/* Amalgamated: #include "v7/src/string.h" */
/* Amalgamated: #include "v7/src/object.h" */
/* Amalgamated: #include "v7/src/primitive.h" */
/* Amalgamated: #include "common/mbuf.h" */

enum bcode_inline_lit_type_tag {
  BCODE_INLINE_STRING_TYPE_TAG = 0,
  BCODE_INLINE_NUMBER_TYPE_TAG,
  BCODE_INLINE_FUNC_TYPE_TAG,
  BCODE_INLINE_REGEXP_TYPE_TAG,

  BCODE_MAX_INLINE_TYPE_TAG
};

#if defined(__cplusplus)
extern "C" {
#endif /* __cplusplus */

typedef uint32_t bcode_off_t;

/*
 * Each JS function will have one bcode structure
 * containing the instruction stream, a literal table, and function
 * metadata.
 * Instructions contain references to literals (strings, constants, etc)
 *
 * The bcode struct can be shared between function instances
 * and keeps a reference count used to free it in the function destructor.
 */
struct bcode {
  /*
   * Names + instruction opcode.
   * Names are null-terminates strings. For function's bcode, there are:
   *  - function name (for anonymous function, the name is still present: an
   *    empty string);
   *  - arg names (a number of args is determined by `args_cnt`, see below);
   *  - local names (a number or locals can be calculated:
   *    `(names_cnt - args_cnt - 1)`).
   *
   * For script's bcode, there are just local names.
   */
  struct v7_vec ops;

  /* Literal table */
  struct v7_vec lit;

#ifndef V7_DISABLE_FILENAMES
  /* Name of the file from which this bcode was generated (used for debug) */
  void *filename;
#endif

  /* Reference count */
  uint8_t refcnt;

  /* Total number of null-terminated strings in the beginning of `ops` */
  unsigned int names_cnt : V7_NAMES_CNT_WIDTH;

  /* Number of args (should be <= `(names_cnt + 1)`) */
  unsigned int args_cnt : V7_ARGS_CNT_WIDTH;

  unsigned int strict_mode : 1;
  /*
   * If true this structure lives on read only memory, either
   * mmapped or constant data section.
   */
  unsigned int frozen : 1;

  /* If set, `ops.buf` points to ROM, so we shouldn't free it */
  unsigned int ops_in_rom : 1;
  /* Set for deserialized bcode. Used for metrics only */
  unsigned int deserialized : 1;

  /* Set when `ops` contains function name as the first `name` */
  unsigned int func_name_present : 1;

#ifndef V7_DISABLE_FILENAMES
  /* If set, `filename` points to ROM, so we shouldn't free it */
  unsigned int filename_in_rom : 1;
#endif
};

/*
 * Bcode builder context: it contains mutable mbufs for opcodes and literals,
 * whereas the bcode itself contains just vectors (`struct v7_vec`).
 */
struct bcode_builder {
  struct v7 *v7;
  struct bcode *bcode;

  struct mbuf ops; /* names + instruction opcode */
  struct mbuf lit; /* literal table */
};

V7_PRIVATE void bcode_builder_init(struct v7 *v7,
                                   struct bcode_builder *bbuilder,
                                   struct bcode *bcode);
V7_PRIVATE void bcode_builder_finalize(struct bcode_builder *bbuilder);

/*
 * Note: `filename` must be either:
 *
 * - `NULL`. In this case, `filename_in_rom` is ignored.
 * - A pointer to ROM (or to any other unmanaged memory). `filename_in_rom`
 *   must be set to 1.
 * - A pointer returned by `shdata_create()`, i.e. a pointer to shared data.
 *
 * If you need to copy filename from another bcode, just pass NULL initially,
 * and after bcode is initialized, call `bcode_copy_filename_from()`.
 *
 * To get later a proper null-terminated filename string from bcode, use
 * `bcode_get_filename()`.
 */
V7_PRIVATE void bcode_init(struct bcode *bcode, uint8_t strict_mode,
                           void *filename, uint8_t filename_in_rom);
V7_PRIVATE void bcode_free(struct v7 *v7, struct bcode *bcode);
V7_PRIVATE void release_bcode(struct v7 *v7, struct bcode *bcode);
V7_PRIVATE void retain_bcode(struct v7 *v7, struct bcode *bcode);

#ifndef V7_DISABLE_FILENAMES
/*
 * Return a pointer to null-terminated filename string
 */
V7_PRIVATE const char *bcode_get_filename(struct bcode *bcode);
#endif

/*
 * Copy filename from `src` to `dst`. If source filename is a pointer to ROM,
 * then just the pointer is copied; otherwise, appropriate shdata pointer is
 * retained.
 */
V7_PRIVATE void bcode_copy_filename_from(struct bcode *dst, struct bcode *src);

/*
 * Serialize a bcode structure.
 *
 * All literals, including functions, are inlined into `ops` data; see
 * the serialization logic in `bcode_op_lit()`.
 *
 * The root bcode looks just like a regular function.
 *
 * This function is used only internally, but used in a complicated mix of
 * configurations, hence the commented V7_PRIVATE
 */
/*V7_PRIVATE*/ void bcode_serialize(struct v7 *v7, struct bcode *bcode,
                                    FILE *f);

V7_PRIVATE void bcode_deserialize(struct v7 *v7, struct bcode *bcode,
                                  const char *data);

#ifdef V7_BCODE_DUMP
V7_PRIVATE void dump_bcode(struct v7 *v7, FILE *, struct bcode *);
#endif

/* mode of literal storage: in literal table or inlined in `ops` */
enum lit_mode {
  /* literal stored in table, index is in `lit_t::lit_idx` */
  LIT_MODE__TABLE,
  /* literal should be inlined in `ops`, value is in `lit_t::inline_val` */
  LIT_MODE__INLINED,
};

/*
 * Result of the addition of literal value to bcode (see `bcode_add_lit()`).
 * There are two possible cases:
 *
 * - Literal is added to the literal table. In this case, `mode ==
 *   LIT_MODE__TABLE`, and the index of the literal is stored in `lit_idx`
 * - Literal is not added anywhere, and should be inlined into `ops`. In this
 *   case, `mode == LIT_MODE__INLINED`, and the value to inline is stored in
 *   `inline_val`.
 *
 * It's `bcode_op_lit()` who handles both of these cases.
 */
typedef struct {
  union {
    /*
     * index in literal table;
     * NOTE: valid if only `mode == LIT_MODE__TABLE`
     */
    size_t lit_idx;

    /*
     * value to be inlined into `ops`;
     * NOTE: valid if only `mode == LIT_MODE__INLINED`
     */
    v7_val_t inline_val;
  } v; /* anonymous unions are a c11 feature */

  /*
   * mode of literal storage (see `enum lit_mode`)
   * NOTE: we need one more bit, because enum can be signed
   * on some compilers (e.g. msvc) and thus will get signextended
   * when moved to a `enum lit_mode` variable basically corrupting
   * the value. See https://github.com/cesanta/v7/issues/551
   */
  enum lit_mode mode : 2;
} lit_t;

V7_PRIVATE void bcode_op(struct bcode_builder *bbuilder, uint8_t op);

#ifndef V7_DISABLE_LINE_NUMBERS
V7_PRIVATE void bcode_append_lineno(struct bcode_builder *bbuilder,
                                    int line_no);
#endif

/*
 * Add a literal to the bcode literal table, or just decide that the literal
 * should be inlined into `ops`. See `lit_t` for details.
 */
V7_PRIVATE
lit_t bcode_add_lit(struct bcode_builder *bbuilder, v7_val_t val);

/* disabled because of short lits */
#if 0
V7_PRIVATE v7_val_t bcode_get_lit(struct bcode *bcode, size_t idx);
#endif

/*
 * Emit an opcode `op`, and handle the literal `lit` (see `bcode_add_lit()`,
 * `lit_t`). Depending on the literal storage mode (see `enum lit_mode`), this
 * function either emits literal table index or inlines the literal directly
 * into `ops.`
 */
V7_PRIVATE void bcode_op_lit(struct bcode_builder *bbuilder, enum opcode op,
                             lit_t lit);

/* Helper function, equivalent of `bcode_op_lit(bbuilder, OP_PUSH_LIT, lit)` */
V7_PRIVATE void bcode_push_lit(struct bcode_builder *bbuilder, lit_t lit);

/*
 * Add name to bcode. If `idx` is null, a name is appended to the end of the
 * `bcode->ops.buf`. If `idx` is provided, it should point to the index at
 * which new name should be inserted; and it is updated by the
 * `bcode_add_name()` to point right after newly added name.
 *
 * This function is used only internally, but used in a complicated mix of
 * configurations, hence the commented V7_PRIVATE
 */
WARN_UNUSED_RESULT
    /*V7_PRIVATE*/ enum v7_err
    bcode_add_name(struct bcode_builder *bbuilder, const char *p, size_t len,
                   size_t *idx);

/*
 * Takes a pointer to the beginning of `ops` buffer and names count, returns
 * a pointer where actual opcodes begin (i.e. skips names).
 *
 * It takes two distinct arguments instead of just `struct bcode` pointer,
 * because during bcode building `ops` is stored in builder.
 *
 * This function is used only internally, but used in a complicated mix of
 * configurations, hence the commented V7_PRIVATE
 */
/*V7_PRIVATE*/ char *bcode_end_names(char *ops, size_t names_cnt);

/*
 * Given a pointer to `ops` (which should be `bcode->ops` or a pointer returned
 * from previous invocation of `bcode_next_name()`), yields a name string via
 * arguments `pname`, `plen`.
 *
 * Returns a pointer that should be given to `bcode_next_name()` to get a next
 * string (Whether there is a next string should be determined via the
 * `names_cnt`; since if there are no more names, this function will return an
 * invalid non-null pointer as next name pointer)
 */
V7_PRIVATE char *bcode_next_name(char *ops, char **pname, size_t *plen);

/*
 * Like `bcode_next_name()`, but instead of yielding a C string, it yields a
 * `val_t` value (via `res`).
 */
V7_PRIVATE char *bcode_next_name_v(struct v7 *v7, struct bcode *bcode,
                                   char *ops, val_t *res);

V7_PRIVATE bcode_off_t bcode_pos(struct bcode_builder *bbuilder);

V7_PRIVATE bcode_off_t bcode_add_target(struct bcode_builder *bbuilder);
/*
 * This function is used only internally, but used in a complicated mix of
 * configurations, hence the commented V7_PRIVATE
 */
/*V7_PRIVATE*/ bcode_off_t bcode_op_target(struct bcode_builder *bbuilder,
                                           uint8_t op);
/*V7_PRIVATE*/ void bcode_patch_target(struct bcode_builder *bbuilder,
                                       bcode_off_t label, bcode_off_t target);

V7_PRIVATE void bcode_add_varint(struct bcode_builder *bbuilder, size_t value);
/*
 * Reads varint-encoded integer from the provided pointer, and adjusts
 * the pointer appropriately
 */
V7_PRIVATE size_t bcode_get_varint(char **ops);

/*
 * Decode a literal value from a string of opcodes and update the cursor to
 * point past it
 */
V7_PRIVATE
v7_val_t bcode_decode_lit(struct v7 *v7, struct bcode *bcode, char **ops);

#if defined(V7_BCODE_DUMP) || defined(V7_BCODE_TRACE)
V7_PRIVATE void dump_op(struct v7 *v7, FILE *f, struct bcode *bcode,
                        char **ops);
#endif

#if defined(__cplusplus)
}
#endif /* __cplusplus */

#endif /* CS_V7_SRC_BCODE_H_ */
#ifdef V7_MODULE_LINES
#line 1 "v7/src/gc_public.h"
#endif
/*
 * Copyright (c) 2014 Cesanta Software Limited
 * All rights reserved
 */

/*
 * === Garbage Collector
 */

#ifndef CS_V7_SRC_GC_PUBLIC_H_
#define CS_V7_SRC_GC_PUBLIC_H_

/* Amalgamated: #include "v7/src/core_public.h" */

#if defined(__cplusplus)
extern "C" {
#endif /* __cplusplus */

#if V7_ENABLE__Memory__stats

/* Heap metric id, see `v7_heap_stat()` */
enum v7_heap_stat_what {
  V7_HEAP_STAT_HEAP_SIZE,
  V7_HEAP_STAT_HEAP_USED,
  V7_HEAP_STAT_STRING_HEAP_RESERVED,
  V7_HEAP_STAT_STRING_HEAP_USED,
  V7_HEAP_STAT_OBJ_HEAP_MAX,
  V7_HEAP_STAT_OBJ_HEAP_FREE,
  V7_HEAP_STAT_OBJ_HEAP_CELL_SIZE,
  V7_HEAP_STAT_FUNC_HEAP_MAX,
  V7_HEAP_STAT_FUNC_HEAP_FREE,
  V7_HEAP_STAT_FUNC_HEAP_CELL_SIZE,
  V7_HEAP_STAT_PROP_HEAP_MAX,
  V7_HEAP_STAT_PROP_HEAP_FREE,
  V7_HEAP_STAT_PROP_HEAP_CELL_SIZE,
  V7_HEAP_STAT_FUNC_AST_SIZE,
  V7_HEAP_STAT_BCODE_OPS_SIZE,
  V7_HEAP_STAT_BCODE_LIT_TOTAL_SIZE,
  V7_HEAP_STAT_BCODE_LIT_DESER_SIZE,
  V7_HEAP_STAT_FUNC_OWNED,
  V7_HEAP_STAT_FUNC_OWNED_MAX
};

/* Returns a given heap statistics */
int v7_heap_stat(struct v7 *v7, enum v7_heap_stat_what what);
#endif

/*
 * Perform garbage collection.
 * Pass true to full in order to reclaim unused heap back to the OS.
 */
void v7_gc(struct v7 *v7, int full);

#if defined(__cplusplus)
}
#endif /* __cplusplus */

#endif /* CS_V7_SRC_GC_PUBLIC_H_ */
#ifdef V7_MODULE_LINES
#line 1 "v7/src/gc.h"
#endif
/*
 * Copyright (c) 2014 Cesanta Software Limited
 * All rights reserved
 */

#ifndef CS_V7_SRC_GC_H_
#define CS_V7_SRC_GC_H_

/* Amalgamated: #include "v7/src/gc_public.h" */

/* Amalgamated: #include "v7/src/internal.h" */
/* Amalgamated: #include "v7/src/core.h" */

/*
 * Macros for marking reachable things: use bit 0.
 */
#define MARK(p) (((struct gc_cell *) (p))->head.word |= 1)
#define UNMARK(p) (((struct gc_cell *) (p))->head.word &= ~1)
#define MARKED(p) (((struct gc_cell *) (p))->head.word & 1)

/*
 * Similar to `MARK()` / `UNMARK()` / `MARKED()`, but `.._FREE` counterparts
 * are intended to mark free cells (as opposed to used ones), so they use
 * bit 1.
 */
#define MARK_FREE(p) (((struct gc_cell *) (p))->head.word |= 2)
#define UNMARK_FREE(p) (((struct gc_cell *) (p))->head.word &= ~2)
#define MARKED_FREE(p) (((struct gc_cell *) (p))->head.word & 2)

/*
 * performs arithmetics on gc_cell pointers as if they were arena->cell_size
 * bytes wide
 */
#define GC_CELL_OP(arena, cell, op, arg) \
  ((struct gc_cell *) (((char *) (cell)) op((arg) * (arena)->cell_size)))

struct gc_tmp_frame {
  struct v7 *v7;
  size_t pos;
};

struct gc_cell {
  union {
    struct gc_cell *link;
    uintptr_t word;
  } head;
};

#if defined(__cplusplus)
extern "C" {
#endif /* __cplusplus */

V7_PRIVATE struct v7_generic_object *new_generic_object(struct v7 *);
V7_PRIVATE struct v7_property *new_property(struct v7 *);
V7_PRIVATE struct v7_js_function *new_function(struct v7 *);

V7_PRIVATE void gc_mark(struct v7 *, val_t);

V7_PRIVATE void gc_arena_init(struct gc_arena *, size_t, size_t, size_t,
                              const char *);
V7_PRIVATE void gc_arena_destroy(struct v7 *, struct gc_arena *a);
V7_PRIVATE void gc_sweep(struct v7 *, struct gc_arena *, size_t);
V7_PRIVATE void *gc_alloc_cell(struct v7 *, struct gc_arena *);

V7_PRIVATE struct gc_tmp_frame new_tmp_frame(struct v7 *);
V7_PRIVATE void tmp_frame_cleanup(struct gc_tmp_frame *);
V7_PRIVATE void tmp_stack_push(struct gc_tmp_frame *, val_t *);

V7_PRIVATE void compute_need_gc(struct v7 *);
/* perform gc if not inhibited */
V7_PRIVATE int maybe_gc(struct v7 *);

#ifndef V7_DISABLE_STR_ALLOC_SEQ
V7_PRIVATE uint16_t
gc_next_allocation_seqn(struct v7 *v7, const char *str, size_t len);
V7_PRIVATE int gc_is_valid_allocation_seqn(struct v7 *v7, uint16_t n);
V7_PRIVATE void gc_check_valid_allocation_seqn(struct v7 *v7, uint16_t n);
#endif

V7_PRIVATE uint64_t gc_string_val_to_offset(val_t v);

/* return 0 if v is an object/function with a bad pointer */
V7_PRIVATE int gc_check_val(struct v7 *v7, val_t v);

/* checks whether a pointer is within the ranges of an arena */
V7_PRIVATE int gc_check_ptr(const struct gc_arena *a, const void *p);

#if V7_ENABLE__Memory__stats
V7_PRIVATE size_t gc_arena_size(struct gc_arena *);
#endif

#if defined(__cplusplus)
}
#endif /* __cplusplus */

#endif /* CS_V7_SRC_GC_H_ */
#ifdef V7_MODULE_LINES
#line 1 "v7/src/regexp_public.h"
#endif
/*
 * Copyright (c) 2014 Cesanta Software Limited
 * All rights reserved
 */

/*
 * === RegExp
 */

#ifndef CS_V7_SRC_REGEXP_PUBLIC_H_
#define CS_V7_SRC_REGEXP_PUBLIC_H_

/* Amalgamated: #include "v7/src/core_public.h" */

#if defined(__cplusplus)
extern "C" {
#endif /* __cplusplus */

/*
 * Make RegExp object.
 * `regex`, `regex_len` specify a pattern, `flags` and `flags_len` specify
 * flags. Both utf8 encoded. For example, `regex` is `(.+)`, `flags` is `gi`.
 * If `regex_len` is ~0, `regex` is assumed to be NUL-terminated and
 * `strlen(regex)` is used.
 */
WARN_UNUSED_RESULT
enum v7_err v7_mk_regexp(struct v7 *v7, const char *regex, size_t regex_len,
                         const char *flags, size_t flags_len, v7_val_t *res);

/* Returns true if given value is a JavaScript RegExp object*/
int v7_is_regexp(struct v7 *v7, v7_val_t v);

#if defined(__cplusplus)
}
#endif /* __cplusplus */

#endif /* CS_V7_SRC_REGEXP_PUBLIC_H_ */
#ifdef V7_MODULE_LINES
#line 1 "v7/src/regexp.h"
#endif
/*
 * Copyright (c) 2014 Cesanta Software Limited
 * All rights reserved
 */

#ifndef CS_V7_SRC_REGEXP_H_
#define CS_V7_SRC_REGEXP_H_

/* Amalgamated: #include "v7/src/regexp_public.h" */

/* Amalgamated: #include "v7/src/core.h" */

#if V7_ENABLE__RegExp

/*
 * Maximum number of flags returned by get_regexp_flags_str().
 * NOTE: does not include null-terminate byte.
 */
#define _V7_REGEXP_MAX_FLAGS_LEN 3

struct v7_regexp;

V7_PRIVATE struct v7_regexp *v7_get_regexp_struct(struct v7 *, v7_val_t);

/*
 * Generates a string containing regexp flags, e.g. "gi".
 *
 * `buf` should point to a buffer of minimum `_V7_REGEXP_MAX_FLAGS_LEN` bytes.
 * Returns length of the resulted string (saved into `buf`)
 */
V7_PRIVATE size_t
get_regexp_flags_str(struct v7 *v7, struct v7_regexp *rp, char *buf);
#endif /* V7_ENABLE__RegExp */

#endif /* CS_V7_SRC_REGEXP_H_ */
#ifdef V7_MODULE_LINES
#line 1 "v7/src/function_public.h"
#endif
/*
 * Copyright (c) 2014 Cesanta Software Limited
 * All rights reserved
 */

/*
 * === Functions
 */

#ifndef CS_V7_SRC_FUNCTION_PUBLIC_H_
#define CS_V7_SRC_FUNCTION_PUBLIC_H_

/* Amalgamated: #include "v7/src/core_public.h" */

#if defined(__cplusplus)
extern "C" {
#endif /* __cplusplus */

/*
 * Make a JS function object backed by a cfunction.
 *
 * `func` is a C callback.
 *
 * A function object is JS object having the Function prototype that holds a
 * cfunction value in a hidden property.
 *
 * The function object will have a `prototype` property holding an object that
 * will be used as the prototype of objects created when calling the function
 * with the `new` operator.
 */
v7_val_t v7_mk_function(struct v7 *, v7_cfunction_t *func);

/*
 * Make f a JS function with specified prototype `proto`, so that the resulting
 * function is better suited for the usage as a constructor.
 */
v7_val_t v7_mk_function_with_proto(struct v7 *v7, v7_cfunction_t *f,
                                   v7_val_t proto);

/*
 * Make a JS value that holds C/C++ callback pointer.
 *
 * CAUTION: This is a low-level function value. It's not a real object and
 * cannot hold user defined properties. You should use `v7_mk_function` unless
 * you know what you're doing.
 */
v7_val_t v7_mk_cfunction(v7_cfunction_t *func);

/*
 * Returns true if given value is callable (i.e. it's either a JS function or
 * cfunction)
 */
int v7_is_callable(struct v7 *v7, v7_val_t v);

#if defined(__cplusplus)
}
#endif /* __cplusplus */

#endif /* CS_V7_SRC_FUNCTION_PUBLIC_H_ */
#ifdef V7_MODULE_LINES
#line 1 "v7/src/function.h"
#endif
/*
 * Copyright (c) 2014 Cesanta Software Limited
 * All rights reserved
 */

#ifndef CS_V7_SRC_FUNCTION_H_
#define CS_V7_SRC_FUNCTION_H_

/* Amalgamated: #include "v7/src/function_public.h" */

/* Amalgamated: #include "v7/src/internal.h" */
/* Amalgamated: #include "v7/src/core.h" */

#if defined(__cplusplus)
extern "C" {
#endif /* __cplusplus */

V7_PRIVATE struct v7_js_function *get_js_function_struct(val_t v);
V7_PRIVATE val_t
mk_js_function(struct v7 *v7, struct v7_generic_object *scope, val_t proto);
V7_PRIVATE int is_js_function(val_t v);
V7_PRIVATE v7_val_t mk_cfunction_lite(v7_cfunction_t *f);

/* Returns true if given value holds a pointer to C callback */
V7_PRIVATE int is_cfunction_lite(v7_val_t v);

/* Returns true if given value holds an object which represents C callback */
V7_PRIVATE int is_cfunction_obj(struct v7 *v7, v7_val_t v);

/*
 * Returns `v7_cfunction_t *` callback pointer stored in `v7_val_t`, or NULL
 * if given value is neither cfunction pointer nor cfunction object.
 */
V7_PRIVATE v7_cfunction_t *get_cfunction_ptr(struct v7 *v7, v7_val_t v);

/*
 * Like v7_mk_function but also sets the function's `length` property.
 *
 * The `length` property is useful for introspection and the stdlib defines it
 * for many core functions mostly because the ECMA test suite requires it and we
 * don't want to skip otherwise useful tests just because the `length` property
 * check fails early in the test. User defined functions don't need to specify
 * the length and passing -1 is a safe choice, as it will also reduce the
 * footprint.
 *
 * The subtle difference between set `length` explicitly to 0 rather than
 * just defaulting the `0` value from the prototype is that in the former case
 * the property cannot be change since it's read only. This again, is important
 * only for ecma compliance and your user code might or might not find this
 * relevant.
 *
 * NODO(lsm): please don't combine v7_mk_function_arg and v7_mk_function
 * into one function. Currently `num_args` is useful only internally. External
 * users can just use `v7_def` to set the length.
 */
V7_PRIVATE
v7_val_t mk_cfunction_obj(struct v7 *v7, v7_cfunction_t *func, int num_args);

/*
 * Like v7_mk_function_with_proto but also sets the function's `length`
 *property.
 *
 * NODO(lsm): please don't combine mk_cfunction_obj_with_proto and
 * v7_mk_function_with_proto.
 * into one function. Currently `num_args` is useful only internally. External
 * users can just use `v7_def` to set the length.
 */
V7_PRIVATE
v7_val_t mk_cfunction_obj_with_proto(struct v7 *v7, v7_cfunction_t *f,
                                     int num_args, v7_val_t proto);

#if defined(__cplusplus)
}
#endif /* __cplusplus */

#endif /* CS_V7_SRC_FUNCTION_H_ */
#ifdef V7_MODULE_LINES
#line 1 "v7/src/util_public.h"
#endif
/*
 * Copyright (c) 2014 Cesanta Software Limited
 * All rights reserved
 */

/*
 * === Utility functions
 */

#ifndef CS_V7_SRC_UTIL_PUBLIC_H_
#define CS_V7_SRC_UTIL_PUBLIC_H_

/* Amalgamated: #include "v7/src/core_public.h" */

#if defined(__cplusplus)
extern "C" {
#endif /* __cplusplus */

/* Output a string representation of the value to stdout.
 * V7_STRINGIFY_DEBUG mode is used. */
void v7_print(struct v7 *v7, v7_val_t v);

/* Output a string representation of the value to stdout followed by a newline.
 * V7_STRINGIFY_DEBUG mode is used. */
void v7_println(struct v7 *v7, v7_val_t v);

/* Output a string representation of the value to a file.
 * V7_STRINGIFY_DEBUG mode is used. */
void v7_fprint(FILE *f, struct v7 *v7, v7_val_t v);

/* Output a string representation of the value to a file followed by a newline.
 * V7_STRINGIFY_DEBUG mode is used. */
void v7_fprintln(FILE *f, struct v7 *v7, v7_val_t v);

/* Output stack trace recorded in the exception `e` to file `f` */
void v7_fprint_stack_trace(FILE *f, struct v7 *v7, v7_val_t e);

/* Output error object message and possibly stack trace to f */
void v7_print_error(FILE *f, struct v7 *v7, const char *ctx, v7_val_t e);

#if V7_ENABLE__Proxy

struct v7_property;

/*
 * C callback, analogue of JS callback `getOwnPropertyDescriptor()`.
 * Callbacks of this type are used for C API only, see `m7_mk_proxy()`.
 *
 * `name` is the name of the property, and the function should fill `attrs` and
 * `value` with the property data. Before this callback is called, `attrs` is
 * set to 0, and `value` is `V7_UNDEFINED`.
 *
 * It should return non-zero if the property should be considered existing, or
 * zero otherwise.
 *
 * You can inspect the property attributes with the `V7_PROP_ATTR_IS_*` macros.
 */
typedef int(v7_get_own_prop_desc_cb_t)(struct v7 *v7, v7_val_t target,
                                       v7_val_t name, v7_prop_attr_t *attrs,
                                       v7_val_t *value);

/* Handler for `v7_mk_proxy()`; each item is a cfunction */
typedef struct {
  v7_cfunction_t *get;
  v7_cfunction_t *set;
  v7_cfunction_t *own_keys;
  v7_get_own_prop_desc_cb_t *get_own_prop_desc;
} v7_proxy_hnd_t;

/*
 * Create a Proxy object, see:
 * https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Proxy
 *
 * Only two traps are implemented so far: `get()` and `set()`. Note that
 * `Object.defineProperty()` bypasses the `set()` trap.
 *
 * If `target` is not an object, the empty object will be used, so it's safe
 * to pass `V7_UNDEFINED` as `target`.
 */
v7_val_t v7_mk_proxy(struct v7 *v7, v7_val_t target,
                     const v7_proxy_hnd_t *handler);

#endif /* V7_ENABLE__Proxy */

#if defined(__cplusplus)
}
#endif /* __cplusplus */

#endif /* CS_V7_SRC_UTIL_PUBLIC_H_ */
#ifdef V7_MODULE_LINES
#line 1 "v7/src/util.h"
#endif
/*
 * Copyright (c) 2014 Cesanta Software Limited
 * All rights reserved
 */

#ifndef CS_V7_SRC_UTIL_H_
#define CS_V7_SRC_UTIL_H_

/* Amalgamated: #include "v7/src/core.h" */
/* Amalgamated: #include "v7/src/util_public.h" */

struct bcode;

V7_PRIVATE enum v7_type val_type(struct v7 *v7, val_t v);

#ifndef V7_DISABLE_LINE_NUMBERS
V7_PRIVATE uint8_t msb_lsb_swap(uint8_t b);
#endif

/*
 * At the moment, all other utility functions are public, and are declared in
 * `util_public.h`
 */

#endif /* CS_V7_SRC_UTIL_H_ */
#ifdef V7_MODULE_LINES
#line 1 "v7/src/shdata.h"
#endif
/*
 * Copyright (c) 2014 Cesanta Software Limited
 * All rights reserved
 */

/*
 * shdata (stands for "shared data") is a simple module that allows to have
 * reference count for an arbitrary payload data, which will be freed as
 * necessary. A poor man's shared_ptr.
 */

#ifndef CS_V7_SRC_SHDATA_H_
#define CS_V7_SRC_SHDATA_H_

/* Amalgamated: #include "v7/src/internal.h" */

#if !defined(V7_DISABLE_FILENAMES) && !defined(V7_DISABLE_LINE_NUMBERS)
struct shdata {
  /* Reference count */
  uint8_t refcnt;

  /*
   * Note: we'd use `unsigned char payload[];` here, but we can't, since this
   * feature was introduced in C99 only
   */
};

/*
 * Allocate memory chunk of appropriate size, copy given `payload` data there,
 * retain (`shdata_retain()`), and return it.
 */
V7_PRIVATE struct shdata *shdata_create(const void *payload, size_t size);

V7_PRIVATE struct shdata *shdata_create_from_string(const char *src);

/*
 * Increment reference count for the given shared data
 */
V7_PRIVATE void shdata_retain(struct shdata *p);

/*
 * Decrement reference count for the given shared data
 */
V7_PRIVATE void shdata_release(struct shdata *p);

/*
 * Get payload data
 */
V7_PRIVATE void *shdata_get_payload(struct shdata *p);

#endif
#endif /* CS_V7_SRC_SHDATA_H_ */
#ifdef V7_MODULE_LINES
#line 1 "v7/src/eval.h"
#endif
/*
 * Copyright (c) 2014 Cesanta Software Limited
 * All rights reserved
 */

#ifndef CS_V7_SRC_EVAL_H_
#define CS_V7_SRC_EVAL_H_

/* Amalgamated: #include "v7/src/internal.h" */
/* Amalgamated: #include "v7/src/bcode.h" */

struct v7_call_frame_base;

#if defined(__cplusplus)
extern "C" {
#endif /* __cplusplus */

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err eval_bcode(struct v7 *v7, struct bcode *bcode,
                                  val_t this_object, uint8_t reset_line_no,
                                  val_t *_res);

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err b_apply(struct v7 *v7, v7_val_t func, v7_val_t this_obj,
                               v7_val_t args, uint8_t is_constructor,
                               v7_val_t *res);

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err b_exec(struct v7 *v7, const char *src, size_t src_len,
                              const char *filename, val_t func, val_t args,
                              val_t this_object, int is_json, int fr,
                              uint8_t is_constructor, val_t *res);

/*
 * Try to find the call frame whose `type_mask` intersects with the given
 * `type_mask`.
 *
 * Start from the top call frame, and go deeper until the matching frame is
 * found, or there's no more call frames. If the needed frame was not found,
 * returns `NULL`.
 */
V7_PRIVATE struct v7_call_frame_base *find_call_frame(struct v7 *v7,
                                                      uint8_t type_mask);

#if defined(__cplusplus)
}
#endif /* __cplusplus */

#endif /* CS_V7_SRC_EVAL_H_ */
#ifdef V7_MODULE_LINES
#line 1 "v7/src/compiler.h"
#endif
/*
 * Copyright (c) 2014 Cesanta Software Limited
 * All rights reserved
 */

#ifndef CS_V7_SRC_COMPILER_H_
#define CS_V7_SRC_COMPILER_H_

/* Amalgamated: #include "v7/src/internal.h" */
/* Amalgamated: #include "v7/src/bcode.h" */
/* Amalgamated: #include "v7/src/ast.h" */

#if !defined(V7_NO_COMPILER)

#if defined(__cplusplus)
extern "C" {
#endif /* __cplusplus */

V7_PRIVATE enum v7_err compile_script(struct v7 *v7, struct ast *a,
                                      struct bcode *bcode);

V7_PRIVATE enum v7_err compile_expr(struct v7 *v7, struct ast *a,
                                    ast_off_t *ppos, struct bcode *bcode);

#if defined(__cplusplus)
}
#endif /* __cplusplus */

#endif /* V7_NO_COMPILER */

#endif /* CS_V7_SRC_COMPILER_H_ */
#ifdef V7_MODULE_LINES
#line 1 "v7/src/cyg_profile.h"
#endif
/*
 * Copyright (c) 2014 Cesanta Software Limited
 * All rights reserved
 */

#ifndef CS_V7_SRC_CYG_PROFILE_H_
#define CS_V7_SRC_CYG_PROFILE_H_

/*
 * This file contains GCC/clang instrumentation callbacks, as well as
 * accompanying code. The actual code in these callbacks depends on enabled
 * features. See cyg_profile.c for some implementation details rationale.
 */

struct v7;

#if defined(V7_ENABLE_STACK_TRACKING)

/*
 * Stack-tracking functionality:
 *
 * The idea is that the caller should allocate `struct stack_track_ctx`
 * (typically on stack) in the function to track the stack usage of, and call
 * `v7_stack_track_start()` in the beginning.
 *
 * Before quitting current stack frame (for example, before returning from
 * function), call `v7_stack_track_end()`, which returns the maximum stack
 * consumed size.
 *
 * These calls can be nested: for example, we may track the stack usage of the
 * whole application by using these functions in `main()`, as well as track
 * stack usage of any nested functions.
 *
 * Just to stress: both `v7_stack_track_start()` / `v7_stack_track_end()`
 * should be called for the same instance of `struct stack_track_ctx` in the
 * same stack frame.
 */

/* stack tracking context */
struct stack_track_ctx {
  struct stack_track_ctx *next;
  void *start;
  void *max;
};

/* see explanation above */
void v7_stack_track_start(struct v7 *v7, struct stack_track_ctx *ctx);
/* see explanation above */
int v7_stack_track_end(struct v7 *v7, struct stack_track_ctx *ctx);

void v7_stack_stat_clean(struct v7 *v7);

#endif /* V7_ENABLE_STACK_TRACKING */

#endif /* CS_V7_SRC_CYG_PROFILE_H_ */
#ifdef V7_MODULE_LINES
#line 1 "v7/builtin/builtin.h"
#endif
/*
 * Copyright (c) 2015 Cesanta Software Limited
 * All rights reserved
 */

/*
 * === Non-Standard API
 *
 *   V7 has several non-standard extensions for `String.prototype` in
 *   order to give a compact and fast API to access raw data obtained from
 *   File, Socket, and hardware input/output such as I2C.
 *   V7 IO API functions return
 *   string data as a result of read operations, and that string data is a
 *   raw byte array. ECMA6 provides `ArrayBuffer` and `DataView` API for dealing
 *   with raw bytes, because strings in JavaScript are Unicode. That standard
 *   API is too bloated for the embedded use, and does not allow to use handy
 *   String API (e.g. `.match()`) against data.
 *
 *   V7 internally stores strings as byte arrays. All strings created by the
 *   String API are UTF8 encoded. Strings that are the result of
 *   input/output API calls might not be a valid UTF8 strings, but nevertheless
 *   they are represented as strings, and the following API allows to access
 *   underlying byte sequence:
 *
 * ==== String.prototype.at(position) -> number or NaN
 *      Return byte at index
 *     `position`. Byte value is in 0,255 range. If `position` is out of bounds
 *     (either negative or larger then the byte array length), NaN is returned.
 *     Example: `"ы".at(0)` returns 0xd1.
 *
 * ==== String.prototype.blen -> number
 *     Return string length in bytes.
 *     Example: `"ы".blen` returns 2. Note that `"ы".length` is 1, since that
 *     string consists of a single Unicode character (2-byte).
 *
 * === Builtin API
 *
 * Builtin API provides additional JavaScript interfaces available for V7
 * scripts.
 * File API is a wrapper around standard C calls `fopen()`, `fclose()`,
 * `fread()`, `fwrite()`, `rename()`, `remove()`.
 * Crypto API provides functions for base64, md5, and sha1 encoding/decoding.
 * Socket API provides low-level socket API.
 *
 * ==== File.eval(file_name)
 * Parse and run `file_name`.
 * Throws an exception if the file doesn't exist, cannot be parsed or if the
 * script throws any exception.
 *
 * ==== File.read(file_name) -> string or undefined
 * Read file `file_name` and return a string with a file content.
 * On any error, return `undefined` as a result.
 *
 * ==== File.write(file_name, str) -> true or false
 * Write string `str` to a file `file_name`. Return `true` on success,
 * `false` on error.
 *
 * ==== File.open(file_name [, mode]) -> file_object or null
 * Open a file `path`. For
 * list of valid `mode` values, see `fopen()` documentation. If `mode` is
 * not specified, mode `rb` is used, i.e. file is opened in read-only mode.
 * Return an opened file object, or null on error. Example:
 * `var f = File.open('/etc/passwd'); f.close();`
 *
 * ==== file_obj.close() -> undefined
 * Close opened file object.
 * NOTE: it is user's responsibility to close all opened file streams. V7
 * does not do that automatically.
 *
 * ==== file_obj.read() -> string
 * Read portion of data from
 * an opened file stream. Return string with data, or empty string on EOF
 * or error.
 *
 * ==== file_obj.write(str) -> num_bytes_written
 * Write string `str` to the opened file object. Return number of bytes written.
 *
 * ==== File.rename(old_name, new_name) -> errno
 * Rename file `old_name` to
 * `new_name`. Return 0 on success, or `errno` value on error.
 *
 * ==== File.list(dir_name) -> array_of_names
 * Return a list of files in a given directory, or `undefined` on error.
 *
 * ==== File.remove(file_name) -> errno
 * Delete file `file_name`.
 * Return 0 on success, or `errno` value on error.
 *
 * ==== Crypto.base64_encode(str)
 * Base64-encode input string `str` and return encoded string.
 *
 * ==== Crypto.base64_decode(str)
 * Base64-decode input string `str` and return decoded string.
 *
 * ==== Crypto.md5(str), Crypto.md5_hex(str)
 * Generate MD5 hash from input string `str`. Return 16-byte hash (`md5()`),
 * or stringified hexadecimal representation of the hash (`md5_hex`).
 *
 * ==== Crypto.sha1(str), Crypto.sha1_hex(str)
 * Generate SHA1 hash from input string `str`. Return 20-byte hash (`sha1()`),
 * or stringified hexadecimal representation of the hash (`sha1_hex`).
 *
 * ==== Socket.connect(host, port [, is_udp]) -> socket_obj
 * Connect to a given host. `host` can be a string IP address, or a host name.
 * Optional `is_udp` parameter, if true, indicates that socket should be UDP.
 * Return socket object on success, null on error.
 *
 * ==== Socket.listen(port [, ip_address [,is_udp]]) -> socket_obj
 * Create a listening socket on a given port. Optional `ip_address` argument
 * specifies and IP address to bind to. Optional `is_udp` parameter, if true,
 * indicates that socket should be UDP. Return socket object on success,
 * null on error.
 *
 * ==== socket_obj.accept() -> socket_obj
 * Sleep until new incoming connection is arrived. Return accepted socket
 * object on success, or `null` on error.
 *
 * ==== socket_obj.close() -> numeric_errno
 * Close socket object. Return 0 on success, or system errno on error.
 *
 * ==== socket_obj.recv() -> string
 * Read data from socket. Return data string, or empty string if peer has
 * disconnected, or `null` on error.
 *
 * ==== socket_obj.recvAll() -> string
 * Same as `recv()`, but keeps reading data until socket is closed.
 *
 * ==== sock.send(string) -> num_bytes_sent
 * Send string to the socket. Return number of bytes sent, or 0 on error.
 * Simple HTTP client example:
 *
 *    var s = Socket.connect("google.com", 80);
 *    s.send("GET / HTTP/1.0\n\n");
 *    var reply = s.recv();
 */

#ifndef CS_V7_BUILTIN_BUILTIN_H_
#define CS_V7_BUILTIN_BUILTIN_H_

struct v7;

void init_file(struct v7 *);
void init_socket(struct v7 *);
void init_crypto(struct v7 *);

#endif /* CS_V7_BUILTIN_BUILTIN_H_ */
#ifdef V7_MODULE_LINES
#line 1 "v7/src/slre.h"
#endif
/*
 * Copyright (c) 2014 Cesanta Software Limited
 * All rights reserved
 *
 * This software is dual-licensed: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation. For the terms of this
 * license, see <http://www.gnu.org/licenses/>.
 *
 * You are free to use this software under the terms of the GNU General
 * Public License, but WITHOUT ANY WARRANTY; without even the implied
 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU General Public License for more details.
 *
 * Alternatively, you can license this software under a commercial
 * license, as set out in <https://www.cesanta.com/license>.
 */

#ifndef CS_V7_SRC_SLRE_H_
#define CS_V7_SRC_SLRE_H_

/* Return codes for slre_compile() */
enum slre_error {
  SLRE_OK,
  SLRE_INVALID_DEC_DIGIT,
  SLRE_INVALID_HEX_DIGIT,
  SLRE_INVALID_ESC_CHAR,
  SLRE_UNTERM_ESC_SEQ,
  SLRE_SYNTAX_ERROR,
  SLRE_UNMATCH_LBR,
  SLRE_UNMATCH_RBR,
  SLRE_NUM_OVERFLOW,
  SLRE_INF_LOOP_M_EMP_STR,
  SLRE_TOO_MANY_CHARSETS,
  SLRE_INV_CHARSET_RANGE,
  SLRE_CHARSET_TOO_LARGE,
  SLRE_MALFORMED_CHARSET,
  SLRE_INVALID_BACK_REFERENCE,
  SLRE_TOO_MANY_CAPTURES,
  SLRE_INVALID_QUANTIFIER,
  SLRE_BAD_CHAR_AFTER_USD
};

#if V7_ENABLE__RegExp

#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */

/* Regex flags */
#define SLRE_FLAG_G 1  /* Global - match in the whole string */
#define SLRE_FLAG_I 2  /* Ignore case */
#define SLRE_FLAG_M 4  /* Multiline */
#define SLRE_FLAG_RE 8 /* flag RegExp/String */

/* Describes single capture */
struct slre_cap {
  const char *start; /* points to the beginning of the capture group */
  const char *end;   /* points to the end of the capture group */
};

/* Describes all captures */
#define SLRE_MAX_CAPS 32
struct slre_loot {
  int num_captures;
  struct slre_cap caps[SLRE_MAX_CAPS];
};

/* Opaque structure that holds compiled regular expression */
struct slre_prog;

int slre_compile(const char *regexp, size_t regexp_len, const char *flags,
                 size_t flags_len, struct slre_prog **, int is_regex);
int slre_exec(struct slre_prog *prog, int flag_g, const char *start,
              const char *end, struct slre_loot *loot);
void slre_free(struct slre_prog *prog);

int slre_match(const char *, size_t, const char *, size_t, const char *, size_t,
               struct slre_loot *);
int slre_replace(struct slre_loot *loot, const char *src, size_t src_len,
                 const char *replace, size_t rep_len, struct slre_loot *dst);
int slre_get_flags(struct slre_prog *);

#ifdef __cplusplus
}
#endif /* __cplusplus */

#endif /* V7_ENABLE__RegExp */

#endif /* CS_V7_SRC_SLRE_H_ */
#ifdef V7_MODULE_LINES
#line 1 "v7/src/stdlib.h"
#endif
/*
 * Copyright (c) 2014 Cesanta Software Limited
 * All rights reserved
 */

#ifndef CS_V7_SRC_STDLIB_H_
#define CS_V7_SRC_STDLIB_H_

/* Amalgamated: #include "v7/src/internal.h" */
/* Amalgamated: #include "v7/src/core.h" */

#if defined(__cplusplus)
extern "C" {
#endif /* __cplusplus */

/*V7_PRIVATE*/ void init_stdlib(struct v7 *v7);

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err std_eval(struct v7 *v7, v7_val_t arg, v7_val_t this_obj,
                                int is_json, v7_val_t *res);

#if defined(__cplusplus)
}
#endif /* __cplusplus */

#endif /* CS_V7_SRC_STDLIB_H_ */
#ifdef V7_MODULE_LINES
#line 1 "v7/src/heapusage.h"
#endif
/*
 * Copyright (c) 2014-2016 Cesanta Software Limited
 * All rights reserved
 */

#ifndef CS_V7_SRC_HEAPUSAGE_H_
#define CS_V7_SRC_HEAPUSAGE_H_

#if defined(V7_HEAPUSAGE_ENABLE)

extern volatile int heap_dont_count;

/*
 * Returns total heap-allocated size in bytes (without any overhead of the
 * heap implementation)
 */
size_t heapusage_alloc_size(void);

/*
 * Returns number of active allocations
 */
size_t heapusage_allocs_cnt(void);

/*
 * Must be called before allocating some memory that should not be indicated as
 * memory consumed for some particular operation: for example, when we
 * preallocate some GC buffer.
 */
#define heapusage_dont_count(a) \
  do {                          \
    heap_dont_count = a;        \
  } while (0)

#else /* V7_HEAPUSAGE_ENABLE */

#define heapusage_alloc_size() (0)
#define heapusage_allocs_cnt() (0)
#define heapusage_dont_count(a)

#endif /* V7_HEAPUSAGE_ENABLE */

#endif /* CS_V7_SRC_HEAPUSAGE_H_ */
#ifdef V7_MODULE_LINES
#line 1 "v7/src/std_proxy.h"
#endif
/*
 * Copyright (c) 2014 Cesanta Software Limited
 * All rights reserved
 */

#ifndef CS_V7_SRC_STD_PROXY_H_
#define CS_V7_SRC_STD_PROXY_H_

/* Amalgamated: #include "v7/src/internal.h" */
/* Amalgamated: #include "v7/src/core.h" */

#if V7_ENABLE__Proxy

#define _V7_PROXY_TARGET_NAME "__tgt"
#define _V7_PROXY_HANDLER_NAME "__hnd"

#if defined(__cplusplus)
extern "C" {
#endif /* __cplusplus */

#if V7_ENABLE__Proxy

V7_PRIVATE enum v7_err Proxy_ctor(struct v7 *v7, v7_val_t *res);

V7_PRIVATE void init_proxy(struct v7 *v7);

/*
 * Returns whether the given name is one of the special Proxy names
 * (_V7_PROXY_TARGET_NAME or _V7_PROXY_HANDLER_NAME)
 */
V7_PRIVATE int is_special_proxy_name(const char *name, size_t name_len);

#endif

#if defined(__cplusplus)
}
#endif /* __cplusplus */

#endif /* V7_ENABLE__Proxy */

#endif /* CS_V7_SRC_STD_PROXY_H_ */
#ifdef V7_MODULE_LINES
#line 1 "v7/src/freeze.h"
#endif
/*
 * Copyright (c) 2014 Cesanta Software Limited
 * All rights reserved
 */

#ifndef CS_V7_SRC_FREEZE_H_
#define CS_V7_SRC_FREEZE_H_

#ifdef V7_FREEZE

/* Amalgamated: #include "v7/src/internal.h" */

struct v7_property;

#if defined(__cplusplus)
extern "C" {
#endif /* __cplusplus */

V7_PRIVATE void freeze(struct v7 *v7, char *filename);
V7_PRIVATE void freeze_obj(struct v7 *v7, FILE *f, v7_val_t v);
V7_PRIVATE void freeze_prop(struct v7 *v7, FILE *f, struct v7_property *prop);

#if defined(__cplusplus)
}
#endif /* __cplusplus */

#endif /* V7_FREEZE */

#endif /* CS_V7_SRC_FREEZE_H_ */
#ifdef V7_MODULE_LINES
#line 1 "v7/src/std_array.h"
#endif
/*
 * Copyright (c) 2014 Cesanta Software Limited
 * All rights reserved
 */

#ifndef CS_V7_SRC_STD_ARRAY_H_
#define CS_V7_SRC_STD_ARRAY_H_

/* Amalgamated: #include "v7/src/internal.h" */

#if defined(__cplusplus)
extern "C" {
#endif /* __cplusplus */

V7_PRIVATE void init_array(struct v7 *v7);

#if defined(__cplusplus)
}
#endif /* __cplusplus */

#endif /* CS_V7_SRC_STD_ARRAY_H_ */
#ifdef V7_MODULE_LINES
#line 1 "v7/src/std_boolean.h"
#endif
/*
 * Copyright (c) 2014 Cesanta Software Limited
 * All rights reserved
 */

#ifndef CS_V7_SRC_STD_BOOLEAN_H_
#define CS_V7_SRC_STD_BOOLEAN_H_

/* Amalgamated: #include "v7/src/internal.h" */

#if defined(__cplusplus)
extern "C" {
#endif /* __cplusplus */

V7_PRIVATE void init_boolean(struct v7 *v7);

#if defined(__cplusplus)
}
#endif /* __cplusplus */

#endif /* CS_V7_SRC_STD_BOOLEAN_H_ */
#ifdef V7_MODULE_LINES
#line 1 "v7/src/std_date.h"
#endif
/*
 * Copyright (c) 2014 Cesanta Software Limited
 * All rights reserved
 */

#ifndef CS_V7_SRC_STD_DATE_H_
#define CS_V7_SRC_STD_DATE_H_

/* Amalgamated: #include "v7/src/internal.h" */

#if V7_ENABLE__Date

#if defined(__cplusplus)
extern "C" {
#endif /* __cplusplus */

V7_PRIVATE void init_date(struct v7 *v7);

#if defined(__cplusplus)
}
#endif /* __cplusplus */

#endif /* V7_ENABLE__Date */
#endif /* CS_V7_SRC_STD_DATE_H_ */
#ifdef V7_MODULE_LINES
#line 1 "v7/src/std_function.h"
#endif
/*
 * Copyright (c) 2014 Cesanta Software Limited
 * All rights reserved
 */

#ifndef CS_V7_SRC_STD_FUNCTION_H_
#define CS_V7_SRC_STD_FUNCTION_H_

/* Amalgamated: #include "v7/src/internal.h" */

#if defined(__cplusplus)
extern "C" {
#endif /* __cplusplus */

V7_PRIVATE void init_function(struct v7 *v7);

#if defined(__cplusplus)
}
#endif /* __cplusplus */

#endif /* CS_V7_SRC_STD_FUNCTION_H_ */
#ifdef V7_MODULE_LINES
#line 1 "v7/src/std_json.h"
#endif
/*
 * Copyright (c) 2014 Cesanta Software Limited
 * All rights reserved
 */

#ifndef CS_V7_SRC_STD_JSON_H_
#define CS_V7_SRC_STD_JSON_H_

/* Amalgamated: #include "v7/src/internal.h" */

#if defined(__cplusplus)
extern "C" {
#endif /* __cplusplus */

V7_PRIVATE void init_json(struct v7 *v7);

#if defined(__cplusplus)
}
#endif /* __cplusplus */

#endif /* CS_V7_SRC_STD_JSON_H_ */
#ifdef V7_MODULE_LINES
#line 1 "v7/src/std_math.h"
#endif
/*
 * Copyright (c) 2014 Cesanta Software Limited
 * All rights reserved
 */

#ifndef CS_V7_SRC_STD_MATH_H_
#define CS_V7_SRC_STD_MATH_H_

/* Amalgamated: #include "v7/src/internal.h" */

#if V7_ENABLE__Math

#if defined(__cplusplus)
extern "C" {
#endif /* __cplusplus */

V7_PRIVATE void init_math(struct v7 *v7);

#if defined(__cplusplus)
}
#endif /* __cplusplus */

#endif /* V7_ENABLE__Math */
#endif /* CS_V7_SRC_STD_MATH_H_ */
#ifdef V7_MODULE_LINES
#line 1 "v7/src/std_number.h"
#endif
/*
 * Copyright (c) 2014 Cesanta Software Limited
 * All rights reserved
 */

#ifndef CS_V7_SRC_STD_NUMBER_H_
#define CS_V7_SRC_STD_NUMBER_H_

/* Amalgamated: #include "v7/src/internal.h" */

#if defined(__cplusplus)
extern "C" {
#endif /* __cplusplus */

V7_PRIVATE void init_number(struct v7 *v7);

#if defined(__cplusplus)
}
#endif /* __cplusplus */

#endif /* CS_V7_SRC_STD_NUMBER_H_ */
#ifdef V7_MODULE_LINES
#line 1 "v7/src/std_object.h"
#endif
/*
 * Copyright (c) 2014 Cesanta Software Limited
 * All rights reserved
 */

#ifndef CS_V7_SRC_STD_OBJECT_H_
#define CS_V7_SRC_STD_OBJECT_H_

/* Amalgamated: #include "v7/src/internal.h" */
/* Amalgamated: #include "v7/src/core.h" */

struct v7;

#if defined(__cplusplus)
extern "C" {
#endif /* __cplusplus */

V7_PRIVATE void init_object(struct v7 *v7);

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Obj_valueOf(struct v7 *v7, v7_val_t *res);

#if defined(__cplusplus)
}
#endif /* __cplusplus */

#endif /* CS_V7_SRC_STD_OBJECT_H_ */
#ifdef V7_MODULE_LINES
#line 1 "v7/src/std_regex.h"
#endif
/*
 * Copyright (c) 2014 Cesanta Software Limited
 * All rights reserved
 */

#ifndef CS_V7_SRC_STD_REGEX_H_
#define CS_V7_SRC_STD_REGEX_H_

/* Amalgamated: #include "v7/src/internal.h" */
/* Amalgamated: #include "v7/src/core.h" */

#if V7_ENABLE__RegExp

#if defined(__cplusplus)
extern "C" {
#endif /* __cplusplus */

V7_PRIVATE enum v7_err Regex_ctor(struct v7 *v7, v7_val_t *res);
V7_PRIVATE enum v7_err rx_exec(struct v7 *v7, v7_val_t rx, v7_val_t vstr,
                               int lind, v7_val_t *res);

V7_PRIVATE void init_regex(struct v7 *v7);

#if defined(__cplusplus)
}
#endif /* __cplusplus */

#endif /* V7_ENABLE__RegExp */

#endif /* CS_V7_SRC_STD_REGEX_H_ */
#ifdef V7_MODULE_LINES
#line 1 "v7/src/std_string.h"
#endif
/*
 * Copyright (c) 2014 Cesanta Software Limited
 * All rights reserved
 */

#ifndef CS_V7_SRC_STD_STRING_H_
#define CS_V7_SRC_STD_STRING_H_

/* Amalgamated: #include "v7/src/internal.h" */
/* Amalgamated: #include "v7/src/core.h" */

/* Max captures for String.replace() */
#define V7_RE_MAX_REPL_SUB 20

#if defined(__cplusplus)
extern "C" {
#endif /* __cplusplus */

V7_PRIVATE void init_string(struct v7 *v7);

#if defined(__cplusplus)
}
#endif /* __cplusplus */

#endif /* CS_V7_SRC_STD_STRING_H_ */
#ifdef V7_MODULE_LINES
#line 1 "v7/src/js_stdlib.h"
#endif
/*
 * Copyright (c) 2014 Cesanta Software Limited
 * All rights reserved
 */

#ifndef CS_V7_SRC_JS_STDLIB_H_
#define CS_V7_SRC_JS_STDLIB_H_

/* Amalgamated: #include "v7/src/internal.h" */

#if defined(__cplusplus)
extern "C" {
#endif /* __cplusplus */

V7_PRIVATE void init_js_stdlib(struct v7 *);

#if defined(__cplusplus)
}
#endif /* __cplusplus */

#endif /* CS_V7_SRC_JS_STDLIB_H_ */
#ifdef V7_MODULE_LINES
#line 1 "v7/src/main_public.h"
#endif
/*
 * Copyright (c) 2014 Cesanta Software Limited
 * All rights reserved
 */

/*
 * === v7 main()
 */

#ifndef CS_V7_SRC_MAIN_PUBLIC_H_
#define CS_V7_SRC_MAIN_PUBLIC_H_

/* Amalgamated: #include "v7/src/core_public.h" */

#if defined(__cplusplus)
extern "C" {
#endif /* __cplusplus */

/*
 * V7 executable main function.
 *
 * There are various callbacks available:
 *
 * `pre_freeze_init()` and `pre_init()` are optional intialization functions,
 * aimed to export any extra functionality into vanilla v7 engine. They are
 * called after v7 initialization, before executing given files or inline
 * expressions. `pre_freeze_init()` is called before "freezing" v7 state;
 * whereas `pre_init` called afterwards.
 *
 * `post_init()`, if provided, is called after executing files and expressions,
 * before destroying v7 instance and exiting.
 */
int v7_main(int argc, char *argv[], void (*pre_freeze_init)(struct v7 *),
            void (*pre_init)(struct v7 *), void (*post_init)(struct v7 *));

#if defined(__cplusplus)
}
#endif /* __cplusplus */

#endif /* CS_V7_SRC_MAIN_PUBLIC_H_ */
#ifdef V7_MODULE_LINES
#line 1 "v7/src/main.h"
#endif
/*
 * Copyright (c) 2014 Cesanta Software Limited
 * All rights reserved
 */

#ifndef CS_V7_SRC_MAIN_H_
#define CS_V7_SRC_MAIN_H_

/* Amalgamated: #include "v7/src/main_public.h" */

#endif /* CS_V7_SRC_MAIN_H_ */
#ifndef V7_EXPORT_INTERNAL_HEADERS
#ifdef V7_MODULE_LINES
#line 1 "common/mbuf.c"
#endif
/*
 * Copyright (c) 2014 Cesanta Software Limited
 * All rights reserved
 */

#ifndef EXCLUDE_COMMON

#include <assert.h>
#include <string.h>
/* Amalgamated: #include "common/mbuf.h" */

#ifndef MBUF_REALLOC
#define MBUF_REALLOC realloc
#endif

#ifndef MBUF_FREE
#define MBUF_FREE free
#endif

void mbuf_init(struct mbuf *mbuf, size_t initial_size) {
  mbuf->len = mbuf->size = 0;
  mbuf->buf = NULL;
  mbuf_resize(mbuf, initial_size);
}

void mbuf_free(struct mbuf *mbuf) {
  if (mbuf->buf != NULL) {
    MBUF_FREE(mbuf->buf);
    mbuf_init(mbuf, 0);
  }
}

void mbuf_resize(struct mbuf *a, size_t new_size) {
  if (new_size > a->size || (new_size < a->size && new_size >= a->len)) {
    char *buf = (char *) MBUF_REALLOC(a->buf, new_size);
    /*
     * In case realloc fails, there's not much we can do, except keep things as
     * they are. Note that NULL is a valid return value from realloc when
     * size == 0, but that is covered too.
     */
    if (buf == NULL && new_size != 0) return;
    a->buf = buf;
    a->size = new_size;
  }
}

void mbuf_trim(struct mbuf *mbuf) {
  mbuf_resize(mbuf, mbuf->len);
}

size_t mbuf_insert(struct mbuf *a, size_t off, const void *buf, size_t len) {
  char *p = NULL;

  assert(a != NULL);
  assert(a->len <= a->size);
  assert(off <= a->len);

  /* check overflow */
  if (~(size_t) 0 - (size_t) a->buf < len) return 0;

  if (a->len + len <= a->size) {
    memmove(a->buf + off + len, a->buf + off, a->len - off);
    if (buf != NULL) {
      memcpy(a->buf + off, buf, len);
    }
    a->len += len;
  } else {
    size_t new_size = (size_t)((a->len + len) * MBUF_SIZE_MULTIPLIER);
    if ((p = (char *) MBUF_REALLOC(a->buf, new_size)) != NULL) {
      a->buf = p;
      memmove(a->buf + off + len, a->buf + off, a->len - off);
      if (buf != NULL) memcpy(a->buf + off, buf, len);
      a->len += len;
      a->size = new_size;
    } else {
      len = 0;
    }
  }

  return len;
}

size_t mbuf_append(struct mbuf *a, const void *buf, size_t len) {
  return mbuf_insert(a, a->len, buf, len);
}

void mbuf_remove(struct mbuf *mb, size_t n) {
  if (n > 0 && n <= mb->len) {
    memmove(mb->buf, mb->buf + n, mb->len - n);
    mb->len -= n;
  }
}

#endif /* EXCLUDE_COMMON */
#ifdef V7_MODULE_LINES
#line 1 "common/str_util.c"
#endif
/*
 * Copyright (c) 2015 Cesanta Software Limited
 * All rights reserved
 */

#ifndef EXCLUDE_COMMON

/* Amalgamated: #include "common/platform.h" */
/* Amalgamated: #include "common/str_util.h" */

size_t c_strnlen(const char *s, size_t maxlen) {
  size_t l = 0;
  for (; l < maxlen && s[l] != '\0'; l++) {
  }
  return l;
}

#define C_SNPRINTF_APPEND_CHAR(ch)       \
  do {                                   \
    if (i < (int) buf_size) buf[i] = ch; \
    i++;                                 \
  } while (0)

#define C_SNPRINTF_FLAG_ZERO 1

#ifdef C_DISABLE_BUILTIN_SNPRINTF
int c_vsnprintf(char *buf, size_t buf_size, const char *fmt, va_list ap) {
  return vsnprintf(buf, buf_size, fmt, ap);
}
#else
static int c_itoa(char *buf, size_t buf_size, int64_t num, int base, int flags,
                  int field_width) {
  char tmp[40];
  int i = 0, k = 0, neg = 0;

  if (num < 0) {
    neg++;
    num = -num;
  }

  /* Print into temporary buffer - in reverse order */
  do {
    int rem = num % base;
    if (rem < 10) {
      tmp[k++] = '0' + rem;
    } else {
      tmp[k++] = 'a' + (rem - 10);
    }
    num /= base;
  } while (num > 0);

  /* Zero padding */
  if (flags && C_SNPRINTF_FLAG_ZERO) {
    while (k < field_width && k < (int) sizeof(tmp) - 1) {
      tmp[k++] = '0';
    }
  }

  /* And sign */
  if (neg) {
    tmp[k++] = '-';
  }

  /* Now output */
  while (--k >= 0) {
    C_SNPRINTF_APPEND_CHAR(tmp[k]);
  }

  return i;
}

int c_vsnprintf(char *buf, size_t buf_size, const char *fmt, va_list ap) {
  int ch, i = 0, len_mod, flags, precision, field_width;

  while ((ch = *fmt++) != '\0') {
    if (ch != '%') {
      C_SNPRINTF_APPEND_CHAR(ch);
    } else {
      /*
       * Conversion specification:
       *   zero or more flags (one of: # 0 - <space> + ')
       *   an optional minimum  field  width (digits)
       *   an  optional precision (. followed by digits, or *)
       *   an optional length modifier (one of: hh h l ll L q j z t)
       *   conversion specifier (one of: d i o u x X e E f F g G a A c s p n)
       */
      flags = field_width = precision = len_mod = 0;

      /* Flags. only zero-pad flag is supported. */
      if (*fmt == '0') {
        flags |= C_SNPRINTF_FLAG_ZERO;
      }

      /* Field width */
      while (*fmt >= '0' && *fmt <= '9') {
        field_width *= 10;
        field_width += *fmt++ - '0';
      }
      /* Dynamic field width */
      if (*fmt == '*') {
        field_width = va_arg(ap, int);
        fmt++;
      }

      /* Precision */
      if (*fmt == '.') {
        fmt++;
        if (*fmt == '*') {
          precision = va_arg(ap, int);
          fmt++;
        } else {
          while (*fmt >= '0' && *fmt <= '9') {
            precision *= 10;
            precision += *fmt++ - '0';
          }
        }
      }

      /* Length modifier */
      switch (*fmt) {
        case 'h':
        case 'l':
        case 'L':
        case 'I':
        case 'q':
        case 'j':
        case 'z':
        case 't':
          len_mod = *fmt++;
          if (*fmt == 'h') {
            len_mod = 'H';
            fmt++;
          }
          if (*fmt == 'l') {
            len_mod = 'q';
            fmt++;
          }
          break;
      }

      ch = *fmt++;
      if (ch == 's') {
        const char *s = va_arg(ap, const char *); /* Always fetch parameter */
        int j;
        int pad = field_width - (precision >= 0 ? c_strnlen(s, precision) : 0);
        for (j = 0; j < pad; j++) {
          C_SNPRINTF_APPEND_CHAR(' ');
        }

        /* `s` may be NULL in case of %.*s */
        if (s != NULL) {
          /* Ignore negative and 0 precisions */
          for (j = 0; (precision <= 0 || j < precision) && s[j] != '\0'; j++) {
            C_SNPRINTF_APPEND_CHAR(s[j]);
          }
        }
      } else if (ch == 'c') {
        ch = va_arg(ap, int); /* Always fetch parameter */
        C_SNPRINTF_APPEND_CHAR(ch);
      } else if (ch == 'd' && len_mod == 0) {
        i += c_itoa(buf + i, buf_size - i, va_arg(ap, int), 10, flags,
                    field_width);
      } else if (ch == 'd' && len_mod == 'l') {
        i += c_itoa(buf + i, buf_size - i, va_arg(ap, long), 10, flags,
                    field_width);
#ifdef SSIZE_MAX
      } else if (ch == 'd' && len_mod == 'z') {
        i += c_itoa(buf + i, buf_size - i, va_arg(ap, ssize_t), 10, flags,
                    field_width);
#endif
      } else if (ch == 'd' && len_mod == 'q') {
        i += c_itoa(buf + i, buf_size - i, va_arg(ap, int64_t), 10, flags,
                    field_width);
      } else if ((ch == 'x' || ch == 'u') && len_mod == 0) {
        i += c_itoa(buf + i, buf_size - i, va_arg(ap, unsigned),
                    ch == 'x' ? 16 : 10, flags, field_width);
      } else if ((ch == 'x' || ch == 'u') && len_mod == 'l') {
        i += c_itoa(buf + i, buf_size - i, va_arg(ap, unsigned long),
                    ch == 'x' ? 16 : 10, flags, field_width);
      } else if ((ch == 'x' || ch == 'u') && len_mod == 'z') {
        i += c_itoa(buf + i, buf_size - i, va_arg(ap, size_t),
                    ch == 'x' ? 16 : 10, flags, field_width);
      } else if (ch == 'p') {
        unsigned long num = (unsigned long) (uintptr_t) va_arg(ap, void *);
        C_SNPRINTF_APPEND_CHAR('0');
        C_SNPRINTF_APPEND_CHAR('x');
        i += c_itoa(buf + i, buf_size - i, num, 16, flags, 0);
      } else {
#ifndef NO_LIBC
        /*
         * TODO(lsm): abort is not nice in a library, remove it
         * Also, ESP8266 SDK doesn't have it
         */
        abort();
#endif
      }
    }
  }

  /* Zero-terminate the result */
  if (buf_size > 0) {
    buf[i < (int) buf_size ? i : (int) buf_size - 1] = '\0';
  }

  return i;
}
#endif

int c_snprintf(char *buf, size_t buf_size, const char *fmt, ...) {
  int result;
  va_list ap;
  va_start(ap, fmt);
  result = c_vsnprintf(buf, buf_size, fmt, ap);
  va_end(ap);
  return result;
}

#ifdef _WIN32
int to_wchar(const char *path, wchar_t *wbuf, size_t wbuf_len) {
  int ret;
  char buf[MAX_PATH * 2], buf2[MAX_PATH * 2], *p;

  strncpy(buf, path, sizeof(buf));
  buf[sizeof(buf) - 1] = '\0';

  /* Trim trailing slashes. Leave backslash for paths like "X:\" */
  p = buf + strlen(buf) - 1;
  while (p > buf && p[-1] != ':' && (p[0] == '\\' || p[0] == '/')) *p-- = '\0';

  memset(wbuf, 0, wbuf_len * sizeof(wchar_t));
  ret = MultiByteToWideChar(CP_UTF8, 0, buf, -1, wbuf, (int) wbuf_len);

  /*
   * Convert back to Unicode. If doubly-converted string does not match the
   * original, something is fishy, reject.
   */
  WideCharToMultiByte(CP_UTF8, 0, wbuf, (int) wbuf_len, buf2, sizeof(buf2),
                      NULL, NULL);
  if (strcmp(buf, buf2) != 0) {
    wbuf[0] = L'\0';
    ret = 0;
  }

  return ret;
}
#endif /* _WIN32 */

/* The simplest O(mn) algorithm. Better implementation are GPLed */
const char *c_strnstr(const char *s, const char *find, size_t slen) {
  size_t find_length = strlen(find);
  size_t i;

  for (i = 0; i < slen; i++) {
    if (i + find_length > slen) {
      return NULL;
    }

    if (strncmp(&s[i], find, find_length) == 0) {
      return &s[i];
    }
  }

  return NULL;
}

#endif /* EXCLUDE_COMMON */
#ifdef V7_MODULE_LINES
#line 1 "common/utf.c"
#endif
/*
 * The authors of this software are Rob Pike and Ken Thompson.
 *              Copyright (c) 2002 by Lucent Technologies.
 * Permission to use, copy, modify, and distribute this software for any
 * purpose without fee is hereby granted, provided that this entire notice
 * is included in all copies of any software which is or includes a copy
 * or modification of this software and in all copies of the supporting
 * documentation for such software.
 * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
 * WARRANTY.  IN PARTICULAR, NEITHER THE AUTHORS NOR LUCENT TECHNOLOGIES MAKE
 * ANY REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
 * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
 */

#ifndef EXCLUDE_COMMON

/* clang-format off */

#include <stdarg.h>
#include <string.h>
/* Amalgamated: #include "common/platform.h" */
/* Amalgamated: #include "common/utf.h" */

#if CS_ENABLE_UTF8
enum {
  Bit1 = 7,
  Bitx = 6,
  Bit2 = 5,
  Bit3 = 4,
  Bit4 = 3,
  Bit5 = 2,

  T1 = ((1 << (Bit1 + 1)) - 1) ^ 0xFF, /* 0000 0000 */
  Tx = ((1 << (Bitx + 1)) - 1) ^ 0xFF, /* 1000 0000 */
  T2 = ((1 << (Bit2 + 1)) - 1) ^ 0xFF, /* 1100 0000 */
  T3 = ((1 << (Bit3 + 1)) - 1) ^ 0xFF, /* 1110 0000 */
  T4 = ((1 << (Bit4 + 1)) - 1) ^ 0xFF, /* 1111 0000 */
  T5 = ((1 << (Bit5 + 1)) - 1) ^ 0xFF, /* 1111 1000 */

  Rune1 = (1 << (Bit1 + 0 * Bitx)) - 1, /* 0000 0000 0000 0000 0111 1111 */
  Rune2 = (1 << (Bit2 + 1 * Bitx)) - 1, /* 0000 0000 0000 0111 1111 1111 */
  Rune3 = (1 << (Bit3 + 2 * Bitx)) - 1, /* 0000 0000 1111 1111 1111 1111 */
  Rune4 = (1 << (Bit4 + 3 * Bitx)) - 1, /* 0011 1111 1111 1111 1111 1111 */

  Maskx = (1 << Bitx) - 1, /* 0011 1111 */
  Testx = Maskx ^ 0xFF,    /* 1100 0000 */

  Bad = Runeerror
};

int chartorune(Rune *rune, const char *str) {
  int c, c1, c2 /* , c3 */;
  unsigned short l;

  /*
   * one character sequence
   *	00000-0007F => T1
   */
  c = *(uchar *) str;
  if (c < Tx) {
    *rune = c;
    return 1;
  }

  /*
   * two character sequence
   *	0080-07FF => T2 Tx
   */
  c1 = *(uchar *) (str + 1) ^ Tx;
  if (c1 & Testx) goto bad;
  if (c < T3) {
    if (c < T2) goto bad;
    l = ((c << Bitx) | c1) & Rune2;
    if (l <= Rune1) goto bad;
    *rune = l;
    return 2;
  }

  /*
   * three character sequence
   *	0800-FFFF => T3 Tx Tx
   */
  c2 = *(uchar *) (str + 2) ^ Tx;
  if (c2 & Testx) goto bad;
  if (c < T4) {
    l = ((((c << Bitx) | c1) << Bitx) | c2) & Rune3;
    if (l <= Rune2) goto bad;
    *rune = l;
    return 3;
  }

/*
 * four character sequence
 *	10000-10FFFF => T4 Tx Tx Tx
 */
/* if(UTFmax >= 4) {
        c3 = *(uchar*)(str+3) ^ Tx;
        if(c3 & Testx)
                goto bad;
        if(c < T5) {
                l = ((((((c << Bitx) | c1) << Bitx) | c2) << Bitx) | c3) &
Rune4;
                if(l <= Rune3)
                        goto bad;
                if(l > Runemax)
                        goto bad;
                *rune = l;
                return 4;
        }
} */

/*
 * bad decoding
 */
bad:
  *rune = Bad;
  return 1;
}

int runetochar(char *str, Rune *rune) {
  unsigned short c;

  /*
   * one character sequence
   *	00000-0007F => 00-7F
   */
  c = *rune;
  if (c <= Rune1) {
    str[0] = c;
    return 1;
  }

  /*
   * two character sequence
   *	00080-007FF => T2 Tx
   */
  if (c <= Rune2) {
    str[0] = T2 | (c >> 1 * Bitx);
    str[1] = Tx | (c & Maskx);
    return 2;
  }

  /*
   * three character sequence
   *	00800-0FFFF => T3 Tx Tx
   */
  /* if(c > Runemax)
          c = Runeerror; */
  /* if(c <= Rune3) { */
  str[0] = T3 | (c >> 2 * Bitx);
  str[1] = Tx | ((c >> 1 * Bitx) & Maskx);
  str[2] = Tx | (c & Maskx);
  return 3;
  /* } */

  /*
   * four character sequence
   *	010000-1FFFFF => T4 Tx Tx Tx
   */
  /* str[0] = T4 |  (c >> 3*Bitx);
  str[1] = Tx | ((c >> 2*Bitx) & Maskx);
  str[2] = Tx | ((c >> 1*Bitx) & Maskx);
  str[3] = Tx |  (c & Maskx);
  return 4; */
}

int fullrune(const char *str, int n) {
  int c;

  if (n <= 0) return 0;
  c = *(uchar *) str;
  if (c < Tx) return 1;
  if (c < T3) return n >= 2;
  if (UTFmax == 3 || c < T4) return n >= 3;
  return n >= 4;
}

int utfnlen(const char *s, long m) {
  int c;
  long n;
  Rune rune;
  const char *es;

  es = s + m;
  for (n = 0; s < es; n++) {
    c = *(uchar *) s;
    if (c < Runeself) {
      s++;
      continue;
    }
    if (!fullrune(s, es - s)) break;
    s += chartorune(&rune, s);
  }
  return n;
}

const char *utfnshift(const char *s, long m) {
  int c;
  long n;
  Rune rune;

  for (n = 0; n < m; n++) {
    c = *(uchar *) s;
    if (c < Runeself) {
      s++;
      continue;
    }
    s += chartorune(&rune, s);
  }
  return s;
}

/*
 * The authors of this software are Rob Pike and Ken Thompson.
 *              Copyright (c) 2002 by Lucent Technologies.
 * Permission to use, copy, modify, and distribute this software for any
 * purpose without fee is hereby granted, provided that this entire notice
 * is included in all copies of any software which is or includes a copy
 * or modification of this software and in all copies of the supporting
 * documentation for such software.
 * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
 * WARRANTY.  IN PARTICULAR, NEITHER THE AUTHORS NOR LUCENT TECHNOLOGIES MAKE
 * ANY REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
 * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
 */
#include <stdarg.h>
#include <string.h>
/* Amalgamated: #include "common/utf.h" */

/*
 * alpha ranges -
 *	only covers ranges not in lower||upper
 */
static Rune __alpha2[] = {
    0x00d8, 0x00f6, /* Ø - ö */
    0x00f8, 0x01f5, /* ø - ǵ */
    0x0250, 0x02a8, /* ɐ - ʨ */
    0x038e, 0x03a1, /* Ύ - Ρ */
    0x03a3, 0x03ce, /* Σ - ώ */
    0x03d0, 0x03d6, /* ϐ - ϖ */
    0x03e2, 0x03f3, /* Ϣ - ϳ */
    0x0490, 0x04c4, /* Ґ - ӄ */
    0x0561, 0x0587, /* ա - և */
    0x05d0, 0x05ea, /* א - ת */
    0x05f0, 0x05f2, /* װ - ײ */
    0x0621, 0x063a, /* ء - غ */
    0x0640, 0x064a, /* ـ - ي */
    0x0671, 0x06b7, /* ٱ - ڷ */
    0x06ba, 0x06be, /* ں - ھ */
    0x06c0, 0x06ce, /* ۀ - ێ */
    0x06d0, 0x06d3, /* ې - ۓ */
    0x0905, 0x0939, /* अ - ह */
    0x0958, 0x0961, /* क़ - ॡ */
    0x0985, 0x098c, /* অ - ঌ */
    0x098f, 0x0990, /* এ - ঐ */
    0x0993, 0x09a8, /* ও - ন */
    0x09aa, 0x09b0, /* প - র */
    0x09b6, 0x09b9, /* শ - হ */
    0x09dc, 0x09dd, /* ড় - ঢ় */
    0x09df, 0x09e1, /* য় - ৡ */
    0x09f0, 0x09f1, /* ৰ - ৱ */
    0x0a05, 0x0a0a, /* ਅ - ਊ */
    0x0a0f, 0x0a10, /* ਏ - ਐ */
    0x0a13, 0x0a28, /* ਓ - ਨ */
    0x0a2a, 0x0a30, /* ਪ - ਰ */
    0x0a32, 0x0a33, /* ਲ - ਲ਼ */
    0x0a35, 0x0a36, /* ਵ - ਸ਼ */
    0x0a38, 0x0a39, /* ਸ - ਹ */
    0x0a59, 0x0a5c, /* ਖ਼ - ੜ */
    0x0a85, 0x0a8b, /* અ - ઋ */
    0x0a8f, 0x0a91, /* એ - ઑ */
    0x0a93, 0x0aa8, /* ઓ - ન */
    0x0aaa, 0x0ab0, /* પ - ર */
    0x0ab2, 0x0ab3, /* લ - ળ */
    0x0ab5, 0x0ab9, /* વ - હ */
    0x0b05, 0x0b0c, /* ଅ - ଌ */
    0x0b0f, 0x0b10, /* ଏ - ଐ */
    0x0b13, 0x0b28, /* ଓ - ନ */
    0x0b2a, 0x0b30, /* ପ - ର */
    0x0b32, 0x0b33, /* ଲ - ଳ */
    0x0b36, 0x0b39, /* ଶ - ହ */
    0x0b5c, 0x0b5d, /* ଡ଼ - ଢ଼ */
    0x0b5f, 0x0b61, /* ୟ - ୡ */
    0x0b85, 0x0b8a, /* அ - ஊ */
    0x0b8e, 0x0b90, /* எ - ஐ */
    0x0b92, 0x0b95, /* ஒ - க */
    0x0b99, 0x0b9a, /* ங - ச */
    0x0b9e, 0x0b9f, /* ஞ - ட */
    0x0ba3, 0x0ba4, /* ண - த */
    0x0ba8, 0x0baa, /* ந - ப */
    0x0bae, 0x0bb5, /* ம - வ */
    0x0bb7, 0x0bb9, /* ஷ - ஹ */
    0x0c05, 0x0c0c, /* అ - ఌ */
    0x0c0e, 0x0c10, /* ఎ - ఐ */
    0x0c12, 0x0c28, /* ఒ - న */
    0x0c2a, 0x0c33, /* ప - ళ */
    0x0c35, 0x0c39, /* వ - హ */
    0x0c60, 0x0c61, /* ౠ - ౡ */
    0x0c85, 0x0c8c, /* ಅ - ಌ */
    0x0c8e, 0x0c90, /* ಎ - ಐ */
    0x0c92, 0x0ca8, /* ಒ - ನ */
    0x0caa, 0x0cb3, /* ಪ - ಳ */
    0x0cb5, 0x0cb9, /* ವ - ಹ */
    0x0ce0, 0x0ce1, /* ೠ - ೡ */
    0x0d05, 0x0d0c, /* അ - ഌ */
    0x0d0e, 0x0d10, /* എ - ഐ */
    0x0d12, 0x0d28, /* ഒ - ന */
    0x0d2a, 0x0d39, /* പ - ഹ */
    0x0d60, 0x0d61, /* ൠ - ൡ */
    0x0e01, 0x0e30, /* ก - ะ */
    0x0e32, 0x0e33, /* า - ำ */
    0x0e40, 0x0e46, /* เ - ๆ */
    0x0e5a, 0x0e5b, /* ๚ - ๛ */
    0x0e81, 0x0e82, /* ກ - ຂ */
    0x0e87, 0x0e88, /* ງ - ຈ */
    0x0e94, 0x0e97, /* ດ - ທ */
    0x0e99, 0x0e9f, /* ນ - ຟ */
    0x0ea1, 0x0ea3, /* ມ - ຣ */
    0x0eaa, 0x0eab, /* ສ - ຫ */
    0x0ead, 0x0eae, /* ອ - ຮ */
    0x0eb2, 0x0eb3, /* າ - ຳ */
    0x0ec0, 0x0ec4, /* ເ - ໄ */
    0x0edc, 0x0edd, /* ໜ - ໝ */
    0x0f18, 0x0f19, /* ༘ - ༙ */
    0x0f40, 0x0f47, /* ཀ - ཇ */
    0x0f49, 0x0f69, /* ཉ - ཀྵ */
    0x10d0, 0x10f6, /* ა - ჶ */
    0x1100, 0x1159, /* ᄀ - ᅙ */
    0x115f, 0x11a2, /* ᅟ - ᆢ */
    0x11a8, 0x11f9, /* ᆨ - ᇹ */
    0x1e00, 0x1e9b, /* Ḁ - ẛ */
    0x1f50, 0x1f57, /* ὐ - ὗ */
    0x1f80, 0x1fb4, /* ᾀ - ᾴ */
    0x1fb6, 0x1fbc, /* ᾶ - ᾼ */
    0x1fc2, 0x1fc4, /* ῂ - ῄ */
    0x1fc6, 0x1fcc, /* ῆ - ῌ */
    0x1fd0, 0x1fd3, /* ῐ - ΐ */
    0x1fd6, 0x1fdb, /* ῖ - Ί */
    0x1fe0, 0x1fec, /* ῠ - Ῥ */
    0x1ff2, 0x1ff4, /* ῲ - ῴ */
    0x1ff6, 0x1ffc, /* ῶ - ῼ */
    0x210a, 0x2113, /* ℊ - ℓ */
    0x2115, 0x211d, /* ℕ - ℝ */
    0x2120, 0x2122, /* ℠ - ™ */
    0x212a, 0x2131, /* K - ℱ */
    0x2133, 0x2138, /* ℳ - ℸ */
    0x3041, 0x3094, /* ぁ - ゔ */
    0x30a1, 0x30fa, /* ァ - ヺ */
    0x3105, 0x312c, /* ㄅ - ㄬ */
    0x3131, 0x318e, /* ㄱ - ㆎ */
    0x3192, 0x319f, /* ㆒ - ㆟ */
    0x3260, 0x327b, /* ㉠ - ㉻ */
    0x328a, 0x32b0, /* ㊊ - ㊰ */
    0x32d0, 0x32fe, /* ㋐ - ㋾ */
    0x3300, 0x3357, /* ㌀ - ㍗ */
    0x3371, 0x3376, /* ㍱ - ㍶ */
    0x337b, 0x3394, /* ㍻ - ㎔ */
    0x3399, 0x339e, /* ㎙ - ㎞ */
    0x33a9, 0x33ad, /* ㎩ - ㎭ */
    0x33b0, 0x33c1, /* ㎰ - ㏁ */
    0x33c3, 0x33c5, /* ㏃ - ㏅ */
    0x33c7, 0x33d7, /* ㏇ - ㏗ */
    0x33d9, 0x33dd, /* ㏙ - ㏝ */
    0x4e00, 0x9fff, /* 一 - 鿿 */
    0xac00, 0xd7a3, /* 가 - 힣 */
    0xf900, 0xfb06, /* 豈 - st */
    0xfb13, 0xfb17, /* ﬓ - ﬗ */
    0xfb1f, 0xfb28, /* ײַ - ﬨ */
    0xfb2a, 0xfb36, /* שׁ - זּ */
    0xfb38, 0xfb3c, /* טּ - לּ */
    0xfb40, 0xfb41, /* נּ - סּ */
    0xfb43, 0xfb44, /* ףּ - פּ */
    0xfb46, 0xfbb1, /* צּ - ﮱ */
    0xfbd3, 0xfd3d, /* ﯓ - ﴽ */
    0xfd50, 0xfd8f, /* ﵐ - ﶏ */
    0xfd92, 0xfdc7, /* ﶒ - ﷇ */
    0xfdf0, 0xfdf9, /* ﷰ - ﷹ */
    0xfe70, 0xfe72, /* ﹰ - ﹲ */
    0xfe76, 0xfefc, /* ﹶ - ﻼ */
    0xff66, 0xff6f, /* ヲ - ッ */
    0xff71, 0xff9d, /* ア - ン */
    0xffa0, 0xffbe, /* ᅠ - ᄒ */
    0xffc2, 0xffc7, /* ᅡ - ᅦ */
    0xffca, 0xffcf, /* ᅧ - ᅬ */
    0xffd2, 0xffd7, /* ᅭ - ᅲ */
    0xffda, 0xffdc, /* ᅳ - ᅵ */
};

/*
 * alpha singlets -
 *	only covers ranges not in lower||upper
 */
static Rune __alpha1[] = {
    0x00aa, /* ª */
    0x00b5, /* µ */
    0x00ba, /* º */
    0x03da, /* Ϛ */
    0x03dc, /* Ϝ */
    0x03de, /* Ϟ */
    0x03e0, /* Ϡ */
    0x06d5, /* ە */
    0x09b2, /* ল */
    0x0a5e, /* ਫ਼ */
    0x0a8d, /* ઍ */
    0x0ae0, /* ૠ */
    0x0b9c, /* ஜ */
    0x0cde, /* ೞ */
    0x0e4f, /* ๏ */
    0x0e84, /* ຄ */
    0x0e8a, /* ຊ */
    0x0e8d, /* ຍ */
    0x0ea5, /* ລ */
    0x0ea7, /* ວ */
    0x0eb0, /* ະ */
    0x0ebd, /* ຽ */
    0x1fbe, /* ι */
    0x207f, /* ⁿ */
    0x20a8, /* ₨ */
    0x2102, /* ℂ */
    0x2107, /* ℇ */
    0x2124, /* ℤ */
    0x2126, /* Ω */
    0x2128, /* ℨ */
    0xfb3e, /* מּ */
    0xfe74, /* ﹴ */
};

/*
 * space ranges
 */
static Rune __space2[] = {
    0x0009, 0x000a, /* tab and newline */
    0x0020, 0x0020, /* space */
    0x00a0, 0x00a0, /*   */
    0x2000, 0x200b, /*   - ​ */
    0x2028, 0x2029, /* 
 - 
 */
    0x3000, 0x3000, /*   */
    0xfeff, 0xfeff, /*  */
};

/*
 * lower case ranges
 *	3rd col is conversion excess 500
 */
static Rune __toupper2[] = {
    0x0061, 0x007a, 468, /* a-z A-Z */
    0x00e0, 0x00f6, 468, /* à-ö À-Ö */
    0x00f8, 0x00fe, 468, /* ø-þ Ø-Þ */
    0x0256, 0x0257, 295, /* ɖ-ɗ Ɖ-Ɗ */
    0x0258, 0x0259, 298, /* ɘ-ə Ǝ-Ə */
    0x028a, 0x028b, 283, /* ʊ-ʋ Ʊ-Ʋ */
    0x03ad, 0x03af, 463, /* έ-ί Έ-Ί */
    0x03b1, 0x03c1, 468, /* α-ρ Α-Ρ */
    0x03c3, 0x03cb, 468, /* σ-ϋ Σ-Ϋ */
    0x03cd, 0x03ce, 437, /* ύ-ώ Ύ-Ώ */
    0x0430, 0x044f, 468, /* а-я А-Я */
    0x0451, 0x045c, 420, /* ё-ќ Ё-Ќ */
    0x045e, 0x045f, 420, /* ў-џ Ў-Џ */
    0x0561, 0x0586, 452, /* ա-ֆ Ա-Ֆ */
    0x1f00, 0x1f07, 508, /* ἀ-ἇ Ἀ-Ἇ */
    0x1f10, 0x1f15, 508, /* ἐ-ἕ Ἐ-Ἕ */
    0x1f20, 0x1f27, 508, /* ἠ-ἧ Ἠ-Ἧ */
    0x1f30, 0x1f37, 508, /* ἰ-ἷ Ἰ-Ἷ */
    0x1f40, 0x1f45, 508, /* ὀ-ὅ Ὀ-Ὅ */
    0x1f60, 0x1f67, 508, /* ὠ-ὧ Ὠ-Ὧ */
    0x1f70, 0x1f71, 574, /* ὰ-ά Ὰ-Ά */
    0x1f72, 0x1f75, 586, /* ὲ-ή Ὲ-Ή */
    0x1f76, 0x1f77, 600, /* ὶ-ί Ὶ-Ί */
    0x1f78, 0x1f79, 628, /* ὸ-ό Ὸ-Ό */
    0x1f7a, 0x1f7b, 612, /* ὺ-ύ Ὺ-Ύ */
    0x1f7c, 0x1f7d, 626, /* ὼ-ώ Ὼ-Ώ */
    0x1f80, 0x1f87, 508, /* ᾀ-ᾇ ᾈ-ᾏ */
    0x1f90, 0x1f97, 508, /* ᾐ-ᾗ ᾘ-ᾟ */
    0x1fa0, 0x1fa7, 508, /* ᾠ-ᾧ ᾨ-ᾯ */
    0x1fb0, 0x1fb1, 508, /* ᾰ-ᾱ Ᾰ-Ᾱ */
    0x1fd0, 0x1fd1, 508, /* ῐ-ῑ Ῐ-Ῑ */
    0x1fe0, 0x1fe1, 508, /* ῠ-ῡ Ῠ-Ῡ */
    0x2170, 0x217f, 484, /* ⅰ-ⅿ Ⅰ-Ⅿ */
    0x24d0, 0x24e9, 474, /* ⓐ-ⓩ Ⓐ-Ⓩ */
    0xff41, 0xff5a, 468, /* a-z A-Z */
};

/*
 * lower case singlets
 *	2nd col is conversion excess 500
 */
static Rune __toupper1[] = {
    0x00ff, 621, /* ÿ Ÿ */
    0x0101, 499, /* ā Ā */
    0x0103, 499, /* ă Ă */
    0x0105, 499, /* ą Ą */
    0x0107, 499, /* ć Ć */
    0x0109, 499, /* ĉ Ĉ */
    0x010b, 499, /* ċ Ċ */
    0x010d, 499, /* č Č */
    0x010f, 499, /* ď Ď */
    0x0111, 499, /* đ Đ */
    0x0113, 499, /* ē Ē */
    0x0115, 499, /* ĕ Ĕ */
    0x0117, 499, /* ė Ė */
    0x0119, 499, /* ę Ę */
    0x011b, 499, /* ě Ě */
    0x011d, 499, /* ĝ Ĝ */
    0x011f, 499, /* ğ Ğ */
    0x0121, 499, /* ġ Ġ */
    0x0123, 499, /* ģ Ģ */
    0x0125, 499, /* ĥ Ĥ */
    0x0127, 499, /* ħ Ħ */
    0x0129, 499, /* ĩ Ĩ */
    0x012b, 499, /* ī Ī */
    0x012d, 499, /* ĭ Ĭ */
    0x012f, 499, /* į Į */
    0x0131, 268, /* ı I */
    0x0133, 499, /* ij IJ */
    0x0135, 499, /* ĵ Ĵ */
    0x0137, 499, /* ķ Ķ */
    0x013a, 499, /* ĺ Ĺ */
    0x013c, 499, /* ļ Ļ */
    0x013e, 499, /* ľ Ľ */
    0x0140, 499, /* ŀ Ŀ */
    0x0142, 499, /* ł Ł */
    0x0144, 499, /* ń Ń */
    0x0146, 499, /* ņ Ņ */
    0x0148, 499, /* ň Ň */
    0x014b, 499, /* ŋ Ŋ */
    0x014d, 499, /* ō Ō */
    0x014f, 499, /* ŏ Ŏ */
    0x0151, 499, /* ő Ő */
    0x0153, 499, /* œ Œ */
    0x0155, 499, /* ŕ Ŕ */
    0x0157, 499, /* ŗ Ŗ */
    0x0159, 499, /* ř Ř */
    0x015b, 499, /* ś Ś */
    0x015d, 499, /* ŝ Ŝ */
    0x015f, 499, /* ş Ş */
    0x0161, 499, /* š Š */
    0x0163, 499, /* ţ Ţ */
    0x0165, 499, /* ť Ť */
    0x0167, 499, /* ŧ Ŧ */
    0x0169, 499, /* ũ Ũ */
    0x016b, 499, /* ū Ū */
    0x016d, 499, /* ŭ Ŭ */
    0x016f, 499, /* ů Ů */
    0x0171, 499, /* ű Ű */
    0x0173, 499, /* ų Ų */
    0x0175, 499, /* ŵ Ŵ */
    0x0177, 499, /* ŷ Ŷ */
    0x017a, 499, /* ź Ź */
    0x017c, 499, /* ż Ż */
    0x017e, 499, /* ž Ž */
    0x017f, 200, /* ſ S */
    0x0183, 499, /* ƃ Ƃ */
    0x0185, 499, /* ƅ Ƅ */
    0x0188, 499, /* ƈ Ƈ */
    0x018c, 499, /* ƌ Ƌ */
    0x0192, 499, /* ƒ Ƒ */
    0x0199, 499, /* ƙ Ƙ */
    0x01a1, 499, /* ơ Ơ */
    0x01a3, 499, /* ƣ Ƣ */
    0x01a5, 499, /* ƥ Ƥ */
    0x01a8, 499, /* ƨ Ƨ */
    0x01ad, 499, /* ƭ Ƭ */
    0x01b0, 499, /* ư Ư */
    0x01b4, 499, /* ƴ Ƴ */
    0x01b6, 499, /* ƶ Ƶ */
    0x01b9, 499, /* ƹ Ƹ */
    0x01bd, 499, /* ƽ Ƽ */
    0x01c5, 499, /* Dž DŽ */
    0x01c6, 498, /* dž DŽ */
    0x01c8, 499, /* Lj LJ */
    0x01c9, 498, /* lj LJ */
    0x01cb, 499, /* Nj NJ */
    0x01cc, 498, /* nj NJ */
    0x01ce, 499, /* ǎ Ǎ */
    0x01d0, 499, /* ǐ Ǐ */
    0x01d2, 499, /* ǒ Ǒ */
    0x01d4, 499, /* ǔ Ǔ */
    0x01d6, 499, /* ǖ Ǖ */
    0x01d8, 499, /* ǘ Ǘ */
    0x01da, 499, /* ǚ Ǚ */
    0x01dc, 499, /* ǜ Ǜ */
    0x01df, 499, /* ǟ Ǟ */
    0x01e1, 499, /* ǡ Ǡ */
    0x01e3, 499, /* ǣ Ǣ */
    0x01e5, 499, /* ǥ Ǥ */
    0x01e7, 499, /* ǧ Ǧ */
    0x01e9, 499, /* ǩ Ǩ */
    0x01eb, 499, /* ǫ Ǫ */
    0x01ed, 499, /* ǭ Ǭ */
    0x01ef, 499, /* ǯ Ǯ */
    0x01f2, 499, /* Dz DZ */
    0x01f3, 498, /* dz DZ */
    0x01f5, 499, /* ǵ Ǵ */
    0x01fb, 499, /* ǻ Ǻ */
    0x01fd, 499, /* ǽ Ǽ */
    0x01ff, 499, /* ǿ Ǿ */
    0x0201, 499, /* ȁ Ȁ */
    0x0203, 499, /* ȃ Ȃ */
    0x0205, 499, /* ȅ Ȅ */
    0x0207, 499, /* ȇ Ȇ */
    0x0209, 499, /* ȉ Ȉ */
    0x020b, 499, /* ȋ Ȋ */
    0x020d, 499, /* ȍ Ȍ */
    0x020f, 499, /* ȏ Ȏ */
    0x0211, 499, /* ȑ Ȑ */
    0x0213, 499, /* ȓ Ȓ */
    0x0215, 499, /* ȕ Ȕ */
    0x0217, 499, /* ȗ Ȗ */
    0x0253, 290, /* ɓ Ɓ */
    0x0254, 294, /* ɔ Ɔ */
    0x025b, 297, /* ɛ Ɛ */
    0x0260, 295, /* ɠ Ɠ */
    0x0263, 293, /* ɣ Ɣ */
    0x0268, 291, /* ɨ Ɨ */
    0x0269, 289, /* ɩ Ɩ */
    0x026f, 289, /* ɯ Ɯ */
    0x0272, 287, /* ɲ Ɲ */
    0x0283, 282, /* ʃ Ʃ */
    0x0288, 282, /* ʈ Ʈ */
    0x0292, 281, /* ʒ Ʒ */
    0x03ac, 462, /* ά Ά */
    0x03cc, 436, /* ό Ό */
    0x03d0, 438, /* ϐ Β */
    0x03d1, 443, /* ϑ Θ */
    0x03d5, 453, /* ϕ Φ */
    0x03d6, 446, /* ϖ Π */
    0x03e3, 499, /* ϣ Ϣ */
    0x03e5, 499, /* ϥ Ϥ */
    0x03e7, 499, /* ϧ Ϧ */
    0x03e9, 499, /* ϩ Ϩ */
    0x03eb, 499, /* ϫ Ϫ */
    0x03ed, 499, /* ϭ Ϭ */
    0x03ef, 499, /* ϯ Ϯ */
    0x03f0, 414, /* ϰ Κ */
    0x03f1, 420, /* ϱ Ρ */
    0x0461, 499, /* ѡ Ѡ */
    0x0463, 499, /* ѣ Ѣ */
    0x0465, 499, /* ѥ Ѥ */
    0x0467, 499, /* ѧ Ѧ */
    0x0469, 499, /* ѩ Ѩ */
    0x046b, 499, /* ѫ Ѫ */
    0x046d, 499, /* ѭ Ѭ */
    0x046f, 499, /* ѯ Ѯ */
    0x0471, 499, /* ѱ Ѱ */
    0x0473, 499, /* ѳ Ѳ */
    0x0475, 499, /* ѵ Ѵ */
    0x0477, 499, /* ѷ Ѷ */
    0x0479, 499, /* ѹ Ѹ */
    0x047b, 499, /* ѻ Ѻ */
    0x047d, 499, /* ѽ Ѽ */
    0x047f, 499, /* ѿ Ѿ */
    0x0481, 499, /* ҁ Ҁ */
    0x0491, 499, /* ґ Ґ */
    0x0493, 499, /* ғ Ғ */
    0x0495, 499, /* ҕ Ҕ */
    0x0497, 499, /* җ Җ */
    0x0499, 499, /* ҙ Ҙ */
    0x049b, 499, /* қ Қ */
    0x049d, 499, /* ҝ Ҝ */
    0x049f, 499, /* ҟ Ҟ */
    0x04a1, 499, /* ҡ Ҡ */
    0x04a3, 499, /* ң Ң */
    0x04a5, 499, /* ҥ Ҥ */
    0x04a7, 499, /* ҧ Ҧ */
    0x04a9, 499, /* ҩ Ҩ */
    0x04ab, 499, /* ҫ Ҫ */
    0x04ad, 499, /* ҭ Ҭ */
    0x04af, 499, /* ү Ү */
    0x04b1, 499, /* ұ Ұ */
    0x04b3, 499, /* ҳ Ҳ */
    0x04b5, 499, /* ҵ Ҵ */
    0x04b7, 499, /* ҷ Ҷ */
    0x04b9, 499, /* ҹ Ҹ */
    0x04bb, 499, /* һ Һ */
    0x04bd, 499, /* ҽ Ҽ */
    0x04bf, 499, /* ҿ Ҿ */
    0x04c2, 499, /* ӂ Ӂ */
    0x04c4, 499, /* ӄ Ӄ */
    0x04c8, 499, /* ӈ Ӈ */
    0x04cc, 499, /* ӌ Ӌ */
    0x04d1, 499, /* ӑ Ӑ */
    0x04d3, 499, /* ӓ Ӓ */
    0x04d5, 499, /* ӕ Ӕ */
    0x04d7, 499, /* ӗ Ӗ */
    0x04d9, 499, /* ә Ә */
    0x04db, 499, /* ӛ Ӛ */
    0x04dd, 499, /* ӝ Ӝ */
    0x04df, 499, /* ӟ Ӟ */
    0x04e1, 499, /* ӡ Ӡ */
    0x04e3, 499, /* ӣ Ӣ */
    0x04e5, 499, /* ӥ Ӥ */
    0x04e7, 499, /* ӧ Ӧ */
    0x04e9, 499, /* ө Ө */
    0x04eb, 499, /* ӫ Ӫ */
    0x04ef, 499, /* ӯ Ӯ */
    0x04f1, 499, /* ӱ Ӱ */
    0x04f3, 499, /* ӳ Ӳ */
    0x04f5, 499, /* ӵ Ӵ */
    0x04f9, 499, /* ӹ Ӹ */
    0x1e01, 499, /* ḁ Ḁ */
    0x1e03, 499, /* ḃ Ḃ */
    0x1e05, 499, /* ḅ Ḅ */
    0x1e07, 499, /* ḇ Ḇ */
    0x1e09, 499, /* ḉ Ḉ */
    0x1e0b, 499, /* ḋ Ḋ */
    0x1e0d, 499, /* ḍ Ḍ */
    0x1e0f, 499, /* ḏ Ḏ */
    0x1e11, 499, /* ḑ Ḑ */
    0x1e13, 499, /* ḓ Ḓ */
    0x1e15, 499, /* ḕ Ḕ */
    0x1e17, 499, /* ḗ Ḗ */
    0x1e19, 499, /* ḙ Ḙ */
    0x1e1b, 499, /* ḛ Ḛ */
    0x1e1d, 499, /* ḝ Ḝ */
    0x1e1f, 499, /* ḟ Ḟ */
    0x1e21, 499, /* ḡ Ḡ */
    0x1e23, 499, /* ḣ Ḣ */
    0x1e25, 499, /* ḥ Ḥ */
    0x1e27, 499, /* ḧ Ḧ */
    0x1e29, 499, /* ḩ Ḩ */
    0x1e2b, 499, /* ḫ Ḫ */
    0x1e2d, 499, /* ḭ Ḭ */
    0x1e2f, 499, /* ḯ Ḯ */
    0x1e31, 499, /* ḱ Ḱ */
    0x1e33, 499, /* ḳ Ḳ */
    0x1e35, 499, /* ḵ Ḵ */
    0x1e37, 499, /* ḷ Ḷ */
    0x1e39, 499, /* ḹ Ḹ */
    0x1e3b, 499, /* ḻ Ḻ */
    0x1e3d, 499, /* ḽ Ḽ */
    0x1e3f, 499, /* ḿ Ḿ */
    0x1e41, 499, /* ṁ Ṁ */
    0x1e43, 499, /* ṃ Ṃ */
    0x1e45, 499, /* ṅ Ṅ */
    0x1e47, 499, /* ṇ Ṇ */
    0x1e49, 499, /* ṉ Ṉ */
    0x1e4b, 499, /* ṋ Ṋ */
    0x1e4d, 499, /* ṍ Ṍ */
    0x1e4f, 499, /* ṏ Ṏ */
    0x1e51, 499, /* ṑ Ṑ */
    0x1e53, 499, /* ṓ Ṓ */
    0x1e55, 499, /* ṕ Ṕ */
    0x1e57, 499, /* ṗ Ṗ */
    0x1e59, 499, /* ṙ Ṙ */
    0x1e5b, 499, /* ṛ Ṛ */
    0x1e5d, 499, /* ṝ Ṝ */
    0x1e5f, 499, /* ṟ Ṟ */
    0x1e61, 499, /* ṡ Ṡ */
    0x1e63, 499, /* ṣ Ṣ */
    0x1e65, 499, /* ṥ Ṥ */
    0x1e67, 499, /* ṧ Ṧ */
    0x1e69, 499, /* ṩ Ṩ */
    0x1e6b, 499, /* ṫ Ṫ */
    0x1e6d, 499, /* ṭ Ṭ */
    0x1e6f, 499, /* ṯ Ṯ */
    0x1e71, 499, /* ṱ Ṱ */
    0x1e73, 499, /* ṳ Ṳ */
    0x1e75, 499, /* ṵ Ṵ */
    0x1e77, 499, /* ṷ Ṷ */
    0x1e79, 499, /* ṹ Ṹ */
    0x1e7b, 499, /* ṻ Ṻ */
    0x1e7d, 499, /* ṽ Ṽ */
    0x1e7f, 499, /* ṿ Ṿ */
    0x1e81, 499, /* ẁ Ẁ */
    0x1e83, 499, /* ẃ Ẃ */
    0x1e85, 499, /* ẅ Ẅ */
    0x1e87, 499, /* ẇ Ẇ */
    0x1e89, 499, /* ẉ Ẉ */
    0x1e8b, 499, /* ẋ Ẋ */
    0x1e8d, 499, /* ẍ Ẍ */
    0x1e8f, 499, /* ẏ Ẏ */
    0x1e91, 499, /* ẑ Ẑ */
    0x1e93, 499, /* ẓ Ẓ */
    0x1e95, 499, /* ẕ Ẕ */
    0x1ea1, 499, /* ạ Ạ */
    0x1ea3, 499, /* ả Ả */
    0x1ea5, 499, /* ấ Ấ */
    0x1ea7, 499, /* ầ Ầ */
    0x1ea9, 499, /* ẩ Ẩ */
    0x1eab, 499, /* ẫ Ẫ */
    0x1ead, 499, /* ậ Ậ */
    0x1eaf, 499, /* ắ Ắ */
    0x1eb1, 499, /* ằ Ằ */
    0x1eb3, 499, /* ẳ Ẳ */
    0x1eb5, 499, /* ẵ Ẵ */
    0x1eb7, 499, /* ặ Ặ */
    0x1eb9, 499, /* ẹ Ẹ */
    0x1ebb, 499, /* ẻ Ẻ */
    0x1ebd, 499, /* ẽ Ẽ */
    0x1ebf, 499, /* ế Ế */
    0x1ec1, 499, /* ề Ề */
    0x1ec3, 499, /* ể Ể */
    0x1ec5, 499, /* ễ Ễ */
    0x1ec7, 499, /* ệ Ệ */
    0x1ec9, 499, /* ỉ Ỉ */
    0x1ecb, 499, /* ị Ị */
    0x1ecd, 499, /* ọ Ọ */
    0x1ecf, 499, /* ỏ Ỏ */
    0x1ed1, 499, /* ố Ố */
    0x1ed3, 499, /* ồ Ồ */
    0x1ed5, 499, /* ổ Ổ */
    0x1ed7, 499, /* ỗ Ỗ */
    0x1ed9, 499, /* ộ Ộ */
    0x1edb, 499, /* ớ Ớ */
    0x1edd, 499, /* ờ Ờ */
    0x1edf, 499, /* ở Ở */
    0x1ee1, 499, /* ỡ Ỡ */
    0x1ee3, 499, /* ợ Ợ */
    0x1ee5, 499, /* ụ Ụ */
    0x1ee7, 499, /* ủ Ủ */
    0x1ee9, 499, /* ứ Ứ */
    0x1eeb, 499, /* ừ Ừ */
    0x1eed, 499, /* ử Ử */
    0x1eef, 499, /* ữ Ữ */
    0x1ef1, 499, /* ự Ự */
    0x1ef3, 499, /* ỳ Ỳ */
    0x1ef5, 499, /* ỵ Ỵ */
    0x1ef7, 499, /* ỷ Ỷ */
    0x1ef9, 499, /* ỹ Ỹ */
    0x1f51, 508, /* ὑ Ὑ */
    0x1f53, 508, /* ὓ Ὓ */
    0x1f55, 508, /* ὕ Ὕ */
    0x1f57, 508, /* ὗ Ὗ */
    0x1fb3, 509, /* ᾳ ᾼ */
    0x1fc3, 509, /* ῃ ῌ */
    0x1fe5, 507, /* ῥ Ῥ */
    0x1ff3, 509, /* ῳ ῼ */
};

/*
 * upper case ranges
 *	3rd col is conversion excess 500
 */
static Rune __tolower2[] = {
    0x0041, 0x005a, 532, /* A-Z a-z */
    0x00c0, 0x00d6, 532, /* À-Ö à-ö */
    0x00d8, 0x00de, 532, /* Ø-Þ ø-þ */
    0x0189, 0x018a, 705, /* Ɖ-Ɗ ɖ-ɗ */
    0x018e, 0x018f, 702, /* Ǝ-Ə ɘ-ə */
    0x01b1, 0x01b2, 717, /* Ʊ-Ʋ ʊ-ʋ */
    0x0388, 0x038a, 537, /* Έ-Ί έ-ί */
    0x038e, 0x038f, 563, /* Ύ-Ώ ύ-ώ */
    0x0391, 0x03a1, 532, /* Α-Ρ α-ρ */
    0x03a3, 0x03ab, 532, /* Σ-Ϋ σ-ϋ */
    0x0401, 0x040c, 580, /* Ё-Ќ ё-ќ */
    0x040e, 0x040f, 580, /* Ў-Џ ў-џ */
    0x0410, 0x042f, 532, /* А-Я а-я */
    0x0531, 0x0556, 548, /* Ա-Ֆ ա-ֆ */
    0x10a0, 0x10c5, 548, /* Ⴀ-Ⴥ ა-ჵ */
    0x1f08, 0x1f0f, 492, /* Ἀ-Ἇ ἀ-ἇ */
    0x1f18, 0x1f1d, 492, /* Ἐ-Ἕ ἐ-ἕ */
    0x1f28, 0x1f2f, 492, /* Ἠ-Ἧ ἠ-ἧ */
    0x1f38, 0x1f3f, 492, /* Ἰ-Ἷ ἰ-ἷ */
    0x1f48, 0x1f4d, 492, /* Ὀ-Ὅ ὀ-ὅ */
    0x1f68, 0x1f6f, 492, /* Ὠ-Ὧ ὠ-ὧ */
    0x1f88, 0x1f8f, 492, /* ᾈ-ᾏ ᾀ-ᾇ */
    0x1f98, 0x1f9f, 492, /* ᾘ-ᾟ ᾐ-ᾗ */
    0x1fa8, 0x1faf, 492, /* ᾨ-ᾯ ᾠ-ᾧ */
    0x1fb8, 0x1fb9, 492, /* Ᾰ-Ᾱ ᾰ-ᾱ */
    0x1fba, 0x1fbb, 426, /* Ὰ-Ά ὰ-ά */
    0x1fc8, 0x1fcb, 414, /* Ὲ-Ή ὲ-ή */
    0x1fd8, 0x1fd9, 492, /* Ῐ-Ῑ ῐ-ῑ */
    0x1fda, 0x1fdb, 400, /* Ὶ-Ί ὶ-ί */
    0x1fe8, 0x1fe9, 492, /* Ῠ-Ῡ ῠ-ῡ */
    0x1fea, 0x1feb, 388, /* Ὺ-Ύ ὺ-ύ */
    0x1ff8, 0x1ff9, 372, /* Ὸ-Ό ὸ-ό */
    0x1ffa, 0x1ffb, 374, /* Ὼ-Ώ ὼ-ώ */
    0x2160, 0x216f, 516, /* Ⅰ-Ⅿ ⅰ-ⅿ */
    0x24b6, 0x24cf, 526, /* Ⓐ-Ⓩ ⓐ-ⓩ */
    0xff21, 0xff3a, 532, /* A-Z a-z */
};

/*
 * upper case singlets
 *	2nd col is conversion excess 500
 */
static Rune __tolower1[] = {
    0x0100, 501, /* Ā ā */
    0x0102, 501, /* Ă ă */
    0x0104, 501, /* Ą ą */
    0x0106, 501, /* Ć ć */
    0x0108, 501, /* Ĉ ĉ */
    0x010a, 501, /* Ċ ċ */
    0x010c, 501, /* Č č */
    0x010e, 501, /* Ď ď */
    0x0110, 501, /* Đ đ */
    0x0112, 501, /* Ē ē */
    0x0114, 501, /* Ĕ ĕ */
    0x0116, 501, /* Ė ė */
    0x0118, 501, /* Ę ę */
    0x011a, 501, /* Ě ě */
    0x011c, 501, /* Ĝ ĝ */
    0x011e, 501, /* Ğ ğ */
    0x0120, 501, /* Ġ ġ */
    0x0122, 501, /* Ģ ģ */
    0x0124, 501, /* Ĥ ĥ */
    0x0126, 501, /* Ħ ħ */
    0x0128, 501, /* Ĩ ĩ */
    0x012a, 501, /* Ī ī */
    0x012c, 501, /* Ĭ ĭ */
    0x012e, 501, /* Į į */
    0x0130, 301, /* İ i */
    0x0132, 501, /* IJ ij */
    0x0134, 501, /* Ĵ ĵ */
    0x0136, 501, /* Ķ ķ */
    0x0139, 501, /* Ĺ ĺ */
    0x013b, 501, /* Ļ ļ */
    0x013d, 501, /* Ľ ľ */
    0x013f, 501, /* Ŀ ŀ */
    0x0141, 501, /* Ł ł */
    0x0143, 501, /* Ń ń */
    0x0145, 501, /* Ņ ņ */
    0x0147, 501, /* Ň ň */
    0x014a, 501, /* Ŋ ŋ */
    0x014c, 501, /* Ō ō */
    0x014e, 501, /* Ŏ ŏ */
    0x0150, 501, /* Ő ő */
    0x0152, 501, /* Œ œ */
    0x0154, 501, /* Ŕ ŕ */
    0x0156, 501, /* Ŗ ŗ */
    0x0158, 501, /* Ř ř */
    0x015a, 501, /* Ś ś */
    0x015c, 501, /* Ŝ ŝ */
    0x015e, 501, /* Ş ş */
    0x0160, 501, /* Š š */
    0x0162, 501, /* Ţ ţ */
    0x0164, 501, /* Ť ť */
    0x0166, 501, /* Ŧ ŧ */
    0x0168, 501, /* Ũ ũ */
    0x016a, 501, /* Ū ū */
    0x016c, 501, /* Ŭ ŭ */
    0x016e, 501, /* Ů ů */
    0x0170, 501, /* Ű ű */
    0x0172, 501, /* Ų ų */
    0x0174, 501, /* Ŵ ŵ */
    0x0176, 501, /* Ŷ ŷ */
    0x0178, 379, /* Ÿ ÿ */
    0x0179, 501, /* Ź ź */
    0x017b, 501, /* Ż ż */
    0x017d, 501, /* Ž ž */
    0x0181, 710, /* Ɓ ɓ */
    0x0182, 501, /* Ƃ ƃ */
    0x0184, 501, /* Ƅ ƅ */
    0x0186, 706, /* Ɔ ɔ */
    0x0187, 501, /* Ƈ ƈ */
    0x018b, 501, /* Ƌ ƌ */
    0x0190, 703, /* Ɛ ɛ */
    0x0191, 501, /* Ƒ ƒ */
    0x0193, 705, /* Ɠ ɠ */
    0x0194, 707, /* Ɣ ɣ */
    0x0196, 711, /* Ɩ ɩ */
    0x0197, 709, /* Ɨ ɨ */
    0x0198, 501, /* Ƙ ƙ */
    0x019c, 711, /* Ɯ ɯ */
    0x019d, 713, /* Ɲ ɲ */
    0x01a0, 501, /* Ơ ơ */
    0x01a2, 501, /* Ƣ ƣ */
    0x01a4, 501, /* Ƥ ƥ */
    0x01a7, 501, /* Ƨ ƨ */
    0x01a9, 718, /* Ʃ ʃ */
    0x01ac, 501, /* Ƭ ƭ */
    0x01ae, 718, /* Ʈ ʈ */
    0x01af, 501, /* Ư ư */
    0x01b3, 501, /* Ƴ ƴ */
    0x01b5, 501, /* Ƶ ƶ */
    0x01b7, 719, /* Ʒ ʒ */
    0x01b8, 501, /* Ƹ ƹ */
    0x01bc, 501, /* Ƽ ƽ */
    0x01c4, 502, /* DŽ dž */
    0x01c5, 501, /* Dž dž */
    0x01c7, 502, /* LJ lj */
    0x01c8, 501, /* Lj lj */
    0x01ca, 502, /* NJ nj */
    0x01cb, 501, /* Nj nj */
    0x01cd, 501, /* Ǎ ǎ */
    0x01cf, 501, /* Ǐ ǐ */
    0x01d1, 501, /* Ǒ ǒ */
    0x01d3, 501, /* Ǔ ǔ */
    0x01d5, 501, /* Ǖ ǖ */
    0x01d7, 501, /* Ǘ ǘ */
    0x01d9, 501, /* Ǚ ǚ */
    0x01db, 501, /* Ǜ ǜ */
    0x01de, 501, /* Ǟ ǟ */
    0x01e0, 501, /* Ǡ ǡ */
    0x01e2, 501, /* Ǣ ǣ */
    0x01e4, 501, /* Ǥ ǥ */
    0x01e6, 501, /* Ǧ ǧ */
    0x01e8, 501, /* Ǩ ǩ */
    0x01ea, 501, /* Ǫ ǫ */
    0x01ec, 501, /* Ǭ ǭ */
    0x01ee, 501, /* Ǯ ǯ */
    0x01f1, 502, /* DZ dz */
    0x01f2, 501, /* Dz dz */
    0x01f4, 501, /* Ǵ ǵ */
    0x01fa, 501, /* Ǻ ǻ */
    0x01fc, 501, /* Ǽ ǽ */
    0x01fe, 501, /* Ǿ ǿ */
    0x0200, 501, /* Ȁ ȁ */
    0x0202, 501, /* Ȃ ȃ */
    0x0204, 501, /* Ȅ ȅ */
    0x0206, 501, /* Ȇ ȇ */
    0x0208, 501, /* Ȉ ȉ */
    0x020a, 501, /* Ȋ ȋ */
    0x020c, 501, /* Ȍ ȍ */
    0x020e, 501, /* Ȏ ȏ */
    0x0210, 501, /* Ȑ ȑ */
    0x0212, 501, /* Ȓ ȓ */
    0x0214, 501, /* Ȕ ȕ */
    0x0216, 501, /* Ȗ ȗ */
    0x0386, 538, /* Ά ά */
    0x038c, 564, /* Ό ό */
    0x03e2, 501, /* Ϣ ϣ */
    0x03e4, 501, /* Ϥ ϥ */
    0x03e6, 501, /* Ϧ ϧ */
    0x03e8, 501, /* Ϩ ϩ */
    0x03ea, 501, /* Ϫ ϫ */
    0x03ec, 501, /* Ϭ ϭ */
    0x03ee, 501, /* Ϯ ϯ */
    0x0460, 501, /* Ѡ ѡ */
    0x0462, 501, /* Ѣ ѣ */
    0x0464, 501, /* Ѥ ѥ */
    0x0466, 501, /* Ѧ ѧ */
    0x0468, 501, /* Ѩ ѩ */
    0x046a, 501, /* Ѫ ѫ */
    0x046c, 501, /* Ѭ ѭ */
    0x046e, 501, /* Ѯ ѯ */
    0x0470, 501, /* Ѱ ѱ */
    0x0472, 501, /* Ѳ ѳ */
    0x0474, 501, /* Ѵ ѵ */
    0x0476, 501, /* Ѷ ѷ */
    0x0478, 501, /* Ѹ ѹ */
    0x047a, 501, /* Ѻ ѻ */
    0x047c, 501, /* Ѽ ѽ */
    0x047e, 501, /* Ѿ ѿ */
    0x0480, 501, /* Ҁ ҁ */
    0x0490, 501, /* Ґ ґ */
    0x0492, 501, /* Ғ ғ */
    0x0494, 501, /* Ҕ ҕ */
    0x0496, 501, /* Җ җ */
    0x0498, 501, /* Ҙ ҙ */
    0x049a, 501, /* Қ қ */
    0x049c, 501, /* Ҝ ҝ */
    0x049e, 501, /* Ҟ ҟ */
    0x04a0, 501, /* Ҡ ҡ */
    0x04a2, 501, /* Ң ң */
    0x04a4, 501, /* Ҥ ҥ */
    0x04a6, 501, /* Ҧ ҧ */
    0x04a8, 501, /* Ҩ ҩ */
    0x04aa, 501, /* Ҫ ҫ */
    0x04ac, 501, /* Ҭ ҭ */
    0x04ae, 501, /* Ү ү */
    0x04b0, 501, /* Ұ ұ */
    0x04b2, 501, /* Ҳ ҳ */
    0x04b4, 501, /* Ҵ ҵ */
    0x04b6, 501, /* Ҷ ҷ */
    0x04b8, 501, /* Ҹ ҹ */
    0x04ba, 501, /* Һ һ */
    0x04bc, 501, /* Ҽ ҽ */
    0x04be, 501, /* Ҿ ҿ */
    0x04c1, 501, /* Ӂ ӂ */
    0x04c3, 501, /* Ӄ ӄ */
    0x04c7, 501, /* Ӈ ӈ */
    0x04cb, 501, /* Ӌ ӌ */
    0x04d0, 501, /* Ӑ ӑ */
    0x04d2, 501, /* Ӓ ӓ */
    0x04d4, 501, /* Ӕ ӕ */
    0x04d6, 501, /* Ӗ ӗ */
    0x04d8, 501, /* Ә ә */
    0x04da, 501, /* Ӛ ӛ */
    0x04dc, 501, /* Ӝ ӝ */
    0x04de, 501, /* Ӟ ӟ */
    0x04e0, 501, /* Ӡ ӡ */
    0x04e2, 501, /* Ӣ ӣ */
    0x04e4, 501, /* Ӥ ӥ */
    0x04e6, 501, /* Ӧ ӧ */
    0x04e8, 501, /* Ө ө */
    0x04ea, 501, /* Ӫ ӫ */
    0x04ee, 501, /* Ӯ ӯ */
    0x04f0, 501, /* Ӱ ӱ */
    0x04f2, 501, /* Ӳ ӳ */
    0x04f4, 501, /* Ӵ ӵ */
    0x04f8, 501, /* Ӹ ӹ */
    0x1e00, 501, /* Ḁ ḁ */
    0x1e02, 501, /* Ḃ ḃ */
    0x1e04, 501, /* Ḅ ḅ */
    0x1e06, 501, /* Ḇ ḇ */
    0x1e08, 501, /* Ḉ ḉ */
    0x1e0a, 501, /* Ḋ ḋ */
    0x1e0c, 501, /* Ḍ ḍ */
    0x1e0e, 501, /* Ḏ ḏ */
    0x1e10, 501, /* Ḑ ḑ */
    0x1e12, 501, /* Ḓ ḓ */
    0x1e14, 501, /* Ḕ ḕ */
    0x1e16, 501, /* Ḗ ḗ */
    0x1e18, 501, /* Ḙ ḙ */
    0x1e1a, 501, /* Ḛ ḛ */
    0x1e1c, 501, /* Ḝ ḝ */
    0x1e1e, 501, /* Ḟ ḟ */
    0x1e20, 501, /* Ḡ ḡ */
    0x1e22, 501, /* Ḣ ḣ */
    0x1e24, 501, /* Ḥ ḥ */
    0x1e26, 501, /* Ḧ ḧ */
    0x1e28, 501, /* Ḩ ḩ */
    0x1e2a, 501, /* Ḫ ḫ */
    0x1e2c, 501, /* Ḭ ḭ */
    0x1e2e, 501, /* Ḯ ḯ */
    0x1e30, 501, /* Ḱ ḱ */
    0x1e32, 501, /* Ḳ ḳ */
    0x1e34, 501, /* Ḵ ḵ */
    0x1e36, 501, /* Ḷ ḷ */
    0x1e38, 501, /* Ḹ ḹ */
    0x1e3a, 501, /* Ḻ ḻ */
    0x1e3c, 501, /* Ḽ ḽ */
    0x1e3e, 501, /* Ḿ ḿ */
    0x1e40, 501, /* Ṁ ṁ */
    0x1e42, 501, /* Ṃ ṃ */
    0x1e44, 501, /* Ṅ ṅ */
    0x1e46, 501, /* Ṇ ṇ */
    0x1e48, 501, /* Ṉ ṉ */
    0x1e4a, 501, /* Ṋ ṋ */
    0x1e4c, 501, /* Ṍ ṍ */
    0x1e4e, 501, /* Ṏ ṏ */
    0x1e50, 501, /* Ṑ ṑ */
    0x1e52, 501, /* Ṓ ṓ */
    0x1e54, 501, /* Ṕ ṕ */
    0x1e56, 501, /* Ṗ ṗ */
    0x1e58, 501, /* Ṙ ṙ */
    0x1e5a, 501, /* Ṛ ṛ */
    0x1e5c, 501, /* Ṝ ṝ */
    0x1e5e, 501, /* Ṟ ṟ */
    0x1e60, 501, /* Ṡ ṡ */
    0x1e62, 501, /* Ṣ ṣ */
    0x1e64, 501, /* Ṥ ṥ */
    0x1e66, 501, /* Ṧ ṧ */
    0x1e68, 501, /* Ṩ ṩ */
    0x1e6a, 501, /* Ṫ ṫ */
    0x1e6c, 501, /* Ṭ ṭ */
    0x1e6e, 501, /* Ṯ ṯ */
    0x1e70, 501, /* Ṱ ṱ */
    0x1e72, 501, /* Ṳ ṳ */
    0x1e74, 501, /* Ṵ ṵ */
    0x1e76, 501, /* Ṷ ṷ */
    0x1e78, 501, /* Ṹ ṹ */
    0x1e7a, 501, /* Ṻ ṻ */
    0x1e7c, 501, /* Ṽ ṽ */
    0x1e7e, 501, /* Ṿ ṿ */
    0x1e80, 501, /* Ẁ ẁ */
    0x1e82, 501, /* Ẃ ẃ */
    0x1e84, 501, /* Ẅ ẅ */
    0x1e86, 501, /* Ẇ ẇ */
    0x1e88, 501, /* Ẉ ẉ */
    0x1e8a, 501, /* Ẋ ẋ */
    0x1e8c, 501, /* Ẍ ẍ */
    0x1e8e, 501, /* Ẏ ẏ */
    0x1e90, 501, /* Ẑ ẑ */
    0x1e92, 501, /* Ẓ ẓ */
    0x1e94, 501, /* Ẕ ẕ */
    0x1ea0, 501, /* Ạ ạ */
    0x1ea2, 501, /* Ả ả */
    0x1ea4, 501, /* Ấ ấ */
    0x1ea6, 501, /* Ầ ầ */
    0x1ea8, 501, /* Ẩ ẩ */
    0x1eaa, 501, /* Ẫ ẫ */
    0x1eac, 501, /* Ậ ậ */
    0x1eae, 501, /* Ắ ắ */
    0x1eb0, 501, /* Ằ ằ */
    0x1eb2, 501, /* Ẳ ẳ */
    0x1eb4, 501, /* Ẵ ẵ */
    0x1eb6, 501, /* Ặ ặ */
    0x1eb8, 501, /* Ẹ ẹ */
    0x1eba, 501, /* Ẻ ẻ */
    0x1ebc, 501, /* Ẽ ẽ */
    0x1ebe, 501, /* Ế ế */
    0x1ec0, 501, /* Ề ề */
    0x1ec2, 501, /* Ể ể */
    0x1ec4, 501, /* Ễ ễ */
    0x1ec6, 501, /* Ệ ệ */
    0x1ec8, 501, /* Ỉ ỉ */
    0x1eca, 501, /* Ị ị */
    0x1ecc, 501, /* Ọ ọ */
    0x1ece, 501, /* Ỏ ỏ */
    0x1ed0, 501, /* Ố ố */
    0x1ed2, 501, /* Ồ ồ */
    0x1ed4, 501, /* Ổ ổ */
    0x1ed6, 501, /* Ỗ ỗ */
    0x1ed8, 501, /* Ộ ộ */
    0x1eda, 501, /* Ớ ớ */
    0x1edc, 501, /* Ờ ờ */
    0x1ede, 501, /* Ở ở */
    0x1ee0, 501, /* Ỡ ỡ */
    0x1ee2, 501, /* Ợ ợ */
    0x1ee4, 501, /* Ụ ụ */
    0x1ee6, 501, /* Ủ ủ */
    0x1ee8, 501, /* Ứ ứ */
    0x1eea, 501, /* Ừ ừ */
    0x1eec, 501, /* Ử ử */
    0x1eee, 501, /* Ữ ữ */
    0x1ef0, 501, /* Ự ự */
    0x1ef2, 501, /* Ỳ ỳ */
    0x1ef4, 501, /* Ỵ ỵ */
    0x1ef6, 501, /* Ỷ ỷ */
    0x1ef8, 501, /* Ỹ ỹ */
    0x1f59, 492, /* Ὑ ὑ */
    0x1f5b, 492, /* Ὓ ὓ */
    0x1f5d, 492, /* Ὕ ὕ */
    0x1f5f, 492, /* Ὗ ὗ */
    0x1fbc, 491, /* ᾼ ᾳ */
    0x1fcc, 491, /* ῌ ῃ */
    0x1fec, 493, /* Ῥ ῥ */
    0x1ffc, 491, /* ῼ ῳ */
};

static Rune *rune_bsearch(Rune c, Rune *t, int n, int ne) {
  Rune *p;
  int m;

  while (n > 1) {
    m = n / 2;
    p = t + m * ne;
    if (c >= p[0]) {
      t = p;
      n = n - m;
    } else
      n = m;
  }
  if (n && c >= t[0]) return t;
  return 0;
}

Rune tolowerrune(Rune c) {
  Rune *p;

  p = rune_bsearch(c, __tolower2, nelem(__tolower2) / 3, 3);
  if (p && c >= p[0] && c <= p[1]) return c + p[2] - 500;
  p = rune_bsearch(c, __tolower1, nelem(__tolower1) / 2, 2);
  if (p && c == p[0]) return c + p[1] - 500;
  return c;
}

Rune toupperrune(Rune c) {
  Rune *p;

  p = rune_bsearch(c, __toupper2, nelem(__toupper2) / 3, 3);
  if (p && c >= p[0] && c <= p[1]) return c + p[2] - 500;
  p = rune_bsearch(c, __toupper1, nelem(__toupper1) / 2, 2);
  if (p && c == p[0]) return c + p[1] - 500;
  return c;
}

int islowerrune(Rune c) {
  Rune *p;

  p = rune_bsearch(c, __toupper2, nelem(__toupper2) / 3, 3);
  if (p && c >= p[0] && c <= p[1]) return 1;
  p = rune_bsearch(c, __toupper1, nelem(__toupper1) / 2, 2);
  if (p && c == p[0]) return 1;
  return 0;
}

int isupperrune(Rune c) {
  Rune *p;

  p = rune_bsearch(c, __tolower2, nelem(__tolower2) / 3, 3);
  if (p && c >= p[0] && c <= p[1]) return 1;
  p = rune_bsearch(c, __tolower1, nelem(__tolower1) / 2, 2);
  if (p && c == p[0]) return 1;
  return 0;
}

int isdigitrune(Rune c) {
  return c >= '0' && c <= '9';
}

int isnewline(Rune c) {
  return c == 0xA || c == 0xD || c == 0x2028 || c == 0x2029;
}

int iswordchar(Rune c) {
  return c == '_' || isdigitrune(c) || (c >= 'a' && c <= 'z') ||
         (c >= 'A' && c <= 'Z');
}

int isalpharune(Rune c) {
  Rune *p;

  if (isupperrune(c) || islowerrune(c)) return 1;
  p = rune_bsearch(c, __alpha2, nelem(__alpha2) / 2, 2);
  if (p && c >= p[0] && c <= p[1]) return 1;
  p = rune_bsearch(c, __alpha1, nelem(__alpha1), 1);
  if (p && c == p[0]) return 1;
  return 0;
}

int isspacerune(Rune c) {
  Rune *p;

  p = rune_bsearch(c, __space2, nelem(__space2) / 2, 2);
  if (p && c >= p[0] && c <= p[1]) return 1;
  return 0;
}

#else /* CS_ENABLE_UTF8 */

int chartorune(Rune *rune, const char *str) {
  *rune = *(uchar *) str;
  return 1;
}

int fullrune(const char *str, int n) {
  (void) str;
  return (n <= 0) ? 0 : 1;
}

int isdigitrune(Rune c) {
  return isdigit(c);
}

int isnewline(Rune c) {
  return c == 0xA || c == 0xD || c == 0x2028 || c == 0x2029;
}

int iswordchar(Rune c) {
  return c == '_' || isdigitrune(c) || (c >= 'a' && c <= 'z') ||
         (c >= 'A' && c <= 'Z');
}

int isalpharune(Rune c) {
  return isalpha(c);
}
int islowerrune(Rune c) {
  return islower(c);
}
int isspacerune(Rune c) {
  return isspace(c);
}
int isupperrune(Rune c) {
  return isupper(c);
}

int runetochar(char *str, Rune *rune) {
  str[0] = (char) *rune;
  return 1;
}

Rune tolowerrune(Rune c) {
  return tolower(c);
}
Rune toupperrune(Rune c) {
  return toupper(c);
}
int utfnlen(const char *s, long m) {
  (void) s;
  return (int) c_strnlen(s, (size_t) m);
}

const char *utfnshift(const char *s, long m) {
  return s + m;
}

#endif /* CS_ENABLE_UTF8 */

#endif /* EXCLUDE_COMMON */
#ifdef V7_MODULE_LINES
#line 1 "common/base64.c"
#endif
/*
 * Copyright (c) 2014 Cesanta Software Limited
 * All rights reserved
 */

#ifndef EXCLUDE_COMMON

/* Amalgamated: #include "common/base64.h" */
#include <string.h>

/* ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/ */

#define NUM_UPPERCASES ('Z' - 'A' + 1)
#define NUM_LETTERS (NUM_UPPERCASES * 2)
#define NUM_DIGITS ('9' - '0' + 1)

/*
 * Emit a base64 code char.
 *
 * Doesn't use memory, thus it's safe to use to safely dump memory in crashdumps
 */
static void cs_base64_emit_code(struct cs_base64_ctx *ctx, int v) {
  if (v < NUM_UPPERCASES) {
    ctx->b64_putc(v + 'A', ctx->user_data);
  } else if (v < (NUM_LETTERS)) {
    ctx->b64_putc(v - NUM_UPPERCASES + 'a', ctx->user_data);
  } else if (v < (NUM_LETTERS + NUM_DIGITS)) {
    ctx->b64_putc(v - NUM_LETTERS + '0', ctx->user_data);
  } else {
    ctx->b64_putc(v - NUM_LETTERS - NUM_DIGITS == 0 ? '+' : '/',
                  ctx->user_data);
  }
}

static void cs_base64_emit_chunk(struct cs_base64_ctx *ctx) {
  int a, b, c;

  a = ctx->chunk[0];
  b = ctx->chunk[1];
  c = ctx->chunk[2];

  cs_base64_emit_code(ctx, a >> 2);
  cs_base64_emit_code(ctx, ((a & 3) << 4) | (b >> 4));
  if (ctx->chunk_size > 1) {
    cs_base64_emit_code(ctx, (b & 15) << 2 | (c >> 6));
  }
  if (ctx->chunk_size > 2) {
    cs_base64_emit_code(ctx, c & 63);
  }
}

void cs_base64_init(struct cs_base64_ctx *ctx, cs_base64_putc_t b64_putc,
                    void *user_data) {
  ctx->chunk_size = 0;
  ctx->b64_putc = b64_putc;
  ctx->user_data = user_data;
}

void cs_base64_update(struct cs_base64_ctx *ctx, const char *str, size_t len) {
  const unsigned char *src = (const unsigned char *) str;
  size_t i;
  for (i = 0; i < len; i++) {
    ctx->chunk[ctx->chunk_size++] = src[i];
    if (ctx->chunk_size == 3) {
      cs_base64_emit_chunk(ctx);
      ctx->chunk_size = 0;
    }
  }
}

void cs_base64_finish(struct cs_base64_ctx *ctx) {
  if (ctx->chunk_size > 0) {
    int i;
    memset(&ctx->chunk[ctx->chunk_size], 0, 3 - ctx->chunk_size);
    cs_base64_emit_chunk(ctx);
    for (i = 0; i < (3 - ctx->chunk_size); i++) {
      ctx->b64_putc('=', ctx->user_data);
    }
  }
}

#define BASE64_ENCODE_BODY                                                \
  static const char *b64 =                                                \
      "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; \
  int i, j, a, b, c;                                                      \
                                                                          \
  for (i = j = 0; i < src_len; i += 3) {                                  \
    a = src[i];                                                           \
    b = i + 1 >= src_len ? 0 : src[i + 1];                                \
    c = i + 2 >= src_len ? 0 : src[i + 2];                                \
                                                                          \
    BASE64_OUT(b64[a >> 2]);                                              \
    BASE64_OUT(b64[((a & 3) << 4) | (b >> 4)]);                           \
    if (i + 1 < src_len) {                                                \
      BASE64_OUT(b64[(b & 15) << 2 | (c >> 6)]);                          \
    }                                                                     \
    if (i + 2 < src_len) {                                                \
      BASE64_OUT(b64[c & 63]);                                            \
    }                                                                     \
  }                                                                       \
                                                                          \
  while (j % 4 != 0) {                                                    \
    BASE64_OUT('=');                                                      \
  }                                                                       \
  BASE64_FLUSH()

#define BASE64_OUT(ch) \
  do {                 \
    dst[j++] = (ch);   \
  } while (0)

#define BASE64_FLUSH() \
  do {                 \
    dst[j++] = '\0';   \
  } while (0)

void cs_base64_encode(const unsigned char *src, int src_len, char *dst) {
  BASE64_ENCODE_BODY;
}

#undef BASE64_OUT
#undef BASE64_FLUSH

#ifndef CS_DISABLE_STDIO
#define BASE64_OUT(ch)      \
  do {                      \
    fprintf(f, "%c", (ch)); \
    j++;                    \
  } while (0)

#define BASE64_FLUSH()

void cs_fprint_base64(FILE *f, const unsigned char *src, int src_len) {
  BASE64_ENCODE_BODY;
}

#undef BASE64_OUT
#undef BASE64_FLUSH
#endif /* !CS_DISABLE_STDIO */

/* Convert one byte of encoded base64 input stream to 6-bit chunk */
static unsigned char from_b64(unsigned char ch) {
  /* Inverse lookup map */
  static const unsigned char tab[128] = {
      255, 255, 255, 255,
      255, 255, 255, 255, /*  0 */
      255, 255, 255, 255,
      255, 255, 255, 255, /*  8 */
      255, 255, 255, 255,
      255, 255, 255, 255, /*  16 */
      255, 255, 255, 255,
      255, 255, 255, 255, /*  24 */
      255, 255, 255, 255,
      255, 255, 255, 255, /*  32 */
      255, 255, 255, 62,
      255, 255, 255, 63, /*  40 */
      52,  53,  54,  55,
      56,  57,  58,  59, /*  48 */
      60,  61,  255, 255,
      255, 200, 255, 255, /*  56   '=' is 200, on index 61 */
      255, 0,   1,   2,
      3,   4,   5,   6, /*  64 */
      7,   8,   9,   10,
      11,  12,  13,  14, /*  72 */
      15,  16,  17,  18,
      19,  20,  21,  22, /*  80 */
      23,  24,  25,  255,
      255, 255, 255, 255, /*  88 */
      255, 26,  27,  28,
      29,  30,  31,  32, /*  96 */
      33,  34,  35,  36,
      37,  38,  39,  40, /*  104 */
      41,  42,  43,  44,
      45,  46,  47,  48, /*  112 */
      49,  50,  51,  255,
      255, 255, 255, 255, /*  120 */
  };
  return tab[ch & 127];
}

int cs_base64_decode(const unsigned char *s, int len, char *dst) {
  unsigned char a, b, c, d;
  int orig_len = len;
  while (len >= 4 && (a = from_b64(s[0])) != 255 &&
         (b = from_b64(s[1])) != 255 && (c = from_b64(s[2])) != 255 &&
         (d = from_b64(s[3])) != 255) {
    s += 4;
    len -= 4;
    if (a == 200 || b == 200) break; /* '=' can't be there */
    *dst++ = a << 2 | b >> 4;
    if (c == 200) break;
    *dst++ = b << 4 | c >> 2;
    if (d == 200) break;
    *dst++ = c << 6 | d;
  }
  *dst = 0;
  return orig_len - len;
}

#endif /* EXCLUDE_COMMON */
#ifdef V7_MODULE_LINES
#line 1 "common/md5.c"
#endif
/*
 * This code implements the MD5 message-digest algorithm.
 * The algorithm is due to Ron Rivest.  This code was
 * written by Colin Plumb in 1993, no copyright is claimed.
 * This code is in the public domain; do with it what you wish.
 *
 * Equivalent code is available from RSA Data Security, Inc.
 * This code has been tested against that, and is equivalent,
 * except that you don't need to include two pages of legalese
 * with every copy.
 *
 * To compute the message digest of a chunk of bytes, declare an
 * MD5Context structure, pass it to MD5Init, call MD5Update as
 * needed on buffers full of bytes, and then call MD5Final, which
 * will fill a supplied 16-byte array with the digest.
 */

#if !defined(DISABLE_MD5) && !defined(EXCLUDE_COMMON)

/* Amalgamated: #include "common/md5.h" */

#ifndef CS_ENABLE_NATIVE_MD5
static void byteReverse(unsigned char *buf, unsigned longs) {
/* Forrest: MD5 expect LITTLE_ENDIAN, swap if BIG_ENDIAN */
#if BYTE_ORDER == BIG_ENDIAN
  do {
    uint32_t t = (uint32_t)((unsigned) buf[3] << 8 | buf[2]) << 16 |
                 ((unsigned) buf[1] << 8 | buf[0]);
    *(uint32_t *) buf = t;
    buf += 4;
  } while (--longs);
#else
  (void) buf;
  (void) longs;
#endif
}

#define F1(x, y, z) (z ^ (x & (y ^ z)))
#define F2(x, y, z) F1(z, x, y)
#define F3(x, y, z) (x ^ y ^ z)
#define F4(x, y, z) (y ^ (x | ~z))

#define MD5STEP(f, w, x, y, z, data, s) \
  (w += f(x, y, z) + data, w = w << s | w >> (32 - s), w += x)

/*
 * Start MD5 accumulation.  Set bit count to 0 and buffer to mysterious
 * initialization constants.
 */
void MD5_Init(MD5_CTX *ctx) {
  ctx->buf[0] = 0x67452301;
  ctx->buf[1] = 0xefcdab89;
  ctx->buf[2] = 0x98badcfe;
  ctx->buf[3] = 0x10325476;

  ctx->bits[0] = 0;
  ctx->bits[1] = 0;
}

static void MD5Transform(uint32_t buf[4], uint32_t const in[16]) {
  register uint32_t a, b, c, d;

  a = buf[0];
  b = buf[1];
  c = buf[2];
  d = buf[3];

  MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7);
  MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12);
  MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17);
  MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22);
  MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7);
  MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12);
  MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17);
  MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22);
  MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7);
  MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12);
  MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17);
  MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22);
  MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7);
  MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12);
  MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17);
  MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22);

  MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5);
  MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9);
  MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14);
  MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20);
  MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5);
  MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9);
  MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14);
  MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20);
  MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5);
  MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9);
  MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14);
  MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20);
  MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5);
  MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9);
  MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14);
  MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20);

  MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4);
  MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11);
  MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16);
  MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23);
  MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4);
  MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11);
  MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16);
  MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23);
  MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4);
  MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11);
  MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16);
  MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23);
  MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4);
  MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11);
  MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16);
  MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23);

  MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6);
  MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10);
  MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15);
  MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21);
  MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6);
  MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10);
  MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15);
  MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21);
  MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6);
  MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10);
  MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15);
  MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21);
  MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6);
  MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10);
  MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15);
  MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21);

  buf[0] += a;
  buf[1] += b;
  buf[2] += c;
  buf[3] += d;
}

void MD5_Update(MD5_CTX *ctx, const unsigned char *buf, size_t len) {
  uint32_t t;

  t = ctx->bits[0];
  if ((ctx->bits[0] = t + ((uint32_t) len << 3)) < t) ctx->bits[1]++;
  ctx->bits[1] += (uint32_t) len >> 29;

  t = (t >> 3) & 0x3f;

  if (t) {
    unsigned char *p = (unsigned char *) ctx->in + t;

    t = 64 - t;
    if (len < t) {
      memcpy(p, buf, len);
      return;
    }
    memcpy(p, buf, t);
    byteReverse(ctx->in, 16);
    MD5Transform(ctx->buf, (uint32_t *) ctx->in);
    buf += t;
    len -= t;
  }

  while (len >= 64) {
    memcpy(ctx->in, buf, 64);
    byteReverse(ctx->in, 16);
    MD5Transform(ctx->buf, (uint32_t *) ctx->in);
    buf += 64;
    len -= 64;
  }

  memcpy(ctx->in, buf, len);
}

void MD5_Final(unsigned char digest[16], MD5_CTX *ctx) {
  unsigned count;
  unsigned char *p;
  uint32_t *a;

  count = (ctx->bits[0] >> 3) & 0x3F;

  p = ctx->in + count;
  *p++ = 0x80;
  count = 64 - 1 - count;
  if (count < 8) {
    memset(p, 0, count);
    byteReverse(ctx->in, 16);
    MD5Transform(ctx->buf, (uint32_t *) ctx->in);
    memset(ctx->in, 0, 56);
  } else {
    memset(p, 0, count - 8);
  }
  byteReverse(ctx->in, 14);

  a = (uint32_t *) ctx->in;
  a[14] = ctx->bits[0];
  a[15] = ctx->bits[1];

  MD5Transform(ctx->buf, (uint32_t *) ctx->in);
  byteReverse((unsigned char *) ctx->buf, 4);
  memcpy(digest, ctx->buf, 16);
  memset((char *) ctx, 0, sizeof(*ctx));
}
#endif /* CS_ENABLE_NATIVE_MD5 */

/*
 * Stringify binary data. Output buffer size must be 2 * size_of_input + 1
 * because each byte of input takes 2 bytes in string representation
 * plus 1 byte for the terminating \0 character.
 */
void cs_to_hex(char *to, const unsigned char *p, size_t len) {
  static const char *hex = "0123456789abcdef";

  for (; len--; p++) {
    *to++ = hex[p[0] >> 4];
    *to++ = hex[p[0] & 0x0f];
  }
  *to = '\0';
}

char *cs_md5(char buf[33], ...) {
  unsigned char hash[16];
  const unsigned char *p;
  va_list ap;
  MD5_CTX ctx;

  MD5_Init(&ctx);

  va_start(ap, buf);
  while ((p = va_arg(ap, const unsigned char *) ) != NULL) {
    size_t len = va_arg(ap, size_t);
    MD5_Update(&ctx, p, len);
  }
  va_end(ap);

  MD5_Final(hash, &ctx);
  cs_to_hex(buf, hash, sizeof(hash));

  return buf;
}

#endif /* EXCLUDE_COMMON */
#ifdef V7_MODULE_LINES
#line 1 "common/sha1.c"
#endif
/* Copyright(c) By Steve Reid <steve@edmweb.com> */
/* 100% Public Domain */

#if !defined(DISABLE_SHA1) && !defined(EXCLUDE_COMMON)

/* Amalgamated: #include "common/sha1.h" */

#define SHA1HANDSOFF
#if defined(__sun)
/* Amalgamated: #include "common/solarisfixes.h" */
#endif

union char64long16 {
  unsigned char c[64];
  uint32_t l[16];
};

#define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits))))

static uint32_t blk0(union char64long16 *block, int i) {
/* Forrest: SHA expect BIG_ENDIAN, swap if LITTLE_ENDIAN */
#if BYTE_ORDER == LITTLE_ENDIAN
  block->l[i] =
      (rol(block->l[i], 24) & 0xFF00FF00) | (rol(block->l[i], 8) & 0x00FF00FF);
#endif
  return block->l[i];
}

/* Avoid redefine warning (ARM /usr/include/sys/ucontext.h define R0~R4) */
#undef blk
#undef R0
#undef R1
#undef R2
#undef R3
#undef R4

#define blk(i)                                                               \
  (block->l[i & 15] = rol(block->l[(i + 13) & 15] ^ block->l[(i + 8) & 15] ^ \
                              block->l[(i + 2) & 15] ^ block->l[i & 15],     \
                          1))
#define R0(v, w, x, y, z, i)                                          \
  z += ((w & (x ^ y)) ^ y) + blk0(block, i) + 0x5A827999 + rol(v, 5); \
  w = rol(w, 30);
#define R1(v, w, x, y, z, i)                                  \
  z += ((w & (x ^ y)) ^ y) + blk(i) + 0x5A827999 + rol(v, 5); \
  w = rol(w, 30);
#define R2(v, w, x, y, z, i)                          \
  z += (w ^ x ^ y) + blk(i) + 0x6ED9EBA1 + rol(v, 5); \
  w = rol(w, 30);
#define R3(v, w, x, y, z, i)                                        \
  z += (((w | x) & y) | (w & x)) + blk(i) + 0x8F1BBCDC + rol(v, 5); \
  w = rol(w, 30);
#define R4(v, w, x, y, z, i)                          \
  z += (w ^ x ^ y) + blk(i) + 0xCA62C1D6 + rol(v, 5); \
  w = rol(w, 30);

void cs_sha1_transform(uint32_t state[5], const unsigned char buffer[64]) {
  uint32_t a, b, c, d, e;
  union char64long16 block[1];

  memcpy(block, buffer, 64);
  a = state[0];
  b = state[1];
  c = state[2];
  d = state[3];
  e = state[4];
  R0(a, b, c, d, e, 0);
  R0(e, a, b, c, d, 1);
  R0(d, e, a, b, c, 2);
  R0(c, d, e, a, b, 3);
  R0(b, c, d, e, a, 4);
  R0(a, b, c, d, e, 5);
  R0(e, a, b, c, d, 6);
  R0(d, e, a, b, c, 7);
  R0(c, d, e, a, b, 8);
  R0(b, c, d, e, a, 9);
  R0(a, b, c, d, e, 10);
  R0(e, a, b, c, d, 11);
  R0(d, e, a, b, c, 12);
  R0(c, d, e, a, b, 13);
  R0(b, c, d, e, a, 14);
  R0(a, b, c, d, e, 15);
  R1(e, a, b, c, d, 16);
  R1(d, e, a, b, c, 17);
  R1(c, d, e, a, b, 18);
  R1(b, c, d, e, a, 19);
  R2(a, b, c, d, e, 20);
  R2(e, a, b, c, d, 21);
  R2(d, e, a, b, c, 22);
  R2(c, d, e, a, b, 23);
  R2(b, c, d, e, a, 24);
  R2(a, b, c, d, e, 25);
  R2(e, a, b, c, d, 26);
  R2(d, e, a, b, c, 27);
  R2(c, d, e, a, b, 28);
  R2(b, c, d, e, a, 29);
  R2(a, b, c, d, e, 30);
  R2(e, a, b, c, d, 31);
  R2(d, e, a, b, c, 32);
  R2(c, d, e, a, b, 33);
  R2(b, c, d, e, a, 34);
  R2(a, b, c, d, e, 35);
  R2(e, a, b, c, d, 36);
  R2(d, e, a, b, c, 37);
  R2(c, d, e, a, b, 38);
  R2(b, c, d, e, a, 39);
  R3(a, b, c, d, e, 40);
  R3(e, a, b, c, d, 41);
  R3(d, e, a, b, c, 42);
  R3(c, d, e, a, b, 43);
  R3(b, c, d, e, a, 44);
  R3(a, b, c, d, e, 45);
  R3(e, a, b, c, d, 46);
  R3(d, e, a, b, c, 47);
  R3(c, d, e, a, b, 48);
  R3(b, c, d, e, a, 49);
  R3(a, b, c, d, e, 50);
  R3(e, a, b, c, d, 51);
  R3(d, e, a, b, c, 52);
  R3(c, d, e, a, b, 53);
  R3(b, c, d, e, a, 54);
  R3(a, b, c, d, e, 55);
  R3(e, a, b, c, d, 56);
  R3(d, e, a, b, c, 57);
  R3(c, d, e, a, b, 58);
  R3(b, c, d, e, a, 59);
  R4(a, b, c, d, e, 60);
  R4(e, a, b, c, d, 61);
  R4(d, e, a, b, c, 62);
  R4(c, d, e, a, b, 63);
  R4(b, c, d, e, a, 64);
  R4(a, b, c, d, e, 65);
  R4(e, a, b, c, d, 66);
  R4(d, e, a, b, c, 67);
  R4(c, d, e, a, b, 68);
  R4(b, c, d, e, a, 69);
  R4(a, b, c, d, e, 70);
  R4(e, a, b, c, d, 71);
  R4(d, e, a, b, c, 72);
  R4(c, d, e, a, b, 73);
  R4(b, c, d, e, a, 74);
  R4(a, b, c, d, e, 75);
  R4(e, a, b, c, d, 76);
  R4(d, e, a, b, c, 77);
  R4(c, d, e, a, b, 78);
  R4(b, c, d, e, a, 79);
  state[0] += a;
  state[1] += b;
  state[2] += c;
  state[3] += d;
  state[4] += e;
  /* Erase working structures. The order of operations is important,
   * used to ensure that compiler doesn't optimize those out. */
  memset(block, 0, sizeof(block));
  a = b = c = d = e = 0;
  (void) a;
  (void) b;
  (void) c;
  (void) d;
  (void) e;
}

void cs_sha1_init(cs_sha1_ctx *context) {
  context->state[0] = 0x67452301;
  context->state[1] = 0xEFCDAB89;
  context->state[2] = 0x98BADCFE;
  context->state[3] = 0x10325476;
  context->state[4] = 0xC3D2E1F0;
  context->count[0] = context->count[1] = 0;
}

void cs_sha1_update(cs_sha1_ctx *context, const unsigned char *data,
                    uint32_t len) {
  uint32_t i, j;

  j = context->count[0];
  if ((context->count[0] += len << 3) < j) context->count[1]++;
  context->count[1] += (len >> 29);
  j = (j >> 3) & 63;
  if ((j + len) > 63) {
    memcpy(&context->buffer[j], data, (i = 64 - j));
    cs_sha1_transform(context->state, context->buffer);
    for (; i + 63 < len; i += 64) {
      cs_sha1_transform(context->state, &data[i]);
    }
    j = 0;
  } else
    i = 0;
  memcpy(&context->buffer[j], &data[i], len - i);
}

void cs_sha1_final(unsigned char digest[20], cs_sha1_ctx *context) {
  unsigned i;
  unsigned char finalcount[8], c;

  for (i = 0; i < 8; i++) {
    finalcount[i] = (unsigned char) ((context->count[(i >= 4 ? 0 : 1)] >>
                                      ((3 - (i & 3)) * 8)) &
                                     255);
  }
  c = 0200;
  cs_sha1_update(context, &c, 1);
  while ((context->count[0] & 504) != 448) {
    c = 0000;
    cs_sha1_update(context, &c, 1);
  }
  cs_sha1_update(context, finalcount, 8);
  for (i = 0; i < 20; i++) {
    digest[i] =
        (unsigned char) ((context->state[i >> 2] >> ((3 - (i & 3)) * 8)) & 255);
  }
  memset(context, '\0', sizeof(*context));
  memset(&finalcount, '\0', sizeof(finalcount));
}

void cs_hmac_sha1(const unsigned char *key, size_t keylen,
                  const unsigned char *data, size_t datalen,
                  unsigned char out[20]) {
  cs_sha1_ctx ctx;
  unsigned char buf1[64], buf2[64], tmp_key[20], i;

  if (keylen > sizeof(buf1)) {
    cs_sha1_init(&ctx);
    cs_sha1_update(&ctx, key, keylen);
    cs_sha1_final(tmp_key, &ctx);
    key = tmp_key;
    keylen = sizeof(tmp_key);
  }

  memset(buf1, 0, sizeof(buf1));
  memset(buf2, 0, sizeof(buf2));
  memcpy(buf1, key, keylen);
  memcpy(buf2, key, keylen);

  for (i = 0; i < sizeof(buf1); i++) {
    buf1[i] ^= 0x36;
    buf2[i] ^= 0x5c;
  }

  cs_sha1_init(&ctx);
  cs_sha1_update(&ctx, buf1, sizeof(buf1));
  cs_sha1_update(&ctx, data, datalen);
  cs_sha1_final(out, &ctx);

  cs_sha1_init(&ctx);
  cs_sha1_update(&ctx, buf2, sizeof(buf2));
  cs_sha1_update(&ctx, out, 20);
  cs_sha1_final(out, &ctx);
}

#endif /* EXCLUDE_COMMON */
#ifdef V7_MODULE_LINES
#line 1 "common/cs_dirent.c"
#endif
/*
 * Copyright (c) 2015 Cesanta Software Limited
 * All rights reserved
 */

#ifndef EXCLUDE_COMMON

/* Amalgamated: #include "common/cs_dirent.h" */

/*
 * This file contains POSIX opendir/closedir/readdir API implementation
 * for systems which do not natively support it (e.g. Windows).
 */

#ifndef MG_FREE
#define MG_FREE free
#endif

#ifndef MG_MALLOC
#define MG_MALLOC malloc
#endif

#ifdef _WIN32
DIR *opendir(const char *name) {
  DIR *dir = NULL;
  wchar_t wpath[MAX_PATH];
  DWORD attrs;

  if (name == NULL) {
    SetLastError(ERROR_BAD_ARGUMENTS);
  } else if ((dir = (DIR *) MG_MALLOC(sizeof(*dir))) == NULL) {
    SetLastError(ERROR_NOT_ENOUGH_MEMORY);
  } else {
    to_wchar(name, wpath, ARRAY_SIZE(wpath));
    attrs = GetFileAttributesW(wpath);
    if (attrs != 0xFFFFFFFF && (attrs & FILE_ATTRIBUTE_DIRECTORY)) {
      (void) wcscat(wpath, L"\\*");
      dir->handle = FindFirstFileW(wpath, &dir->info);
      dir->result.d_name[0] = '\0';
    } else {
      MG_FREE(dir);
      dir = NULL;
    }
  }

  return dir;
}

int closedir(DIR *dir) {
  int result = 0;

  if (dir != NULL) {
    if (dir->handle != INVALID_HANDLE_VALUE)
      result = FindClose(dir->handle) ? 0 : -1;
    MG_FREE(dir);
  } else {
    result = -1;
    SetLastError(ERROR_BAD_ARGUMENTS);
  }

  return result;
}

struct dirent *readdir(DIR *dir) {
  struct dirent *result = NULL;

  if (dir) {
    if (dir->handle != INVALID_HANDLE_VALUE) {
      result = &dir->result;
      (void) WideCharToMultiByte(CP_UTF8, 0, dir->info.cFileName, -1,
                                 result->d_name, sizeof(result->d_name), NULL,
                                 NULL);

      if (!FindNextFileW(dir->handle, &dir->info)) {
        (void) FindClose(dir->handle);
        dir->handle = INVALID_HANDLE_VALUE;
      }

    } else {
      SetLastError(ERROR_FILE_NOT_FOUND);
    }
  } else {
    SetLastError(ERROR_BAD_ARGUMENTS);
  }

  return result;
}
#endif

#ifdef CS_ENABLE_SPIFFS

DIR *opendir(const char *dir_name) {
  DIR *dir = NULL;
  extern spiffs fs;

  if (dir_name != NULL && (dir = (DIR *) malloc(sizeof(*dir))) != NULL &&
      SPIFFS_opendir(&fs, (char *) dir_name, &dir->dh) == NULL) {
    free(dir);
    dir = NULL;
  }

  return dir;
}

int closedir(DIR *dir) {
  if (dir != NULL) {
    SPIFFS_closedir(&dir->dh);
    free(dir);
  }
  return 0;
}

struct dirent *readdir(DIR *dir) {
  return SPIFFS_readdir(&dir->dh, &dir->de);
}

/* SPIFFs doesn't support directory operations */
int rmdir(const char *path) {
  (void) path;
  return ENOTDIR;
}

int mkdir(const char *path, mode_t mode) {
  (void) path;
  (void) mode;
  /* for spiffs supports only root dir, which comes from mongoose as '.' */
  return (strlen(path) == 1 && *path == '.') ? 0 : ENOTDIR;
}

#endif /* CS_ENABLE_SPIFFS */

#endif /* EXCLUDE_COMMON */

/* ISO C requires a translation unit to contain at least one declaration */
typedef int cs_dirent_dummy;
#ifdef V7_MODULE_LINES
#line 1 "common/cs_file.c"
#endif
/*
 * Copyright (c) 2015 Cesanta Software Limited
 * All rights reserved
 */

/* Amalgamated: #include "common/cs_file.h" */

#include <stdio.h>
#include <stdlib.h>

#ifdef CS_MMAP
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#endif

#ifndef EXCLUDE_COMMON
char *cs_read_file(const char *path, size_t *size) {
  FILE *fp;
  char *data = NULL;
  if ((fp = fopen(path, "rb")) == NULL) {
  } else if (fseek(fp, 0, SEEK_END) != 0) {
    fclose(fp);
  } else {
    *size = ftell(fp);
    data = (char *) malloc(*size + 1);
    if (data != NULL) {
      fseek(fp, 0, SEEK_SET); /* Some platforms might not have rewind(), Oo */
      if (fread(data, 1, *size, fp) != *size) {
        free(data);
        return NULL;
      }
      data[*size] = '\0';
    }
    fclose(fp);
  }
  return data;
}
#endif /* EXCLUDE_COMMON */

#ifdef CS_MMAP
char *cs_mmap_file(const char *path, size_t *size) {
  char *r;
  int fd = open(path, O_RDONLY);
  struct stat st;
  if (fd == -1) return NULL;
  fstat(fd, &st);
  *size = (size_t) st.st_size;
  r = (char *) mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
  if (r == MAP_FAILED) return NULL;
  return r;
}
#endif
#ifdef V7_MODULE_LINES
#line 1 "common/cs_strtod.c"
#endif
#include <ctype.h>
#include <math.h>

#include <stdlib.h>

int cs_strncasecmp(const char *s1, const char *s2, size_t n) {
  if (n == 0) {
    return 0;
  }

  while (n-- != 0 && tolower((int) *s1) == tolower((int) *s2)) {
    if (n == 0 || *s1 == '\0' || *s2 == '\0') {
      break;
    }
    s1++;
    s2++;
  }

  return tolower(*(unsigned char *) s1) - tolower(*(unsigned char *) s2);
}

/*
 * based on Source:
 * https://github.com/anakod/Sming/blob/master/Sming/system/stringconversion.cpp#L93
 */

double cs_strtod(const char *str, char **endptr) {
  double result = 0.0;
  char c;
  const char *str_start;
  struct {
    unsigned neg : 1;        /* result is negative */
    unsigned decimals : 1;   /* parsing decimal part */
    unsigned is_exp : 1;     /* parsing exponent like e+5 */
    unsigned is_exp_neg : 1; /* exponent is negative */
  } flags = {0, 0, 0, 0};

  while (isspace((int) *str)) {
    str++;
  }

  if (*str == 0) {
    /* only space in str? */
    if (endptr != 0) *endptr = (char *) str;
    return result;
  }

  /* Handle leading plus/minus signs */
  while (*str == '-' || *str == '+') {
    if (*str == '-') {
      flags.neg = !flags.neg;
    }
    str++;
  }

  if (cs_strncasecmp(str, "NaN", 3) == 0) {
    if (endptr != 0) *endptr = (char *) str + 3;
    return NAN;
  }

  if (cs_strncasecmp(str, "INF", 3) == 0) {
    str += 3;
    if (cs_strncasecmp(str, "INITY", 5) == 0) str += 5;
    if (endptr != 0) *endptr = (char *) str;
    return flags.neg ? -INFINITY : INFINITY;
  }

  str_start = str;

  if (*str == '0' && (*(str + 1) == 'x' || *(str + 1) == 'X')) {
    /* base 16 */
    str += 2;
    while ((c = tolower((int) *str))) {
      int d;
      if (c >= '0' && c <= '9') {
        d = c - '0';
      } else if (c >= 'a' && c <= 'f') {
        d = 10 + (c - 'a');
      } else {
        break;
      }
      result = 16 * result + d;
      str++;
    }
  } else if (*str == '0' && (*(str + 1) == 'b' || *(str + 1) == 'B')) {
    /* base 2 */
    str += 2;
    while ((c = *str)) {
      int d = c - '0';
      if (c != '0' && c != '1') break;
      result = 2 * result + d;
      str++;
    }
  } else if (*str == '0' && *(str + 1) >= '0' && *(str + 1) <= '7') {
    /* base 8 */
    while ((c = *str)) {
      int d = c - '0';
      if (c < '0' || c > '7') {
        /* fallback to base 10 */
        str = str_start;
        break;
      }
      result = 8 * result + d;
      str++;
    }
  }

  if (str == str_start) {
    /* base 10 */

    /* exponent specified explicitly, like in 3e-5, exponent is -5 */
    int exp = 0;
    /* exponent calculated from dot, like in 1.23, exponent is -2 */
    int exp_dot = 0;

    result = 0;

    while ((c = *str)) {
      int d;

      if (c == '.') {
        if (!flags.decimals) {
          /* going to parse decimal part */
          flags.decimals = 1;
          str++;
          continue;
        } else {
          /* non-expected dot: assume number data is over */
          break;
        }
      } else if (c == 'e' || c == 'E') {
        /* going to parse exponent part */
        flags.is_exp = 1;
        str++;
        c = *str;

        /* check sign of the exponent */
        if (c == '-' || c == '+') {
          if (c == '-') {
            flags.is_exp_neg = 1;
          }
          str++;
        }

        continue;
      }

      d = c - '0';
      if (d < 0 || d > 9) {
        break;
      }

      if (!flags.is_exp) {
        /* apply current digit to the result */
        result = 10 * result + d;
        if (flags.decimals) {
          exp_dot--;
        }
      } else {
        /* apply current digit to the exponent */
        if (flags.is_exp_neg) {
          if (exp > -1022) {
            exp = 10 * exp - d;
          }
        } else {
          if (exp < 1023) {
            exp = 10 * exp + d;
          }
        }
      }

      str++;
    }

    exp += exp_dot;

    /*
     * TODO(dfrank): it probably makes sense not to adjust intermediate `double
     * result`, but build double number accordingly to IEEE 754 from taken
     * (integer) mantissa, exponent and sign. That would work faster, and we
     * can avoid any possible round errors.
     */

    /* if exponent is non-zero, apply it */
    if (exp != 0) {
      if (exp < 0) {
        while (exp++ != 0) {
          result /= 10;
        }
      } else {
        while (exp-- != 0) {
          result *= 10;
        }
      }
    }
  }

  if (flags.neg) {
    result = -result;
  }

  if (endptr != 0) {
    *endptr = (char *) str;
  }

  return result;
}
#ifdef V7_MODULE_LINES
#line 1 "common/coroutine.c"
#endif
/*
 * Copyright (c) 2015 Cesanta Software Limited
 * All rights reserved
 */

/*
 * Module that provides generic macros and functions to implement "coroutines",
 * i.e. C code that uses `mbuf` as a stack for function calls.
 *
 * More info: see the design doc: https://goo.gl/kfcG61
 */

#include <string.h>
#include <stdlib.h>

/* Amalgamated: #include "common/coroutine.h" */

/*
 * Unwinds stack by 1 function. Used when we're returning from function and
 * when an exception is thrown.
 */
static void _level_up(struct cr_ctx *p_ctx) {
  /* get size of current function's stack data */
  size_t locals_size = _CR_CURR_FUNC_LOCALS_SIZE(p_ctx);

  /* check stacks underflow */
  if (_CR_STACK_FID_UND_CHECK(p_ctx, 1 /*fid*/)) {
    p_ctx->status = CR_RES__ERR_STACK_CALL_UNDERFLOW;
    return;
  } else if (_CR_STACK_DATA_UND_CHECK(p_ctx, locals_size)) {
    p_ctx->status = CR_RES__ERR_STACK_DATA_UNDERFLOW;
    return;
  }

  /* decrement stacks */
  _CR_STACK_DATA_FREE(p_ctx, locals_size);
  _CR_STACK_FID_FREE(p_ctx, 1 /*fid*/);
  p_ctx->stack_ret.len = p_ctx->cur_fid_idx;

  /* if we have exception marker here, adjust cur_fid_idx */
  while (CR_CURR_FUNC_C(p_ctx) == CR_FID__TRY_MARKER) {
    /* check for stack underflow */
    if (_CR_STACK_FID_UND_CHECK(p_ctx, _CR_TRY_SIZE)) {
      p_ctx->status = CR_RES__ERR_STACK_CALL_UNDERFLOW;
      return;
    }
    _CR_STACK_FID_FREE(p_ctx, _CR_TRY_SIZE);
  }
}

enum cr_status cr_on_iter_begin(struct cr_ctx *p_ctx) {
  if (p_ctx->status != CR_RES__OK) {
    goto out;
  } else if (p_ctx->called_fid != CR_FID__NONE) {
    /* need to call new function */

    size_t locals_size = p_ctx->p_func_descrs[p_ctx->called_fid].locals_size;
    /*
     * increment stack pointers
     */
    /* make sure this function has correct `struct cr_func_desc` entry */
    assert(locals_size == p_ctx->call_locals_size);
    /*
     * make sure we haven't mistakenly included "zero-sized" `.._arg_t`
     * structure in `.._locals_t` struct
     *
     * By "zero-sized" I mean `cr_zero_size_type_t`.
     */
    assert(locals_size < sizeof(cr_zero_size_type_t));

    _CR_STACK_DATA_ALLOC(p_ctx, locals_size);
    _CR_STACK_RET_ALLOC(p_ctx, 1 /*fid*/);
    p_ctx->cur_fid_idx = p_ctx->stack_ret.len;

    /* copy arguments to our "stack" (and advance locals stack pointer) */
    memcpy(p_ctx->stack_data.buf + p_ctx->stack_data.len - locals_size,
           p_ctx->p_arg_retval, p_ctx->call_arg_size);

    /* set function id */
    CR_CURR_FUNC_C(p_ctx) = p_ctx->called_fid;

    /* clear called_fid */
    p_ctx->called_fid = CR_FID__NONE;

  } else if (p_ctx->need_return) {
    /* need to return from the currently running function */

    _level_up(p_ctx);
    if (p_ctx->status != CR_RES__OK) {
      goto out;
    }

    p_ctx->need_return = 0;

  } else if (p_ctx->need_yield) {
    /* need to yield */

    p_ctx->need_yield = 0;
    p_ctx->status = CR_RES__OK_YIELDED;
    goto out;

  } else if (p_ctx->thrown_exc != CR_EXC_ID__NONE) {
    /* exception was thrown */

    /* unwind stack until we reach the bottom, or find some try-catch blocks */
    do {
      _level_up(p_ctx);
      if (p_ctx->status != CR_RES__OK) {
        goto out;
      }

      if (_CR_TRY_MARKER(p_ctx) == CR_FID__TRY_MARKER) {
        /* we have some try-catch here, go to the first catch */
        CR_CURR_FUNC_C(p_ctx) = _CR_TRY_CATCH_FID(p_ctx);
        break;
      } else if (CR_CURR_FUNC_C(p_ctx) == CR_FID__NONE) {
        /* we've reached the bottom of the stack */
        p_ctx->status = CR_RES__ERR_UNCAUGHT_EXCEPTION;
        break;
      }

    } while (1);
  }

  /* remember pointer to current function's locals */
  _CR_CUR_FUNC_LOCALS_UPD(p_ctx);

out:
  return p_ctx->status;
}

void cr_context_init(struct cr_ctx *p_ctx, union user_arg_ret *p_arg_retval,
                     size_t arg_retval_size,
                     const struct cr_func_desc *p_func_descrs) {
  /*
   * make sure we haven't mistakenly included "zero-sized" `.._arg_t`
   * structure in `union user_arg_ret`.
   *
   * By "zero-sized" I mean `cr_zero_size_type_t`.
   */
  assert(arg_retval_size < sizeof(cr_zero_size_type_t));
#ifdef NDEBUG
  (void) arg_retval_size;
#endif

  memset(p_ctx, 0x00, sizeof(*p_ctx));

  p_ctx->p_func_descrs = p_func_descrs;
  p_ctx->p_arg_retval = p_arg_retval;

  mbuf_init(&p_ctx->stack_data, 0);
  mbuf_init(&p_ctx->stack_ret, 0);

  mbuf_append(&p_ctx->stack_ret, NULL, 1 /*starting byte for CR_FID__NONE*/);
  p_ctx->cur_fid_idx = p_ctx->stack_ret.len;

  _CR_CALL_PREPARE(p_ctx, CR_FID__NONE, 0, 0, CR_FID__NONE);
}

void cr_context_free(struct cr_ctx *p_ctx) {
  mbuf_free(&p_ctx->stack_data);
  mbuf_free(&p_ctx->stack_ret);
}
#ifdef V7_MODULE_LINES
#line 1 "common/platforms/mbed/mbed_libc.c"
#endif
/*
 * Copyright (c) 2014-2016 Cesanta Software Limited
 * All rights reserved
 */

#if CS_PLATFORM == CS_P_MBED

long timezone;

#endif /* CS_PLATFORM == CS_P_MBED */
#ifdef V7_MODULE_LINES
#line 1 "v7/builtin/file.c"
#endif
/*
 * Copyright (c) 2014 Cesanta Software Limited
 * All rights reserved
 */

/* Amalgamated: #include "v7/src/internal.h" */
/* Amalgamated: #include "v7/src/core.h" */
/* Amalgamated: #include "v7/src/primitive.h" */
/* Amalgamated: #include "v7/src/string.h" */
/* Amalgamated: #include "v7/src/exceptions.h" */
/* Amalgamated: #include "v7/src/object.h" */
/* Amalgamated: #include "v7/src/exec.h" */
/* Amalgamated: #include "v7/src/array.h" */
/* Amalgamated: #include "common/mbuf.h" */
/* Amalgamated: #include "common/cs_file.h" */
/* Amalgamated: #include "v7/src/v7_features.h" */
/* Amalgamated: #include "common/cs_dirent.h" */

#if defined(V7_ENABLE_FILE) && !defined(V7_NO_FS)

static const char s_fd_prop[] = "__fd";

#ifndef NO_LIBC
static FILE *v7_val_to_file(struct v7 *v7, v7_val_t val) {
  (void) v7;
  return (FILE *) v7_get_ptr(v7, val);
}

static v7_val_t v7_file_to_val(struct v7 *v7, FILE *file) {
  (void) v7;
  return v7_mk_foreign(v7, file);
}

static int v7_is_file_type(v7_val_t val) {
  return v7_is_foreign(val);
}
#else
FILE *v7_val_to_file(struct v7 *v7, v7_val_t val);
v7_val_t v7_file_to_val(struct v7 *v7, FILE *file);
int v7_is_file_type(v7_val_t val);
#endif

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err File_eval(struct v7 *v7, v7_val_t *res) {
  enum v7_err rcode = V7_OK;
  v7_val_t arg0 = v7_arg(v7, 0);

  *res = V7_UNDEFINED;

  if (v7_is_string(arg0)) {
    const char *s = v7_get_cstring(v7, &arg0);
    if (s == NULL) {
      rcode = v7_throwf(v7, "TypeError", "Invalid string");
      goto clean;
    }

    v7_set_gc_enabled(v7, 1);
    rcode = v7_exec_file(v7, s, res);
    if (rcode != V7_OK) {
      goto clean;
    }
  }

clean:
  return rcode;
}

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err File_exists(struct v7 *v7, v7_val_t *res) {
  enum v7_err rcode = V7_OK;
  v7_val_t arg0 = v7_arg(v7, 0);

  *res = v7_mk_boolean(v7, 0);

  if (v7_is_string(arg0)) {
    const char *fname = v7_get_cstring(v7, &arg0);
    if (fname != NULL) {
      struct stat st;
      if (stat(fname, &st) == 0) *res = v7_mk_boolean(v7, 1);
    }
  }

  return rcode;
}

WARN_UNUSED_RESULT
static enum v7_err f_read(struct v7 *v7, int all, v7_val_t *res) {
  enum v7_err rcode = V7_OK;
  v7_val_t this_obj = v7_get_this(v7);
  v7_val_t arg0 = v7_get(v7, this_obj, s_fd_prop, sizeof(s_fd_prop) - 1);

  if (v7_is_file_type(arg0)) {
    struct mbuf m;
    char buf[BUFSIZ];
    int n;
    FILE *fp = v7_val_to_file(v7, arg0);

    /* Read file contents into mbuf */
    mbuf_init(&m, 0);
    while ((n = fread(buf, 1, sizeof(buf), fp)) > 0) {
      mbuf_append(&m, buf, n);
      if (!all) {
        break;
      }
    }

    if (m.len > 0) {
      *res = v7_mk_string(v7, m.buf, m.len, 1);
      mbuf_free(&m);
      goto clean;
    }
  }
  *res = v7_mk_string(v7, "", 0, 1);

clean:
  return rcode;
}

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err File_obj_read(struct v7 *v7, v7_val_t *res) {
  return f_read(v7, 0, res);
}

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err File_obj_write(struct v7 *v7, v7_val_t *res) {
  enum v7_err rcode = V7_OK;
  v7_val_t this_obj = v7_get_this(v7);
  v7_val_t arg0 = v7_get(v7, this_obj, s_fd_prop, sizeof(s_fd_prop) - 1);
  v7_val_t arg1 = v7_arg(v7, 0);
  size_t n, sent = 0, len = 0;

  if (v7_is_file_type(arg0) && v7_is_string(arg1)) {
    const char *s = v7_get_string(v7, &arg1, &len);
    FILE *fp = v7_val_to_file(v7, arg0);
    while (sent < len && (n = fwrite(s + sent, 1, len - sent, fp)) > 0) {
      sent += n;
    }
  }

  *res = v7_mk_number(v7, sent);

  return rcode;
}

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err File_obj_close(struct v7 *v7, v7_val_t *res) {
  enum v7_err rcode = V7_OK;
  v7_val_t this_obj = v7_get_this(v7);
  v7_val_t prop = v7_get(v7, this_obj, s_fd_prop, sizeof(s_fd_prop) - 1);
  int ires = -1;

  if (v7_is_file_type(prop)) {
    ires = fclose(v7_val_to_file(v7, prop));
  }

  *res = v7_mk_number(v7, ires);

  return rcode;
}

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err File_open(struct v7 *v7, v7_val_t *res) {
  enum v7_err rcode = V7_OK;
  v7_val_t arg0 = v7_arg(v7, 0);
  v7_val_t arg1 = v7_arg(v7, 1);
  FILE *fp = NULL;

  if (v7_is_string(arg0)) {
    const char *s1 = v7_get_cstring(v7, &arg0);
    const char *s2 = "rb"; /* Open files in read mode by default */

    if (v7_is_string(arg1)) {
      s2 = v7_get_cstring(v7, &arg1);
    }

    if (s1 == NULL || s2 == NULL) {
      *res = V7_NULL;
      goto clean;
    }

    fp = fopen(s1, s2);
    if (fp != NULL) {
      v7_val_t obj = v7_mk_object(v7);
      v7_val_t file_proto = v7_get(
          v7, v7_get(v7, v7_get_global(v7), "File", ~0), "prototype", ~0);
      v7_set_proto(v7, obj, file_proto);
      v7_def(v7, obj, s_fd_prop, sizeof(s_fd_prop) - 1, V7_DESC_ENUMERABLE(0),
             v7_file_to_val(v7, fp));
      *res = obj;
      goto clean;
    }
  }

  *res = V7_NULL;

clean:
  return rcode;
}

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err File_read(struct v7 *v7, v7_val_t *res) {
  v7_val_t arg0 = v7_arg(v7, 0);

  if (v7_is_string(arg0)) {
    const char *path = v7_get_cstring(v7, &arg0);
    size_t size = 0;
    char *data = cs_read_file(path, &size);
    if (data != NULL) {
      *res = v7_mk_string(v7, data, size, 1);
      free(data);
    }
  }

  return V7_OK;
}

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err File_write(struct v7 *v7, v7_val_t *res) {
  v7_val_t arg0 = v7_arg(v7, 0);
  v7_val_t arg1 = v7_arg(v7, 1);
  *res = v7_mk_boolean(v7, 0);

  if (v7_is_string(arg0) && v7_is_string(arg1)) {
    const char *path = v7_get_cstring(v7, &arg0);
    size_t len;
    const char *buf = v7_get_string(v7, &arg1, &len);
    FILE *fp = fopen(path, "wb+");
    if (fp != NULL) {
      if (fwrite(buf, 1, len, fp) == len) {
        *res = v7_mk_boolean(v7, 1);
      }
      fclose(fp);
    }
  }

  return V7_OK;
}

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err File_rename(struct v7 *v7, v7_val_t *res) {
  enum v7_err rcode = V7_OK;
  v7_val_t arg0 = v7_arg(v7, 0);
  v7_val_t arg1 = v7_arg(v7, 1);
  int ires = -1;

  if (v7_is_string(arg0) && v7_is_string(arg1)) {
    const char *from = v7_get_cstring(v7, &arg0);
    const char *to = v7_get_cstring(v7, &arg1);
    if (from == NULL || to == NULL) {
      *res = v7_mk_number(v7, ENOENT);
      goto clean;
    }

    ires = rename(from, to);
  }

  *res = v7_mk_number(v7, ires == 0 ? 0 : errno);

clean:
  return rcode;
}

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err File_loadJSON(struct v7 *v7, v7_val_t *res) {
  enum v7_err rcode = V7_OK;
  v7_val_t arg0 = v7_arg(v7, 0);

  *res = V7_UNDEFINED;

  if (v7_is_string(arg0)) {
    const char *file_name = v7_get_cstring(v7, &arg0);
    if (file_name == NULL) {
      goto clean;
    }

    rcode = v7_parse_json_file(v7, file_name, res);
    if (rcode != V7_OK) {
      /* swallow exception and return undefined */
      v7_clear_thrown_value(v7);
      rcode = V7_OK;
      *res = V7_UNDEFINED;
    }
  }

clean:
  return rcode;
}

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err File_remove(struct v7 *v7, v7_val_t *res) {
  enum v7_err rcode = V7_OK;
  v7_val_t arg0 = v7_arg(v7, 0);
  int ires = -1;

  if (v7_is_string(arg0)) {
    const char *path = v7_get_cstring(v7, &arg0);
    if (path == NULL) {
      *res = v7_mk_number(v7, ENOENT);
      goto clean;
    }
    ires = remove(path);
  }
  *res = v7_mk_number(v7, ires == 0 ? 0 : errno);

clean:
  return rcode;
}

#if V7_ENABLE__File__list
WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err File_list(struct v7 *v7, v7_val_t *res) {
  enum v7_err rcode = V7_OK;
  v7_val_t arg0 = v7_arg(v7, 0);

  *res = V7_UNDEFINED;

  if (v7_is_string(arg0)) {
    const char *path = v7_get_cstring(v7, &arg0);
    struct dirent *dp;
    DIR *dirp;

    if (path == NULL) {
      goto clean;
    }

    if ((dirp = (opendir(path))) != NULL) {
      *res = v7_mk_array(v7);
      while ((dp = readdir(dirp)) != NULL) {
        /* Do not show current and parent dirs */
        if (strcmp((const char *) dp->d_name, ".") == 0 ||
            strcmp((const char *) dp->d_name, "..") == 0) {
          continue;
        }
        /* Add file name to the list */
        v7_array_push(v7, *res,
                      v7_mk_string(v7, (const char *) dp->d_name,
                                   strlen((const char *) dp->d_name), 1));
      }
      closedir(dirp);
    }
  }

clean:
  return rcode;
}
#endif /* V7_ENABLE__File__list */

void init_file(struct v7 *v7) {
  v7_val_t file_obj = v7_mk_object(v7), file_proto = v7_mk_object(v7);
  v7_set(v7, v7_get_global(v7), "File", 4, file_obj);
  v7_set(v7, file_obj, "prototype", 9, file_proto);

  v7_set_method(v7, file_obj, "eval", File_eval);
  v7_set_method(v7, file_obj, "exists", File_exists);
  v7_set_method(v7, file_obj, "remove", File_remove);
  v7_set_method(v7, file_obj, "rename", File_rename);
  v7_set_method(v7, file_obj, "open", File_open);
  v7_set_method(v7, file_obj, "read", File_read);
  v7_set_method(v7, file_obj, "write", File_write);
  v7_set_method(v7, file_obj, "loadJSON", File_loadJSON);
#if V7_ENABLE__File__list
  v7_set_method(v7, file_obj, "list", File_list);
#endif

  v7_set_method(v7, file_proto, "close", File_obj_close);
  v7_set_method(v7, file_proto, "read", File_obj_read);
  v7_set_method(v7, file_proto, "write", File_obj_write);

#if V7_ENABLE__File__require
  v7_def(v7, v7_get_global(v7), "_modcache", ~0, 0, v7_mk_object(v7));
  if (v7_exec(v7,
              "function require(m) { "
              "  if (m in _modcache) { return _modcache[m]; }"
              "  var module = {exports:{}};"
              "  File.eval(m);"
              "  return (_modcache[m] = module.exports)"
              " }",
              NULL) != V7_OK) {
    /* TODO(mkm): percolate failure */
  }
#endif
}
#else
void init_file(struct v7 *v7) {
  (void) v7;
}
#endif /* NO_LIBC */
#ifdef V7_MODULE_LINES
#line 1 "v7/builtin/socket.c"
#endif
/*
 * Copyright (c) 2015 Cesanta Software Limited
 * All rights reserved
 */

/* Amalgamated: #include "v7/src/internal.h" */
/* Amalgamated: #include "v7/src/core.h" */
/* Amalgamated: #include "v7/src/string.h" */
/* Amalgamated: #include "v7/src/object.h" */
/* Amalgamated: #include "v7/src/primitive.h" */
/* Amalgamated: #include "v7/src/conversion.h" */
/* Amalgamated: #include "common/mbuf.h" */
/* Amalgamated: #include "common/platform.h" */

#ifdef V7_ENABLE_SOCKET

#ifdef __WATCOM__
#define SOMAXCONN 128
#endif

#ifndef RECV_BUF_SIZE
#define RECV_BUF_SIZE 1024
#endif

static const char s_sock_prop[] = "__sock";

static uint32_t s_resolve(struct v7 *v7, v7_val_t ip_address) {
  size_t n;
  const char *s = v7_get_string(v7, &ip_address, &n);
  struct hostent *he = gethostbyname(s);
  return he == NULL ? 0 : *(uint32_t *) he->h_addr_list[0];
}

WARN_UNUSED_RESULT
static enum v7_err s_fd_to_sock_obj(struct v7 *v7, sock_t fd, v7_val_t *res) {
  enum v7_err rcode = V7_OK;
  v7_val_t sock_proto =
      v7_get(v7, v7_get(v7, v7_get_global(v7), "Socket", ~0), "prototype", ~0);

  *res = v7_mk_object(v7);
  v7_set_proto(v7, *res, sock_proto);
  v7_def(v7, *res, s_sock_prop, sizeof(s_sock_prop) - 1, V7_DESC_ENUMERABLE(0),
         v7_mk_number(v7, fd));

  return rcode;
}

/* Socket.connect(host, port [, is_udp]) -> socket_object */
WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Socket_connect(struct v7 *v7, v7_val_t *res) {
  enum v7_err rcode = V7_OK;
  v7_val_t arg0 = v7_arg(v7, 0);
  v7_val_t arg1 = v7_arg(v7, 1);
  v7_val_t arg2 = v7_arg(v7, 2);

  if (v7_is_number(arg1) && v7_is_string(arg0)) {
    struct sockaddr_in sin;
    sock_t sock =
        socket(AF_INET, v7_is_truthy(v7, arg2) ? SOCK_DGRAM : SOCK_STREAM, 0);
    memset(&sin, 0, sizeof(sin));
    sin.sin_family = AF_INET;
    sin.sin_addr.s_addr = s_resolve(v7, arg0);
    sin.sin_port = htons((uint16_t) v7_get_double(v7, arg1));
    if (connect(sock, (struct sockaddr *) &sin, sizeof(sin)) != 0) {
      closesocket(sock);
    } else {
      rcode = s_fd_to_sock_obj(v7, sock, res);
      goto clean;
    }
  }

  *res = V7_NULL;

clean:
  return rcode;
}

/* Socket.listen(port [, ip_address [,is_udp]]) -> sock */
WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Socket_listen(struct v7 *v7, v7_val_t *res) {
  enum v7_err rcode = V7_OK;
  v7_val_t arg0 = v7_arg(v7, 0);
  v7_val_t arg1 = v7_arg(v7, 1);
  v7_val_t arg2 = v7_arg(v7, 2);

  if (v7_is_number(arg0)) {
    struct sockaddr_in sin;
    int on = 1;
    sock_t sock =
        socket(AF_INET, v7_is_truthy(v7, arg2) ? SOCK_DGRAM : SOCK_STREAM, 0);
    memset(&sin, 0, sizeof(sin));
    sin.sin_family = AF_INET;
    sin.sin_port = htons((uint16_t) v7_get_double(v7, arg0));
    if (v7_is_string(arg1)) {
      sin.sin_addr.s_addr = s_resolve(v7, arg1);
    }

#if defined(_WIN32) && defined(SO_EXCLUSIVEADDRUSE)
    /* "Using SO_REUSEADDR and SO_EXCLUSIVEADDRUSE" http://goo.gl/RmrFTm */
    setsockopt(sock, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, (void *) &on, sizeof(on));
#endif

#if !defined(_WIN32) || defined(SO_EXCLUSIVEADDRUSE)
    /*
     * SO_RESUSEADDR is not enabled on Windows because the semantics of
     * SO_REUSEADDR on UNIX and Windows is different. On Windows,
     * SO_REUSEADDR allows to bind a socket to a port without error even if
     * the port is already open by another program. This is not the behavior
     * SO_REUSEADDR was designed for, and leads to hard-to-track failure
     * scenarios. Therefore, SO_REUSEADDR was disabled on Windows unless
     * SO_EXCLUSIVEADDRUSE is supported and set on a socket.
     */
    setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void *) &on, sizeof(on));
#endif

    if (bind(sock, (struct sockaddr *) &sin, sizeof(sin)) == 0) {
      listen(sock, SOMAXCONN);
      rcode = s_fd_to_sock_obj(v7, sock, res);
      goto clean;
    } else {
      closesocket(sock);
    }
  }

  *res = V7_NULL;

clean:
  return rcode;
}

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Socket_accept(struct v7 *v7, v7_val_t *res) {
  enum v7_err rcode = V7_OK;
  v7_val_t this_obj = v7_get_this(v7);
  v7_val_t prop = v7_get(v7, this_obj, s_sock_prop, sizeof(s_sock_prop) - 1);

  if (v7_is_number(prop)) {
    struct sockaddr_in sin;
    socklen_t len = sizeof(sin);
    sock_t sock = (sock_t) v7_get_double(v7, prop);
    sock_t fd = accept(sock, (struct sockaddr *) &sin, &len);
    if (fd != INVALID_SOCKET) {
      rcode = s_fd_to_sock_obj(v7, fd, res);
      if (rcode == V7_OK) {
        char *remote_host = inet_ntoa(sin.sin_addr);
        v7_set(v7, *res, "remoteHost", ~0,
               v7_mk_string(v7, remote_host, ~0, 1));
      }
      goto clean;
    }
  }
  *res = V7_NULL;

clean:
  return rcode;
}

/* sock.close() -> errno */
WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Socket_close(struct v7 *v7, v7_val_t *res) {
  enum v7_err rcode = V7_OK;
  v7_val_t this_obj = v7_get_this(v7);
  v7_val_t prop = v7_get(v7, this_obj, s_sock_prop, sizeof(s_sock_prop) - 1);
  *res = v7_mk_number(v7, closesocket((sock_t) v7_get_double(v7, prop)));

  return rcode;
}

/* sock.recv() -> string */
WARN_UNUSED_RESULT
static enum v7_err s_recv(struct v7 *v7, int all, v7_val_t *res) {
  enum v7_err rcode = V7_OK;
  v7_val_t this_obj = v7_get_this(v7);
  v7_val_t prop = v7_get(v7, this_obj, s_sock_prop, sizeof(s_sock_prop) - 1);

  if (v7_is_number(prop)) {
    char buf[RECV_BUF_SIZE];
    sock_t sock = (sock_t) v7_get_double(v7, prop);
    struct mbuf m;
    int n;

    mbuf_init(&m, 0);
    while ((n = recv(sock, buf, sizeof(buf), 0)) > 0) {
      mbuf_append(&m, buf, n);
      if (!all) {
        break;
      }
    }

    if (n <= 0) {
      closesocket(sock);
      v7_def(v7, this_obj, s_sock_prop, sizeof(s_sock_prop) - 1,
             V7_DESC_ENUMERABLE(0), v7_mk_number(v7, INVALID_SOCKET));
    }

    if (m.len > 0) {
      *res = v7_mk_string(v7, m.buf, m.len, 1);
      mbuf_free(&m);
      goto clean;
    }
  }

  *res = V7_NULL;

clean:
  return rcode;
}

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Socket_recvAll(struct v7 *v7, v7_val_t *res) {
  return s_recv(v7, 1, res);
}

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Socket_recv(struct v7 *v7, v7_val_t *res) {
  return s_recv(v7, 0, res);
}

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Socket_send(struct v7 *v7, v7_val_t *res) {
  enum v7_err rcode = V7_OK;
  v7_val_t this_obj = v7_get_this(v7);
  v7_val_t arg0 = v7_arg(v7, 0);
  v7_val_t prop = v7_get(v7, this_obj, s_sock_prop, sizeof(s_sock_prop) - 1);
  size_t len, sent = 0;

  if (v7_is_number(prop) && v7_is_string(arg0)) {
    const char *s = v7_get_string(v7, &arg0, &len);
    sock_t sock = (sock_t) v7_get_double(v7, prop);
    int n;

    while (sent < len && (n = send(sock, s + sent, len - sent, 0)) > 0) {
      sent += n;
    }
  }

  *res = v7_mk_number(v7, sent);

  return rcode;
}

void init_socket(struct v7 *v7) {
  v7_val_t socket_obj = v7_mk_object(v7), sock_proto = v7_mk_object(v7);

  v7_set(v7, v7_get_global(v7), "Socket", 6, socket_obj);
  sock_proto = v7_mk_object(v7);
  v7_set(v7, socket_obj, "prototype", 9, sock_proto);

  v7_set_method(v7, socket_obj, "connect", Socket_connect);
  v7_set_method(v7, socket_obj, "listen", Socket_listen);

  v7_set_method(v7, sock_proto, "accept", Socket_accept);
  v7_set_method(v7, sock_proto, "send", Socket_send);
  v7_set_method(v7, sock_proto, "recv", Socket_recv);
  v7_set_method(v7, sock_proto, "recvAll", Socket_recvAll);
  v7_set_method(v7, sock_proto, "close", Socket_close);

#ifdef _WIN32
  {
    WSADATA data;
    WSAStartup(MAKEWORD(2, 2), &data);
    /* TODO(alashkin): add WSACleanup call */
  }
#else
  signal(SIGPIPE, SIG_IGN);
#endif
}
#else
void init_socket(struct v7 *v7) {
  (void) v7;
}
#endif
#ifdef V7_MODULE_LINES
#line 1 "v7/builtin/crypto.c"
#endif
/*
 * Copyright (c) 2015 Cesanta Software Limited
 * All rights reserved
 */

#include <stdlib.h>
#include <string.h>

/* Amalgamated: #include "v7/src/internal.h" */
/* Amalgamated: #include "v7/src/core.h" */
/* Amalgamated: #include "v7/src/primitive.h" */
/* Amalgamated: #include "v7/src/string.h" */
/* Amalgamated: #include "v7/src/object.h" */
/* Amalgamated: #include "common/md5.h" */
/* Amalgamated: #include "common/sha1.h" */
/* Amalgamated: #include "common/base64.h" */

#ifdef V7_ENABLE_CRYPTO

typedef void (*b64_func_t)(const unsigned char *, int, char *);

WARN_UNUSED_RESULT
static enum v7_err b64_transform(struct v7 *v7, b64_func_t func, double mult,
                                 v7_val_t *res) {
  enum v7_err rcode = V7_OK;
  v7_val_t arg0 = v7_arg(v7, 0);
  *res = V7_UNDEFINED;

  if (v7_is_string(arg0)) {
    size_t n;
    const char *s = v7_get_string(v7, &arg0, &n);
    char *buf = (char *) malloc(n * mult + 4);
    if (buf != NULL) {
      func((const unsigned char *) s, (int) n, buf);
      *res = v7_mk_string(v7, buf, strlen(buf), 1);
      free(buf);
    }
  }

  return rcode;
}

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Crypto_base64_decode(struct v7 *v7, v7_val_t *res) {
  return b64_transform(v7, (b64_func_t) cs_base64_decode, 0.75, res);
}

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Crypto_base64_encode(struct v7 *v7, v7_val_t *res) {
  return b64_transform(v7, cs_base64_encode, 1.5, res);
}

static void v7_md5(const char *data, size_t len, char buf[16]) {
  MD5_CTX ctx;
  MD5_Init(&ctx);
  MD5_Update(&ctx, (unsigned char *) data, len);
  MD5_Final((unsigned char *) buf, &ctx);
}

static void v7_sha1(const char *data, size_t len, char buf[20]) {
  cs_sha1_ctx ctx;
  cs_sha1_init(&ctx);
  cs_sha1_update(&ctx, (unsigned char *) data, len);
  cs_sha1_final((unsigned char *) buf, &ctx);
}

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Crypto_md5(struct v7 *v7, v7_val_t *res) {
  enum v7_err rcode = V7_OK;
  v7_val_t arg0 = v7_arg(v7, 0);

  if (v7_is_string(arg0)) {
    size_t len;
    const char *data = v7_get_string(v7, &arg0, &len);
    char buf[16];
    v7_md5(data, len, buf);
    *res = v7_mk_string(v7, buf, sizeof(buf), 1);
    goto clean;
  }

  *res = V7_NULL;

clean:
  return rcode;
}

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Crypto_md5_hex(struct v7 *v7, v7_val_t *res) {
  enum v7_err rcode = V7_OK;
  v7_val_t arg0 = v7_arg(v7, 0);

  if (v7_is_string(arg0)) {
    size_t len;
    const char *data = v7_get_string(v7, &arg0, &len);
    char hash[16], buf[sizeof(hash) * 2 + 1];
    v7_md5(data, len, hash);
    cs_to_hex(buf, (unsigned char *) hash, sizeof(hash));
    *res = v7_mk_string(v7, buf, sizeof(buf) - 1, 1);
    goto clean;
  }
  *res = V7_NULL;

clean:
  return rcode;
}

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Crypto_sha1(struct v7 *v7, v7_val_t *res) {
  enum v7_err rcode = V7_OK;
  v7_val_t arg0 = v7_arg(v7, 0);

  if (v7_is_string(arg0)) {
    size_t len;
    const char *data = v7_get_string(v7, &arg0, &len);
    char buf[20];
    v7_sha1(data, len, buf);
    *res = v7_mk_string(v7, buf, sizeof(buf), 1);
    goto clean;
  }
  *res = V7_NULL;

clean:
  return rcode;
}

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Crypto_sha1_hex(struct v7 *v7, v7_val_t *res) {
  enum v7_err rcode = V7_OK;
  v7_val_t arg0 = v7_arg(v7, 0);

  if (v7_is_string(arg0)) {
    size_t len;
    const char *data = v7_get_string(v7, &arg0, &len);
    char hash[20], buf[sizeof(hash) * 2 + 1];
    v7_sha1(data, len, hash);
    cs_to_hex(buf, (unsigned char *) hash, sizeof(hash));
    *res = v7_mk_string(v7, buf, sizeof(buf) - 1, 1);
    goto clean;
  }
  *res = V7_NULL;

clean:
  return rcode;
}
#endif

void init_crypto(struct v7 *v7) {
#ifdef V7_ENABLE_CRYPTO
  v7_val_t obj = v7_mk_object(v7);
  v7_set(v7, v7_get_global(v7), "Crypto", 6, obj);
  v7_set_method(v7, obj, "md5", Crypto_md5);
  v7_set_method(v7, obj, "md5_hex", Crypto_md5_hex);
  v7_set_method(v7, obj, "sha1", Crypto_sha1);
  v7_set_method(v7, obj, "sha1_hex", Crypto_sha1_hex);
  v7_set_method(v7, obj, "base64_encode", Crypto_base64_encode);
  v7_set_method(v7, obj, "base64_decode", Crypto_base64_decode);
#else
  (void) v7;
#endif
}
#ifdef V7_MODULE_LINES
#line 1 "v7/src/varint.c"
#endif
/*
 * Copyright (c) 2014 Cesanta Software Limited
 * All rights reserved
 */

/* Amalgamated: #include "v7/src/internal.h" */
/* Amalgamated: #include "v7/src/varint.h" */

#if defined(__cplusplus)
extern "C" {
#endif /* __cplusplus */

/*
 * Strings in AST are encoded as tuples (length, string).
 * Length is variable-length: if high bit is set in a byte, next byte is used.
 * Maximum string length with such encoding is 2 ^ (7 * 4) == 256 MiB,
 * assuming that sizeof(size_t) == 4.
 * Small string length (less then 128 bytes) is encoded in 1 byte.
 */
V7_PRIVATE size_t decode_varint(const unsigned char *p, int *llen) {
  size_t i = 0, string_len = 0;

  do {
    /*
     * Each byte of varint contains 7 bits, in little endian order.
     * MSB is a continuation bit: it tells whether next byte is used.
     */
    string_len |= (p[i] & 0x7f) << (7 * i);
    /*
     * First we increment i, then check whether it is within boundary and
     * whether decoded byte had continuation bit set.
     */
  } while (++i < sizeof(size_t) && (p[i - 1] & 0x80));
  *llen = i;

  return string_len;
}

/* Return number of bytes to store length */
V7_PRIVATE int calc_llen(size_t len) {
  int n = 0;

  do {
    n++;
  } while (len >>= 7);

  return n;
}

V7_PRIVATE int encode_varint(size_t len, unsigned char *p) {
  int i, llen = calc_llen(len);

  for (i = 0; i < llen; i++) {
    p[i] = (len & 0x7f) | (i < llen - 1 ? 0x80 : 0);
    len >>= 7;
  }

  return llen;
}

#if defined(__cplusplus)
}
#endif /* __cplusplus */
#ifdef V7_MODULE_LINES
#line 1 "v7/src/tokenizer.c"
#endif
/*
 * Copyright (c) 2014 Cesanta Software Limited
 * All rights reserved
 */

/* Amalgamated: #include "common/cs_strtod.h" */
/* Amalgamated: #include "common/utf.h" */

/* Amalgamated: #include "v7/src/internal.h" */
/* Amalgamated: #include "v7/src/core.h" */

#if !defined(V7_NO_COMPILER)

/*
 * NOTE(lsm): Must be in the same order as enum for keywords. See comment
 * for function get_tok() for rationale for that.
 */
static const struct v7_vec_const s_keywords[] = {
    V7_VEC("break"),      V7_VEC("case"),     V7_VEC("catch"),
    V7_VEC("continue"),   V7_VEC("debugger"), V7_VEC("default"),
    V7_VEC("delete"),     V7_VEC("do"),       V7_VEC("else"),
    V7_VEC("false"),      V7_VEC("finally"),  V7_VEC("for"),
    V7_VEC("function"),   V7_VEC("if"),       V7_VEC("in"),
    V7_VEC("instanceof"), V7_VEC("new"),      V7_VEC("null"),
    V7_VEC("return"),     V7_VEC("switch"),   V7_VEC("this"),
    V7_VEC("throw"),      V7_VEC("true"),     V7_VEC("try"),
    V7_VEC("typeof"),     V7_VEC("var"),      V7_VEC("void"),
    V7_VEC("while"),      V7_VEC("with")};

V7_PRIVATE int is_reserved_word_token(enum v7_tok tok) {
  return tok >= TOK_BREAK && tok <= TOK_WITH;
}

/*
 * Move ptr to the next token, skipping comments and whitespaces.
 * Return number of new line characters detected.
 */
V7_PRIVATE int skip_to_next_tok(const char **ptr, const char *src_end) {
  const char *s = *ptr, *p = NULL;
  int num_lines = 0;

  while (s != p && s < src_end && *s != '\0' &&
         (isspace((unsigned char) *s) || *s == '/')) {
    p = s;
    while (s < src_end && *s != '\0' && isspace((unsigned char) *s)) {
      if (*s == '\n') num_lines++;
      s++;
    }
    if ((s + 1) < src_end && s[0] == '/' && s[1] == '/') {
      s += 2;
      while (s < src_end && s[0] != '\0' && s[0] != '\n') s++;
    }
    if ((s + 1) < src_end && s[0] == '/' && s[1] == '*') {
      s += 2;
      while (s < src_end && s[0] != '\0' && !(s[-1] == '/' && s[-2] == '*')) {
        if (s[0] == '\n') num_lines++;
        s++;
      }
    }
  }
  *ptr = s;

  return num_lines;
}

/* Advance `s` pointer to the end of identifier  */
static void ident(const char **s, const char *src_end) {
  const unsigned char *p = (unsigned char *) *s;
  int n;
  Rune r;

  while ((const char *) p < src_end && p[0] != '\0') {
    if (p[0] == '$' || p[0] == '_' || isalnum(p[0])) {
      /* $, _, or any alphanumeric are valid identifier characters */
      p++;
    } else if ((const char *) (p + 5) < src_end && p[0] == '\\' &&
               p[1] == 'u' && isxdigit(p[2]) && isxdigit(p[3]) &&
               isxdigit(p[4]) && isxdigit(p[5])) {
      /* Unicode escape, \uXXXX . Could be used like "var \u0078 = 1;" */
      p += 6;
    } else if ((n = chartorune(&r, (char *) p)) > 1 && isalpharune(r)) {
      /*
       * TODO(dfrank): the chartorune() call above can read `p` past the
       * src_end, so it might crash on incorrect code. The solution would be
       * to modify `chartorune()` to accept src_end argument as well.
       */
      /* Unicode alphanumeric character */
      p += n;
    } else {
      break;
    }
  }

  *s = (char *) p;
}

static enum v7_tok kw(const char *s, size_t len, int ntoks, enum v7_tok tok) {
  int i;

  for (i = 0; i < ntoks; i++) {
    if (s_keywords[(tok - TOK_BREAK) + i].len == len &&
        memcmp(s_keywords[(tok - TOK_BREAK) + i].p + 1, s + 1, len - 1) == 0)
      break;
  }

  return i == ntoks ? TOK_IDENTIFIER : (enum v7_tok)(tok + i);
}

static enum v7_tok punct1(const char **s, const char *src_end, int ch1,
                          enum v7_tok tok1, enum v7_tok tok2) {
  (*s)++;
  if (*s < src_end && **s == ch1) {
    (*s)++;
    return tok1;
  } else {
    return tok2;
  }
}

static enum v7_tok punct2(const char **s, const char *src_end, int ch1,
                          enum v7_tok tok1, int ch2, enum v7_tok tok2,
                          enum v7_tok tok3) {
  if ((*s + 2) < src_end && s[0][1] == ch1 && s[0][2] == ch2) {
    (*s) += 3;
    return tok2;
  }

  return punct1(s, src_end, ch1, tok1, tok3);
}

static enum v7_tok punct3(const char **s, const char *src_end, int ch1,
                          enum v7_tok tok1, int ch2, enum v7_tok tok2,
                          enum v7_tok tok3) {
  (*s)++;
  if (*s < src_end) {
    if (**s == ch1) {
      (*s)++;
      return tok1;
    } else if (**s == ch2) {
      (*s)++;
      return tok2;
    }
  }
  return tok3;
}

static void parse_number(const char *s, const char **end, double *num) {
  *num = cs_strtod(s, (char **) end);
}

static enum v7_tok parse_str_literal(const char **p, const char *src_end) {
  const char *s = *p;
  int quote = '\0';

  if (s < src_end) {
    quote = *s++;
  }

  /* Scan string literal, handle escape sequences */
  for (; s < src_end && *s != '\0' && *s != quote; s++) {
    if (*s == '\\') {
      switch (s[1]) {
        case 'b':
        case 'f':
        case 'n':
        case 'r':
        case 't':
        case 'v':
        case '\\':
          s++;
          break;
        default:
          if (s[1] == quote) s++;
          break;
      }
    }
  }

  if (s < src_end && *s == quote) {
    s++;
    *p = s;
    return TOK_STRING_LITERAL;
  } else {
    return TOK_END_OF_INPUT;
  }
}

/*
 * This function is the heart of the tokenizer.
 * Organized as a giant switch statement.
 *
 * Switch statement is by the first character of the input stream. If first
 * character begins with a letter, it could be either keyword or identifier.
 * get_tok() calls ident() which shifts `s` pointer to the end of the word.
 * Now, tokenizer knows that the word begins at `p` and ends at `s`.
 * It calls function kw() to scan over the keywords that start with `p[0]`
 * letter. Therefore, keyword tokens and keyword strings must be in the
 * same order, to let kw() function work properly.
 * If kw() finds a keyword match, it returns keyword token.
 * Otherwise, it returns TOK_IDENTIFIER.
 * NOTE(lsm): `prev_tok` is a previously parsed token. It is needed for
 * correctly parsing regex literals.
 */
V7_PRIVATE enum v7_tok get_tok(const char **s, const char *src_end, double *n,
                               enum v7_tok prev_tok) {
  const char *p = *s;

  if (p >= src_end) {
    return TOK_END_OF_INPUT;
  }

  switch (*p) {
    /* Letters */
    case 'a':
      ident(s, src_end);
      return TOK_IDENTIFIER;
    case 'b':
      ident(s, src_end);
      return kw(p, *s - p, 1, TOK_BREAK);
    case 'c':
      ident(s, src_end);
      return kw(p, *s - p, 3, TOK_CASE);
    case 'd':
      ident(s, src_end);
      return kw(p, *s - p, 4, TOK_DEBUGGER);
    case 'e':
      ident(s, src_end);
      return kw(p, *s - p, 1, TOK_ELSE);
    case 'f':
      ident(s, src_end);
      return kw(p, *s - p, 4, TOK_FALSE);
    case 'g':
    case 'h':
      ident(s, src_end);
      return TOK_IDENTIFIER;
    case 'i':
      ident(s, src_end);
      return kw(p, *s - p, 3, TOK_IF);
    case 'j':
    case 'k':
    case 'l':
    case 'm':
      ident(s, src_end);
      return TOK_IDENTIFIER;
    case 'n':
      ident(s, src_end);
      return kw(p, *s - p, 2, TOK_NEW);
    case 'o':
    case 'p':
    case 'q':
      ident(s, src_end);
      return TOK_IDENTIFIER;
    case 'r':
      ident(s, src_end);
      return kw(p, *s - p, 1, TOK_RETURN);
    case 's':
      ident(s, src_end);
      return kw(p, *s - p, 1, TOK_SWITCH);
    case 't':
      ident(s, src_end);
      return kw(p, *s - p, 5, TOK_THIS);
    case 'u':
      ident(s, src_end);
      return TOK_IDENTIFIER;
    case 'v':
      ident(s, src_end);
      return kw(p, *s - p, 2, TOK_VAR);
    case 'w':
      ident(s, src_end);
      return kw(p, *s - p, 2, TOK_WHILE);
    case 'x':
    case 'y':
    case 'z':
      ident(s, src_end);
      return TOK_IDENTIFIER;

    case '_':
    case '$':
    case 'A':
    case 'B':
    case 'C':
    case 'D':
    case 'E':
    case 'F':
    case 'G':
    case 'H':
    case 'I':
    case 'J':
    case 'K':
    case 'L':
    case 'M':
    case 'N':
    case 'O':
    case 'P':
    case 'Q':
    case 'R':
    case 'S':
    case 'T':
    case 'U':
    case 'V':
    case 'W':
    case 'X':
    case 'Y':
    case 'Z':
    case '\\': /* Identifier may start with unicode escape sequence */
      ident(s, src_end);
      return TOK_IDENTIFIER;

    /* Numbers */
    case '0':
    case '1':
    case '2':
    case '3':
    case '4':
    case '5':
    case '6':
    case '7':
    case '8':
    case '9':
      parse_number(p, s, n);
      return TOK_NUMBER;

    /* String literals */
    case '\'':
    case '"':
      return parse_str_literal(s, src_end);

    /* Punctuators */
    case '=':
      return punct2(s, src_end, '=', TOK_EQ, '=', TOK_EQ_EQ, TOK_ASSIGN);
    case '!':
      return punct2(s, src_end, '=', TOK_NE, '=', TOK_NE_NE, TOK_NOT);

    case '%':
      return punct1(s, src_end, '=', TOK_REM_ASSIGN, TOK_REM);
    case '*':
      return punct1(s, src_end, '=', TOK_MUL_ASSIGN, TOK_MUL);
    case '/':
      /*
       * TOK_DIV, TOK_DIV_ASSIGN, and TOK_REGEX_LITERAL start with `/` char.
       * Division can happen after an expression.
       * In expressions like this:
       *            a /= b; c /= d;
       * things between slashes is NOT a regex literal.
       * The switch below catches all cases where division happens.
       */
      switch (prev_tok) {
        case TOK_CLOSE_CURLY:
        case TOK_CLOSE_PAREN:
        case TOK_CLOSE_BRACKET:
        case TOK_IDENTIFIER:
        case TOK_NUMBER:
          return punct1(s, src_end, '=', TOK_DIV_ASSIGN, TOK_DIV);
        default:
          /* Not a division - this is a regex. Scan until closing slash */
          for (p++; p < src_end && *p != '\0' && *p != '\n'; p++) {
            if (*p == '\\') {
              /* Skip escape sequence */
              p++;
            } else if (*p == '/') {
              /* This is a closing slash */
              p++;
              /* Skip regex flags */
              while (*p == 'g' || *p == 'i' || *p == 'm') {
                p++;
              }
              *s = p;
              return TOK_REGEX_LITERAL;
            }
          }
          break;
      }
      return punct1(s, src_end, '=', TOK_DIV_ASSIGN, TOK_DIV);
    case '^':
      return punct1(s, src_end, '=', TOK_XOR_ASSIGN, TOK_XOR);

    case '+':
      return punct3(s, src_end, '+', TOK_PLUS_PLUS, '=', TOK_PLUS_ASSIGN,
                    TOK_PLUS);
    case '-':
      return punct3(s, src_end, '-', TOK_MINUS_MINUS, '=', TOK_MINUS_ASSIGN,
                    TOK_MINUS);
    case '&':
      return punct3(s, src_end, '&', TOK_LOGICAL_AND, '=', TOK_AND_ASSIGN,
                    TOK_AND);
    case '|':
      return punct3(s, src_end, '|', TOK_LOGICAL_OR, '=', TOK_OR_ASSIGN,
                    TOK_OR);

    case '<':
      if (*s + 1 < src_end && s[0][1] == '=') {
        (*s) += 2;
        return TOK_LE;
      }
      return punct2(s, src_end, '<', TOK_LSHIFT, '=', TOK_LSHIFT_ASSIGN,
                    TOK_LT);
    case '>':
      if (*s + 1 < src_end && s[0][1] == '=') {
        (*s) += 2;
        return TOK_GE;
      }
      if (*s + 3 < src_end && s[0][1] == '>' && s[0][2] == '>' &&
          s[0][3] == '=') {
        (*s) += 4;
        return TOK_URSHIFT_ASSIGN;
      }
      if (*s + 2 < src_end && s[0][1] == '>' && s[0][2] == '>') {
        (*s) += 3;
        return TOK_URSHIFT;
      }
      return punct2(s, src_end, '>', TOK_RSHIFT, '=', TOK_RSHIFT_ASSIGN,
                    TOK_GT);

    case '{':
      (*s)++;
      return TOK_OPEN_CURLY;
    case '}':
      (*s)++;
      return TOK_CLOSE_CURLY;
    case '(':
      (*s)++;
      return TOK_OPEN_PAREN;
    case ')':
      (*s)++;
      return TOK_CLOSE_PAREN;
    case '[':
      (*s)++;
      return TOK_OPEN_BRACKET;
    case ']':
      (*s)++;
      return TOK_CLOSE_BRACKET;
    case '.':
      switch (*(*s + 1)) {
        /* Numbers */
        case '0':
        case '1':
        case '2':
        case '3':
        case '4':
        case '5':
        case '6':
        case '7':
        case '8':
        case '9':
          parse_number(p, s, n);
          return TOK_NUMBER;
      }
      (*s)++;
      return TOK_DOT;
    case ';':
      (*s)++;
      return TOK_SEMICOLON;
    case ':':
      (*s)++;
      return TOK_COLON;
    case '?':
      (*s)++;
      return TOK_QUESTION;
    case '~':
      (*s)++;
      return TOK_TILDA;
    case ',':
      (*s)++;
      return TOK_COMMA;

    default: {
      /* Handle unicode variables */
      Rune r;
      if (chartorune(&r, *s) > 1 && isalpharune(r)) {
        ident(s, src_end);
        return TOK_IDENTIFIER;
      }
      return TOK_END_OF_INPUT;
    }
  }
}

#ifdef TEST_RUN
int main(void) {
  const char *src =
      "for (var fo++ = -1; /= <= 1.17; x<<) { == <<=, 'x')} "
      "Infinity %=x<<=2";
  const char *src_end = src + strlen(src);
  enum v7_tok tok;
  double num;
  const char *p = src;

  skip_to_next_tok(&src, src_end);
  while ((tok = get_tok(&src, src_end, &num)) != TOK_END_OF_INPUT) {
    printf("%d [%.*s]\n", tok, (int) (src - p), p);
    skip_to_next_tok(&src, src_end);
    p = src;
  }
  printf("%d [%.*s]\n", tok, (int) (src - p), p);

  return 0;
}
#endif

#endif /* V7_NO_COMPILER */
#ifdef V7_MODULE_LINES
#line 1 "v7/src/ast.c"
#endif
/*
 * Copyright (c) 2014 Cesanta Software Limited
 * All rights reserved
 */

/* Amalgamated: #include "common/cs_strtod.h" */
/* Amalgamated: #include "common/mbuf.h" */

/* Amalgamated: #include "v7/src/internal.h" */
/* Amalgamated: #include "v7/src/varint.h" */
/* Amalgamated: #include "v7/src/ast.h" */
/* Amalgamated: #include "v7/src/core.h" */
/* Amalgamated: #include "v7/src/string.h" */
/* Amalgamated: #include "common/str_util.h" */

#if !defined(V7_NO_COMPILER)

#ifdef V7_LARGE_AST
typedef uint32_t ast_skip_t;
#else
typedef uint16_t ast_skip_t;
#define AST_SKIP_MAX UINT16_MAX
#endif

#ifndef V7_DISABLE_AST_TAG_NAMES
#define AST_ENTRY(name, has_varint, has_inlined, num_skips, num_subtrees) \
  { (name), (has_varint), (has_inlined), (num_skips), (num_subtrees) }
#else
#define AST_ENTRY(name, has_varint, has_inlined, num_skips, num_subtrees) \
  { (has_varint), (has_inlined), (num_skips), (num_subtrees) }
#endif

/*
 * The structure of AST nodes cannot be described in portable ANSI C,
 * since they are variable length and packed (unaligned).
 *
 * Here each node's body is described with a pseudo-C structure notation.
 * The pseudo type `child` represents a variable length byte sequence
 * representing a fully serialized child node.
 *
 * `child body[]` represents a sequence of such subtrees.
 *
 * Pseudo-labels, such as `end:` represent the targets of skip fields
 * with the same name (e.g. `ast_skip_t end`).
 *
 * Skips allow skipping a subtree or sequence of subtrees.
 *
 * Sequences of subtrees (i.e. `child []`) have to be terminated by a skip:
 * they don't have a termination tag; all nodes whose position is before the
 * skip are part of the sequence.
 *
 * Skips are encoded as network-byte-order 16-bit offsets counted from the
 * first byte of the node body (i.e. not counting the tag itself).
 * This currently limits the the maximum size of a function body to 64k.
 *
 * Notes:
 *
 * - Some nodes contain skips just for performance or because it simplifies
 * the implementation of the interpreter. For example, technically, the FOR
 * node doesn't need the `body` skip in order to be correctly traversed.
 * However, being able to quickly skip the `iter` expression is useful
 * also because it allows the interpreter to avoid traversing the expression
 * subtree without evaluating it, just in order to find the next subtree.
 *
 * - The name `skip` was chosen because `offset` was too overloaded in general
 * and label` is part of our domain model (i.e. JS has a label AST node type).
 *
 *
 * So, each node has a mandatory field: *tag* (see `enum ast_tag`), and a
 * number of optional fields. Whether the node has one or another optional
 * field is determined by the *node descriptor*: `struct ast_node_def`. For
 * each node type (i.e. for each element of `enum ast_tag`) there is a
 * corresponding descriptor: see `ast_node_defs`.
 *
 * Optional fields are:
 *
 * - *varint*: a varint-encoded number. At the moment, this field is only used
 *   together with the next field: inlined data, and a varint number determines
 *   the inlined data length.
 * - *inlined data*: a node-specific data. Size of it is determined by the
 *   previous field: varint.
 * - *skips*: as explained above, these are integer offsets, encoded in
 *   big-endian. The number of skips is determined by the node descriptor
 *   (`struct ast_node_def`). The size of each skip is either 16 or 32 bits,
 *   depending on whether the macro `V7_LARGE_AST` is set. The order of skips
 *   is determined by the `enum ast_which_skip`. See examples below for
 *   clarity.
 * - *subtrees*: child nodes. Some nodes have fixed number of child nodes; in
 *   this case, the descriptor has non-zero field `num_subtrees`.  Otherwise,
 *   `num_subtrees` is zero, and consumer handles child nodes one by one, until
 *   the end of the node is reached (end of the node is determined by the `end`
 *   skip)
 *
 *
 * Examples:
 *
 * Let's start from the very easy example script: "300;"
 *
 * Tree looks as follows:
 *
 *    $ ./v7 -e "300;" -t
 *      SCRIPT
 *        /- [...] -/
 *        NUM 300
 *
 * Binary data is:
 *
 *    $ ./v7 -e "300;" -b | od -A n -t x1
 *    56 07 41 53 54 56 31 30 00 01 00 09 00 00 13 03
 *    33 30 30
 *
 * Let's break it down and examine:
 *
 *    - 56 07 41 53 54 56 31 30 00
 *        Just a format prefix:
 *        Null-terminated string: `"V\007ASTV10"` (see `BIN_AST_SIGNATURE`)
 *    - 01
 *        AST tag: `AST_SCRIPT`. As you see in `ast_node_defs` below, node of
 *        this type has neither *varint* nor *inlined data* fields, but it has
 *        2 skips: `end` and `next`. `end` is a skip to the end of the current
 *        node (`SCRIPT`), and `next` will be explained below.
 *
 *        The size of each skip depends on whether `V7_LARGE_AST` is defined.
 *        If it is, then size is 32 bit, otherwise it's 16 bit. In this
 *        example, we have 16-bit skips.
 *
 *        The order of skips is determined by the `enum ast_which_skip`. If you
 *        check, you'll see that `AST_END_SKIP` is 0, and `AST_VAR_NEXT_SKIP`
 *        is 1. So, `end` skip fill be the first, and `next` will be the second:
 *    - 00 09
 *        `end` skip: 9 bytes. It's the size of the whole `SCRIPT` data. So, if
 *        we have an index of the `ASC_SCRIPT` tag, we can just add this skip
 *        (9) to this index, and therefore skip over the whole node.
 *    - 00 00
 *        `next` skip. `next` actually means "next variable node": since
 *        variables are hoisted in JavaScript, when the interpreter starts
 *        executing a top-level code or any function, it needs to get a list of
 *        all defined variables. The `SCRIPT` node has a "skip" to the first
 *        `var` or `function` declaration, which, in turn, has a "skip" to the
 *        next one, etc. If there is no next `var` declaration, then 0 is
 *        stored.
 *
 *        In our super-simple script, we have no `var` neither `function`
 *        declarations, so, this skip is 0.
 *
 *        Now, the body of our SCRIPT node goes, which contains child nodes:
 *
 *    - 13
 *        AST tag: `AST_NUM`. Look at the `ast_node_defs`, and we'll see that
 *        nodes of this type don't have any skips, but they do have the varint
 *        field and the inlined data. Here we go:
 *    - 03
 *        Varint value: 3
 *    - 33 30 30
 *        UTF-8 string "300"
 *
 * ---------------
 *
 * The next example is a bit more interesting:
 *
 *    var foo,
 *        bar = 1;
 *    foo = 3;
 *    var baz = 4;
 *
 * Tree:
 *
 *    $ ./v7 -e 'var foo, bar=1; foo=3; var baz = 4;' -t
 *    SCRIPT
 *      /- [...] -/
 *      VAR
 *        /- [...] -/
 *        VAR_DECL foo
 *          NOP
 *        VAR_DECL bar
 *          NUM 1
 *      ASSIGN
 *        IDENT foo
 *        NUM 3
 *      VAR
 *        /- [...] -/
 *        VAR_DECL baz
 *          NUM 4
 *
 * Binary:
 *
 *    $ ./v7 -e 'var foo, bar=1; foo=3; var baz = 4;' -b | od -A n -t x1
 *    56 07 41 53 54 56 31 30 00 01 00 2d 00 05 02 00
 *    12 00 1c 03 03 66 6f 6f 00 03 03 62 61 72 13 01
 *    31 07 14 03 66 6f 6f 13 01 33 02 00 0c 00 00 03
 *    03 62 61 7a 13 01 34
 *
 * Break it down:
 *
 *    - 56 07 41 53 54 56 31 30 00
 *        `"V\007ASTV10"`
 *    - 01:       AST tag: `AST_SCRIPT`
 *    - 00 2d:    `end` skip: 0x2d = 45 bytes
 *    - 00 05:    `next` skip: an offset from `AST_SCRIPT` byte to the first
 *                `var` declaration.
 *
 *        Now, body of the SCRIPT node begins, which contains child nodes,
 *        and the first node is the var declaration `var foo, bar=1;`:
 *
 *        SCRIPT node body: {{{
 *    - 02:       AST tag: `AST_VAR`
 *    - 00 12:    `end` skip: 18 bytes from tag byte to the end of current node
 *    - 00 1c:    `next` skip: 28 bytes from tag byte to the next `var` node
 *
 *        The VAR node contains arbitrary number of child nodes, so, consumer
 *        takes advantage of the `end` skip.
 *
 *        VAR node body: {{{
 *    - 03:       AST tag: `AST_VAR_DECL`
 *    - 03:       Varint value: 3 (the length of the inlined data: a variable
 *name)
 *    - 66 6f 6f: UTF-8 string: "foo"
 *    - 00:       AST tag: `AST_NOP`
 *                Since we haven't provided any value to store into `foo`, NOP
 *                without any additional data is stored in AST.
 *
 *    - 03:       AST tag: `AST_VAR_DECL`
 *    - 03:       Varint value: 3 (the length of the inlined data: a variable
 *name)
 *    - 62 61 72: UTF-8 string: "bar"
 *    - 13:       AST tag: `AST_NUM`
 *    - 01:       Varint value: 1
 *    - 31:       UTF-8 string "1"
 *        VAR body end }}}
 *
 *    - 07:       AST tag: `AST_ASSIGN`
 *
 *        The ASSIGN node has fixed number of subrees: 2 (lvalue and rvalue),
 *        so there's no `end` skip.
 *
 *        ASSIGN node body: {{{
 *    - 14:       AST tag: `AST_IDENT`
 *    - 03:       Varint value: 3
 *    - 66 6f 6f: UTF-8 string: "foo"
 *
 *    - 13:       AST tag: `AST_NUM`
 *    - 01:       Varint value: 1
 *    - 33:       UTF-8 string: "3"
 *        ASSIGN body end }}}
 *
 *    - 02:       AST tag: `AST_VAR`
 *    - 00 0c:    `end` skip: 12 bytes from tag byte to the end of current node
 *    - 00 00:    `next` skip: no more `var` nodes
 *
 *        VAR node body: {{{
 *    - 03:       AST tag: `AST_VAR_DECL`
 *    - 03:       Varint value: 3 (the length of the inlined data: a variable
 *name)
 *    - 62 61 7a: UTF-8 string: "baz"
 *    - 13:       AST tag: `AST_NUM`
 *    - 01:       Varint value: 1
 *    - 34:       UTF-8 string "4"
 *        VAR body end }}}
 *        SCRIPT body end }}}
 *
 * --------------------------
 */

const struct ast_node_def ast_node_defs[] = {
    AST_ENTRY("NOP", 0, 0, 0, 0), /* struct {} */

    /*
     * struct {
     *   ast_skip_t end;
     *   ast_skip_t first_var;
     *   child body[];
     * end:
     * }
     */
    AST_ENTRY("SCRIPT", 0, 0, 2, 0),
    /*
     * struct {
     *   ast_skip_t end;
     *   ast_skip_t next;
     *   child decls[];
     * end:
     * }
     */
    AST_ENTRY("VAR", 0, 0, 2, 0),
    /*
     * struct {
     *   varint len;
     *   char name[len];
     *   child expr;
     * }
     */
    AST_ENTRY("VAR_DECL", 1, 1, 0, 1),
    /*
     * struct {
     *   varint len;
     *   char name[len];
     *   child expr;
     * }
     */
    AST_ENTRY("FUNC_DECL", 1, 1, 0, 1),
    /*
     * struct {
     *   ast_skip_t end;
     *   ast_skip_t end_true;
     *   child cond;
     *   child iftrue[];
     * end_true:
     *   child iffalse[];
     * end:
     * }
     */
    AST_ENTRY("IF", 0, 0, 2, 1),
    /*
     * TODO(mkm) distinguish function expressions
     * from function statements.
     * Function statements behave like vars and need a
     * next field for hoisting.
     * We can also ignore the name for function expressions
     * if it's only needed for debugging.
     *
     * struct {
     *   ast_skip_t end;
     *   ast_skip_t first_var;
     *   ast_skip_t body;
     *   child name;
     *   child params[];
     * body:
     *   child body[];
     * end:
     * }
     */
    AST_ENTRY("FUNC", 0, 0, 3, 1),
    AST_ENTRY("ASSIGN", 0, 0, 0, 2),         /* struct { child left, right; } */
    AST_ENTRY("REM_ASSIGN", 0, 0, 0, 2),     /* struct { child left, right; } */
    AST_ENTRY("MUL_ASSIGN", 0, 0, 0, 2),     /* struct { child left, right; } */
    AST_ENTRY("DIV_ASSIGN", 0, 0, 0, 2),     /* struct { child left, right; } */
    AST_ENTRY("XOR_ASSIGN", 0, 0, 0, 2),     /* struct { child left, right; } */
    AST_ENTRY("PLUS_ASSIGN", 0, 0, 0, 2),    /* struct { child left, right; } */
    AST_ENTRY("MINUS_ASSIGN", 0, 0, 0, 2),   /* struct { child left, right; } */
    AST_ENTRY("OR_ASSIGN", 0, 0, 0, 2),      /* struct { child left, right; } */
    AST_ENTRY("AND_ASSIGN", 0, 0, 0, 2),     /* struct { child left, right; } */
    AST_ENTRY("LSHIFT_ASSIGN", 0, 0, 0, 2),  /* struct { child left, right; } */
    AST_ENTRY("RSHIFT_ASSIGN", 0, 0, 0, 2),  /* struct { child left, right; } */
    AST_ENTRY("URSHIFT_ASSIGN", 0, 0, 0, 2), /* struct { child left, right; } */
    AST_ENTRY("NUM", 1, 1, 0, 0),    /* struct { varint len, char s[len]; } */
    AST_ENTRY("IDENT", 1, 1, 0, 0),  /* struct { varint len, char s[len]; } */
    AST_ENTRY("STRING", 1, 1, 0, 0), /* struct { varint len, char s[len]; } */
    AST_ENTRY("REGEX", 1, 1, 0, 0),  /* struct { varint len, char s[len]; } */
    AST_ENTRY("LABEL", 1, 1, 0, 0),  /* struct { varint len, char s[len]; } */

    /*
     * struct {
     *   ast_skip_t end;
     *   child body[];
     * end:
     * }
     */
    AST_ENTRY("SEQ", 0, 0, 1, 0),
    /*
     * struct {
     *   ast_skip_t end;
     *   child cond;
     *   child body[];
     * end:
     * }
     */
    AST_ENTRY("WHILE", 0, 0, 1, 1),
    /*
     * struct {
     *   ast_skip_t end;
     *   ast_skip_t cond;
     *   child body[];
     * cond:
     *   child cond;
     * end:
     * }
     */
    AST_ENTRY("DOWHILE", 0, 0, 2, 0),
    /*
     * struct {
     *   ast_skip_t end;
     *   ast_skip_t body;
     *   child init;
     *   child cond;
     *   child iter;
     * body:
     *   child body[];
     * end:
     * }
     */
    AST_ENTRY("FOR", 0, 0, 2, 3),
    /*
     * struct {
     *   ast_skip_t end;
     *   ast_skip_t dummy; // allows to quickly promote a for to a for in
     *   child var;
     *   child expr;
     *   child dummy;
     *   child body[];
     * end:
     * }
     */
    AST_ENTRY("FOR_IN", 0, 0, 2, 3),
    AST_ENTRY("COND", 0, 0, 0, 3), /* struct { child cond, iftrue, iffalse; } */
    AST_ENTRY("DEBUGGER", 0, 0, 0, 0), /* struct {} */
    AST_ENTRY("BREAK", 0, 0, 0, 0),    /* struct {} */

    /*
     * struct {
     *   child label; // TODO(mkm): inline
     * }
     */
    AST_ENTRY("LAB_BREAK", 0, 0, 0, 1),
    AST_ENTRY("CONTINUE", 0, 0, 0, 0), /* struct {} */

    /*
     * struct {
     *   child label; // TODO(mkm): inline
     * }
     */
    AST_ENTRY("LAB_CONTINUE", 0, 0, 0, 1),
    AST_ENTRY("RETURN", 0, 0, 0, 0),     /* struct {} */
    AST_ENTRY("VAL_RETURN", 0, 0, 0, 1), /* struct { child expr; } */
    AST_ENTRY("THROW", 0, 0, 0, 1),      /* struct { child expr; } */

    /*
     * struct {
     *   ast_skip_t end;
     *   ast_skip_t catch;
     *   ast_skip_t finally;
     *   child try[];
     * catch:
     *   child var; // TODO(mkm): inline
     *   child catch[];
     * finally:
     *   child finally[];
     * end:
     * }
     */
    AST_ENTRY("TRY", 0, 0, 3, 1),
    /*
     * struct {
     *   ast_skip_t end;
     *   ast_skip_t def;
     *   child expr;
     *   child cases[];
     * def:
     *   child default?; // optional
     * end:
     * }
     */
    AST_ENTRY("SWITCH", 0, 0, 2, 1),
    /*
     * struct {
     *   ast_skip_t end;
     *   child val;
     *   child stmts[];
     * end:
     * }
     */
    AST_ENTRY("CASE", 0, 0, 1, 1),
    /*
     * struct {
     *   ast_skip_t end;
     *   child stmts[];
     * end:
     * }
     */
    AST_ENTRY("DEFAULT", 0, 0, 1, 0),
    /*
     * struct {
     *   ast_skip_t end;
     *   child expr;
     *   child body[];
     * end:
     * }
     */
    AST_ENTRY("WITH", 0, 0, 1, 1),
    AST_ENTRY("LOG_OR", 0, 0, 0, 2),      /* struct { child left, right; } */
    AST_ENTRY("LOG_AND", 0, 0, 0, 2),     /* struct { child left, right; } */
    AST_ENTRY("OR", 0, 0, 0, 2),          /* struct { child left, right; } */
    AST_ENTRY("XOR", 0, 0, 0, 2),         /* struct { child left, right; } */
    AST_ENTRY("AND", 0, 0, 0, 2),         /* struct { child left, right; } */
    AST_ENTRY("EQ", 0, 0, 0, 2),          /* struct { child left, right; } */
    AST_ENTRY("EQ_EQ", 0, 0, 0, 2),       /* struct { child left, right; } */
    AST_ENTRY("NE", 0, 0, 0, 2),          /* struct { child left, right; } */
    AST_ENTRY("NE_NE", 0, 0, 0, 2),       /* struct { child left, right; } */
    AST_ENTRY("LE", 0, 0, 0, 2),          /* struct { child left, right; } */
    AST_ENTRY("LT", 0, 0, 0, 2),          /* struct { child left, right; } */
    AST_ENTRY("GE", 0, 0, 0, 2),          /* struct { child left, right; } */
    AST_ENTRY("GT", 0, 0, 0, 2),          /* struct { child left, right; } */
    AST_ENTRY("IN", 0, 0, 0, 2),          /* struct { child left, right; } */
    AST_ENTRY("INSTANCEOF", 0, 0, 0, 2),  /* struct { child left, right; } */
    AST_ENTRY("LSHIFT", 0, 0, 0, 2),      /* struct { child left, right; } */
    AST_ENTRY("RSHIFT", 0, 0, 0, 2),      /* struct { child left, right; } */
    AST_ENTRY("URSHIFT", 0, 0, 0, 2),     /* struct { child left, right; } */
    AST_ENTRY("ADD", 0, 0, 0, 2),         /* struct { child left, right; } */
    AST_ENTRY("SUB", 0, 0, 0, 2),         /* struct { child left, right; } */
    AST_ENTRY("REM", 0, 0, 0, 2),         /* struct { child left, right; } */
    AST_ENTRY("MUL", 0, 0, 0, 2),         /* struct { child left, right; } */
    AST_ENTRY("DIV", 0, 0, 0, 2),         /* struct { child left, right; } */
    AST_ENTRY("POS", 0, 0, 0, 1),         /* struct { child expr; } */
    AST_ENTRY("NEG", 0, 0, 0, 1),         /* struct { child expr; } */
    AST_ENTRY("NOT", 0, 0, 0, 1),         /* struct { child expr; } */
    AST_ENTRY("LOGICAL_NOT", 0, 0, 0, 1), /* struct { child expr; } */
    AST_ENTRY("VOID", 0, 0, 0, 1),        /* struct { child expr; } */
    AST_ENTRY("DELETE", 0, 0, 0, 1),      /* struct { child expr; } */
    AST_ENTRY("TYPEOF", 0, 0, 0, 1),      /* struct { child expr; } */
    AST_ENTRY("PREINC", 0, 0, 0, 1),      /* struct { child expr; } */
    AST_ENTRY("PREDEC", 0, 0, 0, 1),      /* struct { child expr; } */
    AST_ENTRY("POSTINC", 0, 0, 0, 1),     /* struct { child expr; } */
    AST_ENTRY("POSTDEC", 0, 0, 0, 1),     /* struct { child expr; } */

    /*
     * struct {
     *   varint len;
     *   char ident[len];
     *   child expr;
     * }
     */
    AST_ENTRY("MEMBER", 1, 1, 0, 1),
    /*
     * struct {
     *   child expr;
     *   child index;
     * }
     */
    AST_ENTRY("INDEX", 0, 0, 0, 2),
    /*
     * struct {
     *   ast_skip_t end;
     *   child expr;
     *   child args[];
     * end:
     * }
     */
    AST_ENTRY("CALL", 0, 0, 1, 1),
    /*
     * struct {
     *   ast_skip_t end;
     *   child expr;
     *   child args[];
     * end:
     * }
     */
    AST_ENTRY("NEW", 0, 0, 1, 1),
    /*
     * struct {
     *   ast_skip_t end;
     *   child elements[];
     * end:
     * }
     */
    AST_ENTRY("ARRAY", 0, 0, 1, 0),
    /*
     * struct {
     *   ast_skip_t end;
     *   child props[];
     * end:
     * }
     */
    AST_ENTRY("OBJECT", 0, 0, 1, 0),
    /*
     * struct {
     *   varint len;
     *   char name[len];
     *   child expr;
     * }
     */
    AST_ENTRY("PROP", 1, 1, 0, 1),
    /*
     * struct {
     *   child func;
     * }
     */
    AST_ENTRY("GETTER", 0, 0, 0, 1),
    /*
     * struct {
     *   child func;
     * end:
     * }
     */
    AST_ENTRY("SETTER", 0, 0, 0, 1),
    AST_ENTRY("THIS", 0, 0, 0, 0),       /* struct {} */
    AST_ENTRY("TRUE", 0, 0, 0, 0),       /* struct {} */
    AST_ENTRY("FALSE", 0, 0, 0, 0),      /* struct {} */
    AST_ENTRY("NULL", 0, 0, 0, 0),       /* struct {} */
    AST_ENTRY("UNDEF", 0, 0, 0, 0),      /* struct {} */
    AST_ENTRY("USE_STRICT", 0, 0, 0, 0), /* struct {} */
};

/*
 * A flag which is used to mark node's tag byte if the node has line number
 * data encoded (varint after skips). See `ast_get_line_no()`.
 */
#define AST_TAG_LINENO_PRESENT 0x80

V7_STATIC_ASSERT(AST_MAX_TAG < 256, ast_tag_should_fit_in_char);
V7_STATIC_ASSERT(AST_MAX_TAG == ARRAY_SIZE(ast_node_defs), bad_node_defs);
V7_STATIC_ASSERT(AST_MAX_TAG <= AST_TAG_LINENO_PRESENT, bad_AST_LINE_NO);

#if V7_ENABLE_FOOTPRINT_REPORT
const size_t ast_node_defs_size = sizeof(ast_node_defs);
const size_t ast_node_defs_count = ARRAY_SIZE(ast_node_defs);
#endif

/*
 * Converts a given byte `t` (which should be read from the AST data buffer)
 * into `enum ast_tag`. This function is needed because tag might be marked
 * with the `AST_TAG_LINENO_PRESENT` flag; the returned tag is always unmarked,
 * and if the flag was indeed set, `lineno_present` is set to 1; otherwise
 * it is set to 0.
 *
 * `lineno_present` is allowed to be NULL, if the caller doesn't care of the
 * line number presence.
 */
static enum ast_tag uint8_to_tag(uint8_t t, uint8_t *lineno_present) {
  if (t & AST_TAG_LINENO_PRESENT) {
    t &= ~AST_TAG_LINENO_PRESENT;
    if (lineno_present != NULL) {
      *lineno_present = 1;
    }
  } else if (lineno_present != NULL) {
    *lineno_present = 0;
  }
  return (enum ast_tag) t;
}

V7_PRIVATE ast_off_t
ast_insert_node(struct ast *a, ast_off_t pos, enum ast_tag tag) {
  uint8_t t = (uint8_t) tag;
  const struct ast_node_def *d = &ast_node_defs[tag];
  ast_off_t cur = pos;

  assert(tag < AST_MAX_TAG);

  mbuf_insert(&a->mbuf, cur, (char *) &t, sizeof(t));
  cur += sizeof(t);

  mbuf_insert(&a->mbuf, cur, NULL, sizeof(ast_skip_t) * d->num_skips);
  cur += sizeof(ast_skip_t) * d->num_skips;

  if (d->num_skips) {
    ast_set_skip(a, pos + 1, AST_END_SKIP);
  }

  return pos + 1;
}

V7_PRIVATE void ast_modify_tag(struct ast *a, ast_off_t tag_off,
                               enum ast_tag tag) {
  a->mbuf.buf[tag_off] = tag | (a->mbuf.buf[tag_off] & 0x80);
}

#ifndef V7_DISABLE_LINE_NUMBERS
V7_PRIVATE void ast_add_line_no(struct ast *a, ast_off_t tag_off, int line_no) {
  ast_off_t ln_off = tag_off + 1 /* tag byte */;
  int llen = calc_llen(line_no);

  ast_move_to_inlined_data(a, &ln_off);
  mbuf_insert(&a->mbuf, ln_off, NULL, llen);
  encode_varint(line_no, (unsigned char *) (a->mbuf.buf + ln_off));

  assert(a->mbuf.buf[tag_off] < AST_MAX_TAG);
  a->mbuf.buf[tag_off] |= AST_TAG_LINENO_PRESENT;
}
#endif

V7_PRIVATE ast_off_t
ast_set_skip(struct ast *a, ast_off_t pos, enum ast_which_skip skip) {
  return ast_modify_skip(a, pos, a->mbuf.len, skip);
}

V7_PRIVATE ast_off_t ast_modify_skip(struct ast *a, ast_off_t pos,
                                     ast_off_t where,
                                     enum ast_which_skip skip) {
  uint8_t *p = (uint8_t *) a->mbuf.buf + pos + skip * sizeof(ast_skip_t);
  ast_skip_t delta = where - pos;
#ifndef NDEBUG
  enum ast_tag tag = uint8_to_tag(*(a->mbuf.buf + pos - 1), NULL);
  const struct ast_node_def *def = &ast_node_defs[tag];
#endif
  assert(pos <= where);

#ifndef V7_LARGE_AST
  /* the value of delta overflowed, therefore the ast is not useable */
  if (where - pos > AST_SKIP_MAX) {
    a->has_overflow = 1;
  }
#endif

  /* assertion, to be optimizable out */
  assert((int) skip < def->num_skips);

#ifdef V7_LARGE_AST
  p[0] = delta >> 24;
  p[1] = delta >> 16 & 0xff;
  p[2] = delta >> 8 & 0xff;
  p[3] = delta & 0xff;
#else
  p[0] = delta >> 8;
  p[1] = delta & 0xff;
#endif
  return where;
}

V7_PRIVATE ast_off_t
ast_get_skip(struct ast *a, ast_off_t pos, enum ast_which_skip skip) {
  uint8_t *p;
  assert(pos + skip * sizeof(ast_skip_t) < a->mbuf.len);

  p = (uint8_t *) a->mbuf.buf + pos + skip * sizeof(ast_skip_t);
#ifdef V7_LARGE_AST
  return pos + (p[3] | p[2] << 8 | p[1] << 16 | p[0] << 24);
#else
  return pos + (p[1] | p[0] << 8);
#endif
}

V7_PRIVATE enum ast_tag ast_fetch_tag(struct ast *a, ast_off_t *ppos) {
  enum ast_tag ret;
  assert(*ppos < a->mbuf.len);

  ret = uint8_to_tag(*(a->mbuf.buf + (*ppos)++), NULL);

  return ret;
}

V7_PRIVATE void ast_move_to_children(struct ast *a, ast_off_t *ppos) {
  enum ast_tag tag = uint8_to_tag(*(a->mbuf.buf + *ppos - 1), NULL);
  const struct ast_node_def *def = &ast_node_defs[tag];
  assert(*ppos - 1 < a->mbuf.len);

  ast_move_to_inlined_data(a, ppos);

  /* skip varint + inline data, if present */
  if (def->has_varint) {
    int llen;
    size_t slen = decode_varint((unsigned char *) a->mbuf.buf + *ppos, &llen);
    *ppos += llen;
    if (def->has_inlined) {
      *ppos += slen;
    }
  }
}

V7_PRIVATE ast_off_t ast_insert_inlined_node(struct ast *a, ast_off_t pos,
                                             enum ast_tag tag, const char *name,
                                             size_t len) {
  const struct ast_node_def *d = &ast_node_defs[tag];

  ast_off_t offset = ast_insert_node(a, pos, tag);

  assert(d->has_inlined);

  embed_string(&a->mbuf, offset + sizeof(ast_skip_t) * d->num_skips, name, len,
               EMBSTR_UNESCAPE);

  return offset;
}

V7_PRIVATE int ast_get_line_no(struct ast *a, ast_off_t pos) {
  /*
   * by default we'll return 0, meaning that the AST node does not contain line
   * number data
   */
  int ret = 0;

#ifndef V7_DISABLE_LINE_NUMBERS
  uint8_t lineno_present;
  enum ast_tag tag = uint8_to_tag(*(a->mbuf.buf + pos - 1), &lineno_present);

  if (lineno_present) {
    /* line number is present, so, let's decode it */
    int llen;

    /* skip skips */
    pos += ast_node_defs[tag].num_skips * sizeof(ast_skip_t);

    /* get line number */
    ret = decode_varint((unsigned char *) a->mbuf.buf + pos, &llen);
  }
#else
  (void) a;
  (void) pos;
#endif

  return ret;
}

V7_PRIVATE void ast_move_to_inlined_data(struct ast *a, ast_off_t *ppos) {
  uint8_t lineno_present = 0;
  enum ast_tag tag = uint8_to_tag(*(a->mbuf.buf + *ppos - 1), &lineno_present);
  const struct ast_node_def *def = &ast_node_defs[tag];
  assert(*ppos - 1 < a->mbuf.len);

  /* skip skips */
  *ppos += def->num_skips * sizeof(ast_skip_t);

  /* skip line_no, if present */
  if (lineno_present) {
    int llen;
    int line_no = decode_varint((unsigned char *) a->mbuf.buf + *ppos, &llen);
    *ppos += llen;

    (void) line_no;
  }
}

V7_PRIVATE char *ast_get_inlined_data(struct ast *a, ast_off_t pos, size_t *n) {
  int llen;
  assert(pos < a->mbuf.len);

  ast_move_to_inlined_data(a, &pos);

  *n = decode_varint((unsigned char *) a->mbuf.buf + pos, &llen);
  return a->mbuf.buf + pos + llen;
}

V7_PRIVATE double ast_get_num(struct ast *a, ast_off_t pos) {
  double ret;
  char *str;
  size_t str_len;
  char buf[12];
  char *p = buf;
  str = ast_get_inlined_data(a, pos, &str_len);
  assert(str + str_len <= a->mbuf.buf + a->mbuf.len);

  if (str_len > sizeof(buf) - 1) {
    p = (char *) malloc(str_len + 1);
  }
  strncpy(p, str, str_len);
  p[str_len] = '\0';
  ret = cs_strtod(p, NULL);
  if (p != buf) free(p);
  return ret;
}

#ifndef NO_LIBC
static void comment_at_depth(FILE *fp, const char *fmt, int depth, ...) {
  int i;
  STATIC char buf[256];
  va_list ap;
  va_start(ap, depth);

  c_vsnprintf(buf, sizeof(buf), fmt, ap);

  for (i = 0; i < depth; i++) {
    fprintf(fp, "  ");
  }
  fprintf(fp, "/* [%s] */\n", buf);
}
#endif

V7_PRIVATE void ast_skip_tree(struct ast *a, ast_off_t *ppos) {
  enum ast_tag tag = ast_fetch_tag(a, ppos);
  const struct ast_node_def *def = &ast_node_defs[tag];
  ast_off_t skips = *ppos;
  int i;
  ast_move_to_children(a, ppos);

  for (i = 0; i < def->num_subtrees; i++) {
    ast_skip_tree(a, ppos);
  }

  if (def->num_skips > AST_END_SKIP) {
    ast_off_t end = ast_get_skip(a, skips, AST_END_SKIP);

    while (*ppos < end) {
      ast_skip_tree(a, ppos);
    }
  }
}

#ifndef NO_LIBC
V7_PRIVATE void ast_dump_tree(FILE *fp, struct ast *a, ast_off_t *ppos,
                              int depth) {
  enum ast_tag tag = ast_fetch_tag(a, ppos);
  const struct ast_node_def *def = &ast_node_defs[tag];
  ast_off_t skips = *ppos;
  size_t slen;
  int i, llen;

  for (i = 0; i < depth; i++) {
    fprintf(fp, "  ");
  }

#ifndef V7_DISABLE_AST_TAG_NAMES
  fprintf(fp, "%s", def->name);
#else
  fprintf(fp, "TAG_%d", tag);
#endif

  if (def->has_inlined) {
    ast_off_t pos_tmp = *ppos;
    ast_move_to_inlined_data(a, &pos_tmp);

    slen = decode_varint((unsigned char *) a->mbuf.buf + pos_tmp, &llen);
    fprintf(fp, " %.*s\n", (int) slen, a->mbuf.buf + pos_tmp + llen);
  } else {
    fprintf(fp, "\n");
  }

  ast_move_to_children(a, ppos);

  for (i = 0; i < def->num_subtrees; i++) {
    ast_dump_tree(fp, a, ppos, depth + 1);
  }

  if (ast_node_defs[tag].num_skips) {
    /*
     * first skip always encodes end of the last children sequence.
     * so unless we care how the subtree sequences are grouped together
     * (and we currently don't) we can just read until the end of that skip.
     */
    ast_off_t end = ast_get_skip(a, skips, AST_END_SKIP);

    comment_at_depth(fp, "...", depth + 1);
    while (*ppos < end) {
      int s;
      for (s = ast_node_defs[tag].num_skips - 1; s > 0; s--) {
        if (*ppos == ast_get_skip(a, skips, (enum ast_which_skip) s)) {
          comment_at_depth(fp, "%d ->", depth + 1, s);
          break;
        }
      }
      ast_dump_tree(fp, a, ppos, depth + 1);
    }
  }
}
#endif

V7_PRIVATE void ast_init(struct ast *ast, size_t len) {
  mbuf_init(&ast->mbuf, len);
  ast->refcnt = 0;
  ast->has_overflow = 0;
}

V7_PRIVATE void ast_optimize(struct ast *ast) {
  /*
   * leave one trailing byte so that literals can be
   * null terminated on the fly.
   */
  mbuf_resize(&ast->mbuf, ast->mbuf.len + 1);
}

V7_PRIVATE void ast_free(struct ast *ast) {
  mbuf_free(&ast->mbuf);
  ast->refcnt = 0;
  ast->has_overflow = 0;
}

V7_PRIVATE void release_ast(struct v7 *v7, struct ast *a) {
  (void) v7;

  if (a->refcnt != 0) a->refcnt--;

  if (a->refcnt == 0) {
#if V7_ENABLE__Memory__stats
    v7->function_arena_ast_size -= a->mbuf.size;
#endif
    ast_free(a);
    free(a);
  }
}

#endif /* V7_NO_COMPILER */
#ifdef V7_MODULE_LINES
#line 1 "v7/src/bcode.c"
#endif
/*
 * Copyright (c) 2014 Cesanta Software Limited
 * All rights reserved
 */

/* Amalgamated: #include "v7/src/internal.h" */
/* Amalgamated: #include "v7/src/bcode.h" */
/* Amalgamated: #include "v7/src/varint.h" */
/* Amalgamated: #include "v7/src/exceptions.h" */
/* Amalgamated: #include "v7/src/gc.h" */
/* Amalgamated: #include "v7/src/core.h" */
/* Amalgamated: #include "v7/src/regexp.h" */
/* Amalgamated: #include "v7/src/function.h" */
/* Amalgamated: #include "v7/src/util.h" */
/* Amalgamated: #include "v7/src/shdata.h" */

/*
 * TODO(dfrank): implement `bcode_serialize_*` more generically, so that they
 * can write to buffer instead of a `FILE`. Then, remove a need for mmap here.
 */
#if CS_PLATFORM == CS_P_UNIX
#include <sys/mman.h>
#endif

#if defined(V7_BCODE_DUMP) || defined(V7_BCODE_TRACE)
/* clang-format off */
static const char *op_names[] = {
  "DROP",
  "DUP",
  "2DUP",
  "SWAP",
  "STASH",
  "UNSTASH",
  "SWAP_DROP",
  "PUSH_UNDEFINED",
  "PUSH_NULL",
  "PUSH_THIS",
  "PUSH_TRUE",
  "PUSH_FALSE",
  "PUSH_ZERO",
  "PUSH_ONE",
  "PUSH_LIT",
  "NOT",
  "LOGICAL_NOT",
  "NEG",
  "POS",
  "ADD",
  "SUB",
  "REM",
  "MUL",
  "DIV",
  "LSHIFT",
  "RSHIFT",
  "URSHIFT",
  "OR",
  "XOR",
  "AND",
  "EQ_EQ",
  "EQ",
  "NE",
  "NE_NE",
  "LT",
  "LE",
  "GT",
  "GE",
  "INSTANCEOF",
  "TYPEOF",
  "IN",
  "GET",
  "SET",
  "SET_VAR",
  "GET_VAR",
  "SAFE_GET_VAR",
  "JMP",
  "JMP_TRUE",
  "JMP_FALSE",
  "JMP_TRUE_DROP",
  "JMP_IF_CONTINUE",
  "CREATE_OBJ",
  "CREATE_ARR",
  "PUSH_PROP_ITER_CTX",
  "NEXT_PROP",
  "FUNC_LIT",
  "CALL",
  "NEW",
  "CHECK_CALL",
  "RET",
  "DELETE",
  "DELETE_VAR",
  "TRY_PUSH_CATCH",
  "TRY_PUSH_FINALLY",
  "TRY_PUSH_LOOP",
  "TRY_PUSH_SWITCH",
  "TRY_POP",
  "AFTER_FINALLY",
  "THROW",
  "BREAK",
  "CONTINUE",
  "ENTER_CATCH",
  "EXIT_CATCH",
};
/* clang-format on */

V7_STATIC_ASSERT(OP_MAX == ARRAY_SIZE(op_names), bad_op_names);
V7_STATIC_ASSERT(OP_MAX <= _OP_LINE_NO, bad_OP_LINE_NO);
#endif

static void bcode_serialize_func(struct v7 *v7, struct bcode *bcode, FILE *out);

static size_t bcode_ops_append(struct bcode_builder *bbuilder, const void *buf,
                               size_t len) {
  size_t ret;
#if V7_ENABLE__Memory__stats
  bbuilder->v7->bcode_ops_size -= bbuilder->ops.len;
#endif
  ret = mbuf_append(&bbuilder->ops, buf, len);
#if V7_ENABLE__Memory__stats
  bbuilder->v7->bcode_ops_size += bbuilder->ops.len;
#endif
  return ret;
}

/*
 * Initialize bcode builder. The `bcode` should be already initialized by the
 * caller, and should be empty (i.e. should not own any ops, literals, etc)
 *
 * TODO(dfrank) : probably make `bcode_builder_init()` to initialize `bcode`
 * as well
 */
V7_PRIVATE void bcode_builder_init(struct v7 *v7,
                                   struct bcode_builder *bbuilder,
                                   struct bcode *bcode) {
  memset(bbuilder, 0x00, sizeof(*bbuilder));
  bbuilder->v7 = v7;
  bbuilder->bcode = bcode;

  mbuf_init(&bbuilder->ops, 0);
  mbuf_init(&bbuilder->lit, 0);
}

/*
 * Finalize bcode builder: propagate data to the bcode and transfer the
 * ownership from builder to bcode
 */
V7_PRIVATE void bcode_builder_finalize(struct bcode_builder *bbuilder) {
  mbuf_trim(&bbuilder->ops);
  bbuilder->bcode->ops.p = bbuilder->ops.buf;
  bbuilder->bcode->ops.len = bbuilder->ops.len;
  mbuf_init(&bbuilder->ops, 0);

  mbuf_trim(&bbuilder->lit);
  bbuilder->bcode->lit.p = bbuilder->lit.buf;
  bbuilder->bcode->lit.len = bbuilder->lit.len;
  mbuf_init(&bbuilder->lit, 0);

  memset(bbuilder, 0x00, sizeof(*bbuilder));
}

#if defined(V7_BCODE_DUMP) || defined(V7_BCODE_TRACE)
V7_PRIVATE void dump_op(struct v7 *v7, FILE *f, struct bcode *bcode,
                        char **ops) {
  char *p = *ops;

  assert(*p < OP_MAX);
  fprintf(f, "%zu: %s", (size_t)(p - bcode->ops.p), op_names[(uint8_t) *p]);
  switch (*p) {
    case OP_PUSH_LIT:
    case OP_SAFE_GET_VAR:
    case OP_GET_VAR:
    case OP_SET_VAR: {
      size_t idx = bcode_get_varint(&p);
      fprintf(f, "(%lu): ", (unsigned long) idx);
      v7_fprint(f, v7, ((val_t *) bcode->lit.p)[idx]);
      break;
    }
    case OP_CALL:
    case OP_NEW:
      p++;
      fprintf(f, "(%d)", *p);
      break;
    case OP_JMP:
    case OP_JMP_FALSE:
    case OP_JMP_TRUE:
    case OP_JMP_TRUE_DROP:
    case OP_JMP_IF_CONTINUE:
    case OP_TRY_PUSH_CATCH:
    case OP_TRY_PUSH_FINALLY:
    case OP_TRY_PUSH_LOOP:
    case OP_TRY_PUSH_SWITCH: {
      bcode_off_t target;
      p++;
      memcpy(&target, p, sizeof(target));
      fprintf(f, "(%lu)", (unsigned long) target);
      p += sizeof(target) - 1;
      break;
    }
    default:
      break;
  }
  fprintf(f, "\n");
  *ops = p;
}
#endif

#ifdef V7_BCODE_DUMP
V7_PRIVATE void dump_bcode(struct v7 *v7, FILE *f, struct bcode *bcode) {
  char *p = bcode_end_names(bcode->ops.p, bcode->names_cnt);
  char *end = bcode->ops.p + bcode->ops.len;
  for (; p < end; p++) {
    dump_op(v7, f, bcode, &p);
  }
}
#endif

V7_PRIVATE void bcode_init(struct bcode *bcode, uint8_t strict_mode,
                           void *filename, uint8_t filename_in_rom) {
  memset(bcode, 0x00, sizeof(*bcode));
  bcode->refcnt = 0;
  bcode->args_cnt = 0;
  bcode->strict_mode = strict_mode;
#ifndef V7_DISABLE_FILENAMES
  bcode->filename = filename;
  bcode->filename_in_rom = filename_in_rom;
#else
  (void) filename;
  (void) filename_in_rom;
#endif
}

V7_PRIVATE void bcode_free(struct v7 *v7, struct bcode *bcode) {
  (void) v7;
#if V7_ENABLE__Memory__stats
  if (!bcode->ops_in_rom) {
    v7->bcode_ops_size -= bcode->ops.len;
  }

  v7->bcode_lit_total_size -= bcode->lit.len;
  if (bcode->deserialized) {
    v7->bcode_lit_deser_size -= bcode->lit.len;
  }
#endif

  if (!bcode->ops_in_rom) {
    free(bcode->ops.p);
  }
  memset(&bcode->ops, 0x00, sizeof(bcode->ops));

  free(bcode->lit.p);
  memset(&bcode->lit, 0x00, sizeof(bcode->lit));

#ifndef V7_DISABLE_FILENAMES
  if (!bcode->filename_in_rom && bcode->filename != NULL) {
    shdata_release((struct shdata *) bcode->filename);
    bcode->filename = NULL;
  }
#endif

  bcode->refcnt = 0;
}

V7_PRIVATE void retain_bcode(struct v7 *v7, struct bcode *b) {
  (void) v7;
  if (!b->frozen) {
    b->refcnt++;
  }
}

V7_PRIVATE void release_bcode(struct v7 *v7, struct bcode *b) {
  (void) v7;
  if (b->frozen) return;

  assert(b->refcnt > 0);
  if (b->refcnt != 0) b->refcnt--;

  if (b->refcnt == 0) {
    bcode_free(v7, b);
    free(b);
  }
}

#ifndef V7_DISABLE_FILENAMES
V7_PRIVATE const char *bcode_get_filename(struct bcode *bcode) {
  const char *ret = NULL;
  if (bcode->filename_in_rom) {
    ret = (const char *) bcode->filename;
  } else if (bcode->filename != NULL) {
    ret = (const char *) shdata_get_payload((struct shdata *) bcode->filename);
  }
  return ret;
}
#endif

V7_PRIVATE void bcode_copy_filename_from(struct bcode *dst, struct bcode *src) {
#ifndef V7_DISABLE_FILENAMES
  dst->filename_in_rom = src->filename_in_rom;
  dst->filename = src->filename;

  if (src->filename != NULL && !src->filename_in_rom) {
    shdata_retain((struct shdata *) dst->filename);
  }
#else
  (void) dst;
  (void) src;
#endif
}

V7_PRIVATE void bcode_op(struct bcode_builder *bbuilder, uint8_t op) {
  bcode_ops_append(bbuilder, &op, 1);
}

#ifndef V7_DISABLE_LINE_NUMBERS
V7_PRIVATE void bcode_append_lineno(struct bcode_builder *bbuilder,
                                    int line_no) {
  int offset = bbuilder->ops.len;
  bcode_add_varint(bbuilder, (line_no << 1) | 1);
  bbuilder->ops.buf[offset] = msb_lsb_swap(bbuilder->ops.buf[offset]);
  assert(bbuilder->ops.buf[offset] & _OP_LINE_NO);
}
#endif

/*
 * Appends varint-encoded integer to the `ops` mbuf
 */
V7_PRIVATE void bcode_add_varint(struct bcode_builder *bbuilder, size_t value) {
  int k = calc_llen(value); /* Calculate how many bytes length takes */
  int offset = bbuilder->ops.len;

  /* Allocate buffer */
  bcode_ops_append(bbuilder, NULL, k);

  /* Write value */
  encode_varint(value, (unsigned char *) bbuilder->ops.buf + offset);
}

V7_PRIVATE size_t bcode_get_varint(char **ops) {
  size_t ret = 0;
  int len = 0;
  (*ops)++;
  ret = decode_varint((unsigned char *) *ops, &len);
  *ops += len - 1;
  return ret;
}

static int bcode_is_inline_string(struct v7 *v7, val_t val) {
  uint64_t tag = val & V7_TAG_MASK;
  if (v7->is_precompiling && v7_is_string(val)) {
    return 1;
  }
  return tag == V7_TAG_STRING_I || tag == V7_TAG_STRING_5;
}

static int bcode_is_inline_func(struct v7 *v7, val_t val) {
  return (v7->is_precompiling && is_js_function(val));
}

static int bcode_is_inline_regexp(struct v7 *v7, val_t val) {
  return (v7->is_precompiling && v7_is_regexp(v7, val));
}

V7_PRIVATE lit_t bcode_add_lit(struct bcode_builder *bbuilder, val_t val) {
  lit_t lit;
  memset(&lit, 0, sizeof(lit));

  if (bcode_is_inline_string(bbuilder->v7, val) ||
      bcode_is_inline_func(bbuilder->v7, val) || v7_is_number(val) ||
      bcode_is_inline_regexp(bbuilder->v7, val)) {
    /* literal should be inlined (it's `bcode_op_lit()` who does this) */
    lit.mode = LIT_MODE__INLINED;
    lit.v.inline_val = val;
  } else {
    /* literal will now be added to the literal table */
    lit.mode = LIT_MODE__TABLE;
    lit.v.lit_idx = bbuilder->lit.len / sizeof(val);

#if V7_ENABLE__Memory__stats
    bbuilder->v7->bcode_lit_total_size -= bbuilder->lit.len;
    if (bbuilder->bcode->deserialized) {
      bbuilder->v7->bcode_lit_deser_size -= bbuilder->lit.len;
    }
#endif

    mbuf_append(&bbuilder->lit, &val, sizeof(val));

    /*
     * immediately propagate current lit buffer to the bcode, so that GC will
     * be aware of it
     */
    bbuilder->bcode->lit.p = bbuilder->lit.buf;
    bbuilder->bcode->lit.len = bbuilder->lit.len;

#if V7_ENABLE__Memory__stats
    bbuilder->v7->bcode_lit_total_size += bbuilder->lit.len;
    if (bbuilder->bcode->deserialized) {
      bbuilder->v7->bcode_lit_deser_size += bbuilder->lit.len;
    }
#endif
  }
  return lit;
}

#if 0
V7_PRIVATE v7_val_t bcode_get_lit(struct bcode *bcode, size_t idx) {
  val_t ret;
  memcpy(&ret, bcode->lit.p + (size_t) idx * sizeof(ret), sizeof(ret));
  return ret;
}
#endif

static const char *bcode_deserialize_func(struct v7 *v7, struct bcode *bcode,
                                          const char *data);

V7_PRIVATE v7_val_t
bcode_decode_lit(struct v7 *v7, struct bcode *bcode, char **ops) {
  struct v7_vec *vec = &bcode->lit;
  size_t idx = bcode_get_varint(ops);
  switch (idx) {
    case BCODE_INLINE_STRING_TYPE_TAG: {
      val_t res;
      size_t len = bcode_get_varint(ops);
      res = v7_mk_string(
          v7, (const char *) *ops + 1 /*skip BCODE_INLINE_STRING_TYPE_TAG*/,
          len, !bcode->ops_in_rom);
      *ops += len + 1;
      return res;
    }
    case BCODE_INLINE_NUMBER_TYPE_TAG: {
      val_t res;
      memcpy(&res, *ops + 1 /*skip BCODE_INLINE_NUMBER_TYPE_TAG*/, sizeof(res));
      *ops += sizeof(res);
      return res;
    }
    case BCODE_INLINE_FUNC_TYPE_TAG: {
      /*
       * Create half-done function: without scope but _with_ prototype. Scope
       * will be set by `bcode_instantiate_function()`.
       *
       * The fact that the prototype is already set will make
       * `bcode_instantiate_function()` just set scope on this function,
       * instead of creating a new one.
       */
      val_t res = mk_js_function(v7, NULL, v7_mk_object(v7));

      /* Create bcode in this half-done function */
      struct v7_js_function *func = get_js_function_struct(res);

      func->bcode = (struct bcode *) calloc(1, sizeof(*func->bcode));
      bcode_init(func->bcode, bcode->strict_mode, NULL /* will be set below */,
                 0);
      bcode_copy_filename_from(func->bcode, bcode);
      retain_bcode(v7, func->bcode);

      /* deserialize the function's bcode from `ops` */
      *ops = (char *) bcode_deserialize_func(
          v7, func->bcode, *ops + 1 /*skip BCODE_INLINE_FUNC_TYPE_TAG*/);

      /* decrement *ops, because it will be incremented by `eval_bcode` soon */
      *ops -= 1;

      return res;
    }
    case BCODE_INLINE_REGEXP_TYPE_TAG: {
#if V7_ENABLE__RegExp
      enum v7_err rcode = V7_OK;
      val_t res;
      size_t len_src, len_flags;
      char *buf_src, *buf_flags;

      len_src = bcode_get_varint(ops);
      buf_src = *ops + 1;
      *ops += len_src + 1 /* nul term */;

      len_flags = bcode_get_varint(ops);
      buf_flags = *ops + 1;
      *ops += len_flags + 1 /* nul term */;

      rcode = v7_mk_regexp(v7, buf_src, len_src, buf_flags, len_flags, &res);
      assert(rcode == V7_OK);
      (void) rcode;

      return res;
#else
      fprintf(stderr, "Firmware is built without -DV7_ENABLE__RegExp\n");
      abort();
#endif
    }
    default:
      return ((val_t *) vec->p)[idx - BCODE_MAX_INLINE_TYPE_TAG];
  }
}

V7_PRIVATE void bcode_op_lit(struct bcode_builder *bbuilder, enum opcode op,
                             lit_t lit) {
  bcode_op(bbuilder, op);

  switch (lit.mode) {
    case LIT_MODE__TABLE:
      bcode_add_varint(bbuilder, lit.v.lit_idx + BCODE_MAX_INLINE_TYPE_TAG);
      break;

    case LIT_MODE__INLINED:
      if (v7_is_string(lit.v.inline_val)) {
        size_t len;
        const char *s = v7_get_string(bbuilder->v7, &lit.v.inline_val, &len);
        bcode_add_varint(bbuilder, BCODE_INLINE_STRING_TYPE_TAG);
        bcode_add_varint(bbuilder, len);
        bcode_ops_append(bbuilder, s, len + 1 /* nul term */);
      } else if (v7_is_number(lit.v.inline_val)) {
        bcode_add_varint(bbuilder, BCODE_INLINE_NUMBER_TYPE_TAG);
        /*
         * TODO(dfrank): we can save some memory by storing string
         * representation of a number here, instead of wasting 8 bytes for each
         * number.
         *
         * Alternatively, we can add more tags for integers, like
         * `BCODE_INLINE_S08_TYPE_TAG`, `BCODE_INLINE_S16_TYPE_TAG`, etc, since
         * integers are the most common numbers for sure.
         */
        bcode_ops_append(bbuilder, &lit.v.inline_val, sizeof(lit.v.inline_val));
      } else if (is_js_function(lit.v.inline_val)) {
/*
 * TODO(dfrank): implement `bcode_serialize_*` more generically, so
 * that they can write to buffer instead of a `FILE`. Then, remove this
 * workaround with `CS_PLATFORM == CS_P_UNIX`, `tmpfile()`, etc.
 */
#if CS_PLATFORM == CS_P_UNIX
        struct v7_js_function *func;
        FILE *fp = tmpfile();
        long len = 0;
        char *p;

        func = get_js_function_struct(lit.v.inline_val);

        /* we inline functions if only we're precompiling */
        assert(bbuilder->v7->is_precompiling);

        bcode_add_varint(bbuilder, BCODE_INLINE_FUNC_TYPE_TAG);
        bcode_serialize_func(bbuilder->v7, func->bcode, fp);

        fflush(fp);

        len = ftell(fp);

        p = (char *) mmap(NULL, len, PROT_WRITE, MAP_PRIVATE, fileno(fp), 0);

        bcode_ops_append(bbuilder, p, len);

        fclose(fp);
#endif
      } else if (v7_is_regexp(bbuilder->v7, lit.v.inline_val)) {
#if V7_ENABLE__RegExp
        struct v7_regexp *rp =
            v7_get_regexp_struct(bbuilder->v7, lit.v.inline_val);
        bcode_add_varint(bbuilder, BCODE_INLINE_REGEXP_TYPE_TAG);

        /* append regexp source */
        {
          size_t len;
          const char *buf =
              v7_get_string(bbuilder->v7, &rp->regexp_string, &len);
          bcode_add_varint(bbuilder, len);
          bcode_ops_append(bbuilder, buf, len + 1 /* nul term */);
        }

        /* append regexp flags */
        {
          char buf[_V7_REGEXP_MAX_FLAGS_LEN + 1 /* nul term */];
          size_t len = get_regexp_flags_str(bbuilder->v7, rp, buf);
          bcode_add_varint(bbuilder, len);
          bcode_ops_append(bbuilder, buf, len + 1 /* nul term */);
        }
#else
        fprintf(stderr, "Firmware is built without -DV7_ENABLE__RegExp\n");
        abort();
#endif
      } else {
        /* invalid type of inlined value */
        abort();
      }
      break;

    default:
      /* invalid literal mode */
      abort();
      break;
  }
}

V7_PRIVATE void bcode_push_lit(struct bcode_builder *bbuilder, lit_t lit) {
  bcode_op_lit(bbuilder, OP_PUSH_LIT, lit);
}

WARN_UNUSED_RESULT
    /*V7_PRIVATE*/ enum v7_err
    bcode_add_name(struct bcode_builder *bbuilder, const char *p, size_t len,
                   size_t *idx) {
  enum v7_err rcode = V7_OK;
  int llen;
  size_t ops_index;

  /*
   * if name length is not provided, assume it's null-terminated and calculate
   * it
   */
  if (len == ~((size_t) 0)) {
    len = strlen(p);
  }

  /* index at which to put name. If not provided, we'll append at the end */
  if (idx != NULL) {
    ops_index = *idx;
  } else {
    ops_index = bbuilder->ops.len;
  }

  /* calculate how much varint len will take */
  llen = calc_llen(len);

  /* reserve space in `ops` buffer */
  mbuf_insert(&bbuilder->ops, ops_index, NULL, llen + len + 1 /*null-term*/);

  {
    char *ops = bbuilder->ops.buf + ops_index;

    /* put varint len */
    ops += encode_varint(len, (unsigned char *) ops);

    /* put string */
    memcpy(ops, p, len);
    ops += len;

    /* null-terminate */
    *ops++ = 0x00;

    if (idx != NULL) {
      *idx = ops - bbuilder->ops.buf;
    }
  }

  /* maintain total number of names */
  if (bbuilder->bcode->names_cnt < V7_NAMES_CNT_MAX) {
    bbuilder->bcode->names_cnt++;
  } else {
    rcode = v7_throwf(bbuilder->v7, SYNTAX_ERROR, "Too many local variables");
  }

  return rcode;
}

/*V7_PRIVATE*/ char *bcode_end_names(char *ops, size_t names_cnt) {
  while (names_cnt--) {
    ops = bcode_next_name(ops, NULL, NULL);
  }
  return ops;
}

V7_PRIVATE char *bcode_next_name(char *ops, char **pname, size_t *plen) {
  size_t len;
  int llen;

  len = decode_varint((unsigned char *) ops, &llen);

  ops += llen;

  if (pname != NULL) {
    *pname = ops;
  }

  if (plen != NULL) {
    *plen = len;
  }

  ops += len + 1 /*null-terminator*/;
  return ops;
}

V7_PRIVATE char *bcode_next_name_v(struct v7 *v7, struct bcode *bcode,
                                   char *ops, val_t *res) {
  char *name;
  size_t len;

  ops = bcode_next_name(ops, &name, &len);

  /*
   * If `ops` is in RAM, we create owned string, since the string may outlive
   * bcode. Otherwise (`ops` is in ROM), we create foreign string.
   */
  *res = v7_mk_string(v7, name, len, !bcode->ops_in_rom);

  return ops;
}

V7_PRIVATE bcode_off_t bcode_pos(struct bcode_builder *bbuilder) {
  return bbuilder->ops.len;
}

/*
 * Appends a branch target and returns its location.
 * This location can be updated with bcode_patch_target.
 * To be issued following a JMP_* bytecode
 */
V7_PRIVATE bcode_off_t bcode_add_target(struct bcode_builder *bbuilder) {
  bcode_off_t pos = bcode_pos(bbuilder);
  bcode_off_t zero = 0;
  bcode_ops_append(bbuilder, &zero, sizeof(bcode_off_t));
  return pos;
}

/*
 * Appends an op requiring a branch target. See bcode_add_target.
 *
 * This function is used only internally, but used in a complicated mix of
 * configurations, hence the commented V7_PRIVATE
 */
/*V7_PRIVATE*/ bcode_off_t bcode_op_target(struct bcode_builder *bbuilder,
                                           uint8_t op) {
  bcode_op(bbuilder, op);
  return bcode_add_target(bbuilder);
}

/*V7_PRIVATE*/ void bcode_patch_target(struct bcode_builder *bbuilder,
                                       bcode_off_t label, bcode_off_t target) {
  memcpy(bbuilder->ops.buf + label, &target, sizeof(target));
}

/*V7_PRIVATE*/ void bcode_serialize(struct v7 *v7, struct bcode *bcode,
                                    FILE *out) {
  (void) v7;
  (void) bcode;

  fwrite(BIN_BCODE_SIGNATURE, sizeof(BIN_BCODE_SIGNATURE), 1, out);
  bcode_serialize_func(v7, bcode, out);
}

static void bcode_serialize_varint(int n, FILE *out) {
  unsigned char buf[8];
  int k = calc_llen(n);
  encode_varint(n, buf);
  fwrite(buf, k, 1, out);
}

static void bcode_serialize_func(struct v7 *v7, struct bcode *bcode,
                                 FILE *out) {
  struct v7_vec *vec;
  (void) v7;

  /*
   * All literals should be inlined into `ops`, so we expect literals table
   * to be empty here
   */
  assert(bcode->lit.len == 0);

  /* args_cnt */
  bcode_serialize_varint(bcode->args_cnt, out);

  /* names_cnt */
  bcode_serialize_varint(bcode->names_cnt, out);

  /* func_name_present */
  bcode_serialize_varint(bcode->func_name_present, out);

  /*
   * bcode:
   * <varint> // opcodes length
   * <opcode>*
   */
  vec = &bcode->ops;
  bcode_serialize_varint(vec->len, out);
  fwrite(vec->p, vec->len, 1, out);
}

static size_t bcode_deserialize_varint(const char **data) {
  size_t ret = 0;
  int len = 0;
  ret = decode_varint((const unsigned char *) (*data), &len);
  *data += len;
  return ret;
}

static const char *bcode_deserialize_func(struct v7 *v7, struct bcode *bcode,
                                          const char *data) {
  size_t size;
  struct bcode_builder bbuilder;

  bcode_builder_init(v7, &bbuilder, bcode);

  /*
   * before deserializing, set the corresponding flag, so that metrics will be
   * updated accordingly
   */
  bcode->deserialized = 1;

  /*
   * In serialized functions, all literals are inlined into `ops`, so we don't
   * deserialize them here in any way
   */

  /* get number of args */
  bcode->args_cnt = bcode_deserialize_varint(&data);

  /* get number of names */
  bcode->names_cnt = bcode_deserialize_varint(&data);

  /* get whether the function name is present in `names` */
  bcode->func_name_present = bcode_deserialize_varint(&data);

  /* get opcode size */
  size = bcode_deserialize_varint(&data);

  bbuilder.ops.buf = (char *) data;
  bbuilder.ops.size = size;
  bbuilder.ops.len = size;

  bcode->ops_in_rom = 1;

  data += size;

  bcode_builder_finalize(&bbuilder);
  return data;
}

V7_PRIVATE void bcode_deserialize(struct v7 *v7, struct bcode *bcode,
                                  const char *data) {
  data = bcode_deserialize_func(v7, bcode, data);
}
#ifdef V7_MODULE_LINES
#line 1 "v7/src/eval.c"
#endif
/*
 * Copyright (c) 2014 Cesanta Software Limited
 * All rights reserved
 */

/* Amalgamated: #include "common/str_util.h" */
/* Amalgamated: #include "v7/src/internal.h" */
/* Amalgamated: #include "v7/src/eval.h" */
/* Amalgamated: #include "v7/src/string.h" */
/* Amalgamated: #include "v7/src/array.h" */
/* Amalgamated: #include "v7/src/object.h" */
/* Amalgamated: #include "v7/src/gc.h" */
/* Amalgamated: #include "v7/src/compiler.h" */
/* Amalgamated: #include "v7/src/cyg_profile.h" */
/* Amalgamated: #include "v7/src/core.h" */
/* Amalgamated: #include "v7/src/function.h" */
/* Amalgamated: #include "v7/src/util.h" */
/* Amalgamated: #include "v7/src/shdata.h" */
/* Amalgamated: #include "v7/src/exceptions.h" */
/* Amalgamated: #include "v7/src/conversion.h" */
/* Amalgamated: #include "v7/src/varint.h" */

/*
 * Bcode offsets in "try stack" are stored in JS numbers, i.e.  in `double`s.
 * Apart from the offset itself, we also need some additional data:
 *
 * - type of the block that offset represents (`catch`, `finally`, `switch`,
 *   or some loop)
 * - size of the stack when the block is created (needed when throwing, since
 *   if exception is thrown from the middle of the expression, the stack may
 *   have any arbitrary length)
 *
 * We bake all this data into integer part of the double (53 bits) :
 *
 * - 32 bits: bcode offset
 * - 3 bits: "tag": the type of the block
 * - 16 bits: stack size
 */

/*
 * Widths of data parts
 */
#define LBLOCK_OFFSET_WIDTH 32
#define LBLOCK_TAG_WIDTH 3
#define LBLOCK_STACK_SIZE_WIDTH 16

/*
 * Shifts of data parts
 */
#define LBLOCK_OFFSET_SHIFT (0)
#define LBLOCK_TAG_SHIFT (LBLOCK_OFFSET_SHIFT + LBLOCK_OFFSET_WIDTH)
#define LBLOCK_STACK_SIZE_SHIFT (LBLOCK_TAG_SHIFT + LBLOCK_TAG_WIDTH)
#define LBLOCK_TOTAL_WIDTH (LBLOCK_STACK_SIZE_SHIFT + LBLOCK_STACK_SIZE_WIDTH)

/*
 * Masks of data parts
 */
#define LBLOCK_OFFSET_MASK \
  ((int64_t)(((int64_t) 1 << LBLOCK_OFFSET_WIDTH) - 1) << LBLOCK_OFFSET_SHIFT)
#define LBLOCK_TAG_MASK \
  ((int64_t)(((int64_t) 1 << LBLOCK_TAG_WIDTH) - 1) << LBLOCK_TAG_SHIFT)
#define LBLOCK_STACK_SIZE_MASK                             \
  ((int64_t)(((int64_t) 1 << LBLOCK_STACK_SIZE_WIDTH) - 1) \
   << LBLOCK_STACK_SIZE_SHIFT)

/*
 * Self-check: make sure all the data can fit into double's mantissa
 */
#if (LBLOCK_TOTAL_WIDTH > 53)
#error lblock width is too large, it can't fit into double's mantissa
#endif

/*
 * Tags that are used for bcode offsets in "try stack"
 */
#define LBLOCK_TAG_CATCH ((int64_t) 0x01 << LBLOCK_TAG_SHIFT)
#define LBLOCK_TAG_FINALLY ((int64_t) 0x02 << LBLOCK_TAG_SHIFT)
#define LBLOCK_TAG_LOOP ((int64_t) 0x03 << LBLOCK_TAG_SHIFT)
#define LBLOCK_TAG_SWITCH ((int64_t) 0x04 << LBLOCK_TAG_SHIFT)

/*
 * Yields 32-bit bcode offset value
 */
#define LBLOCK_OFFSET(v) \
  ((bcode_off_t)(((v) &LBLOCK_OFFSET_MASK) >> LBLOCK_OFFSET_SHIFT))

/*
 * Yields tag value (unshifted, to be compared with macros like
 * `LBLOCK_TAG_CATCH`, etc)
 */
#define LBLOCK_TAG(v) ((v) &LBLOCK_TAG_MASK)

/*
 * Yields stack size
 */
#define LBLOCK_STACK_SIZE(v) \
  (((v) &LBLOCK_STACK_SIZE_MASK) >> LBLOCK_STACK_SIZE_SHIFT)

/*
 * Yields `int64_t` value to be stored as a JavaScript number
 */
#define LBLOCK_ITEM_CREATE(offset, tag, stack_size) \
  ((int64_t)(offset) | (tag) |                      \
   (((int64_t)(stack_size)) << LBLOCK_STACK_SIZE_SHIFT))

/*
 * make sure `bcode_off_t` is just 32-bit, so that it can fit in double
 * with 3-bit tag
 */
V7_STATIC_ASSERT((sizeof(bcode_off_t) * 8) == LBLOCK_OFFSET_WIDTH,
                 wrong_size_of_bcode_off_t);

#define PUSH(v) stack_push(&v7->stack, v)
#define POP() stack_pop(&v7->stack)
#define TOS() stack_tos(&v7->stack)
#define SP() stack_sp(&v7->stack)

/*
 * Local-to-function block types that we might want to consider when unwinding
 * stack for whatever reason. see `unwind_local_blocks_stack()`.
 */
enum local_block {
  LOCAL_BLOCK_NONE = (0),
  LOCAL_BLOCK_CATCH = (1 << 0),
  LOCAL_BLOCK_FINALLY = (1 << 1),
  LOCAL_BLOCK_LOOP = (1 << 2),
  LOCAL_BLOCK_SWITCH = (1 << 3),
};

/*
 * Like `V7_TRY()`, but to be used inside `eval_bcode()` only: you should
 * wrap all calls to cfunctions into `BTRY()` instead of `V7_TRY()`.
 *
 * If the provided function returns something other than `V7_OK`, this macro
 * calls `bcode_perform_throw`, which performs bcode stack unwinding.
 */
#define BTRY(call)                                                            \
  do {                                                                        \
    enum v7_err _e = call;                                                    \
    (void) _you_should_use_BTRY_in_eval_bcode_only;                           \
    if (_e != V7_OK) {                                                        \
      V7_TRY(bcode_perform_throw(v7, &r, 0 /*don't take value from stack*/)); \
      goto op_done;                                                           \
    }                                                                         \
  } while (0)

V7_PRIVATE void stack_push(struct mbuf *s, val_t v) {
  mbuf_append(s, &v, sizeof(v));
}

V7_PRIVATE val_t stack_pop(struct mbuf *s) {
  assert(s->len >= sizeof(val_t));
  s->len -= sizeof(val_t);
  return *(val_t *) (s->buf + s->len);
}

V7_PRIVATE val_t stack_tos(struct mbuf *s) {
  assert(s->len >= sizeof(val_t));
  return *(val_t *) (s->buf + s->len - sizeof(val_t));
}

#ifdef V7_BCODE_TRACE_STACK
V7_PRIVATE val_t stack_at(struct mbuf *s, size_t idx) {
  assert(s->len >= sizeof(val_t) * idx);
  return *(val_t *) (s->buf + s->len - sizeof(val_t) - idx * sizeof(val_t));
}
#endif

V7_PRIVATE int stack_sp(struct mbuf *s) {
  return s->len / sizeof(val_t);
}

/*
 * Delete a property with name `name`, `len` from an object `obj`. If the
 * object does not contain own property with the given `name`, moves to `obj`'s
 * prototype, and so on.
 *
 * If the property is eventually found, it is deleted, and `0` is returned.
 * Otherwise, `-1` is returned.
 *
 * If `len` is -1/MAXUINT/~0, then `name` must be 0-terminated.
 *
 * See `v7_del()` as well.
 */
static int del_property_deep(struct v7 *v7, val_t obj, const char *name,
                             size_t len) {
  if (!v7_is_object(obj)) {
    return -1;
  }
  for (; obj != V7_NULL; obj = v7_get_proto(v7, obj)) {
    int del_res;
    if ((del_res = v7_del(v7, obj, name, len)) != -1) {
      return del_res;
    }
  }
  return -1;
}

/* Visual studio 2012+ has signbit() */
#if defined(_MSC_VER) && _MSC_VER < 1700
static int signbit(double x) {
  double s = _copysign(1, x);
  return s < 0;
}
#endif

static double b_int_bin_op(enum opcode op, double a, double b) {
  int32_t ia = isnan(a) || isinf(a) ? 0 : (int32_t)(int64_t) a;
  int32_t ib = isnan(b) || isinf(b) ? 0 : (int32_t)(int64_t) b;

  switch (op) {
    case OP_LSHIFT:
      return (int32_t)((uint32_t) ia << ((uint32_t) ib & 31));
    case OP_RSHIFT:
      return ia >> ((uint32_t) ib & 31);
    case OP_URSHIFT:
      return (uint32_t) ia >> ((uint32_t) ib & 31);
    case OP_OR:
      return ia | ib;
    case OP_XOR:
      return ia ^ ib;
    case OP_AND:
      return ia & ib;
    default:
      assert(0);
  }
  return 0;
}

static double b_num_bin_op(enum opcode op, double a, double b) {
  /*
   * For certain operations, the result is always NaN if either of arguments
   * is NaN
   */
  switch (op) {
    case OP_ADD:
    case OP_SUB:
    case OP_MUL:
    case OP_DIV:
    case OP_REM:
      if (isnan(a) || isnan(b)) {
        return NAN;
      }
      break;
    default:
      break;
  }

  switch (op) {
    case OP_ADD: /* simple fixed width nodes with no payload */
      return a + b;
    case OP_SUB:
      return a - b;
    case OP_REM:
      if (b == 0 || isnan(b) || isnan(a) || isinf(b) || isinf(a)) {
        return NAN;
      }
      return (int) a % (int) b;
    case OP_MUL:
      return a * b;
    case OP_DIV:
      if (b == 0) {
        if (a == 0) return NAN;
        return (!signbit(a) == !signbit(b)) ? INFINITY : -INFINITY;
      }
      return a / b;
    case OP_LSHIFT:
    case OP_RSHIFT:
    case OP_URSHIFT:
    case OP_OR:
    case OP_XOR:
    case OP_AND:
      return b_int_bin_op(op, a, b);
    default:
      assert(0);
  }
  return 0;
}

static int b_bool_bin_op(enum opcode op, double a, double b) {
#ifdef V7_BROKEN_NAN
  if (isnan(a) || isnan(b)) return op == OP_NE || op == OP_NE_NE;
#endif

  switch (op) {
    case OP_EQ:
    case OP_EQ_EQ:
      return a == b;
    case OP_NE:
    case OP_NE_NE:
      return a != b;
    case OP_LT:
      return a < b;
    case OP_LE:
      return a <= b;
    case OP_GT:
      return a > b;
    case OP_GE:
      return a >= b;
    default:
      assert(0);
  }
  return 0;
}

static bcode_off_t bcode_get_target(char **ops) {
  bcode_off_t target;
  (*ops)++;
  memcpy(&target, *ops, sizeof(target));
  *ops += sizeof(target) - 1;
  return target;
}

struct bcode_registers {
  /*
   * TODO(dfrank): make it contain `struct v7_call_frame_bcode *`
   * and use `bcode_ops` in-place, or probably drop the `bcode_registers`
   * whatsoever
   */
  struct bcode *bcode;
  char *ops;
  char *end;
  unsigned int need_inc_ops : 1;
};

/*
 * If returning from function implicitly, then set return value to `undefined`.
 *
 * And if function was called as a constructor, then make sure returned
 * value is an object.
 */
static void bcode_adjust_retval(struct v7 *v7, uint8_t is_explicit_return) {
  if (!is_explicit_return) {
    /* returning implicitly: set return value to `undefined` */
    POP();
    PUSH(V7_UNDEFINED);
  }

  if (v7->call_stack->is_constructor && !v7_is_object(TOS())) {
    /* constructor is going to return non-object: replace it with `this` */
    POP();
    PUSH(v7_get_this(v7));
  }
}

static void bcode_restore_registers(struct v7 *v7, struct bcode *bcode,
                                    struct bcode_registers *r) {
  r->bcode = bcode;
  r->ops = bcode->ops.p;
  r->end = r->ops + bcode->ops.len;

  (void) v7;
}

V7_PRIVATE struct v7_call_frame_base *find_call_frame(struct v7 *v7,
                                                      uint8_t type_mask) {
  struct v7_call_frame_base *ret = v7->call_stack;

  while (ret != NULL && !(ret->type_mask & type_mask)) {
    ret = ret->prev;
  }

  return ret;
}

static struct v7_call_frame_private *find_call_frame_private(struct v7 *v7) {
  return (struct v7_call_frame_private *) find_call_frame(
      v7, V7_CALL_FRAME_MASK_PRIVATE);
}

static struct v7_call_frame_bcode *find_call_frame_bcode(struct v7 *v7) {
  return (struct v7_call_frame_bcode *) find_call_frame(
      v7, V7_CALL_FRAME_MASK_BCODE);
}

#if 0
static struct v7_call_frame_cfunc *find_call_frame_cfunc(struct v7 *v7) {
  return (struct v7_call_frame_cfunc *) find_call_frame(
      v7, V7_CALL_FRAME_MASK_CFUNC);
}
#endif

static struct v7_call_frame_base *create_call_frame(struct v7 *v7,
                                                    size_t size) {
  struct v7_call_frame_base *call_frame_base = NULL;

  call_frame_base = (struct v7_call_frame_base *) calloc(1, size);

  /* save previous call frame */
  call_frame_base->prev = v7->call_stack;

  /* by default, inherit line_no from the previous frame */
  if (v7->call_stack != NULL) {
    call_frame_base->line_no = v7->call_stack->line_no;
  }

  return call_frame_base;
}

static void init_call_frame_private(struct v7 *v7,
                                    struct v7_call_frame_private *call_frame,
                                    val_t scope) {
  /* make a snapshot of the current state */
  {
    struct v7_call_frame_private *cf = find_call_frame_private(v7);
    if (cf != NULL) {
      cf->stack_size = v7->stack.len;
    }
  }

  /* set a type flag */
  call_frame->base.type_mask |= V7_CALL_FRAME_MASK_PRIVATE;

  /* fill the new frame with data */
  call_frame->vals.scope = scope;
  /* `try_stack` will be lazily created in `eval_try_push()`*/
  call_frame->vals.try_stack = V7_UNDEFINED;
}

static void init_call_frame_bcode(struct v7 *v7,
                                  struct v7_call_frame_bcode *call_frame,
                                  char *prev_bcode_ops, struct bcode *bcode,
                                  val_t this_obj, val_t scope,
                                  uint8_t is_constructor) {
  init_call_frame_private(v7, &call_frame->base, scope);

  /* make a snapshot of the current state */
  {
    struct v7_call_frame_bcode *cf = find_call_frame_bcode(v7);
    if (cf != NULL) {
      cf->bcode_ops = prev_bcode_ops;

      /* remember thrown value */
      cf->vals.thrown_error = v7->vals.thrown_error;
      cf->base.base.is_thrown = v7->is_thrown;
    }
  }

  /* set a type flag */
  call_frame->base.base.type_mask |= V7_CALL_FRAME_MASK_BCODE;

  /* fill the new frame with data */
  call_frame->bcode = bcode;
  call_frame->vals.this_obj = this_obj;
  call_frame->base.base.is_constructor = is_constructor;
}

/*
 * Create new bcode call frame object and fill it with data
 */
static void append_call_frame_bcode(struct v7 *v7, char *prev_bcode_ops,
                                    struct bcode *bcode, val_t this_obj,
                                    val_t scope, uint8_t is_constructor) {
  struct v7_call_frame_bcode *call_frame =
      (struct v7_call_frame_bcode *) create_call_frame(v7, sizeof(*call_frame));

  init_call_frame_bcode(v7, call_frame, prev_bcode_ops, bcode, this_obj, scope,
                        is_constructor);

  v7->call_stack = &call_frame->base.base;
}

static void append_call_frame_private(struct v7 *v7, val_t scope) {
  struct v7_call_frame_private *call_frame =
      (struct v7_call_frame_private *) create_call_frame(v7,
                                                         sizeof(*call_frame));
  init_call_frame_private(v7, call_frame, scope);

  v7->call_stack = &call_frame->base;
}

static void append_call_frame_cfunc(struct v7 *v7, val_t this_obj,
                                    v7_cfunction_t *cfunc) {
  struct v7_call_frame_cfunc *call_frame =
      (struct v7_call_frame_cfunc *) create_call_frame(v7, sizeof(*call_frame));

  /* set a type flag */
  call_frame->base.type_mask |= V7_CALL_FRAME_MASK_CFUNC;

  /* fill the new frame with data */
  call_frame->cfunc = cfunc;
  call_frame->vals.this_obj = this_obj;

  v7->call_stack = &call_frame->base;
}

/*
 * The caller's bcode object is needed because we have to restore literals
 * and `end` registers.
 *
 * TODO(mkm): put this state on a return stack
 *
 * Caller of bcode_perform_call is responsible for owning `call_frame`
 */
static enum v7_err bcode_perform_call(struct v7 *v7, v7_val_t scope_frame,
                                      struct v7_js_function *func,
                                      struct bcode_registers *r,
                                      val_t this_object, char *ops,
                                      uint8_t is_constructor) {
  /* new scope_frame will inherit from the function's scope */
  obj_prototype_set(v7, get_object_struct(scope_frame), &func->scope->base);

  /* create new `call_frame` which will replace `v7->call_stack` */
  append_call_frame_bcode(v7, r->ops + 1, func->bcode, this_object, scope_frame,
                          is_constructor);

  bcode_restore_registers(v7, func->bcode, r);

  /* adjust `ops` since names were already read from it */
  r->ops = ops;

  /* `ops` already points to the needed instruction, no need to increment it */
  r->need_inc_ops = 0;

  return V7_OK;
}

/*
 * Apply data from the "private" call frame, typically after some other frame
 * was just unwound.
 *
 * The `call_frame` may actually be `NULL`, if the top frame was unwound.
 */
static void apply_frame_private(struct v7 *v7,
                                struct v7_call_frame_private *call_frame) {
  /*
   * Adjust data stack length (restore saved).
   *
   * If `call_frame` is NULL, it means that the last call frame was just
   * unwound, and hence the data stack size should be 0.
   */
  size_t stack_size = (call_frame != NULL ? call_frame->stack_size : 0);
  assert(stack_size <= v7->stack.len);
  v7->stack.len = stack_size;
}

/*
 * Apply data from the "bcode" call frame, typically after some other frame
 * was just unwound.
 *
 * The `call_frame` may actually be `NULL`, if the top frame was unwound; but
 * in this case, `r` must be `NULL` too, by design. See inline comment below.
 */
static void apply_frame_bcode(struct v7 *v7,
                              struct v7_call_frame_bcode *call_frame,
                              struct bcode_registers *r) {
  if (r != NULL) {
    /*
     * Note: if `r` is non-NULL, then `call_frame` should be non-NULL as well,
     * by design. If this invariant is violated, it means that
     * `unwind_stack_1level()` is misused.
     */
    assert(call_frame != NULL);

    bcode_restore_registers(v7, call_frame->bcode, r);
    r->ops = call_frame->bcode_ops;

    /*
     * restore thrown value if only there's no new thrown value
     * (otherwise, the new one overrides the previous one)
     */
    if (!v7->is_thrown) {
      v7->vals.thrown_error = call_frame->vals.thrown_error;
      v7->is_thrown = call_frame->base.base.is_thrown;
    }
  }
}

/*
 * Unwinds `call_stack` by 1 frame.
 *
 * Returns the type of the unwound frame
 */
static v7_call_frame_mask_t unwind_stack_1level(struct v7 *v7,
                                                struct bcode_registers *r) {
  v7_call_frame_mask_t type_mask;
#ifdef V7_BCODE_TRACE
  fprintf(stderr, "unwinding stack by 1 level\n");
#endif

  type_mask = v7->call_stack->type_mask;

  /* drop the top frame */
  {
    struct v7_call_frame_base *tmp = v7->call_stack;
    v7->call_stack = v7->call_stack->prev;
    free(tmp);
  }

  /*
   * depending on the unwound frame type, apply data from the top call frame(s)
   * which are still alive (if any)
   */

  if (type_mask & V7_CALL_FRAME_MASK_PRIVATE) {
    apply_frame_private(v7, find_call_frame_private(v7));
  }

  if (type_mask & V7_CALL_FRAME_MASK_BCODE) {
    apply_frame_bcode(v7, find_call_frame_bcode(v7), r);
  }

  if (type_mask & V7_CALL_FRAME_MASK_CFUNC) {
    /* Nothing to do here at the moment */
  }

  return type_mask;
}

/*
 * Unwinds local "try stack" (i.e. local-to-current-function stack of nested
 * `try` blocks), looking for local-to-function blocks.
 *
 * Block types of interest are specified with `wanted_blocks_mask`: it's a
 * bitmask of `enum local_block` values.
 *
 * Only blocks of specified types will be considered, others will be dropped.
 *
 * If `restore_stack_size` is non-zero, the `v7->stack.len` will be restored
 * to the value saved when the block was created. This is useful when throwing,
 * since if we throw from the middle of the expression, the stack could have
 * any size. But you probably shouldn't set this flag when breaking and
 * returning, since it may hide real bugs in the opcode.
 *
 * Returns id of the block type that control was transferred into, or
 * `LOCAL_BLOCK_NONE` if no appropriate block was found. Note: returned value
 * contains at most 1 block bit; it can't contain multiple bits.
 */
static enum local_block unwind_local_blocks_stack(
    struct v7 *v7, struct bcode_registers *r, unsigned int wanted_blocks_mask,
    uint8_t restore_stack_size) {
  val_t arr = V7_UNDEFINED;
  struct gc_tmp_frame tf = new_tmp_frame(v7);
  enum local_block found_block = LOCAL_BLOCK_NONE;
  unsigned long length;

  tmp_stack_push(&tf, &arr);

  arr = find_call_frame_private(v7)->vals.try_stack;
  if (v7_is_array(v7, arr)) {
    /*
     * pop latest element from "try stack", loop until we need to transfer
     * control there
     */
    while ((length = v7_array_length(v7, arr)) > 0) {
      /* get latest offset from the "try stack" */
      int64_t offset = v7_get_double(v7, v7_array_get(v7, arr, length - 1));
      enum local_block cur_block = LOCAL_BLOCK_NONE;

      /* get id of the current block type */
      switch (LBLOCK_TAG(offset)) {
        case LBLOCK_TAG_CATCH:
          cur_block = LOCAL_BLOCK_CATCH;
          break;
        case LBLOCK_TAG_FINALLY:
          cur_block = LOCAL_BLOCK_FINALLY;
          break;
        case LBLOCK_TAG_LOOP:
          cur_block = LOCAL_BLOCK_LOOP;
          break;
        case LBLOCK_TAG_SWITCH:
          cur_block = LOCAL_BLOCK_SWITCH;
          break;
        default:
          assert(0);
          break;
      }

      if (cur_block & wanted_blocks_mask) {
        /* need to transfer control to this offset */
        r->ops = r->bcode->ops.p + LBLOCK_OFFSET(offset);
#ifdef V7_BCODE_TRACE
        fprintf(stderr, "transferring to block #%d: %u\n", (int) cur_block,
                (unsigned int) LBLOCK_OFFSET(offset));
#endif
        found_block = cur_block;
        /* if needed, restore stack size to the saved value */
        if (restore_stack_size) {
          v7->stack.len = LBLOCK_STACK_SIZE(offset);
        }
        break;
      } else {
#ifdef V7_BCODE_TRACE
        fprintf(stderr, "skipped block #%d: %u\n", (int) cur_block,
                (unsigned int) LBLOCK_OFFSET(offset));
#endif
        /*
         * since we don't need to control transfer there, just pop
         * it from the "try stack"
         */
        v7_array_del(v7, arr, length - 1);
      }
    }
  }

  tmp_frame_cleanup(&tf);
  return found_block;
}

/*
 * Perform break, if there is a `finally` block in effect, transfer
 * control there.
 */
static void bcode_perform_break(struct v7 *v7, struct bcode_registers *r) {
  enum local_block found;
  unsigned int mask;
  v7->is_breaking = 0;
  if (v7->is_continuing) {
    mask = LOCAL_BLOCK_LOOP;
  } else {
    mask = LOCAL_BLOCK_LOOP | LOCAL_BLOCK_SWITCH;
  }

  /*
   * Keep unwinding until we find local block of interest. We should not
   * encounter any "function" frames; only "private" frames are allowed.
   */
  for (;;) {
    /*
     * Try to unwind local "try stack", considering only `finally` and `break`.
     */
    found = unwind_local_blocks_stack(v7, r, mask | LOCAL_BLOCK_FINALLY, 0);
    if (found == LOCAL_BLOCK_NONE) {
      /*
       * no blocks found: this may happen if only the `break` or `continue` has
       * occurred inside "private" frame. So, unwind this frame, make sure it
       * is indeed a "private" frame, and keep unwinding local blocks.
       */
      v7_call_frame_mask_t frame_type_mask = unwind_stack_1level(v7, r);
      assert(frame_type_mask == V7_CALL_FRAME_MASK_PRIVATE);
      (void) frame_type_mask;
    } else {
      /* found some block to transfer control into, stop unwinding */
      break;
    }
  }

  /*
   * upon exit of a finally block we'll reenter here if is_breaking is true.
   * See OP_AFTER_FINALLY.
   */
  if (found == LOCAL_BLOCK_FINALLY) {
    v7->is_breaking = 1;
  }

  /* `ops` already points to the needed instruction, no need to increment it */
  r->need_inc_ops = 0;
}

/*
 * Perform return, but if there is a `finally` block in effect, transfer
 * control there.
 *
 * If `take_retval` is non-zero, value to return will be popped from stack
 * (and saved into `v7->vals.returned_value`), otherwise, it won't ae affected.
 */
static enum v7_err bcode_perform_return(struct v7 *v7,
                                        struct bcode_registers *r,
                                        int take_retval) {
  /*
   * We should either take retval from the stack, or some value should already
   * de pending to return
   */
  assert(take_retval || v7->is_returned);

  if (take_retval) {
    /* taking return value from stack */
    v7->vals.returned_value = POP();
    v7->is_returned = 1;

    /*
     * returning (say, from `finally`) dismisses any errors that are eeing
     * thrown at the moment as well
     */
    v7->is_thrown = 0;
    v7->vals.thrown_error = V7_UNDEFINED;
  }

  /*
   * Keep unwinding until we unwound "function" frame, or found some `finally`
   * block.
   */
  for (;;) {
    /* Try to unwind local "try stack", considering only `finally` blocks */
    if (unwind_local_blocks_stack(v7, r, (LOCAL_BLOCK_FINALLY), 0) ==
        LOCAL_BLOCK_NONE) {
      /*
       * no `finally` blocks were found, so, unwind stack by 1 level, and see
       * if it's a "function" frame. If not, will keep unwinding.
       */
      if (unwind_stack_1level(v7, r) & V7_CALL_FRAME_MASK_BCODE) {
        /*
         * unwound frame is a "function" frame, so, push returned value to
         * stack, and stop unwinding
         */
        PUSH(v7->vals.returned_value);
        v7->is_returned = 0;
        v7->vals.returned_value = V7_UNDEFINED;

        break;
      }
    } else {
      /* found `finally` block, so, stop unwinding */
      break;
    }
  }

  /* `ops` already points to the needed instruction, no need to increment it */
  r->need_inc_ops = 0;

  return V7_OK;
}

/*
 * Perform throw inside `eval_bcode()`.
 *
 * If `take_thrown_value` is non-zero, value to return will be popped from
 * stack (and saved into `v7->vals.thrown_error`), otherwise, it won't be
 * affected.
 *
 * Returns `V7_OK` if thrown exception was caught, `V7_EXEC_EXCEPTION`
 * otherwise (in this case, evaluation of current script must be stopped)
 *
 * When calling this function from `eval_rcode()`, you should wrap this call
 * into the `V7_TRY()` macro.
 */
static enum v7_err bcode_perform_throw(struct v7 *v7, struct bcode_registers *r,
                                       int take_thrown_value) {
  enum v7_err rcode = V7_OK;
  enum local_block found;

  assert(take_thrown_value || v7->is_thrown);

  if (take_thrown_value) {
    v7->vals.thrown_error = POP();
    v7->is_thrown = 1;

    /* Throwing dismisses any pending return values */
    v7->is_returned = 0;
    v7->vals.returned_value = V7_UNDEFINED;
  }

  while ((found = unwind_local_blocks_stack(
              v7, r, (LOCAL_BLOCK_CATCH | LOCAL_BLOCK_FINALLY), 1)) ==
         LOCAL_BLOCK_NONE) {
    if (v7->call_stack != v7->bottom_call_frame) {
#ifdef V7_BCODE_TRACE
      fprintf(stderr, "not at the bottom of the stack, going to unwind..\n");
#endif
      /* not reached bottom of the stack yet, keep unwinding */
      unwind_stack_1level(v7, r);
    } else {
/* reached stack bottom: uncaught exception */
#ifdef V7_BCODE_TRACE
      fprintf(stderr, "reached stack bottom: uncaught exception\n");
#endif
      rcode = V7_EXEC_EXCEPTION;
      break;
    }
  }

  if (found == LOCAL_BLOCK_CATCH) {
    /*
     * we're going to enter `catch` block, so, populate TOS with the thrown
     * value, and clear it in v7 context.
     */
    PUSH(v7->vals.thrown_error);
    v7->is_thrown = 0;
    v7->vals.thrown_error = V7_UNDEFINED;
  }

  /* `ops` already points to the needed instruction, no need to increment it */
  r->need_inc_ops = 0;

  return rcode;
}

/*
 * Throws reference error from `eval_bcode()`. Always wrap a call to this
 * function into `V7_TRY()`.
 */
static enum v7_err bcode_throw_reference_error(struct v7 *v7,
                                               struct bcode_registers *r,
                                               val_t var_name) {
  enum v7_err rcode = V7_OK;
  const char *s;
  size_t name_len;

  assert(v7_is_string(var_name));
  s = v7_get_string(v7, &var_name, &name_len);

  rcode = v7_throwf(v7, REFERENCE_ERROR, "[%.*s] is not defined",
                    (int) name_len, s);
  (void) rcode;
  return bcode_perform_throw(v7, r, 0);
}

/*
 * Takes a half-done function (either from literal table or deserialized from
 * `ops` inlined data), and returns a ready-to-use function.
 *
 * The actual behaviour depends on whether the half-done function has
 * `prototype` defined. If there's no prototype (i.e. it's `undefined`), then
 * the new function is created, with bcode from a given one. If, however,
 * the prototype is defined, it means that the function was just deserialized
 * from `ops`, so we only need to set `scope` on it.
 *
 * Assumes `func` is owned by the caller.
 */
static val_t bcode_instantiate_function(struct v7 *v7, val_t func) {
  val_t res;
  struct v7_generic_object *scope;
  struct v7_js_function *f;
  assert(is_js_function(func));
  f = get_js_function_struct(func);

  scope = get_generic_object_struct(get_scope(v7));

  if (v7_is_undefined(v7_get(v7, func, "prototype", 9))) {
    /*
     * Function's `prototype` is `undefined`: it means that the function is
     * created by the compiler and is stored in the literal table. We have to
     * create completely new function
     */
    struct v7_js_function *rf;

    res = mk_js_function(v7, scope, v7_mk_object(v7));

    /* Copy and retain bcode */
    rf = get_js_function_struct(res);
    rf->bcode = f->bcode;
    retain_bcode(v7, rf->bcode);
  } else {
    /*
     * Function's `prototype` is NOT `undefined`: it means that the function is
     * deserialized from inline `ops` data, and we just need to set scope on
     * it.
     */
    res = func;
    f->scope = scope;
  }

  return res;
}

/**
 * Call C function `func` with given `this_object` and array of arguments
 * `args`. `func` should be a C function pointer, not C function object.
 */
static enum v7_err call_cfunction(struct v7 *v7, val_t func, val_t this_object,
                                  val_t args, uint8_t is_constructor,
                                  val_t *res) {
  enum v7_err rcode = V7_OK;
  uint8_t saved_inhibit_gc = v7->inhibit_gc;
  val_t saved_arguments = v7->vals.arguments;
  struct gc_tmp_frame tf = new_tmp_frame(v7);
  v7_cfunction_t *cfunc = get_cfunction_ptr(v7, func);

  *res = V7_UNDEFINED;

  tmp_stack_push(&tf, &saved_arguments);

  append_call_frame_cfunc(v7, this_object, cfunc);

  /*
   * prepare cfunction environment
   */
  v7->inhibit_gc = 1;
  v7->vals.arguments = args;

  /* call C function */
  rcode = cfunc(v7, res);
  if (rcode != V7_OK) {
    goto clean;
  }

  if (is_constructor && !v7_is_object(*res)) {
    /* constructor returned non-object: replace it with `this` */
    *res = v7_get_this(v7);
  }

clean:
  v7->vals.arguments = saved_arguments;
  v7->inhibit_gc = saved_inhibit_gc;

  unwind_stack_1level(v7, NULL);

  tmp_frame_cleanup(&tf);
  return rcode;
}

/*
 * Evaluate `OP_TRY_PUSH_CATCH` or `OP_TRY_PUSH_FINALLY`: Take an offset (from
 * the parameter of opcode) and push it onto "try stack"
 */
static void eval_try_push(struct v7 *v7, enum opcode op,
                          struct bcode_registers *r) {
  val_t arr = V7_UNDEFINED;
  struct gc_tmp_frame tf = new_tmp_frame(v7);
  bcode_off_t target;
  int64_t offset_tag = 0;

  tmp_stack_push(&tf, &arr);

  /* make sure "try stack" array exists */
  arr = find_call_frame_private(v7)->vals.try_stack;
  if (!v7_is_array(v7, arr)) {
    arr = v7_mk_dense_array(v7);
    find_call_frame_private(v7)->vals.try_stack = arr;
  }

  /*
   * push the target address at the end of the "try stack" array
   */
  switch (op) {
    case OP_TRY_PUSH_CATCH:
      offset_tag = LBLOCK_TAG_CATCH;
      break;
    case OP_TRY_PUSH_FINALLY:
      offset_tag = LBLOCK_TAG_FINALLY;
      break;
    case OP_TRY_PUSH_LOOP:
      offset_tag = LBLOCK_TAG_LOOP;
      break;
    case OP_TRY_PUSH_SWITCH:
      offset_tag = LBLOCK_TAG_SWITCH;
      break;
    default:
      assert(0);
      break;
  }
  target = bcode_get_target(&r->ops);
  v7_array_push(v7, arr, v7_mk_number(v7, LBLOCK_ITEM_CREATE(target, offset_tag,
                                                             v7->stack.len)));

  tmp_frame_cleanup(&tf);
}

/*
 * Evaluate `OP_TRY_POP`: just pop latest item from "try stack", ignoring it
 */
static enum v7_err eval_try_pop(struct v7 *v7) {
  enum v7_err rcode = V7_OK;
  val_t arr = V7_UNDEFINED;
  unsigned long length;
  struct gc_tmp_frame tf = new_tmp_frame(v7);

  tmp_stack_push(&tf, &arr);

  /* get "try stack" array, which must be defined and must not be emtpy */
  arr = find_call_frame_private(v7)->vals.try_stack;
  if (!v7_is_array(v7, arr)) {
    rcode = v7_throwf(v7, "Error", "TRY_POP when try_stack is not an array");
    V7_TRY(V7_INTERNAL_ERROR);
  }

  length = v7_array_length(v7, arr);
  if (length == 0) {
    rcode = v7_throwf(v7, "Error", "TRY_POP when try_stack is empty");
    V7_TRY(V7_INTERNAL_ERROR);
  }

  /* delete the latest element of this array */
  v7_array_del(v7, arr, length - 1);

clean:
  tmp_frame_cleanup(&tf);
  return rcode;
}

static void own_bcode(struct v7 *v7, struct bcode *p) {
  mbuf_append(&v7->act_bcodes, &p, sizeof(p));
}

static void disown_bcode(struct v7 *v7, struct bcode *p) {
#ifndef NDEBUG
  struct bcode **vp =
      (struct bcode **) (v7->act_bcodes.buf + v7->act_bcodes.len - sizeof(p));

  /* given `p` should be the last item */
  assert(*vp == p);
#endif
  v7->act_bcodes.len -= sizeof(p);
}

/* Keeps track of last evaluated bcodes in order to improve error reporting */
static void push_bcode_history(struct v7 *v7, enum opcode op) {
  size_t i;

  if (op == OP_CHECK_CALL || op == OP_CALL || op == OP_NEW) return;

  for (i = ARRAY_SIZE(v7->last_ops) - 1; i > 0; i--) {
    v7->last_ops[i] = v7->last_ops[i - 1];
  }
  v7->last_ops[0] = op;
}

#ifndef V7_DISABLE_CALL_ERROR_CONTEXT
static void reset_last_name(struct v7 *v7) {
  v7->vals.last_name[0] = V7_UNDEFINED;
  v7->vals.last_name[1] = V7_UNDEFINED;
}
#else
static void reset_last_name(struct v7 *v7) {
  /* should be inlined out */
  (void) v7;
}
#endif

static void prop_iter_ctx_dtor(struct v7 *v7, void *ud) {
  struct prop_iter_ctx *ctx = (struct prop_iter_ctx *) ud;
  v7_destruct_prop_iter_ctx(v7, ctx);
  free(ctx);
}

/*
 * Evaluates given `bcode`. If `reset_line_no` is non-zero, the line number
 * is initially reset to 1; otherwise, it is inherited from the previous call
 * frame.
 */
WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err eval_bcode(struct v7 *v7, struct bcode *bcode,
                                  val_t this_object, uint8_t reset_line_no,
                                  val_t *_res) {
  struct bcode_registers r;
  enum v7_err rcode = V7_OK;
  struct v7_call_frame_base *saved_bottom_call_frame = v7->bottom_call_frame;

  /*
   * Dummy variable just to enforce that `BTRY()` macro is used only inside the
   * `eval_bcode()` function
   */
  uint8_t _you_should_use_BTRY_in_eval_bcode_only = 0;

  char buf[512];

  val_t res = V7_UNDEFINED, v1 = V7_UNDEFINED, v2 = V7_UNDEFINED,
        v3 = V7_UNDEFINED, v4 = V7_UNDEFINED, scope_frame = V7_UNDEFINED;
  struct gc_tmp_frame tf = new_tmp_frame(v7);

  append_call_frame_bcode(v7, NULL, bcode, this_object, get_scope(v7), 0);

  if (reset_line_no) {
    v7->call_stack->line_no = 1;
  }

  /*
   * Set current call stack as the "bottom" call stack, so that bcode evaluator
   * will exit when it reaches this "bottom"
   */
  v7->bottom_call_frame = v7->call_stack;

  bcode_restore_registers(v7, bcode, &r);

  tmp_stack_push(&tf, &res);
  tmp_stack_push(&tf, &v1);
  tmp_stack_push(&tf, &v2);
  tmp_stack_push(&tf, &v3);
  tmp_stack_push(&tf, &v4);
  tmp_stack_push(&tf, &scope_frame);

  /*
   * populate local variables on current scope, making them undeletable
   * (since they're defined with `var`)
   */
  {
    size_t i;
    for (i = 0; i < bcode->names_cnt; ++i) {
      r.ops = bcode_next_name_v(v7, bcode, r.ops, &v1);

      /* set undeletable property on current scope */
      V7_TRY(def_property_v(v7, get_scope(v7), v1, V7_DESC_CONFIGURABLE(0),
                            V7_UNDEFINED, 1 /*as_assign*/, NULL));
    }
  }

restart:
  while (r.ops < r.end && rcode == V7_OK) {
    enum opcode op = (enum opcode) * r.ops;

#ifndef V7_DISABLE_LINE_NUMBERS
    if ((uint8_t) op >= _OP_LINE_NO) {
      unsigned char buf[sizeof(size_t)];
      int len;
      size_t max_llen = sizeof(buf);

      /* ASAN doesn't like out of bound reads */
      if (r.ops + max_llen > r.end) {
        max_llen = r.end - r.ops;
      }

      /*
       * before we decode varint, we'll have to swap MSB and LSB, but we can't
       * do it in place since we're decoding from constant memory, so, we also
       * have to copy the data to the temp buffer first. 4 bytes should be
       * enough for everyone's line number.
       */
      memcpy(buf, r.ops, max_llen);
      buf[0] = msb_lsb_swap(buf[0]);

      v7->call_stack->line_no = decode_varint(buf, &len) >> 1;
      assert((size_t) len <= sizeof(buf));
      r.ops += len;

      continue;
    }
#endif

    push_bcode_history(v7, op);

    if (v7->need_gc) {
      if (maybe_gc(v7)) {
        v7->need_gc = 0;
      }
    }

    r.need_inc_ops = 1;
#ifdef V7_BCODE_TRACE
    {
      char *dops = r.ops;
      fprintf(stderr, "eval ");
      dump_op(v7, stderr, r.bcode, &dops);
    }
#endif

    switch (op) {
      case OP_DROP:
        POP();
        break;
      case OP_DUP:
        v1 = POP();
        PUSH(v1);
        PUSH(v1);
        break;
      case OP_2DUP:
        v2 = POP();
        v1 = POP();
        PUSH(v1);
        PUSH(v2);
        PUSH(v1);
        PUSH(v2);
        break;
      case OP_SWAP:
        v1 = POP();
        v2 = POP();
        PUSH(v1);
        PUSH(v2);
        break;
      case OP_STASH:
        assert(!v7->is_stashed);
        v7->vals.stash = TOS();
        v7->is_stashed = 1;
        break;
      case OP_UNSTASH:
        assert(v7->is_stashed);
        POP();
        PUSH(v7->vals.stash);
        v7->vals.stash = V7_UNDEFINED;
        v7->is_stashed = 0;
        break;

      case OP_SWAP_DROP:
        v1 = POP();
        POP();
        PUSH(v1);
        break;

      case OP_PUSH_UNDEFINED:
        PUSH(V7_UNDEFINED);
        break;
      case OP_PUSH_NULL:
        PUSH(V7_NULL);
        break;
      case OP_PUSH_THIS:
        PUSH(v7_get_this(v7));
        reset_last_name(v7);
        break;
      case OP_PUSH_TRUE:
        PUSH(v7_mk_boolean(v7, 1));
        reset_last_name(v7);
        break;
      case OP_PUSH_FALSE:
        PUSH(v7_mk_boolean(v7, 0));
        reset_last_name(v7);
        break;
      case OP_PUSH_ZERO:
        PUSH(v7_mk_number(v7, 0));
        reset_last_name(v7);
        break;
      case OP_PUSH_ONE:
        PUSH(v7_mk_number(v7, 1));
        reset_last_name(v7);
        break;
      case OP_PUSH_LIT: {
        PUSH(bcode_decode_lit(v7, r.bcode, &r.ops));
#ifndef V7_DISABLE_CALL_ERROR_CONTEXT
        /* name tracking */
        if (!v7_is_string(TOS())) {
          reset_last_name(v7);
        }
#endif
        break;
      }
      case OP_LOGICAL_NOT:
        v1 = POP();
        PUSH(v7_mk_boolean(v7, !v7_is_truthy(v7, v1)));
        break;
      case OP_NOT: {
        v1 = POP();
        BTRY(to_number_v(v7, v1, &v1));
        PUSH(v7_mk_number(v7, ~(int32_t) v7_get_double(v7, v1)));
        break;
      }
      case OP_NEG: {
        v1 = POP();
        BTRY(to_number_v(v7, v1, &v1));
        PUSH(v7_mk_number(v7, -v7_get_double(v7, v1)));
        break;
      }
      case OP_POS: {
        v1 = POP();
        BTRY(to_number_v(v7, v1, &v1));
        PUSH(v1);
        break;
      }
      case OP_ADD: {
        v2 = POP();
        v1 = POP();

        /*
         * If either operand is an object, convert both of them to primitives
         */
        if (v7_is_object(v1) || v7_is_object(v2)) {
          BTRY(to_primitive(v7, v1, V7_TO_PRIMITIVE_HINT_AUTO, &v1));
          BTRY(to_primitive(v7, v2, V7_TO_PRIMITIVE_HINT_AUTO, &v2));
        }

        if (v7_is_string(v1) || v7_is_string(v2)) {
          /* Convert both operands to strings, and concatenate */

          BTRY(primitive_to_str(v7, v1, &v1, NULL, 0, NULL));
          BTRY(primitive_to_str(v7, v2, &v2, NULL, 0, NULL));

          PUSH(s_concat(v7, v1, v2));
        } else {
          /* Convert both operands to numbers, and sum */

          BTRY(primitive_to_number(v7, v1, &v1));
          BTRY(primitive_to_number(v7, v2, &v2));

          PUSH(v7_mk_number(v7, b_num_bin_op(op, v7_get_double(v7, v1),
                                             v7_get_double(v7, v2))));
        }
        break;
      }
      case OP_SUB:
      case OP_REM:
      case OP_MUL:
      case OP_DIV:
      case OP_LSHIFT:
      case OP_RSHIFT:
      case OP_URSHIFT:
      case OP_OR:
      case OP_XOR:
      case OP_AND: {
        v2 = POP();
        v1 = POP();

        BTRY(to_number_v(v7, v1, &v1));
        BTRY(to_number_v(v7, v2, &v2));

        PUSH(v7_mk_number(v7, b_num_bin_op(op, v7_get_double(v7, v1),
                                           v7_get_double(v7, v2))));
        break;
      }
      case OP_EQ_EQ: {
        v2 = POP();
        v1 = POP();
        if (v7_is_string(v1) && v7_is_string(v2)) {
          res = v7_mk_boolean(v7, s_cmp(v7, v1, v2) == 0);
        } else if (v1 == v2 && v1 == V7_TAG_NAN) {
          res = v7_mk_boolean(v7, 0);
        } else {
          res = v7_mk_boolean(v7, v1 == v2);
        }
        PUSH(res);
        break;
      }
      case OP_NE_NE: {
        v2 = POP();
        v1 = POP();
        if (v7_is_string(v1) && v7_is_string(v2)) {
          res = v7_mk_boolean(v7, s_cmp(v7, v1, v2) != 0);
        } else if (v1 == v2 && v1 == V7_TAG_NAN) {
          res = v7_mk_boolean(v7, 1);
        } else {
          res = v7_mk_boolean(v7, v1 != v2);
        }
        PUSH(res);
        break;
      }
      case OP_EQ:
      case OP_NE: {
        v2 = POP();
        v1 = POP();
        /*
         * TODO(dfrank) : it's not really correct. Fix it accordingly to
         * the p. 4.9 of The Definitive Guide (page 71)
         */
        if (((v7_is_object(v1) || v7_is_object(v2)) && v1 == v2)) {
          res = v7_mk_boolean(v7, op == OP_EQ);
          PUSH(res);
          break;
        } else if (v7_is_undefined(v1) || v7_is_null(v1)) {
          res = v7_mk_boolean(
              v7, (op != OP_EQ) ^ (v7_is_undefined(v2) || v7_is_null(v2)));
          PUSH(res);
          break;
        } else if (v7_is_undefined(v2) || v7_is_null(v2)) {
          res = v7_mk_boolean(
              v7, (op != OP_EQ) ^ (v7_is_undefined(v1) || v7_is_null(v1)));
          PUSH(res);
          break;
        }

        if (v7_is_string(v1) && v7_is_string(v2)) {
          int cmp = s_cmp(v7, v1, v2);
          switch (op) {
            case OP_EQ:
              res = v7_mk_boolean(v7, cmp == 0);
              break;
            case OP_NE:
              res = v7_mk_boolean(v7, cmp != 0);
              break;
            default:
              /* should never be here */
              assert(0);
          }
        } else {
          /* Convert both operands to numbers */

          BTRY(to_number_v(v7, v1, &v1));
          BTRY(to_number_v(v7, v2, &v2));

          res = v7_mk_boolean(v7, b_bool_bin_op(op, v7_get_double(v7, v1),
                                                v7_get_double(v7, v2)));
        }
        PUSH(res);
        break;
      }
      case OP_LT:
      case OP_LE:
      case OP_GT:
      case OP_GE: {
        v2 = POP();
        v1 = POP();
        BTRY(to_primitive(v7, v1, V7_TO_PRIMITIVE_HINT_NUMBER, &v1));
        BTRY(to_primitive(v7, v2, V7_TO_PRIMITIVE_HINT_NUMBER, &v2));

        if (v7_is_string(v1) && v7_is_string(v2)) {
          int cmp = s_cmp(v7, v1, v2);
          switch (op) {
            case OP_LT:
              res = v7_mk_boolean(v7, cmp < 0);
              break;
            case OP_LE:
              res = v7_mk_boolean(v7, cmp <= 0);
              break;
            case OP_GT:
              res = v7_mk_boolean(v7, cmp > 0);
              break;
            case OP_GE:
              res = v7_mk_boolean(v7, cmp >= 0);
              break;
            default:
              /* should never be here */
              assert(0);
          }
        } else {
          /* Convert both operands to numbers */

          BTRY(to_number_v(v7, v1, &v1));
          BTRY(to_number_v(v7, v2, &v2));

          res = v7_mk_boolean(v7, b_bool_bin_op(op, v7_get_double(v7, v1),
                                                v7_get_double(v7, v2)));
        }
        PUSH(res);
        break;
      }
      case OP_INSTANCEOF: {
        v2 = POP();
        v1 = POP();
        if (!v7_is_callable(v7, v2)) {
          BTRY(v7_throwf(v7, TYPE_ERROR,
                         "Expecting a function in instanceof check"));
          goto op_done;
        } else {
          PUSH(v7_mk_boolean(
              v7, is_prototype_of(v7, v1, v7_get(v7, v2, "prototype", 9))));
        }
        break;
      }
      case OP_TYPEOF:
        v1 = POP();
        switch (val_type(v7, v1)) {
          case V7_TYPE_NUMBER:
            res = v7_mk_string(v7, "number", 6, 1);
            break;
          case V7_TYPE_STRING:
            res = v7_mk_string(v7, "string", 6, 1);
            break;
          case V7_TYPE_BOOLEAN:
            res = v7_mk_string(v7, "boolean", 7, 1);
            break;
          case V7_TYPE_FUNCTION_OBJECT:
          case V7_TYPE_CFUNCTION_OBJECT:
          case V7_TYPE_CFUNCTION:
            res = v7_mk_string(v7, "function", 8, 1);
            break;
          case V7_TYPE_UNDEFINED:
            res = v7_mk_string(v7, "undefined", 9, 1);
            break;
          default:
            res = v7_mk_string(v7, "object", 6, 1);
            break;
        }
        PUSH(res);
        break;
      case OP_IN: {
        struct v7_property *prop = NULL;
        v2 = POP();
        v1 = POP();
        BTRY(to_string(v7, v1, NULL, buf, sizeof(buf), NULL));
        prop = v7_get_property(v7, v2, buf, ~0);
        PUSH(v7_mk_boolean(v7, prop != NULL));
      } break;
      case OP_GET:
        v2 = POP();
        v1 = POP();
        BTRY(v7_get_throwing_v(v7, v1, v2, &v3));
        PUSH(v3);
#ifndef V7_DISABLE_CALL_ERROR_CONTEXT
        v7->vals.last_name[1] = v7->vals.last_name[0];
        v7->vals.last_name[0] = v2;
#endif
        break;
      case OP_SET: {
        v3 = POP();
        v2 = POP();
        v1 = POP();

        /* convert name to string, if it's not already */
        BTRY(to_string(v7, v2, &v2, NULL, 0, NULL));

        /* set value */
        BTRY(set_property_v(v7, v1, v2, v3, NULL));

        PUSH(v3);
        break;
      }
      case OP_GET_VAR:
      case OP_SAFE_GET_VAR: {
        struct v7_property *p = NULL;
        assert(r.ops < r.end - 1);
        v1 = bcode_decode_lit(v7, r.bcode, &r.ops);
        BTRY(v7_get_property_v(v7, get_scope(v7), v1, &p));
        if (p == NULL) {
          if (op == OP_SAFE_GET_VAR) {
            PUSH(V7_UNDEFINED);
          } else {
            /* variable does not exist: Reference Error */
            V7_TRY(bcode_throw_reference_error(v7, &r, v1));
            goto op_done;
          }
          break;
        } else {
          BTRY(v7_property_value(v7, get_scope(v7), p, &v2));
          PUSH(v2);
        }
#ifndef V7_DISABLE_CALL_ERROR_CONTEXT
        v7->vals.last_name[0] = v1;
        v7->vals.last_name[1] = V7_UNDEFINED;
#endif
        break;
      }
      case OP_SET_VAR: {
        struct v7_property *prop;
        v3 = POP();
        v2 = bcode_decode_lit(v7, r.bcode, &r.ops);
        v1 = get_scope(v7);

        BTRY(to_string(v7, v2, NULL, buf, sizeof(buf), NULL));
        prop = v7_get_property(v7, v1, buf, strlen(buf));
        if (prop != NULL) {
          /* Property already exists: update its value */
          /*
           * TODO(dfrank): currently we can't use `def_property_v()` here,
           * because if the property was already found somewhere in the
           * prototype chain, then it should be updated, instead of creating a
           * new one on the top of the scope.
           *
           * Probably we need to make `def_property_v()` more generic and
           * use it here; or split `def_property_v()` into smaller pieces and
           * use one of them here.
           */
          if (!(prop->attributes & V7_PROPERTY_NON_WRITABLE)) {
            prop->value = v3;
          }
        } else if (!r.bcode->strict_mode) {
          /*
           * Property does not exist: since we're not in strict mode, let's
           * create new property at Global Object
           */
          BTRY(set_property_v(v7, v7_get_global(v7), v2, v3, NULL));
        } else {
          /*
           * In strict mode, throw reference error instead of polluting Global
           * Object
           */
          V7_TRY(bcode_throw_reference_error(v7, &r, v2));
          goto op_done;
        }
        PUSH(v3);
        break;
      }
      case OP_JMP: {
        bcode_off_t target = bcode_get_target(&r.ops);
        r.ops = r.bcode->ops.p + target - 1;
        break;
      }
      case OP_JMP_FALSE: {
        bcode_off_t target = bcode_get_target(&r.ops);
        v1 = POP();
        if (!v7_is_truthy(v7, v1)) {
          r.ops = r.bcode->ops.p + target - 1;
        }
        break;
      }
      case OP_JMP_TRUE: {
        bcode_off_t target = bcode_get_target(&r.ops);
        v1 = POP();
        if (v7_is_truthy(v7, v1)) {
          r.ops = r.bcode->ops.p + target - 1;
        }
        break;
      }
      case OP_JMP_TRUE_DROP: {
        bcode_off_t target = bcode_get_target(&r.ops);
        v1 = POP();
        if (v7_is_truthy(v7, v1)) {
          r.ops = r.bcode->ops.p + target - 1;
          v1 = POP();
          POP();
          PUSH(v1);
        }
        break;
      }
      case OP_JMP_IF_CONTINUE: {
        bcode_off_t target = bcode_get_target(&r.ops);
        if (v7->is_continuing) {
          r.ops = r.bcode->ops.p + target - 1;
        }
        v7->is_continuing = 0;
        break;
      }
      case OP_CREATE_OBJ:
        PUSH(v7_mk_object(v7));
        break;
      case OP_CREATE_ARR:
        PUSH(v7_mk_array(v7));
        break;
      case OP_PUSH_PROP_ITER_CTX: {
        struct prop_iter_ctx *ctx =
            (struct prop_iter_ctx *) calloc(1, sizeof(*ctx));
        BTRY(init_prop_iter_ctx(v7, TOS(), 1, ctx));
        v1 = v7_mk_object(v7);
        v7_set_user_data(v7, v1, ctx);
        v7_set_destructor_cb(v7, v1, prop_iter_ctx_dtor);
        PUSH(v1);
        break;
      }
      case OP_NEXT_PROP: {
        struct prop_iter_ctx *ctx = NULL;
        int ok = 0;
        v1 = POP(); /* ctx */
        v2 = POP(); /* object */

        ctx = (struct prop_iter_ctx *) v7_get_user_data(v7, v1);

        if (v7_is_object(v2)) {
          v7_prop_attr_t attrs;

          do {
            /* iterate properties until we find a non-hidden enumerable one */
            do {
              BTRY(next_prop(v7, ctx, &res, NULL, &attrs, &ok));
            } while (ok && (attrs & (_V7_PROPERTY_HIDDEN |
                                     V7_PROPERTY_NON_ENUMERABLE)));

            if (!ok) {
              /* no more properties in this object: proceed to the prototype */
              v2 = v7_get_proto(v7, v2);
              if (get_generic_object_struct(v2) != NULL) {
                /*
                 * the prototype is a generic object, so, init the context for
                 * props iteration
                 */
                v7_destruct_prop_iter_ctx(v7, ctx);
                BTRY(init_prop_iter_ctx(v7, v2, 1, ctx));
              } else {
                /*
                 * we can't iterate the prototype's props, so, just stop
                 * iteration.
                 */
                ctx = NULL;
              }
            }
          } while (!ok && ctx != NULL);
        } else {
          /*
           * Not an object: reset the context.
           */
          ctx = NULL;
        }

        if (ctx == NULL) {
          PUSH(v7_mk_boolean(v7, 0));

          /*
           * We could leave the context unfreed, and let the
           * `prop_iter_ctx_dtor()` free it when the v1 will be GC-d, but
           * let's do that earlier.
           */
          ctx = (struct prop_iter_ctx *) v7_get_user_data(v7, v1);
          v7_destruct_prop_iter_ctx(v7, ctx);
          free(ctx);
          v7_set_user_data(v7, v1, NULL);
          v7_set_destructor_cb(v7, v1, NULL);
        } else {
          PUSH(v2);
          PUSH(v1);
          PUSH(res);
          PUSH(v7_mk_boolean(v7, 1));
        }
        break;
      }
      case OP_FUNC_LIT: {
        v1 = POP();
        v2 = bcode_instantiate_function(v7, v1);
        PUSH(v2);
        break;
      }
      case OP_CHECK_CALL:
        v1 = TOS();
        if (!v7_is_callable(v7, v1)) {
          int arity = 0;
          enum v7_err ignore;
/* tried to call non-function object: throw a TypeError */

#ifndef V7_DISABLE_CALL_ERROR_CONTEXT
          /*
           * try to provide some useful context for the error message
           * using a good-enough heuristics
           * but defer actual throw when process the incriminated call
           * in order to evaluate the arguments as required by the spec.
           */
          if (v7->last_ops[0] == OP_GET_VAR) {
            arity = 1;
          } else if (v7->last_ops[0] == OP_GET &&
                     v7->last_ops[1] == OP_PUSH_LIT) {
            /*
             * OP_PUSH_LIT is used to both push property names for OP_GET
             * and for pushing actual literals. During PUSH_LIT push lit
             * evaluation we reset the last name variable in case the literal
             * is not a string, such as in `[].foo()`.
             * Unfortunately it doesn't handle `"foo".bar()`; could be
             * solved by adding another bytecode for property literals but
             * probably it doesn't matter much.
             */
            if (v7_is_undefined(v7->vals.last_name[1])) {
              arity = 1;
            } else {
              arity = 2;
            }
          }
#endif

          switch (arity) {
            case 0:
              ignore = v7_throwf(v7, TYPE_ERROR, "value is not a function");
              break;
#ifndef V7_DISABLE_CALL_ERROR_CONTEXT

            case 1:
              ignore = v7_throwf(v7, TYPE_ERROR, "%s is not a function",
                                 v7_get_cstring(v7, &v7->vals.last_name[0]));
              break;
            case 2:
              ignore = v7_throwf(v7, TYPE_ERROR, "%s.%s is not a function",
                                 v7_get_cstring(v7, &v7->vals.last_name[1]),
                                 v7_get_cstring(v7, &v7->vals.last_name[0]));
              break;
#endif
          };

          v7->vals.call_check_ex = v7->vals.thrown_error;
          v7_clear_thrown_value(v7);
          (void) ignore;
        }
        break;
      case OP_CALL:
      case OP_NEW: {
        /* Naive implementation pending stack frame redesign */
        int args = (int) *(++r.ops);
        uint8_t is_constructor = (op == OP_NEW);

        if (SP() < (args + 1 /*func*/ + 1 /*this*/)) {
          BTRY(v7_throwf(v7, INTERNAL_ERROR, "stack underflow"));
          goto op_done;
        } else {
          v2 = v7_mk_dense_array(v7);
          while (args > 0) {
            BTRY(v7_array_set_throwing(v7, v2, --args, POP(), NULL));
          }
          /* pop function to call */
          v1 = POP();

          /* pop `this` */
          v3 = POP();

          /*
           * adjust `this` if the function is called with the constructor
           * invocation pattern
           */
          if (is_constructor) {
            /*
             * The function is invoked as a constructor: we ignore `this`
             * value popped from stack, create new object and set prototype.
             */

            /*
             * get "prototype" property from the constructor function,
             * and make sure it's an object
             */
            v4 = v7_get(v7, v1 /*func*/, "prototype", 9);
            if (!v7_is_object(v4)) {
              /* TODO(dfrank): box primitive value */
              BTRY(v7_throwf(
                  v7, TYPE_ERROR,
                  "Cannot set a primitive value as object prototype"));
              goto op_done;
            } else if (is_cfunction_lite(v4)) {
              /*
               * TODO(dfrank): maybe add support for a cfunction pointer to be
               * a prototype
               */
              BTRY(v7_throwf(v7, TYPE_ERROR,
                             "Not implemented: cfunction as a prototype"));
              goto op_done;
            }

            /* create an object with given prototype */
            v3 = mk_object(v7, v4 /*prototype*/);
            v4 = V7_UNDEFINED;
          }

          if (!v7_is_callable(v7, v1)) {
            /* tried to call non-function object: throw a TypeError */
            BTRY(v7_throw(v7, v7->vals.call_check_ex));
            goto op_done;
          } else if (is_cfunction_lite(v1) || is_cfunction_obj(v7, v1)) {
            /* call cfunction */

            /*
             * In "function invocation pattern", the `this` value popped from
             * stack is an `undefined`. And in non-strict mode, we should change
             * it to global object.
             */
            if (!is_constructor && !r.bcode->strict_mode &&
                v7_is_undefined(v3)) {
              v3 = v7->vals.global_object;
            }

            BTRY(call_cfunction(v7, v1 /*func*/, v3 /*this*/, v2 /*args*/,
                                is_constructor, &v4));

            /* push value returned from C function to bcode stack */
            PUSH(v4);

          } else {
            char *ops;
            struct v7_js_function *func = get_js_function_struct(v1);

            /*
             * In "function invocation pattern", the `this` value popped from
             * stack is an `undefined`. And in non-strict mode, we should change
             * it to global object.
             */
            if (!is_constructor && !func->bcode->strict_mode &&
                v7_is_undefined(v3)) {
              v3 = v7->vals.global_object;
            }

            scope_frame = v7_mk_object(v7);

            /*
             * Before actual opcodes, `ops` contains one or more
             * null-terminated strings: first of all, the function name (if the
             * function is anonymous, it's an empty string).
             *
             * Then, argument names follow. We know number of arguments, so, we
             * know how many names to take.
             *
             * And then, local variable names follow. We know total number of
             * strings (`names_cnt`), so, again, we know how many names to
             * take.
             */

            ops = func->bcode->ops.p;

            /* populate function itself */
            ops = bcode_next_name_v(v7, func->bcode, ops, &v4);
            BTRY(def_property_v(v7, scope_frame, v4, V7_DESC_CONFIGURABLE(0),
                                v1, 0 /*not assign*/, NULL));

            /* populate arguments */
            {
              int arg_num;
              for (arg_num = 0; arg_num < func->bcode->args_cnt; ++arg_num) {
                ops = bcode_next_name_v(v7, func->bcode, ops, &v4);
                BTRY(def_property_v(
                    v7, scope_frame, v4, V7_DESC_CONFIGURABLE(0),
                    v7_array_get(v7, v2, arg_num), 0 /*not assign*/, NULL));
              }
            }

            /* populate `arguments` object */

            /*
             * TODO(dfrank): it's actually much more complicated than that:
             * it's not an array, it's an array-like object. More, in
             * non-strict mode, elements of `arguments` object are just aliases
             * for actual arguments, so this one:
             *
             *   `(function(a){arguments[0]=2; return a;})(1);`
             *
             * should yield 2. Currently, it yields 1.
             */
            v7_def(v7, scope_frame, "arguments", 9, V7_DESC_CONFIGURABLE(0),
                   v2);

            /* populate local variables */
            {
              uint8_t loc_num;
              uint8_t loc_cnt = func->bcode->names_cnt - func->bcode->args_cnt -
                                1 /*func name*/;
              for (loc_num = 0; loc_num < loc_cnt; ++loc_num) {
                ops = bcode_next_name_v(v7, func->bcode, ops, &v4);
                BTRY(def_property_v(v7, scope_frame, v4,
                                    V7_DESC_CONFIGURABLE(0), V7_UNDEFINED,
                                    0 /*not assign*/, NULL));
              }
            }

            /* transfer control to the function */
            V7_TRY(bcode_perform_call(v7, scope_frame, func, &r, v3 /*this*/,
                                      ops, is_constructor));

            scope_frame = V7_UNDEFINED;
          }
        }
        break;
      }
      case OP_RET:
        bcode_adjust_retval(v7, 1 /*explicit return*/);
        V7_TRY(bcode_perform_return(v7, &r, 1 /*take value from stack*/));
        break;
      case OP_DELETE:
      case OP_DELETE_VAR: {
        size_t name_len;
        struct v7_property *prop;

        res = v7_mk_boolean(v7, 1);

        /* pop property name to delete */
        v2 = POP();

        if (op == OP_DELETE) {
          /* pop object to delete the property from */
          v1 = POP();
        } else {
          /* use scope as an object to delete the property from */
          v1 = get_scope(v7);
        }

        if (!v7_is_object(v1)) {
          /*
           * the "object" to delete a property from is not actually an object
           * (at least this can happen with cfunction pointers), will just
           * return `true`
           */
          goto delete_clean;
        }

        BTRY(to_string(v7, v2, NULL, buf, sizeof(buf), &name_len));

        prop = v7_get_property(v7, v1, buf, name_len);
        if (prop == NULL) {
          /* not found a property; will just return `true` */
          goto delete_clean;
        }

        /* found needed property */

        if (prop->attributes & V7_PROPERTY_NON_CONFIGURABLE) {
          /*
           * this property is undeletable. In non-strict mode, we just
           * return `false`; otherwise, we throw.
           */
          if (!r.bcode->strict_mode) {
            res = v7_mk_boolean(v7, 0);
          } else {
            BTRY(v7_throwf(v7, TYPE_ERROR, "Cannot delete property '%s'", buf));
            goto op_done;
          }
        } else {
          /*
           * delete property: when we operate on the current scope, we should
           * walk the prototype chain when deleting property.
           *
           * But when we operate on a "real" object, we should delete own
           * properties only.
           */
          if (op == OP_DELETE) {
            v7_del(v7, v1, buf, name_len);
          } else {
            del_property_deep(v7, v1, buf, name_len);
          }
        }

      delete_clean:
        PUSH(res);
        break;
      }
      case OP_TRY_PUSH_CATCH:
      case OP_TRY_PUSH_FINALLY:
      case OP_TRY_PUSH_LOOP:
      case OP_TRY_PUSH_SWITCH:
        eval_try_push(v7, op, &r);
        break;
      case OP_TRY_POP:
        V7_TRY(eval_try_pop(v7));
        break;
      case OP_AFTER_FINALLY:
        /*
         * exited from `finally` block: if some value is currently being
         * returned, continue returning it.
         *
         * Likewise, if some value is currently being thrown, continue
         * unwinding stack.
         */
        if (v7->is_thrown) {
          V7_TRY(
              bcode_perform_throw(v7, &r, 0 /*don't take value from stack*/));
          goto op_done;
        } else if (v7->is_returned) {
          V7_TRY(
              bcode_perform_return(v7, &r, 0 /*don't take value from stack*/));
          break;
        } else if (v7->is_breaking) {
          bcode_perform_break(v7, &r);
        }
        break;
      case OP_THROW:
        V7_TRY(bcode_perform_throw(v7, &r, 1 /*take thrown value*/));
        goto op_done;
      case OP_BREAK:
        bcode_perform_break(v7, &r);
        break;
      case OP_CONTINUE:
        v7->is_continuing = 1;
        bcode_perform_break(v7, &r);
        break;
      case OP_ENTER_CATCH: {
        /* pop thrown value from stack */
        v1 = POP();
        /* get the name of the thrown value */
        v2 = bcode_decode_lit(v7, r.bcode, &r.ops);

        /*
         * create a new stack frame (a "private" one), and set exception
         * property on it
         */
        scope_frame = v7_mk_object(v7);
        BTRY(set_property_v(v7, scope_frame, v2, v1, NULL));

        /* Push this "private" frame on the call stack */

        /* new scope_frame will inherit from the current scope */

        obj_prototype_set(v7, get_object_struct(scope_frame),
                          get_object_struct(get_scope(v7)));

        /*
         * Create new `call_frame` which will replace `v7->call_stack`.
         */
        append_call_frame_private(v7, scope_frame);

        break;
      }
      case OP_EXIT_CATCH: {
        v7_call_frame_mask_t frame_type_mask;
        /* unwind 1 frame */
        frame_type_mask = unwind_stack_1level(v7, &r);
        /* make sure the unwound frame is a "private" frame */
        assert(frame_type_mask == V7_CALL_FRAME_MASK_PRIVATE);
#if defined(NDEBUG)
        (void) frame_type_mask;
#endif
        break;
      }
      default:
        BTRY(v7_throwf(v7, INTERNAL_ERROR, "Unknown opcode: %d", (int) op));
        goto op_done;
    }

  op_done:
#ifdef V7_BCODE_TRACE
    /* print current stack state */
    {
      char buf[40];
      char *str = v7_stringify(v7, TOS(), buf, sizeof(buf), V7_STRINGIFY_DEBUG);
      fprintf(stderr, "        stack size: %u, TOS: '%s'\n",
              (unsigned int) (v7->stack.len / sizeof(val_t)), str);
      if (str != buf) {
        free(str);
      }

#ifdef V7_BCODE_TRACE_STACK
      {
        size_t i;
        for (i = 0; i < (v7->stack.len / sizeof(val_t)); i++) {
          char *str = v7_stringify(v7, stack_at(&v7->stack, i), buf,
                                   sizeof(buf), V7_STRINGIFY_DEBUG);

          fprintf(stderr, "        #: '%s'\n", str);

          if (str != buf) {
            free(str);
          }
        }
      }
#endif
    }
#endif
    if (r.need_inc_ops) {
      r.ops++;
    }
  }

  /* implicit return */
  if (v7->call_stack != v7->bottom_call_frame) {
#ifdef V7_BCODE_TRACE
    fprintf(stderr, "return implicitly\n");
#endif
    bcode_adjust_retval(v7, 0 /*implicit return*/);
    V7_TRY(bcode_perform_return(v7, &r, 1));
    goto restart;
  } else {
#ifdef V7_BCODE_TRACE
    const char *s = (get_scope(v7) != v7->vals.global_object)
                        ? "not global object"
                        : "global object";
    fprintf(stderr, "reached bottom_call_frame (%s)\n", s);
#endif
  }

clean:

  if (rcode == V7_OK) {
/*
 * bcode evaluated successfully. Make sure try stack is empty.
 * (data stack will be checked below, in `clean`)
 */
#ifndef NDEBUG
    {
      unsigned long try_stack_len =
          v7_array_length(v7, find_call_frame_private(v7)->vals.try_stack);
      if (try_stack_len != 0) {
        fprintf(stderr, "try_stack_len=%lu, should be 0\n", try_stack_len);
      }
      assert(try_stack_len == 0);
    }
#endif

    /* get the value returned from the evaluated script */
    *_res = POP();
  }

  assert(v7->bottom_call_frame == v7->call_stack);
  unwind_stack_1level(v7, NULL);

  v7->bottom_call_frame = saved_bottom_call_frame;

  tmp_frame_cleanup(&tf);
  return rcode;
}

/*
 * TODO(dfrank) this function is probably too overloaded: it handles both
 * `v7_exec` and `v7_apply`. Read below why it's written this way, but it's
 * probably a good idea to factor out common functionality in some other
 * function.
 *
 * If `src` is not `NULL`, then we behave in favour of `v7_exec`: parse,
 * compile, and evaluate the script. The `func` and `args` are ignored.
 *
 * If, however, `src` is `NULL`, then we behave in favour of `v7_apply`: we
 * call the provided `func` with `args`. But unlike interpreter, we can't just
 * call the provided function: we need to setup environment for this call.
 *
 * Currently, we just quickly generate the "wrapper" bcode for the function.
 * This wrapper bcode looks like this:
 *
 *    OP_PUSH_UNDEFINED
 *    OP_PUSH_LIT       # push this
 *    OP_PUSH_LIT       # push function
 *    OP_PUSH_LIT       # push arg1
 *    OP_PUSH_LIT       # push arg2
 *    ...
 *    OP_PUSH_LIT       # push argN
 *    OP_CALL(N)        # call function with N arguments
 *    OP_SWAP_DROP
 *
 * and then, bcode evaluator proceeds with this code.
 *
 * In fact, both cases (eval or apply) are quite similar: we should prepare
 * environment for the bcode evaluation in exactly the same way, and the only
 * different part is where we get the bcode from. This is why that
 * functionality is baked in the single function, but it would be good to make
 * it suck less.
 */
V7_PRIVATE enum v7_err b_exec(struct v7 *v7, const char *src, size_t src_len,
                              const char *filename, val_t func, val_t args,
                              val_t this_object, int is_json, int fr,
                              uint8_t is_constructor, val_t *res) {
#if defined(V7_BCODE_TRACE_SRC)
  fprintf(stderr, "src:'%s'\n", src);
#endif

/* TODO(mkm): use GC pool */
#if !defined(V7_NO_COMPILER)
  struct ast *a = (struct ast *) malloc(sizeof(struct ast));
#endif
  size_t saved_stack_len = v7->stack.len;
  enum v7_err rcode = V7_OK;
  val_t _res = V7_UNDEFINED;
  struct gc_tmp_frame tf = new_tmp_frame(v7);
  struct bcode *bcode = NULL;
#if defined(V7_ENABLE_STACK_TRACKING)
  struct stack_track_ctx stack_track_ctx;
#endif
  struct {
    unsigned noopt : 1;
    unsigned line_no_reset : 1;
  } flags = {0, 0};

  (void) filename;

#if defined(V7_ENABLE_STACK_TRACKING)
  v7_stack_track_start(v7, &stack_track_ctx);
#endif

  tmp_stack_push(&tf, &func);
  tmp_stack_push(&tf, &args);
  tmp_stack_push(&tf, &this_object);
  tmp_stack_push(&tf, &_res);

  /* init new bcode */
  bcode = (struct bcode *) calloc(1, sizeof(*bcode));

  bcode_init(bcode,
#ifndef V7_FORCE_STRICT_MODE
             0,
#else
             1,
#endif
#ifndef V7_DISABLE_FILENAMES
             filename ? shdata_create_from_string(filename) : NULL,
#else
             NULL,
#endif
             0 /*filename not in ROM*/
             );

  retain_bcode(v7, bcode);
  own_bcode(v7, bcode);

#if !defined(V7_NO_COMPILER)
  ast_init(a, 0);
  a->refcnt = 1;
#endif

  if (src != NULL) {
    /* Caller provided some source code, so, handle it somehow */

    flags.line_no_reset = 1;

    if (src_len >= sizeof(BIN_BCODE_SIGNATURE) &&
        strncmp(BIN_BCODE_SIGNATURE, src, sizeof(BIN_BCODE_SIGNATURE)) == 0) {
      /* we have a serialized bcode */

      bcode_deserialize(v7, bcode, src + sizeof(BIN_BCODE_SIGNATURE));

/*
 * Currently, we only support serialized bcode that is stored in some
 * mmapped memory. Otherwise, we don't yet have any mechanism to free
 * this memory at the appropriate time.
 */

/*
 * TODO(dfrank): currently, we remove this assert, and introduce memory
 * leak. We need to support that properly.
 */
#if 0
      assert(fr == 0);
#else
      if (fr) {
        fr = 0;
      }
#endif
    } else {
/* Maybe regular JavaScript source or binary AST data */
#if !defined(V7_NO_COMPILER)

      if (src_len >= sizeof(BIN_AST_SIGNATURE) &&
          strncmp(BIN_AST_SIGNATURE, src, sizeof(BIN_AST_SIGNATURE)) == 0) {
        /* we have binary AST data */

        if (fr == 0) {
          /* Unmanaged memory, usually rom or mmapped flash */
          mbuf_free(&a->mbuf);
          a->mbuf.buf = (char *) (src + sizeof(BIN_AST_SIGNATURE));
          a->mbuf.size = a->mbuf.len = src_len - sizeof(BIN_AST_SIGNATURE);
          a->refcnt++; /* prevent freeing */
          flags.noopt = 1;
        } else {
          mbuf_append(&a->mbuf, src + sizeof(BIN_AST_SIGNATURE),
                      src_len - sizeof(BIN_AST_SIGNATURE));
        }
      } else {
        /* we have regular JavaScript source, so, parse it */
        V7_TRY(parse(v7, a, src, src_len, is_json));
      }

      /* we now have binary AST, let's compile it */

      if (!flags.noopt) {
        ast_optimize(a);
      }
#if V7_ENABLE__Memory__stats
      v7->function_arena_ast_size += a->mbuf.size;
#endif

      if (v7_is_undefined(this_object)) {
        this_object = v7->vals.global_object;
      }

      if (!is_json) {
        V7_TRY(compile_script(v7, a, bcode));
      } else {
        ast_off_t pos = 0;
        V7_TRY(compile_expr(v7, a, &pos, bcode));
      }
#else  /* V7_NO_COMPILER */
      (void) is_json;
      /* Parsing JavaScript code is disabled */
      rcode = v7_throwf(v7, SYNTAX_ERROR,
                        "Parsing JS code is disabled by V7_NO_COMPILER");
      V7_THROW(V7_SYNTAX_ERROR);
#endif /* V7_NO_COMPILER */
    }

  } else if (is_js_function(func)) {
    /*
     * Caller did not provide source code, so, assume we should call
     * provided function. Here, we prepare "wrapper" bcode.
     */

    struct bcode_builder bbuilder;
    lit_t lit;
    int args_cnt = v7_array_length(v7, args);

    bcode_builder_init(v7, &bbuilder, bcode);

    bcode_op(&bbuilder, OP_PUSH_UNDEFINED);

    /* push `this` */
    lit = bcode_add_lit(&bbuilder, this_object);
    bcode_push_lit(&bbuilder, lit);

    /* push func literal */
    lit = bcode_add_lit(&bbuilder, func);
    bcode_push_lit(&bbuilder, lit);

    /* push args */
    {
      int i;
      for (i = 0; i < args_cnt; i++) {
        lit = bcode_add_lit(&bbuilder, v7_array_get(v7, args, i));
        bcode_push_lit(&bbuilder, lit);
      }
    }

    bcode_op(&bbuilder, OP_CALL);
    /* TODO(dfrank): check if args <= 0x7f */
    bcode_op(&bbuilder, (uint8_t) args_cnt);

    bcode_op(&bbuilder, OP_SWAP_DROP);

    bcode_builder_finalize(&bbuilder);
  } else if (is_cfunction_lite(func) || is_cfunction_obj(v7, func)) {
    /* call cfunction */

    V7_TRY(call_cfunction(v7, func, this_object, args, is_constructor, &_res));

    goto clean;
  } else {
    /* value is not a function */
    V7_TRY(v7_throwf(v7, TYPE_ERROR, "value is not a function"));
  }

/* We now have bcode to evaluate; proceed to it */

#if !defined(V7_NO_COMPILER)
  /*
   * Before we evaluate bcode, we can safely release AST since it's not needed
   * anymore. Note that there's no leak here: if we `goto clean` from somewhere
   * above, we'll anyway release the AST under `clean` as well.
   */
  release_ast(v7, a);
  a = NULL;
#endif /* V7_NO_COMPILER */

  /* Evaluate bcode */
  V7_TRY(eval_bcode(v7, bcode, this_object, flags.line_no_reset, &_res));

clean:

  /* free `src` if needed */
  /*
   * TODO(dfrank) : free it above, just after parsing, and make sure you use
   * V7_TRY2() with custom label instead of V7_TRY()
   */
  if (src != NULL && fr) {
    free((void *) src);
  }

  /* disown and release current bcode */
  disown_bcode(v7, bcode);
  release_bcode(v7, bcode);
  bcode = NULL;

  if (rcode != V7_OK) {
    /* some exception happened. */
    _res = v7->vals.thrown_error;

    /*
     * if this is a top-level bcode, clear thrown error from the v7 context
     *
     * TODO(dfrank): do we really need to do this?
     *
     * If we don't clear the error, then we should clear it manually after each
     * call to v7_exec() or friends; otherwise, all the following calls will
     * see this error.
     *
     * On the other hand, user would still need to clear the error if he calls
     * v7_exec() from some cfunction. So, currently, sometimes we don't need
     * to clear the error, and sometimes we do, which is confusing.
     */
    if (v7->act_bcodes.len == 0) {
      v7->vals.thrown_error = V7_UNDEFINED;
      v7->is_thrown = 0;
    }
  }

  /*
   * Data stack should have the same length as it was before evaluating script.
   */
  if (v7->stack.len != saved_stack_len) {
    fprintf(stderr, "len=%d, saved=%d\n", (int) v7->stack.len,
            (int) saved_stack_len);
  }
  assert(v7->stack.len == saved_stack_len);

#if !defined(V7_NO_COMPILER)
  /*
   * release AST if needed (normally, it's already released above, before
   * bcode evaluation)
   */
  if (a != NULL) {
    release_ast(v7, a);
    a = NULL;
  }
#endif /* V7_NO_COMPILER */

  if (is_constructor && !v7_is_object(_res)) {
    /* constructor returned non-object: replace it with `this` */
    _res = v7_get_this(v7);
  }

  /* Provide the caller with the result, if asked to do so */
  if (res != NULL) {
    *res = _res;
  }

#if defined(V7_ENABLE_STACK_TRACKING)
  {
    int diff = v7_stack_track_end(v7, &stack_track_ctx);
    if (diff > v7->stack_stat[V7_STACK_STAT_EXEC]) {
      v7->stack_stat[V7_STACK_STAT_EXEC] = diff;
    }
  }
#endif

  tmp_frame_cleanup(&tf);
  return rcode;
}

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err b_apply(struct v7 *v7, v7_val_t func, v7_val_t this_obj,
                               v7_val_t args, uint8_t is_constructor,
                               v7_val_t *res) {
  return b_exec(v7, NULL, 0, NULL, func, args, this_obj, 0, 0, is_constructor,
                res);
}
#ifdef V7_MODULE_LINES
#line 1 "v7/src/core.c"
#endif
/*
 * Copyright (c) 2014 Cesanta Software Limited
 * All rights reserved
 */

/* Amalgamated: #include "v7/builtin/builtin.h" */
/* Amalgamated: #include "v7/src/internal.h" */
/* Amalgamated: #include "v7/src/object.h" */
/* Amalgamated: #include "v7/src/core.h" */
/* Amalgamated: #include "v7/src/primitive.h" */
/* Amalgamated: #include "v7/src/array.h" */
/* Amalgamated: #include "v7/src/slre.h" */
/* Amalgamated: #include "v7/src/bcode.h" */
/* Amalgamated: #include "v7/src/stdlib.h" */
/* Amalgamated: #include "v7/src/gc.h" */
/* Amalgamated: #include "v7/src/heapusage.h" */
/* Amalgamated: #include "v7/src/eval.h" */

#ifdef V7_THAW
extern struct v7_vals *fr_vals;
#endif

#ifdef HAS_V7_INFINITY
double _v7_infinity;
#endif

#ifdef HAS_V7_NAN
double _v7_nan;
#endif

#if defined(V7_CYG_PROFILE_ON)
struct v7 *v7_head = NULL;
#endif

static void generic_object_destructor(struct v7 *v7, void *ptr) {
  struct v7_generic_object *o = (struct v7_generic_object *) ptr;
  struct v7_property *p;
  struct mbuf *abuf;

  /* TODO(mkm): make regexp use user data API */
  p = v7_get_own_property2(v7, v7_object_to_value(&o->base), "", 0,
                           _V7_PROPERTY_HIDDEN);

#if V7_ENABLE__RegExp
  if (p != NULL && (p->value & V7_TAG_MASK) == V7_TAG_REGEXP) {
    struct v7_regexp *rp = (struct v7_regexp *) get_ptr(p->value);
    v7_disown(v7, &rp->regexp_string);
    slre_free(rp->compiled_regexp);
    free(rp);
  }
#endif

  if (o->base.attributes & V7_OBJ_DENSE_ARRAY) {
    if (p != NULL &&
        ((abuf = (struct mbuf *) v7_get_ptr(v7, p->value)) != NULL)) {
      mbuf_free(abuf);
      free(abuf);
    }
  }

  if (o->base.attributes & V7_OBJ_HAS_DESTRUCTOR) {
    struct v7_property *p;
    for (p = o->base.properties; p != NULL; p = p->next) {
      if (p->attributes & _V7_PROPERTY_USER_DATA_AND_DESTRUCTOR) {
        if (v7_is_foreign(p->name)) {
          v7_destructor_cb_t *cb =
              (v7_destructor_cb_t *) v7_get_ptr(v7, p->name);
          cb(v7, v7_get_ptr(v7, p->value));
        }
        break;
      }
    }
  }

#if defined(V7_ENABLE_ENTITY_IDS)
  o->base.entity_id_base = V7_ENTITY_ID_PART_NONE;
  o->base.entity_id_spec = V7_ENTITY_ID_PART_NONE;
#endif
}

static void function_destructor(struct v7 *v7, void *ptr) {
  struct v7_js_function *f = (struct v7_js_function *) ptr;
  (void) v7;
  if (f == NULL) return;

  if (f->bcode != NULL) {
    release_bcode(v7, f->bcode);
  }

#if defined(V7_ENABLE_ENTITY_IDS)
  f->base.entity_id_base = V7_ENTITY_ID_PART_NONE;
  f->base.entity_id_spec = V7_ENTITY_ID_PART_NONE;
#endif
}

#if defined(V7_ENABLE_ENTITY_IDS)
static void property_destructor(struct v7 *v7, void *ptr) {
  struct v7_property *p = (struct v7_property *) ptr;
  (void) v7;
  if (p == NULL) return;

  p->entity_id = V7_ENTITY_ID_NONE;
}
#endif

struct v7 *v7_create(void) {
  struct v7_create_opts opts;
  memset(&opts, 0, sizeof(opts));
  return v7_create_opt(opts);
}

struct v7 *v7_create_opt(struct v7_create_opts opts) {
  struct v7 *v7 = NULL;
  char z = 0;

#if defined(HAS_V7_INFINITY) || defined(HAS_V7_NAN)
  double zero = 0.0;
#endif

#ifdef HAS_V7_INFINITY
  _v7_infinity = 1.0 / zero;
#endif
#ifdef HAS_V7_NAN
  _v7_nan = zero / zero;
#endif

  if (opts.object_arena_size == 0) opts.object_arena_size = 200;
  if (opts.function_arena_size == 0) opts.function_arena_size = 100;
  if (opts.property_arena_size == 0) opts.property_arena_size = 400;

  if ((v7 = (struct v7 *) calloc(1, sizeof(*v7))) != NULL) {
#ifdef V7_STACK_SIZE
    v7->sp_limit = (void *) ((uintptr_t) opts.c_stack_base - (V7_STACK_SIZE));
    v7->sp_lwm = opts.c_stack_base;
#ifdef V7_STACK_GUARD_MIN_SIZE
    v7_sp_limit = v7->sp_limit;
#endif
#endif

#if defined(V7_CYG_PROFILE_ON)
    v7->next_v7 = v7_head;
    v7_head = v7;
#endif

#ifndef V7_DISABLE_STR_ALLOC_SEQ
    v7->gc_next_asn = 0;
    v7->gc_min_asn = 0;
#endif

    v7->cur_dense_prop =
        (struct v7_property *) calloc(1, sizeof(struct v7_property));
    gc_arena_init(&v7->generic_object_arena, sizeof(struct v7_generic_object),
                  opts.object_arena_size, 10, "object");
    v7->generic_object_arena.destructor = generic_object_destructor;
    gc_arena_init(&v7->function_arena, sizeof(struct v7_js_function),
                  opts.function_arena_size, 10, "function");
    v7->function_arena.destructor = function_destructor;
    gc_arena_init(&v7->property_arena, sizeof(struct v7_property),
                  opts.property_arena_size, 10, "property");
#if defined(V7_ENABLE_ENTITY_IDS)
    v7->property_arena.destructor = property_destructor;
#endif

    /*
     * The compacting GC exploits the null terminator of the previous
     * string as marker.
     */
    mbuf_append(&v7->owned_strings, &z, 1);

    v7->inhibit_gc = 1;
    v7->vals.thrown_error = V7_UNDEFINED;

    v7->call_stack = NULL;
    v7->bottom_call_frame = NULL;

#if defined(V7_THAW) && !defined(V7_FREEZE_NOT_READONLY)
    {
      struct v7_generic_object *obj;
      v7->vals = *fr_vals;
      v7->vals.global_object = v7_mk_object(v7);

      /*
       * The global object has to be mutable.
       */
      obj = get_generic_object_struct(v7->vals.global_object);
      *obj = *get_generic_object_struct(fr_vals->global_object);
      obj->base.attributes &= ~(V7_OBJ_NOT_EXTENSIBLE | V7_OBJ_OFF_HEAP);
      v7_set(v7, v7->vals.global_object, "global", 6, v7->vals.global_object);
    }
#else
    init_stdlib(v7);
    init_file(v7);
    init_crypto(v7);
    init_socket(v7);
#endif

    v7->inhibit_gc = 0;
  }

  return v7;
}

val_t v7_get_global(struct v7 *v7) {
  return v7->vals.global_object;
}

void v7_destroy(struct v7 *v7) {
  if (v7 == NULL) return;
  gc_arena_destroy(v7, &v7->generic_object_arena);
  gc_arena_destroy(v7, &v7->function_arena);
  gc_arena_destroy(v7, &v7->property_arena);

  mbuf_free(&v7->owned_strings);
  mbuf_free(&v7->owned_values);
  mbuf_free(&v7->foreign_strings);
  mbuf_free(&v7->json_visited_stack);
  mbuf_free(&v7->tmp_stack);
  mbuf_free(&v7->act_bcodes);
  mbuf_free(&v7->stack);

#if defined(V7_CYG_PROFILE_ON)
  /* delete this v7 */
  {
    struct v7 *v, **prevp = &v7_head;
    for (v = v7_head; v != NULL; prevp = &v->next_v7, v = v->next_v7) {
      if (v == v7) {
        *prevp = v->next_v7;
        break;
      }
    }
  }
#endif

  free(v7->cur_dense_prop);
  free(v7);
}

v7_val_t v7_get_this(struct v7 *v7) {
  /*
   * By default, when there's no active call frame, will return Global Object
   */
  v7_val_t ret = v7->vals.global_object;

  struct v7_call_frame_base *call_frame =
      find_call_frame(v7, V7_CALL_FRAME_MASK_BCODE | V7_CALL_FRAME_MASK_CFUNC);

  if (call_frame != NULL) {
    if (call_frame->type_mask & V7_CALL_FRAME_MASK_BCODE) {
      ret = ((struct v7_call_frame_bcode *) call_frame)->vals.this_obj;
    } else if (call_frame->type_mask & V7_CALL_FRAME_MASK_CFUNC) {
      ret = ((struct v7_call_frame_cfunc *) call_frame)->vals.this_obj;
    } else {
      assert(0);
    }
  }

  return ret;
}

V7_PRIVATE v7_val_t get_scope(struct v7 *v7) {
  struct v7_call_frame_private *call_frame =
      (struct v7_call_frame_private *) find_call_frame(
          v7, V7_CALL_FRAME_MASK_PRIVATE);

  if (call_frame != NULL) {
    return call_frame->vals.scope;
  } else {
    /* No active call frame, return global object */
    return v7->vals.global_object;
  }
}

V7_PRIVATE uint8_t is_strict_mode(struct v7 *v7) {
  struct v7_call_frame_bcode *call_frame =
      (struct v7_call_frame_bcode *) find_call_frame(v7,
                                                     V7_CALL_FRAME_MASK_BCODE);

  if (call_frame != NULL) {
    return call_frame->bcode->strict_mode;
  } else {
    /* No active call frame, assume no strict mode */
    return 0;
  }
}

v7_val_t v7_get_arguments(struct v7 *v7) {
  return v7->vals.arguments;
}

v7_val_t v7_arg(struct v7 *v7, unsigned long n) {
  return v7_array_get(v7, v7->vals.arguments, n);
}

unsigned long v7_argc(struct v7 *v7) {
  return v7_array_length(v7, v7->vals.arguments);
}

void v7_own(struct v7 *v7, v7_val_t *v) {
  heapusage_dont_count(1);
  mbuf_append(&v7->owned_values, &v, sizeof(v));
  heapusage_dont_count(0);
}

int v7_disown(struct v7 *v7, v7_val_t *v) {
  v7_val_t **vp =
      (v7_val_t **) (v7->owned_values.buf + v7->owned_values.len - sizeof(v));

  for (; (char *) vp >= v7->owned_values.buf; vp--) {
    if (*vp == v) {
      *vp = *(v7_val_t **) (v7->owned_values.buf + v7->owned_values.len -
                            sizeof(v));
      v7->owned_values.len -= sizeof(v);
      return 1;
    }
  }

  return 0;
}

void v7_set_gc_enabled(struct v7 *v7, int enabled) {
  v7->inhibit_gc = !enabled;
}

void v7_interrupt(struct v7 *v7) {
  v7->interrupted = 1;
}

const char *v7_get_parser_error(struct v7 *v7) {
  return v7->error_msg;
}

#if defined(V7_ENABLE_STACK_TRACKING)

int v7_stack_stat(struct v7 *v7, enum v7_stack_stat_what what) {
  assert(what < V7_STACK_STATS_CNT);
  return v7->stack_stat[what];
}

void v7_stack_stat_clean(struct v7 *v7) {
  memset(v7->stack_stat, 0x00, sizeof(v7->stack_stat));
}

#endif /* V7_ENABLE_STACK_TRACKING */
#ifdef V7_MODULE_LINES
#line 1 "v7/src/primitive.c"
#endif
/*
 * Copyright (c) 2014 Cesanta Software Limited
 * All rights reserved
 */

/* Amalgamated: #include "v7/src/internal.h" */
/* Amalgamated: #include "v7/src/core.h" */
/* Amalgamated: #include "v7/src/primitive.h" */

/* Number {{{ */

NOINSTR static v7_val_t mk_number(double v) {
  val_t res;
  /* not every NaN is a JS NaN */
  if (isnan(v)) {
    res = V7_TAG_NAN;
  } else {
    union {
      double d;
      val_t r;
    } u;
    u.d = v;
    res = u.r;
  }
  return res;
}

NOINSTR static double get_double(val_t v) {
  union {
    double d;
    val_t v;
  } u;
  u.v = v;
  /* Due to NaN packing, any non-numeric value is already a valid NaN value */
  return u.d;
}

NOINSTR static v7_val_t mk_boolean(int v) {
  return (!!v) | V7_TAG_BOOLEAN;
}

NOINSTR static int get_bool(val_t v) {
  if (v7_is_boolean(v)) {
    return v & 1;
  } else {
    return 0;
  }
}

NOINSTR v7_val_t v7_mk_number(struct v7 *v7, double v) {
  (void) v7;
  return mk_number(v);
}

NOINSTR double v7_get_double(struct v7 *v7, v7_val_t v) {
  (void) v7;
  return get_double(v);
}

NOINSTR int v7_get_int(struct v7 *v7, v7_val_t v) {
  (void) v7;
  return (int) get_double(v);
}

int v7_is_number(val_t v) {
  return v == V7_TAG_NAN || !isnan(get_double(v));
}

V7_PRIVATE int is_finite(struct v7 *v7, val_t v) {
  return v7_is_number(v) && v != V7_TAG_NAN && !isinf(v7_get_double(v7, v));
}

/* }}} Number */

/* Boolean {{{ */

NOINSTR v7_val_t v7_mk_boolean(struct v7 *v7, int v) {
  (void) v7;
  return mk_boolean(v);
}

NOINSTR int v7_get_bool(struct v7 *v7, val_t v) {
  (void) v7;
  return get_bool(v);
}

int v7_is_boolean(val_t v) {
  return (v & V7_TAG_MASK) == V7_TAG_BOOLEAN;
}

/* }}} Boolean */

/* null {{{ */

NOINSTR v7_val_t v7_mk_null(void) {
  return V7_NULL;
}

int v7_is_null(val_t v) {
  return v == V7_NULL;
}

/* }}} null */

/* undefined {{{ */

NOINSTR v7_val_t v7_mk_undefined(void) {
  return V7_UNDEFINED;
}

int v7_is_undefined(val_t v) {
  return v == V7_UNDEFINED;
}

/* }}} undefined */

/* Foreign {{{ */

V7_PRIVATE val_t pointer_to_value(void *p) {
  uint64_t n = ((uint64_t)(uintptr_t) p);

  assert((n & V7_TAG_MASK) == 0 || (n & V7_TAG_MASK) == (~0 & V7_TAG_MASK));
  return n & ~V7_TAG_MASK;
}

V7_PRIVATE void *get_ptr(val_t v) {
  return (void *) (uintptr_t)(v & 0xFFFFFFFFFFFFUL);
}

NOINSTR void *v7_get_ptr(struct v7 *v7, val_t v) {
  (void) v7;
  if (!v7_is_foreign(v)) {
    return NULL;
  }
  return get_ptr(v);
}

NOINSTR v7_val_t v7_mk_foreign(struct v7 *v7, void *p) {
  (void) v7;
  return pointer_to_value(p) | V7_TAG_FOREIGN;
}

int v7_is_foreign(val_t v) {
  return (v & V7_TAG_MASK) == V7_TAG_FOREIGN;
}

/* }}} Foreign */
#ifdef V7_MODULE_LINES
#line 1 "v7/src/function.c"
#endif
/*
 * Copyright (c) 2014 Cesanta Software Limited
 * All rights reserved
 */

/* Amalgamated: #include "v7/src/internal.h" */
/* Amalgamated: #include "v7/src/primitive.h" */
/* Amalgamated: #include "v7/src/core.h" */
/* Amalgamated: #include "v7/src/function.h" */
/* Amalgamated: #include "v7/src/gc.h" */
/* Amalgamated: #include "v7/src/object.h" */

static val_t js_function_to_value(struct v7_js_function *o) {
  return pointer_to_value(o) | V7_TAG_FUNCTION;
}

V7_PRIVATE struct v7_js_function *get_js_function_struct(val_t v) {
  struct v7_js_function *ret = NULL;
  assert(is_js_function(v));
  ret = (struct v7_js_function *) get_ptr(v);
#if defined(V7_ENABLE_ENTITY_IDS)
  if (ret->base.entity_id_spec != V7_ENTITY_ID_PART_JS_FUNC) {
    fprintf(stderr, "entity_id: not a function!\n");
    abort();
  } else if (ret->base.entity_id_base != V7_ENTITY_ID_PART_OBJ) {
    fprintf(stderr, "entity_id: not an object!\n");
    abort();
  }
#endif
  return ret;
}

V7_PRIVATE
val_t mk_js_function(struct v7 *v7, struct v7_generic_object *scope,
                     val_t proto) {
  struct v7_js_function *f;
  val_t fval = V7_NULL;
  struct gc_tmp_frame tf = new_tmp_frame(v7);
  tmp_stack_push(&tf, &proto);
  tmp_stack_push(&tf, &fval);

  f = new_function(v7);

  if (f == NULL) {
    /* fval is left `null` */
    goto cleanup;
  }

#if defined(V7_ENABLE_ENTITY_IDS)
  f->base.entity_id_base = V7_ENTITY_ID_PART_OBJ;
  f->base.entity_id_spec = V7_ENTITY_ID_PART_JS_FUNC;
#endif

  fval = js_function_to_value(f);

  f->base.properties = NULL;
  f->scope = scope;

  /*
   * Before setting a `V7_OBJ_FUNCTION` flag, make sure we don't have
   * `V7_OBJ_DENSE_ARRAY` flag set
   */
  assert(!(f->base.attributes & V7_OBJ_DENSE_ARRAY));
  f->base.attributes |= V7_OBJ_FUNCTION;

  /* TODO(mkm): lazily create these properties on first access */
  if (v7_is_object(proto)) {
    v7_def(v7, proto, "constructor", 11, V7_DESC_ENUMERABLE(0), fval);
    v7_def(v7, fval, "prototype", 9,
           V7_DESC_ENUMERABLE(0) | V7_DESC_CONFIGURABLE(0), proto);
  }

cleanup:
  tmp_frame_cleanup(&tf);
  return fval;
}

V7_PRIVATE int is_js_function(val_t v) {
  return (v & V7_TAG_MASK) == V7_TAG_FUNCTION;
}

V7_PRIVATE
v7_val_t mk_cfunction_obj(struct v7 *v7, v7_cfunction_t *f, int num_args) {
  val_t obj = mk_object(v7, v7->vals.function_prototype);
  struct gc_tmp_frame tf = new_tmp_frame(v7);
  tmp_stack_push(&tf, &obj);
  v7_def(v7, obj, "", 0, _V7_DESC_HIDDEN(1), v7_mk_cfunction(f));
  if (num_args >= 0) {
    v7_def(v7, obj, "length", 6, (V7_DESC_ENUMERABLE(0) | V7_DESC_WRITABLE(0) |
                                  V7_DESC_CONFIGURABLE(0)),
           v7_mk_number(v7, num_args));
  }
  tmp_frame_cleanup(&tf);
  return obj;
}

V7_PRIVATE v7_val_t mk_cfunction_obj_with_proto(struct v7 *v7,
                                                v7_cfunction_t *f, int num_args,
                                                v7_val_t proto) {
  struct gc_tmp_frame tf = new_tmp_frame(v7);
  v7_val_t res = mk_cfunction_obj(v7, f, num_args);

  tmp_stack_push(&tf, &res);

  v7_def(v7, res, "prototype", 9, (V7_DESC_ENUMERABLE(0) | V7_DESC_WRITABLE(0) |
                                   V7_DESC_CONFIGURABLE(0)),
         proto);
  v7_def(v7, proto, "constructor", 11, V7_DESC_ENUMERABLE(0), res);
  tmp_frame_cleanup(&tf);
  return res;
}

V7_PRIVATE v7_val_t mk_cfunction_lite(v7_cfunction_t *f) {
  union {
    void *p;
    v7_cfunction_t *f;
  } u;
  u.f = f;
  return pointer_to_value(u.p) | V7_TAG_CFUNCTION;
}

V7_PRIVATE v7_cfunction_t *get_cfunction_ptr(struct v7 *v7, val_t v) {
  v7_cfunction_t *ret = NULL;

  if (is_cfunction_lite(v)) {
    /* Implementation is identical to get_ptr but is separate since
     * object pointers are not directly convertible to function pointers
     * according to ISO C and generates a warning in -Wpedantic mode. */
    ret = (v7_cfunction_t *) (uintptr_t)(v & 0xFFFFFFFFFFFFUL);
  } else {
    /* maybe cfunction object */

    /* extract the hidden property from a cfunction_object */
    struct v7_property *p;
    p = v7_get_own_property2(v7, v, "", 0, _V7_PROPERTY_HIDDEN);
    if (p != NULL) {
      /* yes, it's cfunction object. Extract cfunction pointer from it */
      ret = get_cfunction_ptr(v7, p->value);
    }
  }

  return ret;
}

V7_PRIVATE int is_cfunction_lite(val_t v) {
  return (v & V7_TAG_MASK) == V7_TAG_CFUNCTION;
}

V7_PRIVATE int is_cfunction_obj(struct v7 *v7, val_t v) {
  int ret = 0;
  if (v7_is_object(v)) {
    /* extract the hidden property from a cfunction_object */
    struct v7_property *p;
    p = v7_get_own_property2(v7, v, "", 0, _V7_PROPERTY_HIDDEN);
    if (p != NULL) {
      v = p->value;
    }

    ret = is_cfunction_lite(v);
  }
  return ret;
}

v7_val_t v7_mk_function(struct v7 *v7, v7_cfunction_t *f) {
  return mk_cfunction_obj(v7, f, -1);
}

v7_val_t v7_mk_function_with_proto(struct v7 *v7, v7_cfunction_t *f,
                                   v7_val_t proto) {
  return mk_cfunction_obj_with_proto(v7, f, ~0, proto);
}

v7_val_t v7_mk_cfunction(v7_cfunction_t *f) {
  return mk_cfunction_lite(f);
}

int v7_is_callable(struct v7 *v7, val_t v) {
  return is_js_function(v) || is_cfunction_lite(v) || is_cfunction_obj(v7, v);
}
#ifdef V7_MODULE_LINES
#line 1 "v7/src/exec.c"
#endif
/*
 * Copyright (c) 2014 Cesanta Software Limited
 * All rights reserved
 */

/* osdep.h must be included before `cs_file.h` TODO(dfrank) : fix this */
/* Amalgamated: #include "common/cs_file.h" */
/* Amalgamated: #include "v7/src/internal.h" */
/* Amalgamated: #include "v7/src/core.h" */
/* Amalgamated: #include "v7/src/eval.h" */
/* Amalgamated: #include "v7/src/exec.h" */
/* Amalgamated: #include "v7/src/ast.h" */
/* Amalgamated: #include "v7/src/compiler.h" */
/* Amalgamated: #include "v7/src/exceptions.h" */

enum v7_err v7_exec(struct v7 *v7, const char *js_code, v7_val_t *res) {
  return b_exec(v7, js_code, strlen(js_code), NULL, V7_UNDEFINED, V7_UNDEFINED,
                V7_UNDEFINED, 0, 0, 0, res);
}

enum v7_err v7_exec_opt(struct v7 *v7, const char *js_code,
                        const struct v7_exec_opts *opts, v7_val_t *res) {
  return b_exec(v7, js_code, strlen(js_code), opts->filename, V7_UNDEFINED,
                V7_UNDEFINED,
                (opts->this_obj == 0 ? V7_UNDEFINED : opts->this_obj),
                opts->is_json, 0, 0, res);
}

enum v7_err v7_parse_json(struct v7 *v7, const char *str, v7_val_t *res) {
  return b_exec(v7, str, strlen(str), NULL, V7_UNDEFINED, V7_UNDEFINED,
                V7_UNDEFINED, 1, 0, 0, res);
}

#ifndef V7_NO_FS
static enum v7_err exec_file(struct v7 *v7, const char *path, val_t *res,
                             int is_json) {
  enum v7_err rcode = V7_OK;
  char *p;
  size_t file_size;
  char *(*rd)(const char *, size_t *);

  rd = cs_read_file;
#ifdef V7_MMAP_EXEC
  rd = cs_mmap_file;
#ifdef V7_MMAP_EXEC_ONLY
#define I_STRINGIFY(x) #x
#define I_STRINGIFY2(x) I_STRINGIFY(x)

  /* use mmap only for .js files */
  if (strlen(path) <= 3 || strcmp(path + strlen(path) - 3, ".js") != 0) {
    rd = cs_read_file;
  }
#endif
#endif

  if ((p = rd(path, &file_size)) == NULL) {
    rcode = v7_throwf(v7, SYNTAX_ERROR, "cannot open [%s]", path);
    /*
     * In order to maintain compat with existing API, we should save the
     * current exception value into `*res`
     *
     * TODO(dfrank): probably change API: clients can use
     *`v7_get_thrown_value()` now.
     */
    if (res != NULL) *res = v7_get_thrown_value(v7, NULL);
    goto clean;
  } else {
#ifndef V7_MMAP_EXEC
    int fr = 1;
#else
    int fr = 0;
#endif
    rcode = b_exec(v7, p, file_size, path, V7_UNDEFINED, V7_UNDEFINED,
                   V7_UNDEFINED, is_json, fr, 0, res);
    if (rcode != V7_OK) {
      goto clean;
    }
  }

clean:
  return rcode;
}

enum v7_err v7_exec_file(struct v7 *v7, const char *path, val_t *res) {
  return exec_file(v7, path, res, 0);
}

enum v7_err v7_parse_json_file(struct v7 *v7, const char *path, v7_val_t *res) {
  return exec_file(v7, path, res, 1);
}
#endif /* V7_NO_FS */

enum v7_err v7_apply(struct v7 *v7, v7_val_t func, v7_val_t this_obj,
                     v7_val_t args, v7_val_t *res) {
  return b_apply(v7, func, this_obj, args, 0, res);
}

#ifndef NO_LIBC
#if !defined(V7_NO_COMPILER)
enum v7_err _v7_compile(const char *src, size_t js_code_size, int binary,
                        int use_bcode, FILE *fp) {
  struct ast ast;
  struct v7 *v7 = v7_create();
  ast_off_t pos = 0;
  enum v7_err err;

  v7->is_precompiling = 1;

  ast_init(&ast, 0);
  err = parse(v7, &ast, src, js_code_size, 0);
  if (err == V7_OK) {
    if (use_bcode) {
      struct bcode bcode;
      /*
       * We don't set filename here, because the bcode will be just serialized
       * and then freed. We don't currently serialize filename. If we ever do,
       * we'll have to make `_v7_compile()` to also take a filename argument,
       * and use it here.
       */
      bcode_init(&bcode, 0, NULL, 0);
      err = compile_script(v7, &ast, &bcode);
      if (err != V7_OK) {
        goto cleanup_bcode;
      }

      if (binary) {
        bcode_serialize(v7, &bcode, fp);
      } else {
#ifdef V7_BCODE_DUMP
        dump_bcode(v7, fp, &bcode);
#else
        fprintf(stderr, "build flag V7_BCODE_DUMP not enabled\n");
#endif
      }
    cleanup_bcode:
      bcode_free(v7, &bcode);
    } else {
      if (binary) {
        fwrite(BIN_AST_SIGNATURE, sizeof(BIN_AST_SIGNATURE), 1, fp);
        fwrite(ast.mbuf.buf, ast.mbuf.len, 1, fp);
      } else {
        ast_dump_tree(fp, &ast, &pos, 0);
      }
    }
  }

  ast_free(&ast);
  v7_destroy(v7);
  return err;
}

enum v7_err v7_compile(const char *src, int binary, int use_bcode, FILE *fp) {
  return _v7_compile(src, strlen(src), binary, use_bcode, fp);
}
#endif /* V7_NO_COMPILER */
#endif
#ifdef V7_MODULE_LINES
#line 1 "v7/src/util.c"
#endif
/*
 * Copyright (c) 2014 Cesanta Software Limited
 * All rights reserved
 */

/* Amalgamated: #include "v7/src/internal.h" */
/* Amalgamated: #include "v7/src/core.h" */
/* Amalgamated: #include "v7/src/object.h" */
/* Amalgamated: #include "v7/src/util.h" */
/* Amalgamated: #include "v7/src/string.h" */
/* Amalgamated: #include "v7/src/array.h" */
/* Amalgamated: #include "v7/src/eval.h" */
/* Amalgamated: #include "v7/src/conversion.h" */
/* Amalgamated: #include "v7/src/exceptions.h" */
/* Amalgamated: #include "v7/src/primitive.h" */
/* Amalgamated: #include "v7/src/std_proxy.h" */

void v7_print(struct v7 *v7, v7_val_t v) {
  v7_fprint(stdout, v7, v);
}

void v7_fprint(FILE *f, struct v7 *v7, val_t v) {
  char buf[16];
  char *s = v7_stringify(v7, v, buf, sizeof(buf), V7_STRINGIFY_DEBUG);
  fprintf(f, "%s", s);
  if (buf != s) free(s);
}

void v7_println(struct v7 *v7, v7_val_t v) {
  v7_fprintln(stdout, v7, v);
}

void v7_fprintln(FILE *f, struct v7 *v7, val_t v) {
  v7_fprint(f, v7, v);
  fprintf(f, ENDL);
}

void v7_fprint_stack_trace(FILE *f, struct v7 *v7, val_t e) {
  size_t s;
  val_t strace_v = v7_get(v7, e, "stack", ~0);
  const char *strace = NULL;
  if (v7_is_string(strace_v)) {
    strace = v7_get_string(v7, &strace_v, &s);
    fprintf(f, "%s\n", strace);
  }
}

void v7_print_error(FILE *f, struct v7 *v7, const char *ctx, val_t e) {
  /* TODO(mkm): figure out if this is an error object and which kind */
  v7_val_t msg;
  if (v7_is_undefined(e)) {
    fprintf(f, "undefined error [%s]\n ", ctx);
    return;
  }
  msg = v7_get(v7, e, "message", ~0);
  if (v7_is_undefined(msg)) {
    msg = e;
  }
  fprintf(f, "Exec error [%s]: ", ctx);
  v7_fprintln(f, v7, msg);
  v7_fprint_stack_trace(f, v7, e);
}

#if V7_ENABLE__Proxy

v7_val_t v7_mk_proxy(struct v7 *v7, v7_val_t target,
                     const v7_proxy_hnd_t *handler) {
  enum v7_err rcode = V7_OK;
  v7_val_t res = V7_UNDEFINED;
  v7_val_t args = V7_UNDEFINED;
  v7_val_t handler_v = V7_UNDEFINED;

  v7_own(v7, &res);
  v7_own(v7, &args);
  v7_own(v7, &handler_v);
  v7_own(v7, &target);

  /* if target is not an object, create one */
  if (!v7_is_object(target)) {
    target = v7_mk_object(v7);
  }

  /* prepare handler object with necessary properties */
  handler_v = v7_mk_object(v7);
  if (handler->get != NULL) {
    set_cfunc_prop(v7, handler_v, "get", handler->get);
  }
  if (handler->set != NULL) {
    set_cfunc_prop(v7, handler_v, "set", handler->set);
  }
  if (handler->own_keys != NULL) {
    set_cfunc_prop(v7, handler_v, "ownKeys", handler->own_keys);
  }
  if (handler->get_own_prop_desc != NULL) {
    v7_def(v7, handler_v, "_gpdc", ~0, V7_DESC_ENUMERABLE(0),
           v7_mk_foreign(v7, (void *) handler->get_own_prop_desc));
  }

  /* prepare args */
  args = v7_mk_dense_array(v7);
  v7_array_set(v7, args, 0, target);
  v7_array_set(v7, args, 1, handler_v);

  /* call Proxy constructor */
  V7_TRY(b_apply(v7, v7_get(v7, v7->vals.global_object, "Proxy", ~0),
                 v7_mk_object(v7), args, 1 /* as ctor */, &res));

clean:
  if (rcode != V7_OK) {
    fprintf(stderr, "error during v7_mk_proxy()");
    res = V7_UNDEFINED;
  }

  v7_disown(v7, &target);
  v7_disown(v7, &handler_v);
  v7_disown(v7, &args);
  v7_disown(v7, &res);
  return res;
}

#endif /* V7_ENABLE__Proxy */

V7_PRIVATE enum v7_type val_type(struct v7 *v7, val_t v) {
  int tag;
  if (v7_is_number(v)) {
    return V7_TYPE_NUMBER;
  }
  tag = (v & V7_TAG_MASK) >> 48;
  switch (tag) {
    case V7_TAG_FOREIGN >> 48:
      if (v7_is_null(v)) {
        return V7_TYPE_NULL;
      }
      return V7_TYPE_FOREIGN;
    case V7_TAG_UNDEFINED >> 48:
      return V7_TYPE_UNDEFINED;
    case V7_TAG_OBJECT >> 48:
      if (v7_get_proto(v7, v) == v7->vals.array_prototype) {
        return V7_TYPE_ARRAY_OBJECT;
      } else if (v7_get_proto(v7, v) == v7->vals.boolean_prototype) {
        return V7_TYPE_BOOLEAN_OBJECT;
      } else if (v7_get_proto(v7, v) == v7->vals.string_prototype) {
        return V7_TYPE_STRING_OBJECT;
      } else if (v7_get_proto(v7, v) == v7->vals.number_prototype) {
        return V7_TYPE_NUMBER_OBJECT;
      } else if (v7_get_proto(v7, v) == v7->vals.function_prototype) {
        return V7_TYPE_CFUNCTION_OBJECT;
      } else if (v7_get_proto(v7, v) == v7->vals.date_prototype) {
        return V7_TYPE_DATE_OBJECT;
      } else {
        return V7_TYPE_GENERIC_OBJECT;
      }
    case V7_TAG_STRING_I >> 48:
    case V7_TAG_STRING_O >> 48:
    case V7_TAG_STRING_F >> 48:
    case V7_TAG_STRING_D >> 48:
    case V7_TAG_STRING_5 >> 48:
      return V7_TYPE_STRING;
    case V7_TAG_BOOLEAN >> 48:
      return V7_TYPE_BOOLEAN;
    case V7_TAG_FUNCTION >> 48:
      return V7_TYPE_FUNCTION_OBJECT;
    case V7_TAG_CFUNCTION >> 48:
      return V7_TYPE_CFUNCTION;
    case V7_TAG_REGEXP >> 48:
      return V7_TYPE_REGEXP_OBJECT;
    default:
      abort();
      return V7_TYPE_UNDEFINED;
  }
}

#ifndef V7_DISABLE_LINE_NUMBERS
V7_PRIVATE uint8_t msb_lsb_swap(uint8_t b) {
  if ((b & 0x01) != (b >> 7)) {
    b ^= 0x81;
  }
  return b;
}
#endif
#ifdef V7_MODULE_LINES
#line 1 "v7/src/string.c"
#endif
/*
 * Copyright (c) 2014 Cesanta Software Limited
 * All rights reserved
 */

/* Amalgamated: #include "common/utf.h" */
/* Amalgamated: #include "v7/src/string.h" */
/* Amalgamated: #include "v7/src/exceptions.h" */
/* Amalgamated: #include "v7/src/conversion.h" */
/* Amalgamated: #include "v7/src/varint.h" */
/* Amalgamated: #include "v7/src/gc.h" */
/* Amalgamated: #include "v7/src/core.h" */
/* Amalgamated: #include "v7/src/primitive.h" */
/* Amalgamated: #include "v7/src/slre.h" */
/* Amalgamated: #include "v7/src/heapusage.h" */

/* TODO(lsm): NaN payload location depends on endianness, make crossplatform */
#define GET_VAL_NAN_PAYLOAD(v) ((char *) &(v))

/*
 * Dictionary of read-only strings with length > 5.
 * NOTE(lsm): must be sorted lexicographically, because
 * v_find_string_in_dictionary performs binary search over this list.
 */
/* clang-format off */
static const struct v7_vec_const v_dictionary_strings[] = {
    V7_VEC(" is not a function"),
    V7_VEC("Boolean"),
    V7_VEC("Crypto"),
    V7_VEC("EvalError"),
    V7_VEC("Function"),
    V7_VEC("Infinity"),
    V7_VEC("InternalError"),
    V7_VEC("LOG10E"),
    V7_VEC("MAX_VALUE"),
    V7_VEC("MIN_VALUE"),
    V7_VEC("NEGATIVE_INFINITY"),
    V7_VEC("Number"),
    V7_VEC("Object"),
    V7_VEC("POSITIVE_INFINITY"),
    V7_VEC("RangeError"),
    V7_VEC("ReferenceError"),
    V7_VEC("RegExp"),
    V7_VEC("SQRT1_2"),
    V7_VEC("Socket"),
    V7_VEC("String"),
    V7_VEC("SyntaxError"),
    V7_VEC("TypeError"),
    V7_VEC("UBJSON"),
    V7_VEC("_modcache"),
    V7_VEC("accept"),
    V7_VEC("arguments"),
    V7_VEC("base64_decode"),
    V7_VEC("base64_encode"),
    V7_VEC("boolean"),
    V7_VEC("charAt"),
    V7_VEC("charCodeAt"),
    V7_VEC("concat"),
    V7_VEC("configurable"),
    V7_VEC("connect"),
    V7_VEC("constructor"),
    V7_VEC("create"),
    V7_VEC("defineProperties"),
    V7_VEC("defineProperty"),
    V7_VEC("every"),
    V7_VEC("exists"),
    V7_VEC("exports"),
    V7_VEC("filter"),
    V7_VEC("forEach"),
    V7_VEC("fromCharCode"),
    V7_VEC("function"),
    V7_VEC("getDate"),
    V7_VEC("getDay"),
    V7_VEC("getFullYear"),
    V7_VEC("getHours"),
    V7_VEC("getMilliseconds"),
    V7_VEC("getMinutes"),
    V7_VEC("getMonth"),
    V7_VEC("getOwnPropertyDescriptor"),
    V7_VEC("getOwnPropertyNames"),
    V7_VEC("getPrototypeOf"),
    V7_VEC("getSeconds"),
    V7_VEC("getTime"),
    V7_VEC("getTimezoneOffset"),
    V7_VEC("getUTCDate"),
    V7_VEC("getUTCDay"),
    V7_VEC("getUTCFullYear"),
    V7_VEC("getUTCHours"),
    V7_VEC("getUTCMilliseconds"),
    V7_VEC("getUTCMinutes"),
    V7_VEC("getUTCMonth"),
    V7_VEC("getUTCSeconds"),
    V7_VEC("global"),
    V7_VEC("hasOwnProperty"),
    V7_VEC("ignoreCase"),
    V7_VEC("indexOf"),
    V7_VEC("isArray"),
    V7_VEC("isExtensible"),
    V7_VEC("isFinite"),
    V7_VEC("isPrototypeOf"),
    V7_VEC("lastIndex"),
    V7_VEC("lastIndexOf"),
    V7_VEC("length"),
    V7_VEC("listen"),
    V7_VEC("loadJSON"),
    V7_VEC("localeCompare"),
    V7_VEC("md5_hex"),
    V7_VEC("module"),
    V7_VEC("multiline"),
    V7_VEC("number"),
    V7_VEC("parseFloat"),
    V7_VEC("parseInt"),
    V7_VEC("preventExtensions"),
    V7_VEC("propertyIsEnumerable"),
    V7_VEC("prototype"),
    V7_VEC("random"),
    V7_VEC("recvAll"),
    V7_VEC("reduce"),
    V7_VEC("remove"),
    V7_VEC("rename"),
    V7_VEC("render"),
    V7_VEC("replace"),
    V7_VEC("require"),
    V7_VEC("reverse"),
    V7_VEC("search"),
    V7_VEC("setDate"),
    V7_VEC("setFullYear"),
    V7_VEC("setHours"),
    V7_VEC("setMilliseconds"),
    V7_VEC("setMinutes"),
    V7_VEC("setMonth"),
    V7_VEC("setSeconds"),
    V7_VEC("setTime"),
    V7_VEC("setUTCDate"),
    V7_VEC("setUTCFullYear"),
    V7_VEC("setUTCHours"),
    V7_VEC("setUTCMilliseconds"),
    V7_VEC("setUTCMinutes"),
    V7_VEC("setUTCMonth"),
    V7_VEC("setUTCSeconds"),
    V7_VEC("sha1_hex"),
    V7_VEC("source"),
    V7_VEC("splice"),
    V7_VEC("string"),
    V7_VEC("stringify"),
    V7_VEC("substr"),
    V7_VEC("substring"),
    V7_VEC("toDateString"),
    V7_VEC("toExponential"),
    V7_VEC("toFixed"),
    V7_VEC("toISOString"),
    V7_VEC("toJSON"),
    V7_VEC("toLocaleDateString"),
    V7_VEC("toLocaleLowerCase"),
    V7_VEC("toLocaleString"),
    V7_VEC("toLocaleTimeString"),
    V7_VEC("toLocaleUpperCase"),
    V7_VEC("toLowerCase"),
    V7_VEC("toPrecision"),
    V7_VEC("toString"),
    V7_VEC("toTimeString"),
    V7_VEC("toUTCString"),
    V7_VEC("toUpperCase"),
    V7_VEC("valueOf"),
    V7_VEC("writable"),
};
/* clang-format on */

int nextesc(const char **p); /* from SLRE */
V7_PRIVATE size_t unescape(const char *s, size_t len, char *to) {
  const char *end = s + len;
  size_t n = 0;
  char tmp[4];
  Rune r;

  while (s < end) {
    s += chartorune(&r, s);
    if (r == '\\' && s < end) {
      switch (*s) {
        case '"':
          s++, r = '"';
          break;
        case '\'':
          s++, r = '\'';
          break;
        case '\n':
          s++, r = '\n';
          break;
        default: {
          const char *tmp_s = s;
          int i = nextesc(&s);
          switch (i) {
            case -SLRE_INVALID_ESC_CHAR:
              r = '\\';
              s = tmp_s;
              n += runetochar(to == NULL ? tmp : to + n, &r);
              s += chartorune(&r, s);
              break;
            case -SLRE_INVALID_HEX_DIGIT:
            default:
              r = i;
          }
        }
      }
    }
    n += runetochar(to == NULL ? tmp : to + n, &r);
  }

  return n;
}

static int v_find_string_in_dictionary(const char *s, size_t len) {
  size_t start = 0, end = ARRAY_SIZE(v_dictionary_strings);

  while (s != NULL && start < end) {
    size_t mid = start + (end - start) / 2;
    const struct v7_vec_const *v = &v_dictionary_strings[mid];
    size_t min_len = len < v->len ? len : v->len;
    int comparison_result = memcmp(s, v->p, min_len);
    if (comparison_result == 0) {
      comparison_result = len - v->len;
    }
    if (comparison_result < 0) {
      end = mid;
    } else if (comparison_result > 0) {
      start = mid + 1;
    } else {
      return mid;
    }
  }
  return -1;
}

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err v7_char_code_at(struct v7 *v7, val_t obj, val_t arg,
                                       double *res) {
  enum v7_err rcode = V7_OK;
  size_t n;
  val_t s = V7_UNDEFINED;
  const char *p = NULL;
  double at = v7_get_double(v7, arg);

  *res = 0;

  rcode = to_string(v7, obj, &s, NULL, 0, NULL);
  if (rcode != V7_OK) {
    goto clean;
  }

  p = v7_get_string(v7, &s, &n);

  n = utfnlen(p, n);
  if (v7_is_number(arg) && at >= 0 && at < n) {
    Rune r = 0;
    p = utfnshift(p, at);
    chartorune(&r, (char *) p);
    *res = r;
    goto clean;
  } else {
    *res = NAN;
    goto clean;
  }

clean:
  return rcode;
}

V7_PRIVATE int s_cmp(struct v7 *v7, val_t a, val_t b) {
  size_t a_len, b_len;
  const char *a_ptr, *b_ptr;

  a_ptr = v7_get_string(v7, &a, &a_len);
  b_ptr = v7_get_string(v7, &b, &b_len);

  if (a_len == b_len) {
    return memcmp(a_ptr, b_ptr, a_len);
  }
  if (a_len > b_len) {
    return 1;
  } else if (a_len < b_len) {
    return -1;
  } else {
    return 0;
  }
}

V7_PRIVATE val_t s_concat(struct v7 *v7, val_t a, val_t b) {
  size_t a_len, b_len, res_len;
  const char *a_ptr, *b_ptr, *res_ptr;
  val_t res;

  /* Find out lengths of both srtings */
  a_ptr = v7_get_string(v7, &a, &a_len);
  b_ptr = v7_get_string(v7, &b, &b_len);

  /* Create an placeholder string */
  res = v7_mk_string(v7, NULL, a_len + b_len, 1);

  /* v7_mk_string() may have reallocated mbuf - revalidate pointers */
  a_ptr = v7_get_string(v7, &a, &a_len);
  b_ptr = v7_get_string(v7, &b, &b_len);

  /* Copy strings into the placeholder */
  res_ptr = v7_get_string(v7, &res, &res_len);
  memcpy((char *) res_ptr, a_ptr, a_len);
  memcpy((char *) res_ptr + a_len, b_ptr, b_len);

  return res;
}

V7_PRIVATE unsigned long cstr_to_ulong(const char *s, size_t len, int *ok) {
  char *e;
  unsigned long res = strtoul(s, &e, 10);
  *ok = (e == s + len) && len != 0;
  return res;
}

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err str_to_ulong(struct v7 *v7, val_t v, int *ok,
                                    unsigned long *res) {
  enum v7_err rcode = V7_OK;
  char buf[100];
  size_t len = 0;

  V7_TRY(to_string(v7, v, NULL, buf, sizeof(buf), &len));

  *res = cstr_to_ulong(buf, len, ok);

clean:
  return rcode;
}

/* Insert a string into mbuf at specified offset */
V7_PRIVATE void embed_string(struct mbuf *m, size_t offset, const char *p,
                             size_t len, uint8_t /*enum embstr_flags*/ flags) {
  char *old_base = m->buf;
  uint8_t p_backed_by_mbuf = p >= old_base && p < old_base + m->len;
  size_t n = (flags & EMBSTR_UNESCAPE) ? unescape(p, len, NULL) : len;

  /* Calculate how many bytes length takes */
  int k = calc_llen(n);

  /* total length: varing length + string len + zero-term */
  size_t tot_len = k + n + !!(flags & EMBSTR_ZERO_TERM);

  /* Allocate buffer */
  heapusage_dont_count(1);
  mbuf_insert(m, offset, NULL, tot_len);
  heapusage_dont_count(0);

  /* Fixup p if it was relocated by mbuf_insert() above */
  if (p_backed_by_mbuf) {
    p += m->buf - old_base;
  }

  /* Write length */
  encode_varint(n, (unsigned char *) m->buf + offset);

  /* Write string */
  if (p != 0) {
    if (flags & EMBSTR_UNESCAPE) {
      unescape(p, len, m->buf + offset + k);
    } else {
      memcpy(m->buf + offset + k, p, len);
    }
  }

  /* add NULL-terminator if needed */
  if (flags & EMBSTR_ZERO_TERM) {
    m->buf[offset + tot_len - 1] = '\0';
  }
}

/* Create a string */
v7_val_t v7_mk_string(struct v7 *v7, const char *p, size_t len, int copy) {
  struct mbuf *m = copy ? &v7->owned_strings : &v7->foreign_strings;
  val_t offset = m->len, tag = V7_TAG_STRING_F;
  int dict_index;

#ifdef V7_GC_AFTER_STRING_ALLOC
  v7->need_gc = 1;
#endif

  if (len == ~((size_t) 0)) len = strlen(p);

  if (len <= 4) {
    char *s = GET_VAL_NAN_PAYLOAD(offset) + 1;
    offset = 0;
    if (p != 0) {
      memcpy(s, p, len);
    }
    s[-1] = len;
    tag = V7_TAG_STRING_I;
  } else if (len == 5) {
    char *s = GET_VAL_NAN_PAYLOAD(offset);
    offset = 0;
    if (p != 0) {
      memcpy(s, p, len);
    }
    tag = V7_TAG_STRING_5;
  } else if ((dict_index = v_find_string_in_dictionary(p, len)) >= 0) {
    offset = 0;
    GET_VAL_NAN_PAYLOAD(offset)[0] = dict_index;
    tag = V7_TAG_STRING_D;
  } else if (copy) {
    compute_need_gc(v7);

    /*
     * Before embedding new string, check if the reallocation is needed.  If
     * so, perform the reallocation by calling `mbuf_resize` manually, since we
     * need to preallocate some extra space (`_V7_STRING_BUF_RESERVE`)
     */
    if ((m->len + len) > m->size) {
      heapusage_dont_count(1);
      mbuf_resize(m, m->len + len + _V7_STRING_BUF_RESERVE);
      heapusage_dont_count(0);
    }
    embed_string(m, m->len, p, len, EMBSTR_ZERO_TERM);
    tag = V7_TAG_STRING_O;
#ifndef V7_DISABLE_STR_ALLOC_SEQ
    /* TODO(imax): panic if offset >= 2^32. */
    offset |= ((val_t) gc_next_allocation_seqn(v7, p, len)) << 32;
#endif
  } else {
    /* foreign string */
    if (sizeof(void *) <= 4 && len <= UINT16_MAX) {
      /* small foreign strings can fit length and ptr in the val_t */
      offset = (uint64_t) len << 32 | (uint64_t)(uintptr_t) p;
    } else {
      /* bigger strings need indirection that uses ram */
      size_t pos = m->len;
      int llen = calc_llen(len);

      /* allocate space for len and ptr */
      heapusage_dont_count(1);
      mbuf_insert(m, pos, NULL, llen + sizeof(p));
      heapusage_dont_count(0);

      encode_varint(len, (uint8_t *) (m->buf + pos));
      memcpy(m->buf + pos + llen, &p, sizeof(p));
    }
    tag = V7_TAG_STRING_F;
  }

  /* NOTE(lsm): don't use pointer_to_value, 32-bit ptrs will truncate */
  return (offset & ~V7_TAG_MASK) | tag;
}

int v7_is_string(val_t v) {
  uint64_t t = v & V7_TAG_MASK;
  return t == V7_TAG_STRING_I || t == V7_TAG_STRING_F || t == V7_TAG_STRING_O ||
         t == V7_TAG_STRING_5 || t == V7_TAG_STRING_D;
}

/* Get a pointer to string and string length. */
const char *v7_get_string(struct v7 *v7, val_t *v, size_t *sizep) {
  uint64_t tag = v[0] & V7_TAG_MASK;
  const char *p = NULL;
  int llen;
  size_t size = 0;

  if (!v7_is_string(*v)) {
    goto clean;
  }

  if (tag == V7_TAG_STRING_I) {
    p = GET_VAL_NAN_PAYLOAD(*v) + 1;
    size = p[-1];
  } else if (tag == V7_TAG_STRING_5) {
    p = GET_VAL_NAN_PAYLOAD(*v);
    size = 5;
  } else if (tag == V7_TAG_STRING_D) {
    int index = ((unsigned char *) GET_VAL_NAN_PAYLOAD(*v))[0];
    size = v_dictionary_strings[index].len;
    p = v_dictionary_strings[index].p;
  } else if (tag == V7_TAG_STRING_O) {
    size_t offset = (size_t) gc_string_val_to_offset(*v);
    char *s = v7->owned_strings.buf + offset;

#ifndef V7_DISABLE_STR_ALLOC_SEQ
    gc_check_valid_allocation_seqn(v7, (*v >> 32) & 0xFFFF);
#endif

    size = decode_varint((uint8_t *) s, &llen);
    p = s + llen;
  } else if (tag == V7_TAG_STRING_F) {
    /*
     * short foreign strings on <=32-bit machines can be encoded in a compact
     * form:
     *
     *     7         6        5        4        3        2        1        0
     *  11111111|1111tttt|llllllll|llllllll|ssssssss|ssssssss|ssssssss|ssssssss
     *
     * Strings longer than 2^26 will be indireceted through the foreign_strings
     * mbuf.
     *
     * We don't use a different tag to represent those two cases. Instead, all
     * foreign strings represented with the help of the foreign_strings mbuf
     * will have the upper 16-bits of the payload set to zero. This allows us to
     * represent up to 477 million foreign strings longer than 64k.
     */
    uint16_t len = (*v >> 32) & 0xFFFF;
    if (sizeof(void *) <= 4 && len != 0) {
      size = (size_t) len;
      p = (const char *) (uintptr_t) *v;
    } else {
      size_t offset = (size_t) gc_string_val_to_offset(*v);
      char *s = v7->foreign_strings.buf + offset;

      size = decode_varint((uint8_t *) s, &llen);
      memcpy(&p, s + llen, sizeof(p));
    }
  } else {
    assert(0);
  }

clean:
  if (sizep != NULL) {
    *sizep = size;
  }
  return p;
}

const char *v7_get_cstring(struct v7 *v7, v7_val_t *value) {
  size_t size;
  const char *s = v7_get_string(v7, value, &size);
  if (s == NULL) return NULL;
  if (s[size] != 0 || strlen(s) != size) {
    return NULL;
  }
  return s;
}
#ifdef V7_MODULE_LINES
#line 1 "v7/src/array.c"
#endif
/*
 * Copyright (c) 2014 Cesanta Software Limited
 * All rights reserved
 */

/* Amalgamated: #include "common/str_util.h" */
/* Amalgamated: #include "v7/src/internal.h" */
/* Amalgamated: #include "v7/src/array.h" */
/* Amalgamated: #include "v7/src/string.h" */
/* Amalgamated: #include "v7/src/object.h" */
/* Amalgamated: #include "v7/src/exceptions.h" */
/* Amalgamated: #include "v7/src/primitive.h" */
/* Amalgamated: #include "v7/src/core.h" */

/* like c_snprintf but returns `size` if write is truncated */
static int v_sprintf_s(char *buf, size_t size, const char *fmt, ...) {
  size_t n;
  va_list ap;
  va_start(ap, fmt);
  n = c_vsnprintf(buf, size, fmt, ap);
  if (n > size) {
    return size;
  }
  return n;
}

v7_val_t v7_mk_array(struct v7 *v7) {
  val_t a = mk_object(v7, v7->vals.array_prototype);
#if 0
  v7_def(v7, a, "", 0, _V7_DESC_HIDDEN(1), V7_NULL);
#endif
  return a;
}

int v7_is_array(struct v7 *v7, val_t v) {
  return v7_is_generic_object(v) &&
         is_prototype_of(v7, v, v7->vals.array_prototype);
}

/*
 * Dense arrays are backed by mbuf. Currently the array can only grow by
 * appending (i.e. setting an element whose index == array.length)
 *
 * TODO(mkm): automatically promote dense arrays to normal objects
 *            when they are used as sparse arrays or to store arbitrary keys
 *            (perhaps a hybrid approach)
 * TODO(mkm): small sparsness doesn't have to promote the array,
 *            we can just fill empty slots with a tag. In JS missing array
 *            indices are subtly different from indices with an undefined value
 *            (key iteration).
 * TODO(mkm): change the interpreter so it can set elements in dense arrays
 */
V7_PRIVATE val_t v7_mk_dense_array(struct v7 *v7) {
  val_t a = v7_mk_array(v7);
#ifdef V7_ENABLE_DENSE_ARRAYS
  v7_own(v7, &a);
  v7_def(v7, a, "", 0, _V7_DESC_HIDDEN(1), V7_NULL);

  /*
   * Before setting a `V7_OBJ_DENSE_ARRAY` flag, make sure we don't have
   * `V7_OBJ_FUNCTION` flag set
   */
  assert(!(get_object_struct(a)->attributes & V7_OBJ_FUNCTION));
  get_object_struct(a)->attributes |= V7_OBJ_DENSE_ARRAY;

  v7_disown(v7, &a);
#endif
  return a;
}

/* TODO_V7_ERR */
val_t v7_array_get(struct v7 *v7, val_t arr, unsigned long index) {
  return v7_array_get2(v7, arr, index, NULL);
}

/* TODO_V7_ERR */
val_t v7_array_get2(struct v7 *v7, val_t arr, unsigned long index, int *has) {
  enum v7_err rcode = V7_OK;
  val_t res;

  if (has != NULL) {
    *has = 0;
  }
  if (v7_is_object(arr)) {
    if (get_object_struct(arr)->attributes & V7_OBJ_DENSE_ARRAY) {
      struct v7_property *p =
          v7_get_own_property2(v7, arr, "", 0, _V7_PROPERTY_HIDDEN);
      struct mbuf *abuf = NULL;
      unsigned long len;
      if (p != NULL) {
        abuf = (struct mbuf *) v7_get_ptr(v7, p->value);
      }
      if (abuf == NULL) {
        res = V7_UNDEFINED;
        goto clean;
      }
      len = abuf->len / sizeof(val_t);
      if (index >= len) {
        res = V7_UNDEFINED;
        goto clean;
      } else {
        memcpy(&res, abuf->buf + index * sizeof(val_t), sizeof(val_t));
        if (has != NULL && res != V7_TAG_NOVALUE) *has = 1;
        if (res == V7_TAG_NOVALUE) {
          res = V7_UNDEFINED;
        }
        goto clean;
      }
    } else {
      struct v7_property *p;
      char buf[20];
      int n = v_sprintf_s(buf, sizeof(buf), "%lu", index);
      p = v7_get_property(v7, arr, buf, n);
      if (has != NULL && p != NULL) *has = 1;
      V7_TRY(v7_property_value(v7, arr, p, &res));
      goto clean;
    }
  } else {
    res = V7_UNDEFINED;
    goto clean;
  }

clean:
  (void) rcode;
  return res;
}

#if V7_ENABLE_DENSE_ARRAYS

/* Create V7 strings for integers such as array indices */
static val_t ulong_to_str(struct v7 *v7, unsigned long n) {
  char buf[100];
  int len;
  len = c_snprintf(buf, sizeof(buf), "%lu", n);
  return v7_mk_string(v7, buf, len, 1);
}

/*
 * Pack 15-bit length and 15 bit index, leaving 2 bits for tag. the LSB has to
 * be set to distinguish it from a prop pointer.
 * In alternative we just fetch the length from obj at each call to v7_next_prop
 * and just stuff the index here (e.g. on 8/16-bit platforms).
 * TODO(mkm): conditional for 16-bit platforms
 */
#define PACK_ITER(len, idx) \
  ((struct v7_property *) ((len) << 17 | (idx) << 1 | 1))

#define UNPACK_ITER_LEN(p) (((uintptr_t) p) >> 17)
#define UNPACK_ITER_IDX(p) ((((uintptr_t) p) >> 1) & 0x7FFF)
#define IS_PACKED_ITER(p) ((uintptr_t) p & 1)

void *v7_next_prop(struct v7 *v7, val_t obj, void *h, val_t *name, val_t *val,
                   v7_prop_attr_t *attrs) {
  struct v7_property *p = (struct v7_property *) h;

  if (get_object_struct(obj)->attributes & V7_OBJ_DENSE_ARRAY) {
    /* This is a dense array. Find backing mbuf and fetch values from there */
    struct v7_property *hp =
        v7_get_own_property2(v7, obj, "", 0, _V7_PROPERTY_HIDDEN);
    struct mbuf *abuf = NULL;
    unsigned long len, idx;
    if (hp != NULL) {
      abuf = (struct mbuf *) v7_get_ptr(v7, hp->value);
    }
    if (abuf == NULL) return NULL;
    len = abuf->len / sizeof(val_t);
    if (len == 0) return NULL;
    idx = (p == NULL) ? 0 : UNPACK_ITER_IDX(p) + 1;
    p = (idx == len) ? get_object_struct(obj)->properties : PACK_ITER(len, idx);
    if (val != NULL) *val = ((val_t *) abuf->buf)[idx];
    if (attrs != NULL) *attrs = 0;
    if (name != NULL) {
      char buf[20];
      int n = v_sprintf_s(buf, sizeof(buf), "%lu", index);
      *name = v7_mk_string(v7, buf, n, 1);
    }
  } else {
    /* Ordinary object */
    p = (p == NULL) ? get_object_struct(obj)->properties : p->next;
    if (p != NULL) {
      if (name != NULL) *name = p->name;
      if (val != NULL) *val = p->value;
      if (attrs != NULL) *attrs = p->attributes;
    }
  }

  return p;
}

V7_PRIVATE val_t
v7_iter_get_value(struct v7 *v7, val_t obj, struct v7_property *p) {
  return IS_PACKED_ITER(p) ? v7_array_get(v7, obj, UNPACK_ITER_IDX(p))
                           : p->value;
}

V7_PRIVATE val_t v7_iter_get_name(struct v7 *v7, struct v7_property *p) {
  return IS_PACKED_ITER(p) ? ulong_to_str(v7, UNPACK_ITER_IDX(p)) : p->name;
}

V7_PRIVATE uint8_t v7_iter_get_attrs(struct v7_property *p) {
  return IS_PACKED_ITER(p) ? 0 : p->attributes;
}

/* return array index as number or undefined. works with iterators */
V7_PRIVATE enum v7_err v7_iter_get_index(struct v7 *v7, struct v7_property *p,
                                         val_t *res) {
  enum v7_err rcode = V7_OK;
  int ok;
  unsigned long res;
  if (IS_PACKED_ITER(p)) {
    *res = v7_mk_number(v7, UNPACK_ITER_IDX(p));
    goto clean;
  }
  V7_TRY(str_to_ulong(v7, p->name, &ok, &res));
  if (!ok || res >= UINT32_MAX) {
    goto clean;
  }
  *res = v7_mk_number(v7, res);
  goto clean;

clean:
  return rcode;
}
#endif

/* TODO_V7_ERR */
unsigned long v7_array_length(struct v7 *v7, val_t v) {
  enum v7_err rcode = V7_OK;
  struct v7_property *p;
  unsigned long len = 0;

  if (!v7_is_object(v)) {
    len = 0;
    goto clean;
  }

#if V7_ENABLE_DENSE_ARRAYS
  if (get_object_struct(v)->attributes & V7_OBJ_DENSE_ARRAY) {
    struct v7_property *p =
        v7_get_own_property2(v7, v, "", 0, _V7_PROPERTY_HIDDEN);
    struct mbuf *abuf;
    if (p == NULL) {
      len = 0;
      goto clean;
    }
    abuf = (struct mbuf *) v7_get_ptr(v7, p->value);
    if (abuf == NULL) {
      len = 0;
      goto clean;
    }
    len = abuf->len / sizeof(val_t);
    goto clean;
  }
#endif

  for (p = get_object_struct(v)->properties; p != NULL; p = p->next) {
    int ok = 0;
    unsigned long n = 0;
    V7_TRY(str_to_ulong(v7, p->name, &ok, &n));
    if (ok && n >= len && n < UINT32_MAX) {
      len = n + 1;
    }
  }

clean:
  (void) rcode;
  return len;
}

int v7_array_set(struct v7 *v7, val_t arr, unsigned long index, val_t v) {
  enum v7_err rcode = V7_OK;
  uint8_t saved_is_thrown = 0;
  val_t saved_thrown = v7_get_thrown_value(v7, &saved_is_thrown);
  int ret = -1;

  rcode = v7_array_set_throwing(v7, arr, index, v, &ret);
  if (rcode != V7_OK) {
    rcode = V7_OK;
    if (saved_is_thrown) {
      rcode = v7_throw(v7, saved_thrown);
    } else {
      v7_clear_thrown_value(v7);
    }
    ret = -1;
  }

  return ret;
}

enum v7_err v7_array_set_throwing(struct v7 *v7, val_t arr, unsigned long index,
                                  val_t v, int *res) {
  enum v7_err rcode = V7_OK;
  int ires = -1;

  if (v7_is_object(arr)) {
    if (get_object_struct(arr)->attributes & V7_OBJ_DENSE_ARRAY) {
      struct v7_property *p =
          v7_get_own_property2(v7, arr, "", 0, _V7_PROPERTY_HIDDEN);
      struct mbuf *abuf;
      unsigned long len;
      assert(p != NULL);
      abuf = (struct mbuf *) v7_get_ptr(v7, p->value);

      if (get_object_struct(arr)->attributes & V7_OBJ_NOT_EXTENSIBLE) {
        if (is_strict_mode(v7)) {
          rcode = v7_throwf(v7, TYPE_ERROR, "Object is not extensible");
          goto clean;
        }

        goto clean;
      }

      if (abuf == NULL) {
        abuf = (struct mbuf *) malloc(sizeof(*abuf));
        mbuf_init(abuf, sizeof(val_t) * (index + 1));
        p->value = v7_mk_foreign(v7, abuf);
      }
      len = abuf->len / sizeof(val_t);
      /* TODO(mkm): possibly promote to sparse array */
      if (index > len) {
        unsigned long i;
        val_t s = V7_TAG_NOVALUE;
        for (i = len; i < index; i++) {
          mbuf_append(abuf, (char *) &s, sizeof(val_t));
        }
        len = index;
      }

      if (index == len) {
        mbuf_append(abuf, (char *) &v, sizeof(val_t));
      } else {
        memcpy(abuf->buf + index * sizeof(val_t), &v, sizeof(val_t));
      }
    } else {
      char buf[20];
      int n = v_sprintf_s(buf, sizeof(buf), "%lu", index);
      {
        struct v7_property *tmp = NULL;
        rcode = set_property(v7, arr, buf, n, v, &tmp);
        ires = (tmp == NULL) ? -1 : 0;
      }
      if (rcode != V7_OK) {
        goto clean;
      }
    }
  }

clean:
  if (res != NULL) {
    *res = ires;
  }
  return rcode;
}

void v7_array_del(struct v7 *v7, val_t arr, unsigned long index) {
  char buf[20];
  int n = v_sprintf_s(buf, sizeof(buf), "%lu", index);
  v7_del(v7, arr, buf, n);
}

int v7_array_push(struct v7 *v7, v7_val_t arr, v7_val_t v) {
  return v7_array_set(v7, arr, v7_array_length(v7, arr), v);
}

WARN_UNUSED_RESULT
enum v7_err v7_array_push_throwing(struct v7 *v7, v7_val_t arr, v7_val_t v,
                                   int *res) {
  return v7_array_set_throwing(v7, arr, v7_array_length(v7, arr), v, res);
}
#ifdef V7_MODULE_LINES
#line 1 "v7/src/object.c"
#endif
/*
 * Copyright (c) 2014 Cesanta Software Limited
 * All rights reserved
 */

/* Amalgamated: #include "v7/src/internal.h" */
/* Amalgamated: #include "v7/src/core.h" */
/* Amalgamated: #include "v7/src/primitive.h" */
/* Amalgamated: #include "v7/src/function.h" */
/* Amalgamated: #include "v7/src/gc.h" */
/* Amalgamated: #include "v7/src/object.h" */
/* Amalgamated: #include "v7/src/string.h" */
/* Amalgamated: #include "v7/src/array.h" */
/* Amalgamated: #include "v7/src/eval.h" */
/* Amalgamated: #include "v7/src/exceptions.h" */
/* Amalgamated: #include "v7/src/conversion.h" */
/* Amalgamated: #include "v7/src/std_proxy.h" */
/* Amalgamated: #include "v7/src/util.h" */

/*
 * Default property attributes (see `v7_prop_attr_t`)
 */
#define V7_DEFAULT_PROPERTY_ATTRS 0

V7_PRIVATE val_t mk_object(struct v7 *v7, val_t prototype) {
  struct v7_generic_object *o = new_generic_object(v7);
  if (o == NULL) {
    return V7_NULL;
  }
  (void) v7;
#if defined(V7_ENABLE_ENTITY_IDS)
  o->base.entity_id_base = V7_ENTITY_ID_PART_OBJ;
  o->base.entity_id_spec = V7_ENTITY_ID_PART_GEN_OBJ;
#endif
  o->base.properties = NULL;
  obj_prototype_set(v7, &o->base, get_object_struct(prototype));
  return v7_object_to_value(&o->base);
}

v7_val_t v7_mk_object(struct v7 *v7) {
  return mk_object(v7, v7->vals.object_prototype);
}

V7_PRIVATE val_t v7_object_to_value(struct v7_object *o) {
  if (o == NULL) {
    return V7_NULL;
  } else if (o->attributes & V7_OBJ_FUNCTION) {
    return pointer_to_value(o) | V7_TAG_FUNCTION;
  } else {
    return pointer_to_value(o) | V7_TAG_OBJECT;
  }
}

V7_PRIVATE struct v7_generic_object *get_generic_object_struct(val_t v) {
  struct v7_generic_object *ret = NULL;
  if (v7_is_null(v)) {
    ret = NULL;
  } else {
    assert(v7_is_generic_object(v));
    ret = (struct v7_generic_object *) get_ptr(v);
#if defined(V7_ENABLE_ENTITY_IDS)
    if (ret->base.entity_id_base != V7_ENTITY_ID_PART_OBJ) {
      fprintf(stderr, "not a generic object!\n");
      abort();
    } else if (ret->base.entity_id_spec != V7_ENTITY_ID_PART_GEN_OBJ) {
      fprintf(stderr, "not an object (but is a generic object)!\n");
      abort();
    }
#endif
  }
  return ret;
}

V7_PRIVATE struct v7_object *get_object_struct(val_t v) {
  struct v7_object *ret = NULL;
  if (v7_is_null(v)) {
    ret = NULL;
  } else {
    assert(v7_is_object(v));
    ret = (struct v7_object *) get_ptr(v);
#if defined(V7_ENABLE_ENTITY_IDS)
    if (ret->entity_id_base != V7_ENTITY_ID_PART_OBJ) {
      fprintf(stderr, "not an object!\n");
      abort();
    }
#endif
  }
  return ret;
}

int v7_is_object(val_t v) {
  return (v & V7_TAG_MASK) == V7_TAG_OBJECT ||
         (v & V7_TAG_MASK) == V7_TAG_FUNCTION;
}

V7_PRIVATE int v7_is_generic_object(val_t v) {
  return (v & V7_TAG_MASK) == V7_TAG_OBJECT;
}

/* Object properties {{{ */

V7_PRIVATE struct v7_property *v7_mk_property(struct v7 *v7) {
  struct v7_property *p = new_property(v7);
#if defined(V7_ENABLE_ENTITY_IDS)
  p->entity_id = V7_ENTITY_ID_PROP;
#endif
  p->next = NULL;
  p->name = V7_UNDEFINED;
  p->value = V7_UNDEFINED;
  p->attributes = 0;
  return p;
}

V7_PRIVATE struct v7_property *v7_get_own_property2(struct v7 *v7, val_t obj,
                                                    const char *name,
                                                    size_t len,
                                                    v7_prop_attr_t attrs) {
  struct v7_property *p;
  struct v7_object *o;
  val_t ss;
  if (!v7_is_object(obj)) {
    return NULL;
  }
  if (len == (size_t) ~0) {
    len = strlen(name);
  }

  o = get_object_struct(obj);
  /*
   * len check is needed to allow getting the mbuf from the hidden property.
   * TODO(mkm): however hidden properties cannot be safely represented with
   * a zero length string anyway, so this will change.
   */
  if (o->attributes & V7_OBJ_DENSE_ARRAY && len > 0) {
    int ok, has;
    unsigned long i = cstr_to_ulong(name, len, &ok);
    if (ok) {
      v7->cur_dense_prop->value = v7_array_get2(v7, obj, i, &has);
      return has ? v7->cur_dense_prop : NULL;
    }
  }

  if (len <= 5) {
    ss = v7_mk_string(v7, name, len, 1);
    for (p = o->properties; p != NULL; p = p->next) {
#if defined(V7_ENABLE_ENTITY_IDS)
      if (p->entity_id != V7_ENTITY_ID_PROP) {
        fprintf(stderr, "not a prop!=0x%x\n", p->entity_id);
        abort();
      }
#endif
      if (p->name == ss && (attrs == 0 || (p->attributes & attrs))) {
        return p;
      }
    }
  } else {
    for (p = o->properties; p != NULL; p = p->next) {
      size_t n;
      const char *s = v7_get_string(v7, &p->name, &n);
#if defined(V7_ENABLE_ENTITY_IDS)
      if (p->entity_id != V7_ENTITY_ID_PROP) {
        fprintf(stderr, "not a prop!=0x%x\n", p->entity_id);
        abort();
      }
#endif
      if (n == len && strncmp(s, name, len) == 0 &&
          (attrs == 0 || (p->attributes & attrs))) {
        return p;
      }
    }
  }
  return NULL;
}

V7_PRIVATE struct v7_property *v7_get_own_property(struct v7 *v7, val_t obj,
                                                   const char *name,
                                                   size_t len) {
  return v7_get_own_property2(v7, obj, name, len, 0);
}

V7_PRIVATE struct v7_property *v7_get_property(struct v7 *v7, val_t obj,
                                               const char *name, size_t len) {
  if (!v7_is_object(obj)) {
    return NULL;
  }
  for (; obj != V7_NULL; obj = v7_get_proto(v7, obj)) {
    struct v7_property *prop;
    if ((prop = v7_get_own_property(v7, obj, name, len)) != NULL) {
      return prop;
    }
  }
  return NULL;
}

V7_PRIVATE enum v7_err v7_get_property_v(struct v7 *v7, val_t obj,
                                         v7_val_t name,
                                         struct v7_property **res) {
  enum v7_err rcode = V7_OK;
  size_t name_len;
  STATIC char buf[8];
  const char *s = buf;
  uint8_t fr = 0;

  if (v7_is_string(name)) {
    s = v7_get_string(v7, &name, &name_len);
  } else {
    char *stmp;
    V7_TRY(v7_stringify_throwing(v7, name, buf, sizeof(buf),
                                 V7_STRINGIFY_DEFAULT, &stmp));
    s = stmp;
    if (s != buf) {
      fr = 1;
    }
    name_len = strlen(s);
  }

  *res = v7_get_property(v7, obj, s, name_len);

clean:
  if (fr) {
    free((void *) s);
  }
  return rcode;
}

WARN_UNUSED_RESULT
enum v7_err v7_get_throwing(struct v7 *v7, val_t obj, const char *name,
                            size_t name_len, val_t *res) {
  enum v7_err rcode = V7_OK;
  val_t v = obj;

  v7_own(v7, &v);

  if (name_len == (size_t) ~0) {
    name_len = strlen(name);
  }

  if (v7_is_string(obj)) {
    v = v7->vals.string_prototype;
  } else if (v7_is_number(obj)) {
    v = v7->vals.number_prototype;
  } else if (v7_is_boolean(obj)) {
    v = v7->vals.boolean_prototype;
  } else if (v7_is_undefined(obj)) {
    rcode =
        v7_throwf(v7, TYPE_ERROR, "cannot read property '%.*s' of undefined",
                  (int) name_len, name);
    goto clean;
  } else if (v7_is_null(obj)) {
    rcode = v7_throwf(v7, TYPE_ERROR, "cannot read property '%.*s' of null",
                      (int) name_len, name);
    goto clean;
  } else if (is_cfunction_lite(obj)) {
    v = v7->vals.function_prototype;
  }

#if V7_ENABLE__Proxy
  {
    struct v7_object *o = NULL;
    if (v7_is_object(obj)) {
      o = get_object_struct(obj);
    }

    if (o != NULL && (o->attributes & V7_OBJ_PROXY) &&
        !is_special_proxy_name(name, name_len)) {
      /* we need to access the target object through a proxy */

      val_t target_v = V7_UNDEFINED;
      val_t handler_v = V7_UNDEFINED;
      val_t name_v = V7_UNDEFINED;
      val_t get_v = V7_UNDEFINED;
      val_t get_args_v = V7_UNDEFINED;

      /*
       * we need to create a copy of the name, because the given `name` might
       * be returned by v7_get_string(), and any object creation might
       * invalidate this pointer. Below, we're going to create some objects.
       *
       * It would probably be cleaner to always create a copy before calling
       * v7_get_throwing if the name was returned by v7_get_string(), but that
       * would cause additional pressure on the heap, so let's not do that
       */
      char *name_copy = (char *) calloc(1, name_len + 1 /* null-term */);
      memcpy(name_copy, name, name_len);

      v7_own(v7, &target_v);
      v7_own(v7, &handler_v);
      v7_own(v7, &name_v);
      v7_own(v7, &get_v);
      v7_own(v7, &get_args_v);

      V7_TRY2(v7_get_throwing(v7, obj, _V7_PROXY_TARGET_NAME, ~0, &target_v),
              clean_proxy);
      V7_TRY2(v7_get_throwing(v7, obj, _V7_PROXY_HANDLER_NAME, ~0, &handler_v),
              clean_proxy);
      V7_TRY2(v7_get_throwing(v7, handler_v, "get", ~0, &get_v), clean_proxy);

      if (v7_is_callable(v7, get_v)) {
        /* The `get` callback is actually callable, so, use it */

        /* prepare arguments for the callback */
        get_args_v = v7_mk_dense_array(v7);
        /*
         * TODO(dfrank): don't copy string in case we already have val_t (we
         * need some generic function which will take both `const char *` and
         * val_t)
         */
        v7_array_set(v7, get_args_v, 0, target_v);
        v7_array_set(v7, get_args_v, 1,
                     v7_mk_string(v7, name_copy, name_len, 1));

        /* call `get` callback */
        V7_TRY2(b_apply(v7, get_v, V7_UNDEFINED, get_args_v, 0, res),
                clean_proxy);
      } else {
        /*
         * there's no `get` callback: then, get property from the target object
         * (not from the proxy object)
         */
        V7_TRY2(v7_get_throwing(v7, target_v, name_copy, name_len, res),
                clean_proxy);
      }

    clean_proxy:

      free(name_copy);

      v7_disown(v7, &get_args_v);
      v7_disown(v7, &get_v);
      v7_disown(v7, &name_v);
      v7_disown(v7, &handler_v);
      v7_disown(v7, &target_v);
      goto clean;
    }
  }
#endif

  /* regular (non-proxy) property access */
  V7_TRY(
      v7_property_value(v7, obj, v7_get_property(v7, v, name, name_len), res));

clean:
  v7_disown(v7, &v);
  return rcode;
}

v7_val_t v7_get(struct v7 *v7, val_t obj, const char *name, size_t name_len) {
  enum v7_err rcode = V7_OK;
  uint8_t saved_is_thrown = 0;
  val_t saved_thrown = v7_get_thrown_value(v7, &saved_is_thrown);
  v7_val_t ret = V7_UNDEFINED;

  rcode = v7_get_throwing(v7, obj, name, name_len, &ret);
  if (rcode != V7_OK) {
    rcode = V7_OK;
    if (saved_is_thrown) {
      rcode = v7_throw(v7, saved_thrown);
    } else {
      v7_clear_thrown_value(v7);
    }
    ret = V7_UNDEFINED;
  }

  return ret;
}

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err v7_get_throwing_v(struct v7 *v7, v7_val_t obj,
                                         v7_val_t name, v7_val_t *res) {
  enum v7_err rcode = V7_OK;
  size_t name_len;
  STATIC char buf[8];
  const char *s = buf;
  uint8_t fr = 0;

  /* subscripting strings */
  if (v7_is_string(obj)) {
    char ch;
    double dch = 0;

    rcode = v7_char_code_at(v7, obj, name, &dch);
    if (rcode != V7_OK) {
      goto clean;
    }

    if (!isnan(dch)) {
      ch = dch;
      *res = v7_mk_string(v7, &ch, 1, 1);
      goto clean;
    }
  }

  if (v7_is_string(name)) {
    s = v7_get_string(v7, &name, &name_len);
  } else {
    char *stmp;
    V7_TRY(v7_stringify_throwing(v7, name, buf, sizeof(buf),
                                 V7_STRINGIFY_DEFAULT, &stmp));
    s = stmp;
    if (s != buf) {
      fr = 1;
    }
    name_len = strlen(s);
  }
  V7_TRY(v7_get_throwing(v7, obj, s, name_len, res));

clean:
  if (fr) {
    free((void *) s);
  }
  return rcode;
}

V7_PRIVATE void v7_destroy_property(struct v7_property **p) {
  *p = NULL;
}

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err v7_invoke_setter(struct v7 *v7, struct v7_property *prop,
                                        val_t obj, val_t val) {
  enum v7_err rcode = V7_OK;
  val_t setter = prop->value, args;
  v7_own(v7, &val);
  args = v7_mk_dense_array(v7);
  v7_own(v7, &args);
  if (prop->attributes & V7_PROPERTY_GETTER) {
    setter = v7_array_get(v7, prop->value, 1);
  }
  v7_array_set(v7, args, 0, val);
  v7_disown(v7, &args);
  v7_disown(v7, &val);
  {
    val_t val = V7_UNDEFINED;
    V7_TRY(b_apply(v7, setter, obj, args, 0, &val));
  }

clean:
  return rcode;
}

static v7_prop_attr_t apply_attrs_desc(v7_prop_attr_desc_t attrs_desc,
                                       v7_prop_attr_t old_attrs) {
  v7_prop_attr_t ret = old_attrs;
  if (old_attrs & V7_PROPERTY_NON_CONFIGURABLE) {
    /*
     * The property is non-configurable: we can only change it from being
     * writable to non-writable
     */

    if ((attrs_desc >> _V7_DESC_SHIFT) & V7_PROPERTY_NON_WRITABLE &&
        (attrs_desc & V7_PROPERTY_NON_WRITABLE)) {
      ret |= V7_PROPERTY_NON_WRITABLE;
    }

  } else {
    /* The property is configurable: we can change any attributes */
    ret = (old_attrs & ~(attrs_desc >> _V7_DESC_SHIFT)) |
          (attrs_desc & _V7_DESC_MASK);
  }

  return ret;
}

int v7_def(struct v7 *v7, val_t obj, const char *name, size_t len,
           v7_prop_attr_desc_t attrs_desc, v7_val_t val) {
  enum v7_err rcode = V7_OK;
  uint8_t saved_is_thrown = 0;
  val_t saved_thrown = v7_get_thrown_value(v7, &saved_is_thrown);
  int ret = -1;

  {
    struct v7_property *tmp = NULL;
    rcode = def_property(v7, obj, name, len, attrs_desc, val, 0 /*not assign*/,
                         &tmp);
    ret = (tmp == NULL) ? -1 : 0;
  }

  if (rcode != V7_OK) {
    rcode = V7_OK;
    if (saved_is_thrown) {
      rcode = v7_throw(v7, saved_thrown);
    } else {
      v7_clear_thrown_value(v7);
    }
    ret = -1;
  }

  return ret;
}

int v7_set(struct v7 *v7, val_t obj, const char *name, size_t len,
           v7_val_t val) {
  enum v7_err rcode = V7_OK;
  uint8_t saved_is_thrown = 0;
  val_t saved_thrown = v7_get_thrown_value(v7, &saved_is_thrown);
  int ret = -1;

  {
    struct v7_property *tmp = NULL;
    rcode = set_property(v7, obj, name, len, val, &tmp);
    ret = (tmp == NULL) ? -1 : 0;
  }

  if (rcode != V7_OK) {
    rcode = V7_OK;
    if (saved_is_thrown) {
      rcode = v7_throw(v7, saved_thrown);
    } else {
      v7_clear_thrown_value(v7);
    }
    ret = -1;
  }

  return ret;
}

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err set_property_v(struct v7 *v7, val_t obj, val_t name,
                                      val_t val, struct v7_property **res) {
  return def_property_v(v7, obj, name, 0, val, 1 /*as_assign*/, res);
}

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err set_property(struct v7 *v7, val_t obj, const char *name,
                                    size_t len, v7_val_t val,
                                    struct v7_property **res) {
  return def_property(v7, obj, name, len, 0, val, 1 /*as_assign*/, res);
}

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err def_property_v(struct v7 *v7, val_t obj, val_t name,
                                      v7_prop_attr_desc_t attrs_desc, val_t val,
                                      uint8_t as_assign,
                                      struct v7_property **res) {
  enum v7_err rcode = V7_OK;
  struct v7_property *prop = NULL;
  size_t len;
  const char *n = v7_get_string(v7, &name, &len);

  v7_own(v7, &name);
  v7_own(v7, &val);

  if (!v7_is_object(obj)) {
    prop = NULL;
    goto clean;
  }

#if V7_ENABLE__Proxy
  if ((get_object_struct(obj)->attributes & V7_OBJ_PROXY) &&
      !is_special_proxy_name(n, len)) {
    /* we need to access the target object through a proxy */

    val_t target_v = V7_UNDEFINED;
    val_t handler_v = V7_UNDEFINED;
    val_t set_v = V7_UNDEFINED;
    val_t set_args_v = V7_UNDEFINED;

    v7_own(v7, &target_v);
    v7_own(v7, &handler_v);
    v7_own(v7, &set_v);
    v7_own(v7, &set_args_v);

    V7_TRY2(v7_get_throwing(v7, obj, _V7_PROXY_TARGET_NAME, ~0, &target_v),
            clean_proxy);
    V7_TRY2(v7_get_throwing(v7, obj, _V7_PROXY_HANDLER_NAME, ~0, &handler_v),
            clean_proxy);
    /*
     * We'll consult "set" property in case of the plain assignment only;
     * Object.defineProperty() has its own trap `defineProperty` which is not
     * yet implemented in v7
     */
    if (as_assign) {
      V7_TRY2(v7_get_throwing(v7, handler_v, "set", ~0, &set_v), clean_proxy);
    }

    if (v7_is_callable(v7, set_v)) {
      /* The `set` callback is actually callable, so, use it */

      /* prepare arguments for the callback */
      set_args_v = v7_mk_dense_array(v7);
      /*
       * TODO(dfrank): don't copy string in case we already have val_t
       * (we need some generic function which will take both const char * and
       * val_t for that)
       */
      v7_array_set(v7, set_args_v, 0, target_v);
      v7_array_set(v7, set_args_v, 1, name);
      v7_array_set(v7, set_args_v, 2, val);

      /* call `set` callback */
      V7_TRY2(b_apply(v7, set_v, V7_UNDEFINED, set_args_v, 0, &val),
              clean_proxy);

      /* in strict mode, we should throw if trap returned falsy value */
      if (is_strict_mode(v7) && !v7_is_truthy(v7, val)) {
        V7_THROW2(
            v7_throwf(v7, TYPE_ERROR, "Trap returned falsy for property '%s'",
                      v7_get_string(v7, &name, NULL)),
            clean_proxy);
      }

    } else {
      /*
       * there's no `set` callback: then, set property on the target object
       * (not on the proxy object)
       */
      V7_TRY2(
          def_property_v(v7, target_v, name, attrs_desc, val, as_assign, res),
          clean_proxy);
    }

  clean_proxy:
    v7_disown(v7, &set_args_v);
    v7_disown(v7, &set_v);
    v7_disown(v7, &handler_v);
    v7_disown(v7, &target_v);
    goto clean;
  }
#endif

  /* regular (non-proxy) property access */
  prop = v7_get_own_property(v7, obj, n, len);
  if (prop == NULL) {
    /*
     * The own property with given `name` doesn't exist yet: try to create it,
     * set requested `name` and `attributes`, and append to the object's
     * properties
     */

    /* make sure the object is extensible */
    if (get_object_struct(obj)->attributes & V7_OBJ_NOT_EXTENSIBLE) {
      /*
       * We should throw if we use `Object.defineProperty`, or if we're in
       * strict mode.
       */
      if (is_strict_mode(v7) || !as_assign) {
        V7_THROW(v7_throwf(v7, TYPE_ERROR, "Object is not extensible"));
      }
      prop = NULL;
      goto clean;
    }

    if ((prop = v7_mk_property(v7)) == NULL) {
      prop = NULL; /* LCOV_EXCL_LINE */
      goto clean;
    }
    prop->name = name;
    prop->value = val;
    prop->attributes = apply_attrs_desc(attrs_desc, V7_DEFAULT_PROPERTY_ATTRS);

    prop->next = get_object_struct(obj)->properties;
    get_object_struct(obj)->properties = prop;
    goto clean;
  } else {
    /* Property already exists */

    if (prop->attributes & V7_PROPERTY_NON_WRITABLE) {
      /* The property is read-only */

      if (as_assign) {
        /* Plain assignment: in strict mode throw, otherwise ignore */
        if (is_strict_mode(v7)) {
          V7_THROW(
              v7_throwf(v7, TYPE_ERROR, "Cannot assign to read-only property"));
        } else {
          prop = NULL;
          goto clean;
        }
      } else if (prop->attributes & V7_PROPERTY_NON_CONFIGURABLE) {
        /*
         * Use `Object.defineProperty` semantic, and the property is
         * non-configurable: if no value is provided, or if new value is equal
         * to the existing one, then just fall through to change attributes;
         * otherwise, throw.
         */

        if (!(attrs_desc & V7_DESC_PRESERVE_VALUE)) {
          uint8_t equal = 0;
          if (v7_is_string(val) && v7_is_string(prop->value)) {
            equal = (s_cmp(v7, val, prop->value) == 0);
          } else {
            equal = (val == prop->value);
          }

          if (!equal) {
            /* Values are not equal: should throw */
            V7_THROW(v7_throwf(v7, TYPE_ERROR,
                               "Cannot redefine read-only property"));
          } else {
            /*
             * Values are equal. Will fall through so that attributes might
             * change.
             */
          }
        } else {
          /*
           * No value is provided. Will fall through so that attributes might
           * change.
           */
        }
      } else {
        /*
         * Use `Object.defineProperty` semantic, and the property is
         * configurable: will fall through and assign new value, effectively
         * ignoring non-writable flag. This is the same as making a property
         * writable, then assigning a new value, and making a property
         * non-writable again.
         */
      }
    } else if (prop->attributes & V7_PROPERTY_SETTER) {
      /* Invoke setter */
      V7_TRY(v7_invoke_setter(v7, prop, obj, val));
      prop = NULL;
      goto clean;
    }

    /* Set value and apply attrs delta */
    if (!(attrs_desc & V7_DESC_PRESERVE_VALUE)) {
      prop->value = val;
    }
    prop->attributes = apply_attrs_desc(attrs_desc, prop->attributes);
  }

clean:

  if (res != NULL) {
    *res = prop;
  }

  v7_disown(v7, &val);
  v7_disown(v7, &name);

  return rcode;
}

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err def_property(struct v7 *v7, val_t obj, const char *name,
                                    size_t len, v7_prop_attr_desc_t attrs_desc,
                                    v7_val_t val, uint8_t as_assign,
                                    struct v7_property **res) {
  enum v7_err rcode = V7_OK;
  val_t name_val = V7_UNDEFINED;

  v7_own(v7, &obj);
  v7_own(v7, &val);
  v7_own(v7, &name_val);

  if (len == (size_t) ~0) {
    len = strlen(name);
  }

  name_val = v7_mk_string(v7, name, len, 1);
  V7_TRY(def_property_v(v7, obj, name_val, attrs_desc, val, as_assign, res));

clean:
  v7_disown(v7, &name_val);
  v7_disown(v7, &val);
  v7_disown(v7, &obj);

  return rcode;
}

V7_PRIVATE int set_method(struct v7 *v7, v7_val_t obj, const char *name,
                          v7_cfunction_t *func, int num_args) {
  return v7_def(v7, obj, name, strlen(name), V7_DESC_ENUMERABLE(0),
                mk_cfunction_obj(v7, func, num_args));
}

int v7_set_method(struct v7 *v7, v7_val_t obj, const char *name,
                  v7_cfunction_t *func) {
  return set_method(v7, obj, name, func, ~0);
}

V7_PRIVATE int set_cfunc_prop(struct v7 *v7, val_t o, const char *name,
                              v7_cfunction_t *f) {
  return v7_def(v7, o, name, strlen(name), V7_DESC_ENUMERABLE(0),
                v7_mk_cfunction(f));
}

/*
 * See comments in `object_public.h`
 */
int v7_del(struct v7 *v7, val_t obj, const char *name, size_t len) {
  struct v7_property *prop, *prev;

  if (!v7_is_object(obj)) {
    return -1;
  }
  if (len == (size_t) ~0) {
    len = strlen(name);
  }
  for (prev = NULL, prop = get_object_struct(obj)->properties; prop != NULL;
       prev = prop, prop = prop->next) {
    size_t n;
    const char *s = v7_get_string(v7, &prop->name, &n);
    if (n == len && strncmp(s, name, len) == 0) {
      if (prev) {
        prev->next = prop->next;
      } else {
        get_object_struct(obj)->properties = prop->next;
      }
      v7_destroy_property(&prop);
      return 0;
    }
  }
  return -1;
}

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err v7_property_value(struct v7 *v7, val_t obj,
                                         struct v7_property *p, val_t *res) {
  enum v7_err rcode = V7_OK;
  if (p == NULL) {
    *res = V7_UNDEFINED;
    goto clean;
  }
  if (p->attributes & V7_PROPERTY_GETTER) {
    val_t getter = p->value;
    if (p->attributes & V7_PROPERTY_SETTER) {
      getter = v7_array_get(v7, p->value, 0);
    }
    {
      V7_TRY(b_apply(v7, getter, obj, V7_UNDEFINED, 0, res));
      goto clean;
    }
  }

  *res = p->value;
  goto clean;

clean:
  return rcode;
}

enum v7_err v7_init_prop_iter_ctx(struct v7 *v7, v7_val_t obj,
                                  struct prop_iter_ctx *ctx) {
  return init_prop_iter_ctx(v7, obj, 1 /*proxy-transparent*/, ctx);
}

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err init_prop_iter_ctx(struct v7 *v7, v7_val_t obj,
                                          int proxy_transp,
                                          struct prop_iter_ctx *ctx) {
  enum v7_err rcode = V7_OK;

  v7_own(v7, &obj);

  memset(ctx, 0x00, sizeof(*ctx));

  if (v7_is_object(obj)) {
#if V7_ENABLE__Proxy
    if (proxy_transp && get_object_struct(obj)->attributes & V7_OBJ_PROXY) {
      v7_val_t ownKeys_v = V7_UNDEFINED;
      v7_val_t args_v = V7_UNDEFINED;

      v7_own(v7, &ownKeys_v);
      v7_own(v7, &args_v);

      ctx->proxy_ctx =
          (struct prop_iter_proxy_ctx *) calloc(1, sizeof(*ctx->proxy_ctx));

      ctx->proxy_ctx->target_obj = V7_UNDEFINED;
      ctx->proxy_ctx->handler_obj = V7_UNDEFINED;
      ctx->proxy_ctx->own_keys = V7_UNDEFINED;
      ctx->proxy_ctx->get_own_prop_desc = V7_UNDEFINED;

      v7_own(v7, &ctx->proxy_ctx->target_obj);
      v7_own(v7, &ctx->proxy_ctx->handler_obj);
      v7_own(v7, &ctx->proxy_ctx->own_keys);
      v7_own(v7, &ctx->proxy_ctx->get_own_prop_desc);

      V7_TRY2(v7_get_throwing(v7, obj, _V7_PROXY_TARGET_NAME, ~0,
                              &ctx->proxy_ctx->target_obj),
              clean_proxy);
      V7_TRY2(v7_get_throwing(v7, obj, _V7_PROXY_HANDLER_NAME, ~0,
                              &ctx->proxy_ctx->handler_obj),
              clean_proxy);

      V7_TRY2(v7_get_throwing(v7, ctx->proxy_ctx->handler_obj, "ownKeys", ~0,
                              &ownKeys_v),
              clean_proxy);

      if (v7_is_callable(v7, ownKeys_v)) {
        /* prepare arguments for the ownKeys callback */
        args_v = v7_mk_dense_array(v7);
        v7_array_set(v7, args_v, 0, ctx->proxy_ctx->target_obj);

        /* call `ownKeys` callback, and save the result in context */
        V7_TRY2(b_apply(v7, ownKeys_v, V7_UNDEFINED, args_v, 0,
                        &ctx->proxy_ctx->own_keys),
                clean_proxy);

        ctx->proxy_ctx->has_own_keys = 1;
        ctx->proxy_ctx->own_key_idx = 0;

      } else {
        /*
         * No ownKeys callback, so we'll iterate real properties of the target
         * object
         */

        /*
         * TODO(dfrank): add support for the target object which is a proxy as
         * well
         */
        ctx->cur_prop =
            get_object_struct(ctx->proxy_ctx->target_obj)->properties;
      }

      V7_TRY2(v7_get_throwing(v7, ctx->proxy_ctx->handler_obj, "_gpdc", ~0,
                              &ctx->proxy_ctx->get_own_prop_desc),
              clean_proxy);
      if (v7_is_foreign(ctx->proxy_ctx->get_own_prop_desc)) {
        /*
         * C callback for getting property descriptor is provided: will use it
         */
        ctx->proxy_ctx->has_get_own_prop_desc = 1;
        ctx->proxy_ctx->has_get_own_prop_desc_C = 1;
      } else {
        /*
         * No C callback for getting property descriptor is provided, let's
         * check if there is a JS one..
         */
        V7_TRY2(v7_get_throwing(v7, ctx->proxy_ctx->handler_obj,
                                "getOwnPropertyDescriptor", ~0,
                                &ctx->proxy_ctx->get_own_prop_desc),
                clean_proxy);

        if (v7_is_callable(v7, ctx->proxy_ctx->get_own_prop_desc)) {
          /* Yes there is, we'll use it */
          ctx->proxy_ctx->has_get_own_prop_desc = 1;
        }
      }

    clean_proxy:
      v7_disown(v7, &args_v);
      v7_disown(v7, &ownKeys_v);

      if (rcode != V7_OK) {
        /* something went wrong, so, disown values in the context and free it */
        v7_disown(v7, &ctx->proxy_ctx->get_own_prop_desc);
        v7_disown(v7, &ctx->proxy_ctx->own_keys);
        v7_disown(v7, &ctx->proxy_ctx->handler_obj);
        v7_disown(v7, &ctx->proxy_ctx->target_obj);

        free(ctx->proxy_ctx);
        ctx->proxy_ctx = NULL;

        goto clean;
      }
    } else {
#else
    (void) proxy_transp;
#endif

      /* Object is not a proxy: we'll iterate real properties */
      ctx->cur_prop = get_object_struct(obj)->properties;

#if V7_ENABLE__Proxy
    }
#endif
  }

#if V7_ENABLE__Proxy
clean:
#endif
  v7_disown(v7, &obj);
  if (rcode == V7_OK) {
    ctx->init = 1;
  }
  return rcode;
}

void v7_destruct_prop_iter_ctx(struct v7 *v7, struct prop_iter_ctx *ctx) {
  if (ctx->init) {
#if V7_ENABLE__Proxy
    if (ctx->proxy_ctx != NULL) {
      v7_disown(v7, &ctx->proxy_ctx->target_obj);
      v7_disown(v7, &ctx->proxy_ctx->handler_obj);
      v7_disown(v7, &ctx->proxy_ctx->own_keys);
      v7_disown(v7, &ctx->proxy_ctx->get_own_prop_desc);
    }
    free(ctx->proxy_ctx);
    ctx->proxy_ctx = NULL;
#else
    (void) v7;
#endif
    ctx->init = 0;
  }
}

int v7_next_prop(struct v7 *v7, struct prop_iter_ctx *ctx, v7_val_t *name,
                 v7_val_t *value, v7_prop_attr_t *attrs) {
  int ok = 0;
  if (next_prop(v7, ctx, name, value, attrs, &ok) != V7_OK) {
    fprintf(stderr, "next_prop failed\n");
    ok = 0;
  }
  return ok;
}

#if V7_ENABLE__Proxy
WARN_UNUSED_RESULT
static enum v7_err get_custom_prop_desc(struct v7 *v7, v7_val_t name,
                                        struct prop_iter_ctx *ctx,
                                        struct v7_property *res_prop, int *ok) {
  enum v7_err rcode = V7_OK;

  v7_val_t args_v = V7_UNDEFINED;
  v7_val_t desc_v = V7_UNDEFINED;
  v7_val_t tmpflag_v = V7_UNDEFINED;

  v7_own(v7, &name);
  v7_own(v7, &args_v);
  v7_own(v7, &desc_v);
  v7_own(v7, &tmpflag_v);

  *ok = 0;

  if (ctx->proxy_ctx->has_get_own_prop_desc_C) {
    /*
     * There is a C callback which should fill the property descriptor
     * structure, see `v7_get_own_prop_desc_cb_t`
     */
    v7_get_own_prop_desc_cb_t *cb = NULL;
    memset(res_prop, 0, sizeof(*res_prop));
    cb = (v7_get_own_prop_desc_cb_t *) v7_get_ptr(
        v7, ctx->proxy_ctx->get_own_prop_desc);

    res_prop->attributes = 0;
    res_prop->value = V7_UNDEFINED;

    *ok = !!cb(v7, ctx->proxy_ctx->target_obj, name, &res_prop->attributes,
               &res_prop->value);
  } else {
    /* prepare arguments for the getOwnPropertyDescriptor callback */
    args_v = v7_mk_dense_array(v7);
    v7_array_set(v7, args_v, 0, ctx->proxy_ctx->target_obj);
    v7_array_set(v7, args_v, 1, name);

    /* call getOwnPropertyDescriptor callback */
    V7_TRY(b_apply(v7, ctx->proxy_ctx->get_own_prop_desc, V7_UNDEFINED, args_v,
                   0, &desc_v));

    if (v7_is_object(desc_v)) {
      res_prop->attributes = 0;

      V7_TRY(v7_get_throwing(v7, desc_v, "writable", ~0, &tmpflag_v));
      if (!v7_is_truthy(v7, tmpflag_v)) {
        res_prop->attributes |= V7_PROPERTY_NON_WRITABLE;
      }

      V7_TRY(v7_get_throwing(v7, desc_v, "configurable", ~0, &tmpflag_v));
      if (!v7_is_truthy(v7, tmpflag_v)) {
        res_prop->attributes |= V7_PROPERTY_NON_CONFIGURABLE;
      }

      V7_TRY(v7_get_throwing(v7, desc_v, "enumerable", ~0, &tmpflag_v));
      if (!v7_is_truthy(v7, tmpflag_v)) {
        res_prop->attributes |= V7_PROPERTY_NON_ENUMERABLE;
      }

      V7_TRY(v7_get_throwing(v7, desc_v, "value", ~0, &res_prop->value));

      *ok = 1;
    }
  }

  /* We always set the name in the property descriptor to the actual name */
  res_prop->name = name;

clean:
  v7_disown(v7, &tmpflag_v);
  v7_disown(v7, &desc_v);
  v7_disown(v7, &args_v);
  v7_disown(v7, &name);

  return rcode;
}
#endif

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err next_prop(struct v7 *v7, struct prop_iter_ctx *ctx,
                                 v7_val_t *name, v7_val_t *value,
                                 v7_prop_attr_t *attrs, int *ok) {
  enum v7_err rcode = V7_OK;
  struct v7_property p;

  (void) v7;

  memset(&p, 0, sizeof(p));
  p.name = V7_UNDEFINED;
  p.value = V7_UNDEFINED;

  v7_own(v7, &p.name);
  v7_own(v7, &p.value);

  assert(ctx->init);

  *ok = 0;

#if V7_ENABLE__Proxy
  if (ctx->proxy_ctx == NULL || !ctx->proxy_ctx->has_own_keys) {
    /*
     * No `ownKeys` callback, so we'll iterate real properties of the object
     * (either the given object or, if it's a proxy, the proxy's target object)
     */

    if (ctx->cur_prop != NULL) {
      if (ctx->proxy_ctx == NULL || !ctx->proxy_ctx->has_get_own_prop_desc) {
        /*
         * There is no `getOwnPropertyDescriptor` callback, so, use the current
         * real property
         */
        memcpy(&p, ctx->cur_prop, sizeof(p));
        *ok = 1;
      } else {
        /*
         * There is a `getOwnPropertyDescriptor` callback, so call it for the
         * name of the current real property
         */
        V7_TRY(get_custom_prop_desc(v7, ctx->cur_prop->name, ctx, &p, ok));
      }

      ctx->cur_prop = ctx->cur_prop->next;
    }
  } else {
    /* We have custom own keys */
    v7_val_t cur_key = V7_UNDEFINED;
    size_t len = v7_array_length(v7, ctx->proxy_ctx->own_keys);

    v7_own(v7, &cur_key);

    /*
     * Iterate through the custom own keys until we can get the proper property
     * descriptor for the given key
     */
    while (!*ok && (size_t) ctx->proxy_ctx->own_key_idx < len) {
      cur_key = v7_array_get(v7, ctx->proxy_ctx->own_keys,
                             ctx->proxy_ctx->own_key_idx);
      ctx->proxy_ctx->own_key_idx++;

      if (ctx->proxy_ctx->has_get_own_prop_desc) {
        /*
         * There is a `getOwnPropertyDescriptor` callback, so, call it for the
         * current custom key and get all descriptor data from the object
         * returned. The `ok` variable will be updated appropriately (it will
         * be 0 if the callback did not return a proper descriptor)
         */
        V7_TRY2(get_custom_prop_desc(v7, cur_key, ctx, &p, ok), clean_custom);
      } else {
        /*
         * There is no `getOwnPropertyDescriptor` callback, so, try to get
         * real property with the name equal to the current key
         */
        size_t len = 0;
        const char *name = v7_get_string(v7, &cur_key, &len);

        struct v7_property *real_prop =
            v7_get_own_property(v7, ctx->proxy_ctx->target_obj, name, len);
        if (real_prop != NULL) {
          /* Property exists, so use data from its descriptor */
          memcpy(&p, real_prop, sizeof(p));
          *ok = 1;
        }
      }
    }
  clean_custom:
    v7_disown(v7, &cur_key);
    if (rcode != V7_OK) {
      goto clean;
    }
  }

#else
  /*
   * Proxy is disabled: just get the next property
   */
  if (ctx->cur_prop != NULL) {
    memcpy(&p, ctx->cur_prop, sizeof(p));
    *ok = 1;
    ctx->cur_prop = ctx->cur_prop->next;
  }
#endif

  /* If we have a valid property descriptor, use data from it */
  if (*ok) {
    if (name != NULL) *name = p.name;
    if (value != NULL) *value = p.value;
    if (attrs != NULL) *attrs = p.attributes;
  }

#if V7_ENABLE__Proxy
clean:
#endif
  v7_disown(v7, &p.value);
  v7_disown(v7, &p.name);
  return rcode;
}

/* }}} Object properties */

/* Object prototypes {{{ */

V7_PRIVATE int obj_prototype_set(struct v7 *v7, struct v7_object *obj,
                                 struct v7_object *proto) {
  int ret = -1;
  (void) v7;

  if (obj->attributes & V7_OBJ_FUNCTION) {
    ret = -1;
  } else {
    ((struct v7_generic_object *) obj)->prototype = proto;
    ret = 0;
  }

  return ret;
}

V7_PRIVATE struct v7_object *obj_prototype(struct v7 *v7,
                                           struct v7_object *obj) {
  if (obj->attributes & V7_OBJ_FUNCTION) {
    return get_object_struct(v7->vals.function_prototype);
  } else {
    return ((struct v7_generic_object *) obj)->prototype;
  }
}

V7_PRIVATE int is_prototype_of(struct v7 *v7, val_t o, val_t p) {
  if (!v7_is_object(o) || !v7_is_object(p)) {
    return 0;
  }

  /* walk the prototype chain */
  for (; !v7_is_null(o); o = v7_get_proto(v7, o)) {
    if (v7_get_proto(v7, o) == p) {
      return 1;
    }
  }
  return 0;
}

int v7_is_instanceof(struct v7 *v7, val_t o, const char *c) {
  return v7_is_instanceof_v(v7, o, v7_get(v7, v7->vals.global_object, c, ~0));
}

int v7_is_instanceof_v(struct v7 *v7, val_t o, val_t c) {
  return is_prototype_of(v7, o, v7_get(v7, c, "prototype", 9));
}

v7_val_t v7_set_proto(struct v7 *v7, v7_val_t obj, v7_val_t proto) {
  if (v7_is_generic_object(obj)) {
    v7_val_t old_proto =
        v7_object_to_value(obj_prototype(v7, get_object_struct(obj)));
    obj_prototype_set(v7, get_object_struct(obj), get_object_struct(proto));
    return old_proto;
  } else {
    return V7_UNDEFINED;
  }
}

val_t v7_get_proto(struct v7 *v7, val_t obj) {
  /*
   * NOTE: we don't use v7_is_callable() here, because it involves walking
   * through the object's properties, which may be expensive. And it's done
   * anyway for cfunction objects as it would for any other generic objects by
   * the call to `obj_prototype()`.
   *
   * Since this function is called quite often (at least, GC walks the
   * prototype chain), it's better to just handle cfunction objects as generic
   * objects.
   */
  if (is_js_function(obj) || is_cfunction_lite(obj)) {
    return v7->vals.function_prototype;
  }
  return v7_object_to_value(obj_prototype(v7, get_object_struct(obj)));
}

V7_PRIVATE struct v7_property *get_user_data_property(v7_val_t obj) {
  struct v7_property *p;
  struct v7_object *o;
  if (!v7_is_object(obj)) return NULL;
  o = get_object_struct(obj);

  for (p = o->properties; p != NULL; p = p->next) {
    if (p->attributes & _V7_PROPERTY_USER_DATA_AND_DESTRUCTOR) {
      return p;
    }
  }

  return NULL;
}

/*
 * Returns the user data property structure associated with obj, or NULL if
 * `obj` is not an object.
 */
static struct v7_property *get_or_create_user_data_property(struct v7 *v7,
                                                            v7_val_t obj) {
  struct v7_property *p = get_user_data_property(obj);
  struct v7_object *o;

  if (p != NULL) return p;

  if (!v7_is_object(obj)) return NULL;
  o = get_object_struct(obj);
  v7_own(v7, &obj);
  p = v7_mk_property(v7);
  v7_disown(v7, &obj);

  p->attributes |= _V7_PROPERTY_USER_DATA_AND_DESTRUCTOR | _V7_PROPERTY_HIDDEN;

  p->next = o->properties;
  o->properties = p;

  return p;
}

void v7_set_user_data(struct v7 *v7, v7_val_t obj, void *ud) {
  struct v7_property *p = get_or_create_user_data_property(v7, obj);
  if (p == NULL) return;
  p->value = v7_mk_foreign(v7, ud);
}

void *v7_get_user_data(struct v7 *v7, v7_val_t obj) {
  struct v7_property *p = get_user_data_property(obj);
  (void) v7;
  if (p == NULL) return NULL;
  return v7_get_ptr(v7, p->value);
}

void v7_set_destructor_cb(struct v7 *v7, v7_val_t obj, v7_destructor_cb_t *d) {
  struct v7_property *p = get_or_create_user_data_property(v7, obj);
  struct v7_object *o;
  union {
    void *v;
    v7_destructor_cb_t *f;
  } fu;

  if (p == NULL) return;

  o = get_object_struct(obj);
  if (d != NULL) {
    o->attributes |= V7_OBJ_HAS_DESTRUCTOR;
    fu.f = d;
    p->name = v7_mk_foreign(v7, fu.v);
  } else {
    o->attributes &= ~V7_OBJ_HAS_DESTRUCTOR;
    p->name = V7_UNDEFINED;
  }
}

/* }}} Object prototypes */
#ifdef V7_MODULE_LINES
#line 1 "v7/src/regexp.c"
#endif
/*
 * Copyright (c) 2014 Cesanta Software Limited
 * All rights reserved
 */

/* Amalgamated: #include "v7/src/internal.h" */
/* Amalgamated: #include "v7/src/core.h" */
/* Amalgamated: #include "v7/src/primitive.h" */
/* Amalgamated: #include "v7/src/object.h" */
/* Amalgamated: #include "v7/src/regexp.h" */
/* Amalgamated: #include "v7/src/exceptions.h" */
/* Amalgamated: #include "v7/src/string.h" */
/* Amalgamated: #include "v7/src/slre.h" */

#if V7_ENABLE__RegExp
enum v7_err v7_mk_regexp(struct v7 *v7, const char *re, size_t re_len,
                         const char *flags, size_t flags_len, v7_val_t *res) {
  enum v7_err rcode = V7_OK;
  struct slre_prog *p = NULL;
  struct v7_regexp *rp;

  if (re_len == ~((size_t) 0)) re_len = strlen(re);

  if (slre_compile(re, re_len, flags, flags_len, &p, 1) != SLRE_OK ||
      p == NULL) {
    rcode = v7_throwf(v7, TYPE_ERROR, "Invalid regex");
    goto clean;
  } else {
    *res = mk_object(v7, v7->vals.regexp_prototype);
    rp = (struct v7_regexp *) malloc(sizeof(*rp));
    rp->regexp_string = v7_mk_string(v7, re, re_len, 1);
    v7_own(v7, &rp->regexp_string);
    rp->compiled_regexp = p;
    rp->lastIndex = 0;

    v7_def(v7, *res, "", 0, _V7_DESC_HIDDEN(1),
           pointer_to_value(rp) | V7_TAG_REGEXP);
  }

clean:
  return rcode;
}

V7_PRIVATE struct v7_regexp *v7_get_regexp_struct(struct v7 *v7, val_t v) {
  struct v7_property *p;
  int is = v7_is_regexp(v7, v);
  (void) is;
  assert(is == 1);
  /* TODO(mkm): make regexp use user data API */
  p = v7_get_own_property2(v7, v, "", 0, _V7_PROPERTY_HIDDEN);
  assert(p != NULL);
  return (struct v7_regexp *) get_ptr(p->value);
}

int v7_is_regexp(struct v7 *v7, val_t v) {
  struct v7_property *p;
  if (!v7_is_generic_object(v)) return 0;
  /* TODO(mkm): make regexp use user data API */
  p = v7_get_own_property2(v7, v, "", 0, _V7_PROPERTY_HIDDEN);
  if (p == NULL) return 0;
  return (p->value & V7_TAG_MASK) == V7_TAG_REGEXP;
}

V7_PRIVATE size_t
get_regexp_flags_str(struct v7 *v7, struct v7_regexp *rp, char *buf) {
  int re_flags = slre_get_flags(rp->compiled_regexp);
  size_t n = 0;

  (void) v7;
  if (re_flags & SLRE_FLAG_G) buf[n++] = 'g';
  if (re_flags & SLRE_FLAG_I) buf[n++] = 'i';
  if (re_flags & SLRE_FLAG_M) buf[n++] = 'm';

  assert(n <= _V7_REGEXP_MAX_FLAGS_LEN);

  return n;
}

#else /* V7_ENABLE__RegExp */

/*
 * Dummy implementation when RegExp support is disabled: just return 0
 */
int v7_is_regexp(struct v7 *v7, val_t v) {
  (void) v7;
  (void) v;
  return 0;
}

#endif /* V7_ENABLE__RegExp */
#ifdef V7_MODULE_LINES
#line 1 "v7/src/exceptions.c"
#endif
/*
 * Copyright (c) 2014 Cesanta Software Limited
 * All rights reserved
 */

/* Amalgamated: #include "common/str_util.h" */
/* Amalgamated: #include "v7/src/internal.h" */
/* Amalgamated: #include "v7/src/exceptions.h" */
/* Amalgamated: #include "v7/src/array.h" */
/* Amalgamated: #include "v7/src/core.h" */
/* Amalgamated: #include "v7/src/eval.h" */
/* Amalgamated: #include "v7/src/object.h" */

enum v7_err v7_throw(struct v7 *v7, v7_val_t val) {
  v7->vals.thrown_error = val;
  v7->is_thrown = 1;
  return V7_EXEC_EXCEPTION;
}

void v7_clear_thrown_value(struct v7 *v7) {
  v7->vals.thrown_error = V7_UNDEFINED;
  v7->is_thrown = 0;
}

enum v7_err v7_throwf(struct v7 *v7, const char *typ, const char *err_fmt,
                      ...) {
  /* TODO(dfrank) : get rid of v7->error_msg, allocate mem right here */
  enum v7_err rcode = V7_OK;
  va_list ap;
  val_t e = V7_UNDEFINED;
  va_start(ap, err_fmt);
  c_vsnprintf(v7->error_msg, sizeof(v7->error_msg), err_fmt, ap);
  va_end(ap);

  v7_own(v7, &e);
  rcode = create_exception(v7, typ, v7->error_msg, &e);
  if (rcode != V7_OK) {
    goto clean;
  }

  rcode = v7_throw(v7, e);

clean:
  v7_disown(v7, &e);
  return rcode;
}

enum v7_err v7_rethrow(struct v7 *v7) {
  assert(v7->is_thrown);
#ifdef NDEBUG
  (void) v7;
#endif
  return V7_EXEC_EXCEPTION;
}

v7_val_t v7_get_thrown_value(struct v7 *v7, uint8_t *is_thrown) {
  if (is_thrown != NULL) {
    *is_thrown = v7->is_thrown;
  }
  return v7->vals.thrown_error;
}

/*
 * Create an instance of the exception with type `typ` (see `TYPE_ERROR`,
 * `SYNTAX_ERROR`, etc) and message `msg`.
 */
V7_PRIVATE enum v7_err create_exception(struct v7 *v7, const char *typ,
                                        const char *msg, val_t *res) {
  enum v7_err rcode = V7_OK;
  uint8_t saved_creating_exception = v7->creating_exception;
  val_t ctor_args = V7_UNDEFINED, ctor_func = V7_UNDEFINED;
#if 0
  assert(v7_is_undefined(v7->vals.thrown_error));
#endif

  *res = V7_UNDEFINED;

  v7_own(v7, &ctor_args);
  v7_own(v7, &ctor_func);

  if (v7->creating_exception) {
#ifndef NO_LIBC
    fprintf(stderr, "Exception creation throws an exception %s: %s\n", typ,
            msg);
#endif
  } else {
    v7->creating_exception = 1;

    /* Prepare arguments for the `Error` constructor */
    ctor_args = v7_mk_dense_array(v7);
    v7_array_set(v7, ctor_args, 0, v7_mk_string(v7, msg, strlen(msg), 1));

    /* Get constructor for the given error `typ` */
    ctor_func = v7_get(v7, v7->vals.global_object, typ, ~0);
    if (v7_is_undefined(ctor_func)) {
      fprintf(stderr, "cannot find exception %s\n", typ);
    }

    /* Create an error object, with prototype from constructor function */
    *res = mk_object(v7, v7_get(v7, ctor_func, "prototype", 9));

    /*
     * Finally, call the error constructor, passing an error object as `this`
     */
    V7_TRY(b_apply(v7, ctor_func, *res, ctor_args, 0, NULL));
  }

clean:
  v7->creating_exception = saved_creating_exception;

  v7_disown(v7, &ctor_func);
  v7_disown(v7, &ctor_args);

  return rcode;
}
#ifdef V7_MODULE_LINES
#line 1 "v7/src/conversion.c"
#endif
/*
 * Copyright (c) 2014 Cesanta Software Limited
 * All rights reserved
 */

/* Amalgamated: #include "common/cs_strtod.h" */
/* Amalgamated: #include "common/str_util.h" */

/* Amalgamated: #include "v7/src/internal.h" */
/* Amalgamated: #include "v7/src/core.h" */
/* Amalgamated: #include "v7/src/util.h" */
/* Amalgamated: #include "v7/src/primitive.h" */
/* Amalgamated: #include "v7/src/function.h" */
/* Amalgamated: #include "v7/src/conversion.h" */
/* Amalgamated: #include "v7/src/exceptions.h" */
/* Amalgamated: #include "v7/src/eval.h" */
/* Amalgamated: #include "v7/src/gc.h" */
/* Amalgamated: #include "v7/src/array.h" */
/* Amalgamated: #include "v7/src/object.h" */

static void save_val(struct v7 *v7, const char *str, size_t str_len,
                     val_t *dst_v, char *dst, size_t dst_size, int wanted_len,
                     size_t *res_wanted_len) {
  if (dst_v != NULL) {
    *dst_v = v7_mk_string(v7, str, str_len, 1);
  }

  if (dst != NULL && dst_size > 0) {
    size_t size = str_len + 1 /*null-term*/;
    if (size > dst_size) {
      size = dst_size;
    }
    memcpy(dst, str, size);

    /* make sure we have null-term */
    dst[dst_size - 1] = '\0';
  }

  if (res_wanted_len != NULL) {
    *res_wanted_len = (wanted_len >= 0) ? (size_t) wanted_len : str_len;
  }
}

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err primitive_to_str(struct v7 *v7, val_t v, val_t *res,
                                        char *buf, size_t buf_size,
                                        size_t *res_len) {
  enum v7_err rcode = V7_OK;
  char tmp_buf[25];
  double num;
  size_t wanted_len;

  assert(!v7_is_object(v));

  memset(tmp_buf, 0x00, sizeof(tmp_buf));

  v7_own(v7, &v);

  switch (val_type(v7, v)) {
    case V7_TYPE_STRING: {
      /* if `res` provided, set it to source value */
      if (res != NULL) {
        *res = v;
      }

      /* if buf provided, copy string data there */
      if (buf != NULL && buf_size > 0) {
        size_t size;
        const char *str = v7_get_string(v7, &v, &size);
        size += 1 /*null-term*/;

        if (size > buf_size) {
          size = buf_size;
        }

        memcpy(buf, str, size);

        /* make sure we have a null-term */
        buf[buf_size - 1] = '\0';
      }

      if (res_len != NULL) {
        v7_get_string(v7, &v, res_len);
      }

      goto clean;
    }
    case V7_TYPE_NULL:
      strncpy(tmp_buf, "null", sizeof(tmp_buf) - 1);
      save_val(v7, tmp_buf, strlen(tmp_buf), res, buf, buf_size, -1, res_len);
      goto clean;
    case V7_TYPE_UNDEFINED:
      strncpy(tmp_buf, "undefined", sizeof(tmp_buf) - 1);
      save_val(v7, tmp_buf, strlen(tmp_buf), res, buf, buf_size, -1, res_len);
      goto clean;
    case V7_TYPE_BOOLEAN:
      if (v7_get_bool(v7, v)) {
        strncpy(tmp_buf, "true", sizeof(tmp_buf) - 1);
        save_val(v7, tmp_buf, strlen(tmp_buf), res, buf, buf_size, -1, res_len);
        goto clean;
      } else {
        strncpy(tmp_buf, "false", sizeof(tmp_buf) - 1);
        save_val(v7, tmp_buf, strlen(tmp_buf), res, buf, buf_size, -1, res_len);
        goto clean;
      }
    case V7_TYPE_NUMBER:
      if (v == V7_TAG_NAN) {
        strncpy(tmp_buf, "NaN", sizeof(tmp_buf) - 1);
        save_val(v7, tmp_buf, strlen(tmp_buf), res, buf, buf_size, -1, res_len);
        goto clean;
      }
      num = v7_get_double(v7, v);
      if (isinf(num)) {
        if (num < 0.0) {
          strncpy(tmp_buf, "-Infinity", sizeof(tmp_buf) - 1);
        } else {
          strncpy(tmp_buf, "Infinity", sizeof(tmp_buf) - 1);
        }
        save_val(v7, tmp_buf, strlen(tmp_buf), res, buf, buf_size, -1, res_len);
        goto clean;
      }
      {
        const char *fmt = num > 1e10 ? "%.21g" : "%.10g";
        wanted_len = snprintf(tmp_buf, sizeof(tmp_buf), fmt, num);
        save_val(v7, tmp_buf, strlen(tmp_buf), res, buf, buf_size, wanted_len,
                 res_len);
        goto clean;
      }
    case V7_TYPE_CFUNCTION:
#ifdef V7_UNIT_TEST
      wanted_len = c_snprintf(tmp_buf, sizeof(tmp_buf), "cfunc_xxxxxx");
      save_val(v7, tmp_buf, strlen(tmp_buf), res, buf, buf_size, wanted_len,
               res_len);
      goto clean;
#else
      wanted_len = c_snprintf(tmp_buf, sizeof(tmp_buf), "cfunc_%p", get_ptr(v));
      save_val(v7, tmp_buf, strlen(tmp_buf), res, buf, buf_size, wanted_len,
               res_len);
      goto clean;
#endif
    case V7_TYPE_FOREIGN:
      wanted_len = c_snprintf(tmp_buf, sizeof(tmp_buf), "[foreign_%p]",
                              v7_get_ptr(v7, v));
      save_val(v7, tmp_buf, strlen(tmp_buf), res, buf, buf_size, wanted_len,
               res_len);
      goto clean;
    default:
      abort();
  }

clean:

  v7_disown(v7, &v);
  return rcode;
}

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err primitive_to_number(struct v7 *v7, val_t v, val_t *res) {
  enum v7_err rcode = V7_OK;

  assert(!v7_is_object(v));

  *res = v;

  if (v7_is_number(*res)) {
    goto clean;
  }

  if (v7_is_undefined(*res)) {
    *res = V7_TAG_NAN;
    goto clean;
  }

  if (v7_is_null(*res)) {
    *res = v7_mk_number(v7, 0.0);
    goto clean;
  }

  if (v7_is_boolean(*res)) {
    *res = v7_mk_number(v7, !!v7_get_bool(v7, v));
    goto clean;
  }

  if (is_cfunction_lite(*res)) {
    *res = v7_mk_number(v7, 0.0);
    goto clean;
  }

  if (v7_is_string(*res)) {
    double d;
    size_t n;
    char *e, *s = (char *) v7_get_string(v7, res, &n);
    if (n != 0) {
      d = cs_strtod(s, &e);
      if (e - n != s) {
        d = NAN;
      }
    } else {
      /* empty string: convert to 0 */
      d = 0.0;
    }
    *res = v7_mk_number(v7, d);
    goto clean;
  }

  assert(0);

clean:
  return rcode;
}

WARN_UNUSED_RESULT
enum v7_err to_primitive(struct v7 *v7, val_t v, enum to_primitive_hint hint,
                         val_t *res) {
  enum v7_err rcode = V7_OK;
  enum v7_err (*p_func)(struct v7 *v7, val_t v, val_t *res);

  v7_own(v7, &v);

  *res = v;

  /*
   * If given value is an object, try to convert it to string by calling first
   * preferred function (`toString()` or `valueOf()`, depending on the `hint`
   * argument)
   */
  if (v7_is_object(*res)) {
    /* Handle special case for Date object */
    if (hint == V7_TO_PRIMITIVE_HINT_AUTO) {
      hint = (v7_get_proto(v7, *res) == v7->vals.date_prototype)
                 ? V7_TO_PRIMITIVE_HINT_STRING
                 : V7_TO_PRIMITIVE_HINT_NUMBER;
    }

    p_func =
        (hint == V7_TO_PRIMITIVE_HINT_NUMBER) ? obj_value_of : obj_to_string;
    rcode = p_func(v7, *res, res);
    if (rcode != V7_OK) {
      goto clean;
    }

    /*
     * If returned value is still an object, get original argument value
     */
    if (v7_is_object(*res)) {
      *res = v;
    }
  }

  /*
   * If the value is still an object, try to call second function (`valueOf()`
   * or `toString()`)
   */
  if (v7_is_object(*res)) {
    p_func =
        (hint == V7_TO_PRIMITIVE_HINT_NUMBER) ? obj_to_string : obj_value_of;
    rcode = p_func(v7, *res, res);
    if (rcode != V7_OK) {
      goto clean;
    }
  }

  /*
   * If the value is still an object, then throw.
   */
  if (v7_is_object(*res)) {
    rcode =
        v7_throwf(v7, TYPE_ERROR, "Cannot convert object to primitive value");
    goto clean;
  }

clean:
  v7_disown(v7, &v);
  return rcode;
}

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err to_string(struct v7 *v7, val_t v, val_t *res, char *buf,
                                 size_t buf_size, size_t *res_len) {
  enum v7_err rcode = V7_OK;

  v7_own(v7, &v);

  /*
   * Convert value to primitive if needed, calling `toString()` first
   */
  V7_TRY(to_primitive(v7, v, V7_TO_PRIMITIVE_HINT_STRING, &v));

  /*
   * Now, we're guaranteed to have a primitive here. Convert it to string.
   */
  V7_TRY(primitive_to_str(v7, v, res, buf, buf_size, res_len));

clean:
  v7_disown(v7, &v);
  return rcode;
}

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err to_number_v(struct v7 *v7, val_t v, val_t *res) {
  enum v7_err rcode = V7_OK;

  *res = v;

  /*
   * Convert value to primitive if needed, calling `valueOf()` first
   */
  rcode = to_primitive(v7, *res, V7_TO_PRIMITIVE_HINT_NUMBER, res);
  if (rcode != V7_OK) {
    goto clean;
  }

  /*
   * Now, we're guaranteed to have a primitive here. Convert it to number.
   */
  rcode = primitive_to_number(v7, *res, res);
  if (rcode != V7_OK) {
    goto clean;
  }

clean:
  return rcode;
}

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err to_long(struct v7 *v7, val_t v, long default_value,
                               long *res) {
  enum v7_err rcode = V7_OK;
  double d;

  /* if value is `undefined`, just return `default_value` */
  if (v7_is_undefined(v)) {
    *res = default_value;
    goto clean;
  }

  /* Try to convert value to number */
  rcode = to_number_v(v7, v, &v);
  if (rcode != V7_OK) {
    goto clean;
  }

  /*
   * Conversion to number succeeded, so, convert it to long
   */

  d = v7_get_double(v7, v);
  /* We want to return LONG_MAX if d is positive Inf, thus d < 0 check */
  if (isnan(d) || (isinf(d) && d < 0)) {
    *res = 0;
    goto clean;
  } else if (d > LONG_MAX) {
    *res = LONG_MAX;
    goto clean;
  }
  *res = (long) d;
  goto clean;

clean:
  return rcode;
}

V7_PRIVATE enum v7_err obj_value_of(struct v7 *v7, val_t v, val_t *res) {
  enum v7_err rcode = V7_OK;
  val_t func_valueOf = V7_UNDEFINED;

  v7_own(v7, &func_valueOf);
  v7_own(v7, &v);

  /*
   * TODO(dfrank): use `assert(v7_is_object(v))` instead, like `obj_to_string()`
   * does, and fix all callers to ensure it's an object before calling.
   *
   * Or, conversely, make `obj_to_string()` to accept objects.
   */
  if (!v7_is_object(v)) {
    *res = v;
    goto clean;
  }

  V7_TRY(v7_get_throwing(v7, v, "valueOf", 7, &func_valueOf));

  if (v7_is_callable(v7, func_valueOf)) {
    V7_TRY(b_apply(v7, func_valueOf, v, V7_UNDEFINED, 0, res));
  }

clean:
  if (rcode != V7_OK) {
    *res = v;
  }

  v7_disown(v7, &v);
  v7_disown(v7, &func_valueOf);

  return rcode;
}

/*
 * Caller should ensure that `v` is an object
 */
WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err obj_to_string(struct v7 *v7, val_t v, val_t *res) {
  enum v7_err rcode = V7_OK;
  val_t to_string_func = V7_UNDEFINED;

  /* Caller should ensure that `v` is an object */
  assert(v7_is_object(v));

  v7_own(v7, &to_string_func);
  v7_own(v7, &v);

  /*
   * If `toString` is callable, then call it; otherwise, just return source
   * value
   */
  V7_TRY(v7_get_throwing(v7, v, "toString", 8, &to_string_func));
  if (v7_is_callable(v7, to_string_func)) {
    V7_TRY(b_apply(v7, to_string_func, v, V7_UNDEFINED, 0, res));
  } else {
    *res = v;
  }

clean:
  v7_disown(v7, &v);
  v7_disown(v7, &to_string_func);

  return rcode;
}

static const char *hex_digits = "0123456789abcdef";
static char *append_hex(char *buf, char *limit, uint8_t c) {
  if (buf < limit) *buf++ = 'u';
  if (buf < limit) *buf++ = '0';
  if (buf < limit) *buf++ = '0';
  if (buf < limit) *buf++ = hex_digits[(int) ((c >> 4) % 0xf)];
  if (buf < limit) *buf++ = hex_digits[(int) (c & 0xf)];
  return buf;
}

/*
 * Appends quoted s to buf. Any double quote contained in s will be escaped.
 * Returns the number of characters that would have been added,
 * like snprintf.
 * If size is zero it doesn't output anything but keeps counting.
 */
static int snquote(char *buf, size_t size, const char *s, size_t len) {
  char *limit = buf + size - 1;
  const char *end;
  /*
   * String single character escape sequence:
   * http://www.ecma-international.org/ecma-262/6.0/index.html#table-34
   *
   * 0x8 -> \b
   * 0x9 -> \t
   * 0xa -> \n
   * 0xb -> \v
   * 0xc -> \f
   * 0xd -> \r
   */
  const char *specials = "btnvfr";
  size_t i = 0;

  i++;
  if (buf < limit) *buf++ = '"';

  for (end = s + len; s < end; s++) {
    if (*s == '"' || *s == '\\') {
      i++;
      if (buf < limit) *buf++ = '\\';
    } else if (*s >= '\b' && *s <= '\r') {
      i += 2;
      if (buf < limit) *buf++ = '\\';
      if (buf < limit) *buf++ = specials[*s - '\b'];
      continue;
    } else if ((unsigned char) *s < '\b' || (*s > '\r' && *s < ' ')) {
      i += 6 /* \uXXXX */;
      if (buf < limit) *buf++ = '\\';
      buf = append_hex(buf, limit, (uint8_t) *s);
      continue;
    }
    i++;
    if (buf < limit) *buf++ = *s;
  }

  i++;
  if (buf < limit) *buf++ = '"';

  if (size != 0) {
    *buf = '\0';
  }
  return i;
}

/*
 * Returns whether the value of given type should be skipped when generating
 * JSON output
 */
static int should_skip_for_json(enum v7_type type) {
  int ret;
  switch (type) {
    /* All permitted values */
    case V7_TYPE_NULL:
    case V7_TYPE_BOOLEAN:
    case V7_TYPE_BOOLEAN_OBJECT:
    case V7_TYPE_NUMBER:
    case V7_TYPE_NUMBER_OBJECT:
    case V7_TYPE_STRING:
    case V7_TYPE_STRING_OBJECT:
    case V7_TYPE_GENERIC_OBJECT:
    case V7_TYPE_ARRAY_OBJECT:
    case V7_TYPE_DATE_OBJECT:
    case V7_TYPE_REGEXP_OBJECT:
    case V7_TYPE_ERROR_OBJECT:
      ret = 0;
      break;
    default:
      ret = 1;
      break;
  }
  return ret;
}

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err to_json_or_debug(struct v7 *v7, val_t v, char *buf,
                                        size_t size, size_t *res_len,
                                        uint8_t is_debug) {
  val_t el;
  char *vp;
  enum v7_err rcode = V7_OK;
  size_t len = 0;
  struct gc_tmp_frame tf = new_tmp_frame(v7);

  tmp_stack_push(&tf, &v);
  tmp_stack_push(&tf, &el);
  /*
   * TODO(dfrank) : also push all `v7_val_t`s that are declared below
   */

  if (size > 0) *buf = '\0';

  if (!is_debug && should_skip_for_json(val_type(v7, v))) {
    goto clean;
  }

  for (vp = v7->json_visited_stack.buf;
       vp < v7->json_visited_stack.buf + v7->json_visited_stack.len;
       vp += sizeof(val_t)) {
    if (*(val_t *) vp == v) {
      strncpy(buf, "[Circular]", size);
      len = 10;
      goto clean;
    }
  }

  switch (val_type(v7, v)) {
    case V7_TYPE_NULL:
    case V7_TYPE_BOOLEAN:
    case V7_TYPE_NUMBER:
    case V7_TYPE_UNDEFINED:
    case V7_TYPE_CFUNCTION:
    case V7_TYPE_FOREIGN:
      /* For those types, regular `primitive_to_str()` works */
      V7_TRY(primitive_to_str(v7, v, NULL, buf, size, &len));
      goto clean;

    case V7_TYPE_STRING: {
      /*
       * For strings we can't just use `primitive_to_str()`, because we need
       * quoted value
       */
      size_t n;
      const char *str = v7_get_string(v7, &v, &n);
      len = snquote(buf, size, str, n);
      goto clean;
    }

    case V7_TYPE_DATE_OBJECT: {
      v7_val_t func = V7_UNDEFINED, val = V7_UNDEFINED;
      V7_TRY(v7_get_throwing(v7, v, "toString", 8, &func));
#if V7_ENABLE__Date__toJSON
      if (!is_debug) {
        V7_TRY(v7_get_throwing(v7, v, "toJSON", 6, &func));
      }
#endif
      V7_TRY(b_apply(v7, func, v, V7_UNDEFINED, 0, &val));
      V7_TRY(to_json_or_debug(v7, val, buf, size, &len, is_debug));
      goto clean;
    }
    case V7_TYPE_GENERIC_OBJECT:
    case V7_TYPE_BOOLEAN_OBJECT:
    case V7_TYPE_STRING_OBJECT:
    case V7_TYPE_NUMBER_OBJECT:
    case V7_TYPE_REGEXP_OBJECT:
    case V7_TYPE_ERROR_OBJECT: {
      /* TODO(imax): make it return the desired size of the buffer */
      char *b = buf;
      v7_val_t name = V7_UNDEFINED, val = V7_UNDEFINED;
      v7_prop_attr_t attrs = 0;
      const char *pname;
      size_t nlen;
      int ok = 0;
      struct prop_iter_ctx ctx;
      memset(&ctx, 0, sizeof(ctx));

      mbuf_append(&v7->json_visited_stack, (char *) &v, sizeof(v));
      b += c_snprintf(b, BUF_LEFT(size, b - buf), "{");
      V7_TRY2(init_prop_iter_ctx(v7, v, 1 /*proxy-transparent*/, &ctx),
              clean_iter);
      while (1) {
        size_t n;
        const char *s;
        V7_TRY2(next_prop(v7, &ctx, &name, &val, &attrs, &ok), clean_iter);
        if (!ok) {
          break;
        } else if (attrs & (_V7_PROPERTY_HIDDEN | V7_PROPERTY_NON_ENUMERABLE)) {
          continue;
        }
        pname = v7_get_string(v7, &name, &nlen);
        V7_TRY(v7_get_throwing(v7, v, pname, nlen, &val));
        if (!is_debug && should_skip_for_json(val_type(v7, val))) {
          continue;
        }
        if (b - buf != 1) { /* Not the first property to be printed */
          b += c_snprintf(b, BUF_LEFT(size, b - buf), ",");
        }
        s = v7_get_string(v7, &name, &n);
        b += c_snprintf(b, BUF_LEFT(size, b - buf), "\"%.*s\":", (int) n, s);
        {
          size_t tmp = 0;
          V7_TRY2(to_json_or_debug(v7, val, b, BUF_LEFT(size, b - buf), &tmp,
                                   is_debug),
                  clean_iter);
          b += tmp;
        }
      }
      b += c_snprintf(b, BUF_LEFT(size, b - buf), "}");
      v7->json_visited_stack.len -= sizeof(v);

    clean_iter:
      v7_destruct_prop_iter_ctx(v7, &ctx);

      len = b - buf;
      goto clean;
    }
    case V7_TYPE_ARRAY_OBJECT: {
      int has;
      char *b = buf;
      size_t i, alen = v7_array_length(v7, v);
      mbuf_append(&v7->json_visited_stack, (char *) &v, sizeof(v));
      b += c_snprintf(b, BUF_LEFT(size, b - buf), "[");
      for (i = 0; i < alen; i++) {
        el = v7_array_get2(v7, v, i, &has);
        if (has) {
          size_t tmp = 0;
          if (!is_debug && should_skip_for_json(val_type(v7, el))) {
            b += c_snprintf(b, BUF_LEFT(size, b - buf), "null");
          } else {
            V7_TRY(to_json_or_debug(v7, el, b, BUF_LEFT(size, b - buf), &tmp,
                                    is_debug));
          }
          b += tmp;
        }
        if (i != alen - 1) {
          b += c_snprintf(b, BUF_LEFT(size, b - buf), ",");
        }
      }
      b += c_snprintf(b, BUF_LEFT(size, b - buf), "]");
      v7->json_visited_stack.len -= sizeof(v);
      len = b - buf;
      goto clean;
    }
    case V7_TYPE_CFUNCTION_OBJECT:
      V7_TRY(obj_value_of(v7, v, &v));
      len = c_snprintf(buf, size, "Function cfunc_%p", get_ptr(v));
      goto clean;
    case V7_TYPE_FUNCTION_OBJECT:
      V7_TRY(to_string(v7, v, NULL, buf, size, &len));
      goto clean;

    case V7_TYPE_MAX_OBJECT_TYPE:
    case V7_NUM_TYPES:
      abort();
  }

  abort();

  len = 0; /* for compilers that don't know about abort() */
  goto clean;

clean:
  if (rcode != V7_OK) {
    len = 0;
  }
  if (res_len != NULL) {
    *res_len = len;
  }
  tmp_frame_cleanup(&tf);
  return rcode;
}

WARN_UNUSED_RESULT
V7_PRIVATE val_t to_boolean_v(struct v7 *v7, val_t v) {
  size_t len;
  int is_truthy;

  is_truthy = ((v7_is_boolean(v) && v7_get_bool(v7, v)) ||
               (v7_is_number(v) && v7_get_double(v7, v) != 0.0) ||
               (v7_is_string(v) && v7_get_string(v7, &v, &len) && len > 0) ||
               (v7_is_object(v))) &&
              v != V7_TAG_NAN;

  return v7_mk_boolean(v7, is_truthy);
}

/*
 * v7_stringify allocates a new buffer if value representation doesn't fit into
 * buf. Caller is responsible for freeing that buffer.
 */
char *v7_stringify(struct v7 *v7, val_t v, char *buf, size_t size,
                   enum v7_stringify_mode mode) {
  enum v7_err rcode = V7_OK;
  uint8_t saved_is_thrown = 0;
  val_t saved_thrown = v7_get_thrown_value(v7, &saved_is_thrown);
  char *ret = NULL;

  rcode = v7_stringify_throwing(v7, v, buf, size, mode, &ret);
  if (rcode != V7_OK) {
    rcode = V7_OK;
    if (saved_is_thrown) {
      rcode = v7_throw(v7, saved_thrown);
    } else {
      v7_clear_thrown_value(v7);
    }

    buf[0] = '\0';
    ret = buf;
  }

  return ret;
}

enum v7_err v7_stringify_throwing(struct v7 *v7, val_t v, char *buf,
                                  size_t size, enum v7_stringify_mode mode,
                                  char **res) {
  enum v7_err rcode = V7_OK;
  char *p = buf;
  size_t len;

  switch (mode) {
    case V7_STRINGIFY_DEFAULT:
      V7_TRY(to_string(v7, v, NULL, buf, size, &len));
      break;

    case V7_STRINGIFY_JSON:
      V7_TRY(to_json_or_debug(v7, v, buf, size, &len, 0));
      break;

    case V7_STRINGIFY_DEBUG:
      V7_TRY(to_json_or_debug(v7, v, buf, size, &len, 1));
      break;
  }

  /* fit null terminating byte */
  if (len >= size) {
    /* Buffer is not large enough. Allocate a bigger one */
    p = (char *) malloc(len + 1);
    V7_TRY(v7_stringify_throwing(v7, v, p, len + 1, mode, res));
    assert(*res == p);
    goto clean;
  } else {
    *res = p;
    goto clean;
  }

clean:
  /*
   * If we're going to throw, and we allocated a buffer, then free it.
   * But if we don't throw, then the caller will free it.
   */
  if (rcode != V7_OK && p != buf) {
    free(p);
  }
  return rcode;
}

int v7_is_truthy(struct v7 *v7, val_t v) {
  return v7_get_bool(v7, to_boolean_v(v7, v));
}
#ifdef V7_MODULE_LINES
#line 1 "v7/src/shdata.c"
#endif
/*
 * Copyright (c) 2014 Cesanta Software Limited
 * All rights reserved
 */

/* Amalgamated: #include "v7/src/internal.h" */
/* Amalgamated: #include "v7/src/shdata.h" */

#if !defined(V7_DISABLE_FILENAMES) && !defined(V7_DISABLE_LINE_NUMBERS)
V7_PRIVATE struct shdata *shdata_create(const void *payload, size_t size) {
  struct shdata *ret =
      (struct shdata *) calloc(1, sizeof(struct shdata) + size);
  shdata_retain(ret);
  if (payload != NULL) {
    memcpy((char *) shdata_get_payload(ret), (char *) payload, size);
  }
  return ret;
}

V7_PRIVATE struct shdata *shdata_create_from_string(const char *src) {
  return shdata_create(src, strlen(src) + 1 /*null-term*/);
}

V7_PRIVATE void shdata_retain(struct shdata *p) {
  p->refcnt++;
  assert(p->refcnt > 0);
}

V7_PRIVATE void shdata_release(struct shdata *p) {
  assert(p->refcnt > 0);
  p->refcnt--;
  if (p->refcnt == 0) {
    free(p);
  }
}

V7_PRIVATE void *shdata_get_payload(struct shdata *p) {
  return (char *) p + sizeof(*p);
}
#endif
#ifdef V7_MODULE_LINES
#line 1 "v7/src/gc.c"
#endif
/*
 * Copyright (c) 2014 Cesanta Software Limited
 * All rights reserved
 */

/* Amalgamated: #include "v7/src/internal.h" */
/* Amalgamated: #include "v7/src/bcode.h" */
/* Amalgamated: #include "v7/src/varint.h" */
/* Amalgamated: #include "v7/src/gc.h" */
/* Amalgamated: #include "v7/src/freeze.h" */
/* Amalgamated: #include "v7/src/core.h" */
/* Amalgamated: #include "v7/src/function.h" */
/* Amalgamated: #include "v7/src/primitive.h" */
/* Amalgamated: #include "v7/src/object.h" */
/* Amalgamated: #include "v7/src/string.h" */
/* Amalgamated: #include "v7/src/util.h" */
/* Amalgamated: #include "v7/src/primitive.h" */
/* Amalgamated: #include "v7/src/heapusage.h" */

#include <stdio.h>

#ifdef V7_STACK_GUARD_MIN_SIZE
void *v7_sp_limit = NULL;
#endif

void gc_mark_string(struct v7 *, val_t *);

static struct gc_block *gc_new_block(struct gc_arena *a, size_t size);
static void gc_free_block(struct gc_block *b);
static void gc_mark_mbuf_pt(struct v7 *v7, const struct mbuf *mbuf);
static void gc_mark_mbuf_val(struct v7 *v7, const struct mbuf *mbuf);
static void gc_mark_vec_val(struct v7 *v7, const struct v7_vec *vec);

V7_PRIVATE struct v7_generic_object *new_generic_object(struct v7 *v7) {
  return (struct v7_generic_object *) gc_alloc_cell(v7,
                                                    &v7->generic_object_arena);
}

V7_PRIVATE struct v7_property *new_property(struct v7 *v7) {
  return (struct v7_property *) gc_alloc_cell(v7, &v7->property_arena);
}

V7_PRIVATE struct v7_js_function *new_function(struct v7 *v7) {
  return (struct v7_js_function *) gc_alloc_cell(v7, &v7->function_arena);
}

V7_PRIVATE struct gc_tmp_frame new_tmp_frame(struct v7 *v7) {
  struct gc_tmp_frame frame;
  frame.v7 = v7;
  frame.pos = v7->tmp_stack.len;
  return frame;
}

V7_PRIVATE void tmp_frame_cleanup(struct gc_tmp_frame *tf) {
  tf->v7->tmp_stack.len = tf->pos;
}

/*
 * TODO(mkm): perhaps it's safer to keep val_t in the temporary
 * roots stack, instead of keeping val_t*, in order to be better
 * able to debug the relocating GC.
 */
V7_PRIVATE void tmp_stack_push(struct gc_tmp_frame *tf, val_t *vp) {
  mbuf_append(&tf->v7->tmp_stack, (char *) &vp, sizeof(val_t *));
}

/* Initializes a new arena. */
V7_PRIVATE void gc_arena_init(struct gc_arena *a, size_t cell_size,
                              size_t initial_size, size_t size_increment,
                              const char *name) {
  assert(cell_size >= sizeof(uintptr_t));

  memset(a, 0, sizeof(*a));
  a->cell_size = cell_size;
  a->name = name;
  a->size_increment = size_increment;
  a->blocks = gc_new_block(a, initial_size);
}

V7_PRIVATE void gc_arena_destroy(struct v7 *v7, struct gc_arena *a) {
  struct gc_block *b;

  if (a->blocks != NULL) {
    gc_sweep(v7, a, 0);
    for (b = a->blocks; b != NULL;) {
      struct gc_block *tmp;
      tmp = b;
      b = b->next;
      gc_free_block(tmp);
    }
  }
}

static void gc_free_block(struct gc_block *b) {
  free(b->base);
  free(b);
}

static struct gc_block *gc_new_block(struct gc_arena *a, size_t size) {
  struct gc_cell *cur;
  struct gc_block *b;

  heapusage_dont_count(1);
  b = (struct gc_block *) calloc(1, sizeof(*b));
  heapusage_dont_count(0);
  if (b == NULL) abort();

  b->size = size;
  heapusage_dont_count(1);
  b->base = (struct gc_cell *) calloc(a->cell_size, b->size);
  heapusage_dont_count(0);
  if (b->base == NULL) abort();

  for (cur = GC_CELL_OP(a, b->base, +, 0);
       cur < GC_CELL_OP(a, b->base, +, b->size);
       cur = GC_CELL_OP(a, cur, +, 1)) {
    cur->head.link = a->free;
    a->free = cur;
  }

  return b;
}

V7_PRIVATE void *gc_alloc_cell(struct v7 *v7, struct gc_arena *a) {
#if V7_MALLOC_GC
  struct gc_cell *r;
  maybe_gc(v7);
  heapusage_dont_count(1);
  r = (struct gc_cell *) calloc(1, a->cell_size);
  heapusage_dont_count(0);
  mbuf_append(&v7->malloc_trace, &r, sizeof(r));
  return r;
#else
  struct gc_cell *r;
  if (a->free == NULL) {
    if (!maybe_gc(v7)) {
      /* GC is inhibited, so, schedule invocation for later */
      v7->need_gc = 1;
    }

    if (a->free == NULL) {
      struct gc_block *b = gc_new_block(a, a->size_increment);
      b->next = a->blocks;
      a->blocks = b;
    }
  }
  r = a->free;

  UNMARK(r);

  a->free = r->head.link;

#if V7_ENABLE__Memory__stats
  a->allocations++;
  a->alive++;
#endif

  /*
   * TODO(mkm): minor opt possible since most of the fields
   * are overwritten downstream, but not worth the yak shave time
   * when fields are added to GC-able structures */
  memset(r, 0, a->cell_size);
  return (void *) r;
#endif
}

#ifdef V7_MALLOC_GC
/*
 * Scans trough the memory blocks registered in the malloc trace.
 * Free the unmarked ones and reset the mark on the rest.
 */
void gc_sweep_malloc(struct v7 *v7) {
  struct gc_cell **cur;
  for (cur = (struct gc_cell **) v7->malloc_trace.buf;
       cur < (struct gc_cell **) (v7->malloc_trace.buf + v7->malloc_trace.len);
       cur++) {
    if (*cur == NULL) continue;

    if (MARKED(*cur)) {
      UNMARK(*cur);
    } else {
      free(*cur);
      /* TODO(mkm): compact malloc trace buffer */
      *cur = NULL;
    }
  }
}
#endif

/*
 * Scans the arena and add all unmarked cells to the free list.
 *
 * Empty blocks get deallocated. The head of the free list will contais cells
 * from the last (oldest) block. Cells will thus be allocated in block order.
 */
void gc_sweep(struct v7 *v7, struct gc_arena *a, size_t start) {
  struct gc_block *b;
  struct gc_cell *cur;
  struct gc_block **prevp = &a->blocks;
#if V7_ENABLE__Memory__stats
  a->alive = 0;
#endif

  /*
   * Before we sweep, we should mark all free cells in a way that is
   * distinguishable from marked used cells.
   */
  {
    struct gc_cell *next;
    for (cur = a->free; cur != NULL; cur = next) {
      next = cur->head.link;
      MARK_FREE(cur);
    }
  }

  /*
   * We'll rebuild the whole `free` list, so initially we just reset it
   */
  a->free = NULL;

  for (b = a->blocks; b != NULL;) {
    size_t freed_in_block = 0;
    /*
     * if it turns out that this block is 100% garbage
     * we can release the whole block, but the addition
     * of it's cells to the free list has to be undone.
     */
    struct gc_cell *prev_free = a->free;

    for (cur = GC_CELL_OP(a, b->base, +, start);
         cur < GC_CELL_OP(a, b->base, +, b->size);
         cur = GC_CELL_OP(a, cur, +, 1)) {
      if (MARKED(cur)) {
        /* The cell is used and marked  */
        UNMARK(cur);
#if V7_ENABLE__Memory__stats
        a->alive++;
#endif
      } else {
        /*
         * The cell is either:
         * - free
         * - garbage that's about to be freed
         */

        if (MARKED_FREE(cur)) {
          /* The cell is free, so, just unmark it */
          UNMARK_FREE(cur);
        } else {
          /*
           * The cell is used and should be freed: call the destructor and
           * reset the memory
           */
          if (a->destructor != NULL) {
            a->destructor(v7, cur);
          }
          memset(cur, 0, a->cell_size);
        }

        /* Add this cell to the `free` list */
        cur->head.link = a->free;
        a->free = cur;
        freed_in_block++;
#if V7_ENABLE__Memory__stats
        a->garbage++;
#endif
      }
    }

    /*
     * don't free the initial block, which is at the tail
     * because it has a special size aimed at reducing waste
     * and simplifying initial startup. TODO(mkm): improve
     * */
    if (b->next != NULL && freed_in_block == b->size) {
      *prevp = b->next;
      gc_free_block(b);
      b = *prevp;
      a->free = prev_free;
    } else {
      prevp = &b->next;
      b = b->next;
    }
  }
}

/*
 * dense arrays contain only one property pointing to an mbuf with array values.
 */
V7_PRIVATE void gc_mark_dense_array(struct v7 *v7,
                                    struct v7_generic_object *obj) {
  val_t v;
  struct mbuf *mbuf;
  val_t *vp;

#if 0
  /* TODO(mkm): use this when dense array promotion is implemented */
  v = obj->properties->value;
#else
  v = v7_get(v7, v7_object_to_value(&obj->base), "", 0);
#endif

  mbuf = (struct mbuf *) v7_get_ptr(v7, v);

  /* function scope pointer is aliased to the object's prototype pointer */
  gc_mark(v7, v7_object_to_value(obj_prototype(v7, &obj->base)));
  MARK(obj);

  if (mbuf == NULL) return;
  for (vp = (val_t *) mbuf->buf; (char *) vp < mbuf->buf + mbuf->len; vp++) {
    gc_mark(v7, *vp);
    gc_mark_string(v7, vp);
  }
  UNMARK(obj);
}

V7_PRIVATE void gc_mark(struct v7 *v7, val_t v) {
  struct v7_object *obj_base;
  struct v7_property *prop;
  struct v7_property *next;

  if (!v7_is_object(v)) {
    return;
  }
  obj_base = get_object_struct(v);

  /*
   * we ignore objects that are not managed by V7 heap, such as frozen
   * objects, especially when on flash.
   */
  if (obj_base->attributes & V7_OBJ_OFF_HEAP) {
    return;
  }

  /*
   * we treat all object like things like objects but they might be functions,
   * gc_gheck_val checks the appropriate arena per actual value type.
   */
  if (!gc_check_val(v7, v)) {
    abort();
  }

  if (MARKED(obj_base)) return;

#ifdef V7_FREEZE
  if (v7->freeze_file != NULL) {
    freeze_obj(v7, v7->freeze_file, v);
  }
#endif

  if (obj_base->attributes & V7_OBJ_DENSE_ARRAY) {
    struct v7_generic_object *obj = get_generic_object_struct(v);
    gc_mark_dense_array(v7, obj);
  }

  /* mark object itself, and its properties */
  for ((prop = obj_base->properties), MARK(obj_base); prop != NULL;
       prop = next) {
    if (prop->attributes & _V7_PROPERTY_OFF_HEAP) {
      break;
    }

    if (!gc_check_ptr(&v7->property_arena, prop)) {
      abort();
    }

#ifdef V7_FREEZE
    if (v7->freeze_file != NULL) {
      freeze_prop(v7, v7->freeze_file, prop);
    }
#endif

    gc_mark_string(v7, &prop->value);
    gc_mark_string(v7, &prop->name);
    gc_mark(v7, prop->value);

    next = prop->next;
    MARK(prop);
  }

  /* mark object's prototype */
  gc_mark(v7, v7_get_proto(v7, v));

  if (is_js_function(v)) {
    struct v7_js_function *func = get_js_function_struct(v);

    /* mark function's scope */
    gc_mark(v7, v7_object_to_value(&func->scope->base));

    if (func->bcode != NULL) {
      gc_mark_vec_val(v7, &func->bcode->lit);
    }
  }
}

#if V7_ENABLE__Memory__stats

V7_PRIVATE size_t gc_arena_size(struct gc_arena *a) {
  size_t size = 0;
  struct gc_block *b;
  for (b = a->blocks; b != NULL; b = b->next) {
    size += b->size;
  }
  return size;
}

/*
 * TODO(dfrank): move to core
 */
int v7_heap_stat(struct v7 *v7, enum v7_heap_stat_what what) {
  switch (what) {
    case V7_HEAP_STAT_HEAP_SIZE:
      return gc_arena_size(&v7->generic_object_arena) *
                 v7->generic_object_arena.cell_size +
             gc_arena_size(&v7->function_arena) * v7->function_arena.cell_size +
             gc_arena_size(&v7->property_arena) * v7->property_arena.cell_size;
    case V7_HEAP_STAT_HEAP_USED:
      return v7->generic_object_arena.alive *
                 v7->generic_object_arena.cell_size +
             v7->function_arena.alive * v7->function_arena.cell_size +
             v7->property_arena.alive * v7->property_arena.cell_size;
    case V7_HEAP_STAT_STRING_HEAP_RESERVED:
      return v7->owned_strings.size;
    case V7_HEAP_STAT_STRING_HEAP_USED:
      return v7->owned_strings.len;
    case V7_HEAP_STAT_OBJ_HEAP_MAX:
      return gc_arena_size(&v7->generic_object_arena);
    case V7_HEAP_STAT_OBJ_HEAP_FREE:
      return gc_arena_size(&v7->generic_object_arena) -
             v7->generic_object_arena.alive;
    case V7_HEAP_STAT_OBJ_HEAP_CELL_SIZE:
      return v7->generic_object_arena.cell_size;
    case V7_HEAP_STAT_FUNC_HEAP_MAX:
      return gc_arena_size(&v7->function_arena);
    case V7_HEAP_STAT_FUNC_HEAP_FREE:
      return gc_arena_size(&v7->function_arena) - v7->function_arena.alive;
    case V7_HEAP_STAT_FUNC_HEAP_CELL_SIZE:
      return v7->function_arena.cell_size;
    case V7_HEAP_STAT_PROP_HEAP_MAX:
      return gc_arena_size(&v7->property_arena);
    case V7_HEAP_STAT_PROP_HEAP_FREE:
      return gc_arena_size(&v7->property_arena) - v7->property_arena.alive;
    case V7_HEAP_STAT_PROP_HEAP_CELL_SIZE:
      return v7->property_arena.cell_size;
    case V7_HEAP_STAT_FUNC_AST_SIZE:
      return v7->function_arena_ast_size;
    case V7_HEAP_STAT_BCODE_OPS_SIZE:
      return v7->bcode_ops_size;
    case V7_HEAP_STAT_BCODE_LIT_TOTAL_SIZE:
      return v7->bcode_lit_total_size;
    case V7_HEAP_STAT_BCODE_LIT_DESER_SIZE:
      return v7->bcode_lit_deser_size;
    case V7_HEAP_STAT_FUNC_OWNED:
      return v7->owned_values.len / sizeof(val_t *);
    case V7_HEAP_STAT_FUNC_OWNED_MAX:
      return v7->owned_values.size / sizeof(val_t *);
  }

  return -1;
}
#endif

V7_PRIVATE void gc_dump_arena_stats(const char *msg, struct gc_arena *a) {
  (void) msg;
  (void) a;
#ifndef NO_LIBC
#if V7_ENABLE__Memory__stats
  if (a->verbose) {
    fprintf(stderr, "%s: total allocations %lu, max %lu, alive %lu\n", msg,
            (long unsigned int) a->allocations,
            (long unsigned int) gc_arena_size(a), (long unsigned int) a->alive);
  }
#endif
#endif
}

V7_PRIVATE uint64_t gc_string_val_to_offset(val_t v) {
  return (((uint64_t)(uintptr_t) get_ptr(v)) & ~V7_TAG_MASK)
#ifndef V7_DISABLE_STR_ALLOC_SEQ
         & 0xFFFFFFFF
#endif
      ;
}

V7_PRIVATE val_t gc_string_val_from_offset(uint64_t s) {
  return s | V7_TAG_STRING_O;
}

#ifndef V7_DISABLE_STR_ALLOC_SEQ

static uint16_t next_asn(struct v7 *v7) {
  if (v7->gc_next_asn == 0xFFFF) {
    /* Wrap around explicitly. */
    v7->gc_next_asn = 0;
    return 0xFFFF;
  }
  return v7->gc_next_asn++;
}

uint16_t gc_next_allocation_seqn(struct v7 *v7, const char *str, size_t len) {
  uint16_t asn = next_asn(v7);
  (void) str;
  (void) len;
#ifdef V7_GC_VERBOSE
  /*
   * ESP SDK printf cannot cope with null strings
   * as created by s_concat.
   */
  if (str == NULL) {
    fprintf(stderr, "GC ASN %d: <nil>\n", asn);
  } else {
    fprintf(stderr, "GC ASN %d: \"%.*s\"\n", asn, (int) len, str);
  }
#endif
#ifdef V7_GC_PANIC_ON_ASN
  if (asn == (V7_GC_PANIC_ON_ASN)) {
    abort();
  }
#endif
  return asn;
}

int gc_is_valid_allocation_seqn(struct v7 *v7, uint16_t n) {
  /*
   * This functions attempts to handle integer wraparound in a naive way and
   * will give false positives when more than 65536 strings are allocated
   * between GC runs.
   */
  int r = (n >= v7->gc_min_asn && n < v7->gc_next_asn) ||
          (v7->gc_min_asn > v7->gc_next_asn &&
           (n >= v7->gc_min_asn || n < v7->gc_next_asn));
  if (!r) {
    fprintf(stderr, "GC ASN %d is not in [%d,%d)\n", n, v7->gc_min_asn,
            v7->gc_next_asn);
  }
  return r;
}

void gc_check_valid_allocation_seqn(struct v7 *v7, uint16_t n) {
  if (!gc_is_valid_allocation_seqn(v7, n)) {
/*
 * TODO(dfrank) throw exception if V7_GC_ASN_PANIC is not defined.
 */
#if 0 && !defined(V7_GC_ASN_PANIC)
    throw_exception(v7, INTERNAL_ERROR, "Invalid ASN: %d", (int) n);
#else
    fprintf(stderr, "Invalid ASN: %d\n", (int) n);
    abort();
#endif
  }
}

#endif /* V7_DISABLE_STR_ALLOC_SEQ */

/* Mark a string value */
void gc_mark_string(struct v7 *v7, val_t *v) {
  val_t h, tmp = 0;
  char *s;

  /* clang-format off */

  /*
   * If a value points to an unmarked string we shall:
   *  1. save the first 6 bytes of the string
   *     since we need to be able to distinguish real values from
   *     the saved first 6 bytes of the string, we need to tag the chunk
   *     as V7_TAG_STRING_C
   *  2. encode value's address (v) into the first 6 bytes of the string.
   *  3. put the saved 8 bytes (tag + chunk) back into the value.
   *  4. mark the string by putting '\1' in the NUL terminator of the previous
   *     string chunk.
   *
   * If a value points to an already marked string we shall:
   *     (0, <6 bytes of a pointer to a val_t>), hence we have to skip
   *     the first byte. We tag the value pointer as a V7_TAG_FOREIGN
   *     so that it won't be followed during recursive mark.
   *
   *  ... the rest is the same
   *
   *  Note: 64-bit pointers can be represented with 48-bits
   */

  /* clang-format on */

  if ((*v & V7_TAG_MASK) != V7_TAG_STRING_O) {
    return;
  }

#ifdef V7_FREEZE
  if (v7->freeze_file != NULL) {
    return;
  }
#endif

#ifdef V7_GC_VERBOSE
  {
    uint16_t asn = (*v >> 32) & 0xFFFF;
    size_t size;
    fprintf(stderr, "GC marking ASN %d: '%s'\n", asn,
            v7_get_string(v7, v, &size));
  }
#endif

#ifndef V7_DISABLE_STR_ALLOC_SEQ
  gc_check_valid_allocation_seqn(v7, (*v >> 32) & 0xFFFF);
#endif

  s = v7->owned_strings.buf + gc_string_val_to_offset(*v);
  assert(s < v7->owned_strings.buf + v7->owned_strings.len);
  if (s[-1] == '\0') {
    memcpy(&tmp, s, sizeof(tmp) - 2);
    tmp |= V7_TAG_STRING_C;
  } else {
    memcpy(&tmp, s, sizeof(tmp) - 2);
    tmp |= V7_TAG_FOREIGN;
  }

  h = (val_t)(uintptr_t) v;
  s[-1] = 1;
  memcpy(s, &h, sizeof(h) - 2);
  memcpy(v, &tmp, sizeof(tmp));
}

void gc_compact_strings(struct v7 *v7) {
  char *p = v7->owned_strings.buf + 1;
  uint64_t h, next, head = 1;
  int len, llen;

#ifndef V7_DISABLE_STR_ALLOC_SEQ
  v7->gc_min_asn = v7->gc_next_asn;
#endif
  while (p < v7->owned_strings.buf + v7->owned_strings.len) {
    if (p[-1] == '\1') {
#ifndef V7_DISABLE_STR_ALLOC_SEQ
      /* Not using gc_next_allocation_seqn() as we don't have full string. */
      uint16_t asn = next_asn(v7);
#endif
      /* relocate and update ptrs */
      h = 0;
      memcpy(&h, p, sizeof(h) - 2);

      /*
       * relocate pointers until we find the tail.
       * The tail is marked with V7_TAG_STRING_C,
       * while val_t link pointers are tagged with V7_TAG_FOREIGN
       */
      for (; (h & V7_TAG_MASK) != V7_TAG_STRING_C; h = next) {
        h &= ~V7_TAG_MASK;
        memcpy(&next, (char *) (uintptr_t) h, sizeof(h));

        *(val_t *) (uintptr_t) h = gc_string_val_from_offset(head)
#ifndef V7_DISABLE_STR_ALLOC_SEQ
                                   | ((val_t) asn << 32)
#endif
            ;
      }
      h &= ~V7_TAG_MASK;

      /*
       * the tail contains the first 6 bytes we stole from
       * the actual string.
       */
      len = decode_varint((unsigned char *) &h, &llen);
      len += llen + 1;

      /*
       * restore the saved 6 bytes
       * TODO(mkm): think about endianness
       */
      memcpy(p, &h, sizeof(h) - 2);

      /*
       * and relocate the string data by packing it to the left.
       */
      memmove(v7->owned_strings.buf + head, p, len);
      v7->owned_strings.buf[head - 1] = 0x0;
#if defined(V7_GC_VERBOSE) && !defined(V7_DISABLE_STR_ALLOC_SEQ)
      fprintf(stderr, "GC updated ASN %d: \"%.*s\"\n", asn, len - llen - 1,
              v7->owned_strings.buf + head + llen);
#endif
      p += len;
      head += len;
    } else {
      len = decode_varint((unsigned char *) p, &llen);
      len += llen + 1;

      p += len;
    }
  }

#if defined(V7_GC_VERBOSE) && !defined(V7_DISABLE_STR_ALLOC_SEQ)
  fprintf(stderr, "GC valid ASN range: [%d,%d)\n", v7->gc_min_asn,
          v7->gc_next_asn);
#endif

  v7->owned_strings.len = head;
}

void gc_dump_owned_strings(struct v7 *v7) {
  size_t i;
  for (i = 0; i < v7->owned_strings.len; i++) {
    if (isprint((unsigned char) v7->owned_strings.buf[i])) {
      fputc(v7->owned_strings.buf[i], stderr);
    } else {
      fputc('.', stderr);
    }
  }
  fputc('\n', stderr);
}

/*
 * builting on gcc, tried out by redefining it.
 * Using null pointer as base can trigger undefined behavior, hence
 * a portable workaround that involves a valid yet dummy pointer.
 * It's meant to be used as a contant expression.
 */
#ifndef offsetof
#define offsetof(st, m) (((ptrdiff_t)(&((st *) 32)->m)) - 32)
#endif

V7_PRIVATE void compute_need_gc(struct v7 *v7) {
  struct mbuf *m = &v7->owned_strings;
  if ((double) m->len / (double) m->size > 0.9) {
    v7->need_gc = 1;
  }
  /* TODO(mkm): check free heap */
}

V7_PRIVATE int maybe_gc(struct v7 *v7) {
  if (!v7->inhibit_gc) {
    v7_gc(v7, 0);
    return 1;
  }
  return 0;
}
#if defined(V7_GC_VERBOSE)
static int gc_pass = 0;
#endif

/*
 * mark an array of `val_t` values (*not pointers* to them)
 */
static void gc_mark_val_array(struct v7 *v7, val_t *vals, size_t len) {
  val_t *vp;
  for (vp = vals; vp < vals + len; vp++) {
    gc_mark(v7, *vp);
    gc_mark_string(v7, vp);
  }
}

/*
 * mark an mbuf containing *pointers* to `val_t` values
 */
static void gc_mark_mbuf_pt(struct v7 *v7, const struct mbuf *mbuf) {
  val_t **vp;
  for (vp = (val_t **) mbuf->buf; (char *) vp < mbuf->buf + mbuf->len; vp++) {
    gc_mark(v7, **vp);
    gc_mark_string(v7, *vp);
  }
}

/*
 * mark an mbuf containing `val_t` values (*not pointers* to them)
 */
static void gc_mark_mbuf_val(struct v7 *v7, const struct mbuf *mbuf) {
  gc_mark_val_array(v7, (val_t *) mbuf->buf, mbuf->len / sizeof(val_t));
}

/*
 * mark a vector containing `val_t` values (*not pointers* to them)
 */
static void gc_mark_vec_val(struct v7 *v7, const struct v7_vec *vec) {
  gc_mark_val_array(v7, (val_t *) vec->p, vec->len / sizeof(val_t));
}

/*
 * mark an mbuf containing foreign pointers to `struct bcode`
 */
static void gc_mark_mbuf_bcode_pt(struct v7 *v7, const struct mbuf *mbuf) {
  struct bcode **vp;
  for (vp = (struct bcode **) mbuf->buf; (char *) vp < mbuf->buf + mbuf->len;
       vp++) {
    gc_mark_vec_val(v7, &(*vp)->lit);
  }
}

static void gc_mark_call_stack_private(
    struct v7 *v7, struct v7_call_frame_private *call_stack) {
  gc_mark_val_array(v7, (val_t *) &call_stack->vals,
                    sizeof(call_stack->vals) / sizeof(val_t));
}

static void gc_mark_call_stack_cfunc(struct v7 *v7,
                                     struct v7_call_frame_cfunc *call_stack) {
  gc_mark_val_array(v7, (val_t *) &call_stack->vals,
                    sizeof(call_stack->vals) / sizeof(val_t));
}

static void gc_mark_call_stack_bcode(struct v7 *v7,
                                     struct v7_call_frame_bcode *call_stack) {
  gc_mark_val_array(v7, (val_t *) &call_stack->vals,
                    sizeof(call_stack->vals) / sizeof(val_t));
}

/*
 * mark `struct v7_call_frame` and all its back-linked frames
 */
static void gc_mark_call_stack(struct v7 *v7,
                               struct v7_call_frame_base *call_stack) {
  while (call_stack != NULL) {
    if (call_stack->type_mask & V7_CALL_FRAME_MASK_BCODE) {
      gc_mark_call_stack_bcode(v7, (struct v7_call_frame_bcode *) call_stack);
    }

    if (call_stack->type_mask & V7_CALL_FRAME_MASK_PRIVATE) {
      gc_mark_call_stack_private(v7,
                                 (struct v7_call_frame_private *) call_stack);
    }

    if (call_stack->type_mask & V7_CALL_FRAME_MASK_CFUNC) {
      gc_mark_call_stack_cfunc(v7, (struct v7_call_frame_cfunc *) call_stack);
    }

    call_stack = call_stack->prev;
  }
}

/* Perform garbage collection */
void v7_gc(struct v7 *v7, int full) {
#ifdef V7_DISABLE_GC
  (void) v7;
  (void) full;
  return;
#else

#if defined(V7_GC_VERBOSE)
  fprintf(stderr, "V7 GC pass %d\n", ++gc_pass);
#endif

  gc_dump_arena_stats("Before GC objects", &v7->generic_object_arena);
  gc_dump_arena_stats("Before GC functions", &v7->function_arena);
  gc_dump_arena_stats("Before GC properties", &v7->property_arena);

  gc_mark_call_stack(v7, v7->call_stack);

  gc_mark_val_array(v7, (val_t *) &v7->vals, sizeof(v7->vals) / sizeof(val_t));
  /* mark all items on bcode stack */
  gc_mark_mbuf_val(v7, &v7->stack);

  /* mark literals and names of all the active bcodes */
  gc_mark_mbuf_bcode_pt(v7, &v7->act_bcodes);

  gc_mark_mbuf_pt(v7, &v7->tmp_stack);
  gc_mark_mbuf_pt(v7, &v7->owned_values);

  gc_compact_strings(v7);

#ifdef V7_MALLOC_GC
  gc_sweep_malloc(v7);
#else
  gc_sweep(v7, &v7->generic_object_arena, 0);
  gc_sweep(v7, &v7->function_arena, 0);
  gc_sweep(v7, &v7->property_arena, 0);
#endif

  gc_dump_arena_stats("After GC objects", &v7->generic_object_arena);
  gc_dump_arena_stats("After GC functions", &v7->function_arena);
  gc_dump_arena_stats("After GC properties", &v7->property_arena);

  if (full) {
    /*
     * In case of full GC, we also resize strings buffer, but we still leave
     * some extra space (at most, `_V7_STRING_BUF_RESERVE`) in order to avoid
     * frequent reallocations
     */
    size_t trimmed_size = v7->owned_strings.len + _V7_STRING_BUF_RESERVE;
    if (trimmed_size < v7->owned_strings.size) {
      heapusage_dont_count(1);
      mbuf_resize(&v7->owned_strings, trimmed_size);
      heapusage_dont_count(0);
    }
  }
#endif /* V7_DISABLE_GC */
}

V7_PRIVATE int gc_check_val(struct v7 *v7, val_t v) {
  if (is_js_function(v)) {
    return gc_check_ptr(&v7->function_arena, get_js_function_struct(v));
  } else if (v7_is_object(v)) {
    return gc_check_ptr(&v7->generic_object_arena, get_object_struct(v));
  }
  return 1;
}

V7_PRIVATE int gc_check_ptr(const struct gc_arena *a, const void *ptr) {
#ifdef V7_MALLOC_GC
  (void) a;
  (void) ptr;
  return 1;
#else
  const struct gc_cell *p = (const struct gc_cell *) ptr;
  struct gc_block *b;
  for (b = a->blocks; b != NULL; b = b->next) {
    if (p >= b->base && p < GC_CELL_OP(a, b->base, +, b->size)) {
      return 1;
    }
  }
  return 0;
#endif
}
#ifdef V7_MODULE_LINES
#line 1 "v7/src/freeze.c"
#endif
/*
 * Copyright (c) 2014 Cesanta Software Limited
 * All rights reserved
 */

/* Amalgamated: #include "v7/src/core.h" */
/* Amalgamated: #include "v7/src/function.h" */
/* Amalgamated: #include "v7/src/util.h" */
/* Amalgamated: #include "v7/src/freeze.h" */
/* Amalgamated: #include "v7/src/bcode.h" */
/* Amalgamated: #include "v7/src/gc.h" */
/* Amalgamated: #include "common/base64.h" */
/* Amalgamated: #include "v7/src/object.h" */

#include <stdio.h>

#ifdef V7_FREEZE

V7_PRIVATE void freeze(struct v7 *v7, char *filename) {
  size_t i;

  v7->freeze_file = fopen(filename, "w");
  assert(v7->freeze_file != NULL);

#ifndef V7_FREEZE_NOT_READONLY
  /*
   * We have to remove `global` from the global object since
   * when thawing global will actually be a new mutable object
   * living on the heap.
   */
  v7_del(v7, v7->vals.global_object, "global", 6);
#endif

  for (i = 0; i < sizeof(v7->vals) / sizeof(val_t); i++) {
    val_t v = ((val_t *) &v7->vals)[i];
    fprintf(v7->freeze_file,
            "{\"type\":\"global\", \"idx\":%zu, \"value\":\"%p\"}\n", i,
            (void *) (v7_is_object(v) ? get_object_struct(v) : 0x0));
  }

  /*
   * since v7->freeze_file is not NULL this will cause freeze_obj and
   * freeze_prop to be called for each reachable object and property.
   */
  v7_gc(v7, 1);
  assert(v7->stack.len == 0);

  fclose(v7->freeze_file);
  v7->freeze_file = NULL;
}

static char *freeze_vec(struct v7_vec *vec) {
  char *res = (char *) malloc(512 + vec->len);
  res[0] = '"';
  cs_base64_encode((const unsigned char *) vec->p, vec->len, &res[1]);
  strcat(res, "\"");
  return res;
}

V7_PRIVATE void freeze_obj(struct v7 *v7, FILE *f, v7_val_t v) {
  struct v7_object *obj_base = get_object_struct(v);
  unsigned int attrs = V7_OBJ_OFF_HEAP;

#ifndef V7_FREEZE_NOT_READONLY
  attrs |= V7_OBJ_NOT_EXTENSIBLE;
#endif

  if (is_js_function(v)) {
    struct v7_js_function *func = get_js_function_struct(v);
    struct bcode *bcode = func->bcode;
    char *jops = freeze_vec(&bcode->ops);
    int i;

    fprintf(f,
            "{\"type\":\"func\", \"addr\":\"%p\", \"props\":\"%p\", "
            "\"attrs\":%d, \"scope\":\"%p\", \"bcode\":\"%p\""
#if defined(V7_ENABLE_ENTITY_IDS)
            ", \"entity_id_base\":%d, \"entity_id_spec\":\"%d\" "
#endif
            "}\n",
            (void *) obj_base,
            (void *) ((uintptr_t) obj_base->properties & ~0x1),
            obj_base->attributes | attrs, (void *) func->scope, (void *) bcode
#if defined(V7_ENABLE_ENTITY_IDS)
            ,
            obj_base->entity_id_base, obj_base->entity_id_spec
#endif
            );
    fprintf(f,
            "{\"type\":\"bcode\", \"addr\":\"%p\", \"args_cnt\":%d, "
            "\"names_cnt\":%d, "
            "\"strict_mode\": %d, \"func_name_present\": %d, \"ops\":%s, "
            "\"lit\": [",
            (void *) bcode, bcode->args_cnt, bcode->names_cnt,
            bcode->strict_mode, bcode->func_name_present, jops);

    for (i = 0; (size_t) i < bcode->lit.len / sizeof(val_t); i++) {
      val_t v = ((val_t *) bcode->lit.p)[i];
      const char *str;

      if (((v & V7_TAG_MASK) == V7_TAG_STRING_O ||
           (v & V7_TAG_MASK) == V7_TAG_STRING_F) &&
          (str = v7_get_cstring(v7, &v)) != NULL) {
        fprintf(f, "{\"str\": \"%s\"}", str);
      } else {
        fprintf(f, "{\"val\": \"0x%" INT64_X_FMT "\"}", v);
      }
      if ((size_t) i != bcode->lit.len / sizeof(val_t) - 1) {
        fprintf(f, ",");
      }
    }

    fprintf(f, "]}\n");
    free(jops);
  } else {
    struct v7_generic_object *gob = get_generic_object_struct(v);
    fprintf(f,
            "{\"type\":\"obj\", \"addr\":\"%p\", \"props\":\"%p\", "
            "\"attrs\":%d, \"proto\":\"%p\""
#if defined(V7_ENABLE_ENTITY_IDS)
            ", \"entity_id_base\":%d, \"entity_id_spec\":\"%d\" "
#endif
            "}\n",
            (void *) obj_base,
            (void *) ((uintptr_t) obj_base->properties & ~0x1),
            obj_base->attributes | attrs, (void *) gob->prototype
#if defined(V7_ENABLE_ENTITY_IDS)
            ,
            obj_base->entity_id_base, obj_base->entity_id_spec
#endif
            );
  }
}

V7_PRIVATE void freeze_prop(struct v7 *v7, FILE *f, struct v7_property *prop) {
  unsigned int attrs = _V7_PROPERTY_OFF_HEAP;
#ifndef V7_FREEZE_NOT_READONLY
  attrs |= V7_PROPERTY_NON_WRITABLE | V7_PROPERTY_NON_CONFIGURABLE;
#endif

  fprintf(f,
          "{\"type\":\"prop\","
          " \"addr\":\"%p\","
          " \"next\":\"%p\","
          " \"attrs\":%d,"
          " \"name\":\"0x%" INT64_X_FMT
          "\","
          " \"value_type\":%d,"
          " \"value\":\"0x%" INT64_X_FMT
          "\","
          " \"name_str\":\"%s\""
#if defined(V7_ENABLE_ENTITY_IDS)
          ", \"entity_id\":\"%d\""
#endif
          "}\n",
          (void *) prop, (void *) prop->next, prop->attributes | attrs,
          prop->name, val_type(v7, prop->value), prop->value,
          v7_get_cstring(v7, &prop->name)
#if defined(V7_ENABLE_ENTITY_IDS)
              ,
          prop->entity_id
#endif
          );
}

#endif
#ifdef V7_MODULE_LINES
#line 1 "v7/src/parser.c"
#endif
/*
 * Copyright (c) 2014 Cesanta Software Limited
 * All rights reserved
 */

/* Amalgamated: #include "common/coroutine.h" */
/* Amalgamated: #include "v7/src/internal.h" */
/* Amalgamated: #include "v7/src/parser.h" */
/* Amalgamated: #include "v7/src/tokenizer.h" */
/* Amalgamated: #include "v7/src/core.h" */
/* Amalgamated: #include "v7/src/exceptions.h" */
/* Amalgamated: #include "v7/src/ast.h" */
/* Amalgamated: #include "v7/src/primitive.h" */
/* Amalgamated: #include "v7/src/cyg_profile.h" */

#if !defined(V7_NO_COMPILER)

#define ACCEPT(t) (((v7)->cur_tok == (t)) ? next_tok((v7)), 1 : 0)

#define EXPECT(t)                            \
  do {                                       \
    if ((v7)->cur_tok != (t)) {              \
      CR_THROW(PARSER_EXC_ID__SYNTAX_ERROR); \
    }                                        \
    next_tok(v7);                            \
  } while (0)

#define PARSE_WITH_OPT_ARG(tag, arg_tag, arg_parser, label) \
  do {                                                      \
    if (end_of_statement(v7) == V7_OK) {                    \
      add_node(v7, a, (tag));                               \
    } else {                                                \
      add_node(v7, a, (arg_tag));                           \
      arg_parser(label);                                    \
    }                                                       \
  } while (0)

#define N (CR_ARG_RET_PT()->arg)

/*
 * User functions
 * (as well as other in-function entry points)
 */
enum my_fid {
  fid_none = CR_FID__NONE,

  /* parse_script function */
  fid_parse_script = CR_FID__USER,
  fid_p_script_1,
  fid_p_script_2,
  fid_p_script_3,
  fid_p_script_4,

  /* parse_use_strict function */
  fid_parse_use_strict,

  /* parse_body function */
  fid_parse_body,
  fid_p_body_1,
  fid_p_body_2,

  /* parse_statement function */
  fid_parse_statement,
  fid_p_stat_1,
  fid_p_stat_2,
  fid_p_stat_3,
  fid_p_stat_4,
  fid_p_stat_5,
  fid_p_stat_6,
  fid_p_stat_7,
  fid_p_stat_8,
  fid_p_stat_9,
  fid_p_stat_10,
  fid_p_stat_11,
  fid_p_stat_12,
  fid_p_stat_13,
  fid_p_stat_14,

  /* parse_expression function */
  fid_parse_expression,
  fid_p_expr_1,

  /* parse_assign function */
  fid_parse_assign,
  fid_p_assign_1,

  /* parse_binary function */
  fid_parse_binary,
  fid_p_binary_1,
  fid_p_binary_2,
  fid_p_binary_3,
  fid_p_binary_4,
  fid_p_binary_5,
  fid_p_binary_6,

  /* parse_prefix function */
  fid_parse_prefix,
  fid_p_prefix_1,

  /* parse_postfix function */
  fid_parse_postfix,
  fid_p_postfix_1,

  /* parse_callexpr function */
  fid_parse_callexpr,
  fid_p_callexpr_1,
  fid_p_callexpr_2,
  fid_p_callexpr_3,

  /* parse_newexpr function */
  fid_parse_newexpr,
  fid_p_newexpr_1,
  fid_p_newexpr_2,
  fid_p_newexpr_3,
  fid_p_newexpr_4,

  /* parse_terminal function */
  fid_parse_terminal,
  fid_p_terminal_1,
  fid_p_terminal_2,
  fid_p_terminal_3,
  fid_p_terminal_4,

  /* parse_block function */
  fid_parse_block,
  fid_p_block_1,

  /* parse_if function */
  fid_parse_if,
  fid_p_if_1,
  fid_p_if_2,
  fid_p_if_3,

  /* parse_while function */
  fid_parse_while,
  fid_p_while_1,
  fid_p_while_2,

  /* parse_ident function */
  fid_parse_ident,

  /* parse_ident_allow_reserved_words function */
  fid_parse_ident_allow_reserved_words,
  fid_p_ident_arw_1,

  /* parse_funcdecl function */
  fid_parse_funcdecl,
  fid_p_funcdecl_1,
  fid_p_funcdecl_2,
  fid_p_funcdecl_3,
  fid_p_funcdecl_4,
  fid_p_funcdecl_5,
  fid_p_funcdecl_6,
  fid_p_funcdecl_7,
  fid_p_funcdecl_8,
  fid_p_funcdecl_9,

  /* parse_arglist function */
  fid_parse_arglist,
  fid_p_arglist_1,

  /* parse_member function */
  fid_parse_member,
  fid_p_member_1,

  /* parse_memberexpr function */
  fid_parse_memberexpr,
  fid_p_memberexpr_1,
  fid_p_memberexpr_2,

  /* parse_var function */
  fid_parse_var,
  fid_p_var_1,

  /* parse_prop function */
  fid_parse_prop,
#ifdef V7_ENABLE_JS_GETTERS
  fid_p_prop_1_getter,
#endif
  fid_p_prop_2,
#ifdef V7_ENABLE_JS_SETTERS
  fid_p_prop_3_setter,
#endif
  fid_p_prop_4,

  /* parse_dowhile function */
  fid_parse_dowhile,
  fid_p_dowhile_1,
  fid_p_dowhile_2,

  /* parse_for function */
  fid_parse_for,
  fid_p_for_1,
  fid_p_for_2,
  fid_p_for_3,
  fid_p_for_4,
  fid_p_for_5,
  fid_p_for_6,

  /* parse_try function */
  fid_parse_try,
  fid_p_try_1,
  fid_p_try_2,
  fid_p_try_3,
  fid_p_try_4,

  /* parse_switch function */
  fid_parse_switch,
  fid_p_switch_1,
  fid_p_switch_2,
  fid_p_switch_3,
  fid_p_switch_4,

  /* parse_with function */
  fid_parse_with,
  fid_p_with_1,
  fid_p_with_2,

  MY_FID_CNT
};

/*
 * User exception IDs. The first one should have value `CR_EXC_ID__USER`
 */
enum parser_exc_id {
  PARSER_EXC_ID__NONE = CR_EXC_ID__NONE,
  PARSER_EXC_ID__SYNTAX_ERROR = CR_EXC_ID__USER,
};

/* structures with locals and args {{{ */

/* parse_script {{{ */

/* parse_script's arguments */
#if 0
typedef struct fid_parse_script_arg {
} fid_parse_script_arg_t;
#else
typedef cr_zero_size_type_t fid_parse_script_arg_t;
#endif

/* parse_script's data on stack */
typedef struct fid_parse_script_locals {
#if 0
  struct fid_parse_script_arg arg;
#endif

  ast_off_t start;
  ast_off_t outer_last_var_node;
  int saved_in_strict;
} fid_parse_script_locals_t;

/* }}} */

/* parse_use_strict {{{ */
/* parse_use_strict's arguments */
#if 0
typedef struct fid_parse_use_strict_arg {
} fid_parse_use_strict_arg_t;
#else
typedef cr_zero_size_type_t fid_parse_use_strict_arg_t;
#endif

/* parse_use_strict's data on stack */
#if 0
typedef struct fid_parse_use_strict_locals {
  struct fid_parse_use_strict_arg arg;
} fid_parse_use_strict_locals_t;
#else
typedef cr_zero_size_type_t fid_parse_use_strict_locals_t;
#endif

#define CALL_PARSE_USE_STRICT(_label)      \
  do {                                     \
    CR_CALL(fid_parse_use_strict, _label); \
  } while (0)

/* }}} */

/* parse_body {{{ */
/* parse_body's arguments */
typedef struct fid_parse_body_arg { enum v7_tok end; } fid_parse_body_arg_t;

/* parse_body's data on stack */
typedef struct fid_parse_body_locals {
  struct fid_parse_body_arg arg;

  ast_off_t start;
} fid_parse_body_locals_t;

#define CALL_PARSE_BODY(_end, _label) \
  do {                                \
    N.fid_parse_body.end = (_end);    \
    CR_CALL(fid_parse_body, _label);  \
  } while (0)
/* }}} */

/* parse_statement {{{ */
/* parse_statement's arguments */
#if 0
typedef struct fid_parse_statement_arg {
} fid_parse_statement_arg_t;
#else
typedef cr_zero_size_type_t fid_parse_statement_arg_t;
#endif

/* parse_statement's data on stack */
#if 0
typedef struct fid_parse_statement_locals {
  struct fid_parse_statement_arg arg;
} fid_parse_statement_locals_t;
#else
typedef cr_zero_size_type_t fid_parse_statement_locals_t;
#endif

#define CALL_PARSE_STATEMENT(_label)      \
  do {                                    \
    CR_CALL(fid_parse_statement, _label); \
  } while (0)
/* }}} */

/* parse_expression {{{ */
/* parse_expression's arguments */
#if 0
typedef struct fid_parse_expression_arg {
} fid_parse_expression_arg_t;
#else
typedef cr_zero_size_type_t fid_parse_expression_arg_t;
#endif

/* parse_expression's data on stack */
typedef struct fid_parse_expression_locals {
#if 0
  struct fid_parse_expression_arg arg;
#endif

  ast_off_t pos;
  int group;
} fid_parse_expression_locals_t;

#define CALL_PARSE_EXPRESSION(_label)      \
  do {                                     \
    CR_CALL(fid_parse_expression, _label); \
  } while (0)
/* }}} */

/* parse_assign {{{ */
/* parse_assign's arguments */
#if 0
typedef struct fid_parse_assign_arg {
} fid_parse_assign_arg_t;
#else
typedef cr_zero_size_type_t fid_parse_assign_arg_t;
#endif

/* parse_assign's data on stack */
#if 0
typedef struct fid_parse_assign_locals {
  struct fid_parse_assign_arg arg;
} fid_parse_assign_locals_t;
#else
typedef cr_zero_size_type_t fid_parse_assign_locals_t;
#endif

#define CALL_PARSE_ASSIGN(_label)      \
  do {                                 \
    CR_CALL(fid_parse_assign, _label); \
  } while (0)
/* }}} */

/* parse_binary {{{ */
/* parse_binary's arguments */
typedef struct fid_parse_binary_arg {
  ast_off_t pos;
  uint8_t min_level;
} fid_parse_binary_arg_t;

/* parse_binary's data on stack */
typedef struct fid_parse_binary_locals {
  struct fid_parse_binary_arg arg;

  uint8_t i;
  /* during iteration, it becomes negative, so should be signed */
  int8_t level;
  uint8_t /*enum v7_tok*/ tok;
  uint8_t /*enum ast_tag*/ ast;
  ast_off_t saved_mbuf_len;
} fid_parse_binary_locals_t;

#define CALL_PARSE_BINARY(_level, _pos, _label) \
  do {                                          \
    N.fid_parse_binary.min_level = (_level);    \
    N.fid_parse_binary.pos = (_pos);            \
    CR_CALL(fid_parse_binary, _label);          \
  } while (0)
/* }}} */

/* parse_prefix {{{ */
/* parse_prefix's arguments */
#if 0
typedef struct fid_parse_prefix_arg {
} fid_parse_prefix_arg_t;
#else
typedef cr_zero_size_type_t fid_parse_prefix_arg_t;
#endif

/* parse_prefix's data on stack */
#if 0
typedef struct fid_parse_prefix_locals {
  struct fid_parse_prefix_arg arg;
} fid_parse_prefix_locals_t;
#else
typedef cr_zero_size_type_t fid_parse_prefix_locals_t;
#endif

#define CALL_PARSE_PREFIX(_label)      \
  do {                                 \
    CR_CALL(fid_parse_prefix, _label); \
  } while (0)
/* }}} */

/* parse_postfix {{{ */
/* parse_postfix's arguments */
#if 0
typedef struct fid_parse_postfix_arg {
} fid_parse_postfix_arg_t;
#else
typedef cr_zero_size_type_t fid_parse_postfix_arg_t;
#endif

/* parse_postfix's data on stack */
typedef struct fid_parse_postfix_locals {
#if 0
  struct fid_parse_postfix_arg arg;
#endif

  ast_off_t pos;
} fid_parse_postfix_locals_t;

#define CALL_PARSE_POSTFIX(_label)      \
  do {                                  \
    CR_CALL(fid_parse_postfix, _label); \
  } while (0)
/* }}} */

/* parse_callexpr {{{ */
/* parse_callexpr's arguments */
#if 0
typedef struct fid_parse_callexpr_arg {
} fid_parse_callexpr_arg_t;
#else
typedef cr_zero_size_type_t fid_parse_callexpr_arg_t;
#endif

/* parse_callexpr's data on stack */
typedef struct fid_parse_callexpr_locals {
#if 0
  struct fid_parse_callexpr_arg arg;
#endif

  ast_off_t pos;
} fid_parse_callexpr_locals_t;

#define CALL_PARSE_CALLEXPR(_label)      \
  do {                                   \
    CR_CALL(fid_parse_callexpr, _label); \
  } while (0)
/* }}} */

/* parse_newexpr {{{ */
/* parse_newexpr's arguments */
#if 0
typedef struct fid_parse_newexpr_arg {
} fid_parse_newexpr_arg_t;
#else
typedef cr_zero_size_type_t fid_parse_newexpr_arg_t;
#endif

/* parse_newexpr's data on stack */
typedef struct fid_parse_newexpr_locals {
#if 0
  struct fid_parse_newexpr_arg arg;
#endif

  ast_off_t start;
} fid_parse_newexpr_locals_t;

#define CALL_PARSE_NEWEXPR(_label)      \
  do {                                  \
    CR_CALL(fid_parse_newexpr, _label); \
  } while (0)
/* }}} */

/* parse_terminal {{{ */
/* parse_terminal's arguments */
#if 0
typedef struct fid_parse_terminal_arg {
} fid_parse_terminal_arg_t;
#else
typedef cr_zero_size_type_t fid_parse_terminal_arg_t;
#endif

/* parse_terminal's data on stack */
typedef struct fid_parse_terminal_locals {
#if 0
  struct fid_parse_terminal_arg arg;
#endif

  ast_off_t start;
} fid_parse_terminal_locals_t;

#define CALL_PARSE_TERMINAL(_label)      \
  do {                                   \
    CR_CALL(fid_parse_terminal, _label); \
  } while (0)
/* }}} */

/* parse_block {{{ */
/* parse_block's arguments */
#if 0
typedef struct fid_parse_block_arg {
} fid_parse_block_arg_t;
#else
typedef cr_zero_size_type_t fid_parse_block_arg_t;
#endif

/* parse_block's data on stack */
#if 0
typedef struct fid_parse_block_locals {
  struct fid_parse_block_arg arg;
} fid_parse_block_locals_t;
#else
typedef cr_zero_size_type_t fid_parse_block_locals_t;
#endif

#define CALL_PARSE_BLOCK(_label)      \
  do {                                \
    CR_CALL(fid_parse_block, _label); \
  } while (0)
/* }}} */

/* parse_if {{{ */
/* parse_if's arguments */
#if 0
typedef struct fid_parse_if_arg {
} fid_parse_if_arg_t;
#else
typedef cr_zero_size_type_t fid_parse_if_arg_t;
#endif

/* parse_if's data on stack */
typedef struct fid_parse_if_locals {
#if 0
  struct fid_parse_if_arg arg;
#endif

  ast_off_t start;
} fid_parse_if_locals_t;

#define CALL_PARSE_IF(_label)      \
  do {                             \
    CR_CALL(fid_parse_if, _label); \
  } while (0)
/* }}} */

/* parse_while {{{ */
/* parse_while's arguments */
#if 0
typedef struct fid_parse_while_arg {
} fid_parse_while_arg_t;
#else
typedef cr_zero_size_type_t fid_parse_while_arg_t;
#endif

/* parse_while's data on stack */
typedef struct fid_parse_while_locals {
#if 0
  struct fid_parse_while_arg arg;
#endif

  ast_off_t start;
  uint8_t saved_in_loop;
} fid_parse_while_locals_t;

#define CALL_PARSE_WHILE(_label)      \
  do {                                \
    CR_CALL(fid_parse_while, _label); \
  } while (0)
/* }}} */

/* parse_ident {{{ */
/* parse_ident's arguments */
#if 0
typedef struct fid_parse_ident_arg {
} fid_parse_ident_arg_t;
#else
typedef cr_zero_size_type_t fid_parse_ident_arg_t;
#endif

/* parse_ident's data on stack */
#if 0
typedef struct fid_parse_ident_locals {
  struct fid_parse_ident_arg arg;
} fid_parse_ident_locals_t;
#else
typedef cr_zero_size_type_t fid_parse_ident_locals_t;
#endif

#define CALL_PARSE_IDENT(_label)      \
  do {                                \
    CR_CALL(fid_parse_ident, _label); \
  } while (0)
/* }}} */

/* parse_ident_allow_reserved_words {{{ */
/* parse_ident_allow_reserved_words's arguments */
#if 0
typedef struct fid_parse_ident_allow_reserved_words_arg {
} fid_parse_ident_allow_reserved_words_arg_t;
#else
typedef cr_zero_size_type_t fid_parse_ident_allow_reserved_words_arg_t;
#endif

/* parse_ident_allow_reserved_words's data on stack */
#if 0
typedef struct fid_parse_ident_allow_reserved_words_locals {
  struct fid_parse_ident_allow_reserved_words_arg arg;
} fid_parse_ident_allow_reserved_words_locals_t;
#else
typedef cr_zero_size_type_t fid_parse_ident_allow_reserved_words_locals_t;
#endif

#define CALL_PARSE_IDENT_ALLOW_RESERVED_WORDS(_label)      \
  do {                                                     \
    CR_CALL(fid_parse_ident_allow_reserved_words, _label); \
  } while (0)
/* }}} */

/* parse_funcdecl {{{ */
/* parse_funcdecl's arguments */
typedef struct fid_parse_funcdecl_arg {
  uint8_t require_named;
  uint8_t reserved_name;
} fid_parse_funcdecl_arg_t;

/* parse_funcdecl's data on stack */
typedef struct fid_parse_funcdecl_locals {
  struct fid_parse_funcdecl_arg arg;

  ast_off_t start;
  ast_off_t outer_last_var_node;
  uint8_t saved_in_function;
  uint8_t saved_in_strict;
} fid_parse_funcdecl_locals_t;

#define CALL_PARSE_FUNCDECL(_require_named, _reserved_name, _label) \
  do {                                                              \
    N.fid_parse_funcdecl.require_named = (_require_named);          \
    N.fid_parse_funcdecl.reserved_name = (_reserved_name);          \
    CR_CALL(fid_parse_funcdecl, _label);                            \
  } while (0)
/* }}} */

/* parse_arglist {{{ */
/* parse_arglist's arguments */
#if 0
typedef struct fid_parse_arglist_arg {
} fid_parse_arglist_arg_t;
#else
typedef cr_zero_size_type_t fid_parse_arglist_arg_t;
#endif

/* parse_arglist's data on stack */
#if 0
typedef struct fid_parse_arglist_locals {
  struct fid_parse_arglist_arg arg;
} fid_parse_arglist_locals_t;
#else
typedef cr_zero_size_type_t fid_parse_arglist_locals_t;
#endif

#define CALL_PARSE_ARGLIST(_label)      \
  do {                                  \
    CR_CALL(fid_parse_arglist, _label); \
  } while (0)
/* }}} */

/* parse_member {{{ */
/* parse_member's arguments */
typedef struct fid_parse_member_arg { ast_off_t pos; } fid_parse_member_arg_t;

/* parse_member's data on stack */
typedef struct fid_parse_member_locals {
  struct fid_parse_member_arg arg;
} fid_parse_member_locals_t;

#define CALL_PARSE_MEMBER(_pos, _label) \
  do {                                  \
    N.fid_parse_member.pos = (_pos);    \
    CR_CALL(fid_parse_member, _label);  \
  } while (0)
/* }}} */

/* parse_memberexpr {{{ */
/* parse_memberexpr's arguments */
#if 0
typedef struct fid_parse_memberexpr_arg {
} fid_parse_memberexpr_arg_t;
#else
typedef cr_zero_size_type_t fid_parse_memberexpr_arg_t;
#endif

/* parse_memberexpr's data on stack */
typedef struct fid_parse_memberexpr_locals {
#if 0
  struct fid_parse_memberexpr_arg arg;
#endif

  ast_off_t pos;
} fid_parse_memberexpr_locals_t;

#define CALL_PARSE_MEMBEREXPR(_label)      \
  do {                                     \
    CR_CALL(fid_parse_memberexpr, _label); \
  } while (0)
/* }}} */

/* parse_var {{{ */
/* parse_var's arguments */
#if 0
typedef struct fid_parse_var_arg {
} fid_parse_var_arg_t;
#else
typedef cr_zero_size_type_t fid_parse_var_arg_t;
#endif

/* parse_var's data on stack */
typedef struct fid_parse_var_locals {
#if 0
  struct fid_parse_var_arg arg;
#endif

  ast_off_t start;
} fid_parse_var_locals_t;

#define CALL_PARSE_VAR(_label)      \
  do {                              \
    CR_CALL(fid_parse_var, _label); \
  } while (0)
/* }}} */

/* parse_prop {{{ */
/* parse_prop's arguments */
#if 0
typedef struct fid_parse_prop_arg {
} fid_parse_prop_arg_t;
#else
typedef cr_zero_size_type_t fid_parse_prop_arg_t;
#endif

/* parse_prop's data on stack */
#if 0
typedef struct fid_parse_prop_locals {
  struct fid_parse_prop_arg arg;
} fid_parse_prop_locals_t;
#else
typedef cr_zero_size_type_t fid_parse_prop_locals_t;
#endif

#define CALL_PARSE_PROP(_label)      \
  do {                               \
    CR_CALL(fid_parse_prop, _label); \
  } while (0)
/* }}} */

/* parse_dowhile {{{ */
/* parse_dowhile's arguments */
#if 0
typedef struct fid_parse_dowhile_arg {
} fid_parse_dowhile_arg_t;
#else
typedef cr_zero_size_type_t fid_parse_dowhile_arg_t;
#endif

/* parse_dowhile's data on stack */
typedef struct fid_parse_dowhile_locals {
#if 0
  struct fid_parse_dowhile_arg arg;
#endif

  ast_off_t start;
  uint8_t saved_in_loop;
} fid_parse_dowhile_locals_t;

#define CALL_PARSE_DOWHILE(_label)      \
  do {                                  \
    CR_CALL(fid_parse_dowhile, _label); \
  } while (0)
/* }}} */

/* parse_for {{{ */
/* parse_for's arguments */
#if 0
typedef struct fid_parse_for_arg {
} fid_parse_for_arg_t;
#else
typedef cr_zero_size_type_t fid_parse_for_arg_t;
#endif

/* parse_for's data on stack */
typedef struct fid_parse_for_locals {
#if 0
  struct fid_parse_for_arg arg;
#endif

  ast_off_t start;
  uint8_t saved_in_loop;
} fid_parse_for_locals_t;

#define CALL_PARSE_FOR(_label)      \
  do {                              \
    CR_CALL(fid_parse_for, _label); \
  } while (0)
/* }}} */

/* parse_try {{{ */
/* parse_try's arguments */
#if 0
typedef struct fid_parse_try_arg {
} fid_parse_try_arg_t;
#else
typedef cr_zero_size_type_t fid_parse_try_arg_t;
#endif

/* parse_try's data on stack */
typedef struct fid_parse_try_locals {
#if 0
  struct fid_parse_try_arg arg;
#endif

  ast_off_t start;
  uint8_t catch_or_finally;
} fid_parse_try_locals_t;

#define CALL_PARSE_TRY(_label)      \
  do {                              \
    CR_CALL(fid_parse_try, _label); \
  } while (0)
/* }}} */

/* parse_switch {{{ */
/* parse_switch's arguments */
#if 0
typedef struct fid_parse_switch_arg {
} fid_parse_switch_arg_t;
#else
typedef cr_zero_size_type_t fid_parse_switch_arg_t;
#endif

/* parse_switch's data on stack */
typedef struct fid_parse_switch_locals {
#if 0
  struct fid_parse_switch_arg arg;
#endif

  ast_off_t start;
  int saved_in_switch;
  ast_off_t case_start;
} fid_parse_switch_locals_t;

#define CALL_PARSE_SWITCH(_label)      \
  do {                                 \
    CR_CALL(fid_parse_switch, _label); \
  } while (0)
/* }}} */

/* parse_with {{{ */
/* parse_with's arguments */
#if 0
typedef struct fid_parse_with_arg {
} fid_parse_with_arg_t;
#else
typedef cr_zero_size_type_t fid_parse_with_arg_t;
#endif

/* parse_with's data on stack */
typedef struct fid_parse_with_locals {
#if 0
  struct fid_parse_with_arg arg;
#endif

  ast_off_t start;
} fid_parse_with_locals_t;

#define CALL_PARSE_WITH(_label)      \
  do {                               \
    CR_CALL(fid_parse_with, _label); \
  } while (0)
/* }}} */

/* }}} */

/*
 * Array of "function" descriptors. Each descriptor contains just a size
 * of "function"'s locals.
 */
static const struct cr_func_desc _fid_descrs[MY_FID_CNT] = {

    /* fid_none */
    {0},

    /* fid_parse_script ----------------------------------------- */
    /* fid_parse_script */
    {CR_LOCALS_SIZEOF(fid_parse_script_locals_t)},
    /* fid_p_script_1 */
    {CR_LOCALS_SIZEOF(fid_parse_script_locals_t)},
    /* fid_p_script_2 */
    {CR_LOCALS_SIZEOF(fid_parse_script_locals_t)},
    /* fid_p_script_3 */
    {CR_LOCALS_SIZEOF(fid_parse_script_locals_t)},
    /* fid_p_script_4 */
    {CR_LOCALS_SIZEOF(fid_parse_script_locals_t)},

    /* fid_parse_use_strict ----------------------------------------- */
    /* fid_parse_use_strict */
    {CR_LOCALS_SIZEOF(fid_parse_use_strict_locals_t)},

    /* fid_parse_body ----------------------------------------- */
    /* fid_parse_body */
    {CR_LOCALS_SIZEOF(fid_parse_body_locals_t)},
    /* fid_p_body_1 */
    {CR_LOCALS_SIZEOF(fid_parse_body_locals_t)},
    /* fid_p_body_2 */
    {CR_LOCALS_SIZEOF(fid_parse_body_locals_t)},

    /* fid_parse_statement ----------------------------------------- */
    /* fid_parse_statement */
    {CR_LOCALS_SIZEOF(fid_parse_statement_locals_t)},
    /* fid_p_stat_1 */
    {CR_LOCALS_SIZEOF(fid_parse_statement_locals_t)},
    /* fid_p_stat_2 */
    {CR_LOCALS_SIZEOF(fid_parse_statement_locals_t)},
    /* fid_p_stat_3 */
    {CR_LOCALS_SIZEOF(fid_parse_statement_locals_t)},
    /* fid_p_stat_4 */
    {CR_LOCALS_SIZEOF(fid_parse_statement_locals_t)},
    /* fid_p_stat_5 */
    {CR_LOCALS_SIZEOF(fid_parse_statement_locals_t)},
    /* fid_p_stat_6 */
    {CR_LOCALS_SIZEOF(fid_parse_statement_locals_t)},
    /* fid_p_stat_7 */
    {CR_LOCALS_SIZEOF(fid_parse_statement_locals_t)},
    /* fid_p_stat_8 */
    {CR_LOCALS_SIZEOF(fid_parse_statement_locals_t)},
    /* fid_p_stat_9 */
    {CR_LOCALS_SIZEOF(fid_parse_statement_locals_t)},
    /* fid_p_stat_10 */
    {CR_LOCALS_SIZEOF(fid_parse_statement_locals_t)},
    /* fid_p_stat_11 */
    {CR_LOCALS_SIZEOF(fid_parse_statement_locals_t)},
    /* fid_p_stat_12 */
    {CR_LOCALS_SIZEOF(fid_parse_statement_locals_t)},
    /* fid_p_stat_13 */
    {CR_LOCALS_SIZEOF(fid_parse_statement_locals_t)},
    /* fid_p_stat_14 */
    {CR_LOCALS_SIZEOF(fid_parse_statement_locals_t)},

    /* fid_parse_expression ----------------------------------------- */
    /* fid_parse_expression */
    {CR_LOCALS_SIZEOF(fid_parse_expression_locals_t)},
    /* fid_p_expr_1 */
    {CR_LOCALS_SIZEOF(fid_parse_expression_locals_t)},

    /* fid_parse_assign ----------------------------------------- */
    /* fid_parse_assign */
    {CR_LOCALS_SIZEOF(fid_parse_assign_locals_t)},
    /* fid_p_assign_1 */
    {CR_LOCALS_SIZEOF(fid_parse_assign_locals_t)},

    /* fid_parse_binary ----------------------------------------- */
    /* fid_parse_binary */
    {CR_LOCALS_SIZEOF(fid_parse_binary_locals_t)},
    /* fid_p_binary_1 */
    {CR_LOCALS_SIZEOF(fid_parse_binary_locals_t)},
    /* fid_p_binary_2 */
    {CR_LOCALS_SIZEOF(fid_parse_binary_locals_t)},
    /* fid_p_binary_3 */
    {CR_LOCALS_SIZEOF(fid_parse_binary_locals_t)},
    /* fid_p_binary_4 */
    {CR_LOCALS_SIZEOF(fid_parse_binary_locals_t)},
    /* fid_p_binary_5 */
    {CR_LOCALS_SIZEOF(fid_parse_binary_locals_t)},
    /* fid_p_binary_6 */
    {CR_LOCALS_SIZEOF(fid_parse_binary_locals_t)},

    /* fid_parse_prefix ----------------------------------------- */
    /* fid_parse_prefix */
    {CR_LOCALS_SIZEOF(fid_parse_prefix_locals_t)},
    /* fid_p_prefix_1 */
    {CR_LOCALS_SIZEOF(fid_parse_prefix_locals_t)},

    /* fid_parse_postfix ----------------------------------------- */
    /* fid_parse_postfix */
    {CR_LOCALS_SIZEOF(fid_parse_postfix_locals_t)},
    /* fid_p_postfix_1 */
    {CR_LOCALS_SIZEOF(fid_parse_postfix_locals_t)},

    /* fid_parse_callexpr ----------------------------------------- */
    /* fid_parse_callexpr */
    {CR_LOCALS_SIZEOF(fid_parse_callexpr_locals_t)},
    /* fid_p_callexpr_1 */
    {CR_LOCALS_SIZEOF(fid_parse_callexpr_locals_t)},
    /* fid_p_callexpr_2 */
    {CR_LOCALS_SIZEOF(fid_parse_callexpr_locals_t)},
    /* fid_p_callexpr_3 */
    {CR_LOCALS_SIZEOF(fid_parse_callexpr_locals_t)},

    /* fid_parse_newexpr ----------------------------------------- */
    /* fid_parse_newexpr */
    {CR_LOCALS_SIZEOF(fid_parse_newexpr_locals_t)},
    /* fid_p_newexpr_1 */
    {CR_LOCALS_SIZEOF(fid_parse_newexpr_locals_t)},
    /* fid_p_newexpr_2 */
    {CR_LOCALS_SIZEOF(fid_parse_newexpr_locals_t)},
    /* fid_p_newexpr_3 */
    {CR_LOCALS_SIZEOF(fid_parse_newexpr_locals_t)},
    /* fid_p_newexpr_4 */
    {CR_LOCALS_SIZEOF(fid_parse_newexpr_locals_t)},

    /* fid_parse_terminal ----------------------------------------- */
    /* fid_parse_terminal */
    {CR_LOCALS_SIZEOF(fid_parse_terminal_locals_t)},
    /* fid_p_terminal_1 */
    {CR_LOCALS_SIZEOF(fid_parse_terminal_locals_t)},
    /* fid_p_terminal_2 */
    {CR_LOCALS_SIZEOF(fid_parse_terminal_locals_t)},
    /* fid_p_terminal_3 */
    {CR_LOCALS_SIZEOF(fid_parse_terminal_locals_t)},
    /* fid_p_terminal_4 */
    {CR_LOCALS_SIZEOF(fid_parse_terminal_locals_t)},

    /* fid_parse_block ----------------------------------------- */
    /* fid_parse_block */
    {CR_LOCALS_SIZEOF(fid_parse_block_locals_t)},
    /* fid_p_block_1 */
    {CR_LOCALS_SIZEOF(fid_parse_block_locals_t)},

    /* fid_parse_if ----------------------------------------- */
    /* fid_parse_if */
    {CR_LOCALS_SIZEOF(fid_parse_if_locals_t)},
    /* fid_p_if_1 */
    {CR_LOCALS_SIZEOF(fid_parse_if_locals_t)},
    /* fid_p_if_2 */
    {CR_LOCALS_SIZEOF(fid_parse_if_locals_t)},
    /* fid_p_if_3 */
    {CR_LOCALS_SIZEOF(fid_parse_if_locals_t)},

    /* fid_parse_while ----------------------------------------- */
    /* fid_parse_while */
    {CR_LOCALS_SIZEOF(fid_parse_while_locals_t)},
    /* fid_p_while_1 */
    {CR_LOCALS_SIZEOF(fid_parse_while_locals_t)},
    /* fid_p_while_2 */
    {CR_LOCALS_SIZEOF(fid_parse_while_locals_t)},

    /* fid_parse_ident ----------------------------------------- */
    /* fid_parse_ident */
    {CR_LOCALS_SIZEOF(fid_parse_ident_locals_t)},

    /* fid_parse_ident_allow_reserved_words -------------------- */
    /* fid_parse_ident_allow_reserved_words */
    {CR_LOCALS_SIZEOF(fid_parse_ident_allow_reserved_words_locals_t)},
    /* fid_p_ident_allow_reserved_words_1 */
    {CR_LOCALS_SIZEOF(fid_parse_ident_allow_reserved_words_locals_t)},

    /* fid_parse_funcdecl ----------------------------------------- */
    /* fid_parse_funcdecl */
    {CR_LOCALS_SIZEOF(fid_parse_funcdecl_locals_t)},
    /* fid_p_funcdecl_1 */
    {CR_LOCALS_SIZEOF(fid_parse_funcdecl_locals_t)},
    /* fid_p_funcdecl_2 */
    {CR_LOCALS_SIZEOF(fid_parse_funcdecl_locals_t)},
    /* fid_p_funcdecl_3 */
    {CR_LOCALS_SIZEOF(fid_parse_funcdecl_locals_t)},
    /* fid_p_funcdecl_4 */
    {CR_LOCALS_SIZEOF(fid_parse_funcdecl_locals_t)},
    /* fid_p_funcdecl_5 */
    {CR_LOCALS_SIZEOF(fid_parse_funcdecl_locals_t)},
    /* fid_p_funcdecl_6 */
    {CR_LOCALS_SIZEOF(fid_parse_funcdecl_locals_t)},
    /* fid_p_funcdecl_7 */
    {CR_LOCALS_SIZEOF(fid_parse_funcdecl_locals_t)},
    /* fid_p_funcdecl_8 */
    {CR_LOCALS_SIZEOF(fid_parse_funcdecl_locals_t)},
    /* fid_p_funcdecl_9 */
    {CR_LOCALS_SIZEOF(fid_parse_funcdecl_locals_t)},

    /* fid_parse_arglist ----------------------------------------- */
    /* fid_parse_arglist */
    {CR_LOCALS_SIZEOF(fid_parse_arglist_locals_t)},
    /* fid_p_arglist_1 */
    {CR_LOCALS_SIZEOF(fid_parse_arglist_locals_t)},

    /* fid_parse_member ----------------------------------------- */
    /* fid_parse_member */
    {CR_LOCALS_SIZEOF(fid_parse_member_locals_t)},
    /* fid_p_member_1 */
    {CR_LOCALS_SIZEOF(fid_parse_member_locals_t)},

    /* fid_parse_memberexpr ----------------------------------------- */
    /* fid_parse_memberexpr */
    {CR_LOCALS_SIZEOF(fid_parse_memberexpr_locals_t)},
    /* fid_p_memberexpr_1 */
    {CR_LOCALS_SIZEOF(fid_parse_memberexpr_locals_t)},
    /* fid_p_memberexpr_2 */
    {CR_LOCALS_SIZEOF(fid_parse_memberexpr_locals_t)},

    /* fid_parse_var ----------------------------------------- */
    /* fid_parse_var */
    {CR_LOCALS_SIZEOF(fid_parse_var_locals_t)},
    /* fid_p_var_1 */
    {CR_LOCALS_SIZEOF(fid_parse_var_locals_t)},

    /* fid_parse_prop ----------------------------------------- */
    /* fid_parse_prop */
    {CR_LOCALS_SIZEOF(fid_parse_prop_locals_t)},
#ifdef V7_ENABLE_JS_GETTERS
    /* fid_p_prop_1_getter */
    {CR_LOCALS_SIZEOF(fid_parse_prop_locals_t)},
#endif
    /* fid_p_prop_2 */
    {CR_LOCALS_SIZEOF(fid_parse_prop_locals_t)},
#ifdef V7_ENABLE_JS_SETTERS
    /* fid_p_prop_3_setter */
    {CR_LOCALS_SIZEOF(fid_parse_prop_locals_t)},
#endif
    /* fid_p_prop_4 */
    {CR_LOCALS_SIZEOF(fid_parse_prop_locals_t)},

    /* fid_parse_dowhile ----------------------------------------- */
    /* fid_parse_dowhile */
    {CR_LOCALS_SIZEOF(fid_parse_dowhile_locals_t)},
    /* fid_p_dowhile_1 */
    {CR_LOCALS_SIZEOF(fid_parse_dowhile_locals_t)},
    /* fid_p_dowhile_2 */
    {CR_LOCALS_SIZEOF(fid_parse_dowhile_locals_t)},

    /* fid_parse_for ----------------------------------------- */
    /* fid_parse_for */
    {CR_LOCALS_SIZEOF(fid_parse_for_locals_t)},
    /* fid_p_for_1 */
    {CR_LOCALS_SIZEOF(fid_parse_for_locals_t)},
    /* fid_p_for_2 */
    {CR_LOCALS_SIZEOF(fid_parse_for_locals_t)},
    /* fid_p_for_3 */
    {CR_LOCALS_SIZEOF(fid_parse_for_locals_t)},
    /* fid_p_for_4 */
    {CR_LOCALS_SIZEOF(fid_parse_for_locals_t)},
    /* fid_p_for_5 */
    {CR_LOCALS_SIZEOF(fid_parse_for_locals_t)},
    /* fid_p_for_6 */
    {CR_LOCALS_SIZEOF(fid_parse_for_locals_t)},

    /* fid_parse_try ----------------------------------------- */
    /* fid_parse_try */
    {CR_LOCALS_SIZEOF(fid_parse_try_locals_t)},
    /* fid_p_try_1 */
    {CR_LOCALS_SIZEOF(fid_parse_try_locals_t)},
    /* fid_p_try_2 */
    {CR_LOCALS_SIZEOF(fid_parse_try_locals_t)},
    /* fid_p_try_3 */
    {CR_LOCALS_SIZEOF(fid_parse_try_locals_t)},
    /* fid_p_try_4 */
    {CR_LOCALS_SIZEOF(fid_parse_try_locals_t)},

    /* fid_parse_switch ----------------------------------------- */
    /* fid_parse_switch */
    {CR_LOCALS_SIZEOF(fid_parse_switch_locals_t)},
    /* fid_p_switch_1 */
    {CR_LOCALS_SIZEOF(fid_parse_switch_locals_t)},
    /* fid_p_switch_2 */
    {CR_LOCALS_SIZEOF(fid_parse_switch_locals_t)},
    /* fid_p_switch_3 */
    {CR_LOCALS_SIZEOF(fid_parse_switch_locals_t)},
    /* fid_p_switch_4 */
    {CR_LOCALS_SIZEOF(fid_parse_switch_locals_t)},

    /* fid_parse_with ----------------------------------------- */
    /* fid_parse_with */
    {CR_LOCALS_SIZEOF(fid_parse_with_locals_t)},
    /* fid_p_with_1 */
    {CR_LOCALS_SIZEOF(fid_parse_with_locals_t)},
    /* fid_p_with_2 */
    {CR_LOCALS_SIZEOF(fid_parse_with_locals_t)},

};

/*
 * Union of arguments and return values for all existing "functions".
 *
 * Used as an accumulator when we call function, return from function,
 * yield, or resume.
 */
union user_arg_ret {
  /* arguments to the next function */
  union {
#if 0
    fid_parse_script_arg_t fid_parse_script;
    fid_parse_use_strict_arg_t fid_parse_use_strict;
    fid_parse_statement_arg_t fid_parse_statement;
    fid_parse_expression_arg_t fid_parse_expression;
    fid_parse_assign_arg_t fid_parse_assign;
    fid_parse_prefix_arg_t fid_parse_prefix;
    fid_parse_postfix_arg_t fid_parse_postfix;
    fid_parse_callexpr_arg_t fid_parse_callexpr;
    fid_parse_newexpr_arg_t fid_parse_newexpr;
    fid_parse_terminal_arg_t fid_parse_terminal;
    fid_parse_block_arg_t fid_parse_block;
    fid_parse_if_arg_t fid_parse_if;
    fid_parse_while_arg_t fid_parse_while;
    fid_parse_ident_arg_t fid_parse_ident;
    fid_parse_ident_allow_reserved_words_arg_t
      fid_parse_ident_allow_reserved_words;
    fid_parse_arglist_arg_t fid_parse_arglist;
    fid_parse_memberexpr_arg_t fid_parse_memberexpr;
    fid_parse_var_arg_t fid_parse_var;
    fid_parse_prop_arg_t fid_parse_prop;
    fid_parse_dowhile_arg_t fid_parse_dowhile;
    fid_parse_for_arg_t fid_parse_for;
    fid_parse_try_arg_t fid_parse_try;
    fid_parse_switch_arg_t fid_parse_switch;
    fid_parse_with_arg_t fid_parse_with;
#endif
    fid_parse_body_arg_t fid_parse_body;
    fid_parse_binary_arg_t fid_parse_binary;
    fid_parse_funcdecl_arg_t fid_parse_funcdecl;
    fid_parse_member_arg_t fid_parse_member;
  } arg;

  /* value returned from function */
  /*
     union {
     } ret;
     */
};

static enum v7_tok next_tok(struct v7 *v7) {
  int prev_line_no = v7->pstate.prev_line_no;
  v7->pstate.prev_line_no = v7->pstate.line_no;
  v7->pstate.line_no += skip_to_next_tok(&v7->pstate.pc, v7->pstate.src_end);
  v7->after_newline = prev_line_no != v7->pstate.line_no;
  v7->tok = v7->pstate.pc;
  v7->cur_tok = get_tok(&v7->pstate.pc, v7->pstate.src_end, &v7->cur_tok_dbl,
                        v7->cur_tok);
  v7->tok_len = v7->pstate.pc - v7->tok;
  v7->pstate.line_no += skip_to_next_tok(&v7->pstate.pc, v7->pstate.src_end);
  return v7->cur_tok;
}

#ifndef V7_DISABLE_LINE_NUMBERS
/*
 * Assumes `offset` points to the byte right after a tag
 */
static void insert_line_no_if_changed(struct v7 *v7, struct ast *a,
                                      ast_off_t offset) {
  if (v7->pstate.prev_line_no != v7->line_no) {
    v7->line_no = v7->pstate.prev_line_no;
    ast_add_line_no(a, offset - 1, v7->line_no);
  } else {
#if V7_AST_FORCE_LINE_NUMBERS
    /*
     * This mode is needed for debug only: to make sure AST consumers correctly
     * consume all nodes with line numbers data encoded
     */
    ast_add_line_no(a, offset - 1, 0);
#endif
  }
}
#else
static void insert_line_no_if_changed(struct v7 *v7, struct ast *a,
                                      ast_off_t offset) {
  (void) v7;
  (void) a;
  (void) offset;
}
#endif

static ast_off_t insert_node(struct v7 *v7, struct ast *a, ast_off_t start,
                             enum ast_tag tag) {
  ast_off_t ret = ast_insert_node(a, start, tag);
  insert_line_no_if_changed(v7, a, ret);
  return ret;
}

static ast_off_t add_node(struct v7 *v7, struct ast *a, enum ast_tag tag) {
  return insert_node(v7, a, a->mbuf.len, tag);
}

static ast_off_t insert_inlined_node(struct v7 *v7, struct ast *a,
                                     ast_off_t start, enum ast_tag tag,
                                     const char *name, size_t len) {
  ast_off_t ret = ast_insert_inlined_node(a, start, tag, name, len);
  insert_line_no_if_changed(v7, a, ret);
  return ret;
}

static ast_off_t add_inlined_node(struct v7 *v7, struct ast *a,
                                  enum ast_tag tag, const char *name,
                                  size_t len) {
  return insert_inlined_node(v7, a, a->mbuf.len, tag, name, len);
}

static unsigned long get_column(const char *code, const char *pos) {
  const char *p = pos;
  while (p > code && *p != '\n') {
    p--;
  }
  return p == code ? pos - p : pos - (p + 1);
}

static enum v7_err end_of_statement(struct v7 *v7) {
  if (v7->cur_tok == TOK_SEMICOLON || v7->cur_tok == TOK_END_OF_INPUT ||
      v7->cur_tok == TOK_CLOSE_CURLY || v7->after_newline) {
    return V7_OK;
  }
  return V7_SYNTAX_ERROR;
}

static enum v7_tok lookahead(const struct v7 *v7) {
  const char *s = v7->pstate.pc;
  double d;
  return get_tok(&s, v7->pstate.src_end, &d, v7->cur_tok);
}

static int parse_optional(struct v7 *v7, struct ast *a,
                          enum v7_tok terminator) {
  if (v7->cur_tok != terminator) {
    return 1;
  }
  add_node(v7, a, AST_NOP);
  return 0;
}

/*
 * On ESP8266 'levels' declaration have to be outside of 'parse_binary'
 * in order to prevent reboot on return from this function
 * TODO(alashkin): understand why
 */
#define NONE \
  { (enum v7_tok) 0, (enum v7_tok) 0, (enum ast_tag) 0 }

static const struct {
  int len, left_to_right;
  struct {
    enum v7_tok start_tok, end_tok;
    enum ast_tag start_ast;
  } parts[2];
} levels[] = {
    {1, 0, {{TOK_ASSIGN, TOK_URSHIFT_ASSIGN, AST_ASSIGN}, NONE}},
    {1, 0, {{TOK_QUESTION, TOK_QUESTION, AST_COND}, NONE}},
    {1, 1, {{TOK_LOGICAL_OR, TOK_LOGICAL_OR, AST_LOGICAL_OR}, NONE}},
    {1, 1, {{TOK_LOGICAL_AND, TOK_LOGICAL_AND, AST_LOGICAL_AND}, NONE}},
    {1, 1, {{TOK_OR, TOK_OR, AST_OR}, NONE}},
    {1, 1, {{TOK_XOR, TOK_XOR, AST_XOR}, NONE}},
    {1, 1, {{TOK_AND, TOK_AND, AST_AND}, NONE}},
    {1, 1, {{TOK_EQ, TOK_NE_NE, AST_EQ}, NONE}},
    {2, 1, {{TOK_LE, TOK_GT, AST_LE}, {TOK_IN, TOK_INSTANCEOF, AST_IN}}},
    {1, 1, {{TOK_LSHIFT, TOK_URSHIFT, AST_LSHIFT}, NONE}},
    {1, 1, {{TOK_PLUS, TOK_MINUS, AST_ADD}, NONE}},
    {1, 1, {{TOK_REM, TOK_DIV, AST_REM}, NONE}}};

enum cr_status parser_cr_exec(struct cr_ctx *p_ctx, struct v7 *v7,
                              struct ast *a) {
  enum cr_status rc = CR_RES__OK;

_cr_iter_begin:

  rc = cr_on_iter_begin(p_ctx);
  if (rc != CR_RES__OK) {
    return rc;
  }

  /*
   * dispatcher switch: depending on the fid, jump to the corresponding label
   */
  switch ((enum my_fid) CR_CURR_FUNC()) {
    CR_DEFINE_ENTRY_POINT(fid_none);

    CR_DEFINE_ENTRY_POINT(fid_parse_script);
    CR_DEFINE_ENTRY_POINT(fid_p_script_1);
    CR_DEFINE_ENTRY_POINT(fid_p_script_2);
    CR_DEFINE_ENTRY_POINT(fid_p_script_3);
    CR_DEFINE_ENTRY_POINT(fid_p_script_4);

    CR_DEFINE_ENTRY_POINT(fid_parse_use_strict);

    CR_DEFINE_ENTRY_POINT(fid_parse_body);
    CR_DEFINE_ENTRY_POINT(fid_p_body_1);
    CR_DEFINE_ENTRY_POINT(fid_p_body_2);

    CR_DEFINE_ENTRY_POINT(fid_parse_statement);
    CR_DEFINE_ENTRY_POINT(fid_p_stat_1);
    CR_DEFINE_ENTRY_POINT(fid_p_stat_2);
    CR_DEFINE_ENTRY_POINT(fid_p_stat_3);
    CR_DEFINE_ENTRY_POINT(fid_p_stat_4);
    CR_DEFINE_ENTRY_POINT(fid_p_stat_5);
    CR_DEFINE_ENTRY_POINT(fid_p_stat_6);
    CR_DEFINE_ENTRY_POINT(fid_p_stat_7);
    CR_DEFINE_ENTRY_POINT(fid_p_stat_8);
    CR_DEFINE_ENTRY_POINT(fid_p_stat_9);
    CR_DEFINE_ENTRY_POINT(fid_p_stat_10);
    CR_DEFINE_ENTRY_POINT(fid_p_stat_11);
    CR_DEFINE_ENTRY_POINT(fid_p_stat_12);
    CR_DEFINE_ENTRY_POINT(fid_p_stat_13);
    CR_DEFINE_ENTRY_POINT(fid_p_stat_14);

    CR_DEFINE_ENTRY_POINT(fid_parse_expression);
    CR_DEFINE_ENTRY_POINT(fid_p_expr_1);

    CR_DEFINE_ENTRY_POINT(fid_parse_assign);
    CR_DEFINE_ENTRY_POINT(fid_p_assign_1);

    CR_DEFINE_ENTRY_POINT(fid_parse_binary);
    CR_DEFINE_ENTRY_POINT(fid_p_binary_2);
    CR_DEFINE_ENTRY_POINT(fid_p_binary_3);
    CR_DEFINE_ENTRY_POINT(fid_p_binary_4);
    CR_DEFINE_ENTRY_POINT(fid_p_binary_5);
    CR_DEFINE_ENTRY_POINT(fid_p_binary_6);

    CR_DEFINE_ENTRY_POINT(fid_parse_prefix);
    CR_DEFINE_ENTRY_POINT(fid_p_prefix_1);

    CR_DEFINE_ENTRY_POINT(fid_parse_postfix);
    CR_DEFINE_ENTRY_POINT(fid_p_postfix_1);

    CR_DEFINE_ENTRY_POINT(fid_parse_callexpr);
    CR_DEFINE_ENTRY_POINT(fid_p_callexpr_1);
    CR_DEFINE_ENTRY_POINT(fid_p_callexpr_2);
    CR_DEFINE_ENTRY_POINT(fid_p_callexpr_3);

    CR_DEFINE_ENTRY_POINT(fid_parse_newexpr);
    CR_DEFINE_ENTRY_POINT(fid_p_newexpr_1);
    CR_DEFINE_ENTRY_POINT(fid_p_newexpr_2);
    CR_DEFINE_ENTRY_POINT(fid_p_newexpr_3);
    CR_DEFINE_ENTRY_POINT(fid_p_newexpr_4);

    CR_DEFINE_ENTRY_POINT(fid_parse_terminal);
    CR_DEFINE_ENTRY_POINT(fid_p_terminal_1);
    CR_DEFINE_ENTRY_POINT(fid_p_terminal_2);
    CR_DEFINE_ENTRY_POINT(fid_p_terminal_3);
    CR_DEFINE_ENTRY_POINT(fid_p_terminal_4);

    CR_DEFINE_ENTRY_POINT(fid_parse_block);
    CR_DEFINE_ENTRY_POINT(fid_p_block_1);

    CR_DEFINE_ENTRY_POINT(fid_parse_if);
    CR_DEFINE_ENTRY_POINT(fid_p_if_1);
    CR_DEFINE_ENTRY_POINT(fid_p_if_2);
    CR_DEFINE_ENTRY_POINT(fid_p_if_3);

    CR_DEFINE_ENTRY_POINT(fid_parse_while);
    CR_DEFINE_ENTRY_POINT(fid_p_while_1);
    CR_DEFINE_ENTRY_POINT(fid_p_while_2);

    CR_DEFINE_ENTRY_POINT(fid_parse_ident);

    CR_DEFINE_ENTRY_POINT(fid_parse_ident_allow_reserved_words);
    CR_DEFINE_ENTRY_POINT(fid_p_ident_arw_1);

    CR_DEFINE_ENTRY_POINT(fid_parse_funcdecl);
    CR_DEFINE_ENTRY_POINT(fid_p_funcdecl_1);
    CR_DEFINE_ENTRY_POINT(fid_p_funcdecl_2);
    CR_DEFINE_ENTRY_POINT(fid_p_funcdecl_3);
    CR_DEFINE_ENTRY_POINT(fid_p_funcdecl_4);
    CR_DEFINE_ENTRY_POINT(fid_p_funcdecl_5);
    CR_DEFINE_ENTRY_POINT(fid_p_funcdecl_6);
    CR_DEFINE_ENTRY_POINT(fid_p_funcdecl_7);
    CR_DEFINE_ENTRY_POINT(fid_p_funcdecl_8);
    CR_DEFINE_ENTRY_POINT(fid_p_funcdecl_9);

    CR_DEFINE_ENTRY_POINT(fid_parse_arglist);
    CR_DEFINE_ENTRY_POINT(fid_p_arglist_1);

    CR_DEFINE_ENTRY_POINT(fid_parse_member);
    CR_DEFINE_ENTRY_POINT(fid_p_member_1);

    CR_DEFINE_ENTRY_POINT(fid_parse_memberexpr);
    CR_DEFINE_ENTRY_POINT(fid_p_memberexpr_1);
    CR_DEFINE_ENTRY_POINT(fid_p_memberexpr_2);

    CR_DEFINE_ENTRY_POINT(fid_parse_var);
    CR_DEFINE_ENTRY_POINT(fid_p_var_1);

    CR_DEFINE_ENTRY_POINT(fid_parse_prop);
#ifdef V7_ENABLE_JS_GETTERS
    CR_DEFINE_ENTRY_POINT(fid_p_prop_1_getter);
#endif
    CR_DEFINE_ENTRY_POINT(fid_p_prop_2);
#ifdef V7_ENABLE_JS_SETTERS
    CR_DEFINE_ENTRY_POINT(fid_p_prop_3_setter);
#endif
    CR_DEFINE_ENTRY_POINT(fid_p_prop_4);

    CR_DEFINE_ENTRY_POINT(fid_parse_dowhile);
    CR_DEFINE_ENTRY_POINT(fid_p_dowhile_1);
    CR_DEFINE_ENTRY_POINT(fid_p_dowhile_2);

    CR_DEFINE_ENTRY_POINT(fid_parse_for);
    CR_DEFINE_ENTRY_POINT(fid_p_for_1);
    CR_DEFINE_ENTRY_POINT(fid_p_for_2);
    CR_DEFINE_ENTRY_POINT(fid_p_for_3);
    CR_DEFINE_ENTRY_POINT(fid_p_for_4);
    CR_DEFINE_ENTRY_POINT(fid_p_for_5);
    CR_DEFINE_ENTRY_POINT(fid_p_for_6);

    CR_DEFINE_ENTRY_POINT(fid_parse_try);
    CR_DEFINE_ENTRY_POINT(fid_p_try_1);
    CR_DEFINE_ENTRY_POINT(fid_p_try_2);
    CR_DEFINE_ENTRY_POINT(fid_p_try_3);
    CR_DEFINE_ENTRY_POINT(fid_p_try_4);

    CR_DEFINE_ENTRY_POINT(fid_parse_switch);
    CR_DEFINE_ENTRY_POINT(fid_p_switch_1);
    CR_DEFINE_ENTRY_POINT(fid_p_switch_2);
    CR_DEFINE_ENTRY_POINT(fid_p_switch_3);
    CR_DEFINE_ENTRY_POINT(fid_p_switch_4);

    CR_DEFINE_ENTRY_POINT(fid_parse_with);
    CR_DEFINE_ENTRY_POINT(fid_p_with_1);
    CR_DEFINE_ENTRY_POINT(fid_p_with_2);

    default:
      /* should never be here */
      printf("fatal: wrong func id: %d", CR_CURR_FUNC());
      break;
  };

/* static enum v7_err parse_script(struct v7 *v7, struct ast *a) */
fid_parse_script :
#undef L
#define L CR_CUR_LOCALS_PT(fid_parse_script_locals_t)
{
  L->start = add_node(v7, a, AST_SCRIPT);
  L->outer_last_var_node = v7->last_var_node;
  L->saved_in_strict = v7->pstate.in_strict;

  v7->last_var_node = L->start;
  ast_modify_skip(a, L->start, L->start, AST_FUNC_FIRST_VAR_SKIP);

  CR_TRY(fid_p_script_1);
  {
    CALL_PARSE_USE_STRICT(fid_p_script_3);
    v7->pstate.in_strict = 1;
  }
  CR_CATCH(PARSER_EXC_ID__SYNTAX_ERROR, fid_p_script_1, fid_p_script_2);
  CR_ENDCATCH(fid_p_script_2);

  CALL_PARSE_BODY(TOK_END_OF_INPUT, fid_p_script_4);
  ast_set_skip(a, L->start, AST_END_SKIP);
  v7->pstate.in_strict = L->saved_in_strict;
  v7->last_var_node = L->outer_last_var_node;

  CR_RETURN_VOID();
}

/* static enum v7_err parse_use_strict(struct v7 *v7, struct ast *a) */
fid_parse_use_strict :
#undef L
#define L CR_CUR_LOCALS_PT(fid_parse_use_strict_locals_t)
{
  if (v7->cur_tok == TOK_STRING_LITERAL &&
      (strncmp(v7->tok, "\"use strict\"", v7->tok_len) == 0 ||
       strncmp(v7->tok, "'use strict'", v7->tok_len) == 0)) {
    next_tok(v7);
    add_node(v7, a, AST_USE_STRICT);
    CR_RETURN_VOID();
  } else {
    CR_THROW(PARSER_EXC_ID__SYNTAX_ERROR);
  }
}

/*
 * static enum v7_err parse_body(struct v7 *v7, struct ast *a,
 *                               enum v7_tok end)
 */
fid_parse_body :
#undef L
#define L CR_CUR_LOCALS_PT(fid_parse_body_locals_t)
{
  while (v7->cur_tok != L->arg.end) {
    if (ACCEPT(TOK_FUNCTION)) {
      if (v7->cur_tok != TOK_IDENTIFIER) {
        CR_THROW(PARSER_EXC_ID__SYNTAX_ERROR);
      }
      L->start = add_node(v7, a, AST_VAR);
      ast_modify_skip(a, v7->last_var_node, L->start, AST_FUNC_FIRST_VAR_SKIP);
      /* zero out var node pointer */
      ast_modify_skip(a, L->start, L->start, AST_FUNC_FIRST_VAR_SKIP);
      v7->last_var_node = L->start;
      add_inlined_node(v7, a, AST_FUNC_DECL, v7->tok, v7->tok_len);

      CALL_PARSE_FUNCDECL(1, 0, fid_p_body_1);
      ast_set_skip(a, L->start, AST_END_SKIP);
    } else {
      CALL_PARSE_STATEMENT(fid_p_body_2);
    }
  }
  CR_RETURN_VOID();
}

/* static enum v7_err parse_statement(struct v7 *v7, struct ast *a) */
fid_parse_statement :
#undef L
#define L CR_CUR_LOCALS_PT(fid_parse_statement_locals_t)
{
  switch (v7->cur_tok) {
    case TOK_SEMICOLON:
      next_tok(v7);
      /* empty statement */
      CR_RETURN_VOID();
    case TOK_OPEN_CURLY: /* block */
      CALL_PARSE_BLOCK(fid_p_stat_3);
      /* returning because no semicolon required */
      CR_RETURN_VOID();
    case TOK_IF:
      next_tok(v7);
      CALL_PARSE_IF(fid_p_stat_4);
      CR_RETURN_VOID();
    case TOK_WHILE:
      next_tok(v7);
      CALL_PARSE_WHILE(fid_p_stat_5);
      CR_RETURN_VOID();
    case TOK_DO:
      next_tok(v7);
      CALL_PARSE_DOWHILE(fid_p_stat_10);
      CR_RETURN_VOID();
    case TOK_FOR:
      next_tok(v7);
      CALL_PARSE_FOR(fid_p_stat_11);
      CR_RETURN_VOID();
    case TOK_TRY:
      next_tok(v7);
      CALL_PARSE_TRY(fid_p_stat_12);
      CR_RETURN_VOID();
    case TOK_SWITCH:
      next_tok(v7);
      CALL_PARSE_SWITCH(fid_p_stat_13);
      CR_RETURN_VOID();
    case TOK_WITH:
      next_tok(v7);
      CALL_PARSE_WITH(fid_p_stat_14);
      CR_RETURN_VOID();
    case TOK_BREAK:
      if (!(v7->pstate.in_loop || v7->pstate.in_switch)) {
        CR_THROW(PARSER_EXC_ID__SYNTAX_ERROR);
      }
      next_tok(v7);
      PARSE_WITH_OPT_ARG(AST_BREAK, AST_LABELED_BREAK, CALL_PARSE_IDENT,
                         fid_p_stat_7);
      break;
    case TOK_CONTINUE:
      if (!v7->pstate.in_loop) {
        CR_THROW(PARSER_EXC_ID__SYNTAX_ERROR);
      }
      next_tok(v7);
      PARSE_WITH_OPT_ARG(AST_CONTINUE, AST_LABELED_CONTINUE, CALL_PARSE_IDENT,
                         fid_p_stat_8);
      break;
    case TOK_RETURN:
      if (!v7->pstate.in_function) {
        CR_THROW(PARSER_EXC_ID__SYNTAX_ERROR);
      }
      next_tok(v7);
      PARSE_WITH_OPT_ARG(AST_RETURN, AST_VALUE_RETURN, CALL_PARSE_EXPRESSION,
                         fid_p_stat_6);
      break;
    case TOK_THROW:
      next_tok(v7);
      add_node(v7, a, AST_THROW);
      CALL_PARSE_EXPRESSION(fid_p_stat_2);
      break;
    case TOK_DEBUGGER:
      next_tok(v7);
      add_node(v7, a, AST_DEBUGGER);
      break;
    case TOK_VAR:
      next_tok(v7);
      CALL_PARSE_VAR(fid_p_stat_9);
      break;
    case TOK_IDENTIFIER:
      if (lookahead(v7) == TOK_COLON) {
        add_inlined_node(v7, a, AST_LABEL, v7->tok, v7->tok_len);
        next_tok(v7);
        EXPECT(TOK_COLON);
        CR_RETURN_VOID();
      }
    /* fall through */
    default:
      CALL_PARSE_EXPRESSION(fid_p_stat_1);
      break;
  }

  if (end_of_statement(v7) != V7_OK) {
    CR_THROW(PARSER_EXC_ID__SYNTAX_ERROR);
  }
  ACCEPT(TOK_SEMICOLON); /* swallow optional semicolon */
  CR_RETURN_VOID();
}

/* static enum v7_err parse_expression(struct v7 *v7, struct ast *a) */
fid_parse_expression :
#undef L
#define L CR_CUR_LOCALS_PT(fid_parse_expression_locals_t)
{
  L->pos = a->mbuf.len;
  L->group = 0;
  while (1) {
    CALL_PARSE_ASSIGN(fid_p_expr_1);
    if (ACCEPT(TOK_COMMA)) {
      L->group = 1;
    } else {
      break;
    }
  }
  if (L->group) {
    insert_node(v7, a, L->pos, AST_SEQ);
  }
  CR_RETURN_VOID();
}

/* static enum v7_err parse_assign(struct v7 *v7, struct ast *a) */
fid_parse_assign :
#undef L
#define L CR_CUR_LOCALS_PT(fid_parse_assign_locals_t)
{
  CALL_PARSE_BINARY(0, a->mbuf.len, fid_p_assign_1);
  CR_RETURN_VOID();
}

/*
 * static enum v7_err parse_binary(struct v7 *v7, struct ast *a, int level,
 *                                 ast_off_t pos)
 */
#if 1
fid_parse_binary :
#undef L
#define L CR_CUR_LOCALS_PT(fid_parse_binary_locals_t)
{
/*
 * Note: we use macro CUR_POS instead of a local variable, since this
 * function is called really a lot, so, each byte on stack frame counts.
 *
 * It will work a bit slower of course, but slowness is not a problem
 */
#define CUR_POS ((L->level > L->arg.min_level) ? L->saved_mbuf_len : L->arg.pos)
  L->saved_mbuf_len = a->mbuf.len;

  CALL_PARSE_PREFIX(fid_p_binary_6);

  for (L->level = (int) ARRAY_SIZE(levels) - 1; L->level >= L->arg.min_level;
       L->level--) {
    for (L->i = 0; L->i < levels[L->level].len; L->i++) {
      L->tok = levels[L->level].parts[L->i].start_tok;
      L->ast = levels[L->level].parts[L->i].start_ast;
      do {
        if (v7->pstate.inhibit_in && L->tok == TOK_IN) {
          continue;
        }

        /*
         * Ternary operator sits in the middle of the binary operator
         * precedence chain. Deal with it as an exception and don't break
         * the chain.
         */
        if (L->tok == TOK_QUESTION && v7->cur_tok == TOK_QUESTION) {
          next_tok(v7);
          CALL_PARSE_ASSIGN(fid_p_binary_2);
          EXPECT(TOK_COLON);
          CALL_PARSE_ASSIGN(fid_p_binary_3);
          insert_node(v7, a, CUR_POS, AST_COND);
          CR_RETURN_VOID();
        } else if (ACCEPT(L->tok)) {
          if (levels[L->level].left_to_right) {
            insert_node(v7, a, CUR_POS, (enum ast_tag) L->ast);
            CALL_PARSE_BINARY(L->level, CUR_POS, fid_p_binary_4);
          } else {
            CALL_PARSE_BINARY(L->level, a->mbuf.len, fid_p_binary_5);
            insert_node(v7, a, CUR_POS, (enum ast_tag) L->ast);
          }
        }
      } while (L->ast = (enum ast_tag)(L->ast + 1),
               L->tok < levels[L->level].parts[L->i].end_tok &&
                   (L->tok = (enum v7_tok)(L->tok + 1)));
    }
  }

  CR_RETURN_VOID();
#undef CUR_POS
}
#endif

/* enum v7_err parse_prefix(struct v7 *v7, struct ast *a) */
fid_parse_prefix :
#undef L
#define L CR_CUR_LOCALS_PT(fid_parse_prefix_locals_t)
{
  for (;;) {
    switch (v7->cur_tok) {
      case TOK_PLUS:
        next_tok(v7);
        add_node(v7, a, AST_POSITIVE);
        break;
      case TOK_MINUS:
        next_tok(v7);
        add_node(v7, a, AST_NEGATIVE);
        break;
      case TOK_PLUS_PLUS:
        next_tok(v7);
        add_node(v7, a, AST_PREINC);
        break;
      case TOK_MINUS_MINUS:
        next_tok(v7);
        add_node(v7, a, AST_PREDEC);
        break;
      case TOK_TILDA:
        next_tok(v7);
        add_node(v7, a, AST_NOT);
        break;
      case TOK_NOT:
        next_tok(v7);
        add_node(v7, a, AST_LOGICAL_NOT);
        break;
      case TOK_VOID:
        next_tok(v7);
        add_node(v7, a, AST_VOID);
        break;
      case TOK_DELETE:
        next_tok(v7);
        add_node(v7, a, AST_DELETE);
        break;
      case TOK_TYPEOF:
        next_tok(v7);
        add_node(v7, a, AST_TYPEOF);
        break;
      default:
        CALL_PARSE_POSTFIX(fid_p_prefix_1);
        CR_RETURN_VOID();
    }
  }
}

/* static enum v7_err parse_postfix(struct v7 *v7, struct ast *a) */
fid_parse_postfix :
#undef L
#define L CR_CUR_LOCALS_PT(fid_parse_postfix_locals_t)
{
  L->pos = a->mbuf.len;
  CALL_PARSE_CALLEXPR(fid_p_postfix_1);

  if (v7->after_newline) {
    CR_RETURN_VOID();
  }
  switch (v7->cur_tok) {
    case TOK_PLUS_PLUS:
      next_tok(v7);
      insert_node(v7, a, L->pos, AST_POSTINC);
      break;
    case TOK_MINUS_MINUS:
      next_tok(v7);
      insert_node(v7, a, L->pos, AST_POSTDEC);
      break;
    default:
      break; /* nothing */
  }
  CR_RETURN_VOID();
}

/* static enum v7_err parse_callexpr(struct v7 *v7, struct ast *a) */
fid_parse_callexpr :
#undef L
#define L CR_CUR_LOCALS_PT(fid_parse_callexpr_locals_t)
{
  L->pos = a->mbuf.len;
  CALL_PARSE_NEWEXPR(fid_p_callexpr_1);

  for (;;) {
    switch (v7->cur_tok) {
      case TOK_DOT:
      case TOK_OPEN_BRACKET:
        CALL_PARSE_MEMBER(L->pos, fid_p_callexpr_3);
        break;
      case TOK_OPEN_PAREN:
        next_tok(v7);
        CALL_PARSE_ARGLIST(fid_p_callexpr_2);
        EXPECT(TOK_CLOSE_PAREN);
        insert_node(v7, a, L->pos, AST_CALL);
        break;
      default:
        CR_RETURN_VOID();
    }
  }
}

/* static enum v7_err parse_newexpr(struct v7 *v7, struct ast *a) */
fid_parse_newexpr :
#undef L
#define L CR_CUR_LOCALS_PT(fid_parse_newexpr_locals_t)
{
  switch (v7->cur_tok) {
    case TOK_NEW:
      next_tok(v7);
      L->start = add_node(v7, a, AST_NEW);
      CALL_PARSE_MEMBEREXPR(fid_p_newexpr_3);
      if (ACCEPT(TOK_OPEN_PAREN)) {
        CALL_PARSE_ARGLIST(fid_p_newexpr_4);
        EXPECT(TOK_CLOSE_PAREN);
      }
      ast_set_skip(a, L->start, AST_END_SKIP);
      break;
    case TOK_FUNCTION:
      next_tok(v7);
      CALL_PARSE_FUNCDECL(0, 0, fid_p_newexpr_2);
      break;
    default:
      CALL_PARSE_TERMINAL(fid_p_newexpr_1);
      break;
  }
  CR_RETURN_VOID();
}

/* static enum v7_err parse_terminal(struct v7 *v7, struct ast *a) */
fid_parse_terminal :
#undef L
#define L CR_CUR_LOCALS_PT(fid_parse_terminal_locals_t)
{
  switch (v7->cur_tok) {
    case TOK_OPEN_PAREN:
      next_tok(v7);
      CALL_PARSE_EXPRESSION(fid_p_terminal_1);
      EXPECT(TOK_CLOSE_PAREN);
      break;
    case TOK_OPEN_BRACKET:
      next_tok(v7);
      L->start = add_node(v7, a, AST_ARRAY);
      while (v7->cur_tok != TOK_CLOSE_BRACKET) {
        if (v7->cur_tok == TOK_COMMA) {
          /* Array literals allow missing elements, e.g. [,,1,] */
          add_node(v7, a, AST_NOP);
        } else {
          CALL_PARSE_ASSIGN(fid_p_terminal_2);
        }
        ACCEPT(TOK_COMMA);
      }
      EXPECT(TOK_CLOSE_BRACKET);
      ast_set_skip(a, L->start, AST_END_SKIP);
      break;
    case TOK_OPEN_CURLY:
      next_tok(v7);
      L->start = add_node(v7, a, AST_OBJECT);
      if (v7->cur_tok != TOK_CLOSE_CURLY) {
        do {
          if (v7->cur_tok == TOK_CLOSE_CURLY) {
            break;
          }
          CALL_PARSE_PROP(fid_p_terminal_3);
        } while (ACCEPT(TOK_COMMA));
      }
      EXPECT(TOK_CLOSE_CURLY);
      ast_set_skip(a, L->start, AST_END_SKIP);
      break;
    case TOK_THIS:
      next_tok(v7);
      add_node(v7, a, AST_THIS);
      break;
    case TOK_TRUE:
      next_tok(v7);
      add_node(v7, a, AST_TRUE);
      break;
    case TOK_FALSE:
      next_tok(v7);
      add_node(v7, a, AST_FALSE);
      break;
    case TOK_NULL:
      next_tok(v7);
      add_node(v7, a, AST_NULL);
      break;
    case TOK_STRING_LITERAL:
      add_inlined_node(v7, a, AST_STRING, v7->tok + 1, v7->tok_len - 2);
      next_tok(v7);
      break;
    case TOK_NUMBER:
      add_inlined_node(v7, a, AST_NUM, v7->tok, v7->tok_len);
      next_tok(v7);
      break;
    case TOK_REGEX_LITERAL:
      add_inlined_node(v7, a, AST_REGEX, v7->tok, v7->tok_len);
      next_tok(v7);
      break;
    case TOK_IDENTIFIER:
      if (v7->tok_len == 9 && strncmp(v7->tok, "undefined", v7->tok_len) == 0) {
        add_node(v7, a, AST_UNDEFINED);
        next_tok(v7);
        break;
      }
    /* fall through */
    default:
      CALL_PARSE_IDENT(fid_p_terminal_4);
  }
  CR_RETURN_VOID();
}

/* static enum v7_err parse_block(struct v7 *v7, struct ast *a) */
fid_parse_block :
#undef L
#define L CR_CUR_LOCALS_PT(fid_parse_block_locals_t)
{
  EXPECT(TOK_OPEN_CURLY);
  CALL_PARSE_BODY(TOK_CLOSE_CURLY, fid_p_block_1);
  EXPECT(TOK_CLOSE_CURLY);
  CR_RETURN_VOID();
}

/* static enum v7_err parse_if(struct v7 *v7, struct ast *a) */
fid_parse_if :
#undef L
#define L CR_CUR_LOCALS_PT(fid_parse_if_locals_t)
{
  L->start = add_node(v7, a, AST_IF);
  EXPECT(TOK_OPEN_PAREN);
  CALL_PARSE_EXPRESSION(fid_p_if_1);
  EXPECT(TOK_CLOSE_PAREN);
  CALL_PARSE_STATEMENT(fid_p_if_2);
  ast_set_skip(a, L->start, AST_END_IF_TRUE_SKIP);
  if (ACCEPT(TOK_ELSE)) {
    CALL_PARSE_STATEMENT(fid_p_if_3);
  }
  ast_set_skip(a, L->start, AST_END_SKIP);
  CR_RETURN_VOID();
}

/* static enum v7_err parse_while(struct v7 *v7, struct ast *a) */
fid_parse_while :
#undef L
#define L CR_CUR_LOCALS_PT(fid_parse_while_locals_t)
{
  L->start = add_node(v7, a, AST_WHILE);
  L->saved_in_loop = v7->pstate.in_loop;
  EXPECT(TOK_OPEN_PAREN);
  CALL_PARSE_EXPRESSION(fid_p_while_1);
  EXPECT(TOK_CLOSE_PAREN);
  v7->pstate.in_loop = 1;
  CALL_PARSE_STATEMENT(fid_p_while_2);
  ast_set_skip(a, L->start, AST_END_SKIP);
  v7->pstate.in_loop = L->saved_in_loop;
  CR_RETURN_VOID();
}

/* static enum v7_err parse_ident(struct v7 *v7, struct ast *a) */
fid_parse_ident :
#undef L
#define L CR_CUR_LOCALS_PT(fid_parse_ident_locals_t)
{
  if (v7->cur_tok == TOK_IDENTIFIER) {
    add_inlined_node(v7, a, AST_IDENT, v7->tok, v7->tok_len);
    next_tok(v7);
    CR_RETURN_VOID();
  }
  CR_THROW(PARSER_EXC_ID__SYNTAX_ERROR);
}

/*
 * static enum v7_err parse_ident_allow_reserved_words(struct v7 *v7,
 *                                                     struct ast *a)
 *
 */
fid_parse_ident_allow_reserved_words :
#undef L
#define L CR_CUR_LOCALS_PT(fid_parse_ident_allow_reserved_words_locals_t)
{
  /* Allow reserved words as property names. */
  if (is_reserved_word_token(v7->cur_tok)) {
    add_inlined_node(v7, a, AST_IDENT, v7->tok, v7->tok_len);
    next_tok(v7);
  } else {
    CALL_PARSE_IDENT(fid_p_ident_arw_1);
  }
  CR_RETURN_VOID();
}

/* static enum v7_err parse_funcdecl(struct v7 *, struct ast *, int, int) */
fid_parse_funcdecl :
#undef L
#define L CR_CUR_LOCALS_PT(fid_parse_funcdecl_locals_t)
{
  L->start = add_node(v7, a, AST_FUNC);
  L->outer_last_var_node = v7->last_var_node;
  L->saved_in_function = v7->pstate.in_function;
  L->saved_in_strict = v7->pstate.in_strict;

  v7->last_var_node = L->start;
  ast_modify_skip(a, L->start, L->start, AST_FUNC_FIRST_VAR_SKIP);

  CR_TRY(fid_p_funcdecl_2);
  {
    if (L->arg.reserved_name) {
      CALL_PARSE_IDENT_ALLOW_RESERVED_WORDS(fid_p_funcdecl_1);
    } else {
      CALL_PARSE_IDENT(fid_p_funcdecl_9);
    }
  }
  CR_CATCH(PARSER_EXC_ID__SYNTAX_ERROR, fid_p_funcdecl_2, fid_p_funcdecl_3);
  {
    if (L->arg.require_named) {
      /* function name is required, so, rethrow SYNTAX ERROR */
      CR_THROW(PARSER_EXC_ID__SYNTAX_ERROR);
    } else {
      /* it's ok not to have a function name, just insert NOP */
      add_node(v7, a, AST_NOP);
    }
  }
  CR_ENDCATCH(fid_p_funcdecl_3);

  EXPECT(TOK_OPEN_PAREN);
  CALL_PARSE_ARGLIST(fid_p_funcdecl_4);
  EXPECT(TOK_CLOSE_PAREN);
  ast_set_skip(a, L->start, AST_FUNC_BODY_SKIP);
  v7->pstate.in_function = 1;
  EXPECT(TOK_OPEN_CURLY);

  CR_TRY(fid_p_funcdecl_5);
  {
    CALL_PARSE_USE_STRICT(fid_p_funcdecl_7);
    v7->pstate.in_strict = 1;
  }
  CR_CATCH(PARSER_EXC_ID__SYNTAX_ERROR, fid_p_funcdecl_5, fid_p_funcdecl_6);
  CR_ENDCATCH(fid_p_funcdecl_6);

  CALL_PARSE_BODY(TOK_CLOSE_CURLY, fid_p_funcdecl_8);
  EXPECT(TOK_CLOSE_CURLY);
  v7->pstate.in_strict = L->saved_in_strict;
  v7->pstate.in_function = L->saved_in_function;
  ast_set_skip(a, L->start, AST_END_SKIP);
  v7->last_var_node = L->outer_last_var_node;

  CR_RETURN_VOID();
}

/* static enum v7_err parse_arglist(struct v7 *v7, struct ast *a) */
fid_parse_arglist :
#undef L
#define L CR_CUR_LOCALS_PT(fid_parse_arglist_locals_t)
{
  if (v7->cur_tok != TOK_CLOSE_PAREN) {
    do {
      CALL_PARSE_ASSIGN(fid_p_arglist_1);
    } while (ACCEPT(TOK_COMMA));
  }
  CR_RETURN_VOID();
}

/*
 * static enum v7_err parse_member(struct v7 *v7, struct ast *a, ast_off_t pos)
 */
fid_parse_member :
#undef L
#define L CR_CUR_LOCALS_PT(fid_parse_member_locals_t)
{
  switch (v7->cur_tok) {
    case TOK_DOT:
      next_tok(v7);
      /* Allow reserved words as member identifiers */
      if (is_reserved_word_token(v7->cur_tok) ||
          v7->cur_tok == TOK_IDENTIFIER) {
        insert_inlined_node(v7, a, L->arg.pos, AST_MEMBER, v7->tok,
                            v7->tok_len);
        next_tok(v7);
      } else {
        CR_THROW(PARSER_EXC_ID__SYNTAX_ERROR);
      }
      break;
    case TOK_OPEN_BRACKET:
      next_tok(v7);
      CALL_PARSE_EXPRESSION(fid_p_member_1);
      EXPECT(TOK_CLOSE_BRACKET);
      insert_node(v7, a, L->arg.pos, AST_INDEX);
      break;
    default:
      CR_RETURN_VOID();
  }
  /* not necessary, but let it be anyway */
  CR_RETURN_VOID();
}

/* static enum v7_err parse_memberexpr(struct v7 *v7, struct ast *a) */
fid_parse_memberexpr :
#undef L
#define L CR_CUR_LOCALS_PT(fid_parse_memberexpr_locals_t)
{
  L->pos = a->mbuf.len;
  CALL_PARSE_NEWEXPR(fid_p_memberexpr_1);

  for (;;) {
    switch (v7->cur_tok) {
      case TOK_DOT:
      case TOK_OPEN_BRACKET:
        CALL_PARSE_MEMBER(L->pos, fid_p_memberexpr_2);
        break;
      default:
        CR_RETURN_VOID();
    }
  }
}

/* static enum v7_err parse_var(struct v7 *v7, struct ast *a) */
fid_parse_var :
#undef L
#define L CR_CUR_LOCALS_PT(fid_parse_var_locals_t)
{
  L->start = add_node(v7, a, AST_VAR);
  ast_modify_skip(a, v7->last_var_node, L->start, AST_FUNC_FIRST_VAR_SKIP);
  /* zero out var node pointer */
  ast_modify_skip(a, L->start, L->start, AST_FUNC_FIRST_VAR_SKIP);
  v7->last_var_node = L->start;
  do {
    add_inlined_node(v7, a, AST_VAR_DECL, v7->tok, v7->tok_len);
    EXPECT(TOK_IDENTIFIER);
    if (ACCEPT(TOK_ASSIGN)) {
      CALL_PARSE_ASSIGN(fid_p_var_1);
    } else {
      add_node(v7, a, AST_NOP);
    }
  } while (ACCEPT(TOK_COMMA));
  ast_set_skip(a, L->start, AST_END_SKIP);
  CR_RETURN_VOID();
}

/* static enum v7_err parse_prop(struct v7 *v7, struct ast *a) */
fid_parse_prop :
#undef L
#define L CR_CUR_LOCALS_PT(fid_parse_prop_locals_t)
{
#ifdef V7_ENABLE_JS_GETTERS
  if (v7->cur_tok == TOK_IDENTIFIER && v7->tok_len == 3 &&
      strncmp(v7->tok, "get", v7->tok_len) == 0 && lookahead(v7) != TOK_COLON) {
    next_tok(v7);
    add_node(v7, a, AST_GETTER);
    CALL_PARSE_FUNCDECL(1, 1, fid_p_prop_1_getter);
  } else
#endif
      if (v7->cur_tok == TOK_IDENTIFIER && lookahead(v7) == TOK_OPEN_PAREN) {
    /* ecmascript 6 feature */
    CALL_PARSE_FUNCDECL(1, 1, fid_p_prop_2);
#ifdef V7_ENABLE_JS_SETTERS
  } else if (v7->cur_tok == TOK_IDENTIFIER && v7->tok_len == 3 &&
             strncmp(v7->tok, "set", v7->tok_len) == 0 &&
             lookahead(v7) != TOK_COLON) {
    next_tok(v7);
    add_node(v7, a, AST_SETTER);
    CALL_PARSE_FUNCDECL(1, 1, fid_p_prop_3_setter);
#endif
  } else {
    /* Allow reserved words as property names. */
    if (is_reserved_word_token(v7->cur_tok) || v7->cur_tok == TOK_IDENTIFIER ||
        v7->cur_tok == TOK_NUMBER) {
      add_inlined_node(v7, a, AST_PROP, v7->tok, v7->tok_len);
    } else if (v7->cur_tok == TOK_STRING_LITERAL) {
      add_inlined_node(v7, a, AST_PROP, v7->tok + 1, v7->tok_len - 2);
    } else {
      CR_THROW(PARSER_EXC_ID__SYNTAX_ERROR);
    }
    next_tok(v7);
    EXPECT(TOK_COLON);
    CALL_PARSE_ASSIGN(fid_p_prop_4);
  }
  CR_RETURN_VOID();
}

/* static enum v7_err parse_dowhile(struct v7 *v7, struct ast *a) */
fid_parse_dowhile :
#undef L
#define L CR_CUR_LOCALS_PT(fid_parse_dowhile_locals_t)
{
  L->start = add_node(v7, a, AST_DOWHILE);
  L->saved_in_loop = v7->pstate.in_loop;

  v7->pstate.in_loop = 1;
  CALL_PARSE_STATEMENT(fid_p_dowhile_1);
  v7->pstate.in_loop = L->saved_in_loop;
  ast_set_skip(a, L->start, AST_DO_WHILE_COND_SKIP);
  EXPECT(TOK_WHILE);
  EXPECT(TOK_OPEN_PAREN);
  CALL_PARSE_EXPRESSION(fid_p_dowhile_2);
  EXPECT(TOK_CLOSE_PAREN);
  ast_set_skip(a, L->start, AST_END_SKIP);
  CR_RETURN_VOID();
}

/* static enum v7_err parse_for(struct v7 *v7, struct ast *a) */
fid_parse_for :
#undef L
#define L CR_CUR_LOCALS_PT(fid_parse_for_locals_t)
{
  /* TODO(mkm): for of, for each in */
  /*
  ast_off_t start;
  int saved_in_loop;
  */

  L->start = add_node(v7, a, AST_FOR);
  L->saved_in_loop = v7->pstate.in_loop;

  EXPECT(TOK_OPEN_PAREN);

  if (parse_optional(v7, a, TOK_SEMICOLON)) {
    /*
     * TODO(mkm): make this reentrant otherwise this pearl won't parse:
     * for((function(){return 1 in o.a ? o : x})().a in [1,2,3])
     */
    v7->pstate.inhibit_in = 1;
    if (ACCEPT(TOK_VAR)) {
      CALL_PARSE_VAR(fid_p_for_1);
    } else {
      CALL_PARSE_EXPRESSION(fid_p_for_2);
    }
    v7->pstate.inhibit_in = 0;

    if (ACCEPT(TOK_IN)) {
      CALL_PARSE_EXPRESSION(fid_p_for_3);
      add_node(v7, a, AST_NOP);
      /*
       * Assumes that for and for in have the same AST format which is
       * suboptimal but avoids the need of fixing up the var offset chain.
       * TODO(mkm) improve this
       */
      ast_modify_tag(a, L->start - 1, AST_FOR_IN);
      goto for_loop_body;
    }
  }

  EXPECT(TOK_SEMICOLON);
  if (parse_optional(v7, a, TOK_SEMICOLON)) {
    CALL_PARSE_EXPRESSION(fid_p_for_4);
  }
  EXPECT(TOK_SEMICOLON);
  if (parse_optional(v7, a, TOK_CLOSE_PAREN)) {
    CALL_PARSE_EXPRESSION(fid_p_for_5);
  }

for_loop_body:
  EXPECT(TOK_CLOSE_PAREN);
  ast_set_skip(a, L->start, AST_FOR_BODY_SKIP);
  v7->pstate.in_loop = 1;
  CALL_PARSE_STATEMENT(fid_p_for_6);
  v7->pstate.in_loop = L->saved_in_loop;
  ast_set_skip(a, L->start, AST_END_SKIP);
  CR_RETURN_VOID();
}

/* static enum v7_err parse_try(struct v7 *v7, struct ast *a) */
fid_parse_try :
#undef L
#define L CR_CUR_LOCALS_PT(fid_parse_try_locals_t)
{
  L->start = add_node(v7, a, AST_TRY);
  L->catch_or_finally = 0;
  CALL_PARSE_BLOCK(fid_p_try_1);
  ast_set_skip(a, L->start, AST_TRY_CATCH_SKIP);
  if (ACCEPT(TOK_CATCH)) {
    L->catch_or_finally = 1;
    EXPECT(TOK_OPEN_PAREN);
    CALL_PARSE_IDENT(fid_p_try_2);
    EXPECT(TOK_CLOSE_PAREN);
    CALL_PARSE_BLOCK(fid_p_try_3);
  }
  ast_set_skip(a, L->start, AST_TRY_FINALLY_SKIP);
  if (ACCEPT(TOK_FINALLY)) {
    L->catch_or_finally = 1;
    CALL_PARSE_BLOCK(fid_p_try_4);
  }
  ast_set_skip(a, L->start, AST_END_SKIP);

  /* make sure `catch` and `finally` aren't both missing */
  if (!L->catch_or_finally) {
    CR_THROW(PARSER_EXC_ID__SYNTAX_ERROR);
  }

  CR_RETURN_VOID();
}

/* static enum v7_err parse_switch(struct v7 *v7, struct ast *a) */
fid_parse_switch :
#undef L
#define L CR_CUR_LOCALS_PT(fid_parse_switch_locals_t)
{
  L->start = add_node(v7, a, AST_SWITCH);
  L->saved_in_switch = v7->pstate.in_switch;

  ast_set_skip(a, L->start, AST_SWITCH_DEFAULT_SKIP); /* clear out */
  EXPECT(TOK_OPEN_PAREN);
  CALL_PARSE_EXPRESSION(fid_p_switch_1);
  EXPECT(TOK_CLOSE_PAREN);
  EXPECT(TOK_OPEN_CURLY);
  v7->pstate.in_switch = 1;
  while (v7->cur_tok != TOK_CLOSE_CURLY) {
    switch (v7->cur_tok) {
      case TOK_CASE:
        next_tok(v7);
        L->case_start = add_node(v7, a, AST_CASE);
        CALL_PARSE_EXPRESSION(fid_p_switch_2);
        EXPECT(TOK_COLON);
        while (v7->cur_tok != TOK_CASE && v7->cur_tok != TOK_DEFAULT &&
               v7->cur_tok != TOK_CLOSE_CURLY) {
          CALL_PARSE_STATEMENT(fid_p_switch_3);
        }
        ast_set_skip(a, L->case_start, AST_END_SKIP);
        break;
      case TOK_DEFAULT:
        next_tok(v7);
        EXPECT(TOK_COLON);
        ast_set_skip(a, L->start, AST_SWITCH_DEFAULT_SKIP);
        L->case_start = add_node(v7, a, AST_DEFAULT);
        while (v7->cur_tok != TOK_CASE && v7->cur_tok != TOK_DEFAULT &&
               v7->cur_tok != TOK_CLOSE_CURLY) {
          CALL_PARSE_STATEMENT(fid_p_switch_4);
        }
        ast_set_skip(a, L->case_start, AST_END_SKIP);
        break;
      default:
        CR_THROW(PARSER_EXC_ID__SYNTAX_ERROR);
    }
  }
  EXPECT(TOK_CLOSE_CURLY);
  ast_set_skip(a, L->start, AST_END_SKIP);
  v7->pstate.in_switch = L->saved_in_switch;
  CR_RETURN_VOID();
}

/* static enum v7_err parse_with(struct v7 *v7, struct ast *a) */
fid_parse_with :
#undef L
#define L CR_CUR_LOCALS_PT(fid_parse_with_locals_t)
{
  L->start = add_node(v7, a, AST_WITH);
  if (v7->pstate.in_strict) {
    CR_THROW(PARSER_EXC_ID__SYNTAX_ERROR);
  }
  EXPECT(TOK_OPEN_PAREN);
  CALL_PARSE_EXPRESSION(fid_p_with_1);
  EXPECT(TOK_CLOSE_PAREN);
  CALL_PARSE_STATEMENT(fid_p_with_2);
  ast_set_skip(a, L->start, AST_END_SKIP);
  CR_RETURN_VOID();
}

fid_none:
  /* stack is empty, so we're done; return */
  return rc;
}

V7_PRIVATE enum v7_err parse(struct v7 *v7, struct ast *a, const char *src,
                             size_t src_len, int is_json) {
  enum v7_err rcode;
  const char *error_msg = NULL;
  const char *p;
  struct cr_ctx cr_ctx;
  union user_arg_ret arg_retval;
  enum cr_status rc;
#if defined(V7_ENABLE_STACK_TRACKING)
  struct stack_track_ctx stack_track_ctx;
#endif
  int saved_line_no = v7->line_no;

#if defined(V7_ENABLE_STACK_TRACKING)
  v7_stack_track_start(v7, &stack_track_ctx);
#endif

  v7->pstate.source_code = v7->pstate.pc = src;
  v7->pstate.src_end = src + src_len;
  v7->pstate.file_name = "<stdin>";
  v7->pstate.line_no = 1;
  v7->pstate.in_function = 0;
  v7->pstate.in_loop = 0;
  v7->pstate.in_switch = 0;

  /*
   * TODO(dfrank): `v7->parser.line_no` vs `v7->line_no` is confusing.  probaby
   * we need to refactor it.
   *
   * See comment for v7->line_no in core.h for some details.
   */
  v7->line_no = 1;

  next_tok(v7);
  /*
   * setup initial state for "after newline" tracking.
   * next_tok will consume our token and position the current line
   * position at the beginning of the next token.
   * While processing the first token, both the leading and the
   * trailing newlines will be counted and thus it will create a spurious
   * "after newline" condition at the end of the first token
   * regardless if there is actually a newline after it.
   */
  for (p = src; isspace((int) *p); p++) {
    if (*p == '\n') {
      v7->pstate.prev_line_no++;
    }
  }

  /* init cr context */
  cr_context_init(&cr_ctx, &arg_retval, sizeof(arg_retval), _fid_descrs);

  /* prepare first function call: fid_mul_sum */
  if (is_json) {
    CR_FIRST_CALL_PREPARE_C(&cr_ctx, fid_parse_terminal);
  } else {
    CR_FIRST_CALL_PREPARE_C(&cr_ctx, fid_parse_script);
  }

  /* proceed to coroutine execution */
  rc = parser_cr_exec(&cr_ctx, v7, a);

  /* set `rcode` depending on coroutine state */
  switch (rc) {
    case CR_RES__OK:
      rcode = V7_OK;
      break;
    case CR_RES__ERR_UNCAUGHT_EXCEPTION:
      switch ((enum parser_exc_id) CR_THROWN_C(&cr_ctx)) {
        case PARSER_EXC_ID__SYNTAX_ERROR:
          rcode = V7_SYNTAX_ERROR;
          error_msg = "Syntax error";
          break;

        default:
          rcode = V7_INTERNAL_ERROR;
          error_msg = "Internal error: no exception id set";
          break;
      }
      break;
    default:
      rcode = V7_INTERNAL_ERROR;
      error_msg = "Internal error: unexpected parser coroutine return code";
      break;
  }

#if defined(V7_TRACK_MAX_PARSER_STACK_SIZE)
  /* remember how much stack space was consumed */

  if (v7->parser_stack_data_max_size < cr_ctx.stack_data.size) {
    v7->parser_stack_data_max_size = cr_ctx.stack_data.size;
#ifndef NO_LIBC
    printf("parser_stack_data_max_size=%u\n",
           (unsigned int) v7->parser_stack_data_max_size);
#endif
  }

  if (v7->parser_stack_ret_max_size < cr_ctx.stack_ret.size) {
    v7->parser_stack_ret_max_size = cr_ctx.stack_ret.size;
#ifndef NO_LIBC
    printf("parser_stack_ret_max_size=%u\n",
           (unsigned int) v7->parser_stack_ret_max_size);
#endif
  }

#if defined(CR_TRACK_MAX_STACK_LEN)
  if (v7->parser_stack_data_max_len < cr_ctx.stack_data_max_len) {
    v7->parser_stack_data_max_len = cr_ctx.stack_data_max_len;
#ifndef NO_LIBC
    printf("parser_stack_data_max_len=%u\n",
           (unsigned int) v7->parser_stack_data_max_len);
#endif
  }

  if (v7->parser_stack_ret_max_len < cr_ctx.stack_ret_max_len) {
    v7->parser_stack_ret_max_len = cr_ctx.stack_ret_max_len;
#ifndef NO_LIBC
    printf("parser_stack_ret_max_len=%u\n",
           (unsigned int) v7->parser_stack_ret_max_len);
#endif
  }
#endif

#endif

  /* free resources occupied by context (at least, "stack" arrays) */
  cr_context_free(&cr_ctx);

#if defined(V7_ENABLE_STACK_TRACKING)
  {
    int diff = v7_stack_track_end(v7, &stack_track_ctx);
    if (diff > v7->stack_stat[V7_STACK_STAT_PARSER]) {
      v7->stack_stat[V7_STACK_STAT_PARSER] = diff;
    }
  }
#endif

  /* Check if AST was overflown */
  if (a->has_overflow) {
    rcode = v7_throwf(v7, SYNTAX_ERROR,
                      "Script too large (try V7_LARGE_AST build option)");
    V7_THROW(V7_AST_TOO_LARGE);
  }

  if (rcode == V7_OK && v7->cur_tok != TOK_END_OF_INPUT) {
    rcode = V7_SYNTAX_ERROR;
    error_msg = "Syntax error";
  }

  if (rcode != V7_OK) {
    unsigned long col = get_column(v7->pstate.source_code, v7->tok);
    int line_len = 0;

    assert(error_msg != NULL);

    for (p = v7->tok - col; p < v7->pstate.src_end && *p != '\0' && *p != '\n';
         p++) {
      line_len++;
    }

    /* fixup line number: line_no points to the beginning of the next token */
    for (; p < v7->pstate.pc; p++) {
      if (*p == '\n') {
        v7->pstate.line_no--;
      }
    }

    /*
     * We already have a proper `rcode`, that's why we discard returned value
     * of `v7_throwf()`, which is always `V7_EXEC_EXCEPTION`.
     *
     * TODO(dfrank): probably get rid of distinct error types, and use just
     * `V7_JS_EXCEPTION`. However it would be good to have a way to get exact
     * error type, so probably error object should contain some property with
     * error code, but it would make exceptions even more expensive, etc, etc.
     */
    {
      enum v7_err _tmp;
      _tmp = v7_throwf(v7, SYNTAX_ERROR, "%s at line %d col %lu:\n%.*s\n%*s^",
                       error_msg, v7->pstate.line_no, col, line_len,
                       v7->tok - col, (int) col - 1, "");
      (void) _tmp;
    }
    V7_THROW(rcode);
  }

clean:
  v7->line_no = saved_line_no;
  return rcode;
}

#endif /* V7_NO_COMPILER */
#ifdef V7_MODULE_LINES
#line 1 "v7/src/compiler.c"
#endif
/*
 * Copyright (c) 2014 Cesanta Software Limited
 * All rights reserved
 */

/* Amalgamated: #include "v7/src/internal.h" */
/* Amalgamated: #include "v7/src/compiler.h" */
/* Amalgamated: #include "v7/src/std_error.h" */
/* Amalgamated: #include "v7/src/core.h" */
/* Amalgamated: #include "v7/src/function.h" */
/* Amalgamated: #include "v7/src/exceptions.h" */
/* Amalgamated: #include "v7/src/conversion.h" */
/* Amalgamated: #include "v7/src/regexp.h" */

#if !defined(V7_NO_COMPILER)

/*
 * The bytecode compiler takes an AST as input and produces one or more
 * bcode structure as output.
 *
 * Each script or function body is compiled into it's own bcode structure.
 *
 * Each bcode stream produces a new value on the stack, i.e. its overall
 * stack diagram is: `( -- a)`
 *
 * This value will be then popped by the function caller or by v7_exec in case
 * of scripts.
 *
 * In JS, the value of a script is the value of the last statement.
 * A script with no statement has an `undefined` value.
 * Functions instead require an explicit return value, so this matters only
 * for `v7_exec` and JS `eval`.
 *
 * Since an empty script has an undefined value, and each script has to
 * yield a value, the script/function prologue consists of a PUSH_UNDEFINED.
 *
 * Each statement will be compiled to push a value on the stack.
 * When a statement begins evaluating, the current TOS is thus either
 * the value of the previous statement or `undefined` in case of the first
 * statement.
 *
 * Every statement of a given script/function body always evaluates at the same
 * stack depth.
 *
 * In order to achieve that, after a statement is compiled out, a SWAP_DROP
 * opcode is emitted, that drops the value of the previous statement (or the
 * initial `undefined`). Dropping the value after the next statement is
 * evaluated and not before has allows us to correctly implement exception
 * behaviour and the break statement.
 *
 * Compound statements are constructs such as `if`/`while`/`for`/`try`. These
 * constructs contain a body consisting of a possibly empty statement list.
 *
 * Unlike normal statements, compound statements don't produce a value
 * themselves. Their value is either the value of their last executed statement
 * in their body, or the previous statement in case their body is empty or not
 * evaluated at all.
 *
 * An example is:
 *
 * [source,js]
 * ----
 * try {
 *   42;
 *   someUnexistingVariable;
 * } catch(e) {
 *   while(true) {}
 *     if(true) {
 *     }
 *     if(false) {
 *       2;
 *     }
 *     break;
 *   }
 * }
 * ----
 */

static const enum ast_tag assign_ast_map[] = {
    AST_REM, AST_MUL, AST_DIV,    AST_XOR,    AST_ADD,    AST_SUB,
    AST_OR,  AST_AND, AST_LSHIFT, AST_RSHIFT, AST_URSHIFT};

#ifdef V7_BCODE_DUMP
extern void dump_bcode(struct v7 *v7, FILE *f, struct bcode *bcode);
#endif

V7_PRIVATE enum v7_err compile_expr_builder(struct bcode_builder *bbuilder,
                                            struct ast *a, ast_off_t *ppos);

V7_PRIVATE enum v7_err compile_function(struct v7 *v7, struct ast *a,
                                        ast_off_t *ppos, struct bcode *bcode);

V7_PRIVATE enum v7_err binary_op(struct bcode_builder *bbuilder,
                                 enum ast_tag tag) {
  uint8_t op;
  enum v7_err rcode = V7_OK;
  struct v7 *v7 = bbuilder->v7;

  switch (tag) {
    case AST_ADD:
      op = OP_ADD;
      break;
    case AST_SUB:
      op = OP_SUB;
      break;
    case AST_REM:
      op = OP_REM;
      break;
    case AST_MUL:
      op = OP_MUL;
      break;
    case AST_DIV:
      op = OP_DIV;
      break;
    case AST_LSHIFT:
      op = OP_LSHIFT;
      break;
    case AST_RSHIFT:
      op = OP_RSHIFT;
      break;
    case AST_URSHIFT:
      op = OP_URSHIFT;
      break;
    case AST_OR:
      op = OP_OR;
      break;
    case AST_XOR:
      op = OP_XOR;
      break;
    case AST_AND:
      op = OP_AND;
      break;
    case AST_EQ_EQ:
      op = OP_EQ_EQ;
      break;
    case AST_EQ:
      op = OP_EQ;
      break;
    case AST_NE:
      op = OP_NE;
      break;
    case AST_NE_NE:
      op = OP_NE_NE;
      break;
    case AST_LT:
      op = OP_LT;
      break;
    case AST_LE:
      op = OP_LE;
      break;
    case AST_GT:
      op = OP_GT;
      break;
    case AST_GE:
      op = OP_GE;
      break;
    case AST_INSTANCEOF:
      op = OP_INSTANCEOF;
      break;
    default:
      rcode = v7_throwf(bbuilder->v7, SYNTAX_ERROR, "unknown binary ast node");
      V7_THROW(V7_SYNTAX_ERROR);
  }
  bcode_op(bbuilder, op);
clean:
  return rcode;
}

V7_PRIVATE enum v7_err compile_binary(struct bcode_builder *bbuilder,
                                      struct ast *a, ast_off_t *ppos,
                                      enum ast_tag tag) {
  enum v7_err rcode = V7_OK;
  struct v7 *v7 = bbuilder->v7;
  V7_TRY(compile_expr_builder(bbuilder, a, ppos));
  V7_TRY(compile_expr_builder(bbuilder, a, ppos));

  V7_TRY(binary_op(bbuilder, tag));
clean:
  return rcode;
}

/*
 * `pos` should be an offset of the byte right after a tag
 */
static lit_t string_lit(struct bcode_builder *bbuilder, struct ast *a,
                        ast_off_t pos) {
  size_t i = 0, name_len;
  val_t v = V7_UNDEFINED;
  struct mbuf *m = &bbuilder->lit;
  char *name = ast_get_inlined_data(a, pos, &name_len);

/* temp disabled because of short lits */
#if 0
  for (i = 0; i < m->len / sizeof(val_t); i++) {
    v = ((val_t *) m->buf)[i];
    if (v7_is_string(v)) {
      size_t l;
      const char *s = v7_get_string(bbuilder->v7, &v, &l);
      if (name_len == l && memcmp(name, s, name_len) == 0) {
        lit_t res;
        memset(&res, 0, sizeof(res));
        res.idx = i + bcode_max_inline_type_tag;
        return res;
      }
    }
  }
#else
  (void) i;
  (void) v;
  (void) m;
#endif
  return bcode_add_lit(bbuilder, v7_mk_string(bbuilder->v7, name, name_len, 1));
}

#if V7_ENABLE__RegExp
WARN_UNUSED_RESULT
static enum v7_err regexp_lit(struct bcode_builder *bbuilder, struct ast *a,
                              ast_off_t pos, lit_t *res) {
  enum v7_err rcode = V7_OK;
  size_t name_len;
  char *p;
  char *name = ast_get_inlined_data(a, pos, &name_len);
  val_t tmp = V7_UNDEFINED;
  struct v7 *v7 = bbuilder->v7;

  for (p = name + name_len - 1; *p != '/';) p--;

  V7_TRY(v7_mk_regexp(bbuilder->v7, name + 1, p - (name + 1), p + 1,
                      (name + name_len) - p - 1, &tmp));

  *res = bcode_add_lit(bbuilder, tmp);

clean:
  return rcode;
}
#endif

#ifndef V7_DISABLE_LINE_NUMBERS
static void append_lineno_if_changed(struct v7 *v7,
                                     struct bcode_builder *bbuilder,
                                     int line_no) {
  (void) v7;
  if (line_no != 0 && line_no != v7->line_no) {
    v7->line_no = line_no;
    bcode_append_lineno(bbuilder, line_no);
  }
}
#else
static void append_lineno_if_changed(struct v7 *v7,
                                     struct bcode_builder *bbuilder,
                                     int line_no) {
  (void) v7;
  (void) bbuilder;
  (void) line_no;
}
#endif

static enum ast_tag fetch_tag(struct v7 *v7, struct bcode_builder *bbuilder,
                              struct ast *a, ast_off_t *ppos,
                              ast_off_t *ppos_after_tag) {
  enum ast_tag ret = ast_fetch_tag(a, ppos);
  int line_no = ast_get_line_no(a, *ppos);
  append_lineno_if_changed(v7, bbuilder, line_no);
  if (ppos_after_tag != NULL) {
    *ppos_after_tag = *ppos;
  }
  ast_move_to_children(a, ppos);
  return ret;
}

/*
 * a++ and a-- need to ignore the updated value.
 *
 * Call this before updating the lhs.
 */
static void fixup_post_op(struct bcode_builder *bbuilder, enum ast_tag tag) {
  if (tag == AST_POSTINC || tag == AST_POSTDEC) {
    bcode_op(bbuilder, OP_UNSTASH);
  }
}

/*
 * evaluate rhs expression.
 * ++a and a++ are equivalent to a+=1
 */
static enum v7_err eval_assign_rhs(struct bcode_builder *bbuilder,
                                   struct ast *a, ast_off_t *ppos,
                                   enum ast_tag tag) {
  enum v7_err rcode = V7_OK;
  struct v7 *v7 = bbuilder->v7;

  /* a++ and a-- need to preserve initial value. */
  if (tag == AST_POSTINC || tag == AST_POSTDEC) {
    bcode_op(bbuilder, OP_STASH);
  }
  if (tag >= AST_PREINC && tag <= AST_POSTDEC) {
    bcode_op(bbuilder, OP_PUSH_ONE);
  } else {
    V7_TRY(compile_expr_builder(bbuilder, a, ppos));
  }

  switch (tag) {
    case AST_PREINC:
    case AST_POSTINC:
      bcode_op(bbuilder, OP_ADD);
      break;
    case AST_PREDEC:
    case AST_POSTDEC:
      bcode_op(bbuilder, OP_SUB);
      break;
    case AST_ASSIGN:
      /* no operation */
      break;
    default:
      binary_op(bbuilder, assign_ast_map[tag - AST_ASSIGN - 1]);
  }

clean:
  return rcode;
}

static enum v7_err compile_assign(struct bcode_builder *bbuilder, struct ast *a,
                                  ast_off_t *ppos, enum ast_tag tag) {
  lit_t lit;
  enum ast_tag ntag;
  enum v7_err rcode = V7_OK;
  struct v7 *v7 = bbuilder->v7;
  ast_off_t pos_after_tag;

  ntag = fetch_tag(v7, bbuilder, a, ppos, &pos_after_tag);

  switch (ntag) {
    case AST_IDENT:
      lit = string_lit(bbuilder, a, pos_after_tag);
      if (tag != AST_ASSIGN) {
        bcode_op_lit(bbuilder, OP_GET_VAR, lit);
      }

      V7_TRY(eval_assign_rhs(bbuilder, a, ppos, tag));
      bcode_op_lit(bbuilder, OP_SET_VAR, lit);

      fixup_post_op(bbuilder, tag);
      break;
    case AST_MEMBER:
    case AST_INDEX:
      switch (ntag) {
        case AST_MEMBER:
          lit = string_lit(bbuilder, a, pos_after_tag);
          V7_TRY(compile_expr_builder(bbuilder, a, ppos));
          bcode_push_lit(bbuilder, lit);
          break;
        case AST_INDEX:
          V7_TRY(compile_expr_builder(bbuilder, a, ppos));
          V7_TRY(compile_expr_builder(bbuilder, a, ppos));
          break;
        default:
          /* unreachable, compilers are dumb */
          V7_THROW(V7_SYNTAX_ERROR);
      }
      if (tag != AST_ASSIGN) {
        bcode_op(bbuilder, OP_2DUP);
        bcode_op(bbuilder, OP_GET);
      }

      V7_TRY(eval_assign_rhs(bbuilder, a, ppos, tag));
      bcode_op(bbuilder, OP_SET);

      fixup_post_op(bbuilder, tag);
      break;
    default:
      /* We end up here on expressions like `1 = 2;`, it's a ReferenceError */
      rcode = v7_throwf(bbuilder->v7, REFERENCE_ERROR, "unexpected ast node");
      V7_THROW(V7_SYNTAX_ERROR);
  }
clean:
  return rcode;
}

/*
 * Walks through all declarations (`var` and `function`) in the current scope,
 * and adds names of all of them to `bcode->ops`. Additionally, `function`
 * declarations are compiled right here, since they're hoisted in JS.
 */
static enum v7_err compile_local_vars(struct bcode_builder *bbuilder,
                                      struct ast *a, ast_off_t start,
                                      ast_off_t fvar) {
  ast_off_t next, fvar_end;
  char *name;
  size_t name_len;
  lit_t lit;
  enum v7_err rcode = V7_OK;
  struct v7 *v7 = bbuilder->v7;
  size_t names_end = 0;
  ast_off_t pos_after_tag;

  /* calculate `names_end`: offset at which names in `bcode->ops` end */
  names_end =
      (size_t)(bcode_end_names(bbuilder->ops.buf, bbuilder->bcode->names_cnt) -
               bbuilder->ops.buf);

  if (fvar != start) {
    /* iterate all `AST_VAR`s in the current scope */
    do {
      V7_CHECK_INTERNAL(fetch_tag(v7, bbuilder, a, &fvar, &pos_after_tag) ==
                        AST_VAR);

      next = ast_get_skip(a, pos_after_tag, AST_VAR_NEXT_SKIP);
      if (next == pos_after_tag) {
        next = 0;
      }

      fvar_end = ast_get_skip(a, pos_after_tag, AST_END_SKIP);

      /*
       * iterate all `AST_VAR_DECL`s and `AST_FUNC_DECL`s in the current
       * `AST_VAR`
       */
      while (fvar < fvar_end) {
        enum ast_tag tag = fetch_tag(v7, bbuilder, a, &fvar, &pos_after_tag);
        V7_CHECK_INTERNAL(tag == AST_VAR_DECL || tag == AST_FUNC_DECL);
        name = ast_get_inlined_data(a, pos_after_tag, &name_len);
        if (tag == AST_VAR_DECL) {
          /*
           * it's a `var` declaration, so, skip the value for now, it'll be set
           * to `undefined` initially
           */
          ast_skip_tree(a, &fvar);
        } else {
          /*
           * tag is an AST_FUNC_DECL: since functions in JS are hoisted,
           * we compile it and put `OP_SET_VAR` directly here
           */
          lit = string_lit(bbuilder, a, pos_after_tag);
          V7_TRY(compile_expr_builder(bbuilder, a, &fvar));
          bcode_op_lit(bbuilder, OP_SET_VAR, lit);

          /* function declarations are stack-neutral */
          bcode_op(bbuilder, OP_DROP);
          /*
           * Note: the `is_stack_neutral` flag will be set by `compile_stmt`
           * later, when it encounters `AST_FUNC_DECL` again.
           */
        }
        V7_TRY(bcode_add_name(bbuilder, name, name_len, &names_end));
      }

      if (next > 0) {
        fvar = next - 1;
      }

    } while (next != 0);
  }

clean:
  return rcode;
}

/*
 * Just like `compile_expr_builder`, but it takes additional argument:
 *`for_call`.
 * If it's non-zero, the stack is additionally populated with `this` value
 * for call.
 *
 * If there is a refinement (a dot, or a subscript), then it'll be the
 * appropriate object. Otherwise, it's `undefined`.
 *
 * In non-strict mode, `undefined` will be changed to Global Object at runtime,
 * see `OP_CALL` handling in `eval_bcode()`.
 */
V7_PRIVATE enum v7_err compile_expr_ext(struct bcode_builder *bbuilder,
                                        struct ast *a, ast_off_t *ppos,
                                        uint8_t for_call) {
  enum v7_err rcode = V7_OK;
  struct v7 *v7 = bbuilder->v7;
  ast_off_t pos_after_tag;
  enum ast_tag tag = fetch_tag(v7, bbuilder, a, ppos, &pos_after_tag);

  switch (tag) {
    case AST_MEMBER: {
      lit_t lit = string_lit(bbuilder, a, pos_after_tag);
      V7_TRY(compile_expr_builder(bbuilder, a, ppos));
      if (for_call) {
        /* current TOS will be used as `this` */
        bcode_op(bbuilder, OP_DUP);
      }
      bcode_push_lit(bbuilder, lit);
      bcode_op(bbuilder, OP_GET);
      break;
    }

    case AST_INDEX:
      V7_TRY(compile_expr_builder(bbuilder, a, ppos));
      if (for_call) {
        /* current TOS will be used as `this` */
        bcode_op(bbuilder, OP_DUP);
      }
      V7_TRY(compile_expr_builder(bbuilder, a, ppos));
      bcode_op(bbuilder, OP_GET);
      break;

    default:
      if (for_call) {
        /*
         * `this` will be an `undefined` (but it may change to Global Object
         * at runtime)
         */
        bcode_op(bbuilder, OP_PUSH_UNDEFINED);
      }
      *ppos = pos_after_tag - 1;
      V7_TRY(compile_expr_builder(bbuilder, a, ppos));
      break;
  }

clean:
  return rcode;
}

V7_PRIVATE enum v7_err compile_delete(struct bcode_builder *bbuilder,
                                      struct ast *a, ast_off_t *ppos) {
  enum v7_err rcode = V7_OK;
  struct v7 *v7 = bbuilder->v7;
  ast_off_t pos_after_tag;
  enum ast_tag tag = fetch_tag(v7, bbuilder, a, ppos, &pos_after_tag);

  switch (tag) {
    case AST_MEMBER: {
      /* Delete a specified property of an object */
      lit_t lit = string_lit(bbuilder, a, pos_after_tag);
      /* put an object to delete property from */
      V7_TRY(compile_expr_builder(bbuilder, a, ppos));
      /* put a property name */
      bcode_push_lit(bbuilder, lit);
      bcode_op(bbuilder, OP_DELETE);
      break;
    }

    case AST_INDEX:
      /* Delete a specified property of an object */
      /* put an object to delete property from */
      V7_TRY(compile_expr_builder(bbuilder, a, ppos));
      /* put a property name */
      V7_TRY(compile_expr_builder(bbuilder, a, ppos));
      bcode_op(bbuilder, OP_DELETE);
      break;

    case AST_IDENT:
      /* Delete the scope variable (or throw an error if strict mode) */
      if (!bbuilder->bcode->strict_mode) {
        /* put a property name */
        bcode_push_lit(bbuilder, string_lit(bbuilder, a, pos_after_tag));
        bcode_op(bbuilder, OP_DELETE_VAR);
      } else {
        rcode =
            v7_throwf(bbuilder->v7, SYNTAX_ERROR,
                      "Delete of an unqualified identifier in strict mode.");
        V7_THROW(V7_SYNTAX_ERROR);
      }
      break;

    case AST_UNDEFINED:
      /*
       * `undefined` should actually be an undeletable property of the Global
       * Object, so, trying to delete it just yields `false`
       */
      bcode_op(bbuilder, OP_PUSH_FALSE);
      break;

    default:
      /*
       * For all other cases, we evaluate the expression given to `delete`
       * for side effects, then drop the result, and yield `true`
       */
      *ppos = pos_after_tag - 1;
      V7_TRY(compile_expr_builder(bbuilder, a, ppos));
      bcode_op(bbuilder, OP_DROP);
      bcode_op(bbuilder, OP_PUSH_TRUE);
      break;
  }

clean:
  return rcode;
}

V7_PRIVATE enum v7_err compile_expr_builder(struct bcode_builder *bbuilder,
                                            struct ast *a, ast_off_t *ppos) {
  enum v7_err rcode = V7_OK;
  struct v7 *v7 = bbuilder->v7;
  ast_off_t pos_after_tag;
  enum ast_tag tag = fetch_tag(v7, bbuilder, a, ppos, &pos_after_tag);

  switch (tag) {
    case AST_ADD:
    case AST_SUB:
    case AST_REM:
    case AST_MUL:
    case AST_DIV:
    case AST_LSHIFT:
    case AST_RSHIFT:
    case AST_URSHIFT:
    case AST_OR:
    case AST_XOR:
    case AST_AND:
    case AST_EQ_EQ:
    case AST_EQ:
    case AST_NE:
    case AST_NE_NE:
    case AST_LT:
    case AST_LE:
    case AST_GT:
    case AST_GE:
    case AST_INSTANCEOF:
      V7_TRY(compile_binary(bbuilder, a, ppos, tag));
      break;
    case AST_LOGICAL_NOT:
      V7_TRY(compile_expr_builder(bbuilder, a, ppos));
      bcode_op(bbuilder, OP_LOGICAL_NOT);
      break;
    case AST_NOT:
      V7_TRY(compile_expr_builder(bbuilder, a, ppos));
      bcode_op(bbuilder, OP_NOT);
      break;
    case AST_POSITIVE:
      V7_TRY(compile_expr_builder(bbuilder, a, ppos));
      bcode_op(bbuilder, OP_POS);
      break;
    case AST_NEGATIVE:
      V7_TRY(compile_expr_builder(bbuilder, a, ppos));
      bcode_op(bbuilder, OP_NEG);
      break;
    case AST_IDENT:
      bcode_op_lit(bbuilder, OP_GET_VAR,
                   string_lit(bbuilder, a, pos_after_tag));
      break;
    case AST_MEMBER:
    case AST_INDEX:
      /*
       * These two are handled by the "extended" version of this function,
       * since we may need to put `this` value on stack, for "method pattern
       * invocation".
       *
       * First of all, restore starting AST position, and then call extended
       * function.
       */
      *ppos = pos_after_tag - 1;
      V7_TRY(compile_expr_ext(bbuilder, a, ppos, 0 /*not for call*/));
      break;
    case AST_IN:
      V7_TRY(compile_expr_builder(bbuilder, a, ppos));
      V7_TRY(compile_expr_builder(bbuilder, a, ppos));
      bcode_op(bbuilder, OP_IN);
      break;
    case AST_TYPEOF: {
      ast_off_t lookahead = *ppos;
      tag = fetch_tag(v7, bbuilder, a, &lookahead, &pos_after_tag);
      if (tag == AST_IDENT) {
        *ppos = lookahead;
        bcode_op_lit(bbuilder, OP_SAFE_GET_VAR,
                     string_lit(bbuilder, a, pos_after_tag));
      } else {
        V7_TRY(compile_expr_builder(bbuilder, a, ppos));
      }
      bcode_op(bbuilder, OP_TYPEOF);
      break;
    }
    case AST_ASSIGN:
    case AST_PREINC:
    case AST_PREDEC:
    case AST_POSTINC:
    case AST_POSTDEC:
    case AST_REM_ASSIGN:
    case AST_MUL_ASSIGN:
    case AST_DIV_ASSIGN:
    case AST_XOR_ASSIGN:
    case AST_PLUS_ASSIGN:
    case AST_MINUS_ASSIGN:
    case AST_OR_ASSIGN:
    case AST_AND_ASSIGN:
    case AST_LSHIFT_ASSIGN:
    case AST_RSHIFT_ASSIGN:
    case AST_URSHIFT_ASSIGN:
      V7_TRY(compile_assign(bbuilder, a, ppos, tag));
      break;
    case AST_COND: {
      /*
      * A ? B : C
      *
      * ->
      *
      *   <A>
      *   JMP_FALSE false
      *   <B>
      *   JMP end
      * false:
      *   <C>
      * end:
      *
      */
      bcode_off_t false_label, end_label;
      V7_TRY(compile_expr_builder(bbuilder, a, ppos));
      false_label = bcode_op_target(bbuilder, OP_JMP_FALSE);
      V7_TRY(compile_expr_builder(bbuilder, a, ppos));
      end_label = bcode_op_target(bbuilder, OP_JMP);
      bcode_patch_target(bbuilder, false_label, bcode_pos(bbuilder));
      V7_TRY(compile_expr_builder(bbuilder, a, ppos));
      bcode_patch_target(bbuilder, end_label, bcode_pos(bbuilder));
      break;
    }
    case AST_LOGICAL_OR:
    case AST_LOGICAL_AND: {
      /*
       * A && B
       *
       * ->
       *
       *   <A>
       *   JMP_FALSE end
       *   POP
       *   <B>
       * end:
       *
       */
      bcode_off_t end_label;
      V7_TRY(compile_expr_builder(bbuilder, a, ppos));
      bcode_op(bbuilder, OP_DUP);
      end_label = bcode_op_target(
          bbuilder, tag == AST_LOGICAL_AND ? OP_JMP_FALSE : OP_JMP_TRUE);
      bcode_op(bbuilder, OP_DROP);
      V7_TRY(compile_expr_builder(bbuilder, a, ppos));
      bcode_patch_target(bbuilder, end_label, bcode_pos(bbuilder));
      break;
    }
    /*
     * A, B, C
     *
     * ->
     *
     * <A>
     * DROP
     * <B>
     * DROP
     * <C>
     */
    case AST_SEQ: {
      ast_off_t end = ast_get_skip(a, pos_after_tag, AST_END_SKIP);
      while (*ppos < end) {
        V7_TRY(compile_expr_builder(bbuilder, a, ppos));
        if (*ppos < end) {
          bcode_op(bbuilder, OP_DROP);
        }
      }
      break;
    }
    case AST_CALL:
    case AST_NEW: {
      /*
       * f()
       *
       * ->
       *
       *  PUSH_UNDEFINED (value for `this`)
       *  GET_VAR "f"
       *  CHECK_CALL
       *  CALL 0 args
       *
       * ---------------
       *
       * f(a, b)
       *
       * ->
       *
       *  PUSH_UNDEFINED (value for `this`)
       *  GET_VAR "f"
       *  CHECK_CALL
       *  GET_VAR "a"
       *  GET_VAR "b"
       *  CALL 2 args
       *
       * ---------------
       *
       * o.f(a, b)
       *
       * ->
       *
       *  GET_VAR "o" (so that `this` will be an `o`)
       *  DUP         (we'll also need `o` for GET below, so, duplicate it)
       *  PUSH_LIT "f"
       *  GET         (get property "f" of the object "o")
       *  CHECK_CALL
       *  GET_VAR "a"
       *  GET_VAR "b"
       *  CALL 2 args
       *
       */
      int args;
      ast_off_t end = ast_get_skip(a, pos_after_tag, AST_END_SKIP);

      V7_TRY(compile_expr_ext(bbuilder, a, ppos, 1 /*for call*/));
      bcode_op(bbuilder, OP_CHECK_CALL);
      for (args = 0; *ppos < end; args++) {
        V7_TRY(compile_expr_builder(bbuilder, a, ppos));
      }
      bcode_op(bbuilder, (tag == AST_CALL ? OP_CALL : OP_NEW));
      if (args > 0x7f) {
        rcode = v7_throwf(bbuilder->v7, SYNTAX_ERROR, "too many arguments");
        V7_THROW(V7_SYNTAX_ERROR);
      }
      bcode_op(bbuilder, (uint8_t) args);
      break;
    }
    case AST_DELETE: {
      V7_TRY(compile_delete(bbuilder, a, ppos));
      break;
    }
    case AST_OBJECT: {
      /*
       * {a:<B>, ...}
       *
       * ->
       *
       *   CREATE_OBJ
       *   DUP
       *   PUSH_LIT "a"
       *   <B>
       *   SET
       *   POP
       *   ...
       */

      /*
       * Literal indices of property names of current object literal.
       * Needed for strict mode: we need to keep track of the added
       * properties, since duplicates are not allowed
       */
      struct mbuf cur_literals;
      lit_t lit;
      ast_off_t end = ast_get_skip(a, pos_after_tag, AST_END_SKIP);
      mbuf_init(&cur_literals, 0);

      bcode_op(bbuilder, OP_CREATE_OBJ);
      while (*ppos < end) {
        tag = fetch_tag(v7, bbuilder, a, ppos, &pos_after_tag);
        switch (tag) {
          case AST_PROP:
            bcode_op(bbuilder, OP_DUP);
            lit = string_lit(bbuilder, a, pos_after_tag);

/* disabled because we broke get_lit */
#if 0
            if (bbuilder->bcode->strict_mode) {
              /*
               * In strict mode, check for duplicate property names in
               * object literals
               */
              char *plit;
              for (plit = (char *) cur_literals.buf;
                   (char *) plit < cur_literals.buf + cur_literals.len;
                   plit++) {
                const char *str1, *str2;
                size_t size1, size2;
                v7_val_t val1, val2;

                val1 = bcode_get_lit(bbuilder->bcode, lit);
                str1 = v7_get_string(bbuilder->v7, &val1, &size1);

                val2 = bcode_get_lit(bbuilder->bcode, *plit);
                str2 = v7_get_string(bbuilder->v7, &val2, &size2);

                if (size1 == size2 && memcmp(str1, str2, size1) == 0) {
                  /* found already existing property of the same name */
                  rcode = v7_throwf(bbuilder->v7, SYNTAX_ERROR,
                                    "duplicate data property in object literal "
                                    "is not allowed in strict mode");
                  V7_THROW2(V7_SYNTAX_ERROR, ast_object_clean);
                }
              }
              mbuf_append(&cur_literals, &lit, sizeof(lit));
            }
#endif
            bcode_push_lit(bbuilder, lit);
            V7_TRY(compile_expr_builder(bbuilder, a, ppos));
            bcode_op(bbuilder, OP_SET);
            bcode_op(bbuilder, OP_DROP);
            break;
          default:
            rcode = v7_throwf(bbuilder->v7, SYNTAX_ERROR, "not implemented");
            V7_THROW2(V7_SYNTAX_ERROR, ast_object_clean);
        }
      }

    ast_object_clean:
      mbuf_free(&cur_literals);
      if (rcode != V7_OK) {
        V7_THROW(rcode);
      }
      break;
    }
    case AST_ARRAY: {
      /*
       * [<A>,,<B>,...]
       *
       * ->
       *
       *   CREATE_ARR
       *   PUSH_ZERO
       *
       *   2DUP
       *   <A>
       *   SET
       *   POP
       *   PUSH_ONE
       *   ADD
       *
       *   PUSH_ONE
       *   ADD
       *
       *   2DUP
       *   <B>
       *   ...
       *   POP // tmp index
       *
       * TODO(mkm): optimize this out. we can have more compact array push
       * that uses a special marker value for missing array elements
       * (which are not the same as undefined btw)
       */
      ast_off_t end = ast_get_skip(a, pos_after_tag, AST_END_SKIP);
      bcode_op(bbuilder, OP_CREATE_ARR);
      bcode_op(bbuilder, OP_PUSH_ZERO);
      while (*ppos < end) {
        ast_off_t lookahead = *ppos;
        tag = fetch_tag(v7, bbuilder, a, &lookahead, &pos_after_tag);
        if (tag != AST_NOP) {
          bcode_op(bbuilder, OP_2DUP);
          V7_TRY(compile_expr_builder(bbuilder, a, ppos));
          bcode_op(bbuilder, OP_SET);
          bcode_op(bbuilder, OP_DROP);
        } else {
          *ppos = lookahead; /* skip nop */
        }
        bcode_op(bbuilder, OP_PUSH_ONE);
        bcode_op(bbuilder, OP_ADD);
      }
      bcode_op(bbuilder, OP_DROP);
      break;
    }
    case AST_FUNC: {
      lit_t flit;

      /*
       * Create half-done function: without scope and prototype. The real
       * function will be created from this one during bcode evaluation: see
       * `bcode_instantiate_function()`.
       */
      val_t funv = mk_js_function(bbuilder->v7, NULL, V7_UNDEFINED);

      /* Create bcode in this half-done function */
      struct v7_js_function *func = get_js_function_struct(funv);
      func->bcode = (struct bcode *) calloc(1, sizeof(*bbuilder->bcode));
      bcode_init(func->bcode, bbuilder->bcode->strict_mode,
                 NULL /* will be set below */, 0);
      bcode_copy_filename_from(func->bcode, bbuilder->bcode);
      retain_bcode(bbuilder->v7, func->bcode);
      flit = bcode_add_lit(bbuilder, funv);

      *ppos = pos_after_tag - 1;
      V7_TRY(compile_function(v7, a, ppos, func->bcode));
      bcode_push_lit(bbuilder, flit);
      bcode_op(bbuilder, OP_FUNC_LIT);
      break;
    }
    case AST_THIS:
      bcode_op(bbuilder, OP_PUSH_THIS);
      break;
    case AST_VOID:
      V7_TRY(compile_expr_builder(bbuilder, a, ppos));
      bcode_op(bbuilder, OP_DROP);
      bcode_op(bbuilder, OP_PUSH_UNDEFINED);
      break;
    case AST_NULL:
      bcode_op(bbuilder, OP_PUSH_NULL);
      break;
    case AST_NOP:
    case AST_UNDEFINED:
      bcode_op(bbuilder, OP_PUSH_UNDEFINED);
      break;
    case AST_TRUE:
      bcode_op(bbuilder, OP_PUSH_TRUE);
      break;
    case AST_FALSE:
      bcode_op(bbuilder, OP_PUSH_FALSE);
      break;
    case AST_NUM: {
      double dv = ast_get_num(a, pos_after_tag);
      if (dv == 0) {
        bcode_op(bbuilder, OP_PUSH_ZERO);
      } else if (dv == 1) {
        bcode_op(bbuilder, OP_PUSH_ONE);
      } else {
        bcode_push_lit(bbuilder, bcode_add_lit(bbuilder, v7_mk_number(v7, dv)));
      }
      break;
    }
    case AST_STRING:
      bcode_push_lit(bbuilder, string_lit(bbuilder, a, pos_after_tag));
      break;
    case AST_REGEX:
#if V7_ENABLE__RegExp
    {
      lit_t tmp;
      rcode = regexp_lit(bbuilder, a, pos_after_tag, &tmp);
      if (rcode != V7_OK) {
        rcode = V7_SYNTAX_ERROR;
        goto clean;
      }

      bcode_push_lit(bbuilder, tmp);
      break;
    }
#else
      rcode =
          v7_throwf(bbuilder->v7, SYNTAX_ERROR, "Regexp support is disabled");
      V7_THROW(V7_SYNTAX_ERROR);
#endif
    case AST_LABEL:
    case AST_LABELED_BREAK:
    case AST_LABELED_CONTINUE:
      /* TODO(dfrank): implement */
      rcode = v7_throwf(bbuilder->v7, SYNTAX_ERROR, "not implemented");
      V7_THROW(V7_SYNTAX_ERROR);
    case AST_WITH:
      rcode = v7_throwf(bbuilder->v7, SYNTAX_ERROR, "not implemented");
      V7_THROW(V7_SYNTAX_ERROR);
    default:
      /*
       * We end up here if the AST is broken.
       *
       * It might be appropriate to return `V7_INTERNAL_ERROR` here, but since
       * we might receive AST from network or something, we just interpret
       * it as SyntaxError.
       */
      rcode = v7_throwf(bbuilder->v7, SYNTAX_ERROR, "unknown ast node %d",
                        (int) tag);
      V7_THROW(V7_SYNTAX_ERROR);
  }
clean:
  return rcode;
}

V7_PRIVATE enum v7_err compile_stmt(struct bcode_builder *bbuilder,
                                    struct ast *a, ast_off_t *ppos);

V7_PRIVATE enum v7_err compile_stmts(struct bcode_builder *bbuilder,
                                     struct ast *a, ast_off_t *ppos,
                                     ast_off_t end) {
  enum v7_err rcode = V7_OK;
  struct v7 *v7 = bbuilder->v7;

  while (*ppos < end) {
    V7_TRY(compile_stmt(bbuilder, a, ppos));
    if (!bbuilder->v7->is_stack_neutral) {
      bcode_op(bbuilder, OP_SWAP_DROP);
    } else {
      bbuilder->v7->is_stack_neutral = 0;
    }
  }
clean:
  return rcode;
}

V7_PRIVATE enum v7_err compile_stmt(struct bcode_builder *bbuilder,
                                    struct ast *a, ast_off_t *ppos) {
  ast_off_t end;
  enum ast_tag tag;
  ast_off_t cond, pos_after_tag;
  bcode_off_t body_target, body_label, cond_label;
  struct mbuf case_labels;
  enum v7_err rcode = V7_OK;
  struct v7 *v7 = bbuilder->v7;

  tag = fetch_tag(v7, bbuilder, a, ppos, &pos_after_tag);

  mbuf_init(&case_labels, 0);

  switch (tag) {
    /*
     * if(E) {
     *   BT...
     * } else {
     *   BF...
     * }
     *
     * ->
     *
     *   <E>
     *   JMP_FALSE body
     *   <BT>
     *   JMP end
     * body:
     *   <BF>
     * end:
     *
     * If else clause is omitted, it will emit output equivalent to:
     *
     * if(E) {BT} else undefined;
     */
    case AST_IF: {
      ast_off_t if_false;
      bcode_off_t end_label, if_false_label;
      end = ast_get_skip(a, pos_after_tag, AST_END_SKIP);
      if_false = ast_get_skip(a, pos_after_tag, AST_END_IF_TRUE_SKIP);

      V7_TRY(compile_expr_builder(bbuilder, a, ppos));
      if_false_label = bcode_op_target(bbuilder, OP_JMP_FALSE);

      /* body if true */
      V7_TRY(compile_stmts(bbuilder, a, ppos, if_false));

      if (if_false != end) {
        /* `else` branch is present */
        end_label = bcode_op_target(bbuilder, OP_JMP);

        /* will jump here if `false` */
        bcode_patch_target(bbuilder, if_false_label, bcode_pos(bbuilder));

        /* body if false */
        V7_TRY(compile_stmts(bbuilder, a, ppos, end));

        bcode_patch_target(bbuilder, end_label, bcode_pos(bbuilder));
      } else {
        /*
         * `else` branch is not present: just remember where we should
         * jump in case of `false`
         */
        bcode_patch_target(bbuilder, if_false_label, bcode_pos(bbuilder));
      }

      bbuilder->v7->is_stack_neutral = 1;
      break;
    }
    /*
     * while(C) {
     *   B...
     * }
     *
     * ->
     *
     *   TRY_PUSH_LOOP end
     *   JMP cond
     * body:
     *   <B>
     * cond:
     *   <C>
     *   JMP_TRUE body
     * end:
     *   JMP_IF_CONTINUE cond
     *   TRY_POP
     *
     */
    case AST_WHILE: {
      bcode_off_t end_label, continue_label, continue_target;

      end = ast_get_skip(a, pos_after_tag, AST_END_SKIP);
      cond = *ppos;
      ast_skip_tree(a, ppos);

      end_label = bcode_op_target(bbuilder, OP_TRY_PUSH_LOOP);

      /*
       * Condition check is at the end of the loop, this layout
       * reduces the number of jumps in the steady state.
       */
      cond_label = bcode_op_target(bbuilder, OP_JMP);
      body_target = bcode_pos(bbuilder);

      V7_TRY(compile_stmts(bbuilder, a, ppos, end));

      continue_target = bcode_pos(bbuilder);
      bcode_patch_target(bbuilder, cond_label, continue_target);

      V7_TRY(compile_expr_builder(bbuilder, a, &cond));
      body_label = bcode_op_target(bbuilder, OP_JMP_TRUE);
      bcode_patch_target(bbuilder, body_label, body_target);

      bcode_patch_target(bbuilder, end_label, bcode_pos(bbuilder));
      continue_label = bcode_op_target(bbuilder, OP_JMP_IF_CONTINUE);
      bcode_patch_target(bbuilder, continue_label, continue_target);
      bcode_op(bbuilder, OP_TRY_POP);

      bbuilder->v7->is_stack_neutral = 1;
      break;
    }
    case AST_BREAK:
      bcode_op(bbuilder, OP_BREAK);
      break;
    case AST_CONTINUE:
      bcode_op(bbuilder, OP_CONTINUE);
      break;
    /*
     * Frame objects (`v7->vals.call_stack`) contain one more hidden property:
     * `____t`, which is an array of offsets in bcode. Each element of the array
     * is an offset of either `catch` or `finally` block (distinguished by the
     * tag: `OFFSET_TAG_CATCH` or `OFFSET_TAG_FINALLY`). Let's call this array
     * as a "try stack". When evaluator enters new `try` block, it adds
     * appropriate offset(s) at the top of "try stack", and when we unwind the
     * stack, we can "pop" offsets from "try stack" at each level.
     *
     * try {
     *   TRY_B
     * } catch (e) {
     *   CATCH_B
     * } finally {
     *   FIN_B
     * }
     *
     * ->
     *    OP_TRY_PUSH_FINALLY finally
     *    OP_TRY_PUSH_CATCH catch
     *    <TRY_B>
     *    OP_TRY_POP
     *    JMP finally
     *  catch:
     *    OP_TRY_POP
     *    OP_ENTER_CATCH <e>
     *    <CATCH_B>
     *    OP_EXIT_CATCH
     *  finally:
     *    OP_TRY_POP
     *    <FIN_B>
     *    OP_AFTER_FINALLY
     *
     * ---------------
     *
     * try {
     *   TRY_B
     * } catch (e) {
     *   CATCH_B
     * }
     *
     * ->
     *    OP_TRY_PUSH_CATCH catch
     *    <TRY_B>
     *    OP_TRY_POP
     *    JMP end
     *  catch:
     *    OP_TRY_POP
     *    OP_ENTER_CATCH <e>
     *    <CATCH_B>
     *    OP_EXIT_CATCH
     *  end:
     *
     * ---------------
     *
     * try {
     *   TRY_B
     * } finally {
     *   FIN_B
     * }
     *
     * ->
     *    OP_TRY_PUSH_FINALLY finally
     *    <TRY_B>
     *  finally:
     *    OP_TRY_POP
     *    <FIN_B>
     *    OP_AFTER_FINALLY
     */
    case AST_TRY: {
      ast_off_t acatch, afinally;
      bcode_off_t finally_label, catch_label;

      end = ast_get_skip(a, pos_after_tag, AST_END_SKIP);
      acatch = ast_get_skip(a, pos_after_tag, AST_TRY_CATCH_SKIP);
      afinally = ast_get_skip(a, pos_after_tag, AST_TRY_FINALLY_SKIP);

      if (afinally != end) {
        /* `finally` clause is present: push its offset */
        finally_label = bcode_op_target(bbuilder, OP_TRY_PUSH_FINALLY);
      }

      if (acatch != afinally) {
        /* `catch` clause is present: push its offset */
        catch_label = bcode_op_target(bbuilder, OP_TRY_PUSH_CATCH);
      }

      /* compile statements of `try` block */
      V7_TRY(compile_stmts(bbuilder, a, ppos, acatch));

      if (acatch != afinally) {
        /* `catch` clause is present: compile it */
        bcode_off_t after_catch_label;

        /*
         * pop offset pushed by OP_TRY_PUSH_CATCH, and jump over the `catch`
         * block
         */
        bcode_op(bbuilder, OP_TRY_POP);
        after_catch_label = bcode_op_target(bbuilder, OP_JMP);

        /* --- catch --- */

        /* in case of exception in the `try` block above, we'll get here */
        bcode_patch_target(bbuilder, catch_label, bcode_pos(bbuilder));

        /* pop offset pushed by OP_TRY_PUSH_CATCH */
        bcode_op(bbuilder, OP_TRY_POP);

        /*
         * retrieve identifier where to store thrown error, and make sure
         * it is actually an indentifier (AST_IDENT)
         */
        tag = fetch_tag(v7, bbuilder, a, ppos, &pos_after_tag);
        V7_CHECK(tag == AST_IDENT, V7_SYNTAX_ERROR);

        /*
         * when we enter `catch` block, the TOS contains thrown value.
         * We should create private frame for the `catch` clause, and populate
         * a variable with the thrown value there.
         * The `OP_ENTER_CATCH` opcode does just that.
         */
        bcode_op_lit(bbuilder, OP_ENTER_CATCH,
                     string_lit(bbuilder, a, pos_after_tag));

        /*
         * compile statements until the end of `catch` clause
         * (`afinally` points to the end of the `catch` clause independently
         * of whether the `finally` clause is present)
         */
        V7_TRY(compile_stmts(bbuilder, a, ppos, afinally));

        /* pop private frame */
        bcode_op(bbuilder, OP_EXIT_CATCH);

        bcode_patch_target(bbuilder, after_catch_label, bcode_pos(bbuilder));
      }

      if (afinally != end) {
        /* `finally` clause is present: compile it */

        /* --- finally --- */

        /* after the `try` block above executes, we'll get here */
        bcode_patch_target(bbuilder, finally_label, bcode_pos(bbuilder));

        /* pop offset pushed by OP_TRY_PUSH_FINALLY */
        bcode_op(bbuilder, OP_TRY_POP);

        /* compile statements until the end of `finally` clause */
        V7_TRY(compile_stmts(bbuilder, a, ppos, end));

        bcode_op(bbuilder, OP_AFTER_FINALLY);
      }

      bbuilder->v7->is_stack_neutral = 1;
      break;
    }

    case AST_THROW: {
      V7_TRY(compile_expr_builder(bbuilder, a, ppos));
      bcode_op(bbuilder, OP_THROW);
      break;
    }

    /*
     * switch(E) {
     * default:
     *   D...
     * case C1:
     *   B1...
     * case C2:
     *   B2...
     * }
     *
     * ->
     *
     *   TRY_PUSH_SWITCH end
     *   <E>
     *   DUP
     *   <C1>
     *   EQ
     *   JMP_TRUE_DROP l1
     *   DUP
     *   <C2>
     *   EQ
     *   JMP_TRUE_DROP l2
     *   DROP
     *   JMP dfl
     *
     * dfl:
     *   <D>
     *
     * l1:
     *   <B1>
     *
     * l2:
     *   <B2>
     *
     * end:
     *   TRY_POP
     *
     * If the default case is missing we treat it as if had an empty body and
     * placed in last position (i.e. `dfl` label is replaced with `end`).
     *
     * Before emitting a case/default block (except the first one) we have to
     * drop the TOS resulting from evaluating the last expression
     */
    case AST_SWITCH: {
      bcode_off_t dfl_label, end_label;
      ast_off_t case_end, case_start;
      enum ast_tag case_tag;
      int i, has_default = 0, cases = 0;

      end = ast_get_skip(a, pos_after_tag, AST_END_SKIP);

      end_label = bcode_op_target(bbuilder, OP_TRY_PUSH_SWITCH);

      V7_TRY(compile_expr_builder(bbuilder, a, ppos));

      case_start = *ppos;
      /* first pass: evaluate case expression and generate jump table */
      while (*ppos < end) {
        case_tag = fetch_tag(v7, bbuilder, a, ppos, &pos_after_tag);
        assert(case_tag == AST_DEFAULT || case_tag == AST_CASE);

        case_end = ast_get_skip(a, pos_after_tag, AST_END_SKIP);

        switch (case_tag) {
          case AST_DEFAULT:
            /* default jump table entry must be the last one */
            break;
          case AST_CASE: {
            bcode_off_t case_label;
            bcode_op(bbuilder, OP_DUP);
            V7_TRY(compile_expr_builder(bbuilder, a, ppos));
            bcode_op(bbuilder, OP_EQ);
            case_label = bcode_op_target(bbuilder, OP_JMP_TRUE_DROP);
            cases++;
            mbuf_append(&case_labels, &case_label, sizeof(case_label));
            break;
          }
          default:
            assert(case_tag == AST_DEFAULT || case_tag == AST_CASE);
        }
        *ppos = case_end;
      }

      /* jmp table epilogue: unconditional jump to default case */
      bcode_op(bbuilder, OP_DROP);
      dfl_label = bcode_op_target(bbuilder, OP_JMP);

      *ppos = case_start;
      /* second pass: emit case bodies and patch jump table */

      for (i = 0; *ppos < end;) {
        case_tag = fetch_tag(v7, bbuilder, a, ppos, &pos_after_tag);
        assert(case_tag == AST_DEFAULT || case_tag == AST_CASE);
        assert(i <= cases);

        case_end = ast_get_skip(a, pos_after_tag, AST_END_SKIP);

        switch (case_tag) {
          case AST_DEFAULT:
            has_default = 1;
            bcode_patch_target(bbuilder, dfl_label, bcode_pos(bbuilder));
            V7_TRY(compile_stmts(bbuilder, a, ppos, case_end));
            break;
          case AST_CASE: {
            bcode_off_t case_label = ((bcode_off_t *) case_labels.buf)[i++];
            bcode_patch_target(bbuilder, case_label, bcode_pos(bbuilder));
            ast_skip_tree(a, ppos);
            V7_TRY(compile_stmts(bbuilder, a, ppos, case_end));
            break;
          }
          default:
            assert(case_tag == AST_DEFAULT || case_tag == AST_CASE);
        }

        *ppos = case_end;
      }
      mbuf_free(&case_labels);

      if (!has_default) {
        bcode_patch_target(bbuilder, dfl_label, bcode_pos(bbuilder));
      }

      bcode_patch_target(bbuilder, end_label, bcode_pos(bbuilder));
      bcode_op(bbuilder, OP_TRY_POP);

      bbuilder->v7->is_stack_neutral = 1;
      break;
    }
    /*
     * for(INIT,COND,IT) {
     *   B...
     * }
     *
     * ->
     *
     *   <INIT>
     *   DROP
     *   TRY_PUSH_LOOP end
     *   JMP cond
     * body:
     *   <B>
     * next:
     *   <IT>
     *   DROP
     * cond:
     *   <COND>
     *   JMP_TRUE body
     * end:
     *   JMP_IF_CONTINUE next
     *   TRY_POP
     *
     */
    case AST_FOR: {
      ast_off_t iter, body, lookahead;
      bcode_off_t end_label, continue_label, continue_target;
      end = ast_get_skip(a, pos_after_tag, AST_END_SKIP);
      body = ast_get_skip(a, pos_after_tag, AST_FOR_BODY_SKIP);

      lookahead = *ppos;
      tag = fetch_tag(v7, bbuilder, a, &lookahead, &pos_after_tag);
      /*
       * Support for `var` declaration in INIT
       */
      if (tag == AST_VAR) {
        ast_off_t fvar_end;
        lit_t lit;

        *ppos = lookahead;
        fvar_end = ast_get_skip(a, pos_after_tag, AST_END_SKIP);

        /*
         * Iterate through all vars in the given `var` declaration: they are
         * just like assigments here
         */
        while (*ppos < fvar_end) {
          tag = fetch_tag(v7, bbuilder, a, ppos, &pos_after_tag);
          /* Only var declarations are allowed (not function declarations) */
          V7_CHECK_INTERNAL(tag == AST_VAR_DECL);
          lit = string_lit(bbuilder, a, pos_after_tag);
          V7_TRY(compile_expr_builder(bbuilder, a, ppos));

          /* Just like an assigment */
          bcode_op_lit(bbuilder, OP_SET_VAR, lit);

          /* INIT is stack-neutral */
          bcode_op(bbuilder, OP_DROP);
        }
      } else {
        /* normal expression in INIT (not `var` declaration) */
        V7_TRY(compile_expr_builder(bbuilder, a, ppos));
        /* INIT is stack-neutral */
        bcode_op(bbuilder, OP_DROP);
      }
      cond = *ppos;
      ast_skip_tree(a, ppos);
      iter = *ppos;
      *ppos = body;

      end_label = bcode_op_target(bbuilder, OP_TRY_PUSH_LOOP);
      cond_label = bcode_op_target(bbuilder, OP_JMP);
      body_target = bcode_pos(bbuilder);
      V7_TRY(compile_stmts(bbuilder, a, ppos, end));

      continue_target = bcode_pos(bbuilder);

      V7_TRY(compile_expr_builder(bbuilder, a, &iter));
      bcode_op(bbuilder, OP_DROP);

      bcode_patch_target(bbuilder, cond_label, bcode_pos(bbuilder));

      /*
       * Handle for(INIT;;ITER)
       */
      lookahead = cond;
      tag = fetch_tag(v7, bbuilder, a, &lookahead, &pos_after_tag);
      if (tag == AST_NOP) {
        bcode_op(bbuilder, OP_JMP);
      } else {
        V7_TRY(compile_expr_builder(bbuilder, a, &cond));
        bcode_op(bbuilder, OP_JMP_TRUE);
      }
      body_label = bcode_add_target(bbuilder);
      bcode_patch_target(bbuilder, body_label, body_target);
      bcode_patch_target(bbuilder, end_label, bcode_pos(bbuilder));

      continue_label = bcode_op_target(bbuilder, OP_JMP_IF_CONTINUE);
      bcode_patch_target(bbuilder, continue_label, continue_target);

      bcode_op(bbuilder, OP_TRY_POP);

      bbuilder->v7->is_stack_neutral = 1;
      break;
    }
    /*
     * for(I in O) {
     *   B...
     * }
     *
     * ->
     *
     *   DUP
     *   <O>
     *   SWAP
     *   STASH
     *   DROP
     *   PUSH_PROP_ITER_CTX   # push initial iteration context
     *   TRY_PUSH_LOOP brend
     * loop:
     *   NEXT_PROP
     *   JMP_FALSE end
     *   SET_VAR <I>
     *   UNSTASH
     *   <B>
     * next:
     *   STASH
     *   DROP
     *   JMP loop
     * end:
     *   UNSTASH
     *   JMP try_pop:
     * brend:
     *              # got here after a `break` or `continue` from a loop body:
     *   JMP_IF_CONTINUE next
     *
     *              # we're not going to `continue`, so, need to remove an
     *              # extra stuff that was needed for the NEXT_PROP
     *
     *   SWAP_DROP  # drop iteration context
     *   SWAP_DROP  # drop <O>
     *   SWAP_DROP  # drop the value preceding the loop
     * try_pop:
     *   TRY_POP
     *
     */
    case AST_FOR_IN: {
      lit_t lit;
      bcode_off_t loop_label, loop_target, end_label, brend_label,
          continue_label, pop_label, continue_target;
      ast_off_t end = ast_get_skip(a, pos_after_tag, AST_END_SKIP);

      tag = fetch_tag(v7, bbuilder, a, ppos, &pos_after_tag);
      /* TODO(mkm) accept any l-value */
      if (tag == AST_VAR) {
        tag = fetch_tag(v7, bbuilder, a, ppos, &pos_after_tag);
        V7_CHECK_INTERNAL(tag == AST_VAR_DECL);
        lit = string_lit(bbuilder, a, pos_after_tag);
        ast_skip_tree(a, ppos);
      } else {
        V7_CHECK_INTERNAL(tag == AST_IDENT);
        lit = string_lit(bbuilder, a, pos_after_tag);
      }

      /*
       * preserve previous statement value.
       * We need to feed the previous value into the stash
       * because it's required for the loop steady state.
       *
       * The stash register is required to simplify the steady state stack
       * management, in particular the removal of value in 3rd position in case
       * a of not taken exit.
       *
       * TODO(mkm): consider having a stash OP that moves a value to the stash
       * register instead of copying it. The current behaviour has been
       * optimized for the `assign` use case which seems more common.
       */
      bcode_op(bbuilder, OP_DUP);
      V7_TRY(compile_expr_builder(bbuilder, a, ppos));
      bcode_op(bbuilder, OP_SWAP);
      bcode_op(bbuilder, OP_STASH);
      bcode_op(bbuilder, OP_DROP);

      /*
       * OP_NEXT_PROP needs the iteration context, let's push the initial one.
       */
      bcode_op(bbuilder, OP_PUSH_PROP_ITER_CTX);

      brend_label = bcode_op_target(bbuilder, OP_TRY_PUSH_LOOP);

      /* loop: */
      loop_target = bcode_pos(bbuilder);

      /*
       * The loop stead state begins with the following stack layout:
       * `( S:v o h )`
       */

      bcode_op(bbuilder, OP_NEXT_PROP);
      end_label = bcode_op_target(bbuilder, OP_JMP_FALSE);
      bcode_op_lit(bbuilder, OP_SET_VAR, lit);

      /*
       * The stash register contains the value of the previous statement,
       * being it the statement before the for..in statement or
       * the previous iteration. We move it to the data stack. It will
       * be replaced by the values of the body statements as usual.
       */
      bcode_op(bbuilder, OP_UNSTASH);

      /*
       * This node is always a NOP, for compatibility
       * with the layout of the AST_FOR node.
       */
      ast_skip_tree(a, ppos);

      V7_TRY(compile_stmts(bbuilder, a, ppos, end));

      continue_target = bcode_pos(bbuilder);

      /*
       * Save the last body statement. If next evaluation of NEXT_PROP returns
       * false, we'll unstash it.
       */
      bcode_op(bbuilder, OP_STASH);
      bcode_op(bbuilder, OP_DROP);

      loop_label = bcode_op_target(bbuilder, OP_JMP);
      bcode_patch_target(bbuilder, loop_label, loop_target);

      /* end: */
      bcode_patch_target(bbuilder, end_label, bcode_pos(bbuilder));
      bcode_op(bbuilder, OP_UNSTASH);

      pop_label = bcode_op_target(bbuilder, OP_JMP);

      /* brend: */
      bcode_patch_target(bbuilder, brend_label, bcode_pos(bbuilder));

      continue_label = bcode_op_target(bbuilder, OP_JMP_IF_CONTINUE);
      bcode_patch_target(bbuilder, continue_label, continue_target);

      bcode_op(bbuilder, OP_SWAP_DROP);
      bcode_op(bbuilder, OP_SWAP_DROP);
      bcode_op(bbuilder, OP_SWAP_DROP);

      /* try_pop: */
      bcode_patch_target(bbuilder, pop_label, bcode_pos(bbuilder));

      bcode_op(bbuilder, OP_TRY_POP);

      bbuilder->v7->is_stack_neutral = 1;
      break;
    }
    /*
     * do {
     *   B...
     * } while(COND);
     *
     * ->
     *
     *   TRY_PUSH_LOOP end
     * body:
     *   <B>
     * cond:
     *   <COND>
     *   JMP_TRUE body
     * end:
     *   JMP_IF_CONTINUE cond
     *   TRY_POP
     *
     */
    case AST_DOWHILE: {
      bcode_off_t end_label, continue_label, continue_target;
      end = ast_get_skip(a, pos_after_tag, AST_DO_WHILE_COND_SKIP);
      end_label = bcode_op_target(bbuilder, OP_TRY_PUSH_LOOP);
      body_target = bcode_pos(bbuilder);
      V7_TRY(compile_stmts(bbuilder, a, ppos, end));

      continue_target = bcode_pos(bbuilder);
      V7_TRY(compile_expr_builder(bbuilder, a, ppos));
      body_label = bcode_op_target(bbuilder, OP_JMP_TRUE);
      bcode_patch_target(bbuilder, body_label, body_target);

      bcode_patch_target(bbuilder, end_label, bcode_pos(bbuilder));
      continue_label = bcode_op_target(bbuilder, OP_JMP_IF_CONTINUE);
      bcode_patch_target(bbuilder, continue_label, continue_target);
      bcode_op(bbuilder, OP_TRY_POP);

      bbuilder->v7->is_stack_neutral = 1;
      break;
    }
    case AST_VAR: {
      /*
       * Var decls are hoisted when the function frame is created. Vars
       * declared inside a `with` or `catch` block belong to the function
       * lexical scope, and although those clauses create an inner frame
       * no new variables should be created in it. A var decl thus
       * behaves as a normal assignment at runtime.
       */
      lit_t lit;
      end = ast_get_skip(a, pos_after_tag, AST_END_SKIP);
      while (*ppos < end) {
        tag = fetch_tag(v7, bbuilder, a, ppos, &pos_after_tag);
        if (tag == AST_FUNC_DECL) {
          /*
           * function declarations are already set during hoisting (see
           * `compile_local_vars()`), so, skip it.
           *
           * Plus, they are stack-neutral, so don't forget to set
           * `is_stack_neutral`.
           */
          ast_skip_tree(a, ppos);
          bbuilder->v7->is_stack_neutral = 1;
        } else {
          /*
           * compile `var` declaration: basically it looks similar to an
           * assignment, but it differs from an assignment is that it's
           * stack-neutral: `1; var a = 5;` yields `1`, not `5`.
           */
          V7_CHECK_INTERNAL(tag == AST_VAR_DECL);
          lit = string_lit(bbuilder, a, pos_after_tag);
          V7_TRY(compile_expr_builder(bbuilder, a, ppos));
          bcode_op_lit(bbuilder, OP_SET_VAR, lit);

          /* `var` declaration is stack-neutral */
          bcode_op(bbuilder, OP_DROP);
          bbuilder->v7->is_stack_neutral = 1;
        }
      }
      break;
    }
    case AST_RETURN:
      bcode_op(bbuilder, OP_PUSH_UNDEFINED);
      bcode_op(bbuilder, OP_RET);
      break;
    case AST_VALUE_RETURN:
      V7_TRY(compile_expr_builder(bbuilder, a, ppos));
      bcode_op(bbuilder, OP_RET);
      break;
    default:
      *ppos = pos_after_tag - 1;
      V7_TRY(compile_expr_builder(bbuilder, a, ppos));
  }

clean:
  mbuf_free(&case_labels);
  return rcode;
}

static enum v7_err compile_body(struct bcode_builder *bbuilder, struct ast *a,
                                ast_off_t start, ast_off_t end, ast_off_t body,
                                ast_off_t fvar, ast_off_t *ppos) {
  enum v7_err rcode = V7_OK;
  struct v7 *v7 = bbuilder->v7;

#ifndef V7_FORCE_STRICT_MODE
  /* check 'use strict' */
  if (*ppos < end) {
    ast_off_t tmp_pos = body;
    if (fetch_tag(v7, bbuilder, a, &tmp_pos, NULL) == AST_USE_STRICT) {
      bbuilder->bcode->strict_mode = 1;
      /* move `body` offset, effectively removing `AST_USE_STRICT` from it */
      body = tmp_pos;
    }
  }
#endif

  /* put initial value for the function body execution */
  bcode_op(bbuilder, OP_PUSH_UNDEFINED);

  /*
   * populate `bcode->ops` with function's local variable names. Note that we
   * should do this *after* `OP_PUSH_UNDEFINED`, since `compile_local_vars`
   * emits code that assigns the hoisted functions to local variables, and
   * those statements assume that the stack contains `undefined`.
   */
  V7_TRY(compile_local_vars(bbuilder, a, start, fvar));

  /* compile body */
  *ppos = body;
  V7_TRY(compile_stmts(bbuilder, a, ppos, end));

clean:
  return rcode;
}

/*
 * Compiles a given script and populates a bcode structure.
 * The AST must start with an AST_SCRIPT node.
 */
V7_PRIVATE enum v7_err compile_script(struct v7 *v7, struct ast *a,
                                      struct bcode *bcode) {
  ast_off_t pos_after_tag, end, fvar, pos = 0;
  int saved_line_no = v7->line_no;
  enum v7_err rcode = V7_OK;
  struct bcode_builder bbuilder;
  enum ast_tag tag;

  bcode_builder_init(v7, &bbuilder, bcode);
  v7->line_no = 1;

  tag = fetch_tag(v7, &bbuilder, a, &pos, &pos_after_tag);

  /* first tag should always be AST_SCRIPT */
  assert(tag == AST_SCRIPT);
  (void) tag;

  end = ast_get_skip(a, pos_after_tag, AST_END_SKIP);
  fvar = ast_get_skip(a, pos_after_tag, AST_FUNC_FIRST_VAR_SKIP) - 1;

  V7_TRY(compile_body(&bbuilder, a, pos_after_tag - 1, end, pos /* body */,
                      fvar, &pos));

clean:

  bcode_builder_finalize(&bbuilder);

#ifdef V7_BCODE_DUMP
  if (rcode == V7_OK) {
    fprintf(stderr, "--- script ---\n");
    dump_bcode(v7, stderr, bcode);
  }
#endif

  v7->line_no = saved_line_no;

  return rcode;
}

/*
 * Compiles a given function and populates a bcode structure.
 * The AST must contain an AST_FUNC node at offset ast_off.
 */
V7_PRIVATE enum v7_err compile_function(struct v7 *v7, struct ast *a,
                                        ast_off_t *ppos, struct bcode *bcode) {
  ast_off_t pos_after_tag, start, end, body, fvar;
  const char *name;
  size_t name_len;
  size_t args_cnt;
  enum v7_err rcode = V7_OK;
  struct bcode_builder bbuilder;
  enum ast_tag tag;
  size_t names_end = 0;
  bcode_builder_init(v7, &bbuilder, bcode);
  tag = fetch_tag(v7, &bbuilder, a, ppos, &pos_after_tag);
  start = pos_after_tag - 1;

  (void) tag;
  assert(tag == AST_FUNC);
  end = ast_get_skip(a, pos_after_tag, AST_END_SKIP);
  body = ast_get_skip(a, pos_after_tag, AST_FUNC_BODY_SKIP);
  fvar = ast_get_skip(a, pos_after_tag, AST_FUNC_FIRST_VAR_SKIP) - 1;

  /* retrieve function name */
  tag = fetch_tag(v7, &bbuilder, a, ppos, &pos_after_tag);
  if (tag == AST_IDENT) {
    /* function name is provided */
    name = ast_get_inlined_data(a, pos_after_tag, &name_len);
    V7_TRY(bcode_add_name(&bbuilder, name, name_len, &names_end));
  } else {
    /* no name: anonymous function */
    V7_TRY(bcode_add_name(&bbuilder, "", 0, &names_end));
  }

  /* retrieve function's argument names */
  for (args_cnt = 0; *ppos < body; args_cnt++) {
    if (args_cnt > V7_ARGS_CNT_MAX) {
      /* too many arguments */
      rcode = v7_throwf(v7, SYNTAX_ERROR, "Too many arguments");
      V7_THROW(V7_SYNTAX_ERROR);
    }

    tag = fetch_tag(v7, &bbuilder, a, ppos, &pos_after_tag);
    /*
     * TODO(dfrank): it's not actually an internal error, we get here if
     * we compile e.g. the following: (function(1){})
     */
    V7_CHECK_INTERNAL(tag == AST_IDENT);
    name = ast_get_inlined_data(a, pos_after_tag, &name_len);
    V7_TRY(bcode_add_name(&bbuilder, name, name_len, &names_end));
  }

  bcode->args_cnt = args_cnt;
  bcode->func_name_present = 1;

  V7_TRY(compile_body(&bbuilder, a, start, end, body, fvar, ppos));

clean:
  bcode_builder_finalize(&bbuilder);

#ifdef V7_BCODE_DUMP
  if (rcode == V7_OK) {
    fprintf(stderr, "--- function ---\n");
    dump_bcode(v7, stderr, bcode);
  }
#endif

  return rcode;
}

V7_PRIVATE enum v7_err compile_expr(struct v7 *v7, struct ast *a,
                                    ast_off_t *ppos, struct bcode *bcode) {
  enum v7_err rcode = V7_OK;
  struct bcode_builder bbuilder;
  int saved_line_no = v7->line_no;

  bcode_builder_init(v7, &bbuilder, bcode);
  v7->line_no = 1;

  rcode = compile_expr_builder(&bbuilder, a, ppos);

  bcode_builder_finalize(&bbuilder);
  v7->line_no = saved_line_no;
  return rcode;
}

#endif /* V7_NO_COMPILER */
#ifdef V7_MODULE_LINES
#line 1 "v7/src/stdlib.c"
#endif
/*
 * Copyright (c) 2014 Cesanta Software Limited
 * All rights reserved
 */

/* Amalgamated: #include "common/cs_strtod.h" */

/* Amalgamated: #include "v7/src/internal.h" */
/* Amalgamated: #include "v7/src/core.h" */
/* Amalgamated: #include "v7/src/primitive.h" */
/* Amalgamated: #include "v7/src/conversion.h" */
/* Amalgamated: #include "v7/src/stdlib.h" */
/* Amalgamated: #include "v7/src/std_array.h" */
/* Amalgamated: #include "v7/src/std_boolean.h" */
/* Amalgamated: #include "v7/src/std_date.h" */
/* Amalgamated: #include "v7/src/std_error.h" */
/* Amalgamated: #include "v7/src/std_function.h" */
/* Amalgamated: #include "v7/src/std_json.h" */
/* Amalgamated: #include "v7/src/std_math.h" */
/* Amalgamated: #include "v7/src/std_number.h" */
/* Amalgamated: #include "v7/src/std_object.h" */
/* Amalgamated: #include "v7/src/std_regex.h" */
/* Amalgamated: #include "v7/src/std_string.h" */
/* Amalgamated: #include "v7/src/std_proxy.h" */
/* Amalgamated: #include "v7/src/js_stdlib.h" */
/* Amalgamated: #include "v7/src/object.h" */
/* Amalgamated: #include "v7/src/string.h" */
/* Amalgamated: #include "v7/src/util.h" */
/* Amalgamated: #include "v7/src/exec.h" */

#ifdef NO_LIBC
void print_str(const char *str);
#endif

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Std_print(struct v7 *v7, v7_val_t *res) {
  enum v7_err rcode = V7_OK;
  int i, num_args = v7_argc(v7);
  val_t v;

  (void) res;

  for (i = 0; i < num_args; i++) {
    v = v7_arg(v7, i);
    if (v7_is_string(v)) {
      size_t n;
      const char *s = v7_get_string(v7, &v, &n);
      printf("%.*s", (int) n, s);
    } else {
      v7_print(v7, v);
    }
    printf(" ");
  }
  printf(ENDL);

  return rcode;
}

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err std_eval(struct v7 *v7, v7_val_t arg, val_t this_obj,
                                int is_json, v7_val_t *res) {
  enum v7_err rcode = V7_OK;
  char buf[100], *p = buf;
  struct v7_exec_opts opts;
  memset(&opts, 0x00, sizeof(opts));
  opts.filename = "Eval'd code";

  if (arg != V7_UNDEFINED) {
    size_t len;
    rcode = to_string(v7, arg, NULL, buf, sizeof(buf), &len);
    if (rcode != V7_OK) {
      goto clean;
    }

    /* Fit null terminating byte and quotes */
    if (len >= sizeof(buf) - 2) {
      /* Buffer is not large enough. Allocate a bigger one */
      p = (char *) malloc(len + 3);
      rcode = to_string(v7, arg, NULL, p, len + 2, NULL);
      if (rcode != V7_OK) {
        goto clean;
      }
    }

    v7_set_gc_enabled(v7, 1);
    if (is_json) {
      opts.is_json = 1;
    } else {
      opts.this_obj = this_obj;
    }
    rcode = v7_exec_opt(v7, p, &opts, res);
    if (rcode != V7_OK) {
      goto clean;
    }
  }

clean:
  if (p != buf) {
    free(p);
  }

  return rcode;
}

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Std_eval(struct v7 *v7, v7_val_t *res) {
  val_t this_obj = v7_get_this(v7);
  v7_val_t arg = v7_arg(v7, 0);
  return std_eval(v7, arg, this_obj, 0, res);
}

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Std_parseInt(struct v7 *v7, v7_val_t *res) {
  enum v7_err rcode = V7_OK;
  v7_val_t arg0 = V7_UNDEFINED;
  v7_val_t arg1 = V7_UNDEFINED;
  long sign = 1, base, n;
  char buf[20], *p = buf, *end;

  *res = V7_TAG_NAN;

  arg0 = v7_arg(v7, 0);
  arg1 = v7_arg(v7, 1);

  rcode = to_string(v7, arg0, &arg0, NULL, 0, NULL);
  if (rcode != V7_OK) {
    goto clean;
  }

  rcode = to_number_v(v7, arg1, &arg1);
  if (rcode != V7_OK) {
    goto clean;
  }

  if (is_finite(v7, arg1)) {
    base = v7_get_double(v7, arg1);
  } else {
    base = 0;
  }

  if (base == 0) {
    base = 10;
  }

  if (base < 2 || base > 36) {
    *res = V7_TAG_NAN;
    goto clean;
  }

  {
    size_t str_len;
    p = (char *) v7_get_string(v7, &arg0, &str_len);
  }

  /* Strip leading whitespaces */
  while (*p != '\0' && isspace(*(unsigned char *) p)) {
    p++;
  }

  if (*p == '+') {
    sign = 1;
    p++;
  } else if (*p == '-') {
    sign = -1;
    p++;
  }

  if (p[0] == '0' && (p[1] == 'x' || p[1] == 'X')) {
    base = 16;
    p += 2;
  }

  n = strtol(p, &end, base);

  *res = (p == end) ? V7_TAG_NAN : v7_mk_number(v7, n * sign);

clean:
  return rcode;
}

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Std_parseFloat(struct v7 *v7, v7_val_t *res) {
  enum v7_err rcode = V7_OK;
  v7_val_t arg0 = V7_UNDEFINED;
  char buf[20], *p = buf, *end;
  double result;

  rcode = to_primitive(v7, v7_arg(v7, 0), V7_TO_PRIMITIVE_HINT_NUMBER, &arg0);
  if (rcode != V7_OK) {
    goto clean;
  }

  if (v7_is_string(arg0)) {
    size_t str_len;
    p = (char *) v7_get_string(v7, &arg0, &str_len);
  } else {
    rcode = to_string(v7, arg0, NULL, buf, sizeof(buf), NULL);
    if (rcode != V7_OK) {
      goto clean;
    }
    buf[sizeof(buf) - 1] = '\0';
  }

  while (*p != '\0' && isspace(*(unsigned char *) p)) {
    p++;
  }

  result = cs_strtod(p, &end);

  *res = (p == end) ? V7_TAG_NAN : v7_mk_number(v7, result);

clean:
  return rcode;
}

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Std_isNaN(struct v7 *v7, v7_val_t *res) {
  enum v7_err rcode = V7_OK;
  v7_val_t arg0 = V7_TAG_NAN;
  rcode = to_number_v(v7, v7_arg(v7, 0), &arg0);
  if (rcode != V7_OK) {
    goto clean;
  }

  *res = v7_mk_boolean(v7, isnan(v7_get_double(v7, arg0)));

clean:
  return rcode;
}

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Std_isFinite(struct v7 *v7, v7_val_t *res) {
  enum v7_err rcode = V7_OK;
  v7_val_t arg0 = V7_TAG_NAN;

  rcode = to_number_v(v7, v7_arg(v7, 0), &arg0);
  if (rcode != V7_OK) {
    goto clean;
  }

  *res = v7_mk_boolean(v7, is_finite(v7, arg0));

clean:
  return rcode;
}

#ifndef NO_LIBC
WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Std_exit(struct v7 *v7, v7_val_t *res) {
  enum v7_err rcode = V7_OK;
  long exit_code;

  (void) res;

  rcode = to_long(v7, v7_arg(v7, 0), 0, &exit_code);
  if (rcode != V7_OK) {
    /* `to_long` has thrown, so, will return 1 */
    exit_code = 1;
  }
  exit(exit_code);

  return rcode;
}
#endif

/*
 * Initialize standard library.
 *
 * This function is used only internally, but used in a complicated mix of
 * configurations, hence the commented V7_PRIVATE
 */
/*V7_PRIVATE*/ void init_stdlib(struct v7 *v7) {
  v7_prop_attr_desc_t attr_internal =
      (V7_DESC_ENUMERABLE(0) | V7_DESC_WRITABLE(0) | V7_DESC_CONFIGURABLE(0));

  /*
   * Ensure the first call to v7_mk_value will use a null proto:
   * {}.__proto__.__proto__ == null
   */
  v7->vals.object_prototype = mk_object(v7, V7_NULL);
  v7->vals.array_prototype = v7_mk_object(v7);
  v7->vals.boolean_prototype = v7_mk_object(v7);
  v7->vals.string_prototype = v7_mk_object(v7);
  v7->vals.regexp_prototype = v7_mk_object(v7);
  v7->vals.number_prototype = v7_mk_object(v7);
  v7->vals.error_prototype = v7_mk_object(v7);
  v7->vals.global_object = v7_mk_object(v7);
  v7->vals.date_prototype = v7_mk_object(v7);
  v7->vals.function_prototype = v7_mk_object(v7);
  v7->vals.proxy_prototype = v7_mk_object(v7);

  set_method(v7, v7->vals.global_object, "eval", Std_eval, 1);
  set_method(v7, v7->vals.global_object, "print", Std_print, 1);
#ifndef NO_LIBC
  set_method(v7, v7->vals.global_object, "exit", Std_exit, 1);
#endif
  set_method(v7, v7->vals.global_object, "parseInt", Std_parseInt, 2);
  set_method(v7, v7->vals.global_object, "parseFloat", Std_parseFloat, 1);
  set_method(v7, v7->vals.global_object, "isNaN", Std_isNaN, 1);
  set_method(v7, v7->vals.global_object, "isFinite", Std_isFinite, 1);

  v7_def(v7, v7->vals.global_object, "Infinity", 8, attr_internal,
         v7_mk_number(v7, INFINITY));
  v7_set(v7, v7->vals.global_object, "global", 6, v7->vals.global_object);

  init_object(v7);
  init_array(v7);
  init_error(v7);
  init_boolean(v7);
#if V7_ENABLE__Math
  init_math(v7);
#endif
  init_string(v7);
#if V7_ENABLE__RegExp
  init_regex(v7);
#endif
  init_number(v7);
  init_json(v7);
#if V7_ENABLE__Date
  init_date(v7);
#endif
  init_function(v7);
  init_js_stdlib(v7);

#if V7_ENABLE__Proxy
  init_proxy(v7);
#endif
}
#ifdef V7_MODULE_LINES
#line 1 "v7/src/js_stdlib.c"
#endif
/*
 * Copyright (c) 2014 Cesanta Software Limited
 * All rights reserved
 */

/* clang-format off */
/* because clang-format would break JS code, e.g. === converted to == = ... */

/* Amalgamated: #include "v7/src/internal.h" */
/* Amalgamated: #include "v7/src/core.h" */
/* Amalgamated: #include "v7/src/exec.h" */
/* Amalgamated: #include "v7/src/util.h" */

#define STRINGIFY(x) #x

#if defined(__cplusplus)
extern "C" {
#endif /* __cplusplus */

static const char js_array_indexOf[] = STRINGIFY(
    Object.defineProperty(Array.prototype, "indexOf", {
      writable:true,
      configurable: true,
      value: function(a, x) {
        var i; var r = -1; var b = +x;
        if (!b || b < 0) b = 0;
        for (i in this) if (i >= b && (r < 0 || i < r) && this[i] === a) r = +i;
        return r;
    }}););

static const char js_array_lastIndexOf[] = STRINGIFY(
    Object.defineProperty(Array.prototype, "lastIndexOf", {
      writable:true,
      configurable: true,
      value: function(a, x) {
        var i; var r = -1; var b = +x;
        if (isNaN(b) || b < 0 || b >= this.length) b = this.length - 1;
        for (i in this) if (i <= b && (r < 0 || i > r) && this[i] === a) r = +i;
        return r;
    }}););

#if V7_ENABLE__Array__reduce
static const char js_array_reduce[] = STRINGIFY(
    Object.defineProperty(Array.prototype, "reduce", {
      writable:true,
      configurable: true,
      value: function(a, b) {
        var f = 0;
        if (typeof(a) != "function") {
          throw new TypeError(a + " is not a function");
        }
        for (var k in this) {
          if (k > this.length) break;
          if (f == 0 && b === undefined) {
            b = this[k];
            f = 1;
          } else {
            b = a(b, this[k], k, this);
          }
        }
        return b;
    }}););
#endif

static const char js_array_pop[] = STRINGIFY(
    Object.defineProperty(Array.prototype, "pop", {
      writable:true,
      configurable: true,
      value: function() {
      var i = this.length - 1;
        return this.splice(i, 1)[0];
    }}););

static const char js_array_shift[] = STRINGIFY(
    Object.defineProperty(Array.prototype, "shift", {
      writable:true,
      configurable: true,
      value: function() {
        return this.splice(0, 1)[0];
    }}););

#if V7_ENABLE__Function__call
static const char js_function_call[] = STRINGIFY(
    Object.defineProperty(Function.prototype, "call", {
      writable:true,
      configurable: true,
      value: function() {
        var t = arguments.splice(0, 1)[0];
        return this.apply(t, arguments);
    }}););
#endif

#if V7_ENABLE__Function__bind
static const char js_function_bind[] = STRINGIFY(
    Object.defineProperty(Function.prototype, "bind", {
      writable:true,
      configurable: true,
      value: function(t) {
        var f = this;
        return function() {
          return f.apply(t, arguments);
        };
    }}););
#endif

#if V7_ENABLE__Blob
static const char js_Blob[] = STRINGIFY(
    function Blob(a) {
      this.a = a;
    });
#endif

static const char * const js_functions[] = {
#if V7_ENABLE__Blob
  js_Blob,
#endif
#if V7_ENABLE__Function__call
  js_function_call,
#endif
#if V7_ENABLE__Function__bind
  js_function_bind,
#endif
#if V7_ENABLE__Array__reduce
  js_array_reduce,
#endif
  js_array_indexOf,
  js_array_lastIndexOf,
  js_array_pop,
  js_array_shift
};

 V7_PRIVATE void init_js_stdlib(struct v7 *v7) {
  val_t res;
  int i;

  for(i = 0; i < (int) ARRAY_SIZE(js_functions); i++) {
    if (v7_exec(v7, js_functions[i], &res) != V7_OK) {
      fprintf(stderr, "ex: %s:\n", js_functions[i]);
      v7_fprintln(stderr, v7, res);
    }
  }

  /* TODO(lsm): re-enable in a separate PR */
#if 0
  v7_exec(v7, &res, STRINGIFY(
    Array.prototype.unshift = function() {
      var a = new Array(0, 0);
      Array.prototype.push.apply(a, arguments);
      Array.prototype.splice.apply(this, a);
      return this.length;
    };));
#endif
}

#if defined(__cplusplus)
}
#endif /* __cplusplus */
#ifdef V7_MODULE_LINES
#line 1 "v7/src/slre.c"
#endif
/*
 * Copyright (c) 2014 Cesanta Software Limited
 * All rights reserved
 *
 * This software is dual-licensed: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation. For the terms of this
 * license, see <http://www.gnu.org/licenses/>.
 *
 * You are free to use this software under the terms of the GNU General
 * Public License, but WITHOUT ANY WARRANTY; without even the implied
 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU General Public License for more details.
 *
 * Alternatively, you can license this software under a commercial
 * license, as set out in <https://www.cesanta.com/license>.
 */

/* Amalgamated: #include "v7/src/v7_features.h" */

#include <setjmp.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#ifndef NO_LIBC
#include <ctype.h>
#endif

/* Amalgamated: #include "common/utf.h" */
/* Amalgamated: #include "v7/src/slre.h" */

/* Limitations */
#define SLRE_MAX_RANGES 32
#define SLRE_MAX_SETS 16
#define SLRE_MAX_REP 0xFFFF

#define SLRE_MALLOC malloc
#define SLRE_FREE free
#define SLRE_THROW(e, err_code) longjmp((e)->jmp_buf, (err_code))

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

int nextesc(const char **p) {
  const unsigned char *s = (unsigned char *) (*p)++;
  switch (*s) {
    case 0:
      return -SLRE_UNTERM_ESC_SEQ;
    case 'c':
      ++*p;
      return *s & 31;
    case 'b':
      return '\b';
    case 't':
      return '\t';
    case 'n':
      return '\n';
    case 'v':
      return '\v';
    case 'f':
      return '\f';
    case 'r':
      return '\r';
    case '\\':
      return '\\';
    case 'u':
      if (isxdigit(s[1]) && isxdigit(s[2]) && isxdigit(s[3]) &&
          isxdigit(s[4])) {
        (*p) += 4;
        return hex(s[1]) << 12 | hex(s[2]) << 8 | hex(s[3]) << 4 | hex(s[4]);
      }
      return -SLRE_INVALID_HEX_DIGIT;
    case 'x':
      if (isxdigit(s[1]) && isxdigit(s[2])) {
        (*p) += 2;
        return (hex(s[1]) << 4) | hex(s[2]);
      }
      return -SLRE_INVALID_HEX_DIGIT;
    default:
      return -SLRE_INVALID_ESC_CHAR;
  }
}

#if V7_ENABLE__RegExp

/* Parser Information */
struct slre_node {
  unsigned char type;
  union {
    Rune c;                /* character */
    struct slre_class *cp; /* class pointer */
    struct {
      struct slre_node *x;
      union {
        struct slre_node *y;
        unsigned char n;
        struct {
          unsigned char ng; /* not greedy flag */
          unsigned short min;
          unsigned short max;
        } rp;
      } y;
    } xy;
  } par;
};

struct slre_range {
  unsigned short s, e;
};

/* character class, each pair of rune's defines a range */
struct slre_class {
  struct slre_range *end;
  struct slre_range spans[SLRE_MAX_RANGES];
};

struct slre_instruction {
  unsigned char opcode;
  union {
    unsigned char n;
    Rune c;                /* character */
    struct slre_class *cp; /* class pointer */
    struct {
      struct slre_instruction *x;
      union {
        struct {
          unsigned short min;
          unsigned short max;
        } rp;
        struct slre_instruction *y;
      } y;
    } xy;
  } par;
};

struct slre_prog {
  struct slre_instruction *start, *end;
  unsigned int num_captures;
  int flags;
  struct slre_class charset[SLRE_MAX_SETS];
};

struct slre_env {
  int is_regex;
  const char *src;
  const char *src_end;
  Rune curr_rune;

  struct slre_prog *prog;
  struct slre_node *pstart, *pend;

  struct slre_node *caps[SLRE_MAX_CAPS];
  unsigned int num_captures;
  unsigned int sets_num;

  int lookahead;
  struct slre_class *curr_set;
  int min_rep, max_rep;

#if defined(__cplusplus)
  ::jmp_buf jmp_buf;
#else
  jmp_buf jmp_buf;
#endif
};

struct slre_thread {
  struct slre_thread *prev;
  struct slre_instruction *pc;
  const char *start;
  struct slre_loot loot;
};

enum slre_opcode {
  I_END = 10, /* Terminate: match found */
  I_ANY,
  P_ANY = I_ANY, /* Any character except newline, . */
  I_ANYNL,       /* Any character including newline, . */
  I_BOL,
  P_BOL = I_BOL, /* Beginning of line, ^ */
  I_CH,
  P_CH = I_CH,
  I_EOL,
  P_EOL = I_EOL, /* End of line, $ */
  I_EOS,
  P_EOS = I_EOS, /* End of string, \0 */
  I_JUMP,
  I_LA,
  P_LA = I_LA,
  I_LA_N,
  P_LA_N = I_LA_N,
  I_LBRA,
  P_BRA = I_LBRA, /* Left bracket, ( */
  I_REF,
  P_REF = I_REF,
  I_REP,
  P_REP = I_REP,
  I_REP_INI,
  I_RBRA, /* Right bracket, ) */
  I_SET,
  P_SET = I_SET, /* Character set, [] */
  I_SET_N,
  P_SET_N = I_SET_N, /* Negated character set, [] */
  I_SPLIT,
  I_WORD,
  P_WORD = I_WORD,
  I_WORD_N,
  P_WORD_N = I_WORD_N,
  P_ALT, /* Alternation, | */
  P_CAT, /* Concatentation, implicit operator */
  L_CH = 256,
  L_COUNT,  /* {M,N} */
  L_EOS,    /* End of string, \0 */
  L_LA,     /* "(?=" lookahead */
  L_LA_CAP, /* "(?:" lookahead, capture */
  L_LA_N,   /* "(?!" negative lookahead */
  L_REF,    /* "\1" back-reference */
  L_CHSET,  /* character set */
  L_SET_N,  /* negative character set */
  L_WORD,   /* "\b" word boundary */
  L_WORD_N  /* "\B" non-word boundary */
};

static signed char dec(int c) {
  if (isdigitrune(c)) return c - '0';
  return SLRE_INVALID_DEC_DIGIT;
}

static unsigned char re_dec_digit(struct slre_env *e, int c) {
  signed char ret = dec(c);
  if (ret < 0) {
    SLRE_THROW(e, SLRE_INVALID_DEC_DIGIT);
  }
  return ret;
}

static int re_nextc(Rune *r, const char **src, const char *src_end) {
  *r = 0;
  if (*src >= src_end) return 0;
  *src += chartorune(r, *src);
  if (*r == '\\') {
    const char *tmp_s = *src;
    int i = nextesc(src);
    switch (i) {
      case -SLRE_INVALID_ESC_CHAR:
        *r = '\\';
        *src = tmp_s;
        *src += chartorune(r, *src);
        break;
      case -SLRE_INVALID_HEX_DIGIT:
      default:
        *r = i;
    }
    return 1;
  }
  return 0;
}

static int re_nextc_raw(Rune *r, const char **src, const char *src_end) {
  *r = 0;
  if (*src >= src_end) return 0;
  *src += chartorune(r, *src);
  return 0;
}

static int re_nextc_env(struct slre_env *e) {
  return re_nextc(&e->curr_rune, &e->src, e->src_end);
}

static void re_nchset(struct slre_env *e) {
  if (e->sets_num >= nelem(e->prog->charset)) {
    SLRE_THROW(e, SLRE_TOO_MANY_CHARSETS);
  }
  e->curr_set = e->prog->charset + e->sets_num++;
  e->curr_set->end = e->curr_set->spans;
}

static void re_rng2set(struct slre_env *e, Rune start, Rune end) {
  if (start > end) {
    SLRE_THROW(e, SLRE_INV_CHARSET_RANGE);
  }
  if (e->curr_set->end + 2 == e->curr_set->spans + nelem(e->curr_set->spans)) {
    SLRE_THROW(e, SLRE_CHARSET_TOO_LARGE);
  }
  e->curr_set->end->s = start;
  e->curr_set->end->e = end;
  e->curr_set->end++;
}

#define re_char2set(e, c) re_rng2set(e, c, c)

#define re_d_2set(e) re_rng2set(e, '0', '9')

static void re_D_2set(struct slre_env *e) {
  re_rng2set(e, 0, '0' - 1);
  re_rng2set(e, '9' + 1, 0xFFFF);
}

static void re_s_2set(struct slre_env *e) {
  re_char2set(e, 0x9);
  re_rng2set(e, 0xA, 0xD);
  re_char2set(e, 0x20);
  re_char2set(e, 0xA0);
  re_rng2set(e, 0x2028, 0x2029);
  re_char2set(e, 0xFEFF);
}

static void re_S_2set(struct slre_env *e) {
  re_rng2set(e, 0, 0x9 - 1);
  re_rng2set(e, 0xD + 1, 0x20 - 1);
  re_rng2set(e, 0x20 + 1, 0xA0 - 1);
  re_rng2set(e, 0xA0 + 1, 0x2028 - 1);
  re_rng2set(e, 0x2029 + 1, 0xFEFF - 1);
  re_rng2set(e, 0xFEFF + 1, 0xFFFF);
}

static void re_w_2set(struct slre_env *e) {
  re_d_2set(e);
  re_rng2set(e, 'A', 'Z');
  re_char2set(e, '_');
  re_rng2set(e, 'a', 'z');
}

static void re_W_2set(struct slre_env *e) {
  re_rng2set(e, 0, '0' - 1);
  re_rng2set(e, '9' + 1, 'A' - 1);
  re_rng2set(e, 'Z' + 1, '_' - 1);
  re_rng2set(e, '_' + 1, 'a' - 1);
  re_rng2set(e, 'z' + 1, 0xFFFF);
}

static unsigned char re_endofcount(Rune c) {
  switch (c) {
    case ',':
    case '}':
      return 1;
  }
  return 0;
}

static void re_ex_num_overfl(struct slre_env *e) {
  SLRE_THROW(e, SLRE_NUM_OVERFLOW);
}

static enum slre_opcode re_countrep(struct slre_env *e) {
  e->min_rep = 0;
  while (e->src < e->src_end && !re_endofcount(e->curr_rune = *e->src++)) {
    e->min_rep = e->min_rep * 10 + re_dec_digit(e, e->curr_rune);
    if (e->min_rep >= SLRE_MAX_REP) re_ex_num_overfl(e);
  }

  if (e->curr_rune != ',') {
    e->max_rep = e->min_rep;
    return L_COUNT;
  }
  e->max_rep = 0;
  while (e->src < e->src_end && (e->curr_rune = *e->src++) != '}') {
    e->max_rep = e->max_rep * 10 + re_dec_digit(e, e->curr_rune);
    if (e->max_rep >= SLRE_MAX_REP) re_ex_num_overfl(e);
  }
  if (!e->max_rep) {
    e->max_rep = SLRE_MAX_REP;
    return L_COUNT;
  }

  return L_COUNT;
}

static enum slre_opcode re_lexset(struct slre_env *e) {
  Rune ch = 0;
  unsigned char esc, ch_fl = 0, dash_fl = 0;
  enum slre_opcode type = L_CHSET;

  re_nchset(e);

  esc = re_nextc_env(e);
  if (!esc && e->curr_rune == '^') {
    type = L_SET_N;
    esc = re_nextc_env(e);
  }

  for (; esc || e->curr_rune != ']'; esc = re_nextc_env(e)) {
    if (!e->curr_rune) {
      SLRE_THROW(e, SLRE_MALFORMED_CHARSET);
    }
    if (esc) {
      if (strchr("DdSsWw", e->curr_rune)) {
        if (ch_fl) {
          re_char2set(e, ch);
          if (dash_fl) re_char2set(e, '-');
        }
        switch (e->curr_rune) {
          case 'D':
            re_D_2set(e);
            break;
          case 'd':
            re_d_2set(e);
            break;
          case 'S':
            re_S_2set(e);
            break;
          case 's':
            re_s_2set(e);
            break;
          case 'W':
            re_W_2set(e);
            break;
          case 'w':
            re_w_2set(e);
            break;
        }
        ch_fl = dash_fl = 0;
        continue;
      }
      switch (e->curr_rune) {
        default:
          /* case '-':
          case '\\':
          case '.':
          case '/':
          case ']':
          case '|': */
          break;
        case '0':
          e->curr_rune = 0;
          break;
        case 'b':
          e->curr_rune = '\b';
          break;
          /* default:
            SLRE_THROW(e->catch_point, e->err_msg,
            SLRE_INVALID_ESC_CHAR); */
      }
    } else {
      if (e->curr_rune == '-') {
        if (ch_fl) {
          if (dash_fl) {
            re_rng2set(e, ch, '-');
            ch_fl = dash_fl = 0;
          } else
            dash_fl = 1;
        } else {
          ch = '-';
          ch_fl = 1;
        }
        continue;
      }
    }
    if (ch_fl) {
      if (dash_fl) {
        re_rng2set(e, ch, e->curr_rune);
        ch_fl = dash_fl = 0;
      } else {
        re_char2set(e, ch);
        ch = e->curr_rune;
      }
    } else {
      ch = e->curr_rune;
      ch_fl = 1;
    }
  }
  if (ch_fl) {
    re_char2set(e, ch);
    if (dash_fl) re_char2set(e, '-');
  }
  return type;
}

static int re_lexer(struct slre_env *e) {
  if (re_nextc_env(e)) {
    switch (e->curr_rune) {
      case '0':
        e->curr_rune = 0;
        return L_EOS;
      case 'b':
        return L_WORD;
      case 'B':
        return L_WORD_N;
      case 'd':
        re_nchset(e);
        re_d_2set(e);
        return L_CHSET;
      case 'D':
        re_nchset(e);
        re_d_2set(e);
        return L_SET_N;
      case 's':
        re_nchset(e);
        re_s_2set(e);
        return L_CHSET;
      case 'S':
        re_nchset(e);
        re_s_2set(e);
        return L_SET_N;
      case 'w':
        re_nchset(e);
        re_w_2set(e);
        return L_CHSET;
      case 'W':
        re_nchset(e);
        re_w_2set(e);
        return L_SET_N;
    }
    if (isdigitrune(e->curr_rune)) {
      e->curr_rune -= '0';
      if (isdigitrune(*e->src))
        e->curr_rune = e->curr_rune * 10 + *e->src++ - '0';
      return L_REF;
    }
    return L_CH;
  }

  if (e->is_regex) {
    switch (e->curr_rune) {
      case 0:
        return 0;
      case '$':
      case ')':
      case '*':
      case '+':
      case '.':
      case '?':
      case '^':
      case '|':
        return e->curr_rune;
      case '{':
        return re_countrep(e);
      case '[':
        return re_lexset(e);
      case '(':
        if (e->src[0] == '?') switch (e->src[1]) {
            case '=':
              e->src += 2;
              return L_LA;
            case ':':
              e->src += 2;
              return L_LA_CAP;
            case '!':
              e->src += 2;
              return L_LA_N;
          }
        return '(';
    }
  } else if (e->curr_rune == 0) {
    return 0;
  }

  return L_CH;
}

#define RE_NEXT(env) (env)->lookahead = re_lexer(env)
#define RE_ACCEPT(env, t) ((env)->lookahead == (t) ? RE_NEXT(env), 1 : 0)

static struct slre_node *re_nnode(struct slre_env *e, int type) {
  memset(e->pend, 0, sizeof(struct slre_node));
  e->pend->type = type;
  return e->pend++;
}

static unsigned char re_isemptynd(struct slre_node *nd) {
  if (!nd) return 1;
  switch (nd->type) {
    default:
      return 1;
    case P_ANY:
    case P_CH:
    case P_SET:
    case P_SET_N:
      return 0;
    case P_BRA:
    case P_REF:
      return re_isemptynd(nd->par.xy.x);
    case P_CAT:
      return re_isemptynd(nd->par.xy.x) && re_isemptynd(nd->par.xy.y.y);
    case P_ALT:
      return re_isemptynd(nd->par.xy.x) || re_isemptynd(nd->par.xy.y.y);
    case P_REP:
      return re_isemptynd(nd->par.xy.x) || !nd->par.xy.y.rp.min;
  }
}

static struct slre_node *re_nrep(struct slre_env *e, struct slre_node *nd,
                                 int ng, unsigned short min,
                                 unsigned short max) {
  struct slre_node *rep = re_nnode(e, P_REP);
  if (max == SLRE_MAX_REP && re_isemptynd(nd)) {
    SLRE_THROW(e, SLRE_INF_LOOP_M_EMP_STR);
  }
  rep->par.xy.y.rp.ng = ng;
  rep->par.xy.y.rp.min = min;
  rep->par.xy.y.rp.max = max;
  rep->par.xy.x = nd;
  return rep;
}

static struct slre_node *re_parser(struct slre_env *e);

static struct slre_node *re_parse_la(struct slre_env *e) {
  struct slre_node *nd;
  int min, max;
  switch (e->lookahead) {
    case '^':
      RE_NEXT(e);
      return re_nnode(e, P_BOL);
    case '$':
      RE_NEXT(e);
      return re_nnode(e, P_EOL);
    case L_EOS:
      RE_NEXT(e);
      return re_nnode(e, P_EOS);
    case L_WORD:
      RE_NEXT(e);
      return re_nnode(e, P_WORD);
    case L_WORD_N:
      RE_NEXT(e);
      return re_nnode(e, P_WORD_N);
  }

  switch (e->lookahead) {
    case L_CH:
      nd = re_nnode(e, P_CH);
      nd->par.c = e->curr_rune;
      RE_NEXT(e);
      break;
    case L_CHSET:
      nd = re_nnode(e, P_SET);
      nd->par.cp = e->curr_set;
      RE_NEXT(e);
      break;
    case L_SET_N:
      nd = re_nnode(e, P_SET_N);
      nd->par.cp = e->curr_set;
      RE_NEXT(e);
      break;
    case L_REF:
      nd = re_nnode(e, P_REF);
      if (!e->curr_rune || e->curr_rune > e->num_captures ||
          !e->caps[e->curr_rune]) {
        SLRE_THROW(e, SLRE_INVALID_BACK_REFERENCE);
      }
      nd->par.xy.y.n = e->curr_rune;
      nd->par.xy.x = e->caps[e->curr_rune];
      RE_NEXT(e);
      break;
    case '.':
      RE_NEXT(e);
      nd = re_nnode(e, P_ANY);
      break;
    case '(':
      RE_NEXT(e);
      nd = re_nnode(e, P_BRA);
      if (e->num_captures == SLRE_MAX_CAPS) {
        SLRE_THROW(e, SLRE_TOO_MANY_CAPTURES);
      }
      nd->par.xy.y.n = e->num_captures++;
      nd->par.xy.x = re_parser(e);
      e->caps[nd->par.xy.y.n] = nd;
      if (!RE_ACCEPT(e, ')')) {
        SLRE_THROW(e, SLRE_UNMATCH_LBR);
      }
      break;
    case L_LA:
      RE_NEXT(e);
      nd = re_nnode(e, P_LA);
      nd->par.xy.x = re_parser(e);
      if (!RE_ACCEPT(e, ')')) {
        SLRE_THROW(e, SLRE_UNMATCH_LBR);
      }
      break;
    case L_LA_CAP:
      RE_NEXT(e);
      nd = re_parser(e);
      if (!RE_ACCEPT(e, ')')) {
        SLRE_THROW(e, SLRE_UNMATCH_LBR);
      }
      break;
    case L_LA_N:
      RE_NEXT(e);
      nd = re_nnode(e, P_LA_N);
      nd->par.xy.x = re_parser(e);
      if (!RE_ACCEPT(e, ')')) {
        SLRE_THROW(e, SLRE_UNMATCH_LBR);
      }
      break;
    default:
      SLRE_THROW(e, SLRE_SYNTAX_ERROR);
  }

  switch (e->lookahead) {
    case '*':
      RE_NEXT(e);
      return re_nrep(e, nd, RE_ACCEPT(e, '?'), 0, SLRE_MAX_REP);
    case '+':
      RE_NEXT(e);
      return re_nrep(e, nd, RE_ACCEPT(e, '?'), 1, SLRE_MAX_REP);
    case '?':
      RE_NEXT(e);
      return re_nrep(e, nd, RE_ACCEPT(e, '?'), 0, 1);
    case L_COUNT:
      min = e->min_rep, max = e->max_rep;
      RE_NEXT(e);
      if (max < min) {
        SLRE_THROW(e, SLRE_INVALID_QUANTIFIER);
      }
      return re_nrep(e, nd, RE_ACCEPT(e, '?'), min, max);
  }
  return nd;
}

static unsigned char re_endofcat(Rune c, int is_regex) {
  switch (c) {
    case 0:
      return 1;
    case '|':
    case ')':
      if (is_regex) return 1;
  }
  return 0;
}

static struct slre_node *re_parser(struct slre_env *e) {
  struct slre_node *alt = NULL, *cat, *nd;
  if (!re_endofcat(e->lookahead, e->is_regex)) {
    cat = re_parse_la(e);
    while (!re_endofcat(e->lookahead, e->is_regex)) {
      nd = cat;
      cat = re_nnode(e, P_CAT);
      cat->par.xy.x = nd;
      cat->par.xy.y.y = re_parse_la(e);
    }
    alt = cat;
  }
  if (e->lookahead == '|') {
    RE_NEXT(e);
    nd = alt;
    alt = re_nnode(e, P_ALT);
    alt->par.xy.x = nd;
    alt->par.xy.y.y = re_parser(e);
  }
  return alt;
}

static unsigned int re_nodelen(struct slre_node *nd) {
  unsigned int n = 0;
  if (!nd) return 0;
  switch (nd->type) {
    case P_ALT:
      n = 2;
    case P_CAT:
      return re_nodelen(nd->par.xy.x) + re_nodelen(nd->par.xy.y.y) + n;
    case P_BRA:
    case P_LA:
    case P_LA_N:
      return re_nodelen(nd->par.xy.x) + 2;
    case P_REP:
      n = nd->par.xy.y.rp.max - nd->par.xy.y.rp.min;
      switch (nd->par.xy.y.rp.min) {
        case 0:
          if (!n) return 0;
          if (nd->par.xy.y.rp.max >= SLRE_MAX_REP)
            return re_nodelen(nd->par.xy.x) + 2;
        case 1:
          if (!n) return re_nodelen(nd->par.xy.x);
          if (nd->par.xy.y.rp.max >= SLRE_MAX_REP)
            return re_nodelen(nd->par.xy.x) + 1;
        default:
          n = 4;
          if (nd->par.xy.y.rp.max >= SLRE_MAX_REP) n++;
          return re_nodelen(nd->par.xy.x) + n;
      }
    default:
      return 1;
  }
}

static struct slre_instruction *re_newinst(struct slre_prog *prog, int opcode) {
  memset(prog->end, 0, sizeof(struct slre_instruction));
  prog->end->opcode = opcode;
  return prog->end++;
}

static void re_compile(struct slre_env *e, struct slre_node *nd) {
  struct slre_instruction *inst, *split, *jump, *rep;
  unsigned int n;

  if (!nd) return;

  switch (nd->type) {
    case P_ALT:
      split = re_newinst(e->prog, I_SPLIT);
      re_compile(e, nd->par.xy.x);
      jump = re_newinst(e->prog, I_JUMP);
      re_compile(e, nd->par.xy.y.y);
      split->par.xy.x = split + 1;
      split->par.xy.y.y = jump + 1;
      jump->par.xy.x = e->prog->end;
      break;

    case P_ANY:
      re_newinst(e->prog, I_ANY);
      break;

    case P_BOL:
      re_newinst(e->prog, I_BOL);
      break;

    case P_BRA:
      inst = re_newinst(e->prog, I_LBRA);
      inst->par.n = nd->par.xy.y.n;
      re_compile(e, nd->par.xy.x);
      inst = re_newinst(e->prog, I_RBRA);
      inst->par.n = nd->par.xy.y.n;
      break;

    case P_CAT:
      re_compile(e, nd->par.xy.x);
      re_compile(e, nd->par.xy.y.y);
      break;

    case P_CH:
      inst = re_newinst(e->prog, I_CH);
      inst->par.c = nd->par.c;
      break;

    case P_EOL:
      re_newinst(e->prog, I_EOL);
      break;

    case P_EOS:
      re_newinst(e->prog, I_EOS);
      break;

    case P_LA:
      split = re_newinst(e->prog, I_LA);
      re_compile(e, nd->par.xy.x);
      re_newinst(e->prog, I_END);
      split->par.xy.x = split + 1;
      split->par.xy.y.y = e->prog->end;
      break;
    case P_LA_N:
      split = re_newinst(e->prog, I_LA_N);
      re_compile(e, nd->par.xy.x);
      re_newinst(e->prog, I_END);
      split->par.xy.x = split + 1;
      split->par.xy.y.y = e->prog->end;
      break;

    case P_REF:
      inst = re_newinst(e->prog, I_REF);
      inst->par.n = nd->par.xy.y.n;
      break;

    case P_REP:
      n = nd->par.xy.y.rp.max - nd->par.xy.y.rp.min;
      switch (nd->par.xy.y.rp.min) {
        case 0:
          if (!n) break;
          if (nd->par.xy.y.rp.max >= SLRE_MAX_REP) {
            split = re_newinst(e->prog, I_SPLIT);
            re_compile(e, nd->par.xy.x);
            jump = re_newinst(e->prog, I_JUMP);
            jump->par.xy.x = split;
            split->par.xy.x = split + 1;
            split->par.xy.y.y = e->prog->end;
            if (nd->par.xy.y.rp.ng) {
              split->par.xy.y.y = split + 1;
              split->par.xy.x = e->prog->end;
            }
            break;
          }
        case 1:
          if (!n) {
            re_compile(e, nd->par.xy.x);
            break;
          }
          if (nd->par.xy.y.rp.max >= SLRE_MAX_REP) {
            inst = e->prog->end;
            re_compile(e, nd->par.xy.x);
            split = re_newinst(e->prog, I_SPLIT);
            split->par.xy.x = inst;
            split->par.xy.y.y = e->prog->end;
            if (nd->par.xy.y.rp.ng) {
              split->par.xy.y.y = inst;
              split->par.xy.x = e->prog->end;
            }
            break;
          }
        default:
          inst = re_newinst(e->prog, I_REP_INI);
          inst->par.xy.y.rp.min = nd->par.xy.y.rp.min;
          inst->par.xy.y.rp.max = n;
          rep = re_newinst(e->prog, I_REP);
          split = re_newinst(e->prog, I_SPLIT);
          re_compile(e, nd->par.xy.x);
          jump = re_newinst(e->prog, I_JUMP);
          jump->par.xy.x = rep;
          rep->par.xy.x = e->prog->end;
          split->par.xy.x = split + 1;
          split->par.xy.y.y = e->prog->end;
          if (nd->par.xy.y.rp.ng) {
            split->par.xy.y.y = split + 1;
            split->par.xy.x = e->prog->end;
          }
          if (nd->par.xy.y.rp.max >= SLRE_MAX_REP) {
            inst = split + 1;
            split = re_newinst(e->prog, I_SPLIT);
            split->par.xy.x = inst;
            split->par.xy.y.y = e->prog->end;
            if (nd->par.xy.y.rp.ng) {
              split->par.xy.y.y = inst;
              split->par.xy.x = e->prog->end;
            }
            break;
          }
          break;
      }
      break;

    case P_SET:
      inst = re_newinst(e->prog, I_SET);
      inst->par.cp = nd->par.cp;
      break;
    case P_SET_N:
      inst = re_newinst(e->prog, I_SET_N);
      inst->par.cp = nd->par.cp;
      break;

    case P_WORD:
      re_newinst(e->prog, I_WORD);
      break;
    case P_WORD_N:
      re_newinst(e->prog, I_WORD_N);
      break;
  }
}

#ifdef RE_TEST
static void print_set(struct slre_class *cp) {
  struct slre_range *p;
  for (p = cp->spans; p < cp->end; p++) {
    printf("%s", p == cp->spans ? "'" : ",'");
    printf(
        p->s >= 32 && p->s < 127 ? "%c" : (p->s < 256 ? "\\x%02X" : "\\u%04X"),
        p->s);
    if (p->s != p->e) {
      printf(p->e >= 32 && p->e < 127 ? "-%c"
                                      : (p->e < 256 ? "-\\x%02X" : "-\\u%04X"),
             p->e);
    }
    printf("'");
  }
  printf("]");
}

static void node_print(struct slre_node *nd) {
  if (!nd) {
    printf("Empty");
    return;
  }
  switch (nd->type) {
    case P_ALT:
      printf("{");
      node_print(nd->par.xy.x);
      printf(" | ");
      node_print(nd->par.xy.y.y);
      printf("}");
      break;
    case P_ANY:
      printf(".");
      break;
    case P_BOL:
      printf("^");
      break;
    case P_BRA:
      node_print(nd->par.xy.x);
      printf(")");
      break;
    case P_CAT:
      printf("{");
      node_print(nd->par.xy.x);
      printf(" & ");
      node_print(nd->par.xy.y.y);
      printf("}");
      break;
    case P_CH:
      printf(nd->par.c >= 32 && nd->par.c < 127 ? "'%c'" : "'\\u%04X'",
             nd->par.c);
      break;
    case P_EOL:
      printf("$");
      break;
    case P_EOS:
      printf("\\0");
      break;
    case P_LA:
      printf("LA(");
      node_print(nd->par.xy.x);
      printf(")");
      break;
    case P_LA_N:
      printf("LA_N(");
      node_print(nd->par.xy.x);
      printf(")");
      break;
    case P_REF:
      printf("\\%d", nd->par.xy.y.n);
      break;
    case P_REP:
      node_print(nd->par.xy.x);
      printf(nd->par.xy.y.rp.ng ? "{%d,%d}?" : "{%d,%d}", nd->par.xy.y.rp.min,
             nd->par.xy.y.rp.max);
      break;
    case P_SET:
      printf("[");
      print_set(nd->par.cp);
      break;
    case P_SET_N:
      printf("[^");
      print_set(nd->par.cp);
      break;
    case P_WORD:
      printf("\\b");
      break;
    case P_WORD_N:
      printf("\\B");
      break;
  }
}

static void program_print(struct slre_prog *prog) {
  struct slre_instruction *inst;
  for (inst = prog->start; inst < prog->end; ++inst) {
    printf("%3d: ", inst - prog->start);
    switch (inst->opcode) {
      case I_END:
        puts("end");
        break;
      case I_ANY:
        puts(".");
        break;
      case I_ANYNL:
        puts(". | '\\r' | '\\n'");
        break;
      case I_BOL:
        puts("^");
        break;
      case I_CH:
        printf(
            inst->par.c >= 32 && inst->par.c < 127 ? "'%c'\n" : "'\\u%04X'\n",
            inst->par.c);
        break;
      case I_EOL:
        puts("$");
        break;
      case I_EOS:
        puts("\\0");
        break;
      case I_JUMP:
        printf("-->%d\n", inst->par.xy.x - prog->start);
        break;
      case I_LA:
        printf("la %d %d\n", inst->par.xy.x - prog->start,
               inst->par.xy.y.y - prog->start);
        break;
      case I_LA_N:
        printf("la_n %d %d\n", inst->par.xy.x - prog->start,
               inst->par.xy.y.y - prog->start);
        break;
      case I_LBRA:
        printf("( %d\n", inst->par.n);
        break;
      case I_RBRA:
        printf(") %d\n", inst->par.n);
        break;
      case I_SPLIT:
        printf("-->%d | -->%d\n", inst->par.xy.x - prog->start,
               inst->par.xy.y.y - prog->start);
        break;
      case I_REF:
        printf("\\%d\n", inst->par.n);
        break;
      case I_REP:
        printf("repeat -->%d\n", inst->par.xy.x - prog->start);
        break;
      case I_REP_INI:
        printf("init_rep %d %d\n", inst->par.xy.y.rp.min,
               inst->par.xy.y.rp.min + inst->par.xy.y.rp.max);
        break;
      case I_SET:
        printf("[");
        print_set(inst->par.cp);
        puts("");
        break;
      case I_SET_N:
        printf("[^");
        print_set(inst->par.cp);
        puts("");
        break;
      case I_WORD:
        puts("\\w");
        break;
      case I_WORD_N:
        puts("\\W");
        break;
    }
  }
}
#endif

int slre_compile(const char *pat, size_t pat_len, const char *flags,
                 volatile size_t fl_len, struct slre_prog **pr, int is_regex) {
  struct slre_env e;
  struct slre_node *nd;
  struct slre_instruction *split, *jump;
  int err_code;

  e.is_regex = is_regex;
  e.prog = (struct slre_prog *) SLRE_MALLOC(sizeof(struct slre_prog));
  e.pstart = e.pend =
      (struct slre_node *) SLRE_MALLOC(sizeof(struct slre_node) * pat_len * 2);
  e.prog->flags = is_regex ? SLRE_FLAG_RE : 0;

  if ((err_code = setjmp(e.jmp_buf)) != SLRE_OK) {
    SLRE_FREE(e.pstart);
    SLRE_FREE(e.prog);
    return err_code;
  }

  while (fl_len--) {
    switch (flags[fl_len]) {
      case 'g':
        e.prog->flags |= SLRE_FLAG_G;
        break;
      case 'i':
        e.prog->flags |= SLRE_FLAG_I;
        break;
      case 'm':
        e.prog->flags |= SLRE_FLAG_M;
        break;
    }
  }

  e.src = pat;
  e.src_end = pat + pat_len;
  e.sets_num = 0;
  e.num_captures = 1;
  /*e.flags = flags;*/
  memset(e.caps, 0, sizeof(e.caps));

  RE_NEXT(&e);
  nd = re_parser(&e);
  if (e.lookahead == ')') {
    SLRE_THROW(&e, SLRE_UNMATCH_RBR);
  }
  if (e.lookahead != 0) {
    SLRE_THROW(&e, SLRE_SYNTAX_ERROR);
  }

  e.prog->num_captures = e.num_captures;
  e.prog->start = e.prog->end = (struct slre_instruction *) SLRE_MALLOC(
      (re_nodelen(nd) + 6) * sizeof(struct slre_instruction));

  split = re_newinst(e.prog, I_SPLIT);
  split->par.xy.x = split + 3;
  split->par.xy.y.y = split + 1;
  re_newinst(e.prog, I_ANYNL);
  jump = re_newinst(e.prog, I_JUMP);
  jump->par.xy.x = split;
  re_newinst(e.prog, I_LBRA);
  re_compile(&e, nd);
  re_newinst(e.prog, I_RBRA);
  re_newinst(e.prog, I_END);

#ifdef RE_TEST
  node_print(nd);
  putchar('\n');
  program_print(e.prog);
#endif

  SLRE_FREE(e.pstart);

  if (pr != NULL) {
    *pr = e.prog;
  } else {
    slre_free(e.prog);
  }

  return err_code;
}

void slre_free(struct slre_prog *prog) {
  if (prog) {
    SLRE_FREE(prog->start);
    SLRE_FREE(prog);
  }
}

static struct slre_thread *re_newthread(struct slre_thread *t,
                                        struct slre_instruction *pc,
                                        const char *start,
                                        struct slre_loot *loot) {
  struct slre_thread *new_thread =
      (struct slre_thread *) SLRE_MALLOC(sizeof(struct slre_thread));
  if (new_thread != NULL) new_thread->prev = t;
  t->pc = pc;
  t->start = start;
  t->loot = *loot;
  return new_thread;
}

static struct slre_thread *get_prev_thread(struct slre_thread *t) {
  struct slre_thread *tmp_thr = t->prev;
  SLRE_FREE(t);
  return tmp_thr;
}

static void free_threads(struct slre_thread *t) {
  while (t->prev != NULL) t = get_prev_thread(t);
}

static unsigned char re_match(struct slre_instruction *pc, const char *current,
                              const char *end, const char *bol,
                              unsigned int flags, struct slre_loot *loot) {
  struct slre_loot sub, tmpsub;
  Rune c, r;
  struct slre_range *p;
  size_t i;
  struct slre_thread thread, *curr_thread, *tmp_thr;

  /* queue initial thread */
  thread.prev = NULL;
  curr_thread = re_newthread(&thread, pc, current, loot);

  /* run threads in stack order */
  do {
    curr_thread = get_prev_thread(curr_thread);
    pc = curr_thread->pc;
    current = curr_thread->start;
    sub = curr_thread->loot;
    for (;;) {
      switch (pc->opcode) {
        case I_END:
          memcpy(loot->caps, sub.caps, sizeof loot->caps);
          free_threads(curr_thread);
          return 1;
        case I_ANY:
        case I_ANYNL:
          if (current < end) {
            current += chartorune(&c, current);
            if (c && !(pc->opcode == I_ANY && isnewline(c))) break;
          }
          goto no_match;

        case I_BOL:
          if (current == bol) break;
          if ((flags & SLRE_FLAG_M) && isnewline(current[-1])) break;
          goto no_match;
        case I_CH:
          if (current < end) {
            current += chartorune(&c, current);
            if (c &&
                (c == pc->par.c || ((flags & SLRE_FLAG_I) &&
                                    tolowerrune(c) == tolowerrune(pc->par.c))))
              break;
          }
          goto no_match;
        case I_EOL:
          if (current >= end) break;
          if ((flags & SLRE_FLAG_M) && isnewline(*current)) break;
          goto no_match;
        case I_EOS:
          if (current >= end) break;
          goto no_match;

        case I_JUMP:
          pc = pc->par.xy.x;
          continue;

        case I_LA:
          if (re_match(pc->par.xy.x, current, end, bol, flags, &sub)) {
            pc = pc->par.xy.y.y;
            continue;
          }
          goto no_match;
        case I_LA_N:
          tmpsub = sub;
          if (!re_match(pc->par.xy.x, current, end, bol, flags, &tmpsub)) {
            pc = pc->par.xy.y.y;
            continue;
          }
          goto no_match;

        case I_LBRA:
          sub.caps[pc->par.n].start = current;
          break;

        case I_REF:
          i = sub.caps[pc->par.n].end - sub.caps[pc->par.n].start;
          if (flags & SLRE_FLAG_I) {
            int num = i;
            const char *s = current, *p = sub.caps[pc->par.n].start;
            Rune rr;
            for (; num && *s && *p; num--) {
              s += chartorune(&r, s);
              p += chartorune(&rr, p);
              if (tolowerrune(r) != tolowerrune(rr)) break;
            }
            if (num) goto no_match;
          } else if (strncmp(current, sub.caps[pc->par.n].start, i)) {
            goto no_match;
          }
          if (i > 0) current += i;
          break;

        case I_REP:
          if (pc->par.xy.y.rp.min) {
            pc->par.xy.y.rp.min--;
            pc++;
          } else if (!pc->par.xy.y.rp.max--) {
            pc = pc->par.xy.x;
            continue;
          }
          break;

        case I_REP_INI:
          (pc + 1)->par.xy.y.rp.min = pc->par.xy.y.rp.min;
          (pc + 1)->par.xy.y.rp.max = pc->par.xy.y.rp.max;
          break;

        case I_RBRA:
          sub.caps[pc->par.n].end = current;
          break;

        case I_SET:
        case I_SET_N:
          if (current >= end) goto no_match;
          current += chartorune(&c, current);
          if (!c) goto no_match;

          i = 1;
          for (p = pc->par.cp->spans; i && p < pc->par.cp->end; p++)
            if (flags & SLRE_FLAG_I) {
              for (r = p->s; r <= p->e; ++r)
                if (tolowerrune(c) == tolowerrune(r)) {
                  i = 0;
                  break;
                }
            } else if (p->s <= c && c <= p->e)
              i = 0;

          if (pc->opcode == I_SET) i = !i;
          if (i) break;
          goto no_match;

        case I_SPLIT:
          tmp_thr = curr_thread;
          curr_thread =
              re_newthread(curr_thread, pc->par.xy.y.y, current, &sub);
          if (curr_thread == NULL) {
            fprintf(stderr, "re_match: no memory for thread!\n");
            free_threads(tmp_thr);
            return 0;
          }
          pc = pc->par.xy.x;
          continue;

        case I_WORD:
        case I_WORD_N:
          i = (current > bol && iswordchar(current[-1]));
          if (iswordchar(current[0])) i = !i;
          if (pc->opcode == I_WORD_N) i = !i;
          if (i) break;
        /* goto no_match; */

        default:
          goto no_match;
      }
      pc++;
    }
  no_match:
    ;
  } while (curr_thread->prev != NULL);
  return 0;
}

int slre_exec(struct slre_prog *prog, int flag_g, const char *start,
              const char *end, struct slre_loot *loot) {
  struct slre_loot tmpsub;
  const char *st = start;

  if (!loot) loot = &tmpsub;
  memset(loot, 0, sizeof(*loot));

  if (!flag_g) {
    loot->num_captures = prog->num_captures;
    return !re_match(prog->start, start, end, start, prog->flags, loot);
  }

  while (re_match(prog->start, st, end, start, prog->flags, &tmpsub)) {
    unsigned int i;
    st = tmpsub.caps[0].end;
    for (i = 0; i < prog->num_captures; i++) {
      struct slre_cap *l = &loot->caps[loot->num_captures + i];
      struct slre_cap *s = &tmpsub.caps[i];
      l->start = s->start;
      l->end = s->end;
    }
    loot->num_captures += prog->num_captures;
  }
  return !loot->num_captures;
}

int slre_replace(struct slre_loot *loot, const char *src, size_t src_len,
                 const char *rstr, size_t rstr_len, struct slre_loot *dstsub) {
  int size = 0, n;
  Rune curr_rune;
  const char *const rstr_end = rstr + rstr_len;

  memset(dstsub, 0, sizeof(*dstsub));
  while (rstr < rstr_end && !(n = re_nextc_raw(&curr_rune, &rstr, rstr_end)) &&
         curr_rune) {
    int sz;
    if (n < 0) return n;
    if (curr_rune == '$') {
      n = re_nextc(&curr_rune, &rstr, rstr_end);
      if (n < 0) return n;
      switch (curr_rune) {
        case '&':
          sz = loot->caps[0].end - loot->caps[0].start;
          size += sz;
          dstsub->caps[dstsub->num_captures++] = loot->caps[0];
          break;
        case '0':
        case '1':
        case '2':
        case '3':
        case '4':
        case '5':
        case '6':
        case '7':
        case '8':
        case '9': {
          int sbn = dec(curr_rune);
          if (0 == sbn && rstr[0] && isdigitrune(rstr[0])) {
            n = re_nextc(&curr_rune, &rstr, rstr_end);
            if (n < 0) return n;
            sz = dec(curr_rune);
            sbn = sbn * 10 + sz;
          }
          if (sbn >= loot->num_captures) break;
          sz = loot->caps[sbn].end - loot->caps[sbn].start;
          size += sz;
          dstsub->caps[dstsub->num_captures++] = loot->caps[sbn];
          break;
        }
        case '`':
          sz = loot->caps[0].start - src;
          size += sz;
          dstsub->caps[dstsub->num_captures].start = src;
          dstsub->caps[dstsub->num_captures++].end = loot->caps[0].start;
          break;
        case '\'':
          sz = src + src_len - loot->caps[0].end;
          size += sz;
          dstsub->caps[dstsub->num_captures].start = loot->caps[0].end;
          dstsub->caps[dstsub->num_captures++].end = loot->caps[0].end + sz;
          break;
        case '$':
          size++;
          dstsub->caps[dstsub->num_captures].start = rstr - 1;
          dstsub->caps[dstsub->num_captures++].end = rstr;
          break;
        default:
          return SLRE_BAD_CHAR_AFTER_USD;
      }
    } else {
      char tmps[300], *d = tmps;
      size += (sz = runetochar(d, &curr_rune));
      if (!dstsub->num_captures ||
          dstsub->caps[dstsub->num_captures - 1].end != rstr - sz) {
        dstsub->caps[dstsub->num_captures].start = rstr - sz;
        dstsub->caps[dstsub->num_captures++].end = rstr;
      } else
        dstsub->caps[dstsub->num_captures - 1].end = rstr;
    }
  }
  return size;
}

int slre_match(const char *re, size_t re_len, const char *flags, size_t fl_len,
               const char *str, size_t str_len, struct slre_loot *loot) {
  struct slre_prog *prog = NULL;
  int res;

  if ((res = slre_compile(re, re_len, flags, fl_len, &prog, 1)) == SLRE_OK) {
    res = slre_exec(prog, prog->flags & SLRE_FLAG_G, str, str + str_len, loot);
    slre_free(prog);
  }

  return res;
}

int slre_get_flags(struct slre_prog *crp) {
  return crp->flags;
}

#ifdef SLRE_TEST

#include <errno.h>

static const char *err_code_to_str(int err_code) {
  static const char *ar[] = {
      "no error", "invalid decimal digit", "invalid hex digit",
      "invalid escape character", "invalid unterminated escape sequence",
      "syntax error", "unmatched left parenthesis",
      "unmatched right parenthesis", "numeric overflow",
      "infinite loop empty string", "too many charsets",
      "invalid charset range", "charset is too large", "malformed charset",
      "invalid back reference", "too many captures", "invalid quantifier",
      "bad character after $"};

  typedef char static_assertion_err_codes_out_of_sync
      [2 * !!(((sizeof(ar) / sizeof(ar[0])) == SLRE_BAD_CHAR_AFTER_USD + 1)) -
       1];

  return err_code >= 0 && err_code < (int) (sizeof(ar) / sizeof(ar[0]))
             ? ar[err_code]
             : "invalid error code";
}

#define RE_TEST_STR_SIZE 2000

static unsigned get_flags(const char *ch) {
  unsigned int flags = 0;

  while (*ch != '\0') {
    switch (*ch) {
      case 'g':
        flags |= SLRE_FLAG_G;
        break;
      case 'i':
        flags |= SLRE_FLAG_I;
        break;
      case 'm':
        flags |= SLRE_FLAG_M;
        break;
      case 'r':
        flags |= SLRE_FLAG_RE;
        break;
      default:
        return flags;
    }
    ch++;
  }
  return flags;
}

static void show_usage_and_exit(char *argv[]) {
  fprintf(stderr, "Usage: %s [OPTIONS]\n", argv[0]);
  fprintf(stderr, "%s\n", "OPTIONS:");
  fprintf(stderr, "%s\n", "  -p <regex_pattern>     Regex pattern");
  fprintf(stderr, "%s\n", "  -o <regex_flags>       Combination of g,i,m");
  fprintf(stderr, "%s\n", "  -s <string>            String to match");
  fprintf(stderr, "%s\n", "  -f <file_name>         Match lines from file");
  fprintf(stderr, "%s\n", "  -n <cap_no>            Show given capture");
  fprintf(stderr, "%s\n", "  -r <replace_str>       Replace given capture");
  fprintf(stderr, "%s\n", "  -v                     Show verbose stats");
  exit(1);
}

static int process_line(struct slre_prog *pr, const char *flags,
                        const char *line, const char *cap_no,
                        const char *replace, const char *verbose) {
  struct slre_loot loot;
  unsigned int fl = flags == NULL ? 0 : get_flags(flags);
  int i, n = cap_no == NULL ? -1 : atoi(cap_no), err_code = 0;
  struct slre_cap *cap = &loot.caps[n];

  err_code =
      slre_exec(pr, pr->flags & SLRE_FLAG_G, line, line + strlen(line), &loot);
  if (err_code == SLRE_OK) {
    if (n >= 0 && n < loot.num_captures && replace != NULL) {
      struct slre_cap *cap = &loot.caps[n];
      printf("%.*s", (int) (cap->start - line), line);
      printf("%s", replace);
      printf("%.*s", (int) ((line + strlen(line)) - cap->end), cap->end);
    } else if (n >= 0 && n < loot.num_captures) {
      printf("%.*s\n", (int) (cap->end - cap->start), cap->start);
    }

    if (verbose != NULL) {
      fprintf(stderr, "%s\n", "Captures:");
      for (i = 0; i < loot.num_captures; i++) {
        fprintf(stderr, "%d [%.*s]\n", i,
                (int) (loot.caps[i].end - loot.caps[i].start),
                loot.caps[i].start);
      }
    }
  }

  return err_code;
}

int main(int argc, char **argv) {
  const char *str = NULL, *pattern = NULL, *replace = NULL;
  const char *flags = "", *file_name = NULL, *cap_no = NULL, *verbose = NULL;
  struct slre_prog *pr = NULL;
  int i, err_code = 0;

  /* Execute inline code */
  for (i = 1; i < argc; i++) {
    if (strcmp(argv[i], "-p") == 0 && i + 1 < argc) {
      pattern = argv[++i];
    } else if (strcmp(argv[i], "-o") == 0 && i + 1 < argc) {
      flags = argv[++i];
    } else if (strcmp(argv[i], "-s") == 0 && i + 1 < argc) {
      str = argv[++i];
    } else if (strcmp(argv[i], "-f") == 0 && i + 1 < argc) {
      file_name = argv[++i];
    } else if (strcmp(argv[i], "-n") == 0 && i + 1 < argc) {
      cap_no = argv[++i];
    } else if (strcmp(argv[i], "-r") == 0 && i + 1 < argc) {
      replace = argv[++i];
    } else if (strcmp(argv[i], "-v") == 0) {
      verbose = "";
    } else if (strcmp(argv[i], "-h") == 0) {
      show_usage_and_exit(argv);
    } else {
      show_usage_and_exit(argv);
    }
  }

  if (pattern == NULL) {
    fprintf(stderr, "%s\n", "-p option is mandatory");
    exit(1);
  } else if ((err_code = slre_compile(pattern, strlen(pattern), flags,
                                      strlen(flags), &pr, 1)) != SLRE_OK) {
    fprintf(stderr, "slre_compile(%s): %s\n", argv[0],
            err_code_to_str(err_code));
    exit(1);
  } else if (str != NULL) {
    err_code = process_line(pr, flags, str, cap_no, replace, verbose);
  } else if (file_name != NULL) {
    FILE *fp = strcmp(file_name, "-") == 0 ? stdin : fopen(file_name, "rb");
    char line[20 * 1024];
    if (fp == NULL) {
      fprintf(stderr, "Cannot open %s: %s\n", file_name, strerror(errno));
      exit(1);
    } else {
      /* Return success if at least one line matches */
      err_code = 1;
      while (fgets(line, sizeof(line), fp) != NULL) {
        if (process_line(pr, flags, line, cap_no, replace, verbose) ==
            SLRE_OK) {
          err_code = 0;
        }
      }
      fclose(fp); /* If fp == stdin, it is safe to close, too */
    }
  } else {
    fprintf(stderr, "%s\n", "Please specify one of -s or -f options");
    exit(1);
  }
  slre_free(pr);

  return err_code;
}
#endif /* SLRE_TEST */

#endif /* V7_ENABLE__RegExp */
#ifdef V7_MODULE_LINES
#line 1 "v7/src/heapusage.c"
#endif
/*
 * Copyright (c) 2014-2016 Cesanta Software Limited
 * All rights reserved
 */

#include <stdlib.h>
#include <stdio.h>
#include <assert.h>

#if defined(V7_HEAPUSAGE_ENABLE)

/*
 * A flag that is set by GC before allocating its buffers, so we can
 * distinguish these buffers from other allocations
 */
volatile int heap_dont_count = 0;

extern void *__real_malloc(size_t size);
extern void *__real_calloc(size_t num, size_t size);
extern void *__real_realloc(void *p, size_t size);
extern void __real_free(void *p);

/* TODO(dfrank): make it dynamically allocated from heap */
#define CELLS_CNT (1024 * 32)

typedef struct cell {
  void *p;
  unsigned dont_count : 1;
  unsigned size : 31;
} cell_t;

typedef struct alloc_registry {
  size_t used_cells_cnt;
  size_t allocated_size;
  size_t real_used_cells_cnt;
  size_t real_allocated_size;
  cell_t cells[CELLS_CNT];
} alloc_registry_t;

static alloc_registry_t registry = {0};

/*
 * Make a record about an allocated buffer `p` of size `size`
 */
static void cell_allocated(void *p, size_t size) {
  int i;
  int cell_num = -1;

  if (p != NULL && size != 0) {
    /* TODO(dfrank): make it dynamically allocated from heap */
    assert(registry.real_used_cells_cnt < CELLS_CNT);

    for (i = 0; i < CELLS_CNT; ++i) {
      if (registry.cells[i].p == NULL) {
        cell_num = i;
        break;
      }
    }

    assert(cell_num != -1);

    registry.cells[cell_num].p = p;
    registry.cells[cell_num].size = size;
    registry.cells[cell_num].dont_count = !!heap_dont_count;

    registry.real_allocated_size += size;
    registry.real_used_cells_cnt += 1;

    if (!heap_dont_count) {
      registry.allocated_size += size;
      registry.used_cells_cnt += 1;
    }

#if 0
    printf("alloc=0x%lx, size=%lu, total=%lu\n", (unsigned long)p, size,
        registry.allocated_size);
#endif
  }
}

/*
 * Delete a record about an allocated buffer `p`. If our registry does not
 * contain anything about the given pointer, the call is ignored. We can't
 * generate an error because shared libraries still use unwrapped heap
 * functions, so we can face "unknown" pointers.
 */
static void cell_freed(void *p) {
  int i;
  int cell_num = -1;

  if (p != NULL) {
    assert(registry.real_used_cells_cnt > 0);

    for (i = 0; i < CELLS_CNT; ++i) {
      if (registry.cells[i].p == p) {
        cell_num = i;
        break;
      }
    }

    /*
     * NOTE: it would be nice to have `assert(cell_num != -1);`, but
     * unfortunately not all allocations are wrapped: shared libraries will
     * still use unwrapped mallocs, so we might get unknown pointers here.
     */

    if (cell_num != -1) {
      registry.real_allocated_size -= registry.cells[cell_num].size;
      registry.real_used_cells_cnt -= 1;

      if (!registry.cells[cell_num].dont_count) {
        registry.allocated_size -= registry.cells[cell_num].size;
        registry.used_cells_cnt -= 1;
      }

      registry.cells[cell_num].p = NULL;
      registry.cells[cell_num].size = 0;
      registry.cells[cell_num].dont_count = 0;

#if 0
      printf("free=0x%lx, total=%lu\n", (unsigned long)p, registry.allocated_size);
#endif
    }
  }
}

/*
 * Wrappers of the standard heap functions
 */

void *__wrap_malloc(size_t size) {
  void *ret = __real_malloc(size);
  cell_allocated(ret, size);
  return ret;
}

void *__wrap_calloc(size_t num, size_t size) {
  void *ret = __real_calloc(num, size);
  cell_allocated(ret, num * size);
  return ret;
}

void *__wrap_realloc(void *p, size_t size) {
  void *ret;
  cell_freed(p);
  ret = __real_realloc(p, size);
  cell_allocated(ret, size);
  return ret;
}

void __wrap_free(void *p) {
  __real_free(p);
  cell_freed(p);
}

/*
 * Small API to get some stats, see header file for details
 */

size_t heapusage_alloc_size(void) {
  return registry.allocated_size;
}

size_t heapusage_allocs_cnt(void) {
  return registry.used_cells_cnt;
}

#endif /* V7_HEAPUSAGE_ENABLE */
#ifdef V7_MODULE_LINES
#line 1 "v7/src/cyg_profile.c"
#endif
/*
 * Copyright (c) 2014 Cesanta Software Limited
 * All rights reserved
 */

/*
 * This file contains GCC/clang instrumentation callbacks. The actual
 * code in these callbacks depends on enabled features.
 *
 * Currently, the code from different subsystems is embedded right into
 * callbacks for performance reasons. It would be probably more elegant
 * to have subsystem-specific functions that will be called from these
 * callbacks, but since the callbacks are called really a lot (on each v7
 * function call), I decided it's better to inline the code right here.
 */

/* Amalgamated: #include "v7/src/internal.h" */
/* Amalgamated: #include "v7/src/cyg_profile.h" */
/* Amalgamated: #include "v7/src/core.h" */

#if defined(V7_CYG_PROFILE_ON)

#if defined(V7_ENABLE_CALL_TRACE)

#define CALL_TRACE_SIZE 32

typedef struct {
  uint16_t size;
  uint16_t missed_cnt;
  void *addresses[CALL_TRACE_SIZE];
} call_trace_t;

static call_trace_t call_trace = {0};

NOINSTR
void call_trace_print(const char *prefix, const char *suffix, size_t skip_cnt,
                      size_t max_cnt) {
  int i;
  if (call_trace.missed_cnt > 0) {
    fprintf(stderr, "missed calls! (%d) ", (int) call_trace.missed_cnt);
  }
  if (prefix != NULL) {
    fprintf(stderr, "%s", prefix);
  }
  for (i = (int) call_trace.size - 1 - skip_cnt; i >= 0; i--) {
    fprintf(stderr, " %lx", (unsigned long) call_trace.addresses[i]);
    if (max_cnt > 0) {
      if (--max_cnt == 0) {
        break;
      }
    }
  }
  if (suffix != NULL) {
    fprintf(stderr, "%s", suffix);
  }
  fprintf(stderr, "\n");
}

#endif

#ifndef IRAM
#define IRAM
#endif

#ifndef NOINSTR
#define NOINSTR __attribute__((no_instrument_function))
#endif

#if defined(__cplusplus)
extern "C" {
#endif /* __cplusplus */
IRAM NOINSTR void __cyg_profile_func_enter(void *this_fn, void *call_site);

IRAM NOINSTR void __cyg_profile_func_exit(void *this_fn, void *call_site);

#if defined(__cplusplus)
}
#endif /* __cplusplus */

IRAM void __cyg_profile_func_enter(void *this_fn, void *call_site) {
#if defined(V7_STACK_GUARD_MIN_SIZE)
  {
    static int profile_enter = 0;
    void *fp = __builtin_frame_address(0);

    (void) call_site;

    if (profile_enter || v7_sp_limit == NULL) return;

    profile_enter++;
    if (v7_head != NULL && fp < v7_head->sp_lwm) v7_head->sp_lwm = fp;

    if (((int) fp - (int) v7_sp_limit) < V7_STACK_GUARD_MIN_SIZE) {
      printf("fun %p sp %p limit %p left %d\n", this_fn, fp, v7_sp_limit,
             (int) fp - (int) v7_sp_limit);
      abort();
    }
    profile_enter--;
  }
#endif

#if defined(V7_ENABLE_GC_CHECK)
  {
    (void) this_fn;
    (void) call_site;
  }
#endif

#if defined(V7_ENABLE_STACK_TRACKING)
  {
    struct v7 *v7;
    struct stack_track_ctx *ctx;
    void *fp = __builtin_frame_address(1);

    (void) this_fn;
    (void) call_site;

    /*
     * TODO(dfrank): it actually won't work for multiple instances of v7 running
     * in parallel threads. We need to know the exact v7 instance for which
     * current function is called, but so far I failed to find a way to do this.
     */
    for (v7 = v7_head; v7 != NULL; v7 = v7->next_v7) {
      for (ctx = v7->stack_track_ctx; ctx != NULL; ctx = ctx->next) {
        /* commented because it fails on legal code compiled with -O3 */
        /*assert(fp <= ctx->start);*/

        if (fp < ctx->max) {
          ctx->max = fp;
        }
      }
    }
  }
#endif

#if defined(V7_ENABLE_CALL_TRACE)
  if (call_trace.size < CALL_TRACE_SIZE) {
    call_trace.addresses[call_trace.size] = this_fn;
    call_trace.size++;
  } else {
    call_trace.missed_cnt++;
  }
#endif
}

IRAM void __cyg_profile_func_exit(void *this_fn, void *call_site) {
#if defined(V7_STACK_GUARD_MIN_SIZE)
  {
    (void) this_fn;
    (void) call_site;
  }
#endif

#if defined(V7_ENABLE_GC_CHECK)
  {
    struct v7 *v7;
    void *fp = __builtin_frame_address(1);

    (void) this_fn;
    (void) call_site;

    for (v7 = v7_head; v7 != NULL; v7 = v7->next_v7) {
      v7_val_t **vp;
      if (v7->owned_values.buf == NULL) continue;
      vp = (v7_val_t **) (v7->owned_values.buf + v7->owned_values.len -
                          sizeof(v7_val_t *));

      for (; (char *) vp >= v7->owned_values.buf; vp--) {
        /*
         * Check if a variable belongs to a dead stack frame.
         * Addresses lower than the parent frame belong to the
         * stack frame of the function about to return.
         * But the heap also usually below the stack and
         * we don't know the end of the stack. But this hook
         * is called at each function return, so we have
         * to check only up to the maximum stack frame size,
         * let's arbitrarily but reasonably set that at 8k.
         */
        if ((void *) *vp <= fp && (void *) *vp > (fp + 8196)) {
          fprintf(stderr, "Found owned variable after return\n");
          abort();
        }
      }
    }
  }
#endif

#if defined(V7_ENABLE_STACK_TRACKING)
  {
    (void) this_fn;
    (void) call_site;
  }
#endif

#if defined(V7_ENABLE_CALL_TRACE)
  if (call_trace.missed_cnt > 0) {
    call_trace.missed_cnt--;
  } else if (call_trace.size > 0) {
    if (call_trace.addresses[call_trace.size - 1] != this_fn) {
      abort();
    }
    call_trace.size--;
  } else {
    /*
     * We may get here if calls to `__cyg_profile_func_exit()` and
     * `__cyg_profile_func_enter()` are unbalanced.
     *
     * TODO(dfrank) understand, why in the beginning of the program execution
     * we get here. I was sure this should be impossible.
     */
    /* abort(); */
  }
#endif
}

#if defined(V7_ENABLE_STACK_TRACKING)

void v7_stack_track_start(struct v7 *v7, struct stack_track_ctx *ctx) {
  /* insert new context at the head of the list */
  ctx->next = v7->stack_track_ctx;
  v7->stack_track_ctx = ctx;

  /* init both `max` and `start` to the current frame pointer */
  ctx->max = ctx->start = __builtin_frame_address(0);
}

int v7_stack_track_end(struct v7 *v7, struct stack_track_ctx *ctx) {
  int diff;

  /* this function can be called only for the head context */
  assert(v7->stack_track_ctx == ctx);

  diff = (int) ((char *) ctx->start - (char *) ctx->max);

  /* remove context from the linked list */
  v7->stack_track_ctx = ctx->next;

  return (int) diff;
}

#endif /* V7_ENABLE_STACK_TRACKING */
#endif /* V7_CYG_PROFILE_ON */
#ifdef V7_MODULE_LINES
#line 1 "v7/src/std_object.c"
#endif
/*
 * Copyright (c) 2014 Cesanta Software Limited
 * All rights reserved
 */

/* Amalgamated: #include "common/str_util.h" */
/* Amalgamated: #include "v7/src/internal.h" */
/* Amalgamated: #include "v7/src/std_object.h" */
/* Amalgamated: #include "v7/src/function.h" */
/* Amalgamated: #include "v7/src/core.h" */
/* Amalgamated: #include "v7/src/conversion.h" */
/* Amalgamated: #include "v7/src/array.h" */
/* Amalgamated: #include "v7/src/object.h" */
/* Amalgamated: #include "v7/src/exceptions.h" */
/* Amalgamated: #include "v7/src/primitive.h" */
/* Amalgamated: #include "v7/src/string.h" */
/* Amalgamated: #include "v7/src/regexp.h" */
/* Amalgamated: #include "v7/src/exec.h" */

#if V7_ENABLE__Object__getPrototypeOf
WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Obj_getPrototypeOf(struct v7 *v7, v7_val_t *res) {
  enum v7_err rcode = V7_OK;
  val_t arg = v7_arg(v7, 0);

  if (!v7_is_object(arg)) {
    rcode =
        v7_throwf(v7, TYPE_ERROR, "Object.getPrototypeOf called on non-object");
    goto clean;
  }
  *res = v7_get_proto(v7, arg);

clean:
  return rcode;
}
#endif

#if V7_ENABLE__Object__isPrototypeOf
WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Obj_isPrototypeOf(struct v7 *v7, v7_val_t *res) {
  enum v7_err rcode = V7_OK;
  val_t obj = v7_arg(v7, 0);
  val_t proto = v7_get_this(v7);

  *res = v7_mk_boolean(v7, is_prototype_of(v7, obj, proto));

  return rcode;
}
#endif

#if V7_ENABLE__Object__getOwnPropertyNames || V7_ENABLE__Object__keys
/*
 * Hack to ensure that the iteration order of the keys array is consistent
 * with the iteration order if properties in `for in`
 * This will be obsoleted when arrays will have a special object type.
 */
static void _Obj_append_reverse(struct v7 *v7, struct v7_property *p, val_t res,
                                int i, v7_prop_attr_t ignore_flags) {
  while (p && p->attributes & ignore_flags) p = p->next;
  if (p == NULL) return;
  if (p->next) _Obj_append_reverse(v7, p->next, res, i + 1, ignore_flags);

  v7_array_set(v7, res, i, p->name);
}

WARN_UNUSED_RESULT
static enum v7_err _Obj_ownKeys(struct v7 *v7, unsigned int ignore_flags,
                                val_t *res) {
  enum v7_err rcode = V7_OK;
  val_t obj = v7_arg(v7, 0);

  *res = v7_mk_dense_array(v7);

  if (!v7_is_object(obj)) {
    rcode = v7_throwf(v7, TYPE_ERROR, "Object.keys called on non-object");
    goto clean;
  }

  _Obj_append_reverse(v7, get_object_struct(obj)->properties, *res, 0,
                      ignore_flags);

clean:
  return rcode;
}
#endif

#if V7_ENABLE__Object__hasOwnProperty ||       \
    V7_ENABLE__Object__propertyIsEnumerable || \
    V7_ENABLE__Object__getOwnPropertyDescriptor
static enum v7_err _Obj_getOwnProperty(struct v7 *v7, val_t obj, val_t name,
                                       struct v7_property **res) {
  enum v7_err rcode = V7_OK;
  char name_buf[512];
  size_t name_len;

  rcode = to_string(v7, name, NULL, name_buf, sizeof(name_buf), &name_len);
  if (rcode != V7_OK) {
    goto clean;
  }

  *res = v7_get_own_property(v7, obj, name_buf, name_len);

clean:
  return rcode;
}
#endif

#if V7_ENABLE__Object__keys
WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Obj_keys(struct v7 *v7, v7_val_t *res) {
  return _Obj_ownKeys(v7, _V7_PROPERTY_HIDDEN | V7_PROPERTY_NON_ENUMERABLE,
                      res);
}
#endif

#if V7_ENABLE__Object__getOwnPropertyNames
WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Obj_getOwnPropertyNames(struct v7 *v7, v7_val_t *res) {
  return _Obj_ownKeys(v7, _V7_PROPERTY_HIDDEN, res);
}
#endif

#if V7_ENABLE__Object__getOwnPropertyDescriptor
WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Obj_getOwnPropertyDescriptor(struct v7 *v7,
                                                    v7_val_t *res) {
  enum v7_err rcode = V7_OK;
  struct v7_property *prop;
  val_t obj = v7_arg(v7, 0);
  val_t name = v7_arg(v7, 1);
  val_t desc;

  rcode = _Obj_getOwnProperty(v7, obj, name, &prop);
  if (rcode != V7_OK) {
    goto clean;
  }

  if (prop == NULL) {
    goto clean;
  }

  desc = v7_mk_object(v7);
  v7_set(v7, desc, "value", 5, prop->value);
  v7_set(v7, desc, "writable", 8,
         v7_mk_boolean(v7, !(prop->attributes & V7_PROPERTY_NON_WRITABLE)));
  v7_set(v7, desc, "enumerable", 10,
         v7_mk_boolean(v7, !(prop->attributes & (_V7_PROPERTY_HIDDEN |
                                                 V7_PROPERTY_NON_ENUMERABLE))));
  v7_set(v7, desc, "configurable", 12,
         v7_mk_boolean(v7, !(prop->attributes & V7_PROPERTY_NON_CONFIGURABLE)));

  *res = desc;

clean:
  return rcode;
}
#endif

WARN_UNUSED_RESULT
static enum v7_err o_set_attr(struct v7 *v7, val_t desc, const char *name,
                              size_t n, v7_prop_attr_desc_t *pattrs_delta,
                              v7_prop_attr_desc_t flag_true,
                              v7_prop_attr_desc_t flag_false) {
  enum v7_err rcode = V7_OK;

  val_t v = V7_UNDEFINED;
  rcode = v7_get_throwing(v7, desc, name, n, &v);
  if (rcode != V7_OK) {
    goto clean;
  }

  if (v7_is_truthy(v7, v)) {
    *pattrs_delta |= flag_true;
  } else {
    *pattrs_delta |= flag_false;
  }

clean:
  return rcode;
}

WARN_UNUSED_RESULT
static enum v7_err _Obj_defineProperty(struct v7 *v7, val_t obj,
                                       const char *name, int name_len,
                                       val_t desc, val_t *res) {
  enum v7_err rcode = V7_OK;
  val_t val = V7_UNDEFINED;
  v7_prop_attr_desc_t attrs_desc = 0;

  /*
   * get provided value, or set `V7_DESC_PRESERVE_VALUE` flag if no value is
   * provided at all
   */
  {
    struct v7_property *prop = v7_get_property(v7, desc, "value", 5);
    if (prop == NULL) {
      /* no value is provided */
      attrs_desc |= V7_DESC_PRESERVE_VALUE;
    } else {
      /* value is provided: use it */
      rcode = v7_property_value(v7, desc, prop, &val);
      if (rcode != V7_OK) {
        goto clean;
      }
    }
  }

  /* Examine given properties, and set appropriate flags for `def_property` */

  rcode = o_set_attr(v7, desc, "enumerable", 10, &attrs_desc,
                     V7_DESC_ENUMERABLE(1), V7_DESC_ENUMERABLE(0));
  if (rcode != V7_OK) {
    goto clean;
  }

  rcode = o_set_attr(v7, desc, "writable", 8, &attrs_desc, V7_DESC_WRITABLE(1),
                     V7_DESC_WRITABLE(0));
  if (rcode != V7_OK) {
    goto clean;
  }

  rcode = o_set_attr(v7, desc, "configurable", 12, &attrs_desc,
                     V7_DESC_CONFIGURABLE(1), V7_DESC_CONFIGURABLE(0));
  if (rcode != V7_OK) {
    goto clean;
  }

  /* TODO(dfrank) : add getter/setter support */

  /* Finally, do the job on defining the property */
  rcode = def_property(v7, obj, name, name_len, attrs_desc, val,
                       0 /*not assign*/, NULL);
  if (rcode != V7_OK) {
    goto clean;
  }

  *res = obj;
  goto clean;

clean:
  return rcode;
}

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Obj_defineProperty(struct v7 *v7, v7_val_t *res) {
  enum v7_err rcode = V7_OK;
  val_t obj = v7_arg(v7, 0);
  val_t name = v7_arg(v7, 1);
  val_t desc = v7_arg(v7, 2);
  char name_buf[512];
  size_t name_len;

  if (!v7_is_object(obj)) {
    rcode = v7_throwf(v7, TYPE_ERROR, "object expected");
    goto clean;
  }

  rcode = to_string(v7, name, NULL, name_buf, sizeof(name_buf), &name_len);
  if (rcode != V7_OK) {
    goto clean;
  }

  rcode = _Obj_defineProperty(v7, obj, name_buf, name_len, desc, res);
  goto clean;

clean:
  return rcode;
}

#if V7_ENABLE__Object__create || V7_ENABLE__Object__defineProperties
WARN_UNUSED_RESULT
static enum v7_err o_define_props(struct v7 *v7, val_t obj, val_t descs,
                                  val_t *res) {
  enum v7_err rcode = V7_OK;
  struct v7_property *p;

  if (!v7_is_object(descs)) {
    rcode = v7_throwf(v7, TYPE_ERROR, "object expected");
    goto clean;
  }

  for (p = get_object_struct(descs)->properties; p; p = p->next) {
    size_t n;
    const char *s = v7_get_string(v7, &p->name, &n);
    if (p->attributes & (_V7_PROPERTY_HIDDEN | V7_PROPERTY_NON_ENUMERABLE)) {
      continue;
    }
    rcode = _Obj_defineProperty(v7, obj, s, n, p->value, res);
    if (rcode != V7_OK) {
      goto clean;
    }
  }

clean:
  return rcode;
}
#endif

#if V7_ENABLE__Object__defineProperties
WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Obj_defineProperties(struct v7 *v7, v7_val_t *res) {
  enum v7_err rcode = V7_OK;
  val_t descs = V7_UNDEFINED;

  *res = v7_arg(v7, 0);
  descs = v7_arg(v7, 1);
  rcode = o_define_props(v7, *res, descs, res);
  if (rcode != V7_OK) {
    goto clean;
  }

clean:
  return rcode;
}
#endif

#if V7_ENABLE__Object__create
WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Obj_create(struct v7 *v7, v7_val_t *res) {
  enum v7_err rcode = V7_OK;
  val_t proto = v7_arg(v7, 0);
  val_t descs = v7_arg(v7, 1);
  if (!v7_is_null(proto) && !v7_is_object(proto)) {
    rcode = v7_throwf(v7, TYPE_ERROR,
                      "Object prototype may only be an Object or null");
    goto clean;
  }
  *res = mk_object(v7, proto);
  if (v7_is_object(descs)) {
    rcode = o_define_props(v7, *res, descs, res);
    if (rcode != V7_OK) {
      goto clean;
    }
  }

clean:
  return rcode;
}
#endif

#if V7_ENABLE__Object__propertyIsEnumerable
WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Obj_propertyIsEnumerable(struct v7 *v7, v7_val_t *res) {
  enum v7_err rcode = V7_OK;
  val_t this_obj = v7_get_this(v7);
  struct v7_property *prop;
  val_t name = v7_arg(v7, 0);

  rcode = _Obj_getOwnProperty(v7, this_obj, name, &prop);
  if (rcode != V7_OK) {
    goto clean;
  }

  if (prop == NULL) {
    *res = v7_mk_boolean(v7, 0);
  } else {
    *res =
        v7_mk_boolean(v7, !(prop->attributes & (_V7_PROPERTY_HIDDEN |
                                                V7_PROPERTY_NON_ENUMERABLE)));
  }

  goto clean;

clean:
  return rcode;
}
#endif

#if V7_ENABLE__Object__hasOwnProperty
WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Obj_hasOwnProperty(struct v7 *v7, v7_val_t *res) {
  enum v7_err rcode = V7_OK;
  val_t this_obj = v7_get_this(v7);
  val_t name = v7_arg(v7, 0);
  struct v7_property *ptmp = NULL;

  rcode = _Obj_getOwnProperty(v7, this_obj, name, &ptmp);
  if (rcode != V7_OK) {
    goto clean;
  }

  *res = v7_mk_boolean(v7, ptmp != NULL);
  goto clean;

clean:
  return rcode;
}
#endif

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Obj_valueOf(struct v7 *v7, v7_val_t *res) {
  enum v7_err rcode = V7_OK;
  val_t this_obj = v7_get_this(v7);
  struct v7_property *p;

  *res = this_obj;

  if (v7_is_regexp(v7, this_obj)) {
    /* res is `this_obj` */
    goto clean;
  }

  p = v7_get_own_property2(v7, this_obj, "", 0, _V7_PROPERTY_HIDDEN);
  if (p != NULL) {
    *res = p->value;
    goto clean;
  }

clean:
  return rcode;
}

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Obj_toString(struct v7 *v7, v7_val_t *res) {
  enum v7_err rcode = V7_OK;
  val_t ctor, name, this_obj = v7_get_this(v7);
  char buf[20];
  const char *str = "Object";
  size_t name_len = ~0;

  if (v7_is_undefined(this_obj)) {
    str = "Undefined";
  } else if (v7_is_null(this_obj)) {
    str = "Null";
  } else if (v7_is_number(this_obj)) {
    str = "Number";
  } else if (v7_is_boolean(this_obj)) {
    str = "Boolean";
  } else if (v7_is_string(this_obj)) {
    str = "String";
  } else if (v7_is_callable(v7, this_obj)) {
    str = "Function";
  } else {
    rcode = v7_get_throwing(v7, this_obj, "constructor", ~0, &ctor);
    if (rcode != V7_OK) {
      goto clean;
    }

    if (!v7_is_undefined(ctor)) {
      rcode = v7_get_throwing(v7, ctor, "name", ~0, &name);
      if (rcode != V7_OK) {
        goto clean;
      }

      if (!v7_is_undefined(name)) {
        size_t tmp_len;
        const char *tmp_str;
        tmp_str = v7_get_string(v7, &name, &tmp_len);
        /*
         * objects constructed with an anonymous constructor are represented as
         * Object, ch11/11.1/11.1.1/S11.1.1_A4.2.js
         */
        if (tmp_len > 0) {
          str = tmp_str;
          name_len = tmp_len;
        }
      }
    }
  }

  if (name_len == (size_t) ~0) {
    name_len = strlen(str);
  }

  c_snprintf(buf, sizeof(buf), "[object %.*s]", (int) name_len, str);
  *res = v7_mk_string(v7, buf, strlen(buf), 1);

clean:
  return rcode;
}

#if V7_ENABLE__Object__preventExtensions
WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Obj_preventExtensions(struct v7 *v7, v7_val_t *res) {
  enum v7_err rcode = V7_OK;
  val_t arg = v7_arg(v7, 0);
  if (!v7_is_object(arg)) {
    rcode = v7_throwf(v7, TYPE_ERROR, "Object expected");
    goto clean;
  }
  get_object_struct(arg)->attributes |= V7_OBJ_NOT_EXTENSIBLE;
  *res = arg;

clean:
  return rcode;
}
#endif

#if V7_ENABLE__Object__isExtensible
WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Obj_isExtensible(struct v7 *v7, v7_val_t *res) {
  enum v7_err rcode = V7_OK;
  val_t arg = v7_arg(v7, 0);

  if (!v7_is_object(arg)) {
    rcode = v7_throwf(v7, TYPE_ERROR, "Object expected");
    goto clean;
  }

  *res = v7_mk_boolean(
      v7, !(get_object_struct(arg)->attributes & V7_OBJ_NOT_EXTENSIBLE));

clean:
  return rcode;
}
#endif

#if V7_ENABLE__Object__isFrozen || V7_ENABLE__Object__isSealed
static enum v7_err is_rigid(struct v7 *v7, v7_val_t *res, int is_frozen) {
  enum v7_err rcode = V7_OK;
  int ok = 0;
  val_t arg = v7_arg(v7, 0);

  if (!v7_is_object(arg)) {
    rcode = v7_throwf(v7, TYPE_ERROR, "Object expected");
    goto clean;
  }

  *res = v7_mk_boolean(v7, 0);

  if (get_object_struct(arg)->attributes & V7_OBJ_NOT_EXTENSIBLE) {
    v7_prop_attr_t attrs = 0;
    struct prop_iter_ctx ctx;
    memset(&ctx, 0, sizeof(ctx));
    V7_TRY2(init_prop_iter_ctx(v7, arg, 1, &ctx), clean_iter);
    while (1) {
      V7_TRY2(next_prop(v7, &ctx, NULL, NULL, &attrs, &ok), clean_iter);
      if (!ok) {
        break;
      }
      if (!(attrs & V7_PROPERTY_NON_CONFIGURABLE)) {
        goto clean_iter;
      }
      if (is_frozen) {
        if (!(attrs & V7_PROPERTY_SETTER) &&
            !(attrs & V7_PROPERTY_NON_WRITABLE)) {
          goto clean_iter;
        }
      }
    }

    *res = v7_mk_boolean(v7, 1);

  clean_iter:
    v7_destruct_prop_iter_ctx(v7, &ctx);
    goto clean;
  }

clean:
  return rcode;
}
#endif

#if V7_ENABLE__Object__isSealed
WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Obj_isSealed(struct v7 *v7, v7_val_t *res) {
  return is_rigid(v7, res, 0 /* is_frozen */);
}
#endif

#if V7_ENABLE__Object__isFrozen
WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Obj_isFrozen(struct v7 *v7, v7_val_t *res) {
  return is_rigid(v7, res, 1 /* is_frozen */);
}
#endif

static const char js_function_Object[] =
    "function Object(v) {"
    "if (typeof v === 'boolean') return new Boolean(v);"
    "if (typeof v === 'number') return new Number(v);"
    "if (typeof v === 'string') return new String(v);"
    "if (typeof v === 'date') return new Date(v);"
    "}";

V7_PRIVATE void init_object(struct v7 *v7) {
  enum v7_err rcode = V7_OK;
  val_t object, v;
  /* TODO(mkm): initialize global object without requiring a parser */
  rcode = v7_exec(v7, js_function_Object, &v);
  assert(rcode == V7_OK);
#if defined(NDEBUG)
  (void) rcode;
#endif

  object = v7_get(v7, v7->vals.global_object, "Object", 6);
  v7_set(v7, object, "prototype", 9, v7->vals.object_prototype);
  v7_def(v7, v7->vals.object_prototype, "constructor", 11,
         V7_DESC_ENUMERABLE(0), object);

  set_method(v7, v7->vals.object_prototype, "toString", Obj_toString, 0);
#if V7_ENABLE__Object__getPrototypeOf
  set_cfunc_prop(v7, object, "getPrototypeOf", Obj_getPrototypeOf);
#endif
#if V7_ENABLE__Object__getOwnPropertyDescriptor
  set_cfunc_prop(v7, object, "getOwnPropertyDescriptor",
                 Obj_getOwnPropertyDescriptor);
#endif

  /* defineProperty is currently required to perform stdlib initialization */
  set_method(v7, object, "defineProperty", Obj_defineProperty, 3);

#if V7_ENABLE__Object__defineProperties
  set_cfunc_prop(v7, object, "defineProperties", Obj_defineProperties);
#endif
#if V7_ENABLE__Object__create
  set_cfunc_prop(v7, object, "create", Obj_create);
#endif
#if V7_ENABLE__Object__keys
  set_cfunc_prop(v7, object, "keys", Obj_keys);
#endif
#if V7_ENABLE__Object__getOwnPropertyNames
  set_cfunc_prop(v7, object, "getOwnPropertyNames", Obj_getOwnPropertyNames);
#endif
#if V7_ENABLE__Object__preventExtensions
  set_method(v7, object, "preventExtensions", Obj_preventExtensions, 1);
#endif
#if V7_ENABLE__Object__isExtensible
  set_method(v7, object, "isExtensible", Obj_isExtensible, 1);
#endif
#if V7_ENABLE__Object__isSealed
  set_method(v7, object, "isSealed", Obj_isSealed, 1);
#endif
#if V7_ENABLE__Object__isFrozen
  set_method(v7, object, "isFrozen", Obj_isFrozen, 1);
#endif

#if V7_ENABLE__Object__propertyIsEnumerable
  set_cfunc_prop(v7, v7->vals.object_prototype, "propertyIsEnumerable",
                 Obj_propertyIsEnumerable);
#endif
#if V7_ENABLE__Object__hasOwnProperty
  set_cfunc_prop(v7, v7->vals.object_prototype, "hasOwnProperty",
                 Obj_hasOwnProperty);
#endif
#if V7_ENABLE__Object__isPrototypeOf
  set_cfunc_prop(v7, v7->vals.object_prototype, "isPrototypeOf",
                 Obj_isPrototypeOf);
#endif
  set_cfunc_prop(v7, v7->vals.object_prototype, "valueOf", Obj_valueOf);
}
#ifdef V7_MODULE_LINES
#line 1 "v7/src/std_error.c"
#endif
/*
 * Copyright (c) 2014 Cesanta Software Limited
 * All rights reserved
 */

/* Amalgamated: #include "v7/src/internal.h" */
/* Amalgamated: #include "v7/src/core.h" */
/* Amalgamated: #include "v7/src/function.h" */
/* Amalgamated: #include "v7/src/string.h" */
/* Amalgamated: #include "v7/src/std_error.h" */
/* Amalgamated: #include "v7/src/object.h" */
/* Amalgamated: #include "v7/src/bcode.h" */
/* Amalgamated: #include "v7/src/primitive.h" */
/* Amalgamated: #include "v7/src/util.h" */

/*
 * TODO(dfrank): make the top of v7->call_frame to represent the current
 * frame, and thus get rid of the `CUR_LINENO()`
 */
#ifndef V7_DISABLE_LINE_NUMBERS
#define CALLFRAME_LINENO(call_frame) ((call_frame)->line_no)
#define CUR_LINENO() (v7->line_no)
#else
#define CALLFRAME_LINENO(call_frame) 0
#define CUR_LINENO() 0
#endif

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Error_ctor(struct v7 *v7, v7_val_t *res);

#if !defined(V7_DISABLE_FILENAMES) && !defined(V7_DISABLE_LINE_NUMBERS)
static int printf_stack_line(char *p, size_t len, struct bcode *bcode,
                             int line_no, const char *leading) {
  int ret;
  const char *fn = bcode_get_filename(bcode);
  if (fn == NULL) {
    fn = "<no filename>";
  }

  if (bcode->func_name_present) {
    /* this is a function's bcode: let's show the function's name as well */
    char *funcname;

    /*
     * read first name from the bcode ops, which is the function name,
     * since `func_name_present` is set
     */
    bcode_next_name(bcode->ops.p, &funcname, NULL);

    /* Check if it's an anonymous function */
    if (funcname[0] == '\0') {
      funcname = (char *) "<anonymous>";
    }
    ret =
        snprintf(p, len, "%s    at %s (%s:%d)", leading, funcname, fn, line_no);
  } else {
    /* it's a file's bcode: show only filename and line number */
    ret = snprintf(p, len, "%s    at %s:%d", leading, fn, line_no);
  }
  return ret;
}

static int printf_stack_line_cfunc(char *p, size_t len, v7_cfunction_t *cfunc,
                                   const char *leading) {
  int ret = 0;

#if !defined(V7_FILENAMES_SUPPRESS_CFUNC_ADDR)
  int name_len =
      snprintf(NULL, 0, "cfunc_%p", (void *) cfunc) + 1 /*null-term*/;
  char *buf = (char *) malloc(name_len);

  snprintf(buf, name_len, "cfunc_%p", (void *) cfunc);
#else
  /*
   * We need this mode only for ecma test reporting, so that the
   * report is not different from one run to another
   */
  char *buf = (char *) "cfunc";
  (void) cfunc;
#endif

  ret = snprintf(p, len, "%s    at %s", leading, buf);

#if !defined(V7_FILENAMES_SUPPRESS_CFUNC_ADDR)
  free(buf);
#endif

  return ret;
}

static int print_stack_trace(char *p, size_t len,
                             struct v7_call_frame_base *call_frame) {
  char *p_cur = p;
  int total_len = 0;

  assert(call_frame->type_mask == V7_CALL_FRAME_MASK_CFUNC &&
         ((struct v7_call_frame_cfunc *) call_frame)->cfunc == Error_ctor);
  call_frame = call_frame->prev;

  while (call_frame != NULL) {
    int cur_len = 0;
    const char *leading = (total_len ? "\n" : "");
    size_t line_len = len - (p_cur - p);

    if (call_frame->type_mask & V7_CALL_FRAME_MASK_BCODE) {
      struct bcode *bcode = ((struct v7_call_frame_bcode *) call_frame)->bcode;
      if (bcode != NULL) {
        cur_len = printf_stack_line(p_cur, line_len, bcode,
                                    CALLFRAME_LINENO(call_frame), leading);
      }
    } else if (call_frame->type_mask & V7_CALL_FRAME_MASK_CFUNC) {
      cur_len = printf_stack_line_cfunc(
          p_cur, line_len, ((struct v7_call_frame_cfunc *) call_frame)->cfunc,
          leading);
    }

    total_len += cur_len;
    if (p_cur != NULL) {
      p_cur += cur_len;
    }

    call_frame = call_frame->prev;

#if !(V7_ENABLE__StackTrace)
    break;
#endif
  }

  return total_len;
}
#endif

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Error_ctor(struct v7 *v7, v7_val_t *res) {
  enum v7_err rcode = V7_OK;
  val_t this_obj = v7_get_this(v7);
  val_t arg0 = v7_arg(v7, 0);

  if (v7_is_object(this_obj) && this_obj != v7->vals.global_object) {
    *res = this_obj;
  } else {
    *res = mk_object(v7, v7->vals.error_prototype);
  }
  /* TODO(mkm): set non enumerable but provide toString method */
  v7_set(v7, *res, "message", 7, arg0);

#if !defined(V7_DISABLE_FILENAMES) && !defined(V7_DISABLE_LINE_NUMBERS)
  /* Save the stack trace */
  {
    size_t len = 0;
    val_t st_v = V7_UNDEFINED;

    v7_own(v7, &st_v);

    len = print_stack_trace(NULL, 0, v7->call_stack);

    if (len > 0) {
      /* Now, create a placeholder for string */
      st_v = v7_mk_string(v7, NULL, len, 1);
      len += 1 /*null-term*/;

      /* And fill it with actual data */
      print_stack_trace((char *) v7_get_string(v7, &st_v, NULL), len,
                        v7->call_stack);

      v7_set(v7, *res, "stack", ~0, st_v);
    }

    v7_disown(v7, &st_v);
  }
#endif

  return rcode;
}

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Error_toString(struct v7 *v7, v7_val_t *res) {
  enum v7_err rcode = V7_OK;
  val_t this_obj = v7_get_this(v7);
  val_t prefix, msg = v7_get(v7, this_obj, "message", ~0);

  if (!v7_is_string(msg)) {
    *res = v7_mk_string(v7, "Error", ~0, 1);
    goto clean;
  }

  prefix = v7_mk_string(v7, "Error: ", ~0, 1);
  *res = s_concat(v7, prefix, msg);
  goto clean;

clean:
  return rcode;
}

static const char *const error_names[] = {TYPE_ERROR,      SYNTAX_ERROR,
                                          REFERENCE_ERROR, INTERNAL_ERROR,
                                          RANGE_ERROR,     EVAL_ERROR};

V7_STATIC_ASSERT(ARRAY_SIZE(error_names) == ERROR_CTOR_MAX,
                 error_name_count_mismatch);

V7_PRIVATE void init_error(struct v7 *v7) {
  val_t error;
  size_t i;

  error =
      mk_cfunction_obj_with_proto(v7, Error_ctor, 1, v7->vals.error_prototype);
  v7_def(v7, v7->vals.global_object, "Error", 5, V7_DESC_ENUMERABLE(0), error);
  set_method(v7, v7->vals.error_prototype, "toString", Error_toString, 0);

  for (i = 0; i < ARRAY_SIZE(error_names); i++) {
    error = mk_cfunction_obj_with_proto(
        v7, Error_ctor, 1, mk_object(v7, v7->vals.error_prototype));
    v7_def(v7, v7->vals.global_object, error_names[i], strlen(error_names[i]),
           V7_DESC_ENUMERABLE(0), error);
    v7->vals.error_objects[i] = error;
  }
}
#ifdef V7_MODULE_LINES
#line 1 "v7/src/std_number.c"
#endif
/*
 * Copyright (c) 2014 Cesanta Software Limited
 * All rights reserved
 */

/* Amalgamated: #include "v7/src/internal.h" */
/* Amalgamated: #include "v7/src/std_object.h" */
/* Amalgamated: #include "v7/src/conversion.h" */
/* Amalgamated: #include "v7/src/core.h" */
/* Amalgamated: #include "v7/src/function.h" */
/* Amalgamated: #include "v7/src/object.h" */
/* Amalgamated: #include "v7/src/primitive.h" */
/* Amalgamated: #include "v7/src/string.h" */
/* Amalgamated: #include "v7/src/exceptions.h" */

#if defined(__cplusplus)
extern "C" {
#endif /* __cplusplus */

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Number_ctor(struct v7 *v7, v7_val_t *res) {
  enum v7_err rcode = V7_OK;
  val_t this_obj = v7_get_this(v7);
  val_t arg0 = v7_argc(v7) == 0 ? v7_mk_number(v7, 0.0) : v7_arg(v7, 0);

  if (v7_is_number(arg0)) {
    *res = arg0;
  } else {
    rcode = to_number_v(v7, arg0, res);
    if (rcode != V7_OK) {
      goto clean;
    }
  }

  if (v7_is_generic_object(this_obj) && this_obj != v7->vals.global_object) {
    obj_prototype_set(v7, get_object_struct(this_obj),
                      get_object_struct(v7->vals.number_prototype));
    v7_def(v7, this_obj, "", 0, _V7_DESC_HIDDEN(1), *res);

    /*
     * implicitly returning `this`: `call_cfunction()` in bcode.c will do
     * that for us
     */
  }

clean:
  return rcode;
}

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err n_to_str(struct v7 *v7, const char *format, val_t *res) {
  enum v7_err rcode = V7_OK;
  val_t this_obj = v7_get_this(v7);
  val_t arg0 = v7_arg(v7, 0);
  int len, digits = 0;
  char fmt[10], buf[100];

  rcode = to_number_v(v7, arg0, &arg0);
  if (rcode != V7_OK) {
    goto clean;
  }

  if (v7_get_double(v7, arg0) > 0) {
    digits = (int) v7_get_double(v7, arg0);
  }

  /*
   * NOTE: we don't own `arg0` and `this_obj`, since this function is called
   * from cfunctions only, and GC is inhibited during these calls
   */

  rcode = obj_value_of(v7, this_obj, &this_obj);
  if (rcode != V7_OK) {
    goto clean;
  }

  snprintf(fmt, sizeof(fmt), format, digits);
  len = snprintf(buf, sizeof(buf), fmt, v7_get_double(v7, this_obj));

  *res = v7_mk_string(v7, buf, len, 1);

clean:
  return rcode;
}

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Number_toFixed(struct v7 *v7, v7_val_t *res) {
  return n_to_str(v7, "%%.%dlf", res);
}

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Number_toExp(struct v7 *v7, v7_val_t *res) {
  return n_to_str(v7, "%%.%de", res);
}

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Number_toPrecision(struct v7 *v7, v7_val_t *res) {
  return Number_toExp(v7, res);
}

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Number_valueOf(struct v7 *v7, v7_val_t *res) {
  enum v7_err rcode = V7_OK;
  val_t this_obj = v7_get_this(v7);

  if (!v7_is_number(this_obj) &&
      (v7_is_object(this_obj) &&
       v7_get_proto(v7, this_obj) != v7->vals.number_prototype)) {
    rcode =
        v7_throwf(v7, TYPE_ERROR, "Number.valueOf called on non-number object");
    goto clean;
  }

  rcode = Obj_valueOf(v7, res);
  if (rcode != V7_OK) {
    goto clean;
  }

clean:
  return rcode;
}

/*
 * Converts a 64 bit signed integer into a string of a given base.
 * Requires space for 65 bytes (64 bit + null terminator) in the result buffer
 */
static char *cs_itoa(int64_t value, char *result, int base) {
  char *ptr = result, *ptr1 = result, tmp_char;
  int64_t tmp_value;
  int64_t sign = value < 0 ? -1 : 1;
  const char *base36 = "0123456789abcdefghijklmnopqrstuvwxyz";

  if (base < 2 || base > 36) {
    *result = '\0';
    return result;
  }

  /* let's think positive */
  value = value * sign;
  do {
    tmp_value = value;
    value /= base;
    *ptr++ = base36[tmp_value - value * base];
  } while (value);

  /* sign */
  if (sign < 0) *ptr++ = '-';
  *ptr-- = '\0';
  while (ptr1 < ptr) {
    tmp_char = *ptr;
    *ptr-- = *ptr1;
    *ptr1++ = tmp_char;
  }
  return result;
}

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Number_toString(struct v7 *v7, v7_val_t *res) {
  enum v7_err rcode = V7_OK;
  val_t this_obj = v7_get_this(v7);
  val_t radixv = v7_arg(v7, 0);
  char buf[65];
  double d, radix;

  if (this_obj == v7->vals.number_prototype) {
    *res = v7_mk_string(v7, "0", 1, 1);
    goto clean;
  }

  /* Make sure this function was called on Number instance */
  if (!v7_is_number(this_obj) &&
      !(v7_is_generic_object(this_obj) &&
        is_prototype_of(v7, this_obj, v7->vals.number_prototype))) {
    rcode = v7_throwf(v7, TYPE_ERROR,
                      "Number.toString called on non-number object");
    goto clean;
  }

  /* Get number primitive */
  rcode = to_number_v(v7, this_obj, &this_obj);
  if (rcode != V7_OK) {
    goto clean;
  }

  /* Get radix if provided, or 10 otherwise */
  if (!v7_is_undefined(radixv)) {
    rcode = to_number_v(v7, radixv, &radixv);
    if (rcode != V7_OK) {
      goto clean;
    }
    radix = v7_get_double(v7, radixv);
  } else {
    radix = 10.0;
  }

  d = v7_get_double(v7, this_obj);
  if (!isnan(d) && (int64_t) d == d && radix >= 2) {
    cs_itoa(d, buf, radix);
    *res = v7_mk_string(v7, buf, strlen(buf), 1);
  } else {
    rcode = to_string(v7, this_obj, res, NULL, 0, NULL);
    if (rcode != V7_OK) {
      goto clean;
    }
  }

clean:
  return rcode;
}

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err n_isNaN(struct v7 *v7, v7_val_t *res) {
  val_t arg0 = v7_arg(v7, 0);
  *res = v7_mk_boolean(v7, !v7_is_number(arg0) || arg0 == V7_TAG_NAN);
  return V7_OK;
}

V7_PRIVATE void init_number(struct v7 *v7) {
  v7_prop_attr_desc_t attrs_desc =
      (V7_DESC_WRITABLE(0) | V7_DESC_ENUMERABLE(0) | V7_DESC_CONFIGURABLE(0));
  val_t num = mk_cfunction_obj_with_proto(v7, Number_ctor, 1,
                                          v7->vals.number_prototype);

  v7_def(v7, v7->vals.global_object, "Number", 6, V7_DESC_ENUMERABLE(0), num);

  set_cfunc_prop(v7, v7->vals.number_prototype, "toFixed", Number_toFixed);
  set_cfunc_prop(v7, v7->vals.number_prototype, "toPrecision",
                 Number_toPrecision);
  set_cfunc_prop(v7, v7->vals.number_prototype, "toExponential", Number_toExp);
  set_cfunc_prop(v7, v7->vals.number_prototype, "valueOf", Number_valueOf);
  set_cfunc_prop(v7, v7->vals.number_prototype, "toString", Number_toString);

  v7_def(v7, num, "MAX_VALUE", 9, attrs_desc,
         v7_mk_number(v7, 1.7976931348623157e+308));
  v7_def(v7, num, "MIN_VALUE", 9, attrs_desc, v7_mk_number(v7, 5e-324));
#if V7_ENABLE__NUMBER__NEGATIVE_INFINITY
  v7_def(v7, num, "NEGATIVE_INFINITY", 17, attrs_desc,
         v7_mk_number(v7, -INFINITY));
#endif
#if V7_ENABLE__NUMBER__POSITIVE_INFINITY
  v7_def(v7, num, "POSITIVE_INFINITY", 17, attrs_desc,
         v7_mk_number(v7, INFINITY));
#endif
  v7_def(v7, num, "NaN", 3, attrs_desc, V7_TAG_NAN);

  v7_def(v7, v7->vals.global_object, "NaN", 3, attrs_desc, V7_TAG_NAN);
  v7_def(v7, v7->vals.global_object, "isNaN", 5, V7_DESC_ENUMERABLE(0),
         v7_mk_cfunction(n_isNaN));
}

#if defined(__cplusplus)
}
#endif /* __cplusplus */
#ifdef V7_MODULE_LINES
#line 1 "v7/src/std_json.c"
#endif
/*
 * Copyright (c) 2014 Cesanta Software Limited
 * All rights reserved
 */

/* Amalgamated: #include "v7/src/internal.h" */
/* Amalgamated: #include "v7/src/stdlib.h" */
/* Amalgamated: #include "v7/src/core.h" */
/* Amalgamated: #include "v7/src/object.h" */
/* Amalgamated: #include "v7/src/conversion.h" */
/* Amalgamated: #include "v7/src/string.h" */
/* Amalgamated: #include "v7/src/primitive.h" */

#if defined(__cplusplus)
extern "C" {
#endif /* __cplusplus */

#if defined(V7_ALT_JSON_PARSE)
extern enum v7_err v7_alt_json_parse(struct v7 *v7, v7_val_t json_string,
                                     v7_val_t *res);
#endif

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Json_stringify(struct v7 *v7, v7_val_t *res) {
  val_t arg0 = v7_arg(v7, 0);
  char buf[100], *p = v7_to_json(v7, arg0, buf, sizeof(buf));
  *res = v7_mk_string(v7, p, strlen(p), 1);

  if (p != buf) free(p);
  return V7_OK;
}

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Json_parse(struct v7 *v7, v7_val_t *res) {
  v7_val_t arg = v7_arg(v7, 0);
  enum v7_err rcode = V7_OK;
#if defined(V7_ALT_JSON_PARSE)
  rcode = v7_alt_json_parse(v7, arg, res);
#else
  rcode = std_eval(v7, arg, V7_UNDEFINED, 1, res);
#endif
  return rcode;
}

V7_PRIVATE void init_json(struct v7 *v7) {
  val_t o = v7_mk_object(v7);
  set_method(v7, o, "stringify", Json_stringify, 1);
  set_method(v7, o, "parse", Json_parse, 1);
  v7_def(v7, v7->vals.global_object, "JSON", 4, V7_DESC_ENUMERABLE(0), o);
}

#if defined(__cplusplus)
}
#endif /* __cplusplus */
#ifdef V7_MODULE_LINES
#line 1 "v7/src/std_array.c"
#endif
/*
 * Copyright (c) 2014 Cesanta Software Limited
 * All rights reserved
 */

/* Amalgamated: #include "common/str_util.h" */
/* Amalgamated: #include "v7/src/internal.h" */
/* Amalgamated: #include "v7/src/gc.h" */
/* Amalgamated: #include "v7/src/eval.h" */
/* Amalgamated: #include "v7/src/std_string.h" */
/* Amalgamated: #include "v7/src/conversion.h" */
/* Amalgamated: #include "v7/src/core.h" */
/* Amalgamated: #include "v7/src/function.h" */
/* Amalgamated: #include "v7/src/array.h" */
/* Amalgamated: #include "v7/src/object.h" */
/* Amalgamated: #include "v7/src/exceptions.h" */

#if defined(__cplusplus)
extern "C" {
#endif /* __cplusplus */

struct a_sort_data {
  val_t sort_func;
};

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Array_ctor(struct v7 *v7, v7_val_t *res) {
  enum v7_err rcode = V7_OK;
  unsigned long i, len;

  (void) v7;
  *res = v7_mk_array(v7);
  /*
   * The interpreter passes dense array to C functions.
   * However dense array implementation is not yet complete
   * so we don't want to propagate them at each call to Array()
   */
  len = v7_argc(v7);
  for (i = 0; i < len; i++) {
    rcode = v7_array_set_throwing(v7, *res, i, v7_arg(v7, i), NULL);
    if (rcode != V7_OK) {
      goto clean;
    }
  }

clean:
  return rcode;
}

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Array_push(struct v7 *v7, v7_val_t *res) {
  enum v7_err rcode = V7_OK;
  int i, len = v7_argc(v7);

  *res = V7_UNDEFINED;

  for (i = 0; i < len; i++) {
    *res = v7_arg(v7, i);
    rcode = v7_array_push_throwing(v7, v7_get_this(v7), *res, NULL);
    if (rcode != V7_OK) {
      goto clean;
    }
  }

/*
 * TODO(dfrank) : we need to implement `length` as a real property, and here
 * we need to set new length and return it (even if the object is not an
 * array)
 */

clean:
  return rcode;
}

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Array_get_length(struct v7 *v7, v7_val_t *res) {
  enum v7_err rcode = V7_OK;
  val_t this_obj = v7_get_this(v7);
  long len = 0;

  if (is_prototype_of(v7, this_obj, v7->vals.array_prototype)) {
    len = v7_array_length(v7, this_obj);
  }
  *res = v7_mk_number(v7, len);

  return rcode;
}

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Array_set_length(struct v7 *v7, v7_val_t *res) {
  enum v7_err rcode = V7_OK;
  val_t arg0 = v7_arg(v7, 0);
  val_t this_obj = v7_get_this(v7);
  long new_len = 0;

  rcode = to_long(v7, v7_arg(v7, 0), -1, &new_len);
  if (rcode != V7_OK) {
    goto clean;
  }

  if (!v7_is_object(this_obj)) {
    rcode = v7_throwf(v7, TYPE_ERROR, "Array expected");
    goto clean;
  } else if (new_len < 0 ||
             (v7_is_number(arg0) && (isnan(v7_get_double(v7, arg0)) ||
                                     isinf(v7_get_double(v7, arg0))))) {
    rcode = v7_throwf(v7, RANGE_ERROR, "Invalid array length");
    goto clean;
  } else {
    struct v7_property **p, **next;
    long index, max_index = -1;

    /* Remove all items with an index higher than new_len */
    for (p = &get_object_struct(this_obj)->properties; *p != NULL; p = next) {
      size_t n;
      const char *s = v7_get_string(v7, &p[0]->name, &n);
      next = &p[0]->next;
      index = strtol(s, NULL, 10);
      if (index >= new_len) {
        v7_destroy_property(p);
        *p = *next;
        next = p;
      } else if (index > max_index) {
        max_index = index;
      }
    }

    /* If we have to expand, insert an item with appropriate index */
    if (new_len > 0 && max_index < new_len - 1) {
      char buf[40];
      c_snprintf(buf, sizeof(buf), "%ld", new_len - 1);
      v7_set(v7, this_obj, buf, strlen(buf), V7_UNDEFINED);
    }
  }

  *res = v7_mk_number(v7, new_len);

clean:
  return rcode;
}

WARN_UNUSED_RESULT
static enum v7_err a_cmp(struct v7 *v7, void *user_data, const void *pa,
                         const void *pb, int *res) {
  enum v7_err rcode = V7_OK;
  struct a_sort_data *sort_data = (struct a_sort_data *) user_data;
  val_t a = *(val_t *) pa, b = *(val_t *) pb, func = sort_data->sort_func;

  if (v7_is_callable(v7, func)) {
    int saved_inhibit_gc = v7->inhibit_gc;
    val_t vres = V7_UNDEFINED, args = v7_mk_dense_array(v7);
    v7_array_push(v7, args, a);
    v7_array_push(v7, args, b);
    v7->inhibit_gc = 0;
    rcode = b_apply(v7, func, V7_UNDEFINED, args, 0, &vres);
    if (rcode != V7_OK) {
      goto clean;
    }
    v7->inhibit_gc = saved_inhibit_gc;
    *res = (int) -v7_get_double(v7, vres);
    goto clean;
  } else {
    char sa[100], sb[100];

    rcode = to_string(v7, a, NULL, sa, sizeof(sa), NULL);
    if (rcode != V7_OK) {
      goto clean;
    }

    rcode = to_string(v7, b, NULL, sb, sizeof(sb), NULL);
    if (rcode != V7_OK) {
      goto clean;
    }

    sa[sizeof(sa) - 1] = sb[sizeof(sb) - 1] = '\0';
    *res = strcmp(sb, sa);
    goto clean;
  }

clean:
  return rcode;
}

WARN_UNUSED_RESULT
static enum v7_err a_partition(struct v7 *v7, val_t *a, int l, int r,
                               void *user_data, int *res) {
  enum v7_err rcode = V7_OK;
  val_t t, pivot = a[l];
  int i = l, j = r + 1;

  for (;;) {
    while (1) {
      ++i;

      if (i <= r) {
        int tmp = 0;
        rcode = a_cmp(v7, user_data, &a[i], &pivot, &tmp);
        if (rcode != V7_OK) {
          goto clean;
        }

        if (tmp > 0) {
          break;
        }
      } else {
        break;
      }
    }
    while (1) {
      int tmp = 0;
      --j;

      rcode = a_cmp(v7, user_data, &a[j], &pivot, &tmp);
      if (rcode != V7_OK) {
        goto clean;
      }

      if (tmp <= 0) {
        break;
      }
    }
    if (i >= j) break;
    t = a[i];
    a[i] = a[j];
    a[j] = t;
  }
  t = a[l];
  a[l] = a[j];
  a[j] = t;

  *res = j;
clean:
  return rcode;
}

WARN_UNUSED_RESULT
static enum v7_err a_qsort(struct v7 *v7, val_t *a, int l, int r,
                           void *user_data) {
  enum v7_err rcode = V7_OK;
  if (l < r) {
    int j = 0;
    rcode = a_partition(v7, a, l, r, user_data, &j);
    if (rcode != V7_OK) {
      goto clean;
    }

    rcode = a_qsort(v7, a, l, j - 1, user_data);
    if (rcode != V7_OK) {
      goto clean;
    }

    rcode = a_qsort(v7, a, j + 1, r, user_data);
    if (rcode != V7_OK) {
      goto clean;
    }
  }
clean:
  return rcode;
}

WARN_UNUSED_RESULT
static enum v7_err a_sort(struct v7 *v7,
                          enum v7_err (*sorting_func)(struct v7 *v7, void *,
                                                      const void *,
                                                      const void *, int *res),
                          v7_val_t *res) {
  enum v7_err rcode = V7_OK;
  int i = 0, len = 0;
  val_t *arr = NULL;
  val_t arg0 = v7_arg(v7, 0);

  *res = v7_get_this(v7);
  len = v7_array_length(v7, *res);

  if (!v7_is_object(*res)) {
    goto clean;
  }

  arr = (val_t *) malloc(len * sizeof(arr[0]));

  assert(*res != v7->vals.global_object);

  for (i = 0; i < len; i++) {
    arr[i] = v7_array_get(v7, *res, i);
  }

  /* TODO(dfrank): sorting_func isn't actually used! something is wrong here */
  if (sorting_func != NULL) {
    struct a_sort_data sort_data;
    sort_data.sort_func = arg0;
    rcode = a_qsort(v7, arr, 0, len - 1, &sort_data);
    if (rcode != V7_OK) {
      goto clean;
    }
  }

  for (i = 0; i < len; i++) {
    v7_array_set(v7, *res, i, arr[len - (i + 1)]);
  }

clean:
  if (arr != NULL) {
    free(arr);
  }
  return rcode;
}

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Array_sort(struct v7 *v7, v7_val_t *res) {
  return a_sort(v7, a_cmp, res);
}

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Array_reverse(struct v7 *v7, v7_val_t *res) {
  return a_sort(v7, NULL, res);
}

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Array_join(struct v7 *v7, v7_val_t *res) {
  enum v7_err rcode = V7_OK;
  val_t this_obj = v7_get_this(v7);
  val_t arg0 = v7_arg(v7, 0);
  size_t sep_size = 0;
  const char *sep = NULL;

  *res = V7_UNDEFINED;

  /* Get pointer to the separator string */
  if (!v7_is_string(arg0)) {
    /* If no separator is provided, use comma */
    arg0 = v7_mk_string(v7, ",", 1, 1);
  }
  sep = v7_get_string(v7, &arg0, &sep_size);

  /* Do the actual join */
  if (is_prototype_of(v7, this_obj, v7->vals.array_prototype)) {
    struct mbuf m;
    char buf[100], *p;
    long i, n, num_elems = v7_array_length(v7, this_obj);

    mbuf_init(&m, 0);

    for (i = 0; i < num_elems; i++) {
      /* Append separator */
      if (i > 0) {
        mbuf_append(&m, sep, sep_size);
      }

      /* Append next item from an array */
      p = buf;
      {
        size_t tmp;
        rcode = to_string(v7, v7_array_get(v7, this_obj, i), NULL, buf,
                          sizeof(buf), &tmp);
        if (rcode != V7_OK) {
          goto clean;
        }
        n = tmp;
      }
      if (n > (long) sizeof(buf)) {
        p = (char *) malloc(n + 1);
        rcode = to_string(v7, v7_array_get(v7, this_obj, i), NULL, p, n, NULL);
        if (rcode != V7_OK) {
          goto clean;
        }
      }
      mbuf_append(&m, p, n);
      if (p != buf) {
        free(p);
      }
    }

    /* mbuf contains concatenated string now. Copy it to the result. */
    *res = v7_mk_string(v7, m.buf, m.len, 1);
    mbuf_free(&m);
  }

clean:
  return rcode;
}

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Array_toString(struct v7 *v7, v7_val_t *res) {
  return Array_join(v7, res);
}

WARN_UNUSED_RESULT
static enum v7_err a_splice(struct v7 *v7, int mutate, v7_val_t *res) {
  enum v7_err rcode = V7_OK;
  val_t this_obj = v7_get_this(v7);
  long i, len = v7_array_length(v7, this_obj);
  long num_args = v7_argc(v7);
  long elems_to_insert = num_args > 2 ? num_args - 2 : 0;
  long arg0, arg1;

  if (!v7_is_object(this_obj)) {
    rcode = v7_throwf(v7, TYPE_ERROR,
                      "Array.splice or Array.slice called on non-object value");
    goto clean;
  }

  *res = v7_mk_dense_array(v7);

  rcode = to_long(v7, v7_arg(v7, 0), 0, &arg0);
  if (rcode != V7_OK) {
    goto clean;
  }

  rcode = to_long(v7, v7_arg(v7, 1), len, &arg1);
  if (rcode != V7_OK) {
    goto clean;
  }

  /* Bounds check */
  if (!mutate && len <= 0) {
    goto clean;
  }
  if (arg0 < 0) arg0 = len + arg0;
  if (arg0 < 0) arg0 = 0;
  if (arg0 > len) arg0 = len;
  if (mutate) {
    if (arg1 < 0) arg1 = 0;
    arg1 += arg0;
  } else if (arg1 < 0) {
    arg1 = len + arg1;
  }

  /* Create return value - slice */
  for (i = arg0; i < arg1 && i < len; i++) {
    rcode =
        v7_array_push_throwing(v7, *res, v7_array_get(v7, this_obj, i), NULL);
    if (rcode != V7_OK) {
      goto clean;
    }
  }

  if (mutate && get_object_struct(this_obj)->attributes & V7_OBJ_DENSE_ARRAY) {
    /*
     * dense arrays are spliced by memmoving leaving the trailing
     * space allocated for future appends.
     * TODO(mkm): figure out if trimming is better
     */
    struct v7_property *p =
        v7_get_own_property2(v7, this_obj, "", 0, _V7_PROPERTY_HIDDEN);
    struct mbuf *abuf;
    if (p == NULL) {
      goto clean;
    }
    abuf = (struct mbuf *) v7_get_ptr(v7, p->value);
    if (abuf == NULL) {
      goto clean;
    }

    memmove(abuf->buf + arg0 * sizeof(val_t), abuf->buf + arg1 * sizeof(val_t),
            (len - arg1) * sizeof(val_t));
    abuf->len -= (arg1 - arg0) * sizeof(val_t);
  } else if (mutate) {
    /* If splicing, modify this_obj array: remove spliced sub-array */
    struct v7_property **p, **next;
    long i;

    for (p = &get_object_struct(this_obj)->properties; *p != NULL; p = next) {
      size_t n;
      const char *s = v7_get_string(v7, &p[0]->name, &n);
      next = &p[0]->next;
      i = strtol(s, NULL, 10);
      if (i >= arg0 && i < arg1) {
        /* Remove items from spliced sub-array */
        v7_destroy_property(p);
        *p = *next;
        next = p;
      } else if (i >= arg1) {
        /* Modify indices of the elements past sub-array */
        char key[20];
        size_t n = c_snprintf(key, sizeof(key), "%ld",
                              i - (arg1 - arg0) + elems_to_insert);
        p[0]->name = v7_mk_string(v7, key, n, 1);
      }
    }

    /* Insert optional extra elements */
    for (i = 2; i < num_args; i++) {
      char key[20];
      size_t n = c_snprintf(key, sizeof(key), "%ld", arg0 + i - 2);
      rcode = set_property(v7, this_obj, key, n, v7_arg(v7, i), NULL);
      if (rcode != V7_OK) {
        goto clean;
      }
    }
  }

clean:
  return rcode;
}

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Array_slice(struct v7 *v7, v7_val_t *res) {
  return a_splice(v7, 0, res);
}

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Array_splice(struct v7 *v7, v7_val_t *res) {
  return a_splice(v7, 1, res);
}

static void a_prep1(struct v7 *v7, val_t t, val_t *a0, val_t *a1) {
  *a0 = v7_arg(v7, 0);
  *a1 = v7_arg(v7, 1);
  if (v7_is_undefined(*a1)) {
    *a1 = t;
  }
}

/*
 * Call callback function `cb`, passing `this_obj` as `this`, with the
 * following arguments:
 *
 *   cb(v, n, this_obj);
 *
 */
WARN_UNUSED_RESULT
static enum v7_err a_prep2(struct v7 *v7, val_t cb, val_t v, val_t n,
                           val_t this_obj, val_t *res) {
  enum v7_err rcode = V7_OK;
  int saved_inhibit_gc = v7->inhibit_gc;
  val_t args = v7_mk_dense_array(v7);

  *res = v7_mk_dense_array(v7);

  v7_own(v7, &args);

  v7_array_push(v7, args, v);
  v7_array_push(v7, args, n);
  v7_array_push(v7, args, this_obj);

  v7->inhibit_gc = 0;
  rcode = b_apply(v7, cb, this_obj, args, 0, res);
  if (rcode != V7_OK) {
    goto clean;
  }

clean:
  v7->inhibit_gc = saved_inhibit_gc;
  v7_disown(v7, &args);

  return rcode;
}

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Array_forEach(struct v7 *v7, v7_val_t *res) {
  enum v7_err rcode = V7_OK;
  val_t this_obj = v7_get_this(v7);
  val_t v = V7_UNDEFINED, cb = v7_arg(v7, 0);
  unsigned long len, i;
  int has;
  /* a_prep2 uninhibits GC when calling cb */
  struct gc_tmp_frame vf = new_tmp_frame(v7);

  if (!v7_is_object(this_obj)) {
    rcode = v7_throwf(v7, TYPE_ERROR, "Array expected");
    goto clean;
  }

  if (!v7_is_callable(v7, cb)) {
    rcode = v7_throwf(v7, TYPE_ERROR, "Function expected");
    goto clean;
  }

  tmp_stack_push(&vf, &v);

  len = v7_array_length(v7, this_obj);
  for (i = 0; i < len; i++) {
    v = v7_array_get2(v7, this_obj, i, &has);
    if (!has) continue;

    rcode = a_prep2(v7, cb, v, v7_mk_number(v7, i), this_obj, res);
    if (rcode != V7_OK) {
      goto clean;
    }
  }

clean:
  tmp_frame_cleanup(&vf);
  return rcode;
}

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Array_map(struct v7 *v7, v7_val_t *res) {
  enum v7_err rcode = V7_OK;
  val_t this_obj = v7_get_this(v7);
  val_t arg0, arg1, el, v;
  unsigned long len, i;
  int has;
  /* a_prep2 uninhibits GC when calling cb */
  struct gc_tmp_frame vf = new_tmp_frame(v7);

  if (!v7_is_object(this_obj)) {
    rcode = v7_throwf(v7, TYPE_ERROR, "Array expected");
    goto clean;
  } else {
    a_prep1(v7, this_obj, &arg0, &arg1);
    *res = v7_mk_dense_array(v7);
    len = v7_array_length(v7, this_obj);

    tmp_stack_push(&vf, &arg0);
    tmp_stack_push(&vf, &arg1);
    tmp_stack_push(&vf, &v);

    for (i = 0; i < len; i++) {
      v = v7_array_get2(v7, this_obj, i, &has);
      if (!has) continue;
      rcode = a_prep2(v7, arg0, v, v7_mk_number(v7, i), arg1, &el);
      if (rcode != V7_OK) {
        goto clean;
      }

      rcode = v7_array_set_throwing(v7, *res, i, el, NULL);
      if (rcode != V7_OK) {
        goto clean;
      }
    }
  }

clean:
  tmp_frame_cleanup(&vf);
  return rcode;
}

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Array_every(struct v7 *v7, v7_val_t *res) {
  enum v7_err rcode = V7_OK;
  val_t this_obj = v7_get_this(v7);
  val_t arg0, arg1, el, v;
  unsigned long i, len;
  int has;
  /* a_prep2 uninhibits GC when calling cb */
  struct gc_tmp_frame vf = new_tmp_frame(v7);

  *res = v7_mk_boolean(v7, 0);

  if (!v7_is_object(this_obj)) {
    rcode = v7_throwf(v7, TYPE_ERROR, "Array expected");
    goto clean;
  } else {
    a_prep1(v7, this_obj, &arg0, &arg1);

    tmp_stack_push(&vf, &arg0);
    tmp_stack_push(&vf, &arg1);
    tmp_stack_push(&vf, &v);

    len = v7_array_length(v7, this_obj);
    for (i = 0; i < len; i++) {
      v = v7_array_get2(v7, this_obj, i, &has);
      if (!has) continue;
      rcode = a_prep2(v7, arg0, v, v7_mk_number(v7, i), arg1, &el);
      if (rcode != V7_OK) {
        goto clean;
      }
      if (!v7_is_truthy(v7, el)) {
        *res = v7_mk_boolean(v7, 0);
        goto clean;
      }
    }
  }

  *res = v7_mk_boolean(v7, 1);

clean:
  tmp_frame_cleanup(&vf);
  return rcode;
}

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Array_some(struct v7 *v7, v7_val_t *res) {
  enum v7_err rcode = V7_OK;
  val_t this_obj = v7_get_this(v7);
  val_t arg0, arg1, el, v;
  unsigned long i, len;
  int has;
  /* a_prep2 uninhibits GC when calling cb */
  struct gc_tmp_frame vf = new_tmp_frame(v7);

  *res = v7_mk_boolean(v7, 1);

  if (!v7_is_object(this_obj)) {
    rcode = v7_throwf(v7, TYPE_ERROR, "Array expected");
    goto clean;
  } else {
    a_prep1(v7, this_obj, &arg0, &arg1);

    tmp_stack_push(&vf, &arg0);
    tmp_stack_push(&vf, &arg1);
    tmp_stack_push(&vf, &v);

    len = v7_array_length(v7, this_obj);
    for (i = 0; i < len; i++) {
      v = v7_array_get2(v7, this_obj, i, &has);
      if (!has) continue;
      rcode = a_prep2(v7, arg0, v, v7_mk_number(v7, i), arg1, &el);
      if (rcode != V7_OK) {
        goto clean;
      }
      if (v7_is_truthy(v7, el)) {
        *res = v7_mk_boolean(v7, 1);
        goto clean;
      }
    }
  }

  *res = v7_mk_boolean(v7, 0);

clean:
  tmp_frame_cleanup(&vf);
  return rcode;
}

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Array_filter(struct v7 *v7, v7_val_t *res) {
  enum v7_err rcode = V7_OK;
  val_t this_obj = v7_get_this(v7);
  val_t arg0, arg1, el, v;
  unsigned long len, i;
  int has;
  /* a_prep2 uninhibits GC when calling cb */
  struct gc_tmp_frame vf = new_tmp_frame(v7);

  if (!v7_is_object(this_obj)) {
    rcode = v7_throwf(v7, TYPE_ERROR, "Array expected");
    goto clean;
  } else {
    a_prep1(v7, this_obj, &arg0, &arg1);
    *res = v7_mk_dense_array(v7);
    len = v7_array_length(v7, this_obj);

    tmp_stack_push(&vf, &arg0);
    tmp_stack_push(&vf, &arg1);
    tmp_stack_push(&vf, &v);

    for (i = 0; i < len; i++) {
      v = v7_array_get2(v7, this_obj, i, &has);
      if (!has) continue;
      rcode = a_prep2(v7, arg0, v, v7_mk_number(v7, i), arg1, &el);
      if (rcode != V7_OK) {
        goto clean;
      }
      if (v7_is_truthy(v7, el)) {
        rcode = v7_array_push_throwing(v7, *res, v, NULL);
        if (rcode != V7_OK) {
          goto clean;
        }
      }
    }
  }

clean:
  tmp_frame_cleanup(&vf);
  return rcode;
}

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Array_concat(struct v7 *v7, v7_val_t *res) {
  enum v7_err rcode = V7_OK;
  val_t this_obj = v7_get_this(v7);
  size_t i, j, len;
  val_t saved_args;

  if (!v7_is_array(v7, this_obj)) {
    rcode = v7_throwf(v7, TYPE_ERROR, "Array expected");
    goto clean;
  }

  len = v7_argc(v7);

  /*
   * reuse a_splice but override it's arguments. a_splice
   * internally uses a lot of helpers that fetch arguments
   * from the v7 context.
   * TODO(mkm): we need a better helper call another cfunction
   * from a cfunction.
   */
  saved_args = v7->vals.arguments;
  v7->vals.arguments = V7_UNDEFINED;
  rcode = a_splice(v7, 1, res);
  if (rcode != V7_OK) {
    goto clean;
  }
  v7->vals.arguments = saved_args;

  for (i = 0; i < len; i++) {
    val_t a = v7_arg(v7, i);
    if (!v7_is_array(v7, a)) {
      rcode = v7_array_push_throwing(v7, *res, a, NULL);
      if (rcode != V7_OK) {
        goto clean;
      }
    } else {
      size_t alen = v7_array_length(v7, a);
      for (j = 0; j < alen; j++) {
        rcode = v7_array_push_throwing(v7, *res, v7_array_get(v7, a, j), NULL);
        if (rcode != V7_OK) {
          goto clean;
        }
      }
    }
  }

clean:
  return rcode;
}

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Array_isArray(struct v7 *v7, v7_val_t *res) {
  val_t arg0 = v7_arg(v7, 0);
  *res = v7_mk_boolean(v7, v7_is_array(v7, arg0));
  return V7_OK;
}

V7_PRIVATE void init_array(struct v7 *v7) {
  val_t ctor = mk_cfunction_obj(v7, Array_ctor, 1);
  val_t length = v7_mk_dense_array(v7);

  v7_set(v7, ctor, "prototype", 9, v7->vals.array_prototype);
  set_method(v7, ctor, "isArray", Array_isArray, 1);
  v7_set(v7, v7->vals.global_object, "Array", 5, ctor);
  v7_def(v7, v7->vals.array_prototype, "constructor", ~0, _V7_DESC_HIDDEN(1),
         ctor);
  v7_set(v7, ctor, "name", 4, v7_mk_string(v7, "Array", ~0, 1));

  set_method(v7, v7->vals.array_prototype, "concat", Array_concat, 1);
  set_method(v7, v7->vals.array_prototype, "every", Array_every, 1);
  set_method(v7, v7->vals.array_prototype, "filter", Array_filter, 1);
  set_method(v7, v7->vals.array_prototype, "forEach", Array_forEach, 1);
  set_method(v7, v7->vals.array_prototype, "join", Array_join, 1);
  set_method(v7, v7->vals.array_prototype, "map", Array_map, 1);
  set_method(v7, v7->vals.array_prototype, "push", Array_push, 1);
  set_method(v7, v7->vals.array_prototype, "reverse", Array_reverse, 0);
  set_method(v7, v7->vals.array_prototype, "slice", Array_slice, 2);
  set_method(v7, v7->vals.array_prototype, "some", Array_some, 1);
  set_method(v7, v7->vals.array_prototype, "sort", Array_sort, 1);
  set_method(v7, v7->vals.array_prototype, "splice", Array_splice, 2);
  set_method(v7, v7->vals.array_prototype, "toString", Array_toString, 0);

  v7_array_set(v7, length, 0, v7_mk_cfunction(Array_get_length));
  v7_array_set(v7, length, 1, v7_mk_cfunction(Array_set_length));
  v7_def(v7, v7->vals.array_prototype, "length", 6,
         V7_DESC_ENUMERABLE(0) | V7_DESC_GETTER(1) | V7_DESC_SETTER(1), length);
}

#if defined(__cplusplus)
}
#endif /* __cplusplus */
#ifdef V7_MODULE_LINES
#line 1 "v7/src/std_boolean.c"
#endif
/*
 * Copyright (c) 2014 Cesanta Software Limited
 * All rights reserved
 */

/* Amalgamated: #include "v7/src/internal.h" */
/* Amalgamated: #include "v7/src/std_object.h" */
/* Amalgamated: #include "v7/src/core.h" */
/* Amalgamated: #include "v7/src/function.h" */
/* Amalgamated: #include "v7/src/object.h" */
/* Amalgamated: #include "v7/src/conversion.h" */
/* Amalgamated: #include "v7/src/primitive.h" */
/* Amalgamated: #include "v7/src/exceptions.h" */
/* Amalgamated: #include "v7/src/string.h" */

#if defined(__cplusplus)
extern "C" {
#endif /* __cplusplus */

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Boolean_ctor(struct v7 *v7, v7_val_t *res) {
  enum v7_err rcode = V7_OK;
  val_t this_obj = v7_get_this(v7);

  *res = to_boolean_v(v7, v7_arg(v7, 0));

  if (v7_is_generic_object(this_obj) && this_obj != v7->vals.global_object) {
    /* called as "new Boolean(...)" */
    obj_prototype_set(v7, get_object_struct(this_obj),
                      get_object_struct(v7->vals.boolean_prototype));
    v7_def(v7, this_obj, "", 0, _V7_DESC_HIDDEN(1), *res);

    /*
     * implicitly returning `this`: `call_cfunction()` in bcode.c will do
     * that for us
     */
  }

  return rcode;
}

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Boolean_valueOf(struct v7 *v7, v7_val_t *res) {
  enum v7_err rcode = V7_OK;
  val_t this_obj = v7_get_this(v7);
  if (!v7_is_boolean(this_obj) &&
      (v7_is_object(this_obj) &&
       v7_get_proto(v7, this_obj) != v7->vals.boolean_prototype)) {
    rcode = v7_throwf(v7, TYPE_ERROR,
                      "Boolean.valueOf called on non-boolean object");
    goto clean;
  }

  rcode = Obj_valueOf(v7, res);
  if (rcode != V7_OK) {
    goto clean;
  }

clean:
  return rcode;
}

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Boolean_toString(struct v7 *v7, v7_val_t *res) {
  enum v7_err rcode = V7_OK;
  val_t this_obj = v7_get_this(v7);

  *res = V7_UNDEFINED;

  if (this_obj == v7->vals.boolean_prototype) {
    *res = v7_mk_string(v7, "false", 5, 1);
    goto clean;
  }

  if (!v7_is_boolean(this_obj) &&
      !(v7_is_generic_object(this_obj) &&
        is_prototype_of(v7, this_obj, v7->vals.boolean_prototype))) {
    rcode = v7_throwf(v7, TYPE_ERROR,
                      "Boolean.toString called on non-boolean object");
    goto clean;
  }

  rcode = obj_value_of(v7, this_obj, res);
  if (rcode != V7_OK) {
    goto clean;
  }

  rcode = primitive_to_str(v7, *res, res, NULL, 0, NULL);
  if (rcode != V7_OK) {
    goto clean;
  }

clean:
  return rcode;
}

V7_PRIVATE void init_boolean(struct v7 *v7) {
  val_t ctor = mk_cfunction_obj_with_proto(v7, Boolean_ctor, 1,
                                           v7->vals.boolean_prototype);
  v7_set(v7, v7->vals.global_object, "Boolean", 7, ctor);

  set_cfunc_prop(v7, v7->vals.boolean_prototype, "valueOf", Boolean_valueOf);
  set_cfunc_prop(v7, v7->vals.boolean_prototype, "toString", Boolean_toString);
}

#if defined(__cplusplus)
}
#endif /* __cplusplus */
#ifdef V7_MODULE_LINES
#line 1 "v7/src/std_math.c"
#endif
/*
 * Copyright (c) 2014 Cesanta Software Limited
 * All rights reserved
 */

/* Amalgamated: #include "v7/src/internal.h" */
/* Amalgamated: #include "v7/src/core.h" */
/* Amalgamated: #include "v7/src/object.h" */
/* Amalgamated: #include "v7/src/primitive.h" */

#if V7_ENABLE__Math

#if defined(__cplusplus)
extern "C" {
#endif /* __cplusplus */

#ifdef __WATCOM__
int matherr(struct _exception *exc) {
  if (exc->type == DOMAIN) {
    exc->retval = NAN;
    return 0;
  }
}
#endif

#if V7_ENABLE__Math__abs || V7_ENABLE__Math__acos || V7_ENABLE__Math__asin ||  \
    V7_ENABLE__Math__atan || V7_ENABLE__Math__ceil || V7_ENABLE__Math__cos ||  \
    V7_ENABLE__Math__exp || V7_ENABLE__Math__floor || V7_ENABLE__Math__log ||  \
    V7_ENABLE__Math__round || V7_ENABLE__Math__sin || V7_ENABLE__Math__sqrt || \
    V7_ENABLE__Math__tan
WARN_UNUSED_RESULT
static enum v7_err m_one_arg(struct v7 *v7, double (*f)(double), val_t *res) {
  enum v7_err rcode = V7_OK;
  val_t arg0 = v7_arg(v7, 0);
  double d0 = v7_get_double(v7, arg0);
#ifdef V7_BROKEN_NAN
  if (isnan(d0)) {
    *res = V7_TAG_NAN;
    goto clean;
  }
#endif
  *res = v7_mk_number(v7, f(d0));
  goto clean;

clean:
  return rcode;
}
#endif /* V7_ENABLE__Math__* */

#if V7_ENABLE__Math__pow || V7_ENABLE__Math__atan2
WARN_UNUSED_RESULT
static enum v7_err m_two_arg(struct v7 *v7, double (*f)(double, double),
                             val_t *res) {
  enum v7_err rcode = V7_OK;
  val_t arg0 = v7_arg(v7, 0);
  val_t arg1 = v7_arg(v7, 1);
  double d0 = v7_get_double(v7, arg0);
  double d1 = v7_get_double(v7, arg1);
#ifdef V7_BROKEN_NAN
  /* pow(NaN,0) == 1, doesn't fix atan2, but who cares */
  if (isnan(d1)) {
    *res = V7_TAG_NAN;
    goto clean;
  }
#endif
  *res = v7_mk_number(v7, f(d0, d1));
  goto clean;

clean:
  return rcode;
}
#endif /* V7_ENABLE__Math__pow || V7_ENABLE__Math__atan2 */

#define DEFINE_WRAPPER(name, func)                                   \
  WARN_UNUSED_RESULT                                                 \
  V7_PRIVATE enum v7_err Math_##name(struct v7 *v7, v7_val_t *res) { \
    return func(v7, name, res);                                      \
  }

/* Visual studio 2012+ has round() */
#if V7_ENABLE__Math__round && \
    ((defined(V7_WINDOWS) && _MSC_VER < 1700) || defined(__WATCOM__))
static double round(double n) {
  return n;
}
#endif

#if V7_ENABLE__Math__abs
DEFINE_WRAPPER(fabs, m_one_arg)
#endif
#if V7_ENABLE__Math__acos
DEFINE_WRAPPER(acos, m_one_arg)
#endif
#if V7_ENABLE__Math__asin
DEFINE_WRAPPER(asin, m_one_arg)
#endif
#if V7_ENABLE__Math__atan
DEFINE_WRAPPER(atan, m_one_arg)
#endif
#if V7_ENABLE__Math__atan2
DEFINE_WRAPPER(atan2, m_two_arg)
#endif
#if V7_ENABLE__Math__ceil
DEFINE_WRAPPER(ceil, m_one_arg)
#endif
#if V7_ENABLE__Math__cos
DEFINE_WRAPPER(cos, m_one_arg)
#endif
#if V7_ENABLE__Math__exp
DEFINE_WRAPPER(exp, m_one_arg)
#endif
#if V7_ENABLE__Math__floor
DEFINE_WRAPPER(floor, m_one_arg)
#endif
#if V7_ENABLE__Math__log
DEFINE_WRAPPER(log, m_one_arg)
#endif
#if V7_ENABLE__Math__pow
DEFINE_WRAPPER(pow, m_two_arg)
#endif
#if V7_ENABLE__Math__round
DEFINE_WRAPPER(round, m_one_arg)
#endif
#if V7_ENABLE__Math__sin
DEFINE_WRAPPER(sin, m_one_arg)
#endif
#if V7_ENABLE__Math__sqrt
DEFINE_WRAPPER(sqrt, m_one_arg)
#endif
#if V7_ENABLE__Math__tan
DEFINE_WRAPPER(tan, m_one_arg)
#endif

#if V7_ENABLE__Math__random
WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Math_random(struct v7 *v7, v7_val_t *res) {
  (void) v7;
  *res = v7_mk_number(v7, (double) rand() / RAND_MAX);
  return V7_OK;
}
#endif /* V7_ENABLE__Math__random */

#if V7_ENABLE__Math__min || V7_ENABLE__Math__max
WARN_UNUSED_RESULT
static enum v7_err min_max(struct v7 *v7, int is_min, val_t *res) {
  enum v7_err rcode = V7_OK;
  double dres = NAN;
  int i, len = v7_argc(v7);

  for (i = 0; i < len; i++) {
    double v = v7_get_double(v7, v7_arg(v7, i));
    if (isnan(dres) || (is_min && v < dres) || (!is_min && v > dres)) {
      dres = v;
    }
  }

  *res = v7_mk_number(v7, dres);

  return rcode;
}
#endif /* V7_ENABLE__Math__min || V7_ENABLE__Math__max */

#if V7_ENABLE__Math__min
WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Math_min(struct v7 *v7, v7_val_t *res) {
  return min_max(v7, 1, res);
}
#endif

#if V7_ENABLE__Math__max
WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Math_max(struct v7 *v7, v7_val_t *res) {
  return min_max(v7, 0, res);
}
#endif

V7_PRIVATE void init_math(struct v7 *v7) {
  val_t math = v7_mk_object(v7);

#if V7_ENABLE__Math__abs
  set_cfunc_prop(v7, math, "abs", Math_fabs);
#endif
#if V7_ENABLE__Math__acos
  set_cfunc_prop(v7, math, "acos", Math_acos);
#endif
#if V7_ENABLE__Math__asin
  set_cfunc_prop(v7, math, "asin", Math_asin);
#endif
#if V7_ENABLE__Math__atan
  set_cfunc_prop(v7, math, "atan", Math_atan);
#endif
#if V7_ENABLE__Math__atan2
  set_cfunc_prop(v7, math, "atan2", Math_atan2);
#endif
#if V7_ENABLE__Math__ceil
  set_cfunc_prop(v7, math, "ceil", Math_ceil);
#endif
#if V7_ENABLE__Math__cos
  set_cfunc_prop(v7, math, "cos", Math_cos);
#endif
#if V7_ENABLE__Math__exp
  set_cfunc_prop(v7, math, "exp", Math_exp);
#endif
#if V7_ENABLE__Math__floor
  set_cfunc_prop(v7, math, "floor", Math_floor);
#endif
#if V7_ENABLE__Math__log
  set_cfunc_prop(v7, math, "log", Math_log);
#endif
#if V7_ENABLE__Math__max
  set_cfunc_prop(v7, math, "max", Math_max);
#endif
#if V7_ENABLE__Math__min
  set_cfunc_prop(v7, math, "min", Math_min);
#endif
#if V7_ENABLE__Math__pow
  set_cfunc_prop(v7, math, "pow", Math_pow);
#endif
#if V7_ENABLE__Math__random
  /* Incorporate our pointer into the RNG.
   * If srand() has not been called before, this will provide some randomness.
   * If it has, it will hopefully not make things worse.
   */
  srand(rand() ^ ((uintptr_t) v7));
  set_cfunc_prop(v7, math, "random", Math_random);
#endif
#if V7_ENABLE__Math__round
  set_cfunc_prop(v7, math, "round", Math_round);
#endif
#if V7_ENABLE__Math__sin
  set_cfunc_prop(v7, math, "sin", Math_sin);
#endif
#if V7_ENABLE__Math__sqrt
  set_cfunc_prop(v7, math, "sqrt", Math_sqrt);
#endif
#if V7_ENABLE__Math__tan
  set_cfunc_prop(v7, math, "tan", Math_tan);
#endif

#if V7_ENABLE__Math__constants
  v7_set(v7, math, "E", 1, v7_mk_number(v7, M_E));
  v7_set(v7, math, "PI", 2, v7_mk_number(v7, M_PI));
  v7_set(v7, math, "LN2", 3, v7_mk_number(v7, M_LN2));
  v7_set(v7, math, "LN10", 4, v7_mk_number(v7, M_LN10));
  v7_set(v7, math, "LOG2E", 5, v7_mk_number(v7, M_LOG2E));
  v7_set(v7, math, "LOG10E", 6, v7_mk_number(v7, M_LOG10E));
  v7_set(v7, math, "SQRT1_2", 7, v7_mk_number(v7, M_SQRT1_2));
  v7_set(v7, math, "SQRT2", 5, v7_mk_number(v7, M_SQRT2));
#endif

  v7_set(v7, v7->vals.global_object, "Math", 4, math);
}

#if defined(__cplusplus)
}
#endif /* __cplusplus */

#endif /* V7_ENABLE__Math */
#ifdef V7_MODULE_LINES
#line 1 "v7/src/std_string.c"
#endif
/*
 * Copyright (c) 2014 Cesanta Software Limited
 * All rights reserved
 */

/* Amalgamated: #include "common/utf.h" */
/* Amalgamated: #include "v7/src/internal.h" */
/* Amalgamated: #include "v7/src/std_string.h" */
/* Amalgamated: #include "v7/src/core.h" */
/* Amalgamated: #include "v7/src/function.h" */
/* Amalgamated: #include "v7/src/string.h" */
/* Amalgamated: #include "v7/src/slre.h" */
/* Amalgamated: #include "v7/src/std_regex.h" */
/* Amalgamated: #include "v7/src/std_object.h" */
/* Amalgamated: #include "v7/src/eval.h" */
/* Amalgamated: #include "v7/src/conversion.h" */
/* Amalgamated: #include "v7/src/array.h" */
/* Amalgamated: #include "v7/src/object.h" */
/* Amalgamated: #include "v7/src/regexp.h" */
/* Amalgamated: #include "v7/src/exceptions.h" */

/* Substring implementations: RegExp-based and String-based {{{ */

/*
 * Substring context: currently, used in Str_split() only, but will probably
 * be used in Str_replace() and other functions as well.
 *
 * Needed to provide different implementation for RegExp or String arguments,
 * keeping common parts reusable.
 */
struct _str_split_ctx {
  /* implementation-specific data */
  union {
#if V7_ENABLE__RegExp
    struct {
      struct slre_prog *prog;
      struct slre_loot loot;
    } regexp;
#endif

    struct {
      val_t sep;
    } string;
  } impl;

  struct v7 *v7;

  /* start and end of previous match (set by `p_exec()`) */
  const char *match_start;
  const char *match_end;

  /* pointers to implementation functions */

  /*
   * Initialize context
   */
  void (*p_init)(struct _str_split_ctx *ctx, struct v7 *v7, val_t sep);

  /*
   * Look for the next match, set `match_start` and `match_end` to appropriate
   * values.
   *
   * Returns 0 if match found, 1 otherwise (in accordance with `slre_exec()`)
   */
  int (*p_exec)(struct _str_split_ctx *ctx, const char *start, const char *end);

#if V7_ENABLE__RegExp
  /*
   * Add captured data to resulting array (for RegExp-based implementation only)
   *
   * Returns updated `elem` value
   */
  long (*p_add_caps)(struct _str_split_ctx *ctx, val_t res, long elem,
                     long limit);
#endif
};

#if V7_ENABLE__RegExp
/* RegExp-based implementation of `p_init` in `struct _str_split_ctx` */
static void subs_regexp_init(struct _str_split_ctx *ctx, struct v7 *v7,
                             val_t sep) {
  ctx->v7 = v7;
  ctx->impl.regexp.prog = v7_get_regexp_struct(v7, sep)->compiled_regexp;
}

/* RegExp-based implementation of `p_exec` in `struct _str_split_ctx` */
static int subs_regexp_exec(struct _str_split_ctx *ctx, const char *start,
                            const char *end) {
  int ret =
      slre_exec(ctx->impl.regexp.prog, 0, start, end, &ctx->impl.regexp.loot);

  ctx->match_start = ctx->impl.regexp.loot.caps[0].start;
  ctx->match_end = ctx->impl.regexp.loot.caps[0].end;

  return ret;
}

/* RegExp-based implementation of `p_add_caps` in `struct _str_split_ctx` */
static long subs_regexp_split_add_caps(struct _str_split_ctx *ctx, val_t res,
                                       long elem, long limit) {
  int i;
  for (i = 1; i < ctx->impl.regexp.loot.num_captures && elem < limit; i++) {
    size_t cap_len =
        ctx->impl.regexp.loot.caps[i].end - ctx->impl.regexp.loot.caps[i].start;
    v7_array_push(
        ctx->v7, res,
        (ctx->impl.regexp.loot.caps[i].start != NULL)
            ? v7_mk_string(ctx->v7, ctx->impl.regexp.loot.caps[i].start,
                           cap_len, 1)
            : V7_UNDEFINED);
    elem++;
  }
  return elem;
}
#endif

/* String-based implementation of `p_init` in `struct _str_split_ctx` */
static void subs_string_init(struct _str_split_ctx *ctx, struct v7 *v7,
                             val_t sep) {
  ctx->v7 = v7;
  ctx->impl.string.sep = sep;
}

/* String-based implementation of `p_exec` in `struct _str_split_ctx` */
static int subs_string_exec(struct _str_split_ctx *ctx, const char *start,
                            const char *end) {
  int ret = 1;
  size_t sep_len;
  const char *psep = v7_get_string(ctx->v7, &ctx->impl.string.sep, &sep_len);

  if (sep_len == 0) {
    /* separator is an empty string: match empty string */
    ctx->match_start = start;
    ctx->match_end = start;
    ret = 0;
  } else {
    size_t i;
    for (i = 0; start <= (end - sep_len); ++i, start = utfnshift(start, 1)) {
      if (memcmp(start, psep, sep_len) == 0) {
        ret = 0;
        ctx->match_start = start;
        ctx->match_end = start + sep_len;
        break;
      }
    }
  }

  return ret;
}

#if V7_ENABLE__RegExp
/* String-based implementation of `p_add_caps` in `struct _str_split_ctx` */
static long subs_string_split_add_caps(struct _str_split_ctx *ctx, val_t res,
                                       long elem, long limit) {
  /* this is a stub function */
  (void) ctx;
  (void) res;
  (void) limit;
  return elem;
}
#endif

/* }}} */

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err String_ctor(struct v7 *v7, v7_val_t *res) {
  enum v7_err rcode = V7_OK;
  val_t this_obj = v7_get_this(v7);
  val_t arg0 = v7_arg(v7, 0);

  *res = arg0;

  if (v7_argc(v7) == 0) {
    *res = v7_mk_string(v7, NULL, 0, 1);
  } else if (!v7_is_string(arg0)) {
    rcode = to_string(v7, arg0, res, NULL, 0, NULL);
    if (rcode != V7_OK) {
      goto clean;
    }
  }

  if (v7_is_generic_object(this_obj) && this_obj != v7->vals.global_object) {
    obj_prototype_set(v7, get_object_struct(this_obj),
                      get_object_struct(v7->vals.string_prototype));
    v7_def(v7, this_obj, "", 0, _V7_DESC_HIDDEN(1), *res);
    /*
     * implicitly returning `this`: `call_cfunction()` in bcode.c will do
     * that for us
     */
    goto clean;
  }

clean:
  return rcode;
}

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Str_fromCharCode(struct v7 *v7, v7_val_t *res) {
  enum v7_err rcode = V7_OK;
  int i, num_args = v7_argc(v7);

  *res = v7_mk_string(v7, "", 0, 1); /* Empty string */

  for (i = 0; i < num_args; i++) {
    char buf[10];
    val_t arg = v7_arg(v7, i);
    double d = v7_get_double(v7, arg);
    Rune r = (Rune)((int32_t)(isnan(d) || isinf(d) ? 0 : d) & 0xffff);
    int n = runetochar(buf, &r);
    val_t s = v7_mk_string(v7, buf, n, 1);
    *res = s_concat(v7, *res, s);
  }

  return rcode;
}

WARN_UNUSED_RESULT
static enum v7_err s_charCodeAt(struct v7 *v7, double *res) {
  return v7_char_code_at(v7, v7_get_this(v7), v7_arg(v7, 0), res);
}

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Str_charCodeAt(struct v7 *v7, v7_val_t *res) {
  enum v7_err rcode = V7_OK;
  double dnum = 0;

  rcode = s_charCodeAt(v7, &dnum);
  if (rcode != V7_OK) {
    goto clean;
  }

  *res = v7_mk_number(v7, dnum);

clean:
  return rcode;
}

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Str_charAt(struct v7 *v7, v7_val_t *res) {
  enum v7_err rcode = V7_OK;
  double code = 0;
  char buf[10] = {0};
  int len = 0;

  rcode = s_charCodeAt(v7, &code);
  if (rcode != V7_OK) {
    goto clean;
  }

  if (!isnan(code)) {
    Rune r = (Rune) code;
    len = runetochar(buf, &r);
  }
  *res = v7_mk_string(v7, buf, len, 1);

clean:
  return rcode;
}

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Str_concat(struct v7 *v7, v7_val_t *res) {
  enum v7_err rcode = V7_OK;
  val_t this_obj = v7_get_this(v7);
  int i, num_args = v7_argc(v7);

  rcode = to_string(v7, this_obj, res, NULL, 0, NULL);
  if (rcode != V7_OK) {
    goto clean;
  }

  for (i = 0; i < num_args; i++) {
    val_t str = V7_UNDEFINED;

    rcode = to_string(v7, v7_arg(v7, i), &str, NULL, 0, NULL);
    if (rcode != V7_OK) {
      goto clean;
    }

    *res = s_concat(v7, *res, str);
  }

clean:
  return rcode;
}

WARN_UNUSED_RESULT
static enum v7_err s_index_of(struct v7 *v7, int last, val_t *res) {
  enum v7_err rcode = V7_OK;
  val_t this_obj = v7_get_this(v7);
  val_t arg0 = v7_arg(v7, 0);
  size_t fromIndex = 0;
  double dres = -1;

  if (!v7_is_undefined(arg0)) {
    const char *p1, *p2, *end;
    size_t i, len1, len2, bytecnt1, bytecnt2;
    val_t sub = V7_UNDEFINED;

    rcode = to_string(v7, arg0, &sub, NULL, 0, NULL);
    if (rcode != V7_OK) {
      goto clean;
    }

    rcode = to_string(v7, this_obj, &this_obj, NULL, 0, NULL);
    if (rcode != V7_OK) {
      goto clean;
    }

    p1 = v7_get_string(v7, &this_obj, &bytecnt1);
    p2 = v7_get_string(v7, &sub, &bytecnt2);

    if (bytecnt2 <= bytecnt1) {
      end = p1 + bytecnt1;
      len1 = utfnlen(p1, bytecnt1);
      len2 = utfnlen(p2, bytecnt2);

      if (v7_argc(v7) > 1) {
        /* `fromIndex` was provided. Normalize it */
        double d = 0;
        {
          val_t arg = v7_arg(v7, 1);
          rcode = to_number_v(v7, arg, &arg);
          if (rcode != V7_OK) {
            goto clean;
          }
          d = v7_get_double(v7, arg);
        }
        if (isnan(d) || d < 0) {
          d = 0.0;
        } else if (isinf(d) || d > len1) {
          d = len1;
        }
        fromIndex = d;

        /* adjust pointers accordingly to `fromIndex` */
        if (last) {
          const char *end_tmp = utfnshift(p1, fromIndex + len2);
          end = (end_tmp < end) ? end_tmp : end;
        } else {
          p1 = utfnshift(p1, fromIndex);
        }
      }

      /*
       * TODO(dfrank): when `last` is set, start from the end and look
       * backward. We'll need to improve `utfnshift()` for that, so that it can
       * handle negative offsets.
       */
      for (i = 0; p1 <= (end - bytecnt2); i++, p1 = utfnshift(p1, 1)) {
        if (memcmp(p1, p2, bytecnt2) == 0) {
          dres = i;
          if (!last) break;
        }
      }
    }
  }
  if (!last && dres >= 0) dres += fromIndex;
  *res = v7_mk_number(v7, dres);

clean:
  return rcode;
}

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Str_valueOf(struct v7 *v7, v7_val_t *res) {
  enum v7_err rcode = V7_OK;
  val_t this_obj = v7_get_this(v7);

  if (!v7_is_string(this_obj) &&
      (v7_is_object(this_obj) &&
       v7_get_proto(v7, this_obj) != v7->vals.string_prototype)) {
    rcode =
        v7_throwf(v7, TYPE_ERROR, "String.valueOf called on non-string object");
    goto clean;
  }

  rcode = Obj_valueOf(v7, res);
  goto clean;

clean:
  return rcode;
}

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Str_indexOf(struct v7 *v7, v7_val_t *res) {
  return s_index_of(v7, 0, res);
}

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Str_lastIndexOf(struct v7 *v7, v7_val_t *res) {
  return s_index_of(v7, 1, res);
}

#if V7_ENABLE__String__localeCompare
WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Str_localeCompare(struct v7 *v7, v7_val_t *res) {
  enum v7_err rcode = V7_OK;
  val_t this_obj = v7_get_this(v7);
  val_t arg0 = V7_UNDEFINED;
  val_t s = V7_UNDEFINED;

  rcode = to_string(v7, v7_arg(v7, 0), &arg0, NULL, 0, NULL);
  if (rcode != V7_OK) {
    goto clean;
  }

  rcode = to_string(v7, this_obj, &s, NULL, 0, NULL);
  if (rcode != V7_OK) {
    goto clean;
  }

  *res = v7_mk_number(v7, s_cmp(v7, s, arg0));

clean:
  return rcode;
}
#endif

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Str_toString(struct v7 *v7, v7_val_t *res) {
  enum v7_err rcode = V7_OK;
  val_t this_obj = v7_get_this(v7);

  if (this_obj == v7->vals.string_prototype) {
    *res = v7_mk_string(v7, "false", 5, 1);
    goto clean;
  }

  if (!v7_is_string(this_obj) &&
      !(v7_is_generic_object(this_obj) &&
        is_prototype_of(v7, this_obj, v7->vals.string_prototype))) {
    rcode = v7_throwf(v7, TYPE_ERROR,
                      "String.toString called on non-string object");
    goto clean;
  }

  rcode = obj_value_of(v7, this_obj, &this_obj);
  if (rcode != V7_OK) {
    goto clean;
  }

  rcode = to_string(v7, this_obj, res, NULL, 0, NULL);
  if (rcode != V7_OK) {
    goto clean;
  }

clean:
  return rcode;
}

#if V7_ENABLE__RegExp
WARN_UNUSED_RESULT
enum v7_err call_regex_ctor(struct v7 *v7, val_t arg, val_t *res) {
  /* TODO(mkm): make general helper out of this */
  enum v7_err rcode = V7_OK;
  val_t saved_args = v7->vals.arguments, args = v7_mk_dense_array(v7);
  v7_array_push(v7, args, arg);
  v7->vals.arguments = args;

  rcode = Regex_ctor(v7, res);
  if (rcode != V7_OK) {
    goto clean;
  }
  v7->vals.arguments = saved_args;

clean:
  return rcode;
}

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Str_match(struct v7 *v7, v7_val_t *res) {
  enum v7_err rcode = V7_OK;
  val_t this_obj = v7_get_this(v7);
  val_t so = V7_UNDEFINED, ro = V7_UNDEFINED;
  long previousLastIndex = 0;
  int lastMatch = 1, n = 0, flag_g;
  struct v7_regexp *rxp;

  *res = V7_NULL;

  rcode = to_string(v7, this_obj, &so, NULL, 0, NULL);
  if (rcode != V7_OK) {
    goto clean;
  }

  if (v7_argc(v7) == 0) {
    rcode = v7_mk_regexp(v7, "", 0, "", 0, &ro);
    if (rcode != V7_OK) {
      goto clean;
    }
  } else {
    rcode = obj_value_of(v7, v7_arg(v7, 0), &ro);
    if (rcode != V7_OK) {
      goto clean;
    }
  }

  if (!v7_is_regexp(v7, ro)) {
    rcode = call_regex_ctor(v7, ro, &ro);
    if (rcode != V7_OK) {
      goto clean;
    }
  }

  rxp = v7_get_regexp_struct(v7, ro);
  flag_g = slre_get_flags(rxp->compiled_regexp) & SLRE_FLAG_G;
  if (!flag_g) {
    rcode = rx_exec(v7, ro, so, 0, res);
    goto clean;
  }

  rxp->lastIndex = 0;
  *res = v7_mk_dense_array(v7);
  while (lastMatch) {
    val_t result;

    rcode = rx_exec(v7, ro, so, 1, &result);
    if (rcode != V7_OK) {
      goto clean;
    }

    if (v7_is_null(result)) {
      lastMatch = 0;
    } else {
      long thisIndex = rxp->lastIndex;
      if (thisIndex == previousLastIndex) {
        previousLastIndex = thisIndex + 1;
        rxp->lastIndex = previousLastIndex;
      } else {
        previousLastIndex = thisIndex;
      }
      rcode =
          v7_array_push_throwing(v7, *res, v7_array_get(v7, result, 0), NULL);
      if (rcode != V7_OK) {
        goto clean;
      }
      n++;
    }
  }

  if (n == 0) {
    *res = V7_NULL;
    goto clean;
  }

clean:
  return rcode;
}

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Str_replace(struct v7 *v7, v7_val_t *res) {
  enum v7_err rcode = V7_OK;
  val_t this_obj = v7_get_this(v7);
  const char *s;
  size_t s_len;
  /*
   * Buffer of temporary strings returned by the replacement funciton.  Will be
   * allocated below if only the replacement is a function.  We need to store
   * each string in a separate `val_t`, because string data of length <= 5 is
   * stored right in `val_t`, so if there's more than one replacement,
   * each subsequent replacement will overwrite the previous one.
   */
  val_t *tmp_str_buf = NULL;
  val_t out_str_o;
  char *old_owned_mbuf_base = v7->owned_strings.buf;
  char *old_owned_mbuf_end = v7->owned_strings.buf + v7->owned_strings.len;

  rcode = to_string(v7, this_obj, &this_obj, NULL, 0, NULL);
  if (rcode != V7_OK) {
    goto clean;
  }

  s = v7_get_string(v7, &this_obj, &s_len);

  if (s_len != 0 && v7_argc(v7) > 1) {
    const char *const str_end = s + s_len;
    char *p = (char *) s;
    uint32_t out_sub_num = 0;
    val_t ro = V7_UNDEFINED, str_func = V7_UNDEFINED;
    struct slre_prog *prog;
    struct slre_cap out_sub[V7_RE_MAX_REPL_SUB], *ptok = out_sub;
    struct slre_loot loot;
    int flag_g;

    rcode = obj_value_of(v7, v7_arg(v7, 0), &ro);
    if (rcode != V7_OK) {
      goto clean;
    }
    rcode = obj_value_of(v7, v7_arg(v7, 1), &str_func);
    if (rcode != V7_OK) {
      goto clean;
    }

    if (!v7_is_regexp(v7, ro)) {
      rcode = call_regex_ctor(v7, ro, &ro);
      if (rcode != V7_OK) {
        goto clean;
      }
    }
    prog = v7_get_regexp_struct(v7, ro)->compiled_regexp;
    flag_g = slre_get_flags(prog) & SLRE_FLAG_G;

    if (!v7_is_callable(v7, str_func)) {
      rcode = to_string(v7, str_func, &str_func, NULL, 0, NULL);
      if (rcode != V7_OK) {
        goto clean;
      }
    }

    do {
      int i;
      if (slre_exec(prog, 0, p, str_end, &loot)) break;
      if (p != loot.caps->start) {
        ptok->start = p;
        ptok->end = loot.caps->start;
        ptok++;
        out_sub_num++;
      }

      if (v7_is_callable(v7, str_func)) { /* replace function */
        const char *rez_str;
        size_t rez_len;
        val_t arr = v7_mk_dense_array(v7);

        for (i = 0; i < loot.num_captures; i++) {
          rcode = v7_array_push_throwing(
              v7, arr, v7_mk_string(v7, loot.caps[i].start,
                                    loot.caps[i].end - loot.caps[i].start, 1),
              NULL);
          if (rcode != V7_OK) {
            goto clean;
          }
        }
        rcode = v7_array_push_throwing(
            v7, arr, v7_mk_number(v7, utfnlen(s, loot.caps[0].start - s)),
            NULL);
        if (rcode != V7_OK) {
          goto clean;
        }

        rcode = v7_array_push_throwing(v7, arr, this_obj, NULL);
        if (rcode != V7_OK) {
          goto clean;
        }

        {
          val_t val = V7_UNDEFINED;

          rcode = b_apply(v7, str_func, this_obj, arr, 0, &val);
          if (rcode != V7_OK) {
            goto clean;
          }

          if (tmp_str_buf == NULL) {
            tmp_str_buf = (val_t *) calloc(sizeof(val_t), V7_RE_MAX_REPL_SUB);
          }

          rcode = to_string(v7, val, &tmp_str_buf[out_sub_num], NULL, 0, NULL);
          if (rcode != V7_OK) {
            goto clean;
          }
        }
        rez_str = v7_get_string(v7, &tmp_str_buf[out_sub_num], &rez_len);
        if (rez_len) {
          ptok->start = rez_str;
          ptok->end = rez_str + rez_len;
          ptok++;
          out_sub_num++;
        }
      } else { /* replace string */
        struct slre_loot newsub;
        size_t f_len;
        const char *f_str = v7_get_string(v7, &str_func, &f_len);
        slre_replace(&loot, s, s_len, f_str, f_len, &newsub);
        for (i = 0; i < newsub.num_captures; i++) {
          ptok->start = newsub.caps[i].start;
          ptok->end = newsub.caps[i].end;
          ptok++;
          out_sub_num++;
        }
      }
      p = (char *) loot.caps[0].end;
    } while (flag_g && p < str_end);
    if (p <= str_end) {
      ptok->start = p;
      ptok->end = str_end;
      ptok++;
      out_sub_num++;
    }
    out_str_o = v7_mk_string(v7, NULL, 0, 1);
    ptok = out_sub;
    do {
      size_t ln = ptok->end - ptok->start;
      const char *ps = ptok->start;
      if (ptok->start >= old_owned_mbuf_base &&
          ptok->start < old_owned_mbuf_end) {
        ps += v7->owned_strings.buf - old_owned_mbuf_base;
      }
      out_str_o = s_concat(v7, out_str_o, v7_mk_string(v7, ps, ln, 1));
      p += ln;
      ptok++;
    } while (--out_sub_num);

    *res = out_str_o;
    goto clean;
  }

  *res = this_obj;

clean:
  if (tmp_str_buf != NULL) {
    free(tmp_str_buf);
    tmp_str_buf = NULL;
  }
  return rcode;
}

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Str_search(struct v7 *v7, v7_val_t *res) {
  enum v7_err rcode = V7_OK;
  val_t this_obj = v7_get_this(v7);
  long utf_shift = -1;

  if (v7_argc(v7) > 0) {
    size_t s_len;
    struct slre_loot sub;
    val_t so = V7_UNDEFINED, ro = V7_UNDEFINED;
    const char *s;

    rcode = obj_value_of(v7, v7_arg(v7, 0), &ro);
    if (rcode != V7_OK) {
      goto clean;
    }

    if (!v7_is_regexp(v7, ro)) {
      rcode = call_regex_ctor(v7, ro, &ro);
      if (rcode != V7_OK) {
        goto clean;
      }
    }

    rcode = to_string(v7, this_obj, &so, NULL, 0, NULL);
    if (rcode != V7_OK) {
      goto clean;
    }

    s = v7_get_string(v7, &so, &s_len);

    if (!slre_exec(v7_get_regexp_struct(v7, ro)->compiled_regexp, 0, s,
                   s + s_len, &sub))
      utf_shift = utfnlen(s, sub.caps[0].start - s); /* calc shift for UTF-8 */
  } else {
    utf_shift = 0;
  }

  *res = v7_mk_number(v7, utf_shift);

clean:
  return rcode;
}

#endif /* V7_ENABLE__RegExp */

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Str_slice(struct v7 *v7, v7_val_t *res) {
  enum v7_err rcode = V7_OK;
  val_t this_obj = v7_get_this(v7);
  long from = 0, to = 0;
  size_t len;
  val_t so = V7_UNDEFINED;
  const char *begin, *end;
  int num_args = v7_argc(v7);

  rcode = to_string(v7, this_obj, &so, NULL, 0, NULL);
  if (rcode != V7_OK) {
    goto clean;
  }

  begin = v7_get_string(v7, &so, &len);

  to = len = utfnlen(begin, len);
  if (num_args > 0) {
    rcode = to_long(v7, v7_arg(v7, 0), 0, &from);
    if (rcode != V7_OK) {
      goto clean;
    }

    if (from < 0) {
      from += len;
      if (from < 0) from = 0;
    } else if ((size_t) from > len)
      from = len;
    if (num_args > 1) {
      rcode = to_long(v7, v7_arg(v7, 1), 0, &to);
      if (rcode != V7_OK) {
        goto clean;
      }

      if (to < 0) {
        to += len;
        if (to < 0) to = 0;
      } else if ((size_t) to > len)
        to = len;
    }
  }

  if (from > to) to = from;
  end = utfnshift(begin, to);
  begin = utfnshift(begin, from);

  *res = v7_mk_string(v7, begin, end - begin, 1);

clean:
  return rcode;
}

WARN_UNUSED_RESULT
static enum v7_err s_transform(struct v7 *v7, val_t obj, Rune (*func)(Rune),
                               val_t *res) {
  enum v7_err rcode = V7_OK;
  val_t s = V7_UNDEFINED;
  size_t i, n, len;
  const char *p2, *p;

  rcode = to_string(v7, obj, &s, NULL, 0, NULL);
  if (rcode != V7_OK) {
    goto clean;
  }

  p = v7_get_string(v7, &s, &len);

  /* Pass NULL to make sure we're not creating dictionary value */
  *res = v7_mk_string(v7, NULL, len, 1);

  {
    Rune r;
    p2 = v7_get_string(v7, res, &len);
    for (i = 0; i < len; i += n) {
      n = chartorune(&r, p + i);
      r = func(r);
      runetochar((char *) p2 + i, &r);
    }
  }

clean:
  return rcode;
}

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Str_toLowerCase(struct v7 *v7, v7_val_t *res) {
  val_t this_obj = v7_get_this(v7);
  return s_transform(v7, this_obj, tolowerrune, res);
}

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Str_toUpperCase(struct v7 *v7, v7_val_t *res) {
  val_t this_obj = v7_get_this(v7);
  return s_transform(v7, this_obj, toupperrune, res);
}

static int s_isspace(Rune c) {
  return isspacerune(c) || isnewline(c);
}

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Str_trim(struct v7 *v7, v7_val_t *res) {
  enum v7_err rcode = V7_OK;
  val_t this_obj = v7_get_this(v7);
  val_t s = V7_UNDEFINED;
  size_t i, n, len, start = 0, end, state = 0;
  const char *p;
  Rune r;

  rcode = to_string(v7, this_obj, &s, NULL, 0, NULL);
  if (rcode != V7_OK) {
    goto clean;
  }
  p = v7_get_string(v7, &s, &len);

  end = len;
  for (i = 0; i < len; i += n) {
    n = chartorune(&r, p + i);
    if (!s_isspace(r)) {
      if (state++ == 0) start = i;
      end = i + n;
    }
  }

  *res = v7_mk_string(v7, p + start, end - start, 1);

clean:
  return rcode;
}

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Str_length(struct v7 *v7, v7_val_t *res) {
  enum v7_err rcode = V7_OK;
  val_t this_obj = v7_get_this(v7);
  size_t len = 0;
  val_t s = V7_UNDEFINED;

  rcode = obj_value_of(v7, this_obj, &s);
  if (rcode != V7_OK) {
    goto clean;
  }

  if (v7_is_string(s)) {
    const char *p = v7_get_string(v7, &s, &len);
    len = utfnlen(p, len);
  }

  *res = v7_mk_number(v7, len);
clean:
  return rcode;
}

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Str_at(struct v7 *v7, v7_val_t *res) {
  enum v7_err rcode = V7_OK;
  val_t this_obj = v7_get_this(v7);
  long arg0;
  val_t s = V7_UNDEFINED;

  rcode = to_long(v7, v7_arg(v7, 0), -1, &arg0);
  if (rcode != V7_OK) {
    goto clean;
  }

  rcode = obj_value_of(v7, this_obj, &s);
  if (rcode != V7_OK) {
    goto clean;
  }

  if (v7_is_string(s)) {
    size_t n;
    const unsigned char *p = (unsigned char *) v7_get_string(v7, &s, &n);
    if (arg0 >= 0 && (size_t) arg0 < n) {
      *res = v7_mk_number(v7, p[arg0]);
      goto clean;
    }
  }

  *res = v7_mk_number(v7, NAN);

clean:
  return rcode;
}

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Str_blen(struct v7 *v7, v7_val_t *res) {
  enum v7_err rcode = V7_OK;
  val_t this_obj = v7_get_this(v7);
  size_t len = 0;
  val_t s = V7_UNDEFINED;

  rcode = obj_value_of(v7, this_obj, &s);
  if (rcode != V7_OK) {
    goto clean;
  }

  if (v7_is_string(s)) {
    v7_get_string(v7, &s, &len);
  }

  *res = v7_mk_number(v7, len);

clean:
  return rcode;
}

WARN_UNUSED_RESULT
static enum v7_err s_substr(struct v7 *v7, val_t s, long start, long len,
                            val_t *res) {
  enum v7_err rcode = V7_OK;
  size_t n;
  const char *p;

  rcode = to_string(v7, s, &s, NULL, 0, NULL);
  if (rcode != V7_OK) {
    goto clean;
  }

  p = v7_get_string(v7, &s, &n);
  n = utfnlen(p, n);

  if (start < (long) n && len > 0) {
    if (start < 0) start = (long) n + start;
    if (start < 0) start = 0;

    if (start > (long) n) start = n;
    if (len < 0) len = 0;
    if (len > (long) n - start) len = n - start;
    p = utfnshift(p, start);
  } else {
    len = 0;
  }

  *res = v7_mk_string(v7, p, len, 1);

clean:
  return rcode;
}

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Str_substr(struct v7 *v7, v7_val_t *res) {
  enum v7_err rcode = V7_OK;
  val_t this_obj = v7_get_this(v7);
  long start, len;

  rcode = to_long(v7, v7_arg(v7, 0), 0, &start);
  if (rcode != V7_OK) {
    goto clean;
  }

  rcode = to_long(v7, v7_arg(v7, 1), LONG_MAX, &len);
  if (rcode != V7_OK) {
    goto clean;
  }

  rcode = s_substr(v7, this_obj, start, len, res);
  if (rcode != V7_OK) {
    goto clean;
  }

clean:
  return rcode;
}

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Str_substring(struct v7 *v7, v7_val_t *res) {
  enum v7_err rcode = V7_OK;
  val_t this_obj = v7_get_this(v7);
  long start, end;

  rcode = to_long(v7, v7_arg(v7, 0), 0, &start);
  if (rcode != V7_OK) {
    goto clean;
  }

  rcode = to_long(v7, v7_arg(v7, 1), LONG_MAX, &end);
  if (rcode != V7_OK) {
    goto clean;
  }

  if (start < 0) start = 0;
  if (end < 0) end = 0;
  if (start > end) {
    long tmp = start;
    start = end;
    end = tmp;
  }

  rcode = s_substr(v7, this_obj, start, end - start, res);
  goto clean;

clean:
  return rcode;
}

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Str_split(struct v7 *v7, v7_val_t *res) {
  enum v7_err rcode = V7_OK;
  val_t this_obj = v7_get_this(v7);
  const char *s, *s_end;
  size_t s_len;
  long num_args = v7_argc(v7);
  rcode = to_string(v7, this_obj, &this_obj, NULL, 0, NULL);
  if (rcode != V7_OK) {
    goto clean;
  }
  s = v7_get_string(v7, &this_obj, &s_len);
  s_end = s + s_len;

  *res = v7_mk_dense_array(v7);

  if (num_args == 0) {
    /*
     * No arguments were given: resulting array will contain just a single
     * element: the source string
     */
    rcode = v7_array_push_throwing(v7, *res, this_obj, NULL);
    if (rcode != V7_OK) {
      goto clean;
    }
  } else {
    val_t ro = V7_UNDEFINED;
    long elem, limit;
    size_t lookup_idx = 0, substr_idx = 0;
    struct _str_split_ctx ctx;

    rcode = to_long(v7, v7_arg(v7, 1), LONG_MAX, &limit);
    if (rcode != V7_OK) {
      goto clean;
    }

    rcode = obj_value_of(v7, v7_arg(v7, 0), &ro);
    if (rcode != V7_OK) {
      goto clean;
    }

    /* Initialize substring context depending on the argument type */
    if (v7_is_regexp(v7, ro)) {
/* use RegExp implementation */
#if V7_ENABLE__RegExp
      ctx.p_init = subs_regexp_init;
      ctx.p_exec = subs_regexp_exec;
      ctx.p_add_caps = subs_regexp_split_add_caps;
#else
      assert(0);
#endif
    } else {
      /*
       * use String implementation: first of all, convert to String (if it's
       * not already a String)
       */
      rcode = to_string(v7, ro, &ro, NULL, 0, NULL);
      if (rcode != V7_OK) {
        goto clean;
      }

      ctx.p_init = subs_string_init;
      ctx.p_exec = subs_string_exec;
#if V7_ENABLE__RegExp
      ctx.p_add_caps = subs_string_split_add_caps;
#endif
    }
    /* initialize context */
    ctx.p_init(&ctx, v7, ro);

    if (s_len == 0) {
      /*
       * if `this` is (or converts to) an empty string, resulting array should
       * contain empty string if only separator does not match an empty string.
       * Otherwise, the array is left empty
       */
      int matches_empty = !ctx.p_exec(&ctx, s, s);
      if (!matches_empty) {
        rcode = v7_array_push_throwing(v7, *res, this_obj, NULL);
        if (rcode != V7_OK) {
          goto clean;
        }
      }
    } else {
      size_t last_match_len = 0;

      for (elem = 0; elem < limit && lookup_idx < s_len;) {
        size_t substr_len;
        /* find next match, and break if there's no match */
        if (ctx.p_exec(&ctx, s + lookup_idx, s_end)) break;

        last_match_len = ctx.match_end - ctx.match_start;
        substr_len = ctx.match_start - s - substr_idx;

        /* add next substring to the resulting array, if needed */
        if (substr_len > 0 || last_match_len > 0) {
          rcode = v7_array_push_throwing(
              v7, *res, v7_mk_string(v7, s + substr_idx, substr_len, 1), NULL);
          if (rcode != V7_OK) {
            goto clean;
          }
          elem++;

#if V7_ENABLE__RegExp
          /* Add captures (for RegExp only) */
          elem = ctx.p_add_caps(&ctx, *res, elem, limit);
#endif
        }

        /* advance lookup_idx appropriately */
        if (last_match_len == 0) {
          /* empty match: advance to the next char */
          const char *next = utfnshift((s + lookup_idx), 1);
          lookup_idx += (next - (s + lookup_idx));
        } else {
          /* non-empty match: advance to the end of match */
          lookup_idx = ctx.match_end - s;
        }

        /*
         * always remember the end of the match, so that next time we will take
         * substring from that position
         */
        substr_idx = ctx.match_end - s;
      }

      /* add the last substring to the resulting array, if needed */
      if (elem < limit) {
        size_t substr_len = s_len - substr_idx;
        if (substr_len > 0 || last_match_len > 0) {
          rcode = v7_array_push_throwing(
              v7, *res, v7_mk_string(v7, s + substr_idx, substr_len, 1), NULL);
          if (rcode != V7_OK) {
            goto clean;
          }
        }
      }
    }
  }

clean:
  return rcode;
}

V7_PRIVATE void init_string(struct v7 *v7) {
  val_t str = mk_cfunction_obj_with_proto(v7, String_ctor, 1,
                                          v7->vals.string_prototype);
  v7_def(v7, v7->vals.global_object, "String", 6, V7_DESC_ENUMERABLE(0), str);

  set_cfunc_prop(v7, str, "fromCharCode", Str_fromCharCode);
  set_cfunc_prop(v7, v7->vals.string_prototype, "charCodeAt", Str_charCodeAt);
  set_cfunc_prop(v7, v7->vals.string_prototype, "charAt", Str_charAt);
  set_cfunc_prop(v7, v7->vals.string_prototype, "concat", Str_concat);
  set_cfunc_prop(v7, v7->vals.string_prototype, "indexOf", Str_indexOf);
  set_cfunc_prop(v7, v7->vals.string_prototype, "substr", Str_substr);
  set_cfunc_prop(v7, v7->vals.string_prototype, "substring", Str_substring);
  set_cfunc_prop(v7, v7->vals.string_prototype, "valueOf", Str_valueOf);
  set_cfunc_prop(v7, v7->vals.string_prototype, "lastIndexOf", Str_lastIndexOf);
#if V7_ENABLE__String__localeCompare
  set_cfunc_prop(v7, v7->vals.string_prototype, "localeCompare",
                 Str_localeCompare);
#endif
#if V7_ENABLE__RegExp
  set_cfunc_prop(v7, v7->vals.string_prototype, "match", Str_match);
  set_cfunc_prop(v7, v7->vals.string_prototype, "replace", Str_replace);
  set_cfunc_prop(v7, v7->vals.string_prototype, "search", Str_search);
#endif
  set_cfunc_prop(v7, v7->vals.string_prototype, "split", Str_split);
  set_cfunc_prop(v7, v7->vals.string_prototype, "slice", Str_slice);
  set_cfunc_prop(v7, v7->vals.string_prototype, "trim", Str_trim);
  set_cfunc_prop(v7, v7->vals.string_prototype, "toLowerCase", Str_toLowerCase);
#if V7_ENABLE__String__localeLowerCase
  set_cfunc_prop(v7, v7->vals.string_prototype, "toLocaleLowerCase",
                 Str_toLowerCase);
#endif
  set_cfunc_prop(v7, v7->vals.string_prototype, "toUpperCase", Str_toUpperCase);
#if V7_ENABLE__String__localeUpperCase
  set_cfunc_prop(v7, v7->vals.string_prototype, "toLocaleUpperCase",
                 Str_toUpperCase);
#endif
  set_cfunc_prop(v7, v7->vals.string_prototype, "toString", Str_toString);

  v7_def(v7, v7->vals.string_prototype, "length", 6, V7_DESC_GETTER(1),
         v7_mk_cfunction(Str_length));

  /* Non-standard Cesanta extension */
  set_cfunc_prop(v7, v7->vals.string_prototype, "at", Str_at);
  v7_def(v7, v7->vals.string_prototype, "blen", 4, V7_DESC_GETTER(1),
         v7_mk_cfunction(Str_blen));
}
#ifdef V7_MODULE_LINES
#line 1 "v7/src/std_date.c"
#endif
/*
 * Copyright (c) 2015 Cesanta Software Limited
 * All rights reserved
 */

/* Amalgamated: #include "v7/src/internal.h" */
/* Amalgamated: #include "common/str_util.h" */
/* Amalgamated: #include "v7/src/std_object.h" */
/* Amalgamated: #include "v7/src/core.h" */
/* Amalgamated: #include "v7/src/util.h" */
/* Amalgamated: #include "v7/src/function.h" */
/* Amalgamated: #include "v7/src/object.h" */
/* Amalgamated: #include "v7/src/conversion.h" */
/* Amalgamated: #include "v7/src/exceptions.h" */
/* Amalgamated: #include "v7/src/primitive.h" */
/* Amalgamated: #include "v7/src/string.h" */

#if V7_ENABLE__Date

#include <locale.h>
#include <time.h>

#ifndef _WIN32
extern long timezone;
#include <sys/time.h>
#endif

#if defined(_MSC_VER)
#define timezone _timezone
#define tzname _tzname
#if _MSC_VER >= 1800
#define tzset _tzset
#endif
#endif

#if defined(__cplusplus)
extern "C" {
#endif /* __cplusplus */

typedef double etime_t; /* double is suitable type for ECMA time */
/* inernally we have to use 64-bit integer for some operations */
typedef int64_t etimeint_t;
#define INVALID_TIME NAN

#define msPerDay 86400000
#define HoursPerDay 24
#define MinutesPerHour 60
#define SecondsPerMinute 60
#define SecondsPerHour 3600
#define msPerSecond 1000
#define msPerMinute 60000
#define msPerHour 3600000
#define MonthsInYear 12

/* ECMA alternative to struct tm */
struct timeparts {
  int year;  /* can be negative, up to +-282000 */
  int month; /* 0-11 */
  int day;   /* 1-31 */
  int hour;  /* 0-23 */
  int min;   /* 0-59 */
  int sec;   /* 0-59 */
  int msec;
  int dayofweek; /* 0-6 */
};

static etimeint_t g_gmtoffms; /* timezone offset, ms, no DST */
static const char *g_tzname;  /* current timezone name */

/* Leap year formula copied from ECMA 5.1 standart as is */
static int ecma_DaysInYear(int y) {
  if (y % 4 != 0) {
    return 365;
  } else if (y % 4 == 0 && y % 100 != 0) {
    return 366;
  } else if (y % 100 == 0 && y % 400 != 0) {
    return 365;
  } else if (y % 400 == 0) {
    return 366;
  } else {
    return 365;
  }
}

static etimeint_t ecma_DayFromYear(etimeint_t y) {
  return 365 * (y - 1970) + floor((y - 1969) / 4) - floor((y - 1901) / 100) +
         floor((y - 1601) / 400);
}

static etimeint_t ecma_TimeFromYear(etimeint_t y) {
  return msPerDay * ecma_DayFromYear(y);
}

static int ecma_IsLeapYear(int year) {
  return ecma_DaysInYear(year) == 366;
}

static int *ecma_getfirstdays(int isleap) {
  static int sdays[2][MonthsInYear + 1] = {
      {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365},
      {0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366}};

  return sdays[isleap];
}

static int ecma_DaylightSavingTA(etime_t t) {
  time_t time = t / 1000;
  /*
   * Win32 doesn't have locatime_r
   * nixes don't have localtime_s
   * as result using localtime
   */
  struct tm *tm = localtime(&time);
  if (tm == NULL) {
    /* doesn't work on windows for times before epoch */
    return 0;
  }
  if (tm->tm_isdst > 0) {
    return msPerHour;
  } else {
    return 0;
  }
}

static int ecma_LocalTZA(void) {
  return (int) -g_gmtoffms;
}

static etimeint_t ecma_UTC(etime_t t) {
  return t - ecma_LocalTZA() - ecma_DaylightSavingTA(t - ecma_LocalTZA());
}

#if V7_ENABLE__Date__toString || V7_ENABLE__Date__toLocaleString || \
    V7_ENABLE__Date__toJSON || V7_ENABLE__Date__getters ||          \
    V7_ENABLE__Date__setters
static int ecma_YearFromTime_s(etime_t t) {
  int first = floor((t / msPerDay) / 366) + 1970,
      last = floor((t / msPerDay) / 365) + 1970, middle = 0;

  if (last < first) {
    int temp = first;
    first = last;
    last = temp;
  }

  while (last > first) {
    middle = (last + first) / 2;
    if (ecma_TimeFromYear(middle) > t) {
      last = middle - 1;
    } else {
      if (ecma_TimeFromYear(middle) <= t) {
        if (ecma_TimeFromYear(middle + 1) > t) {
          first = middle;
          break;
        }
        first = middle + 1;
      }
    }
  }

  return first;
}

static etimeint_t ecma_Day(etime_t t) {
  return floor(t / msPerDay);
}

static int ecma_DayWithinYear(etime_t t, int year) {
  return (int) (ecma_Day(t) - ecma_DayFromYear(year));
}

static int ecma_MonthFromTime(etime_t t, int year) {
  int *days, i;
  etimeint_t dwy = ecma_DayWithinYear(t, year);

  days = ecma_getfirstdays(ecma_IsLeapYear(year));

  for (i = 0; i < MonthsInYear; i++) {
    if (dwy >= days[i] && dwy < days[i + 1]) {
      return i;
    }
  }

  return -1;
}

static int ecma_DateFromTime(etime_t t, int year) {
  int *days, mft = ecma_MonthFromTime(t, year),
             dwy = ecma_DayWithinYear(t, year);

  if (mft > 11) {
    return -1;
  }

  days = ecma_getfirstdays(ecma_IsLeapYear(year));

  return dwy - days[mft] + 1;
}

#define DEF_EXTRACT_TIMEPART(funcname, c1, c2) \
  static int ecma_##funcname(etime_t t) {      \
    int ret = (etimeint_t) floor(t / c1) % c2; \
    if (ret < 0) {                             \
      ret += c2;                               \
    }                                          \
    return ret;                                \
  }

DEF_EXTRACT_TIMEPART(HourFromTime, msPerHour, HoursPerDay)
DEF_EXTRACT_TIMEPART(MinFromTime, msPerMinute, MinutesPerHour)
DEF_EXTRACT_TIMEPART(SecFromTime, msPerSecond, SecondsPerMinute)
DEF_EXTRACT_TIMEPART(msFromTime, 1, msPerSecond)

static int ecma_WeekDay(etime_t t) {
  int ret = (ecma_Day(t) + 4) % 7;
  if (ret < 0) {
    ret += 7;
  }

  return ret;
}

static void d_gmtime(const etime_t *t, struct timeparts *tp) {
  tp->year = ecma_YearFromTime_s(*t);
  tp->month = ecma_MonthFromTime(*t, tp->year);
  tp->day = ecma_DateFromTime(*t, tp->year);
  tp->hour = ecma_HourFromTime(*t);
  tp->min = ecma_MinFromTime(*t);
  tp->sec = ecma_SecFromTime(*t);
  tp->msec = ecma_msFromTime(*t);
  tp->dayofweek = ecma_WeekDay(*t);
}
#endif /* V7_ENABLE__Date__toString || V7_ENABLE__Date__toLocaleString || \
          V7_ENABLE__Date__getters || V7_ENABLE__Date__setters */

#if V7_ENABLE__Date__toString || V7_ENABLE__Date__toLocaleString || \
    V7_ENABLE__Date__getters || V7_ENABLE__Date__setters
static etimeint_t ecma_LocalTime(etime_t t) {
  return t + ecma_LocalTZA() + ecma_DaylightSavingTA(t);
}

static void d_localtime(const etime_t *time, struct timeparts *tp) {
  etime_t local_time = ecma_LocalTime(*time);
  d_gmtime(&local_time, tp);
}
#endif

static etimeint_t ecma_MakeTime(etimeint_t hour, etimeint_t min, etimeint_t sec,
                                etimeint_t ms) {
  return ((hour * MinutesPerHour + min) * SecondsPerMinute + sec) *
             msPerSecond +
         ms;
}

static etimeint_t ecma_MakeDay(int year, int month, int date) {
  int *days;
  etimeint_t yday, mday;

  year += floor(month / 12);
  month = month % 12;
  yday = floor(ecma_TimeFromYear(year) / msPerDay);
  days = ecma_getfirstdays(ecma_IsLeapYear(year));
  mday = days[month];

  return yday + mday + date - 1;
}

static etimeint_t ecma_MakeDate(etimeint_t day, etimeint_t time) {
  return (day * msPerDay + time);
}

static void d_gettime(etime_t *t) {
#ifndef _WIN32
  struct timeval tv;
  gettimeofday(&tv, NULL);
  *t = (etime_t) tv.tv_sec * 1000 + (etime_t) tv.tv_usec / 1000;
#else
  /* TODO(mkm): use native windows API in order to get ms granularity */
  *t = time(NULL) * 1000.0;
#endif
}

static etime_t d_mktime_impl(const struct timeparts *tp) {
  return ecma_MakeDate(ecma_MakeDay(tp->year, tp->month, tp->day),
                       ecma_MakeTime(tp->hour, tp->min, tp->sec, tp->msec));
}

#if V7_ENABLE__Date__setters
static etime_t d_lmktime(const struct timeparts *tp) {
  return ecma_UTC(d_mktime_impl(tp));
}
#endif

static etime_t d_gmktime(const struct timeparts *tp) {
  return d_mktime_impl(tp);
}

typedef etime_t (*fmaketime_t)(const struct timeparts *);
typedef void (*fbreaktime_t)(const etime_t *, struct timeparts *);

#if V7_ENABLE__Date__toString || V7_ENABLE__Date__toLocaleString || \
    V7_ENABLE__Date__toJSON
static val_t d_trytogetobjforstring(struct v7 *v7, val_t obj) {
  enum v7_err rcode = V7_OK;
  val_t ret = V7_UNDEFINED;

  rcode = obj_value_of(v7, obj, &ret);
  if (rcode != V7_OK) {
    goto clean;
  }

  if (ret == V7_TAG_NAN) {
    rcode = v7_throwf(v7, TYPE_ERROR, "Date is invalid (for string)");
    goto clean;
  }

clean:
  (void) rcode;
  return ret;
}
#endif

#if V7_ENABLE__Date__parse || V7_ENABLE__Date__UTC
static int d_iscalledasfunction(struct v7 *v7, val_t this_obj) {
  /* TODO(alashkin): verify this statement */
  return is_prototype_of(v7, this_obj, v7->vals.date_prototype);
}
#endif

static const char *mon_name[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
                                 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};

int d_getnumbyname(const char **arr, int arr_size, const char *str) {
  int i;
  for (i = 0; i < arr_size; i++) {
    if (strncmp(arr[i], str, 3) == 0) {
      return i + 1;
    }
  }

  return -1;
}

int date_parse(const char *str, int *a1, int *a2, int *a3, char sep,
               char *rest) {
  char frmDate[] = " %d/%d/%d%[^\0]";
  frmDate[3] = frmDate[6] = sep;
  return sscanf(str, frmDate, a1, a2, a3, rest);
}

#define NO_TZ 0x7FFFFFFF

/*
 * not very smart but simple, and working according
 * to ECMA5.1 StringToDate function
 */
static int d_parsedatestr(const char *jstr, size_t len, struct timeparts *tp,
                          int *tz) {
  char gmt[4];
  char buf1[100] = {0}, buf2[100] = {0};
  int res = 0;
  char str[101];
  memcpy(str, jstr, len);
  str[len] = '\0';
  memset(tp, 0, sizeof(*tp));
  *tz = NO_TZ;

  /* trying toISOSrting() format */
  {
    const char *frmISOString = " %d-%02d-%02dT%02d:%02d:%02d.%03dZ";
    res = sscanf(str, frmISOString, &tp->year, &tp->month, &tp->day, &tp->hour,
                 &tp->min, &tp->sec, &tp->msec);
    if (res == 7) {
      *tz = 0;
      return 1;
    }
  }

  /* trying toString()/toUTCString()/toDateFormat() formats */
  {
    char month[4];
    int dowlen;
    const char *frmString = " %*s%n %03s %02d %d %02d:%02d:%02d %03s%d";
    res = sscanf(str, frmString, &dowlen, month, &tp->day, &tp->year, &tp->hour,
                 &tp->min, &tp->sec, gmt, tz);
    if ((res == 3 || (res >= 6 && res <= 8)) && dowlen == 3) {
      if ((tp->month = d_getnumbyname(mon_name, ARRAY_SIZE(mon_name), month)) !=
          -1) {
        if (res == 7 && strncmp(gmt, "GMT", 3) == 0) {
          *tz = 0;
        }
        return 1;
      }
    }
  }

  /* trying the rest */

  /* trying date */

  if (!(date_parse(str, &tp->month, &tp->day, &tp->year, '/', buf1) >= 3 ||
        date_parse(str, &tp->day, &tp->month, &tp->year, '.', buf1) >= 3 ||
        date_parse(str, &tp->year, &tp->month, &tp->day, '-', buf1) >= 3)) {
    return 0;
  }

  /*  there is date, trying time; from here return 0 only on errors */

  /* trying HH:mm */
  {
    const char *frmMMhh = " %d:%d%[^\0]";
    res = sscanf(buf1, frmMMhh, &tp->hour, &tp->min, buf2);
    /* can't get time, but have some symbols, assuming error */
    if (res < 2) {
      return (strlen(buf2) == 0);
    }
  }

  /* trying seconds */
  {
    const char *frmss = ":%d%[^\0]";
    memset(buf1, 0, sizeof(buf1));
    res = sscanf(buf2, frmss, &tp->sec, buf1);
  }

  /* even if we don't get seconds we gonna try to get tz */
  {
    char *rest = res ? buf1 : buf2;
    char *buf = res ? buf2 : buf1;
    const char *frmtz = " %03s%d%[^\0]";

    res = sscanf(rest, frmtz, gmt, tz, buf);
    if (res == 1 && strncmp(gmt, "GMT", 3) == 0) {
      *tz = 0;
    }
  }

  /* return OK if we are at the end of string */
  return res <= 2;
}

static int d_timeFromString(etime_t *time, const char *str, size_t str_len) {
  struct timeparts tp;
  int tz;

  *time = INVALID_TIME;

  if (str_len > 100) {
    /* too long for valid date string */
    return 0;
  }

  if (d_parsedatestr(str, str_len, &tp, &tz)) {
    /* check results */
    int valid = 0;

    tp.month--;
    valid = tp.day >= 1 && tp.day <= 31;
    valid &= tp.month >= 0 && tp.month <= 11;
    valid &= tp.hour >= 0 && tp.hour <= 23;
    valid &= tp.min >= 0 && tp.min <= 59;
    valid &= tp.sec >= 0 && tp.sec <= 59;

    if (tz != NO_TZ && tz > 12) {
      tz /= 100;
    }

    valid &= (abs(tz) <= 12 || tz == NO_TZ);

    if (valid) {
      *time = d_gmktime(&tp);

      if (tz != NO_TZ) {
        /* timezone specified, use it */
        *time -= (tz * msPerHour);
      } else if (tz != 0) {
        /* assuming local timezone and moving back to UTC */
        *time = ecma_UTC(*time);
      }
    }
  }

  return !isnan(*time);
}

/* notice: holding month in calendar format (1-12, not 0-11) */
struct dtimepartsarr {
  etime_t args[7];
};

enum detimepartsarr {
  tpyear = 0,
  tpmonth,
  tpdate,
  tphours,
  tpminutes,
  tpseconds,
  tpmsec,
  tpmax
};

static etime_t d_changepartoftime(const etime_t *current,
                                  struct dtimepartsarr *a,
                                  fbreaktime_t breaktimefunc,
                                  fmaketime_t maketimefunc) {
  /*
   * 0 = year, 1 = month , 2 = date , 3 = hours,
   * 4 = minutes, 5 = seconds, 6 = ms
   */
  struct timeparts tp;
  unsigned long i;
  int *tp_arr[7];
  /*
   * C89 doesn't allow initialization
   * like x = {&tp.year, &tp.month, .... }
   */
  tp_arr[0] = &tp.year;
  tp_arr[1] = &tp.month;
  tp_arr[2] = &tp.day;
  tp_arr[3] = &tp.hour;
  tp_arr[4] = &tp.min;
  tp_arr[5] = &tp.sec;
  tp_arr[6] = &tp.msec;

  memset(&tp, 0, sizeof(tp));

  if (breaktimefunc != NULL) {
    breaktimefunc(current, &tp);
  }

  for (i = 0; i < ARRAY_SIZE(tp_arr); i++) {
    if (!isnan(a->args[i]) && !isinf(a->args[i])) {
      *tp_arr[i] = (int) a->args[i];
    }
  }

  return maketimefunc(&tp);
}

#if V7_ENABLE__Date__setters || V7_ENABLE__Date__UTC
static etime_t d_time_number_from_arr(struct v7 *v7, int start_pos,
                                      fbreaktime_t breaktimefunc,
                                      fmaketime_t maketimefunc) {
  enum v7_err rcode = V7_OK;
  val_t this_obj = v7_get_this(v7);
  etime_t ret_time = INVALID_TIME;
  long cargs;

  val_t objtime = V7_UNDEFINED;
  rcode = obj_value_of(v7, this_obj, &objtime);
  if (rcode != V7_OK) {
    goto clean;
  }

  if ((cargs = v7_argc(v7)) >= 1 && objtime != V7_TAG_NAN) {
    int i;
    etime_t new_part = INVALID_TIME;
    struct dtimepartsarr a;
    for (i = 0; i < 7; i++) {
      a.args[i] = INVALID_TIME;
    }

    for (i = 0; i < cargs && (i + start_pos < tpmax); i++) {
      {
        val_t arg = v7_arg(v7, i);
        rcode = to_number_v(v7, arg, &arg);
        if (rcode != V7_OK) {
          goto clean;
        }
        new_part = v7_get_double(v7, arg);
      }

      if (isnan(new_part)) {
        break;
      }

      a.args[i + start_pos] = new_part;
    }

    if (!isnan(new_part)) {
      etime_t current_time = v7_get_double(v7, objtime);
      ret_time =
          d_changepartoftime(&current_time, &a, breaktimefunc, maketimefunc);
    }
  }

clean:
  (void) rcode;
  return ret_time;
}
#endif /* V7_ENABLE__Date__setters */

#if V7_ENABLE__Date__toString
static int d_tptostr(const struct timeparts *tp, char *buf, int addtz);
#endif

/* constructor */
WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Date_ctor(struct v7 *v7, v7_val_t *res) {
  enum v7_err rcode = V7_OK;
  val_t this_obj = v7_get_this(v7);
  etime_t ret_time = INVALID_TIME;
  if (v7_is_generic_object(this_obj) && this_obj != v7->vals.global_object) {
    long cargs = v7_argc(v7);
    if (cargs <= 0) {
      /* no parameters - return current date & time */
      d_gettime(&ret_time);
    } else if (cargs == 1) {
      /* one parameter */
      val_t arg = v7_arg(v7, 0);
      if (v7_is_string(arg)) { /* it could be string */
        size_t str_size;
        const char *str = v7_get_string(v7, &arg, &str_size);
        d_timeFromString(&ret_time, str, str_size);
      }
      if (isnan(ret_time)) {
        /*
         * if cannot be parsed or
         * not string at all - trying to convert to number
         */
        ret_time = 0;
        rcode = to_number_v(v7, arg, &arg);
        if (rcode != V7_OK) {
          goto clean;
        }
        ret_time = v7_get_double(v7, arg);
        if (rcode != V7_OK) {
          goto clean;
        }
      }
    } else {
      /* 2+ paramaters - should be parts of a date */
      struct dtimepartsarr a;
      int i;

      memset(&a, 0, sizeof(a));

      for (i = 0; i < cargs; i++) {
        val_t arg = v7_arg(v7, i);
        rcode = to_number_v(v7, arg, &arg);
        if (rcode != V7_OK) {
          goto clean;
        }
        a.args[i] = v7_get_double(v7, arg);
        if (isnan(a.args[i])) {
          break;
        }
      }

      if (i >= cargs) {
        /*
         * If date is supplied then let
         * dt be ToNumber(date); else let dt be 1.
         */
        if (a.args[tpdate] == 0) {
          a.args[tpdate] = 1;
        }

        if (a.args[tpyear] >= 0 && a.args[tpyear] <= 99) {
          /*
           * If y is not NaN and 0 <= ToInteger(y) <= 99,
           * then let yr be 1900+ToInteger(y); otherwise, let yr be y.
           */
          a.args[tpyear] += 1900;
        }

        ret_time = ecma_UTC(d_changepartoftime(0, &a, 0, d_gmktime));
      }
    }

    obj_prototype_set(v7, get_object_struct(this_obj),
                      get_object_struct(v7->vals.date_prototype));

    v7_def(v7, this_obj, "", 0, _V7_DESC_HIDDEN(1), v7_mk_number(v7, ret_time));
    /*
     * implicitly returning `this`: `call_cfunction()` in bcode.c will do
     * that for us
     */
    goto clean;
  } else {
    /*
     * according to 15.9.2.1 we should ignore all
     * parameters in case of function-call
     */
    char buf[50];
    int len;

#if V7_ENABLE__Date__toString
    struct timeparts tp;
    d_gettime(&ret_time);
    d_localtime(&ret_time, &tp);
    len = d_tptostr(&tp, buf, 1);
#else
    len = 0;
#endif /* V7_ENABLE__Date__toString */

    *res = v7_mk_string(v7, buf, len, 1);
    goto clean;
  }

clean:
  return rcode;
}

#if V7_ENABLE__Date__toString || V7_ENABLE__Date__toJSON
static int d_timetoISOstr(const etime_t *time, char *buf, size_t buf_size) {
  /* ISO format: "+XXYYYY-MM-DDTHH:mm:ss.sssZ"; */
  struct timeparts tp;
  char use_ext = 0;
  const char *ey_frm = "%06d-%02d-%02dT%02d:%02d:%02d.%03dZ";
  const char *simpl_frm = "%d-%02d-%02dT%02d:%02d:%02d.%03dZ";

  d_gmtime(time, &tp);

  if (abs(tp.year) > 9999 || tp.year < 0) {
    *buf = (tp.year > 0) ? '+' : '-';
    use_ext = 1;
  }

  return c_snprintf(buf + use_ext, buf_size - use_ext,
                    use_ext ? ey_frm : simpl_frm, abs(tp.year), tp.month + 1,
                    tp.day, tp.hour, tp.min, tp.sec, tp.msec) +
         use_ext;
}

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Date_toISOString(struct v7 *v7, v7_val_t *res) {
  enum v7_err rcode = V7_OK;
  val_t this_obj = v7_get_this(v7);
  char buf[30];
  etime_t time;
  int len;

  if (val_type(v7, this_obj) != V7_TYPE_DATE_OBJECT) {
    rcode = v7_throwf(v7, TYPE_ERROR, "This is not a Date object");
    goto clean;
  }

  time = v7_get_double(v7, d_trytogetobjforstring(v7, this_obj));
  len = d_timetoISOstr(&time, buf, sizeof(buf));
  if (len > (int) (sizeof(buf) - 1 /*null-term*/)) {
    len = (int) (sizeof(buf) - 1 /*null-term*/);
  }

  *res = v7_mk_string(v7, buf, len, 1);

clean:
  return rcode;
}
#endif /* V7_ENABLE__Date__toString || V7_ENABLE__Date__toJSON */

#if V7_ENABLE__Date__toString
typedef int (*ftostring_t)(const struct timeparts *, char *, int);

WARN_UNUSED_RESULT
static enum v7_err d_tostring(struct v7 *v7, val_t obj,
                              fbreaktime_t breaktimefunc,
                              ftostring_t tostringfunc, int addtz,
                              v7_val_t *res) {
  enum v7_err rcode = V7_OK;
  struct timeparts tp;
  int len;
  char buf[100];
  etime_t time;

  time = v7_get_double(v7, d_trytogetobjforstring(v7, obj));

  breaktimefunc(&time, &tp);
  len = tostringfunc(&tp, buf, addtz);

  *res = v7_mk_string(v7, buf, len, 1);
  return rcode;
}

/* using macros to avoid copy-paste technic */
#define DEF_TOSTR(funcname, breaktimefunc, tostrfunc, addtz)               \
  WARN_UNUSED_RESULT                                                       \
  V7_PRIVATE enum v7_err Date_to##funcname(struct v7 *v7, v7_val_t *res) { \
    val_t this_obj = v7_get_this(v7);                                      \
    return d_tostring(v7, this_obj, breaktimefunc, tostrfunc, addtz, res); \
  }

/* non-locale function should always return in english and 24h-format */
static const char *wday_name[] = {"Sun", "Mon", "Tue", "Wed",
                                  "Thu", "Fri", "Sat"};

static int d_tptodatestr(const struct timeparts *tp, char *buf, int addtz) {
  (void) addtz;

  return sprintf(buf, "%s %s %02d %d", wday_name[tp->dayofweek],
                 mon_name[tp->month], tp->day, tp->year);
}

DEF_TOSTR(DateString, d_localtime, d_tptodatestr, 1)

static const char *d_gettzname(void) {
  return g_tzname;
}

static int d_tptotimestr(const struct timeparts *tp, char *buf, int addtz) {
  int len;

  len = sprintf(buf, "%02d:%02d:%02d GMT", tp->hour, tp->min, tp->sec);

  if (addtz && g_gmtoffms != 0) {
    len = sprintf(buf + len, "%c%02d00 (%s)", g_gmtoffms > 0 ? '-' : '+',
                  abs((int) g_gmtoffms / msPerHour), d_gettzname());
  }

  return (int) strlen(buf);
}

DEF_TOSTR(TimeString, d_localtime, d_tptotimestr, 1)

static int d_tptostr(const struct timeparts *tp, char *buf, int addtz) {
  int len = d_tptodatestr(tp, buf, addtz);
  *(buf + len) = ' ';
  return d_tptotimestr(tp, buf + len + 1, addtz) + len + 1;
}

DEF_TOSTR(String, d_localtime, d_tptostr, 1)
DEF_TOSTR(UTCString, d_gmtime, d_tptostr, 0)
#endif /* V7_ENABLE__Date__toString */

#if V7_ENABLE__Date__toLocaleString
struct d_locale {
  char locale[50];
};

static void d_getcurrentlocale(struct d_locale *loc) {
  strcpy(loc->locale, setlocale(LC_TIME, 0));
}

static void d_setlocale(const struct d_locale *loc) {
  setlocale(LC_TIME, loc ? loc->locale : "");
}

/* TODO(alashkin): check portability */
WARN_UNUSED_RESULT
static enum v7_err d_tolocalestr(struct v7 *v7, val_t obj, const char *frm,
                                 v7_val_t *res) {
  enum v7_err rcode = V7_OK;
  char buf[250];
  size_t len;
  struct tm t;
  etime_t time;
  struct d_locale prev_locale;
  struct timeparts tp;

  time = v7_get_double(v7, d_trytogetobjforstring(v7, obj));

  d_getcurrentlocale(&prev_locale);
  d_setlocale(0);
  d_localtime(&time, &tp);

  memset(&t, 0, sizeof(t));
  t.tm_year = tp.year - 1900;
  t.tm_mon = tp.month;
  t.tm_mday = tp.day;
  t.tm_hour = tp.hour;
  t.tm_min = tp.min;
  t.tm_sec = tp.sec;
  t.tm_wday = tp.dayofweek;

  len = strftime(buf, sizeof(buf), frm, &t);

  d_setlocale(&prev_locale);

  *res = v7_mk_string(v7, buf, len, 1);
  return rcode;
}

#define DEF_TOLOCALESTR(funcname, frm)                                     \
  WARN_UNUSED_RESULT                                                       \
  V7_PRIVATE enum v7_err Date_to##funcname(struct v7 *v7, v7_val_t *res) { \
    val_t this_obj = v7_get_this(v7);                                      \
    return d_tolocalestr(v7, this_obj, frm, res);                          \
  }

DEF_TOLOCALESTR(LocaleString, "%c")
DEF_TOLOCALESTR(LocaleDateString, "%x")
DEF_TOLOCALESTR(LocaleTimeString, "%X")
#endif /* V7_ENABLE__Date__toLocaleString */

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Date_valueOf(struct v7 *v7, v7_val_t *res) {
  enum v7_err rcode = V7_OK;
  val_t this_obj = v7_get_this(v7);
  if (!v7_is_generic_object(this_obj) ||
      (v7_is_generic_object(this_obj) &&
       v7_get_proto(v7, this_obj) != v7->vals.date_prototype)) {
    rcode = v7_throwf(v7, TYPE_ERROR, "Date.valueOf called on non-Date object");
    goto clean;
  }

  rcode = Obj_valueOf(v7, res);
  if (rcode != V7_OK) {
    goto clean;
  }

clean:
  return rcode;
}

#if V7_ENABLE__Date__getters
static struct timeparts *d_getTimePart(struct v7 *v7, val_t val,
                                       struct timeparts *tp,
                                       fbreaktime_t breaktimefunc) {
  etime_t time;
  time = v7_get_double(v7, val);
  breaktimefunc(&time, tp);
  return tp;
}

#define DEF_GET_TP_FUNC(funcName, tpmember, breaktimefunc)                   \
  WARN_UNUSED_RESULT                                                         \
  V7_PRIVATE enum v7_err Date_get##funcName(struct v7 *v7, v7_val_t *res) {  \
    enum v7_err rcode = V7_OK;                                               \
    val_t v = V7_UNDEFINED;                                                  \
    struct timeparts tp;                                                     \
    val_t this_obj = v7_get_this(v7);                                        \
                                                                             \
    rcode = obj_value_of(v7, this_obj, &v);                                  \
    if (rcode != V7_OK) {                                                    \
      goto clean;                                                            \
    }                                                                        \
    *res = v7_mk_number(                                                     \
        v7, v == V7_TAG_NAN ? NAN : d_getTimePart(v7, v, &tp, breaktimefunc) \
                                        ->tpmember);                         \
  clean:                                                                     \
    return rcode;                                                            \
  }

#define DEF_GET_TP(funcName, tpmember)               \
  DEF_GET_TP_FUNC(UTC##funcName, tpmember, d_gmtime) \
  DEF_GET_TP_FUNC(funcName, tpmember, d_localtime)

DEF_GET_TP(Date, day)
DEF_GET_TP(FullYear, year)
DEF_GET_TP(Month, month)
DEF_GET_TP(Hours, hour)
DEF_GET_TP(Minutes, min)
DEF_GET_TP(Seconds, sec)
DEF_GET_TP(Milliseconds, msec)
DEF_GET_TP(Day, dayofweek)

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Date_getTime(struct v7 *v7, v7_val_t *res) {
  return Date_valueOf(v7, res);
}

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Date_getTimezoneOffset(struct v7 *v7, v7_val_t *res) {
  (void) v7;
  *res = v7_mk_number(v7, g_gmtoffms / msPerMinute);
  return V7_OK;
}
#endif /* V7_ENABLE__Date__getters */

#if V7_ENABLE__Date__setters
WARN_UNUSED_RESULT
static enum v7_err d_setTimePart(struct v7 *v7, int start_pos,
                                 fbreaktime_t breaktimefunc,
                                 fmaketime_t maketimefunc, v7_val_t *res) {
  enum v7_err rcode = V7_OK;
  val_t this_obj = v7_get_this(v7);
  etime_t ret_time =
      d_time_number_from_arr(v7, start_pos, breaktimefunc, maketimefunc);

  *res = v7_mk_number(v7, ret_time);
  v7_def(v7, this_obj, "", 0, _V7_DESC_HIDDEN(1), *res);

  return rcode;
}

#define DEF_SET_TP(name, start_pos)                                        \
  WARN_UNUSED_RESULT                                                       \
  V7_PRIVATE enum v7_err Date_setUTC##name(struct v7 *v7, v7_val_t *res) { \
    return d_setTimePart(v7, start_pos, d_gmtime, d_gmktime, res);         \
  }                                                                        \
  WARN_UNUSED_RESULT                                                       \
  V7_PRIVATE enum v7_err Date_set##name(struct v7 *v7, v7_val_t *res) {    \
    return d_setTimePart(v7, start_pos, d_localtime, d_lmktime, res);      \
  }

DEF_SET_TP(Milliseconds, tpmsec)
DEF_SET_TP(Seconds, tpseconds)
DEF_SET_TP(Minutes, tpminutes)
DEF_SET_TP(Hours, tphours)
DEF_SET_TP(Date, tpdate)
DEF_SET_TP(Month, tpmonth)
DEF_SET_TP(FullYear, tpyear)

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Date_setTime(struct v7 *v7, v7_val_t *res) {
  enum v7_err rcode = V7_OK;
  val_t this_obj = v7_get_this(v7);

  if (v7_argc(v7) >= 1) {
    rcode = to_number_v(v7, v7_arg(v7, 0), res);
    if (rcode != V7_OK) {
      goto clean;
    }
  }

  v7_def(v7, this_obj, "", 0, _V7_DESC_HIDDEN(1), *res);

clean:
  return rcode;
}
#endif /* V7_ENABLE__Date__setters */

#if V7_ENABLE__Date__toJSON
WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Date_toJSON(struct v7 *v7, v7_val_t *res) {
  return Date_toISOString(v7, res);
}
#endif /* V7_ENABLE__Date__toJSON */

#if V7_ENABLE__Date__now
WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Date_now(struct v7 *v7, v7_val_t *res) {
  etime_t ret_time;
  (void) v7;

  d_gettime(&ret_time);

  *res = v7_mk_number(v7, ret_time);
  return V7_OK;
}
#endif /* V7_ENABLE__Date__now */

#if V7_ENABLE__Date__parse
WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Date_parse(struct v7 *v7, v7_val_t *res) {
  enum v7_err rcode = V7_OK;
  val_t this_obj = v7_get_this(v7);
  etime_t ret_time = INVALID_TIME;

  if (!d_iscalledasfunction(v7, this_obj)) {
    rcode = v7_throwf(v7, TYPE_ERROR, "Date.parse() called on object");
    goto clean;
  }

  if (v7_argc(v7) >= 1) {
    val_t arg0 = v7_arg(v7, 0);
    if (v7_is_string(arg0)) {
      size_t size;
      const char *time_str = v7_get_string(v7, &arg0, &size);

      d_timeFromString(&ret_time, time_str, size);
    }
  }

  *res = v7_mk_number(v7, ret_time);

clean:
  return rcode;
}
#endif /* V7_ENABLE__Date__parse */

#if V7_ENABLE__Date__UTC
WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Date_UTC(struct v7 *v7, v7_val_t *res) {
  enum v7_err rcode = V7_OK;
  val_t this_obj = v7_get_this(v7);
  etime_t ret_time;

  if (!d_iscalledasfunction(v7, this_obj)) {
    rcode = v7_throwf(v7, TYPE_ERROR, "Date.now() called on object");
    goto clean;
  }

  ret_time = d_time_number_from_arr(v7, tpyear, 0, d_gmktime);
  *res = v7_mk_number(v7, ret_time);

clean:
  return rcode;
}
#endif /* V7_ENABLE__Date__UTC */

/****** Initialization *******/

/*
 * We should clear V7_PROPERTY_ENUMERABLE for all Date props
 * TODO(mkm): check other objects
*/
static int d_set_cfunc_prop(struct v7 *v7, val_t o, const char *name,
                            v7_cfunction_t *f) {
  return v7_def(v7, o, name, strlen(name), V7_DESC_ENUMERABLE(0),
                v7_mk_cfunction(f));
}

#define DECLARE_GET(func)                                       \
  d_set_cfunc_prop(v7, v7->vals.date_prototype, "getUTC" #func, \
                   Date_getUTC##func);                          \
  d_set_cfunc_prop(v7, v7->vals.date_prototype, "get" #func, Date_get##func);

#define DECLARE_SET(func)                                       \
  d_set_cfunc_prop(v7, v7->vals.date_prototype, "setUTC" #func, \
                   Date_setUTC##func);                          \
  d_set_cfunc_prop(v7, v7->vals.date_prototype, "set" #func, Date_set##func);

V7_PRIVATE void init_date(struct v7 *v7) {
  val_t date =
      mk_cfunction_obj_with_proto(v7, Date_ctor, 7, v7->vals.date_prototype);
  v7_def(v7, v7->vals.global_object, "Date", 4, V7_DESC_ENUMERABLE(0), date);
  d_set_cfunc_prop(v7, v7->vals.date_prototype, "valueOf", Date_valueOf);

#if V7_ENABLE__Date__getters
  DECLARE_GET(Date);
  DECLARE_GET(FullYear);
  DECLARE_GET(Month);
  DECLARE_GET(Hours);
  DECLARE_GET(Minutes);
  DECLARE_GET(Seconds);
  DECLARE_GET(Milliseconds);
  DECLARE_GET(Day);
  d_set_cfunc_prop(v7, v7->vals.date_prototype, "getTime", Date_getTime);
#endif

#if V7_ENABLE__Date__setters
  DECLARE_SET(Date);
  DECLARE_SET(FullYear);
  DECLARE_SET(Month);
  DECLARE_SET(Hours);
  DECLARE_SET(Minutes);
  DECLARE_SET(Seconds);
  DECLARE_SET(Milliseconds);
  d_set_cfunc_prop(v7, v7->vals.date_prototype, "setTime", Date_setTime);
  d_set_cfunc_prop(v7, v7->vals.date_prototype, "getTimezoneOffset",
                   Date_getTimezoneOffset);
#endif

#if V7_ENABLE__Date__now
  d_set_cfunc_prop(v7, date, "now", Date_now);
#endif
#if V7_ENABLE__Date__parse
  d_set_cfunc_prop(v7, date, "parse", Date_parse);
#endif
#if V7_ENABLE__Date__UTC
  d_set_cfunc_prop(v7, date, "UTC", Date_UTC);
#endif

#if V7_ENABLE__Date__toString
  d_set_cfunc_prop(v7, v7->vals.date_prototype, "toString", Date_toString);
  d_set_cfunc_prop(v7, v7->vals.date_prototype, "toISOString",
                   Date_toISOString);
  d_set_cfunc_prop(v7, v7->vals.date_prototype, "toUTCString",
                   Date_toUTCString);
  d_set_cfunc_prop(v7, v7->vals.date_prototype, "toDateString",
                   Date_toDateString);
  d_set_cfunc_prop(v7, v7->vals.date_prototype, "toTimeString",
                   Date_toTimeString);
#endif
#if V7_ENABLE__Date__toLocaleString
  d_set_cfunc_prop(v7, v7->vals.date_prototype, "toLocaleString",
                   Date_toLocaleString);
  d_set_cfunc_prop(v7, v7->vals.date_prototype, "toLocaleDateString",
                   Date_toLocaleDateString);
  d_set_cfunc_prop(v7, v7->vals.date_prototype, "toLocaleTimeString",
                   Date_toLocaleTimeString);
#endif
#if V7_ENABLE__Date__toJSON
  d_set_cfunc_prop(v7, v7->vals.date_prototype, "toJSON", Date_toJSON);
#endif

  /*
   * GTM offset without DST
   * TODO(alashkin): check this
   * Could be changed to tm::tm_gmtoff,
   * but tm_gmtoff includes DST, so
   * side effects are possible
   */
  tzset();
  g_gmtoffms = timezone * msPerSecond;
  /*
   * tzname could be changed by localtime_r call,
   * so we have to store pointer
   * TODO(alashkin): need restart on tz change???
   */
  g_tzname = tzname[0];
}

#if defined(__cplusplus)
}
#endif /* __cplusplus */

#endif /* V7_ENABLE__Date */
#ifdef V7_MODULE_LINES
#line 1 "v7/src/std_function.c"
#endif
/*
 * Copyright (c) 2014 Cesanta Software Limited
 * All rights reserved
 */

/* Amalgamated: #include "common/str_util.h" */
/* Amalgamated: #include "v7/src/internal.h" */
/* Amalgamated: #include "v7/src/core.h" */
/* Amalgamated: #include "v7/src/function.h" */
/* Amalgamated: #include "v7/src/bcode.h" */
/* Amalgamated: #include "v7/src/eval.h" */
/* Amalgamated: #include "v7/src/conversion.h" */
/* Amalgamated: #include "v7/src/object.h" */
/* Amalgamated: #include "v7/src/exec.h" */
/* Amalgamated: #include "v7/src/exceptions.h" */

#if defined(__cplusplus)
extern "C" {
#endif /* __cplusplus */

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Function_ctor(struct v7 *v7, v7_val_t *res) {
  enum v7_err rcode = V7_OK;
  long i, num_args = v7_argc(v7);
  size_t size;
  const char *s;
  struct mbuf m;

  mbuf_init(&m, 0);

  if (num_args <= 0) {
    goto clean;
  }

  mbuf_append(&m, "(function(", 10);

  for (i = 0; i < num_args - 1; i++) {
    rcode = obj_value_of(v7, v7_arg(v7, i), res);
    if (rcode != V7_OK) {
      goto clean;
    }
    if (v7_is_string(*res)) {
      if (i > 0) mbuf_append(&m, ",", 1);
      s = v7_get_string(v7, res, &size);
      mbuf_append(&m, s, size);
    }
  }
  mbuf_append(&m, "){", 2);
  rcode = obj_value_of(v7, v7_arg(v7, num_args - 1), res);
  if (rcode != V7_OK) {
    goto clean;
  }
  if (v7_is_string(*res)) {
    s = v7_get_string(v7, res, &size);
    mbuf_append(&m, s, size);
  }
  mbuf_append(&m, "})\0", 3);

  rcode = v7_exec(v7, m.buf, res);
  if (rcode != V7_OK) {
    rcode = v7_throwf(v7, SYNTAX_ERROR, "Invalid function body");
    goto clean;
  }

clean:
  mbuf_free(&m);
  return rcode;
}

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Function_length(struct v7 *v7, v7_val_t *res) {
  enum v7_err rcode = V7_OK;
  v7_val_t this_obj = v7_get_this(v7);
  struct v7_js_function *func;

  rcode = obj_value_of(v7, this_obj, &this_obj);
  if (rcode != V7_OK) {
    goto clean;
  }
  if (!is_js_function(this_obj)) {
    *res = v7_mk_number(v7, 0);
    goto clean;
  }

  func = get_js_function_struct(this_obj);

  *res = v7_mk_number(v7, func->bcode->args_cnt);

clean:
  return rcode;
}

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Function_name(struct v7 *v7, v7_val_t *res) {
  enum v7_err rcode = V7_OK;
  v7_val_t this_obj = v7_get_this(v7);
  struct v7_js_function *func;

  rcode = obj_value_of(v7, this_obj, &this_obj);
  if (rcode != V7_OK) {
    goto clean;
  }
  if (!is_js_function(this_obj)) {
    goto clean;
  }

  func = get_js_function_struct(this_obj);

  assert(func->bcode != NULL);

  assert(func->bcode->names_cnt >= 1);
  bcode_next_name_v(v7, func->bcode, func->bcode->ops.p, res);

clean:
  return rcode;
}

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Function_apply(struct v7 *v7, v7_val_t *res) {
  enum v7_err rcode = V7_OK;
  val_t this_obj = v7_get_this(v7);
  val_t this_arg = v7_arg(v7, 0);
  val_t func_args = v7_arg(v7, 1);

  rcode = obj_value_of(v7, this_obj, &this_obj);
  if (rcode != V7_OK) {
    goto clean;
  }

  if (is_js_function(this_obj)) {
    /*
     * `Function_apply` is a cfunction, so, GC is inhibited before calling it.
     * But the given function to call is a JS function, so we should enable GC;
     * otherwise, it will be inhibited during the whole execution of the given
     * JS function
     */
    v7_set_gc_enabled(v7, 1);
  }

  rcode = b_apply(v7, this_obj, this_arg, func_args, 0, res);
  if (rcode != V7_OK) {
    goto clean;
  }

clean:
  return rcode;
}

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Function_toString(struct v7 *v7, v7_val_t *res) {
  enum v7_err rcode = V7_OK;
  char *ops;
  char *name;
  size_t name_len;
  char buf[50];
  char *b = buf;
  struct v7_js_function *func = get_js_function_struct(v7_get_this(v7));
  int i;

  b += c_snprintf(b, BUF_LEFT(sizeof(buf), b - buf), "[function");

  assert(func->bcode != NULL);
  ops = func->bcode->ops.p;

  /* first entry in name list */
  ops = bcode_next_name(ops, &name, &name_len);

  if (name_len > 0) {
    b += c_snprintf(b, BUF_LEFT(sizeof(buf), b - buf), " %.*s", (int) name_len,
                    name);
  }
  b += c_snprintf(b, BUF_LEFT(sizeof(buf), b - buf), "(");
  for (i = 0; i < func->bcode->args_cnt; i++) {
    ops = bcode_next_name(ops, &name, &name_len);

    b += c_snprintf(b, BUF_LEFT(sizeof(buf), b - buf), "%.*s", (int) name_len,
                    name);
    if (i < func->bcode->args_cnt - 1) {
      b += c_snprintf(b, BUF_LEFT(sizeof(buf), b - buf), ",");
    }
  }
  b += c_snprintf(b, BUF_LEFT(sizeof(buf), b - buf), ")");

  {
    uint8_t loc_cnt =
        func->bcode->names_cnt - func->bcode->args_cnt - 1 /*func name*/;

    if (loc_cnt > 0) {
      b += c_snprintf(b, BUF_LEFT(sizeof(buf), b - buf), "{var ");
      for (i = 0; i < loc_cnt; ++i) {
        ops = bcode_next_name(ops, &name, &name_len);

        b += c_snprintf(b, BUF_LEFT(sizeof(buf), b - buf), "%.*s",
                        (int) name_len, name);
        if (i < (loc_cnt - 1)) {
          b += c_snprintf(b, BUF_LEFT(sizeof(buf), b - buf), ",");
        }
      }

      b += c_snprintf(b, BUF_LEFT(sizeof(buf), b - buf), "}");
    }
  }

  b += c_snprintf(b, BUF_LEFT(sizeof(buf), b - buf), "]");

  *res = v7_mk_string(v7, buf, strlen(buf), 1);

  return rcode;
}

V7_PRIVATE void init_function(struct v7 *v7) {
  val_t ctor = mk_cfunction_obj(v7, Function_ctor, 1);

  v7_set(v7, ctor, "prototype", 9, v7->vals.function_prototype);
  v7_set(v7, v7->vals.global_object, "Function", 8, ctor);
  set_method(v7, v7->vals.function_prototype, "apply", Function_apply, 1);
  set_method(v7, v7->vals.function_prototype, "toString", Function_toString, 0);
  v7_def(v7, v7->vals.function_prototype, "length", 6,
         (V7_DESC_ENUMERABLE(0) | V7_DESC_GETTER(1)),
         v7_mk_cfunction(Function_length));
  v7_def(v7, v7->vals.function_prototype, "name", 4,
         (V7_DESC_ENUMERABLE(0) | V7_DESC_GETTER(1)),
         v7_mk_cfunction(Function_name));
}

#if defined(__cplusplus)
}
#endif /* __cplusplus */
#ifdef V7_MODULE_LINES
#line 1 "v7/src/std_regex.c"
#endif
/*
 * Copyright (c) 2014 Cesanta Software Limited
 * All rights reserved
 */

/* Amalgamated: #include "common/utf.h" */
/* Amalgamated: #include "common/str_util.h" */
/* Amalgamated: #include "v7/src/internal.h" */
/* Amalgamated: #include "v7/src/std_regex.h" */
/* Amalgamated: #include "v7/src/std_string.h" */
/* Amalgamated: #include "v7/src/slre.h" */
/* Amalgamated: #include "v7/src/core.h" */
/* Amalgamated: #include "v7/src/function.h" */
/* Amalgamated: #include "v7/src/conversion.h" */
/* Amalgamated: #include "v7/src/array.h" */
/* Amalgamated: #include "v7/src/object.h" */
/* Amalgamated: #include "v7/src/regexp.h" */
/* Amalgamated: #include "v7/src/exceptions.h" */
/* Amalgamated: #include "v7/src/string.h" */
/* Amalgamated: #include "v7/src/primitive.h" */

#if V7_ENABLE__RegExp

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Regex_ctor(struct v7 *v7, v7_val_t *res) {
  enum v7_err rcode = V7_OK;
  long argnum = v7_argc(v7);

  if (argnum > 0) {
    val_t arg = v7_arg(v7, 0);
    val_t ro, fl;
    size_t re_len, flags_len = 0;
    const char *re, *flags = NULL;

    if (v7_is_regexp(v7, arg)) {
      if (argnum > 1) {
        /* ch15/15.10/15.10.3/S15.10.3.1_A2_T1.js */
        rcode = v7_throwf(v7, TYPE_ERROR, "invalid flags");
        goto clean;
      }
      *res = arg;
      goto clean;
    }
    rcode = to_string(v7, arg, &ro, NULL, 0, NULL);
    if (rcode != V7_OK) {
      goto clean;
    }

    if (argnum > 1) {
      rcode = to_string(v7, v7_arg(v7, 1), &fl, NULL, 0, NULL);
      if (rcode != V7_OK) {
        goto clean;
      }

      flags = v7_get_string(v7, &fl, &flags_len);
    }
    re = v7_get_string(v7, &ro, &re_len);
    rcode = v7_mk_regexp(v7, re, re_len, flags, flags_len, res);
    if (rcode != V7_OK) {
      goto clean;
    }

  } else {
    rcode = v7_mk_regexp(v7, "(?:)", 4, NULL, 0, res);
    if (rcode != V7_OK) {
      goto clean;
    }
  }

clean:
  return rcode;
}

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Regex_global(struct v7 *v7, v7_val_t *res) {
  enum v7_err rcode = V7_OK;
  int flags = 0;
  val_t this_obj = v7_get_this(v7);
  val_t r = V7_UNDEFINED;
  rcode = obj_value_of(v7, this_obj, &r);
  if (rcode != V7_OK) {
    goto clean;
  }

  if (v7_is_regexp(v7, r)) {
    flags = slre_get_flags(v7_get_regexp_struct(v7, r)->compiled_regexp);
  }

  *res = v7_mk_boolean(v7, flags & SLRE_FLAG_G);

clean:
  return rcode;
}

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Regex_ignoreCase(struct v7 *v7, v7_val_t *res) {
  enum v7_err rcode = V7_OK;
  int flags = 0;
  val_t this_obj = v7_get_this(v7);
  val_t r = V7_UNDEFINED;
  rcode = obj_value_of(v7, this_obj, &r);
  if (rcode != V7_OK) {
    goto clean;
  }

  if (v7_is_regexp(v7, r)) {
    flags = slre_get_flags(v7_get_regexp_struct(v7, r)->compiled_regexp);
  }

  *res = v7_mk_boolean(v7, flags & SLRE_FLAG_I);

clean:
  return rcode;
}

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Regex_multiline(struct v7 *v7, v7_val_t *res) {
  enum v7_err rcode = V7_OK;
  int flags = 0;
  val_t this_obj = v7_get_this(v7);
  val_t r = V7_UNDEFINED;
  rcode = obj_value_of(v7, this_obj, &r);
  if (rcode != V7_OK) {
    goto clean;
  }

  if (v7_is_regexp(v7, r)) {
    flags = slre_get_flags(v7_get_regexp_struct(v7, r)->compiled_regexp);
  }

  *res = v7_mk_boolean(v7, flags & SLRE_FLAG_M);

clean:
  return rcode;
}

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Regex_source(struct v7 *v7, v7_val_t *res) {
  enum v7_err rcode = V7_OK;
  val_t this_obj = v7_get_this(v7);
  val_t r = V7_UNDEFINED;
  const char *buf = 0;
  size_t len = 0;

  rcode = obj_value_of(v7, this_obj, &r);
  if (rcode != V7_OK) {
    goto clean;
  }

  if (v7_is_regexp(v7, r)) {
    buf = v7_get_string(v7, &v7_get_regexp_struct(v7, r)->regexp_string, &len);
  }

  *res = v7_mk_string(v7, buf, len, 1);

clean:
  return rcode;
}

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Regex_get_lastIndex(struct v7 *v7, v7_val_t *res) {
  enum v7_err rcode = V7_OK;
  long lastIndex = 0;
  val_t this_obj = v7_get_this(v7);

  if (v7_is_regexp(v7, this_obj)) {
    lastIndex = v7_get_regexp_struct(v7, this_obj)->lastIndex;
  }

  *res = v7_mk_number(v7, lastIndex);

  return rcode;
}

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Regex_set_lastIndex(struct v7 *v7, v7_val_t *res) {
  enum v7_err rcode = V7_OK;
  long lastIndex = 0;
  val_t this_obj = v7_get_this(v7);

  if (v7_is_regexp(v7, this_obj)) {
    rcode = to_long(v7, v7_arg(v7, 0), 0, &lastIndex);
    if (rcode != V7_OK) {
      goto clean;
    }
    v7_get_regexp_struct(v7, this_obj)->lastIndex = lastIndex;
  }

  *res = v7_mk_number(v7, lastIndex);

clean:
  return rcode;
}

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err rx_exec(struct v7 *v7, val_t rx, val_t vstr, int lind,
                               val_t *res) {
  enum v7_err rcode = V7_OK;
  if (v7_is_regexp(v7, rx)) {
    val_t s = V7_UNDEFINED;
    size_t len;
    struct slre_loot sub;
    struct slre_cap *ptok = sub.caps;
    const char *str = NULL;
    const char *end = NULL;
    const char *begin = NULL;
    struct v7_regexp *rp = v7_get_regexp_struct(v7, rx);
    int flag_g = slre_get_flags(rp->compiled_regexp) & SLRE_FLAG_G;

    rcode = to_string(v7, vstr, &s, NULL, 0, NULL);
    if (rcode != V7_OK) {
      goto clean;
    }
    str = v7_get_string(v7, &s, &len);
    end = str + len;
    begin = str;

    if (rp->lastIndex < 0) rp->lastIndex = 0;
    if (flag_g || lind) begin = utfnshift(str, rp->lastIndex);

    if (!slre_exec(rp->compiled_regexp, 0, begin, end, &sub)) {
      int i;
      val_t arr = v7_mk_array(v7);
      char *old_mbuf_base = v7->owned_strings.buf;
      ptrdiff_t rel = 0; /* creating strings might relocate the mbuf */

      for (i = 0; i < sub.num_captures; i++, ptok++) {
        rel = v7->owned_strings.buf - old_mbuf_base;
        v7_array_push(v7, arr, v7_mk_string(v7, ptok->start + rel,
                                            ptok->end - ptok->start, 1));
      }
      if (flag_g) rp->lastIndex = utfnlen(str, sub.caps->end + rel - str);
      v7_def(v7, arr, "index", 5, V7_DESC_WRITABLE(0),
             v7_mk_number(v7, utfnlen(str + rel, sub.caps->start - str)));
      *res = arr;
      goto clean;
    } else {
      rp->lastIndex = 0;
    }
  }

  *res = V7_NULL;

clean:
  return rcode;
}

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Regex_exec(struct v7 *v7, v7_val_t *res) {
  enum v7_err rcode = V7_OK;
  val_t this_obj = v7_get_this(v7);

  if (v7_argc(v7) > 0) {
    rcode = rx_exec(v7, this_obj, v7_arg(v7, 0), 0, res);
    if (rcode != V7_OK) {
      goto clean;
    }
  } else {
    *res = V7_NULL;
  }

clean:
  return rcode;
}

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Regex_test(struct v7 *v7, v7_val_t *res) {
  enum v7_err rcode = V7_OK;
  val_t tmp = V7_UNDEFINED;

  rcode = Regex_exec(v7, &tmp);
  if (rcode != V7_OK) {
    goto clean;
  }

  *res = v7_mk_boolean(v7, !v7_is_null(tmp));

clean:
  return rcode;
}

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Regex_flags(struct v7 *v7, v7_val_t *res) {
  enum v7_err rcode = V7_OK;
  char buf[3] = {0};
  val_t this_obj = v7_get_this(v7);
  struct v7_regexp *rp = v7_get_regexp_struct(v7, this_obj);
  size_t n = get_regexp_flags_str(v7, rp, buf);
  *res = v7_mk_string(v7, buf, n, 1);

  return rcode;
}

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Regex_toString(struct v7 *v7, v7_val_t *res) {
  enum v7_err rcode = V7_OK;
  size_t n1, n2 = 0;
  char s2[3] = {0};
  char buf[50];
  val_t this_obj = v7_get_this(v7);
  struct v7_regexp *rp;
  const char *s1;

  rcode = obj_value_of(v7, this_obj, &this_obj);
  if (rcode != V7_OK) {
    goto clean;
  }

  if (!v7_is_regexp(v7, this_obj)) {
    rcode = v7_throwf(v7, TYPE_ERROR, "Not a regexp");
    goto clean;
  }

  rp = v7_get_regexp_struct(v7, this_obj);
  s1 = v7_get_string(v7, &rp->regexp_string, &n1);
  n2 = get_regexp_flags_str(v7, rp, s2);

  c_snprintf(buf, sizeof(buf), "/%.*s/%.*s", (int) n1, s1, (int) n2, s2);

  *res = v7_mk_string(v7, buf, strlen(buf), 1);

clean:
  return rcode;
}

V7_PRIVATE void init_regex(struct v7 *v7) {
  val_t ctor =
      mk_cfunction_obj_with_proto(v7, Regex_ctor, 1, v7->vals.regexp_prototype);
  val_t lastIndex = v7_mk_dense_array(v7);

  v7_def(v7, v7->vals.global_object, "RegExp", 6, V7_DESC_ENUMERABLE(0), ctor);

  set_cfunc_prop(v7, v7->vals.regexp_prototype, "exec", Regex_exec);
  set_cfunc_prop(v7, v7->vals.regexp_prototype, "test", Regex_test);
  set_method(v7, v7->vals.regexp_prototype, "toString", Regex_toString, 0);

  v7_def(v7, v7->vals.regexp_prototype, "global", 6, V7_DESC_GETTER(1),
         v7_mk_cfunction(Regex_global));
  v7_def(v7, v7->vals.regexp_prototype, "ignoreCase", 10, V7_DESC_GETTER(1),
         v7_mk_cfunction(Regex_ignoreCase));
  v7_def(v7, v7->vals.regexp_prototype, "multiline", 9, V7_DESC_GETTER(1),
         v7_mk_cfunction(Regex_multiline));
  v7_def(v7, v7->vals.regexp_prototype, "source", 6, V7_DESC_GETTER(1),
         v7_mk_cfunction(Regex_source));
  v7_def(v7, v7->vals.regexp_prototype, "flags", 5, V7_DESC_GETTER(1),
         v7_mk_cfunction(Regex_flags));

  v7_array_set(v7, lastIndex, 0, v7_mk_cfunction(Regex_get_lastIndex));
  v7_array_set(v7, lastIndex, 1, v7_mk_cfunction(Regex_set_lastIndex));
  v7_def(v7, v7->vals.regexp_prototype, "lastIndex", 9,
         (V7_DESC_GETTER(1) | V7_DESC_SETTER(1)), lastIndex);
}

#endif /* V7_ENABLE__RegExp */
#ifdef V7_MODULE_LINES
#line 1 "v7/src/std_proxy.c"
#endif
/*
 * Copyright (c) 2014 Cesanta Software Limited
 * All rights reserved
 */

/* Amalgamated: #include "v7/src/internal.h" */
/* Amalgamated: #include "v7/src/std_object.h" */
/* Amalgamated: #include "v7/src/std_proxy.h" */
/* Amalgamated: #include "v7/src/conversion.h" */
/* Amalgamated: #include "v7/src/core.h" */
/* Amalgamated: #include "v7/src/function.h" */
/* Amalgamated: #include "v7/src/object.h" */
/* Amalgamated: #include "v7/src/primitive.h" */
/* Amalgamated: #include "v7/src/string.h" */
/* Amalgamated: #include "v7/src/exceptions.h" */

#if defined(__cplusplus)
extern "C" {
#endif /* __cplusplus */

#if V7_ENABLE__Proxy

WARN_UNUSED_RESULT
V7_PRIVATE enum v7_err Proxy_ctor(struct v7 *v7, v7_val_t *res) {
  enum v7_err rcode = V7_OK;
  val_t this_obj = v7_get_this(v7);
  val_t target_v = v7_arg(v7, 0);
  val_t handler_v = v7_arg(v7, 1);
  struct v7_object *t = NULL;
  v7_prop_attr_desc_t attrs_desc =
      (V7_DESC_WRITABLE(0) | V7_DESC_ENUMERABLE(0) | V7_DESC_CONFIGURABLE(0));

  if (this_obj == v7_get_global(v7) || !v7_is_object(this_obj)) {
    rcode = v7_throwf(v7, TYPE_ERROR, "Wrong 'this' object for Proxy ctor");
    goto clean;
  }

  if (!v7_is_object(target_v) || !v7_is_object(handler_v)) {
    rcode =
        v7_throwf(v7, TYPE_ERROR,
                  "Cannot create proxy with a non-object as target or handler");
    goto clean;
  }

  t = get_object_struct(this_obj);
  t->attributes |= V7_OBJ_PROXY;

  v7_def(v7, this_obj, _V7_PROXY_TARGET_NAME, ~0, attrs_desc, target_v);
  v7_def(v7, this_obj, _V7_PROXY_HANDLER_NAME, ~0, attrs_desc, handler_v);

  (void) res;

clean:
  return rcode;
}

V7_PRIVATE void init_proxy(struct v7 *v7) {
  /*v7_prop_attr_desc_t attrs_desc =*/
  /*(V7_DESC_WRITABLE(0) | V7_DESC_ENUMERABLE(0) | V7_DESC_CONFIGURABLE(0));*/
  val_t proxy =
      mk_cfunction_obj_with_proto(v7, Proxy_ctor, 1, v7->vals.proxy_prototype);

  v7_def(v7, v7->vals.global_object, "Proxy", ~0, V7_DESC_ENUMERABLE(0), proxy);
}

V7_PRIVATE int is_special_proxy_name(const char *name, size_t name_len) {
  int ret = 0;
  if (name_len == (size_t) ~0) {
    name_len = strlen(name);
  }
  if (name_len == 5 && (memcmp(name, _V7_PROXY_TARGET_NAME, name_len) == 0 ||
                        memcmp(name, _V7_PROXY_HANDLER_NAME, name_len) == 0)) {
    ret = 1;
  }
  return ret;
}

#endif /* V7_ENABLE__Proxy */

#if defined(__cplusplus)
}
#endif /* __cplusplus */
#ifdef V7_MODULE_LINES
#line 1 "v7/src/main.c"
#endif
/*
 * Copyright (c) 2014 Cesanta Software Limited
 * All rights reserved
 */

/* Amalgamated: #include "v7/src/internal.h" */
/* Amalgamated: #include "v7/src/gc.h" */
/* Amalgamated: #include "v7/src/freeze.h" */
/* Amalgamated: #include "v7/src/main.h" */
/* Amalgamated: #include "v7/src/primitive.h" */
/* Amalgamated: #include "v7/src/exec.h" */
/* Amalgamated: #include "v7/src/util.h" */
/* Amalgamated: #include "v7/src/conversion.h" */
/* Amalgamated: #include "common/platform.h" */
/* Amalgamated: #include "common/cs_file.h" */

#if defined(_MSC_VER) && _MSC_VER >= 1800
#define fileno _fileno
#endif

#ifdef V7_EXE
#define V7_MAIN
#endif

#ifdef V7_MAIN

#include <sys/stat.h>

static void show_usage(char *argv[]) {
  fprintf(stderr, "V7 version %s (c) Cesanta Software, built on %s\n",
          V7_VERSION, __DATE__);
  fprintf(stderr, "Usage: %s [OPTIONS] js_file ...\n", argv[0]);
  fprintf(stderr, "%s\n", "OPTIONS:");
  fprintf(stderr, "%s\n", "  -e <expr>            execute expression");
  fprintf(stderr, "%s\n", "  -t                   dump generated text AST");
  fprintf(stderr, "%s\n", "  -b                   dump generated binary AST");
  fprintf(stderr, "%s\n", "  -c                   dump compiled binary bcode");
  fprintf(stderr, "%s\n", "  -mm                  dump memory stats");
  fprintf(stderr, "%s\n", "  -vo <n>              object arena size");
  fprintf(stderr, "%s\n", "  -vf <n>              function arena size");
  fprintf(stderr, "%s\n", "  -vp <n>              property arena size");
#ifdef V7_FREEZE
  fprintf(stderr, "%s\n", "  -freeze filename     dump JS heap into a file");
#endif
  exit(EXIT_FAILURE);
}

#if V7_ENABLE__Memory__stats
static void dump_mm_arena_stats(const char *msg, struct gc_arena *a) {
  printf("%s: total allocations %lu, total garbage %lu, max %" SIZE_T_FMT
         ", alive %lu\n",
         msg, a->allocations, a->garbage, gc_arena_size(a), a->alive);
  printf(
      "%s: (bytes: total allocations %lu, total garbage %lu, max %" SIZE_T_FMT
      ", alive %lu)\n",
      msg, a->allocations * a->cell_size, a->garbage * a->cell_size,
      gc_arena_size(a) * a->cell_size, a->alive * a->cell_size);
}

static void dump_mm_stats(struct v7 *v7) {
  dump_mm_arena_stats("object: ", &v7->generic_object_arena);
  dump_mm_arena_stats("function: ", &v7->function_arena);
  dump_mm_arena_stats("property: ", &v7->property_arena);
  printf("string arena len: %" SIZE_T_FMT "\n", v7->owned_strings.len);
  printf("Total heap size: %" SIZE_T_FMT "\n",
         v7->owned_strings.len +
             gc_arena_size(&v7->generic_object_arena) *
                 v7->generic_object_arena.cell_size +
             gc_arena_size(&v7->function_arena) * v7->function_arena.cell_size +
             gc_arena_size(&v7->property_arena) * v7->property_arena.cell_size);
}
#endif

int v7_main(int argc, char *argv[], void (*pre_freeze_init)(struct v7 *),
            void (*pre_init)(struct v7 *), void (*post_init)(struct v7 *)) {
  int exit_rcode = EXIT_SUCCESS;
  struct v7 *v7;
  struct v7_create_opts opts;
  int as_json = 0;
  int i, j, show_ast = 0, binary_ast = 0, dump_bcode = 0, dump_stats = 0;
  val_t res;
  int nexprs = 0;
  const char *exprs[16];

  memset(&opts, 0, sizeof(opts));

  (void) show_ast;
  (void) binary_ast;
  (void) dump_bcode;

  /* Execute inline code */
  for (i = 1; i < argc && argv[i][0] == '-'; i++) {
    if (strcmp(argv[i], "-e") == 0 && i + 1 < argc) {
      exprs[nexprs++] = argv[i + 1];
      i++;
    } else if (strcmp(argv[i], "-t") == 0) {
      show_ast = 1;
    } else if (strcmp(argv[i], "-b") == 0) {
      show_ast = 1;
      binary_ast = 1;
    } else if (strcmp(argv[i], "-c") == 0) {
      binary_ast = 1;
      dump_bcode = 1;
    } else if (strcmp(argv[i], "-h") == 0) {
      show_usage(argv);
    } else if (strcmp(argv[i], "-j") == 0) {
      as_json = 1;
#if V7_ENABLE__Memory__stats
    } else if (strcmp(argv[i], "-mm") == 0) {
      dump_stats = 1;
#endif
    } else if (strcmp(argv[i], "-vo") == 0 && i + 1 < argc) {
      opts.object_arena_size = atoi(argv[i + 1]);
      i++;
    } else if (strcmp(argv[i], "-vf") == 0 && i + 1 < argc) {
      opts.function_arena_size = atoi(argv[i + 1]);
      i++;
    } else if (strcmp(argv[i], "-vp") == 0 && i + 1 < argc) {
      opts.property_arena_size = atoi(argv[i + 1]);
      i++;
    }
#ifdef V7_FREEZE
    else if (strcmp(argv[i], "-freeze") == 0 && i + 1 < argc) {
      opts.freeze_file = argv[i + 1];
      i++;
    }
#endif
  }

#ifndef V7_ALLOW_ARGLESS_MAIN
  if (argc == 1) {
    show_usage(argv);
  }
#endif

  v7 = v7_create_opt(opts);
  res = V7_UNDEFINED;

  if (pre_freeze_init != NULL) {
    pre_freeze_init(v7);
  }

#ifdef V7_FREEZE
  /*
   * Skip pre_init if freezing, but still execute cmdline expressions.
   * This makes it easier to add custom code when freezing from cmdline.
   */
  if (opts.freeze_file == NULL) {
#endif

    if (pre_init != NULL) {
      pre_init(v7);
    }

#ifdef V7_FREEZE
  }
#endif

#if V7_ENABLE__Memory__stats > 0 && !defined(V7_DISABLE_GC)
  if (dump_stats) {
    printf("Memory stats during init:\n");
    dump_mm_stats(v7);
    v7_gc(v7, 0);
    printf("Memory stats before run:\n");
    dump_mm_stats(v7);
  }
#else
  (void) dump_stats;
#endif

  /* Execute inline expressions */
  for (j = 0; j < nexprs; j++) {
    enum v7_err (*exec)(struct v7 *, const char *, v7_val_t *);
    exec = v7_exec;

    if (show_ast || dump_bcode) {
#if !defined(V7_NO_COMPILER)
      if (v7_compile(exprs[j], binary_ast, dump_bcode, stdout) != V7_OK) {
        exit_rcode = EXIT_FAILURE;
        fprintf(stderr, "%s\n", "parse error");
      }
#else  /* V7_NO_COMPILER */
      exit_rcode = EXIT_FAILURE;
      fprintf(stderr, "%s\n", "Parsing is disabled by V7_NO_COMPILER");
#endif /* V7_NO_COMPILER */
    } else if (exec(v7, exprs[j], &res) != V7_OK) {
      v7_print_error(stderr, v7, exprs[j], res);
      exit_rcode = EXIT_FAILURE;
      res = V7_UNDEFINED;
    }
  }

  /* Execute files */
  for (; i < argc; i++) {
    if (show_ast || dump_bcode) {
#if !defined(V7_NO_COMPILER)
      size_t size;
      char *source_code;
      if ((source_code = cs_read_file(argv[i], &size)) == NULL) {
        exit_rcode = EXIT_FAILURE;
        fprintf(stderr, "Cannot read [%s]\n", argv[i]);
      } else {
        if (_v7_compile(source_code, size, binary_ast, dump_bcode, stdout) !=
            V7_OK) {
          fprintf(stderr, "error: %s\n", v7->error_msg);
          exit_rcode = EXIT_FAILURE;
          exit(exit_rcode);
        }
        free(source_code);
      }
#else  /* V7_NO_COMPILER */
      exit_rcode = EXIT_FAILURE;
      fprintf(stderr, "%s\n", "Parsing is disabled by V7_NO_COMPILER");
#endif /* V7_NO_COMPILER */
    } else if (v7_exec_file(v7, argv[i], &res) != V7_OK) {
      v7_print_error(stderr, v7, argv[i], res);
      res = V7_UNDEFINED;
    }
  }

#ifdef V7_FREEZE
  if (opts.freeze_file != NULL) {
    freeze(v7, opts.freeze_file);
    exit(0);
  }
#endif

  if (!(show_ast || dump_bcode)) {
    char buf[2000];
    char *s = v7_stringify(v7, res, buf, sizeof(buf),
                           as_json ? V7_STRINGIFY_JSON : V7_STRINGIFY_DEBUG);
    printf("%s\n", s);
    if (s != buf) {
      free(s);
    }
  }

  if (post_init != NULL) {
    post_init(v7);
  }

#if V7_ENABLE__Memory__stats
  if (dump_stats) {
    printf("Memory stats after run:\n");
    dump_mm_stats(v7);
  }
#else
  (void) dump_stats;
#endif

  v7_destroy(v7);
  return exit_rcode;
}
#endif

#ifdef V7_EXE
int main(int argc, char *argv[]) {
  return v7_main(argc, argv, NULL, NULL, NULL);
}
#endif
#endif /* V7_EXPORT_INTERNAL_HEADERS */