Permalink
Cannot retrieve contributors at this time
Fetching contributors…
| #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_ESP32 15 | |
| #define CS_P_ESP8266 3 | |
| #define CS_P_CC3100 6 | |
| #define CS_P_CC3200 4 | |
| #define CS_P_CC3220 17 | |
| #define CS_P_MSP432 5 | |
| #define CS_P_TM4C129 14 | |
| #define CS_P_MBED 7 | |
| #define CS_P_WINCE 8 | |
| #define CS_P_NXP_LPC 13 | |
| #define CS_P_NXP_KINETIS 9 | |
| #define CS_P_NRF51 12 | |
| #define CS_P_NRF52 10 | |
| #define CS_P_PIC32 11 | |
| #define CS_P_STM32 16 | |
| /* Next id: 18 */ | |
| /* 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) || defined(TARGET_IS_CC3200) | |
| #define CS_PLATFORM CS_P_CC3200 | |
| #elif defined(cc3220) || defined(TARGET_IS_CC3220) | |
| #define CS_PLATFORM CS_P_CC3220 | |
| #elif defined(__unix__) || defined(__APPLE__) | |
| #define CS_PLATFORM CS_P_UNIX | |
| #elif defined(WINCE) | |
| #define CS_PLATFORM CS_P_WINCE | |
| #elif defined(_WIN32) | |
| #define CS_PLATFORM CS_P_WINDOWS | |
| #elif defined(__MBED__) | |
| #define CS_PLATFORM CS_P_MBED | |
| #elif defined(__USE_LPCOPEN) | |
| #define CS_PLATFORM CS_P_NXP_LPC | |
| #elif defined(FRDM_K64F) || defined(FREEDOM) | |
| #define CS_PLATFORM CS_P_NXP_KINETIS | |
| #elif defined(PIC32) | |
| #define CS_PLATFORM CS_P_PIC32 | |
| #elif defined(ESP_PLATFORM) | |
| #define CS_PLATFORM CS_P_ESP32 | |
| #elif defined(ICACHE_FLASH) | |
| #define CS_PLATFORM CS_P_ESP8266 | |
| #elif defined(TARGET_IS_TM4C129_RA0) || defined(TARGET_IS_TM4C129_RA1) || \ | |
| defined(TARGET_IS_TM4C129_RA2) | |
| #define CS_PLATFORM CS_P_TM4C129 | |
| #elif defined(STM32) | |
| #define CS_PLATFORM CS_P_STM32 | |
| #endif | |
| #ifndef CS_PLATFORM | |
| #error "CS_PLATFORM is not specified and we couldn't guess it." | |
| #endif | |
| #endif /* !defined(CS_PLATFORM) */ | |
| #define MG_NET_IF_SOCKET 1 | |
| #define MG_NET_IF_SIMPLELINK 2 | |
| #define MG_NET_IF_LWIP_LOW_LEVEL 3 | |
| #define MG_NET_IF_PIC32 4 | |
| #define MG_SSL_IF_OPENSSL 1 | |
| #define MG_SSL_IF_MBEDTLS 2 | |
| #define MG_SSL_IF_SIMPLELINK 3 | |
| /* Amalgamated: #include "common/platforms/platform_unix.h" */ | |
| /* Amalgamated: #include "common/platforms/platform_windows.h" */ | |
| /* Amalgamated: #include "common/platforms/platform_esp32.h" */ | |
| /* Amalgamated: #include "common/platforms/platform_esp8266.h" */ | |
| /* Amalgamated: #include "common/platforms/platform_cc3100.h" */ | |
| /* Amalgamated: #include "common/platforms/platform_cc3200.h" */ | |
| /* Amalgamated: #include "common/platforms/platform_cc3220.h" */ | |
| /* Amalgamated: #include "common/platforms/platform_mbed.h" */ | |
| /* Amalgamated: #include "common/platforms/platform_nrf51.h" */ | |
| /* Amalgamated: #include "common/platforms/platform_nrf52.h" */ | |
| /* Amalgamated: #include "common/platforms/platform_wince.h" */ | |
| /* Amalgamated: #include "common/platforms/platform_nxp_lpc.h" */ | |
| /* Amalgamated: #include "common/platforms/platform_nxp_kinetis.h" */ | |
| /* Amalgamated: #include "common/platforms/platform_pic32.h" */ | |
| /* Amalgamated: #include "common/platforms/platform_stm32.h" */ | |
| /* Common stuff */ | |
| #if !defined(WEAK) | |
| #if (defined(__GNUC__) || defined(__TI_COMPILER_VERSION__)) && !defined(_WIN32) | |
| #define WEAK __attribute__((weak)) | |
| #else | |
| #define WEAK | |
| #endif | |
| #endif | |
| #ifdef __GNUC__ | |
| #define NORETURN __attribute__((noreturn)) | |
| #define NOINLINE __attribute__((noinline)) | |
| #define WARN_UNUSED_RESULT __attribute__((warn_unused_result)) | |
| #define NOINSTR __attribute__((no_instrument_function)) | |
| #define DO_NOT_WARN_UNUSED __attribute__((unused)) | |
| #else | |
| #define NORETURN | |
| #define NOINLINE | |
| #define WARN_UNUSED_RESULT | |
| #define NOINSTR | |
| #define DO_NOT_WARN_UNUSED | |
| #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 | |
| #ifndef _WINSOCK_DEPRECATED_NO_WARNINGS | |
| #define _WINSOCK_DEPRECATED_NO_WARNINGS 1 | |
| #endif | |
| #ifndef _CRT_SECURE_NO_WARNINGS | |
| #define _CRT_SECURE_NO_WARNINGS | |
| #endif | |
| #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> | |
| #include <ctype.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 vsnprintf _vsnprintf | |
| #define to64(x) _atoi64(x) | |
| #if !defined(__MINGW32__) && !defined(__MINGW64__) | |
| #define popen(x, y) _popen((x), (y)) | |
| #define pclose(x) _pclose(x) | |
| #define fileno _fileno | |
| #endif | |
| #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" | |
| typedef struct _stati64 cs_stat_t; | |
| #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 '\\' | |
| #define CS_DEFINE_DIRENT | |
| #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 | |
| #ifndef CS_ENABLE_STDIO | |
| #define CS_ENABLE_STDIO 1 | |
| #endif | |
| #ifndef MG_ENABLE_BROADCAST | |
| #define MG_ENABLE_BROADCAST 1 | |
| #endif | |
| #ifndef MG_ENABLE_DIRECTORY_LISTING | |
| #define MG_ENABLE_DIRECTORY_LISTING 1 | |
| #endif | |
| #ifndef MG_ENABLE_FILESYSTEM | |
| #define MG_ENABLE_FILESYSTEM 1 | |
| #endif | |
| #ifndef MG_ENABLE_HTTP_CGI | |
| #define MG_ENABLE_HTTP_CGI MG_ENABLE_FILESYSTEM | |
| #endif | |
| #ifndef MG_NET_IF | |
| #define MG_NET_IF MG_NET_IF_SOCKET | |
| #endif | |
| int rmdir(const char *dirname); | |
| unsigned int sleep(unsigned int seconds); | |
| #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/param.h> | |
| #include <sys/socket.h> | |
| #include <sys/select.h> | |
| #include <sys/stat.h> | |
| #include <sys/time.h> | |
| #include <sys/types.h> | |
| #include <unistd.h> | |
| #ifdef __APPLE__ | |
| #include <machine/endian.h> | |
| #ifndef BYTE_ORDER | |
| #define LITTLE_ENDIAN __DARWIN_LITTLE_ENDIAN | |
| #define BIG_ENDIAN __DARWIN_BIG_ENDIAN | |
| #define PDP_ENDIAN __DARWIN_PDP_ENDIAN | |
| #define BYTE_ORDER __DARWIN_BYTE_ORDER | |
| #endif | |
| #endif | |
| /* | |
| * osx correctly avoids defining strtoll when compiling in strict ansi mode. | |
| * c++ 11 standard defines strtoll as well. | |
| * We require strtoll, and if your embedded pre-c99 compiler lacks one, please | |
| * implement a shim. | |
| */ | |
| #if !(defined(__cplusplus) && __cplusplus >= 201103L) && \ | |
| !(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 | |
| #ifndef CS_ENABLE_STDIO | |
| #define CS_ENABLE_STDIO 1 | |
| #endif | |
| #ifndef MG_ENABLE_BROADCAST | |
| #define MG_ENABLE_BROADCAST 1 | |
| #endif | |
| #ifndef MG_ENABLE_DIRECTORY_LISTING | |
| #define MG_ENABLE_DIRECTORY_LISTING 1 | |
| #endif | |
| #ifndef MG_ENABLE_FILESYSTEM | |
| #define MG_ENABLE_FILESYSTEM 1 | |
| #endif | |
| #ifndef MG_ENABLE_HTTP_CGI | |
| #define MG_ENABLE_HTTP_CGI MG_ENABLE_FILESYSTEM | |
| #endif | |
| #ifndef MG_NET_IF | |
| #define MG_NET_IF MG_NET_IF_SOCKET | |
| #endif | |
| #ifndef MG_HOSTS_FILE_NAME | |
| #define MG_HOSTS_FILE_NAME "/etc/hosts" | |
| #endif | |
| #ifndef MG_RESOLV_CONF_FILE_NAME | |
| #define MG_RESOLV_CONF_FILE_NAME "/etc/resolv.conf" | |
| #endif | |
| #endif /* CS_PLATFORM == CS_P_UNIX */ | |
| #endif /* CS_COMMON_PLATFORMS_PLATFORM_UNIX_H_ */ | |
| #ifdef V7_MODULE_LINES | |
| #line 1 "common/platforms/platform_esp32.h" | |
| #endif | |
| /* | |
| * Copyright (c) 2014-2016 Cesanta Software Limited | |
| * All rights reserved | |
| */ | |
| #ifndef CS_COMMON_PLATFORMS_PLATFORM_ESP32_H_ | |
| #define CS_COMMON_PLATFORMS_PLATFORM_ESP32_H_ | |
| #if CS_PLATFORM == CS_P_ESP32 | |
| #include <assert.h> | |
| #include <ctype.h> | |
| #include <dirent.h> | |
| #include <fcntl.h> | |
| #include <inttypes.h> | |
| #include <machine/endian.h> | |
| #include <stdint.h> | |
| #include <string.h> | |
| #include <sys/stat.h> | |
| #include <sys/time.h> | |
| #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 _FILE_OFFSET_BITS 32 | |
| #define MG_LWIP 1 | |
| #ifndef MG_NET_IF | |
| #define MG_NET_IF MG_NET_IF_SOCKET | |
| #endif | |
| #ifndef CS_ENABLE_STDIO | |
| #define CS_ENABLE_STDIO 1 | |
| #endif | |
| #endif /* CS_PLATFORM == CS_P_ESP32 */ | |
| #endif /* CS_COMMON_PLATFORMS_PLATFORM_ESP32_H_ */ | |
| #ifdef V7_MODULE_LINES | |
| #line 1 "common/platforms/platform_esp8266.h" | |
| #endif | |
| /* | |
| * Copyright (c) 2014-2016 Cesanta Software Limited | |
| * All rights reserved | |
| */ | |
| #ifndef CS_COMMON_PLATFORMS_PLATFORM_ESP8266_H_ | |
| #define CS_COMMON_PLATFORMS_PLATFORM_ESP8266_H_ | |
| #if CS_PLATFORM == CS_P_ESP8266 | |
| #include <assert.h> | |
| #include <ctype.h> | |
| #include <fcntl.h> | |
| #include <inttypes.h> | |
| #include <machine/endian.h> | |
| #include <string.h> | |
| #include <sys/stat.h> | |
| #include <sys/time.h> | |
| #define SIZE_T_FMT "u" | |
| typedef struct stat cs_stat_t; | |
| #define DIRSEP '/' | |
| #if !defined(MGOS_VFS_DEFINE_DIRENT) | |
| #define CS_DEFINE_DIRENT | |
| #endif | |
| #define to64(x) strtoll(x, NULL, 10) | |
| #define INT64_FMT PRId64 | |
| #define INT64_X_FMT PRIx64 | |
| #define __cdecl | |
| #define _FILE_OFFSET_BITS 32 | |
| #if !defined(RTOS_SDK) && !defined(__cplusplus) | |
| #define fileno(x) -1 | |
| #endif | |
| #define MG_LWIP 1 | |
| /* struct timeval is defined in sys/time.h. */ | |
| #define LWIP_TIMEVAL_PRIVATE 0 | |
| #ifndef MG_NET_IF | |
| #include <lwip/opt.h> | |
| #if LWIP_SOCKET /* RTOS SDK has LWIP sockets */ | |
| #define MG_NET_IF MG_NET_IF_SOCKET | |
| #else | |
| #define MG_NET_IF MG_NET_IF_LWIP_LOW_LEVEL | |
| #endif | |
| #endif | |
| #ifndef CS_ENABLE_STDIO | |
| #define CS_ENABLE_STDIO 1 | |
| #endif | |
| #endif /* CS_PLATFORM == CS_P_ESP8266 */ | |
| #endif /* CS_COMMON_PLATFORMS_PLATFORM_ESP8266_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_NET_IF MG_NET_IF_SIMPLELINK | |
| #define MG_SSL_IF MG_SSL_IF_SIMPLELINK | |
| /* | |
| * 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/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 defined(MG_NET_IF) && MG_NET_IF == MG_NET_IF_SIMPLELINK | |
| /* If simplelink.h is already included, all bets are off. */ | |
| #if !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 | |
| #if CS_PLATFORM == CS_P_CC3220 | |
| #include <ti/drivers/net/wifi/porting/user.h> | |
| #include <ti/drivers/net/wifi/simplelink.h> | |
| #include <ti/drivers/net/wifi/sl_socket.h> | |
| #include <ti/drivers/net/wifi/netapp.h> | |
| #else | |
| /* 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> | |
| #endif /* CS_PLATFORM == CS_P_CC3220 */ | |
| /* 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(__SIMPLELINK_H__) */ | |
| /* Compatibility with older versions of SimpleLink */ | |
| #if SL_MAJOR_VERSION_NUM < 2 | |
| #define SL_ERROR_BSD_EAGAIN SL_EAGAIN | |
| #define SL_ERROR_BSD_EALREADY SL_EALREADY | |
| #define SL_ERROR_BSD_ENOPROTOOPT SL_ENOPROTOOPT | |
| #define SL_ERROR_BSD_ESECDATEERROR SL_ESECDATEERROR | |
| #define SL_ERROR_BSD_ESECSNOVERIFY SL_ESECSNOVERIFY | |
| #define SL_ERROR_FS_FAILED_TO_ALLOCATE_MEM SL_FS_ERR_FAILED_TO_ALLOCATE_MEM | |
| #define SL_ERROR_FS_FILE_HAS_NOT_BEEN_CLOSE_CORRECTLY \ | |
| SL_FS_FILE_HAS_NOT_BEEN_CLOSE_CORRECTLY | |
| #define SL_ERROR_FS_FILE_NAME_EXIST SL_FS_FILE_NAME_EXIST | |
| #define SL_ERROR_FS_FILE_NOT_EXISTS SL_FS_ERR_FILE_NOT_EXISTS | |
| #define SL_ERROR_FS_NO_AVAILABLE_NV_INDEX SL_FS_ERR_NO_AVAILABLE_NV_INDEX | |
| #define SL_ERROR_FS_NOT_ENOUGH_STORAGE_SPACE SL_FS_ERR_NO_AVAILABLE_BLOCKS | |
| #define SL_ERROR_FS_NOT_SUPPORTED SL_FS_ERR_NOT_SUPPORTED | |
| #define SL_ERROR_FS_WRONG_FILE_NAME SL_FS_WRONG_FILE_NAME | |
| #define SL_ERROR_FS_INVALID_HANDLE SL_FS_ERR_INVALID_HANDLE | |
| #define SL_NETCFG_MAC_ADDRESS_GET SL_MAC_ADDRESS_GET | |
| #define SL_SOCKET_FD_ZERO SL_FD_ZERO | |
| #define SL_SOCKET_FD_SET SL_FD_SET | |
| #define SL_SOCKET_FD_ISSET SL_FD_ISSET | |
| #define SL_SO_SECURE_DOMAIN_NAME_VERIFICATION SO_SECURE_DOMAIN_NAME_VERIFICATION | |
| #define SL_FS_READ FS_MODE_OPEN_READ | |
| #define SL_FS_WRITE FS_MODE_OPEN_WRITE | |
| #define SL_FI_FILE_SIZE(fi) ((fi).FileLen) | |
| #define SL_FI_FILE_MAX_SIZE(fi) ((fi).AllocatedLen) | |
| #define SlDeviceVersion_t SlVersionFull | |
| #define sl_DeviceGet sl_DevGet | |
| #define SL_DEVICE_GENERAL SL_DEVICE_GENERAL_CONFIGURATION | |
| #define SL_LEN_TYPE _u8 | |
| #define SL_OPT_TYPE _u8 | |
| #else /* SL_MAJOR_VERSION_NUM >= 2 */ | |
| #define FS_MODE_OPEN_CREATE(max_size, flag) \ | |
| (SL_FS_CREATE | SL_FS_CREATE_MAX_SIZE(max_size)) | |
| #define SL_FI_FILE_SIZE(fi) ((fi).Len) | |
| #define SL_FI_FILE_MAX_SIZE(fi) ((fi).MaxSize) | |
| #define SL_LEN_TYPE _u16 | |
| #define SL_OPT_TYPE _u16 | |
| #endif /* SL_MAJOR_VERSION_NUM < 2 */ | |
| int slfs_open(const unsigned char *fname, uint32_t flags); | |
| #endif /* MG_NET_IF == MG_NET_IF_SIMPLELINK */ | |
| #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_NET_IF MG_NET_IF_SIMPLELINK | |
| #define MG_SSL_IF MG_SSL_IF_SIMPLELINK | |
| /* Only SPIFFS supports directories, SLFS does not. */ | |
| #if defined(CC3200_FS_SPIFFS) && !defined(MG_ENABLE_DIRECTORY_LISTING) | |
| #define MG_ENABLE_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 settimeofday(const struct timeval *tv, const 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); | |
| int stat(const char *pathname, struct stat *st); | |
| #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) | |
| /* 5.x series compilers don't have va_copy, 16.x do. */ | |
| #if __TI_COMPILER_VERSION__ < 16000000 | |
| #define va_copy(apc, ap) ((apc) = (ap)) | |
| #endif | |
| #endif /* __TI_COMPILER_VERSION__ */ | |
| #ifdef CC3200_FS_SLFS | |
| #define MG_FS_SLFS | |
| #endif | |
| #if (defined(CC3200_FS_SPIFFS) || defined(CC3200_FS_SLFS)) && \ | |
| !defined(MG_ENABLE_FILESYSTEM) | |
| #define MG_ENABLE_FILESYSTEM 1 | |
| #define CS_DEFINE_DIRENT | |
| #endif | |
| #ifndef CS_ENABLE_STDIO | |
| #define CS_ENABLE_STDIO 1 | |
| #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_cc3220.h" | |
| #endif | |
| /* | |
| * Copyright (c) 2014-2016 Cesanta Software Limited | |
| * All rights reserved | |
| */ | |
| #ifndef CS_COMMON_PLATFORMS_PLATFORM_CC3220_H_ | |
| #define CS_COMMON_PLATFORMS_PLATFORM_CC3220_H_ | |
| #if CS_PLATFORM == CS_P_CC3220 | |
| #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_NET_IF MG_NET_IF_SIMPLELINK | |
| #define MG_SSL_IF MG_SSL_IF_SIMPLELINK | |
| /* Only SPIFFS supports directories, SLFS does not. */ | |
| #if defined(CC3220_FS_SPIFFS) && !defined(MG_ENABLE_DIRECTORY_LISTING) | |
| #define MG_ENABLE_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 settimeofday(const struct timeval *tv, const 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); | |
| int stat(const char *pathname, struct stat *st); | |
| #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) | |
| #endif /* __TI_COMPILER_VERSION__ */ | |
| #ifndef CS_ENABLE_STDIO | |
| #define CS_ENABLE_STDIO 1 | |
| #endif | |
| #ifdef __cplusplus | |
| } | |
| #endif | |
| #endif /* CS_PLATFORM == CS_P_CC3220 */ | |
| #endif /* CS_COMMON_PLATFORMS_PLATFORM_CC3200_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 | |
| /* | |
| * mbed.h contains C++ code (e.g. templates), thus, it should be processed | |
| * only if included directly to startup file (ex: main.cpp) | |
| */ | |
| #ifdef __cplusplus | |
| /* Amalgamated: #include "mbed.h" */ | |
| #endif /* __cplusplus */ | |
| #include <assert.h> | |
| #include <ctype.h> | |
| #include <errno.h> | |
| #include <inttypes.h> | |
| #include <stdint.h> | |
| #include <string.h> | |
| #include <time.h> | |
| #include <sys/stat.h> | |
| #include <sys/types.h> | |
| #include <fcntl.h> | |
| #include <stdio.h> | |
| typedef struct stat cs_stat_t; | |
| #define DIRSEP '/' | |
| #ifndef CS_ENABLE_STDIO | |
| #define CS_ENABLE_STDIO 1 | |
| #endif | |
| /* | |
| * mbed can be compiled with the ARM compiler which | |
| * just doesn't come with a gettimeofday shim | |
| * because it's a BSD API and ARM targets embedded | |
| * non-unix platforms. | |
| */ | |
| #if defined(__ARMCC_VERSION) || defined(__ICCARM__) | |
| #define _TIMEVAL_DEFINED | |
| #define gettimeofday _gettimeofday | |
| /* copied from GCC on ARM; for some reason useconds are signed */ | |
| typedef long suseconds_t; /* microseconds (signed) */ | |
| struct timeval { | |
| time_t tv_sec; /* seconds */ | |
| suseconds_t tv_usec; /* and microseconds */ | |
| }; | |
| #endif | |
| #if MG_NET_IF == MG_NET_IF_SIMPLELINK | |
| #define MG_SIMPLELINK_NO_OSI 1 | |
| #include <simplelink.h> | |
| 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); | |
| int inet_aton(const char *cp, struct in_addr *inp); | |
| in_addr_t inet_addr(const char *cp); | |
| #endif /* MG_NET_IF == MG_NET_IF_SIMPLELINK */ | |
| #endif /* CS_PLATFORM == CS_P_MBED */ | |
| #endif /* CS_COMMON_PLATFORMS_PLATFORM_MBED_H_ */ | |
| #ifdef V7_MODULE_LINES | |
| #line 1 "common/platforms/platform_nrf51.h" | |
| #endif | |
| /* | |
| * Copyright (c) 2014-2016 Cesanta Software Limited | |
| * All rights reserved | |
| */ | |
| #ifndef CS_COMMON_PLATFORMS_PLATFORM_NRF51_H_ | |
| #define CS_COMMON_PLATFORMS_PLATFORM_NRF51_H_ | |
| #if CS_PLATFORM == CS_P_NRF51 | |
| #include <assert.h> | |
| #include <ctype.h> | |
| #include <inttypes.h> | |
| #include <stdint.h> | |
| #include <string.h> | |
| #include <time.h> | |
| #define to64(x) strtoll(x, NULL, 10) | |
| #define MG_NET_IF MG_NET_IF_LWIP_LOW_LEVEL | |
| #define MG_LWIP 1 | |
| #define MG_ENABLE_IPV6 1 | |
| /* | |
| * For ARM C Compiler, make lwip to export `struct timeval`; for other | |
| * compilers, suppress it. | |
| */ | |
| #if !defined(__ARMCC_VERSION) | |
| #define LWIP_TIMEVAL_PRIVATE 0 | |
| #else | |
| struct timeval; | |
| int gettimeofday(struct timeval *tp, void *tzp); | |
| #endif | |
| #define INT64_FMT PRId64 | |
| #define SIZE_T_FMT "u" | |
| /* | |
| * ARM C Compiler doesn't have strdup, so we provide it | |
| */ | |
| #define CS_ENABLE_STRDUP defined(__ARMCC_VERSION) | |
| #endif /* CS_PLATFORM == CS_P_NRF51 */ | |
| #endif /* CS_COMMON_PLATFORMS_PLATFORM_NRF51_H_ */ | |
| #ifdef V7_MODULE_LINES | |
| #line 1 "common/platforms/platform_nrf52.h" | |
| #endif | |
| /* | |
| * Copyright (c) 2014-2016 Cesanta Software Limited | |
| * All rights reserved | |
| */ | |
| #ifndef CS_COMMON_PLATFORMS_PLATFORM_NRF52_H_ | |
| #define CS_COMMON_PLATFORMS_PLATFORM_NRF52_H_ | |
| #if CS_PLATFORM == CS_P_NRF52 | |
| #include <assert.h> | |
| #include <ctype.h> | |
| #include <errno.h> | |
| #include <inttypes.h> | |
| #include <stdint.h> | |
| #include <string.h> | |
| #include <time.h> | |
| #define to64(x) strtoll(x, NULL, 10) | |
| #define MG_NET_IF MG_NET_IF_LWIP_LOW_LEVEL | |
| #define MG_LWIP 1 | |
| #define MG_ENABLE_IPV6 1 | |
| #if !defined(ENOSPC) | |
| #define ENOSPC 28 /* No space left on device */ | |
| #endif | |
| /* | |
| * For ARM C Compiler, make lwip to export `struct timeval`; for other | |
| * compilers, suppress it. | |
| */ | |
| #if !defined(__ARMCC_VERSION) | |
| #define LWIP_TIMEVAL_PRIVATE 0 | |
| #endif | |
| #define INT64_FMT PRId64 | |
| #define SIZE_T_FMT "u" | |
| /* | |
| * ARM C Compiler doesn't have strdup, so we provide it | |
| */ | |
| #define CS_ENABLE_STRDUP defined(__ARMCC_VERSION) | |
| #endif /* CS_PLATFORM == CS_P_NRF52 */ | |
| #endif /* CS_COMMON_PLATFORMS_PLATFORM_NRF52_H_ */ | |
| #ifdef V7_MODULE_LINES | |
| #line 1 "common/platforms/platform_wince.h" | |
| #endif | |
| #ifndef CS_COMMON_PLATFORMS_PLATFORM_WINCE_H_ | |
| #define CS_COMMON_PLATFORMS_PLATFORM_WINCE_H_ | |
| #if CS_PLATFORM == CS_P_WINCE | |
| /* | |
| * 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 | |
| */ | |
| #pragma warning(disable : 4127) /* FD_SET() emits warning, disable it */ | |
| #pragma warning(disable : 4204) /* missing c99 support */ | |
| #ifndef _WINSOCK_DEPRECATED_NO_WARNINGS | |
| #define _WINSOCK_DEPRECATED_NO_WARNINGS 1 | |
| #endif | |
| #ifndef _CRT_SECURE_NO_WARNINGS | |
| #define _CRT_SECURE_NO_WARNINGS | |
| #endif | |
| #include <assert.h> | |
| #include <limits.h> | |
| #include <stddef.h> | |
| #include <stdio.h> | |
| #include <stdlib.h> | |
| #include <time.h> | |
| #pragma comment(lib, "ws2.lib") /* Linking with WinCE winsock library */ | |
| #include <winsock2.h> | |
| #include <ws2tcpip.h> | |
| #include <windows.h> | |
| #define strdup _strdup | |
| #ifndef EINPROGRESS | |
| #define EINPROGRESS WSAEINPROGRESS | |
| #endif | |
| #ifndef EWOULDBLOCK | |
| #define EWOULDBLOCK WSAEWOULDBLOCK | |
| #endif | |
| #ifndef EAGAIN | |
| #define EAGAIN EWOULDBLOCK | |
| #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) | |
| #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 | |
| 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" | |
| /* TODO(alashkin): check if this is correct */ | |
| #define SIZE_T_FMT "u" | |
| #define DIRSEP '\\' | |
| #define CS_DEFINE_DIRENT | |
| #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 | |
| #ifndef CS_ENABLE_STDIO | |
| #define CS_ENABLE_STDIO 1 | |
| #endif | |
| #define abort() DebugBreak(); | |
| #ifndef BUFSIZ | |
| #define BUFSIZ 4096 | |
| #endif | |
| /* | |
| * Explicitly disabling MG_ENABLE_THREADS for WinCE | |
| * because they are enabled for _WIN32 by default | |
| */ | |
| #ifndef MG_ENABLE_THREADS | |
| #define MG_ENABLE_THREADS 0 | |
| #endif | |
| #ifndef MG_ENABLE_FILESYSTEM | |
| #define MG_ENABLE_FILESYSTEM 1 | |
| #endif | |
| #ifndef MG_NET_IF | |
| #define MG_NET_IF MG_NET_IF_SOCKET | |
| #endif | |
| typedef struct _stati64 { | |
| uint32_t st_mtime; | |
| uint32_t st_size; | |
| uint32_t st_mode; | |
| } cs_stat_t; | |
| /* | |
| * WinCE 6.0 has a lot of useful definitions in ATL (not windows.h) headers | |
| * use #ifdefs to avoid conflicts | |
| */ | |
| #ifndef ENOENT | |
| #define ENOENT ERROR_PATH_NOT_FOUND | |
| #endif | |
| #ifndef EACCES | |
| #define EACCES ERROR_ACCESS_DENIED | |
| #endif | |
| #ifndef ENOMEM | |
| #define ENOMEM ERROR_NOT_ENOUGH_MEMORY | |
| #endif | |
| #ifndef _UINTPTR_T_DEFINED | |
| typedef unsigned int *uintptr_t; | |
| #endif | |
| #define _S_IFREG 2 | |
| #define _S_IFDIR 4 | |
| #ifndef S_ISDIR | |
| #define S_ISDIR(x) (((x) &_S_IFDIR) != 0) | |
| #endif | |
| #ifndef S_ISREG | |
| #define S_ISREG(x) (((x) &_S_IFREG) != 0) | |
| #endif | |
| int open(const char *filename, int oflag, int pmode); | |
| int _wstati64(const wchar_t *path, cs_stat_t *st); | |
| const char *strerror(); | |
| #endif /* CS_PLATFORM == CS_P_WINCE */ | |
| #endif /* CS_COMMON_PLATFORMS_PLATFORM_WINCE_H_ */ | |
| #ifdef V7_MODULE_LINES | |
| #line 1 "common/platforms/platform_nxp_lpc.h" | |
| #endif | |
| /* | |
| * Copyright (c) 2014-2016 Cesanta Software Limited | |
| * All rights reserved | |
| */ | |
| #ifndef CS_COMMON_PLATFORMS_PLATFORM_NXP_LPC_H_ | |
| #define CS_COMMON_PLATFORMS_PLATFORM_NXP_LPC_H_ | |
| #if CS_PLATFORM == CS_P_NXP_LPC | |
| #include <ctype.h> | |
| #include <stdint.h> | |
| #include <string.h> | |
| #define SIZE_T_FMT "u" | |
| typedef struct stat cs_stat_t; | |
| #define INT64_FMT "lld" | |
| #define INT64_X_FMT "llx" | |
| #define __cdecl | |
| #define MG_LWIP 1 | |
| #define MG_NET_IF MG_NET_IF_LWIP_LOW_LEVEL | |
| /* | |
| * LPCXpress comes with 3 C library implementations: Newlib, NewlibNano and | |
| *Redlib. | |
| * See https://community.nxp.com/message/630860 for more details. | |
| * | |
| * Redlib is the default and lacks certain things, so we provide them. | |
| */ | |
| #ifdef __REDLIB_INTERFACE_VERSION__ | |
| /* Let LWIP define timeval for us. */ | |
| #define LWIP_TIMEVAL_PRIVATE 1 | |
| #define va_copy(d, s) __builtin_va_copy(d, s) | |
| #define CS_ENABLE_TO64 1 | |
| #define to64(x) cs_to64(x) | |
| #define CS_ENABLE_STRDUP 1 | |
| #else | |
| #include <sys/time.h> | |
| #define LWIP_TIMEVAL_PRIVATE 0 | |
| #define to64(x) strtoll(x, NULL, 10) | |
| #endif | |
| #endif /* CS_PLATFORM == CS_P_NXP_LPC */ | |
| #endif /* CS_COMMON_PLATFORMS_PLATFORM_NXP_LPC_H_ */ | |
| #ifdef V7_MODULE_LINES | |
| #line 1 "common/platforms/platform_nxp_kinetis.h" | |
| #endif | |
| /* | |
| * Copyright (c) 2014-2016 Cesanta Software Limited | |
| * All rights reserved | |
| */ | |
| #ifndef CS_COMMON_PLATFORMS_PLATFORM_NXP_KINETIS_H_ | |
| #define CS_COMMON_PLATFORMS_PLATFORM_NXP_KINETIS_H_ | |
| #if CS_PLATFORM == CS_P_NXP_KINETIS | |
| #include <ctype.h> | |
| #include <inttypes.h> | |
| #include <string.h> | |
| #include <sys/time.h> | |
| #define SIZE_T_FMT "u" | |
| typedef struct stat cs_stat_t; | |
| #define to64(x) strtoll(x, NULL, 10) | |
| #define INT64_FMT "lld" | |
| #define INT64_X_FMT "llx" | |
| #define __cdecl | |
| #define MG_LWIP 1 | |
| #define MG_NET_IF MG_NET_IF_LWIP_LOW_LEVEL | |
| /* struct timeval is defined in sys/time.h. */ | |
| #define LWIP_TIMEVAL_PRIVATE 0 | |
| #endif /* CS_PLATFORM == CS_P_NXP_KINETIS */ | |
| #endif /* CS_COMMON_PLATFORMS_PLATFORM_NXP_KINETIS_H_ */ | |
| #ifdef V7_MODULE_LINES | |
| #line 1 "common/platforms/platform_pic32.h" | |
| #endif | |
| /* | |
| * Copyright (c) 2014-2016 Cesanta Software Limited | |
| * All rights reserved | |
| */ | |
| #ifndef CS_COMMON_PLATFORMS_PLATFORM_PIC32_H_ | |
| #define CS_COMMON_PLATFORMS_PLATFORM_PIC32_H_ | |
| #if CS_PLATFORM == CS_P_PIC32 | |
| #define MG_NET_IF MG_NET_IF_PIC32 | |
| #include <stdint.h> | |
| #include <time.h> | |
| #include <ctype.h> | |
| #include <stdlib.h> | |
| #include <system_config.h> | |
| #include <system_definitions.h> | |
| #include <sys/types.h> | |
| typedef TCP_SOCKET sock_t; | |
| #define to64(x) strtoll(x, NULL, 10) | |
| #define SIZE_T_FMT "lu" | |
| #define INT64_FMT "lld" | |
| #ifndef CS_ENABLE_STDIO | |
| #define CS_ENABLE_STDIO 1 | |
| #endif | |
| char *inet_ntoa(struct in_addr in); | |
| #endif /* CS_PLATFORM == CS_P_PIC32 */ | |
| #endif /* CS_COMMON_PLATFORMS_PLATFORM_PIC32_H_ */ | |
| #ifdef V7_MODULE_LINES | |
| #line 1 "common/platforms/platform_stm32.h" | |
| #endif | |
| /* | |
| * Copyright (c) 2014-2016 Cesanta Software Limited | |
| * All rights reserved | |
| */ | |
| #ifndef CS_COMMON_PLATFORMS_PLATFORM_STM32_H_ | |
| #define CS_COMMON_PLATFORMS_PLATFORM_STM32_H_ | |
| #if CS_PLATFORM == CS_P_STM32 | |
| #include <sys/types.h> | |
| #include <sys/stat.h> | |
| #include <stdint.h> | |
| #include <inttypes.h> | |
| #include <stdio.h> | |
| #include <ctype.h> | |
| #include <errno.h> | |
| #include <memory.h> | |
| #include <fcntl.h> | |
| #include <stm32_sdk_hal.h> | |
| #define to64(x) strtoll(x, NULL, 10) | |
| #define INT64_FMT PRId64 | |
| #define SIZE_T_FMT "u" | |
| typedef struct stat cs_stat_t; | |
| #define DIRSEP '/' | |
| #ifndef CS_ENABLE_STDIO | |
| #define CS_ENABLE_STDIO 1 | |
| #endif | |
| #ifndef MG_ENABLE_FILESYSTEM | |
| #define MG_ENABLE_FILESYSTEM 1 | |
| #endif | |
| #define CS_DEFINE_DIRENT | |
| #endif /* CS_PLATFORM == CS_P_STM32 */ | |
| #endif /* CS_COMMON_PLATFORMS_PLATFORM_STM32_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_ | |
| #include <stdlib.h> | |
| /* Amalgamated: #include "common/platform.h" */ | |
| #if defined(__cplusplus) | |
| extern "C" { | |
| #endif | |
| #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/mg_mem.h" | |
| #endif | |
| /* | |
| * Copyright (c) 2014-2016 Cesanta Software Limited | |
| * All rights reserved | |
| */ | |
| #ifndef CS_COMMON_MG_MEM_H_ | |
| #define CS_COMMON_MG_MEM_H_ | |
| #ifdef __cplusplus | |
| extern "C" { | |
| #endif | |
| #ifndef MG_MALLOC | |
| #define MG_MALLOC malloc | |
| #endif | |
| #ifndef MG_CALLOC | |
| #define MG_CALLOC calloc | |
| #endif | |
| #ifndef MG_REALLOC | |
| #define MG_REALLOC realloc | |
| #endif | |
| #ifndef MG_FREE | |
| #define MG_FREE free | |
| #endif | |
| #ifdef __cplusplus | |
| } | |
| #endif | |
| #endif /* CS_COMMON_MG_MEM_H_ */ | |
| #ifdef V7_MODULE_LINES | |
| #line 1 "common/mg_str.h" | |
| #endif | |
| /* | |
| * Copyright (c) 2014-2016 Cesanta Software Limited | |
| * All rights reserved | |
| */ | |
| #ifndef CS_COMMON_MG_STR_H_ | |
| #define CS_COMMON_MG_STR_H_ | |
| #include <stddef.h> | |
| /* Amalgamated: #include "common/platform.h" */ | |
| #ifdef __cplusplus | |
| extern "C" { | |
| #endif | |
| /* Describes chunk of memory */ | |
| struct mg_str { | |
| const char *p; /* Memory chunk pointer */ | |
| size_t len; /* Memory chunk length */ | |
| }; | |
| /* | |
| * Helper functions for creating mg_str struct from plain C string. | |
| * `NULL` is allowed and becomes `{NULL, 0}`. | |
| */ | |
| struct mg_str mg_mk_str(const char *s); | |
| struct mg_str mg_mk_str_n(const char *s, size_t len); | |
| /* Macro for initializing mg_str. */ | |
| #define MG_MK_STR(str_literal) \ | |
| { str_literal, sizeof(str_literal) - 1 } | |
| #define MG_NULL_STR \ | |
| { NULL, 0 } | |
| /* | |
| * Cross-platform version of `strcmp()` where where first string is | |
| * specified by `struct mg_str`. | |
| */ | |
| int mg_vcmp(const struct mg_str *str2, const char *str1); | |
| /* | |
| * Cross-platform version of `strncasecmp()` where first string is | |
| * specified by `struct mg_str`. | |
| */ | |
| int mg_vcasecmp(const struct mg_str *str2, const char *str1); | |
| /* Creates a copy of s (heap-allocated). */ | |
| struct mg_str mg_strdup(const struct mg_str s); | |
| /* | |
| * Creates a copy of s (heap-allocated). | |
| * Resulting string is NUL-terminated (but NUL is not included in len). | |
| */ | |
| struct mg_str mg_strdup_nul(const struct mg_str s); | |
| /* | |
| * Locates character in a string. | |
| */ | |
| const char *mg_strchr(const struct mg_str s, int c); | |
| int mg_strcmp(const struct mg_str str1, const struct mg_str str2); | |
| int mg_strncmp(const struct mg_str str1, const struct mg_str str2, size_t n); | |
| const char *mg_strstr(const struct mg_str haystack, const struct mg_str needle); | |
| #ifdef __cplusplus | |
| } | |
| #endif | |
| #endif /* CS_COMMON_MG_STR_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> | |
| /* Amalgamated: #include "common/platform.h" */ | |
| /* Amalgamated: #include "common/mg_str.h" */ | |
| #ifndef CS_ENABLE_STRDUP | |
| #define CS_ENABLE_STRDUP 0 | |
| #endif | |
| #ifndef CS_ENABLE_TO64 | |
| #define CS_ENABLE_TO64 0 | |
| #endif | |
| /* | |
| * Expands to a string representation of its argument: e.g. | |
| * `CS_STRINGIFY_LIT(5) expands to "5"` | |
| */ | |
| #define CS_STRINGIFY_LIT(x) #x | |
| /* | |
| * Expands to a string representation of its argument, which is allowed | |
| * to be a macro: e.g. | |
| * | |
| * #define FOO 123 | |
| * CS_STRINGIFY_MACRO(FOO) | |
| * | |
| * expands to 123. | |
| */ | |
| #define CS_STRINGIFY_MACRO(x) CS_STRINGIFY_LIT(x) | |
| #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); | |
| /* | |
| * 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); | |
| /* | |
| * Convert stringified binary data back to binary. | |
| * Does the reverse of `cs_to_hex()`. | |
| */ | |
| void cs_from_hex(char *to, const char *p, size_t len); | |
| #if CS_ENABLE_STRDUP | |
| char *strdup(const char *src); | |
| #endif | |
| #if CS_ENABLE_TO64 | |
| #include <stdint.h> | |
| /* | |
| * Simple string -> int64 conversion routine. | |
| */ | |
| int64_t cs_to64(const char *s); | |
| #endif | |
| /* | |
| * Cross-platform version of `strncasecmp()`. | |
| */ | |
| int mg_ncasecmp(const char *s1, const char *s2, size_t len); | |
| /* | |
| * Cross-platform version of `strcasecmp()`. | |
| */ | |
| int mg_casecmp(const char *s1, const char *s2); | |
| /* | |
| * Prints message to the buffer. If the buffer is large enough to hold the | |
| * message, it returns buffer. If buffer is to small, it allocates a large | |
| * enough buffer on heap and returns allocated buffer. | |
| * This is a supposed use case: | |
| * | |
| * char buf[5], *p = buf; | |
| * mg_avprintf(&p, sizeof(buf), "%s", "hi there"); | |
| * use_p_somehow(p); | |
| * if (p != buf) { | |
| * free(p); | |
| * } | |
| * | |
| * The purpose of this is to avoid malloc-ing if generated strings are small. | |
| */ | |
| int mg_asprintf(char **buf, size_t size, const char *fmt, ...); | |
| /* Same as mg_asprintf, but takes varargs list. */ | |
| int mg_avprintf(char **buf, size_t size, const char *fmt, va_list ap); | |
| /* | |
| * A helper function for traversing a comma separated list of values. | |
| * It returns a list pointer shifted to the next value or NULL if the end | |
| * of the list found. | |
| * The value is stored in a val vector. If the value has a form "x=y", then | |
| * eq_val vector is initialised to point to the "y" part, and val vector length | |
| * is adjusted to point only to "x". | |
| * If the list is just a comma separated list of entries, like "aa,bb,cc" then | |
| * `eq_val` will contain zero-length string. | |
| * | |
| * The purpose of this function is to parse comma separated string without | |
| * any copying/memory allocation. | |
| */ | |
| const char *mg_next_comma_list_entry(const char *list, struct mg_str *val, | |
| struct mg_str *eq_val); | |
| struct mg_str mg_next_comma_list_entry_n(struct mg_str list, struct mg_str *val, | |
| struct mg_str *eq_val); | |
| /* | |
| * Matches 0-terminated string (mg_match_prefix) or string with given length | |
| * mg_match_prefix_n against a glob pattern. | |
| * | |
| * Match is case-insensitive. Returns number of bytes matched, or -1 if no | |
| * match. | |
| */ | |
| int mg_match_prefix(const char *pattern, int pattern_len, const char *str); | |
| int mg_match_prefix_n(const struct mg_str pattern, const struct mg_str str); | |
| #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 | |
| #define DISABLE_BASE64 0 | |
| #endif | |
| #if !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, int *dec_len); | |
| #ifdef __cplusplus | |
| } | |
| #endif | |
| #endif /* DISABLE_BASE64 */ | |
| #endif /* CS_COMMON_BASE64_H_ */ | |
| #ifdef V7_MODULE_LINES | |
| #line 1 "common/cs_dbg.h" | |
| #endif | |
| /* | |
| * Copyright (c) 2014-2016 Cesanta Software Limited | |
| * All rights reserved | |
| */ | |
| #ifndef CS_COMMON_CS_DBG_H_ | |
| #define CS_COMMON_CS_DBG_H_ | |
| /* Amalgamated: #include "common/platform.h" */ | |
| #if CS_ENABLE_STDIO | |
| #include <stdio.h> | |
| #endif | |
| #ifndef CS_ENABLE_DEBUG | |
| #define CS_ENABLE_DEBUG 0 | |
| #endif | |
| #ifndef CS_LOG_ENABLE_TS_DIFF | |
| #define CS_LOG_ENABLE_TS_DIFF 0 | |
| #endif | |
| #ifdef __cplusplus | |
| extern "C" { | |
| #endif /* __cplusplus */ | |
| enum cs_log_level { | |
| LL_NONE = -1, | |
| LL_ERROR = 0, | |
| LL_WARN = 1, | |
| LL_INFO = 2, | |
| LL_DEBUG = 3, | |
| LL_VERBOSE_DEBUG = 4, | |
| _LL_MIN = -2, | |
| _LL_MAX = 5, | |
| }; | |
| /* Set log level. */ | |
| void cs_log_set_level(enum cs_log_level level); | |
| /* Set log filter. NULL (a default) logs everything. */ | |
| void cs_log_set_filter(const char *source_file_name); | |
| int cs_log_print_prefix(enum cs_log_level level, const char *func, | |
| const char *filename); | |
| extern enum cs_log_level cs_log_threshold; | |
| #if CS_ENABLE_STDIO | |
| void cs_log_set_file(FILE *file); | |
| void cs_log_printf(const char *fmt, ...) | |
| #ifdef __GNUC__ | |
| __attribute__((format(printf, 1, 2))) | |
| #endif | |
| ; | |
| #define LOG(l, x) \ | |
| do { \ | |
| if (cs_log_print_prefix(l, __func__, __FILE__)) cs_log_printf x; \ | |
| } while (0) | |
| #ifndef CS_NDEBUG | |
| #define DBG(x) LOG(LL_VERBOSE_DEBUG, x) | |
| #else /* NDEBUG */ | |
| #define DBG(x) | |
| #endif | |
| #else /* CS_ENABLE_STDIO */ | |
| #define LOG(l, x) | |
| #define DBG(x) | |
| #endif | |
| #ifdef __cplusplus | |
| } | |
| #endif /* __cplusplus */ | |
| #endif /* CS_COMMON_CS_DBG_H_ */ | |
| #ifdef V7_MODULE_LINES | |
| #line 1 "common/cs_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" */ | |
| #ifndef CS_DISABLE_MD5 | |
| #define CS_DISABLE_MD5 0 | |
| #endif | |
| #ifdef __cplusplus | |
| extern "C" { | |
| #endif /* __cplusplus */ | |
| typedef struct { | |
| uint32_t buf[4]; | |
| uint32_t bits[2]; | |
| unsigned char in[64]; | |
| } cs_md5_ctx; | |
| void cs_md5_init(cs_md5_ctx *c); | |
| void cs_md5_update(cs_md5_ctx *c, const unsigned char *data, size_t len); | |
| void cs_md5_final(unsigned char *md, cs_md5_ctx *c); | |
| #ifdef __cplusplus | |
| } | |
| #endif /* __cplusplus */ | |
| #endif /* CS_COMMON_MD5_H_ */ | |
| #ifdef V7_MODULE_LINES | |
| #line 1 "common/cs_endian.h" | |
| #endif | |
| /* | |
| * Copyright (c) 2014-2016 Cesanta Software Limited | |
| * All rights reserved | |
| */ | |
| #ifndef CS_COMMON_CS_ENDIAN_H_ | |
| #define CS_COMMON_CS_ENDIAN_H_ | |
| #ifdef __cplusplus | |
| extern "C" { | |
| #endif | |
| /* | |
| * clang with std=-c99 uses __LITTLE_ENDIAN, by default | |
| * while for ex, RTOS gcc - LITTLE_ENDIAN, by default | |
| * it depends on __USE_BSD, but let's have everything | |
| */ | |
| #if !defined(BYTE_ORDER) && defined(__BYTE_ORDER) | |
| #define BYTE_ORDER __BYTE_ORDER | |
| #ifndef LITTLE_ENDIAN | |
| #define LITTLE_ENDIAN __LITTLE_ENDIAN | |
| #endif /* LITTLE_ENDIAN */ | |
| #ifndef BIG_ENDIAN | |
| #define BIG_ENDIAN __LITTLE_ENDIAN | |
| #endif /* BIG_ENDIAN */ | |
| #endif /* BYTE_ORDER */ | |
| #ifdef __cplusplus | |
| } | |
| #endif | |
| #endif /* CS_COMMON_CS_ENDIAN_H_ */ | |
| #ifdef V7_MODULE_LINES | |
| #line 1 "common/cs_sha1.h" | |
| #endif | |
| /* | |
| * Copyright (c) 2014 Cesanta Software Limited | |
| * All rights reserved | |
| */ | |
| #ifndef CS_COMMON_SHA1_H_ | |
| #define CS_COMMON_SHA1_H_ | |
| #ifndef CS_DISABLE_SHA1 | |
| #define CS_DISABLE_SHA1 0 | |
| #endif | |
| #if !CS_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 /* CS_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_ | |
| #include <limits.h> | |
| /* Amalgamated: #include "common/platform.h" */ | |
| #ifdef __cplusplus | |
| extern "C" { | |
| #endif /* __cplusplus */ | |
| #ifdef CS_DEFINE_DIRENT | |
| typedef struct { int dummy; } DIR; | |
| struct dirent { | |
| int d_ino; | |
| #ifdef _WIN32 | |
| char d_name[MAX_PATH]; | |
| #else | |
| /* TODO(rojer): Use PATH_MAX but make sure it's sane on every platform */ | |
| char d_name[256]; | |
| #endif | |
| }; | |
| DIR *opendir(const char *dir_name); | |
| int closedir(DIR *dir); | |
| struct dirent *readdir(DIR *dir); | |
| #endif /* CS_DEFINE_DIRENT */ | |
| #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" */ | |
| #ifdef __cplusplus | |
| extern "C" { | |
| #endif | |
| /* 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); | |
| #ifdef __cplusplus | |
| } | |
| #endif | |
| #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 /* ifdef-ok */ | |
| #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/features_all.h" | |
| #endif | |
| /* | |
| * Copyright (c) 2014 Cesanta Software Limited | |
| * All rights reserved | |
| */ | |
| #ifndef CS_V7_SRC_FEATURES_ALL_H_ | |
| #define CS_V7_SRC_FEATURES_ALL_H_ | |
| /* | |
| * DO NOT EDIT. | |
| * This file is generated by scripts/gen-features-all.pl. | |
| */ | |
| #ifndef V7_ENABLE__Array__reduce | |
| #define V7_ENABLE__Array__reduce 0 | |
| #endif | |
| #ifndef V7_ENABLE__Blob | |
| #define V7_ENABLE__Blob 0 | |
| #endif | |
| #ifndef V7_ENABLE__Date | |
| #define V7_ENABLE__Date 0 | |
| #endif | |
| #ifndef V7_ENABLE__Date__UTC | |
| #define V7_ENABLE__Date__UTC 0 | |
| #endif | |
| #ifndef V7_ENABLE__Date__getters | |
| #define V7_ENABLE__Date__getters 0 | |
| #endif | |
| #ifndef V7_ENABLE__Date__now | |
| #define V7_ENABLE__Date__now 0 | |
| #endif | |
| #ifndef V7_ENABLE__Date__parse | |
| #define V7_ENABLE__Date__parse 0 | |
| #endif | |
| #ifndef V7_ENABLE__Date__setters | |
| #define V7_ENABLE__Date__setters 0 | |
| #endif | |
| #ifndef V7_ENABLE__Date__toJSON | |
| #define V7_ENABLE__Date__toJSON 0 | |
| #endif | |
| #ifndef V7_ENABLE__Date__toLocaleString | |
| #define V7_ENABLE__Date__toLocaleString 0 | |
| #endif | |
| #ifndef V7_ENABLE__Date__toString | |
| #define V7_ENABLE__Date__toString 0 | |
| #endif | |
| #ifndef V7_ENABLE__File__list | |
| #define V7_ENABLE__File__list 0 | |
| #endif | |
| #ifndef V7_ENABLE__File__require | |
| #define V7_ENABLE__File__require 0 | |
| #endif | |
| #ifndef V7_ENABLE__Function__bind | |
| #define V7_ENABLE__Function__bind 0 | |
| #endif | |
| #ifndef V7_ENABLE__Function__call | |
| #define V7_ENABLE__Function__call 0 | |
| #endif | |
| #ifndef V7_ENABLE__Math | |
| #define V7_ENABLE__Math 0 | |
| #endif | |
| #ifndef V7_ENABLE__Math__abs | |
| #define V7_ENABLE__Math__abs 0 | |
| #endif | |
| #ifndef V7_ENABLE__Math__acos | |
| #define V7_ENABLE__Math__acos 0 | |
| #endif | |
| #ifndef V7_ENABLE__Math__asin | |
| #define V7_ENABLE__Math__asin 0 | |
| #endif | |
| #ifndef V7_ENABLE__Math__atan | |
| #define V7_ENABLE__Math__atan 0 | |
| #endif | |
| #ifndef V7_ENABLE__Math__atan2 | |
| #define V7_ENABLE__Math__atan2 0 | |
| #endif | |
| #ifndef V7_ENABLE__Math__ceil | |
| #define V7_ENABLE__Math__ceil 0 | |
| #endif | |
| #ifndef V7_ENABLE__Math__constants | |
| #define V7_ENABLE__Math__constants 0 | |
| #endif | |
| #ifndef V7_ENABLE__Math__cos | |
| #define V7_ENABLE__Math__cos 0 | |
| #endif | |
| #ifndef V7_ENABLE__Math__exp | |
| #define V7_ENABLE__Math__exp 0 | |
| #endif | |
| #ifndef V7_ENABLE__Math__floor | |
| #define V7_ENABLE__Math__floor 0 | |
| #endif | |
| #ifndef V7_ENABLE__Math__log | |
| #define V7_ENABLE__Math__log 0 | |
| #endif | |
| #ifndef V7_ENABLE__Math__max | |
| #define V7_ENABLE__Math__max 0 | |
| #endif | |
| #ifndef V7_ENABLE__Math__min | |
| #define V7_ENABLE__Math__min 0 | |
| #endif | |
| #ifndef V7_ENABLE__Math__pow | |
| #define V7_ENABLE__Math__pow 0 | |
| #endif | |
| #ifndef V7_ENABLE__Math__random | |
| #define V7_ENABLE__Math__random 0 | |
| #endif | |
| #ifndef V7_ENABLE__Math__round | |
| #define V7_ENABLE__Math__round 0 | |
| #endif | |
| #ifndef V7_ENABLE__Math__sin | |
| #define V7_ENABLE__Math__sin 0 | |
| #endif | |
| #ifndef V7_ENABLE__Math__sqrt | |
| #define V7_ENABLE__Math__sqrt 0 | |
| #endif | |
| #ifndef V7_ENABLE__Math__tan | |
| #define V7_ENABLE__Math__tan 0 | |
| #endif | |
| #ifndef V7_ENABLE__Memory__stats | |
| #define V7_ENABLE__Memory__stats 0 | |
| #endif | |
| #ifndef V7_ENABLE__NUMBER__NEGATIVE_INFINITY | |
| #define V7_ENABLE__NUMBER__NEGATIVE_INFINITY 0 | |
| #endif | |
| #ifndef V7_ENABLE__NUMBER__POSITIVE_INFINITY | |
| #define V7_ENABLE__NUMBER__POSITIVE_INFINITY 0 | |
| #endif | |
| #ifndef V7_ENABLE__Object__create | |
| #define V7_ENABLE__Object__create 0 | |
| #endif | |
| #ifndef V7_ENABLE__Object__defineProperties | |
| #define V7_ENABLE__Object__defineProperties 0 | |
| #endif | |
| #ifndef V7_ENABLE__Object__getOwnPropertyDescriptor | |
| #define V7_ENABLE__Object__getOwnPropertyDescriptor 0 | |
| #endif | |
| #ifndef V7_ENABLE__Object__getOwnPropertyNames | |
| #define V7_ENABLE__Object__getOwnPropertyNames 0 | |
| #endif | |
| #ifndef V7_ENABLE__Object__getPrototypeOf | |
| #define V7_ENABLE__Object__getPrototypeOf 0 | |
| #endif | |
| #ifndef V7_ENABLE__Object__hasOwnProperty | |
| #define V7_ENABLE__Object__hasOwnProperty 0 | |
| #endif | |
| #ifndef V7_ENABLE__Object__isExtensible | |
| #define V7_ENABLE__Object__isExtensible 0 | |
| #endif | |
| #ifndef V7_ENABLE__Object__isFrozen | |
| #define V7_ENABLE__Object__isFrozen 0 | |
| #endif | |
| #ifndef V7_ENABLE__Object__isPrototypeOf | |
| #define V7_ENABLE__Object__isPrototypeOf 0 | |
| #endif | |
| #ifndef V7_ENABLE__Object__isSealed | |
| #define V7_ENABLE__Object__isSealed 0 | |
| #endif | |
| #ifndef V7_ENABLE__Object__keys | |
| #define V7_ENABLE__Object__keys 0 | |
| #endif | |
| #ifndef V7_ENABLE__Object__preventExtensions | |
| #define V7_ENABLE__Object__preventExtensions 0 | |
| #endif | |
| #ifndef V7_ENABLE__Object__propertyIsEnumerable | |
| #define V7_ENABLE__Object__propertyIsEnumerable 0 | |
| #endif | |
| #ifndef V7_ENABLE__Proxy | |
| #define V7_ENABLE__Proxy 0 | |
| #endif | |
| #ifndef V7_ENABLE__RegExp | |
| #define V7_ENABLE__RegExp 0 | |
| #endif | |
| #ifndef V7_ENABLE__StackTrace | |
| #define V7_ENABLE__StackTrace 0 | |
| #endif | |
| #ifndef V7_ENABLE__String__localeCompare | |
| #define V7_ENABLE__String__localeCompare 0 | |
| #endif | |
| #ifndef V7_ENABLE__String__localeLowerCase | |
| #define V7_ENABLE__String__localeLowerCase 0 | |
| #endif | |
| #ifndef V7_ENABLE__String__localeUpperCase | |
| #define V7_ENABLE__String__localeUpperCase 0 | |
| #endif | |
| #endif /* CS_V7_SRC_FEATURES_ALL_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" */ | |
| /* All the features will be default-defined to 0. */ | |
| /* Amalgamated: #include "v7/src/features_all.h" */ | |
| #ifndef V7_DISABLE_AST_TAG_NAMES | |
| #define V7_DISABLE_AST_TAG_NAMES 0 | |
| #endif | |
| #ifndef V7_DISABLE_GC | |
| #define V7_DISABLE_GC 0 | |
| #endif | |
| #ifndef V7_DISABLE_CALL_ERROR_CONTEXT | |
| #define V7_DISABLE_CALL_ERROR_CONTEXT 0 | |
| #endif | |
| #ifndef V7_DISABLE_FILENAMES | |
| #define V7_DISABLE_FILENAMES 0 | |
| #endif | |
| #ifndef V7_DISABLE_LINE_NUMBERS | |
| #define V7_DISABLE_LINE_NUMBERS 0 | |
| #endif | |
| #ifndef V7_DISABLE_STR_ALLOC_SEQ | |
| #define V7_DISABLE_STR_ALLOC_SEQ 0 | |
| #endif | |
| #ifndef V7_ENABLE_CALL_TRACE | |
| #define V7_ENABLE_CALL_TRACE 0 | |
| #endif | |
| #ifndef V7_ENABLE_CRYPTO | |
| #define V7_ENABLE_CRYPTO 0 | |
| #endif | |
| #ifndef V7_ENABLE_DENSE_ARRAYS | |
| #define V7_ENABLE_DENSE_ARRAYS 0 | |
| #endif | |
| #ifndef V7_ENABLE_ENTITY_IDS | |
| #define V7_ENABLE_ENTITY_IDS 0 | |
| #endif | |
| #ifndef V7_ENABLE_FILE | |
| #define V7_ENABLE_FILE 0 | |
| #endif | |
| #ifndef V7_ENABLE_FOOTPRINT_REPORT | |
| #define V7_ENABLE_FOOTPRINT_REPORT 0 | |
| #endif | |
| #ifndef V7_ENABLE_GC_CHECK | |
| #define V7_ENABLE_GC_CHECK 0 | |
| #endif | |
| #ifndef V7_ENABLE_JS_GETTERS | |
| #define V7_ENABLE_JS_GETTERS 0 | |
| #endif | |
| #ifndef V7_ENABLE_JS_SETTERS | |
| #define V7_ENABLE_JS_SETTERS 0 | |
| #endif | |
| #ifndef V7_ENABLE_STACK_TRACKING | |
| #define V7_ENABLE_STACK_TRACKING 0 | |
| #endif | |
| #ifndef V7_ENABLE_SOCKET | |
| #define V7_ENABLE_SOCKET 0 | |
| #endif | |
| #ifndef V7_AST_FORCE_LINE_NUMBERS | |
| #define V7_AST_FORCE_LINE_NUMBERS 0 | |
| #endif | |
| #ifndef V7_HEAPUSAGE_ENABLE | |
| #define V7_HEAPUSAGE_ENABLE 0 | |
| #endif | |
| #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" */ | |
| #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 V7_ENABLE_GC_CHECK || defined(V7_STACK_GUARD_MIN_SIZE) || \ | |
| V7_ENABLE_STACK_TRACKING || 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 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 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 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. | |
| */ | |
| #if !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 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 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. | |
| * The code can be either a JavaScript source or a precompiled bytecode. | |
| * | |
| * 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); | |
| /* | |
| * Like v7_exec but it expects an explicit length instead of treating the code | |
| * as a null terminated string. | |
| * | |
| * The code can be either a JS source or a precompiled bytecode. | |
| */ | |
| WARN_UNUSED_RESULT | |
| enum v7_err v7_exec_buf(struct v7 *v7, const char *js_code, size_t len, | |
| v7_val_t *result); | |
| /* | |
| * 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_ | |
| #ifdef __cplusplus | |
| extern "C" { | |
| #endif | |
| double cs_strtod(const char *str, char **endptr); | |
| #ifdef __cplusplus | |
| } | |
| #endif | |
| #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 { | |
| #if !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); | |
| #if !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; | |
| #if !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; | |
| #if !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); | |
| #if !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); | |
| #if !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 *); | |
| #if !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); | |
| #if !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 !V7_DISABLE_FILENAMES && !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 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 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) WEAK; | |
| 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) WEAK; | |
| 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) WEAK; | |
| 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) WEAK; | |
| 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) WEAK; | |
| 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) WEAK; | |
| 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) WEAK; | |
| 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/mg_mem.h" */ | |
| /* Amalgamated: #include "common/platform.h" */ | |
| /* Amalgamated: #include "common/str_util.h" */ | |
| #ifndef C_DISABLE_BUILTIN_SNPRINTF | |
| #define C_DISABLE_BUILTIN_SNPRINTF 0 | |
| #endif | |
| /* Amalgamated: #include "common/mg_mem.h" */ | |
| size_t c_strnlen(const char *s, size_t maxlen) WEAK; | |
| 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 | |
| #if C_DISABLE_BUILTIN_SNPRINTF | |
| int c_vsnprintf(char *buf, size_t buf_size, const char *fmt, va_list ap) WEAK; | |
| 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) WEAK; | |
| 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, ...) WEAK; | |
| 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) WEAK; | |
| 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; | |
| } | |
| #if CS_ENABLE_STRDUP | |
| char *strdup(const char *src) WEAK; | |
| char *strdup(const char *src) { | |
| size_t len = strlen(src) + 1; | |
| char *ret = MG_MALLOC(len); | |
| if (ret != NULL) { | |
| strcpy(ret, src); | |
| } | |
| return ret; | |
| } | |
| #endif | |
| void cs_to_hex(char *to, const unsigned char *p, size_t len) WEAK; | |
| 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'; | |
| } | |
| static int fourbit(int ch) { | |
| if (ch >= '0' && ch <= '9') { | |
| return ch - '0'; | |
| } else if (ch >= 'a' && ch <= 'f') { | |
| return ch - 'a' + 10; | |
| } else if (ch >= 'A' && ch <= 'F') { | |
| return ch - 'A' + 10; | |
| } | |
| return 0; | |
| } | |
| void cs_from_hex(char *to, const char *p, size_t len) WEAK; | |
| void cs_from_hex(char *to, const char *p, size_t len) { | |
| size_t i; | |
| for (i = 0; i < len; i += 2) { | |
| *to++ = (fourbit(p[i]) << 4) + fourbit(p[i + 1]); | |
| } | |
| *to = '\0'; | |
| } | |
| #if CS_ENABLE_TO64 | |
| int64_t cs_to64(const char *s) WEAK; | |
| int64_t cs_to64(const char *s) { | |
| int64_t result = 0; | |
| int64_t neg = 1; | |
| while (*s && isspace((unsigned char) *s)) s++; | |
| if (*s == '-') { | |
| neg = -1; | |
| s++; | |
| } | |
| while (isdigit((unsigned char) *s)) { | |
| result *= 10; | |
| result += (*s - '0'); | |
| s++; | |
| } | |
| return result * neg; | |
| } | |
| #endif | |
| static int str_util_lowercase(const char *s) { | |
| return tolower(*(const unsigned char *) s); | |
| } | |
| int mg_ncasecmp(const char *s1, const char *s2, size_t len) WEAK; | |
| int mg_ncasecmp(const char *s1, const char *s2, size_t len) { | |
| int diff = 0; | |
| if (len > 0) do { | |
| diff = str_util_lowercase(s1++) - str_util_lowercase(s2++); | |
| } while (diff == 0 && s1[-1] != '\0' && --len > 0); | |
| return diff; | |
| } | |
| int mg_casecmp(const char *s1, const char *s2) WEAK; | |
| int mg_casecmp(const char *s1, const char *s2) { | |
| return mg_ncasecmp(s1, s2, (size_t) ~0); | |
| } | |
| int mg_asprintf(char **buf, size_t size, const char *fmt, ...) WEAK; | |
| int mg_asprintf(char **buf, size_t size, const char *fmt, ...) { | |
| int ret; | |
| va_list ap; | |
| va_start(ap, fmt); | |
| ret = mg_avprintf(buf, size, fmt, ap); | |
| va_end(ap); | |
| return ret; | |
| } | |
| int mg_avprintf(char **buf, size_t size, const char *fmt, va_list ap) WEAK; | |
| int mg_avprintf(char **buf, size_t size, const char *fmt, va_list ap) { | |
| va_list ap_copy; | |
| int len; | |
| va_copy(ap_copy, ap); | |
| len = vsnprintf(*buf, size, fmt, ap_copy); | |
| va_end(ap_copy); | |
| if (len < 0) { | |
| /* eCos and Windows are not standard-compliant and return -1 when | |
| * the buffer is too small. Keep allocating larger buffers until we | |
| * succeed or out of memory. */ | |
| *buf = NULL; /* LCOV_EXCL_START */ | |
| while (len < 0) { | |
| MG_FREE(*buf); | |
| size *= 2; | |
| if ((*buf = (char *) MG_MALLOC(size)) == NULL) break; | |
| va_copy(ap_copy, ap); | |
| len = vsnprintf(*buf, size, fmt, ap_copy); | |
| va_end(ap_copy); | |
| } | |
| /* LCOV_EXCL_STOP */ | |
| } else if (len >= (int) size) { | |
| /* Standard-compliant code path. Allocate a buffer that is large enough. */ | |
| if ((*buf = (char *) MG_MALLOC(len + 1)) == NULL) { | |
| len = -1; /* LCOV_EXCL_LINE */ | |
| } else { /* LCOV_EXCL_LINE */ | |
| va_copy(ap_copy, ap); | |
| len = vsnprintf(*buf, len + 1, fmt, ap_copy); | |
| va_end(ap_copy); | |
| } | |
| } | |
| return len; | |
| } | |
| const char *mg_next_comma_list_entry(const char *, struct mg_str *, | |
| struct mg_str *) WEAK; | |
| const char *mg_next_comma_list_entry(const char *list, struct mg_str *val, | |
| struct mg_str *eq_val) { | |
| struct mg_str ret = mg_next_comma_list_entry_n(mg_mk_str(list), val, eq_val); | |
| return ret.p; | |
| } | |
| struct mg_str mg_next_comma_list_entry_n(struct mg_str list, struct mg_str *val, | |
| struct mg_str *eq_val) WEAK; | |
| struct mg_str mg_next_comma_list_entry_n(struct mg_str list, struct mg_str *val, | |
| struct mg_str *eq_val) { | |
| if (list.len == 0) { | |
| /* End of the list */ | |
| list = mg_mk_str(NULL); | |
| } else { | |
| const char *chr = NULL; | |
| *val = list; | |
| if ((chr = mg_strchr(*val, ',')) != NULL) { | |
| /* Comma found. Store length and shift the list ptr */ | |
| val->len = chr - val->p; | |
| chr++; | |
| list.len -= (chr - list.p); | |
| list.p = chr; | |
| } else { | |
| /* This value is the last one */ | |
| list = mg_mk_str_n(list.p + list.len, 0); | |
| } | |
| if (eq_val != NULL) { | |
| /* Value has form "x=y", adjust pointers and lengths */ | |
| /* so that val points to "x", and eq_val points to "y". */ | |
| eq_val->len = 0; | |
| eq_val->p = (const char *) memchr(val->p, '=', val->len); | |
| if (eq_val->p != NULL) { | |
| eq_val->p++; /* Skip over '=' character */ | |
| eq_val->len = val->p + val->len - eq_val->p; | |
| val->len = (eq_val->p - val->p) - 1; | |
| } | |
| } | |
| } | |
| return list; | |
| } | |
| int mg_match_prefix_n(const struct mg_str, const struct mg_str) WEAK; | |
| int mg_match_prefix_n(const struct mg_str pattern, const struct mg_str str) { | |
| const char *or_str; | |
| size_t len, i = 0, j = 0; | |
| int res; | |
| if ((or_str = (const char *) memchr(pattern.p, '|', pattern.len)) != NULL || | |
| (or_str = (const char *) memchr(pattern.p, ',', pattern.len)) != NULL) { | |
| struct mg_str pstr = {pattern.p, (size_t)(or_str - pattern.p)}; | |
| res = mg_match_prefix_n(pstr, str); | |
| if (res > 0) return res; | |
| pstr.p = or_str + 1; | |
| pstr.len = (pattern.p + pattern.len) - (or_str + 1); | |
| return mg_match_prefix_n(pstr, str); | |
| } | |
| for (; i < pattern.len; i++, j++) { | |
| if (pattern.p[i] == '?' && j != str.len) { | |
| continue; | |
| } else if (pattern.p[i] == '$') { | |
| return j == str.len ? (int) j : -1; | |
| } else if (pattern.p[i] == '*') { | |
| i++; | |
| if (i < pattern.len && pattern.p[i] == '*') { | |
| i++; | |
| len = str.len - j; | |
| } else { | |
| len = 0; | |
| while (j + len != str.len && str.p[j + len] != '/') { | |
| len++; | |
| } | |
| } | |
| if (i == pattern.len) { | |
| return j + len; | |
| } | |
| do { | |
| const struct mg_str pstr = {pattern.p + i, pattern.len - i}; | |
| const struct mg_str sstr = {str.p + j + len, str.len - j - len}; | |
| res = mg_match_prefix_n(pstr, sstr); | |
| } while (res == -1 && len-- > 0); | |
| return res == -1 ? -1 : (int) (j + res + len); | |
| } else if (str_util_lowercase(&pattern.p[i]) != | |
| str_util_lowercase(&str.p[j])) { | |
| return -1; | |
| } | |
| } | |
| return j; | |
| } | |
| int mg_match_prefix(const char *, int, const char *) WEAK; | |
| int mg_match_prefix(const char *pattern, int pattern_len, const char *str) { | |
| const struct mg_str pstr = {pattern, (size_t) pattern_len}; | |
| struct mg_str s = {str, 0}; | |
| if (str != NULL) s.len = strlen(str); | |
| return mg_match_prefix_n(pstr, s); | |
| } | |
| #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/str_util.h" */ | |
| /* Amalgamated: #include "common/utf.h" */ | |
| #ifndef CS_ENABLE_UTF8 | |
| #define CS_ENABLE_UTF8 0 | |
| #endif | |
| #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> | |
| /* Amalgamated: #include "common/cs_dbg.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 | |
| #if CS_ENABLE_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_ENABLE_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, int *dec_len) { | |
| unsigned char a, b, c, d; | |
| int orig_len = len; | |
| char *orig_dst = dst; | |
| 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; | |
| if (dec_len != NULL) *dec_len = (dst - orig_dst); | |
| return orig_len - len; | |
| } | |
| #endif /* EXCLUDE_COMMON */ | |
| #ifdef V7_MODULE_LINES | |
| #line 1 "common/cs_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. | |
| */ | |
| /* Amalgamated: #include "common/cs_md5.h" */ | |
| /* Amalgamated: #include "common/str_util.h" */ | |
| #if !defined(EXCLUDE_COMMON) | |
| #if !CS_DISABLE_MD5 | |
| /* Amalgamated: #include "common/cs_endian.h" */ | |
| 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 cs_md5_init(cs_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 cs_md5_transform(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 cs_md5_update(cs_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); | |
| cs_md5_transform(ctx->buf, (uint32_t *) ctx->in); | |
| buf += t; | |
| len -= t; | |
| } | |
| while (len >= 64) { | |
| memcpy(ctx->in, buf, 64); | |
| byteReverse(ctx->in, 16); | |
| cs_md5_transform(ctx->buf, (uint32_t *) ctx->in); | |
| buf += 64; | |
| len -= 64; | |
| } | |
| memcpy(ctx->in, buf, len); | |
| } | |
| void cs_md5_final(unsigned char digest[16], cs_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); | |
| cs_md5_transform(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]; | |
| cs_md5_transform(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_DISABLE_MD5 */ | |
| #endif /* EXCLUDE_COMMON */ | |
| #ifdef V7_MODULE_LINES | |
| #line 1 "common/cs_sha1.c" | |
| #endif | |
| /* Copyright(c) By Steve Reid <steve@edmweb.com> */ | |
| /* 100% Public Domain */ | |
| /* Amalgamated: #include "common/cs_sha1.h" */ | |
| #if !CS_DISABLE_SHA1 && !defined(EXCLUDE_COMMON) | |
| /* Amalgamated: #include "common/cs_endian.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/mg_mem.h" */ | |
| /* 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). | |
| */ | |
| #ifdef _WIN32 | |
| struct win32_dir { | |
| DIR d; | |
| HANDLE handle; | |
| WIN32_FIND_DATAW info; | |
| struct dirent result; | |
| }; | |
| DIR *opendir(const char *name) { | |
| struct win32_dir *dir = NULL; | |
| wchar_t wpath[MAX_PATH]; | |
| DWORD attrs; | |
| if (name == NULL) { | |
| SetLastError(ERROR_BAD_ARGUMENTS); | |
| } else if ((dir = (struct win32_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 *) dir; | |
| } | |
| int closedir(DIR *d) { | |
| struct win32_dir *dir = (struct win32_dir *) d; | |
| 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 *d) { | |
| struct win32_dir *dir = (struct win32_dir *) d; | |
| struct dirent *result = NULL; | |
| if (dir) { | |
| memset(&dir->result, 0, sizeof(dir->result)); | |
| 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 | |
| #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) WEAK; | |
| 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) WEAK; | |
| char *cs_mmap_file(const char *path, size_t *size) { | |
| char *r; | |
| int fd = open(path, O_RDONLY, 0); | |
| struct stat st; | |
| if (fd < 0) 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_heap_trace.c" | |
| #endif | |
| /* | |
| * Copyright (c) 2014-2016 Cesanta Software Limited | |
| * All rights reserved | |
| */ | |
| #include <stdint.h> | |
| #include <stdio.h> | |
| #ifndef MGOS_ENABLE_CALL_TRACE | |
| #define MGOS_ENABLE_CALL_TRACE 0 | |
| #endif | |
| #ifndef V7_ENABLE_CALL_TRACE | |
| #define V7_ENABLE_CALL_TRACE 0 | |
| #endif | |
| #if MGOS_ENABLE_CALL_TRACE || V7_ENABLE_CALL_TRACE | |
| /* | |
| * If we don't have V7's profiling functions, roll our own. | |
| * This is copy-pasta from v7/src/cyg_profile.c | |
| */ | |
| #ifndef CALL_TRACE_SIZE | |
| #define CALL_TRACE_SIZE 32 | |
| #endif | |
| typedef struct { | |
| void *addresses[CALL_TRACE_SIZE]; | |
| uint16_t size; | |
| } call_trace_t; | |
| static call_trace_t call_trace; | |
| #if MGOS_ENABLE_CALL_TRACE | |
| void esp_exc_printf(const char *fmt, ...); | |
| #define call_trace_printf esp_exc_printf | |
| #else | |
| #define call_trace_printf printf | |
| #endif | |
| NOINSTR void print_call_trace() { | |
| static void *prev_trace[CALL_TRACE_SIZE]; | |
| unsigned int size = call_trace.size; | |
| if (size > CALL_TRACE_SIZE) size = CALL_TRACE_SIZE; | |
| unsigned int i; | |
| uintptr_t pa = 0; | |
| for (i = 0; i < size; i++) { | |
| if (call_trace.addresses[i] != prev_trace[i]) break; | |
| pa = (uintptr_t) call_trace.addresses[i]; | |
| } | |
| call_trace_printf("%u %u", size, i); | |
| for (; i < size; i++) { | |
| const uintptr_t a = (uintptr_t) call_trace.addresses[i]; | |
| /* | |
| * Perform a rudimentary deduplication: an address is likely to have higher | |
| * bits the same as previous, turn them off. | |
| * Do it in 4-bit nibbles so they fall nicely on hex digit boundary. | |
| */ | |
| uintptr_t mask = ~((uintptr_t) 0); | |
| while (mask != 0 && (a & mask) != (pa & mask)) mask <<= 4; | |
| call_trace_printf(" %lx", (unsigned long) (a & ~mask)); | |
| prev_trace[i] = (void *) a; | |
| pa = a; | |
| } | |
| call_trace_printf("\n"); | |
| } | |
| #if MGOS_ENABLE_CALL_TRACE && !V7_ENABLE_CALL_TRACE | |
| IRAM NOINSTR void __cyg_profile_func_enter(void *this_fn, void *call_site) { | |
| if (call_trace.size < CALL_TRACE_SIZE) { | |
| call_trace.addresses[call_trace.size] = this_fn; | |
| } | |
| call_trace.size++; | |
| (void) this_fn; | |
| (void) call_site; | |
| } | |
| IRAM NOINSTR void __cyg_profile_func_exit(void *this_fn, void *call_site) { | |
| if (call_trace.size > 0) call_trace.size--; | |
| (void) this_fn; | |
| (void) call_site; | |
| } | |
| #endif | |
| #endif /* MGOS_ENABLE_CALL_TRACE || V7_ENABLE_CALL_TRACE */ | |
| #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 | |
| */ | |
| /* Amalgamated: #include "common/platform.h" */ | |
| #if CS_PLATFORM == CS_P_MBED | |
| long timezone; | |
| /* | |
| * The GCC ARM toolchain for implements a weak | |
| * gettimeofday stub that should be implemented | |
| * to hook the OS time source. But mbed OS doesn't do it; | |
| * the mbed doc only talks about C date and time functions: | |
| * | |
| * https://docs.mbed.com/docs/mbed-os-api-reference/en/5.1/APIs/tasks/Time/ | |
| * | |
| * gettimeof day is a BSD API. | |
| */ | |
| int _gettimeofday(struct timeval *tv, void *tzvp) { | |
| tv->tv_sec = time(NULL); | |
| tv->tv_usec = 0; | |
| return 0; | |
| } | |
| int inet_aton(const char *cp, struct in_addr *inp) { | |
| /* We don't have aton, but have pton in mbed */ | |
| return inet_pton(AF_INET, cp, inp); | |
| } | |
| in_addr_t inet_addr(const char *cp) { | |
| in_addr_t ret; | |
| if (inet_pton(AF_INET, cp, &ret) != 1) { | |
| return 0; | |
| } | |
| return ret; | |
| } | |
| #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 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" */ | |
| #if 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/str_util.h" */ | |
| /* Amalgamated: #include "common/base64.h" */ | |
| #if 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]) { | |
| cs_md5_ctx ctx; | |
| cs_md5_init(&ctx); | |
| cs_md5_update(&ctx, (unsigned char *) data, len); | |
| cs_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) { | |
| #if 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 | |
| #if !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); | |
| } | |
| #if !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; | |
| #if !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, " "); | |
| } | |
| #if !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; | |
| #if !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)); | |
| #if !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); | |
| } | |
| } | |
| #if !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) { | |
| #if !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); | |
| } | |
| #if !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; | |
| } | |
| #if !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; | |
| #if !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)); | |
| #if !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); | |
| #if !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); | |
| } | |
| #if !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 */ | |
| #if !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; | |
| #if !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 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 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 | |
| #if !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 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 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 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 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 | |
| #if !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 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 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 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 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_exec_buf(struct v7 *v7, const char *js_code, size_t len, | |
| v7_val_t *res) { | |
| return b_exec(v7, js_code, len, NULL, V7_UNDEFINED, V7_UNDEFINED, | |
| V7_UNDEFINED, 0, 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; | |
| } | |
| } | |
| #if !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; | |
| #if !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; | |
| #if !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); | |
| #if 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 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 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 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 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 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 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; | |
| 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 (buf < limit) { | |
| *buf = '\0'; | |
| } else if (size != 0) { | |
| /* | |
| * There is no room for the NULL char, but the size wasn't zero, so we can | |
| * safely put NULL in the previous byte | |
| */ | |
| *(buf - 1) = '\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 !V7_DISABLE_FILENAMES && !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) { | |
| #ifdef 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) | |
| #if !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; | |
| } | |
| #if !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 | |
| #if !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; | |
| #if !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') { | |
| #if !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) | |
| #if !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) && !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) && !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) { | |
| #if 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 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 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 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 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 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 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, | |
| #if V7_ENABLE_JS_GETTERS | |
| fid_p_prop_1_getter, | |
| #endif | |
| fid_p_prop_2, | |
| #if 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)}, | |
| #if 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)}, | |
| #if 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; | |
| } | |
| #if !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); | |
| #if V7_ENABLE_JS_GETTERS | |
| CR_DEFINE_ENTRY_POINT(fid_p_prop_1_getter); | |
| #endif | |
| CR_DEFINE_ENTRY_POINT(fid_p_prop_2); | |
| #if 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) | |
| { | |
| #if 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); | |
| #if 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 V7_ENABLE_STACK_TRACKING | |
| struct stack_track_ctx stack_track_ctx; | |
| #endif | |
| int saved_line_no = v7->line_no; | |
| #if 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 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 | |
| #if !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> | |
| /* Amalgamated: #include "v7/src/internal.h" */ | |
| #if 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) | |
| #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 V7_ENABLE_GC_CHECK | |
| { | |
| (void) this_fn; | |
| (void) call_site; | |
| } | |
| #endif | |
| #if 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 V7_ENABLE_CALL_TRACE | |
| if (call_trace.size < CALL_TRACE_SIZE) { | |
| call_trace.addresses[call_trace.size] = this_fn; | |
| } | |
| call_trace.size++; | |
| #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 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 V7_ENABLE_STACK_TRACKING | |
| { | |
| (void) this_fn; | |
| (void) call_site; | |
| } | |
| #endif | |
| #if V7_ENABLE_CALL_TRACE | |
| if (call_trace.size > 0) call_trace.size--; | |
| #endif | |
| } | |
| #if 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()` | |
| */ | |
| #if !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 !V7_DISABLE_FILENAMES && !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 !V7_DISABLE_FILENAMES && !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(¤t_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 && !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 */ |