#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <inttypes.h>
#include <string.h>
#include <assert.h>
#include <math.h>
#include <setjmp.h>
#include "cutils.h"
#include "dtoa.h"
#include "mquickjs_priv.h"
#define __exception __attribute__((warn_unused_result))
#define JS_STACK_SLACK 16
#define JS_MIN_FREE_SIZE 512
#define JS_MIN_CRITICAL_FREE_SIZE (JS_MIN_FREE_SIZE - 256)
#define JS_MAX_LOCAL_VARS 65535
#define JS_MAX_FUNC_STACK_SIZE 65535
#define JS_MAX_ARGC 65535
#define JS_MAX_CALL_RECURSE 8
#define JS_VALUE_IS_BOTH_INT(a, b) ((((a) | (b)) & 1) == 0)
#define JS_VALUE_IS_BOTH_SHORT_FLOAT(a, b) (((((a) - JS_TAG_SHORT_FLOAT) | ((b) - JS_TAG_SHORT_FLOAT)) & 7) == 0)
static __maybe_unused const char *js_mtag_name[JS_MTAG_COUNT] = {
"free",
"object",
"float64",
"string",
"func_bytecode",
"value_array",
"byte_array",
"varref",
};
#define FRAME_CF_ARGC_MASK 0xffff
#define FRAME_CF_POP_RET (1 << 17)
#define FRAME_CF_PC_ADD1 (1 << 18)
#define JS_MB_PAD(n) (JSW * 8 - (n))
typedef struct {
JS_MB_HEADER;
JSWord dummy: JS_MB_PAD(JS_MTAG_BITS);
} JSMemBlockHeader;
typedef struct {
JS_MB_HEADER;
JSWord size: JS_MB_PAD(JS_MTAG_BITS);
} JSFreeBlock;
#if JSW == 8
#define JS_STRING_LEN_MAX 0x7ffffffe
#else
#define JS_STRING_LEN_MAX ((1 << (32 - JS_MTAG_BITS - 3)) - 1)
#endif
typedef struct {
JS_MB_HEADER;
JSWord is_unique: 1;
JSWord is_ascii: 1;
JSWord is_numeric: 1;
JSWord len: JS_MB_PAD(JS_MTAG_BITS + 3);
uint8_t buf[];
} JSString;
typedef struct {
JSWord string_buf[sizeof(JSString) / sizeof(JSWord)];
uint8_t buf[5];
} JSStringCharBuf;
#define JS_BYTE_ARRAY_SIZE_MAX ((1 << (32 - JS_MTAG_BITS)) - 1)
typedef struct {
JS_MB_HEADER;
JSWord size: JS_MB_PAD(JS_MTAG_BITS);
uint8_t buf[];
} JSByteArray;
#define JS_VALUE_ARRAY_SIZE_MAX ((1 << (32 - JS_MTAG_BITS)) - 1)
typedef struct {
JS_MB_HEADER;
JSWord size: JS_MB_PAD(JS_MTAG_BITS);
JSValue arr[];
} JSValueArray;
typedef struct JSVarRef {
JS_MB_HEADER;
JSWord is_detached : 1;
JSWord dummy: JS_MB_PAD(JS_MTAG_BITS + 1);
union {
JSValue value;
struct {
JSValue next;
JSValue *pvalue;
};
} u;
} JSVarRef;
typedef struct {
JS_MB_HEADER;
JSWord dummy: JS_MB_PAD(JS_MTAG_BITS);
#ifdef JS_PTR64
struct {
double dval;
} u;
#else
struct __attribute__((packed)) {
double dval;
} u;
#endif
} JSFloat64;
typedef struct JSROMClass {
JS_MB_HEADER;
JSWord dummy: JS_MB_PAD(JS_MTAG_BITS);
JSValue props;
int32_t ctor_idx;
JSValue proto_props;
JSValue parent_class;
} JSROMClass;
#define N_ROM_ATOM_TABLES_MAX 2
#define JS_INTERRUPT_COUNTER_INIT 10000
#define JS_STRING_POS_CACHE_SIZE 2
#define JS_STRING_POS_CACHE_MIN_LEN 16
typedef enum {
POS_TYPE_UTF8,
POS_TYPE_UTF16,
} StringPosTypeEnum;
typedef struct {
JSValue str;
uint32_t str_pos[2];
} JSStringPosCacheEntry;
struct JSContext {
uint8_t *heap_base;
uint8_t *heap_free;
uint8_t *stack_top;
JSValue *stack_bottom;
JSValue *sp;
JSValue *fp;
uint32_t min_free_size;
BOOL in_out_of_memory : 8;
uint8_t n_rom_atom_tables;
uint8_t string_pos_cache_counter;
uint16_t class_count;
int16_t interrupt_counter;
BOOL current_exception_is_uncatchable : 8;
struct JSParseState *parse_state;
int unique_strings_len;
int js_call_rec_count;
JSGCRef *top_gc_ref;
JSGCRef *last_gc_ref;
const JSWord *atom_table;
const JSValueArray *rom_atom_tables[N_ROM_ATOM_TABLES_MAX];
const JSCFunctionDef *c_function_table;
const JSCFinalizer *c_finalizer_table;
uint64_t random_state;
JSInterruptHandler *interrupt_handler;
JSWriteFunc *write_func;
void *opaque;
JSValue *class_obj;
JSStringPosCacheEntry string_pos_cache[JS_STRING_POS_CACHE_SIZE];
JSValue unique_strings;
JSValue current_exception;
#ifdef DEBUG_GC
JSValue dummy_block;
#endif
JSValue empty_props;
JSValue global_obj;
JSValue minus_zero;
JSValue class_proto[];
};
typedef enum {
JS_VARREF_KIND_ARG,
JS_VARREF_KIND_VAR,
JS_VARREF_KIND_VAR_REF,
JS_VARREF_KIND_GLOBAL,
} JSVarRefKindEnum;
typedef struct JSObject JSObject;
typedef struct {
JSValue key;
JSValue value;
uint32_t hash_next : 30;
uint32_t prop_type : 2;
} JSProperty;
typedef struct {
JSValue func_bytecode;
JSValue var_refs[];
} JSClosureData;
typedef struct {
uint32_t idx;
JSValue params;
} JSCFunctionData;
typedef struct {
JSValue tab;
uint32_t len;
} JSArrayData;
typedef struct {
JSValue message;
JSValue stack;
} JSErrorData;
typedef struct {
JSValue byte_buffer;
} JSArrayBuffer;
typedef struct {
JSValue buffer;
uint32_t len;
uint32_t offset;
} JSTypedArray;
typedef struct {
JSValue source;
JSValue byte_code;
int last_index;
} JSRegExp;
typedef struct {
void *opaque;
} JSObjectUserData;
struct JSObject {
JS_MB_HEADER;
JSWord class_id: 8;
JSWord extra_size: JS_MB_PAD(JS_MTAG_BITS + 8);
JSValue proto;
JSValue props;
union {
JSClosureData closure;
JSCFunctionData cfunc;
JSArrayData array;
JSErrorData error;
JSArrayBuffer array_buffer;
JSTypedArray typed_array;
JSRegExp regexp;
JSObjectUserData user;
} u;
};
typedef struct JSFunctionBytecode {
JS_MB_HEADER;
JSWord has_arguments : 1;
JSWord has_local_func_name : 1;
JSWord has_column : 1;
JSWord arg_count : 16;
JSWord dummy: JS_MB_PAD(JS_MTAG_BITS + 3 + 16);
JSValue func_name;
JSValue byte_code;
JSValue cpool;
JSValue vars;
JSValue ext_vars;
uint16_t stack_size;
uint16_t ext_vars_len;
JSValue filename;
JSValue pc2line;
uint32_t source_pos;
} JSFunctionBytecode;
static JSValue js_resize_value_array(JSContext *ctx, JSValue val, int new_size);
static int get_mblock_size(const void *ptr);
static JSValue JS_NewObjectProtoClass(JSContext *ctx, JSValue proto, int class_id, int extra_size);
static void js_shrink_byte_array(JSContext *ctx, JSValue *pval, int new_size);
static void build_backtrace(JSContext *ctx, JSValue error_obj,
const char *filename, int line_num, int col_num, int skip_level);
static JSValue JS_ToPropertyKey(JSContext *ctx, JSValue val);
static JSByteArray *js_alloc_byte_array(JSContext *ctx, int size);
static JSValue js_new_c_function_proto(JSContext *ctx, int func_idx, JSValue proto, BOOL has_params,
JSValue params);
static int JS_ToUint8Clamp(JSContext *ctx, int *pres, JSValue val);
static JSValue js_set_prototype_internal(JSContext *ctx, JSValue obj, JSValue proto);
static JSValue js_resize_byte_array(JSContext *ctx, JSValue val, int new_size);
static JSValueArray *js_alloc_props(JSContext *ctx, int n);
typedef enum OPCodeFormat {
#define FMT(f) OP_FMT_ ## f,
#define DEF(id, size, n_pop, n_push, f)
#include "mquickjs_opcode.h"
#undef DEF
#undef FMT
} OPCodeFormat;
typedef enum OPCodeEnum {
#define FMT(f)
#define DEF(id, size, n_pop, n_push, f) OP_ ## id,
#define def(id, size, n_pop, n_push, f)
#include "mquickjs_opcode.h"
#undef def
#undef DEF
#undef FMT
OP_COUNT,
} OPCodeEnum;
typedef struct {
#ifdef DUMP_BYTECODE
const char *name;
#endif
uint8_t size;
uint8_t n_pop;
uint8_t n_push;
uint8_t fmt;
} JSOpCode;
static __maybe_unused const JSOpCode opcode_info[OP_COUNT] = {
#define FMT(f)
#ifdef DUMP_BYTECODE
#define DEF(id, size, n_pop, n_push, f) { #id, size, n_pop, n_push, OP_FMT_ ## f },
#else
#define DEF(id, size, n_pop, n_push, f) { size, n_pop, n_push, OP_FMT_ ## f },
#endif
#include "mquickjs_opcode.h"
#undef DEF
#undef FMT
};
#include "mquickjs_atom.h"
JSValue *JS_PushGCRef(JSContext *ctx, JSGCRef *ref)
{
ref->prev = ctx->top_gc_ref;
ctx->top_gc_ref = ref;
ref->val = JS_UNDEFINED;
return &ref->val;
}
JSValue JS_PopGCRef(JSContext *ctx, JSGCRef *ref)
{
ctx->top_gc_ref = ref->prev;
return ref->val;
}
JSValue *JS_AddGCRef(JSContext *ctx, JSGCRef *ref)
{
ref->prev = ctx->last_gc_ref;
ctx->last_gc_ref = ref;
ref->val = JS_UNDEFINED;
return &ref->val;
}
void JS_DeleteGCRef(JSContext *ctx, JSGCRef *ref)
{
JSGCRef **pref, *ref1;
pref = &ctx->last_gc_ref;
for(;;) {
ref1 = *pref;
if (ref1 == NULL)
abort();
if (ref1 == ref) {
*pref = ref1->prev;
break;
}
pref = &ref1->prev;
}
}
#undef JS_PUSH_VALUE
#undef JS_POP_VALUE
#define JS_PUSH_VALUE(ctx, v) do { \
v ## _ref.prev = ctx->top_gc_ref; \
ctx->top_gc_ref = &v ## _ref; \
v ## _ref.val = v; \
} while (0)
#define JS_POP_VALUE(ctx, v) do { \
v = v ## _ref.val; \
ctx->top_gc_ref = v ## _ref.prev; \
} while (0)
static JSValue js_get_atom(JSContext *ctx, int a)
{
return JS_VALUE_FROM_PTR(&ctx->atom_table[a]);
}
static force_inline JSValue JS_NewTailCall(int val)
{
return JS_VALUE_MAKE_SPECIAL(JS_TAG_EXCEPTION, JS_EX_CALL + val);
}
static inline JS_BOOL JS_IsExceptionOrTailCall(JSValue v)
{
return JS_VALUE_GET_SPECIAL_TAG(v) == JS_TAG_EXCEPTION;
}
static int js_get_mtag(void *ptr)
{
return ((JSMemBlockHeader *)ptr)->mtag;
}
static int check_free_mem(JSContext *ctx, JSValue *stack_bottom, uint32_t size)
{
#ifdef DEBUG_GC
assert(ctx->sp >= stack_bottom);
if (JS_IsPtr(ctx->dummy_block)) {
JS_GC(ctx);
}
#endif
if (((uint8_t *)stack_bottom - ctx->heap_free) < size + ctx->min_free_size) {
JS_GC(ctx);
if (((uint8_t *)stack_bottom - ctx->heap_free) < size + ctx->min_free_size) {
JS_ThrowOutOfMemory(ctx);
return -1;
}
}
return 0;
}
int JS_StackCheck(JSContext *ctx, uint32_t len)
{
JSValue *new_stack_bottom;
len += JS_STACK_SLACK;
new_stack_bottom = ctx->sp - len;
if (check_free_mem(ctx, new_stack_bottom, len * sizeof(JSValue)))
return -1;
ctx->stack_bottom = new_stack_bottom;
return 0;
}
static void *js_malloc(JSContext *ctx, uint32_t size, int mtag)
{
JSMemBlockHeader *p;
if (size == 0)
return NULL;
size = (size + JSW - 1) & ~(JSW - 1);
if (check_free_mem(ctx, ctx->stack_bottom, size))
return NULL;
p = (JSMemBlockHeader *)ctx->heap_free;
ctx->heap_free += size;
p->mtag = mtag;
p->gc_mark = 0;
p->dummy = 0;
return p;
}
static void *js_mallocz(JSContext *ctx, uint32_t size, int mtag)
{
uint8_t *ptr;
ptr = js_malloc(ctx, size, mtag);
if (!ptr)
return NULL;
if (size > sizeof(uint32_t)) {
memset(ptr + sizeof(uint32_t), 0, size - sizeof(uint32_t));
}
return ptr;
}
static void js_free(JSContext *ctx, void *ptr)
{
uint8_t *ptr1;
if (!ptr)
return;
ptr1 = ptr;
ptr1 += get_mblock_size(ptr1);
if (ptr1 == ctx->heap_free)
ctx->heap_free = ptr;
}
static void set_free_block(void *ptr, uint32_t size)
{
JSFreeBlock *p;
p = (JSFreeBlock *)ptr;
p->mtag = JS_MTAG_FREE;
p->gc_mark = 0;
p->size = (size - sizeof(JSFreeBlock)) / sizeof(JSWord);
}
static void *js_shrink(JSContext *ctx, void *ptr, uint32_t new_size)
{
uint32_t old_size;
uint32_t diff;
new_size = (new_size + (JSW - 1)) & ~(JSW - 1);
if (new_size == 0) {
js_free(ctx, ptr);
return NULL;
}
old_size = get_mblock_size(ptr);
assert(new_size <= old_size);
diff = old_size - new_size;
if (diff == 0)
return ptr;
set_free_block((uint8_t *)ptr + new_size, diff);
return ptr;
}
JSValue JS_Throw(JSContext *ctx, JSValue obj)
{
ctx->current_exception = obj;
ctx->current_exception_is_uncatchable = FALSE;
return JS_EXCEPTION;
}
static int get_short_string(uint8_t *buf, JSValue val)
{
int len;
len = unicode_to_utf8(buf, JS_VALUE_GET_SPECIAL_VALUE(val));
buf[len] = '\0';
return len;
}
#define PF_ZERO_PAD (1 << 0)
#define PF_ALT_FORM (1 << 1)
#define PF_MARK_POS (1 << 2)
#define PF_LEFT_ADJ (1 << 3)
#define PF_PAD_POS (1 << 4)
#define PF_INT64 (1 << 5)
static BOOL is_digit(int c)
{
return (c >= '0' && c <= '9');
}
static void pad(JSWriteFunc *write_func, void *opaque, char c,
int width, int len)
{
char buf[16];
int l;
if (len >= width)
return;
width -= len;
memset(buf, c, min_int(sizeof(buf), width));
while (width != 0) {
l = min_int(width, sizeof(buf));
write_func(opaque, buf, l);
width -= l;
}
}
static void js_vprintf(JSWriteFunc *write_func, void *opaque, const char *fmt, va_list ap)
{
const char *p;
int width, prec, flags, c;
char tmp_buf[32], *buf;
size_t len;
while (*fmt != '\0') {
p = fmt;
while (*fmt != '%' && *fmt != '\0')
fmt++;
if (fmt > p)
write_func(opaque, p, fmt - p);
if (*fmt == '\0')
break;
fmt++;
flags = 0;
for(;;) {
c = *fmt;
if (c == '0') {
flags |= PF_ZERO_PAD;
} else if (c == '#') {
flags |= PF_ALT_FORM;
} else if (c == '+') {
flags |= PF_MARK_POS;
} else if (c == '-') {
flags |= PF_LEFT_ADJ;
} else if (c == ' ') {
flags |= PF_MARK_POS;
} else {
break;
}
fmt++;
}
width = 0;
if (*fmt == '*') {
width = va_arg(ap, int);
} else {
while (is_digit(*fmt)) {
width = width * 10 + *fmt - '0';
fmt++;
}
}
prec = 0;
if (*fmt == '.') {
fmt++;
if (*fmt == '*') {
prec = va_arg(ap, int);
} else {
while (is_digit(*fmt)) {
prec = prec * 10 + *fmt - '0';
fmt++;
}
}
}
for(;;) {
c = *fmt;
if (c == 'l') {
if (sizeof(long) == sizeof(int64_t) || fmt[-1] == 'l')
flags |= PF_INT64;
} else
if (c == 'z' || c == 't') {
if (sizeof(size_t) == sizeof(uint64_t))
flags |= PF_INT64;
} else {
break;
}
fmt++;
}
c = *fmt++;
buf = tmp_buf;
len = 0;
switch(c) {
case '%':
write_func(opaque, fmt - 1, 1);
break;
case 'c':
buf[0] = va_arg(ap, int);
len = 1;
flags &= ~PF_ZERO_PAD;
break;
case 's':
buf = va_arg(ap, char *);
if (!buf)
buf = "null";
len = strlen(buf);
flags &= ~PF_ZERO_PAD;
break;
case 'd':
if (flags & PF_INT64)
len = i64toa(buf, va_arg(ap, int64_t));
else
len = i32toa(buf, va_arg(ap, int32_t));
break;
case 'u':
if (flags & PF_INT64)
len = u64toa(buf, va_arg(ap, uint64_t));
else
len = u32toa(buf, va_arg(ap, uint32_t));
break;
case 'x':
if (flags & PF_INT64)
len = u64toa_radix(buf, va_arg(ap, uint64_t), 16);
else
len = u64toa_radix(buf, va_arg(ap, uint32_t), 16);
break;
case 'p':
buf[0] = '0';
buf[1] = 'x';
len = u64toa_radix(buf + 2, (uintptr_t)va_arg(ap, void *), 16);
len += 2;
break;
case 'o':
{
JSValue val = (flags & PF_INT64) ? va_arg(ap, uint64_t) : va_arg(ap, uint32_t);
if (JS_IsInt(val)) {
len = i32toa(buf, JS_VALUE_GET_INT(val));
} else
#ifdef JS_USE_SHORT_FLOAT
if (JS_IsShortFloat(val)) {
buf = "[short_float]";
goto do_strlen;
} else
#endif
if (!JS_IsPtr(val)) {
switch(JS_VALUE_GET_SPECIAL_TAG(val)) {
case JS_TAG_NULL:
buf = "null";
goto do_strlen;
case JS_TAG_UNDEFINED:
buf = "undefined";
goto do_strlen;
case JS_TAG_UNINITIALIZED:
buf = "uninitialized";
goto do_strlen;
case JS_TAG_BOOL:
buf = JS_VALUE_GET_SPECIAL_VALUE(val) ? "true" : "false";
goto do_strlen;
case JS_TAG_STRING_CHAR:
len = get_short_string((uint8_t *)buf, val);
break;
default:
buf = "[tag]";
goto do_strlen;
}
} else {
void *ptr = JS_VALUE_TO_PTR(val);
int mtag = ((JSMemBlockHeader *)ptr)->mtag;
switch(mtag) {
case JS_MTAG_STRING:
{
JSString *p = ptr;
buf = (char *)p->buf;
len = p->len;
}
break;
default:
buf = "[mtag]";
do_strlen:
len = strlen(buf);
break;
}
}
if ((flags & PF_ALT_FORM) && len > 0 && buf[len - 1] == '\n')
len--;
flags &= ~PF_ZERO_PAD;
}
break;
default:
goto error;
}
if (flags & PF_ZERO_PAD) {
pad(write_func, opaque, '0', width, len);
} else {
if (!(flags & PF_LEFT_ADJ))
pad(write_func, opaque, ' ', width, len);
}
write_func(opaque, buf, len);
if (flags & PF_LEFT_ADJ)
pad(write_func, opaque, ' ', width, len);
}
return;
error:
return;
}
static void __js_printf_like(2, 3) js_printf(JSContext *ctx,
const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
js_vprintf(ctx->write_func, ctx->opaque, fmt, ap);
va_end(ap);
}
static __maybe_unused void js_putchar(JSContext *ctx, uint8_t c)
{
ctx->write_func(ctx->opaque, &c, 1);
}
typedef struct {
char *ptr;
char *buf_end;
int len;
} SNPrintfState;
static void snprintf_write_func(void *opaque, const void *buf, size_t buf_len)
{
SNPrintfState *s = opaque;
size_t l;
s->len += buf_len;
l = min_size_t(buf_len, s->buf_end - s->ptr);
if (l != 0) {
memcpy(s->ptr, buf, l);
s->ptr += l;
}
}
static int js_vsnprintf(char *buf, size_t buf_size, const char *fmt, va_list ap)
{
SNPrintfState ss, *s = &ss;
s->ptr = buf;
s->buf_end = buf + max_size_t(buf_size, 1) - 1;
s->len = 0;
js_vprintf(snprintf_write_func, s, fmt, ap);
if (buf_size > 0)
*s->ptr = '\0';
return s->len;
}
static int __maybe_unused __js_printf_like(3, 4) js_snprintf(char *buf, size_t buf_size, const char *fmt, ...)
{
va_list ap;
int ret;
va_start(ap, fmt);
ret = js_vsnprintf(buf, buf_size, fmt, ap);
va_end(ap);
return ret;
}
JSValue __js_printf_like(3, 4) JS_ThrowError(JSContext *ctx, JSObjectClassEnum error_num,
const char *fmt, ...)
{
JSObject *p;
va_list ap;
char buf[128];
JSValue msg, error_obj;
JSGCRef msg_ref, error_obj_ref;
va_start(ap, fmt);
js_vsnprintf(buf, sizeof(buf), fmt, ap);
va_end(ap);
msg = JS_NewString(ctx, buf);
JS_PUSH_VALUE(ctx, msg);
error_obj = JS_NewObjectProtoClass(ctx, ctx->class_proto[error_num], JS_CLASS_ERROR,
sizeof(JSErrorData));
JS_POP_VALUE(ctx, msg);
if (JS_IsException(error_obj))
return error_obj;
p = JS_VALUE_TO_PTR(error_obj);
p->u.error.message = msg;
p->u.error.stack = JS_NULL;
if (error_num != JS_CLASS_SYNTAX_ERROR) {
JS_PUSH_VALUE(ctx, error_obj);
build_backtrace(ctx, error_obj, NULL, 0, 0, 0);
JS_POP_VALUE(ctx, error_obj);
}
return JS_Throw(ctx, error_obj);
}
JSValue JS_ThrowOutOfMemory(JSContext *ctx)
{
JSValue val;
if (ctx->in_out_of_memory)
return JS_Throw(ctx, JS_NULL);
ctx->in_out_of_memory = TRUE;
ctx->min_free_size = JS_MIN_CRITICAL_FREE_SIZE;
val = JS_ThrowInternalError(ctx, "out of memory");
ctx->in_out_of_memory = FALSE;
ctx->min_free_size = JS_MIN_FREE_SIZE;
return val;
}
#define JS_SHORTINT_MIN (-(1 << 30))
#define JS_SHORTINT_MAX ((1 << 30) - 1)
#ifdef JS_USE_SHORT_FLOAT
#define JS_FLOAT64_VALUE_EXP_MIN (1023 - 127)
#define JS_FLOAT64_VALUE_ADDEND ((uint64_t)(JS_FLOAT64_VALUE_EXP_MIN - (JS_TAG_SHORT_FLOAT << 8)) << 52)
static inline uint64_t rotl64(uint64_t a, int n)
{
return (a << n) | (a >> (64 - n));
}
static double js_get_short_float(JSValue v)
{
return uint64_as_float64(rotl64(v, 60) + JS_FLOAT64_VALUE_ADDEND);
}
static JSValue js_to_short_float(double d)
{
return rotl64(float64_as_uint64(d) - JS_FLOAT64_VALUE_ADDEND, 4);
}
#endif
static JSValue js_alloc_float64(JSContext *ctx, double d)
{
JSFloat64 *f;
f = js_malloc(ctx, sizeof(JSFloat64), JS_MTAG_FLOAT64);
if (!f)
return JS_EXCEPTION;
f->u.dval = d;
return JS_VALUE_FROM_PTR(f);
}
static JSValue __JS_NewFloat64(JSContext *ctx, double d)
{
if (float64_as_uint64(d) == 0x8000000000000000) {
return ctx->minus_zero;
} else
#ifdef JS_USE_SHORT_FLOAT
if (fabs(d) >= 0x1p-127 && fabs(d) <= 0x1p+128) {
return js_to_short_float(d);
} else
#endif
{
return js_alloc_float64(ctx, d);
}
}
static inline JSValue JS_NewShortInt(int32_t val)
{
return JS_TAG_INT + (val << 1);
}
#if defined(USE_SOFTFLOAT)
JSValue JS_NewFloat64(JSContext *ctx, double d)
{
uint64_t a, m;
int e, b, shift;
JSValue v;
a = float64_as_uint64(d);
if (a == 0) {
v = JS_NewShortInt(0);
} else {
e = (a >> 52) & 0x7ff;
if (e >= 1023 && e <= 1023 + 30 - 1) {
m = (a & (((uint64_t)1 << 52) - 1)) | ((uint64_t)1 << 52);
shift = 52 - (e - 1023);
if ((m & (((uint64_t)1 << shift) - 1)) != 0)
goto not_int;
b = m >> shift;
if (a >> 63)
b = -b;
v = JS_NewShortInt(b);
} else if (a == 0xc1d0000000000000) {
v = JS_NewShortInt(-(1 << 30));
} else {
not_int:
v = __JS_NewFloat64(ctx, d);
}
}
return v;
}
#else
JSValue JS_NewFloat64(JSContext *ctx, double d)
{
int32_t val;
if (d >= JS_SHORTINT_MIN && d <= JS_SHORTINT_MAX) {
val = (int32_t)d;
if (float64_as_uint64(d) == float64_as_uint64((double)val))
return JS_NewShortInt(val);
}
return __JS_NewFloat64(ctx, d);
}
#endif
static inline BOOL int64_is_short_int(int64_t val)
{
return val >= JS_SHORTINT_MIN && val <= JS_SHORTINT_MAX;
}
JSValue JS_NewInt64(JSContext *ctx, int64_t val)
{
JSValue v;
if (likely(int64_is_short_int(val))) {
v = JS_NewShortInt(val);
} else {
v = __JS_NewFloat64(ctx, val);
}
return v;
}
JSValue JS_NewInt32(JSContext *ctx, int32_t val)
{
return JS_NewInt64(ctx, val);
}
JSValue JS_NewUint32(JSContext *ctx, uint32_t val)
{
return JS_NewInt64(ctx, val);
}
static BOOL JS_IsPrimitive(JSContext *ctx, JSValue val)
{
if (!JS_IsPtr(val)) {
return JS_VALUE_GET_SPECIAL_TAG(val) != JS_TAG_SHORT_FUNC;
} else {
return (js_get_mtag(JS_VALUE_TO_PTR(val)) != JS_MTAG_OBJECT);
}
}
static BOOL JS_IsObject(JSContext *ctx, JSValue val)
{
if (!JS_IsPtr(val)) {
return FALSE;
} else {
JSObject *p = JS_VALUE_TO_PTR(val);
return (p->mtag == JS_MTAG_OBJECT);
}
}
int JS_GetClassID(JSContext *ctx, JSValue val)
{
if (!JS_IsPtr(val)) {
return -1;
} else {
JSObject *p = JS_VALUE_TO_PTR(val);
if (p->mtag != JS_MTAG_OBJECT)
return -1;
else
return p->class_id;
}
}
void JS_SetOpaque(JSContext *ctx, JSValue val, void *opaque)
{
JSObject *p;
assert(JS_IsPtr(val));
p = JS_VALUE_TO_PTR(val);
assert(p->mtag == JS_MTAG_OBJECT);
assert(p->class_id >= JS_CLASS_USER);
p->u.user.opaque = opaque;
}
void *JS_GetOpaque(JSContext *ctx, JSValue val)
{
JSObject *p;
assert(JS_IsPtr(val));
p = JS_VALUE_TO_PTR(val);
assert(p->mtag == JS_MTAG_OBJECT);
assert(p->class_id >= JS_CLASS_USER);
return p->u.user.opaque;
}
static JSObject *js_get_object_class(JSContext *ctx, JSValue val, int class_id)
{
if (!JS_IsPtr(val)) {
return NULL;
} else {
JSObject *p = JS_VALUE_TO_PTR(val);
if (p->mtag != JS_MTAG_OBJECT || p->class_id != class_id)
return NULL;
else
return p;
}
}
BOOL JS_IsFunction(JSContext *ctx, JSValue val)
{
if (!JS_IsPtr(val)) {
return JS_VALUE_GET_SPECIAL_TAG(val) == JS_TAG_SHORT_FUNC;
} else {
JSObject *p = JS_VALUE_TO_PTR(val);
return (p->mtag == JS_MTAG_OBJECT &&
(p->class_id == JS_CLASS_CLOSURE ||
p->class_id == JS_CLASS_C_FUNCTION));
}
}
static BOOL JS_IsFunctionObject(JSContext *ctx, JSValue val)
{
if (!JS_IsPtr(val)) {
return FALSE;
} else {
JSObject *p = JS_VALUE_TO_PTR(val);
return (p->mtag == JS_MTAG_OBJECT &&
(p->class_id == JS_CLASS_CLOSURE ||
p->class_id == JS_CLASS_C_FUNCTION));
}
}
BOOL JS_IsError(JSContext *ctx, JSValue val)
{
if (!JS_IsPtr(val)) {
return FALSE;
} else {
JSObject *p = JS_VALUE_TO_PTR(val);
return (p->mtag == JS_MTAG_OBJECT && p->class_id == JS_CLASS_ERROR);
}
}
static force_inline BOOL JS_IsIntOrShortFloat(JSValue val)
{
#ifdef JS_USE_SHORT_FLOAT
return JS_IsInt(val) || JS_IsShortFloat(val);
#else
return JS_IsInt(val);
#endif
}
BOOL JS_IsNumber(JSContext *ctx, JSValue val)
{
if (JS_IsIntOrShortFloat(val)) {
return TRUE;
} else if (JS_IsPtr(val)) {
void *ptr = JS_VALUE_TO_PTR(val);
return (js_get_mtag(ptr) == JS_MTAG_FLOAT64);
} else {
return FALSE;
}
}
BOOL JS_IsString(JSContext *ctx, JSValue val)
{
if (!JS_IsPtr(val)) {
return JS_VALUE_GET_SPECIAL_TAG(val) == JS_TAG_STRING_CHAR;
} else {
void *ptr = JS_VALUE_TO_PTR(val);
return (js_get_mtag(ptr) == JS_MTAG_STRING);
}
}
static JSString *js_alloc_string(JSContext *ctx, uint32_t buf_len)
{
JSString *p;
if (buf_len > JS_STRING_LEN_MAX) {
JS_ThrowInternalError(ctx, "string too long");
return NULL;
}
p = js_malloc(ctx, sizeof(JSString) + buf_len + 1, JS_MTAG_STRING);
if (!p)
return NULL;
p->is_unique = FALSE;
p->is_ascii = FALSE;
p->is_numeric = FALSE;
p->len = buf_len;
p->buf[buf_len] = '\0';
return p;
}
static inline JSValue JS_NewStringChar(uint32_t c)
{
return JS_VALUE_MAKE_SPECIAL(JS_TAG_STRING_CHAR, c);
}
static force_inline int utf8_char_len(int c)
{
int l;
if (c < 0x80) {
l = 1;
} else if (c < 0xc0) {
l = 1;
} else if (c < 0xe0) {
l = 2;
} else if (c < 0xf0) {
l = 3;
} else if (c < 0xf8) {
l = 4;
} else {
l = 1;
}
return l;
}
static BOOL is_ascii_string(const char *buf, size_t len)
{
size_t i;
for(i = 0; i < len; i++) {
if ((uint8_t)buf[i] > 0x7f)
return FALSE;
}
return TRUE;
}
static JSString *get_string_ptr(JSContext *ctx, JSStringCharBuf *buf,
JSValue val)
{
if (JS_VALUE_GET_SPECIAL_TAG(val) == JS_TAG_STRING_CHAR) {
JSString *p = (JSString *)buf;
p->is_unique = FALSE;
p->is_ascii = JS_VALUE_GET_SPECIAL_VALUE(val) <= 0x7f;
p->len = get_short_string(p->buf, val);
return p;
} else {
return JS_VALUE_TO_PTR(val);
}
}
static JSValue js_sub_string_utf8(JSContext *ctx, JSValue val,
uint32_t start0, uint32_t end0)
{
JSString *p, *p1;
int len, start, end, c;
BOOL start_surrogate, end_surrogate;
JSStringCharBuf buf;
JSGCRef val_ref;
const uint8_t *ptr;
size_t clen;
if (end0 - start0 == 0) {
return js_get_atom(ctx, JS_ATOM_empty);
}
start_surrogate = start0 & 1;
end_surrogate = end0 & 1;
start = start0 >> 1;
end = end0 >> 1;
len = end - start;
p1 = get_string_ptr(ctx, &buf, val);
ptr = p1->buf;
if (!start_surrogate && !end_surrogate && utf8_char_len(ptr[start]) == len) {
c = utf8_get(ptr + start, &clen);
return JS_NewStringChar(c);
}
JS_PUSH_VALUE(ctx, val);
p = js_alloc_string(ctx, len - start_surrogate + (end_surrogate ? 3 : 0));
JS_POP_VALUE(ctx, val);
if (!p)
return JS_EXCEPTION;
p1 = get_string_ptr(ctx, &buf, val);
ptr = p1->buf;
if (unlikely(start_surrogate || end_surrogate)) {
uint8_t *q = p->buf;
p->is_ascii = FALSE;
if (start_surrogate) {
c = utf8_get(ptr + start, &clen);
c = 0xdc00 + ((c - 0x10000) & 0x3ff);
q += unicode_to_utf8(q, c);
start += 4;
}
memcpy(q, ptr + start, end - start);
q += end - start;
if (end_surrogate) {
c = utf8_get(ptr + end, &clen);
c = 0xd800 + ((c - 0x10000) >> 10);
q += unicode_to_utf8(q, c);
}
assert((q - p->buf) == p->len);
} else {
p->is_ascii = p1->is_ascii ? TRUE : is_ascii_string((const char *)(ptr + start), len);
memcpy(p->buf, ptr + start, len);
}
return JS_VALUE_FROM_PTR(p);
}
JSValue JS_NewStringLen(JSContext *ctx, const char *buf, size_t len)
{
JSString *p;
if (len == 0) {
return js_get_atom(ctx, JS_ATOM_empty);
} else {
if (utf8_char_len(buf[0]) == len) {
size_t clen;
int c;
c = utf8_get((const uint8_t *)buf, &clen);
return JS_NewStringChar(c);
}
}
p = js_alloc_string(ctx, len);
if (!p)
return JS_EXCEPTION;
p->is_ascii = is_ascii_string((const char *)buf, len);
memcpy(p->buf, buf, len);
return JS_VALUE_FROM_PTR(p);
}
JSValue JS_NewString(JSContext *ctx, const char *buf)
{
return JS_NewStringLen(ctx, buf, strlen(buf));
}
static JSValue js_byte_array_to_string(JSContext *ctx, JSValue val, int len, BOOL is_ascii)
{
JSByteArray *arr = JS_VALUE_TO_PTR(val);
JSString *p;
assert(len + 1 <= arr->size);
if (len == 0) {
return js_get_atom(ctx, JS_ATOM_empty);
} else if (utf8_char_len(arr->buf[0]) == len) {
size_t clen;
return JS_NewStringChar(utf8_get(arr->buf, &clen));
} else {
js_shrink_byte_array(ctx, &val, len + 1);
p = (JSString *)arr;
p->mtag = JS_MTAG_STRING;
p->is_ascii = is_ascii;
p->is_unique = FALSE;
p->is_numeric = FALSE;
p->len = len;
return val;
}
}
static __maybe_unused int js_string_byte_len(JSContext *ctx, JSValue val)
{
if (JS_VALUE_GET_SPECIAL_TAG(val) == JS_TAG_STRING_CHAR) {
int c = JS_VALUE_GET_SPECIAL_VALUE(val);
if (c < 0x80)
return 1;
else if (c < 0x800)
return 2;
else if (c < 0x10000)
return 3;
else
return 4;
} else {
JSString *p = JS_VALUE_TO_PTR(val);
return p->len;
}
}
static BOOL is_valid_len4_utf8(const uint8_t *buf)
{
return (((buf[0] & 0xf) << 6) | (buf[1] & 0x3f)) >= 0x10;
}
static __maybe_unused void dump_string_pos_cache(JSContext *ctx)
{
int i;
JSStringPosCacheEntry *ce;
for(i = 0; i < JS_STRING_POS_CACHE_SIZE; i++) {
ce = &ctx->string_pos_cache[i];
printf("%d: ", i);
if (ce->str == JS_NULL) {
printf("<empty>\n");
} else {
JSString *p = JS_VALUE_TO_PTR(ce->str);
printf(" utf8_pos=%u/%u utf16_pos=%u\n",
ce->str_pos[POS_TYPE_UTF8], (int)p->len, ce->str_pos[POS_TYPE_UTF16]);
}
}
}
static uint32_t js_string_convert_pos(JSContext *ctx, JSValue val, uint32_t pos,
StringPosTypeEnum pos_type)
{
JSStringCharBuf buf;
JSString *p;
size_t i, clen, len, start;
uint32_t d_min, d, j;
JSStringPosCacheEntry *ce, *ce1;
uint32_t surrogate_flag, has_surrogate, limit;
int ce_idx;
p = get_string_ptr(ctx, &buf, val);
len = p->len;
if (p->is_ascii) {
if (pos_type == POS_TYPE_UTF8)
return min_int(len, pos / 2);
else
return min_int(len, pos) * 2;
}
if (pos_type == POS_TYPE_UTF8) {
has_surrogate = pos & 1;
pos >>= 1;
} else {
has_surrogate = 0;
}
ce = NULL;
if (len < JS_STRING_POS_CACHE_MIN_LEN) {
j = 0;
i = 0;
goto uncached;
}
d_min = pos;
for(ce_idx = 0; ce_idx < JS_STRING_POS_CACHE_SIZE; ce_idx++) {
ce1 = &ctx->string_pos_cache[ce_idx];
if (ce1->str == val) {
d = ce1->str_pos[pos_type];
d = d >= pos ? d - pos : pos - d;
if (d < d_min) {
d_min = d;
ce = ce1;
}
}
}
if (!ce) {
ce = &ctx->string_pos_cache[ctx->string_pos_cache_counter];
if (++ctx->string_pos_cache_counter == JS_STRING_POS_CACHE_SIZE)
ctx->string_pos_cache_counter = 0;
ce->str = val;
ce->str_pos[POS_TYPE_UTF8] = 0;
ce->str_pos[POS_TYPE_UTF16] = 0;
}
i = ce->str_pos[POS_TYPE_UTF8];
j = ce->str_pos[POS_TYPE_UTF16];
if (ce->str_pos[pos_type] <= pos) {
uncached:
surrogate_flag = 0;
if (pos_type == POS_TYPE_UTF8) {
limit = INT32_MAX;
len = pos;
} else {
limit = pos;
}
for(; i < len; i += clen) {
if (j == limit)
break;
clen = utf8_char_len(p->buf[i]);
if (clen == 4 && is_valid_len4_utf8(p->buf + i)) {
if ((j + 1) == limit) {
surrogate_flag = 1;
break;
}
j += 2;
} else {
j++;
}
}
} else {
surrogate_flag = 0;
if (pos_type == POS_TYPE_UTF8) {
start = pos;
limit = INT32_MAX;
} else {
limit = pos;
start = 0;
}
while (i > start) {
size_t i0 = i;
i--;
while ((p->buf[i] & 0xc0) == 0x80)
i--;
clen = i0 - i;
if (clen == 4 && is_valid_len4_utf8(p->buf + i)) {
j -= 2;
if ((j + 1) == limit) {
surrogate_flag = 1;
break;
}
} else {
j--;
}
if (j == limit)
break;
}
}
if (ce) {
ce->str_pos[POS_TYPE_UTF8] = i;
ce->str_pos[POS_TYPE_UTF16] = j;
}
if (pos_type == POS_TYPE_UTF8)
return j + has_surrogate;
else
return i * 2 + surrogate_flag;
}
static uint32_t js_string_utf16_to_utf8_pos(JSContext *ctx, JSValue val, uint32_t utf16_pos)
{
return js_string_convert_pos(ctx, val, utf16_pos, POS_TYPE_UTF16);
}
static uint32_t js_string_utf8_to_utf16_pos(JSContext *ctx, JSValue val, uint32_t utf8_pos)
{
return js_string_convert_pos(ctx, val, utf8_pos, POS_TYPE_UTF8);
}
static BOOL is_utf8_left_surrogate(const uint8_t *p)
{
return p[0] == 0xed && (p[1] >= 0xa0 && p[1] <= 0xaf);
}
static BOOL is_utf8_right_surrogate(const uint8_t *p)
{
return p[0] == 0xed && (p[1] >= 0xb0 && p[1] <= 0xbf);
}
typedef struct {
JSGCRef buffer_ref;
int len;
BOOL is_ascii;
} StringBuffer;
static int string_buffer_push(JSContext *ctx, StringBuffer *s, int len)
{
s->len = 0;
s->is_ascii = TRUE;
if (len > 0) {
JSByteArray *arr;
arr = js_alloc_byte_array(ctx, len);
if (!arr)
return -1;
s->buffer_ref.val = JS_VALUE_FROM_PTR(arr);
} else {
s->buffer_ref.val = js_get_atom(ctx, JS_ATOM_empty);
}
s->buffer_ref.prev = ctx->top_gc_ref;
ctx->top_gc_ref = &s->buffer_ref;
return 0;
}
static int string_buffer_concat_str(JSContext *ctx, StringBuffer *s, JSValue val2)
{
JSStringCharBuf buf1, buf2;
JSByteArray *arr;
JSString *p1, *p2;
int len, len1, len2;
JSValue val1;
uint8_t *q;
if (JS_IsException(s->buffer_ref.val))
return -1;
p2 = get_string_ptr(ctx, &buf2, val2);
len2 = p2->len;
if (len2 == 0)
return 0;
if (JS_IsString(ctx, s->buffer_ref.val)) {
p1 = get_string_ptr(ctx, &buf1, s->buffer_ref.val);
len1 = p1->len;
if (len1 == 0) {
s->buffer_ref.val = val2;
return 0;
}
arr = NULL;
val1 = s->buffer_ref.val;
s->buffer_ref.val = JS_NULL;
} else {
arr = JS_VALUE_TO_PTR(s->buffer_ref.val);
len1 = s->len;
val1 = JS_NULL;
}
len = len1 + len2;
if (len > JS_STRING_LEN_MAX) {
s->buffer_ref.val = JS_ThrowInternalError(ctx, "string too long");
return -1;
}
if (!arr || (len + 1) > arr->size) {
JSGCRef val1_ref, val2_ref;
JS_PUSH_VALUE(ctx, val1);
JS_PUSH_VALUE(ctx, val2);
s->buffer_ref.val = js_resize_byte_array(ctx, s->buffer_ref.val, len + 1);
JS_POP_VALUE(ctx, val2);
JS_POP_VALUE(ctx, val1);
if (JS_IsException(s->buffer_ref.val))
return -1;
arr = JS_VALUE_TO_PTR(s->buffer_ref.val);
if (val1 != JS_NULL) {
p1 = get_string_ptr(ctx, &buf1, val1);
s->is_ascii = p1->is_ascii;
memcpy(arr->buf, p1->buf, len1);
}
p2 = get_string_ptr(ctx, &buf2, val2);
}
q = arr->buf + len1;
if (len2 >= 3 && unlikely(is_utf8_right_surrogate(p2->buf)) &&
len1 >= 3 && is_utf8_left_surrogate(q - 3)) {
size_t clen;
int c;
c = (utf8_get(q - 3, &clen) & 0x3ff) << 10;
c |= (utf8_get(p2->buf, &clen) & 0x3ff);
c += 0x10000;
len -= 2;
len2 -= 3;
q -= 3;
q += unicode_to_utf8(q, c);
s->is_ascii = FALSE;
}
memcpy(q, p2->buf + p2->len - len2, len2);
s->len = len;
s->is_ascii &= p2->is_ascii;
return 0;
}
static int string_buffer_concat_utf8(JSContext *ctx, StringBuffer *s, JSValue str,
uint32_t start, uint32_t end)
{
JSValue val2;
if (end <= start)
return 0;
val2 = js_sub_string_utf8(ctx, str, start, end);
if (JS_IsException(val2)) {
s->buffer_ref.val = JS_EXCEPTION;
return -1;
}
return string_buffer_concat_str(ctx, s, val2);
}
static int string_buffer_concat_utf16(JSContext *ctx, StringBuffer *s, JSValue str,
uint32_t start, uint32_t end)
{
uint32_t start_utf8, end_utf8;
if (end <= start)
return 0;
start_utf8 = js_string_utf16_to_utf8_pos(ctx, str, start);
end_utf8 = js_string_utf16_to_utf8_pos(ctx, str, end);
return string_buffer_concat_utf8(ctx, s, str, start_utf8, end_utf8);
}
static int string_buffer_concat(JSContext *ctx, StringBuffer *s, JSValue val2)
{
val2 = JS_ToString(ctx, val2);
if (JS_IsException(val2)) {
s->buffer_ref.val = JS_EXCEPTION;
return -1;
}
return string_buffer_concat_str(ctx, s, val2);
}
static int string_buffer_putc(JSContext *ctx, StringBuffer *s, int c)
{
return string_buffer_concat_str(ctx, s, JS_NewStringChar(c));
}
static int string_buffer_puts(JSContext *ctx, StringBuffer *s, const char *str)
{
JSValue val;
val = JS_NewString(ctx, str);
if (JS_IsException(val))
return -1;
return string_buffer_concat_str(ctx, s, val);
}
static JSValue string_buffer_pop(JSContext *ctx, StringBuffer *s)
{
JSValue res;
if (JS_IsException(s->buffer_ref.val) ||
JS_IsString(ctx, s->buffer_ref.val)) {
res = s->buffer_ref.val;
} else {
if (s->len != 0) {
JSByteArray *arr = JS_VALUE_TO_PTR(s->buffer_ref.val);
arr->buf[s->len] = '\0';
}
res = js_byte_array_to_string(ctx, s->buffer_ref.val, s->len, s->is_ascii);
}
ctx->top_gc_ref = s->buffer_ref.prev;
return res;
}
static JSValue JS_ConcatString(JSContext *ctx, JSValue val1, JSValue val2)
{
StringBuffer b_s, *b = &b_s;
if (JS_IsException(val1) ||
JS_IsException(val2))
return JS_EXCEPTION;
string_buffer_push(ctx, b, 0);
string_buffer_concat_str(ctx, b, val1);
string_buffer_concat_str(ctx, b, val2);
return string_buffer_pop(ctx, b);
}
static BOOL js_string_eq(JSContext *ctx, JSValue val1, JSValue val2)
{
JSStringCharBuf buf1, buf2;
JSString *p1, *p2;
p1 = get_string_ptr(ctx, &buf1, val1);
p2 = get_string_ptr(ctx, &buf2, val2);
if (p1->len != p2->len)
return FALSE;
return !memcmp(p1->buf, p2->buf, p1->len);
}
static int string_get_cp(const uint8_t *p)
{
size_t clen;
while ((*p & 0xc0) == 0x80)
p--;
return utf8_get(p, &clen);
}
static int js_string_compare(JSContext *ctx, JSValue val1, JSValue val2)
{
JSStringCharBuf buf1, buf2;
int len, i, res;
JSString *p1, *p2;
p1 = get_string_ptr(ctx, &buf1, val1);
p2 = get_string_ptr(ctx, &buf2, val2);
len = min_int(p1->len, p2->len);
for(i = 0; i < len; i++) {
if (p1->buf[i] != p2->buf[i])
break;
}
if (i != len) {
int c1, c2;
c1 = string_get_cp(p1->buf + i);
c2 = string_get_cp(p2->buf + i);
if ((c1 < 0x10000 && c2 < 0x10000) ||
(c1 >= 0x10000 && c2 >= 0x10000)) {
if (c1 < c2)
res = -1;
else
res = 1;
} else if (c1 < 0x10000) {
c2 = 0xd800 + ((c2 - 0x10000) >> 10);
if (c1 <= c2)
res = -1;
else
res = 1;
} else {
c1 = 0xd800 + ((c1 - 0x10000) >> 10);
if (c1 < c2)
res = -1;
else
res = 1;
}
} else {
if (p1->len == p2->len)
res = 0;
else if (p1->len < p2->len)
res = -1;
else
res = 1;
}
return res;
}
static int js_string_len(JSContext *ctx, JSValue val)
{
if (JS_VALUE_GET_SPECIAL_TAG(val) == JS_TAG_STRING_CHAR) {
return JS_VALUE_GET_SPECIAL_VALUE(val) >= 0x10000 ? 2 : 1;
} else {
JSString *p;
p = JS_VALUE_TO_PTR(val);
if (p->is_ascii)
return p->len;
else
return js_string_utf8_to_utf16_pos(ctx, val, p->len * 2);
}
}
static int string_getcp(JSContext *ctx, JSValue str, uint32_t utf16_pos, BOOL is_codepoint)
{
JSString *p;
JSStringCharBuf buf;
uint32_t surrogate_flag, c, utf8_pos;
size_t clen;
utf8_pos = js_string_utf16_to_utf8_pos(ctx, str, utf16_pos);
surrogate_flag = utf8_pos & 1;
utf8_pos >>= 1;
p = get_string_ptr(ctx, &buf, str);
if (utf8_pos >= p->len)
return -1;
c = utf8_get(p->buf + utf8_pos, &clen);
if (c < 0x10000 || (!surrogate_flag && is_codepoint)) {
return c;
} else {
c -= 0x10000;
if (!surrogate_flag)
return 0xd800 + (c >> 10);
else
return 0xdc00 + (c & 0x3ff);
}
}
static int string_getc(JSContext *ctx, JSValue str, uint32_t utf16_pos)
{
return string_getcp(ctx, str, utf16_pos, FALSE);
}
static JSValue js_sub_string(JSContext *ctx, JSValue val, int start, int end)
{
uint32_t start_utf8, end_utf8;
if (end <= start)
return js_get_atom(ctx, JS_ATOM_empty);
start_utf8 = js_string_utf16_to_utf8_pos(ctx, val, start);
end_utf8 = js_string_utf16_to_utf8_pos(ctx, val, end);
return js_sub_string_utf8(ctx, val, start_utf8, end_utf8);
}
static inline int is_num(int c)
{
return c >= '0' && c <= '9';
}
static int js_is_numeric_string(JSContext *ctx, JSValue val)
{
int c, len;
double d;
const char *r, *q;
JSString *p;
JSByteArray *tmp_arr;
JSGCRef val_ref;
char buf[32];
p = JS_VALUE_TO_PTR(val);
if (p->len == 0 || !p->is_ascii)
return FALSE;
q = (const char *)p->buf;
c = *q;
if (c == '-') {
if (p->len == 1)
return FALSE;
q++;
c = *q;
}
if (!is_num(c))
return FALSE;
JS_PUSH_VALUE(ctx, val);
tmp_arr = js_alloc_byte_array(ctx, max_int(sizeof(JSATODTempMem),
sizeof(JSDTOATempMem)));
JS_POP_VALUE(ctx, val);
if (!tmp_arr)
return -1;
p = JS_VALUE_TO_PTR(val);
d = js_atod((char *)p->buf, &r, 10, 0, (JSATODTempMem *)tmp_arr->buf);
if ((r - (char *)p->buf) != p->len) {
js_free(ctx, tmp_arr);
return FALSE;
}
len = js_dtoa(buf, d, 10, 0, JS_DTOA_FORMAT_FREE, (JSDTOATempMem *)tmp_arr->buf);
js_free(ctx, tmp_arr);
return (p->len == len && !memcmp(buf, p->buf, len));
}
static JSValue find_atom(JSContext *ctx, int *pidx, const JSValueArray *arr, int len, JSValue val)
{
int a, b, m, r;
JSValue val1;
a = 0;
b = len - 1;
while (a <= b) {
m = (a + b) >> 1;
val1 = arr->arr[m];
r = js_string_compare(ctx, val, val1);
if (r == 0) {
*pidx = m;
return val1;
} else if (r < 0) {
b = m - 1;
} else {
a = m + 1;
}
}
*pidx = a;
return JS_NULL;
}
static JSValue JS_MakeUniqueString(JSContext *ctx, JSValue val)
{
JSString *p;
int a, is_numeric, i;
JSValueArray *arr;
const JSValueArray *arr1;
JSValue val1, new_tab;
JSGCRef val_ref;
if (!JS_IsPtr(val))
return val;
p = JS_VALUE_TO_PTR(val);
if (p->mtag != JS_MTAG_STRING || p->is_unique)
return val;
for(i = 0; i < ctx->n_rom_atom_tables; i++) {
arr1 = ctx->rom_atom_tables[i];
if (arr1) {
val1 = find_atom(ctx, &a, arr1, arr1->size, val);
if (!JS_IsNull(val1))
return val1;
}
}
arr = JS_VALUE_TO_PTR( ctx->unique_strings);
val1 = find_atom(ctx, &a, arr, ctx->unique_strings_len, val);
if (!JS_IsNull(val1))
return val1;
JS_PUSH_VALUE(ctx, val);
is_numeric = js_is_numeric_string(ctx, val);
JS_POP_VALUE(ctx, val);
if (is_numeric < 0)
return JS_EXCEPTION;
JS_PUSH_VALUE(ctx, val);
new_tab = js_resize_value_array(ctx, ctx->unique_strings,
ctx->unique_strings_len + 1);
JS_POP_VALUE(ctx, val);
if (JS_IsException(new_tab))
return JS_EXCEPTION;
ctx->unique_strings = new_tab;
arr = JS_VALUE_TO_PTR( ctx->unique_strings);
memmove(&arr->arr[a + 1], &arr->arr[a],
sizeof(arr->arr[0]) * (ctx->unique_strings_len - a));
arr->arr[a] = val;
p = JS_VALUE_TO_PTR(val);
p->is_unique = TRUE;
p->is_numeric = is_numeric;
ctx->unique_strings_len++;
return val;
}
static int JS_ToBool(JSContext *ctx, JSValue val)
{
if (JS_IsInt(val)) {
return JS_VALUE_GET_INT(val) != 0;
} else
#ifdef JS_USE_SHORT_FLOAT
if (JS_IsShortFloat(val)) {
double d;
d = js_get_short_float(val);
return !isnan(d) && d != 0;
} else
#endif
if (!JS_IsPtr(val)) {
switch(JS_VALUE_GET_SPECIAL_TAG(val)) {
case JS_TAG_BOOL:
case JS_TAG_NULL:
case JS_TAG_UNDEFINED:
return JS_VALUE_GET_SPECIAL_VALUE(val);
case JS_TAG_SHORT_FUNC:
case JS_TAG_STRING_CHAR:
return TRUE;
default:
return FALSE;
}
} else {
JSMemBlockHeader *h = JS_VALUE_TO_PTR(val);
switch(h->mtag) {
case JS_MTAG_STRING:
{
JSString *p = (JSString *)h;
return p->len != 0;
}
case JS_MTAG_FLOAT64:
{
JSFloat64 *p = (JSFloat64 *)h;
return !isnan(p->u.dval) && p->u.dval != 0;
}
default:
case JS_MTAG_OBJECT:
return TRUE;
}
}
}
const char *JS_ToCStringLen(JSContext *ctx, size_t *plen, JSValue val,
JSCStringBuf *buf)
{
const char *p;
int len;
val = JS_ToString(ctx, val);
if (JS_IsException(val))
return NULL;
if (JS_VALUE_GET_SPECIAL_TAG(val) == JS_TAG_STRING_CHAR) {
len = get_short_string(buf->buf, val);
p = (const char *)buf->buf;
} else {
JSString *r;
r = JS_VALUE_TO_PTR(val);
p = (const char *)r->buf;
len = r->len;
}
if (plen)
*plen = len;
return p;
}
const char *JS_ToCString(JSContext *ctx, JSValue val, JSCStringBuf *buf)
{
return JS_ToCStringLen(ctx, NULL, val, buf);
}
JSValue JS_GetException(JSContext *ctx)
{
JSValue obj;
obj = ctx->current_exception;
ctx->current_exception = JS_UNDEFINED;
return obj;
}
static JSValue JS_ToStringCheckObject(JSContext *ctx, JSValue val)
{
if (val == JS_NULL || val == JS_UNDEFINED)
return JS_ThrowTypeError(ctx, "null or undefined are forbidden");
return JS_ToString(ctx, val);
}
static JSValue JS_ThrowTypeErrorNotAnObject(JSContext *ctx)
{
return JS_ThrowTypeError(ctx, "not an object");
}
static inline BOOL is_num_string(JSContext *ctx, int32_t *pval, JSValue val)
{
JSStringCharBuf buf;
uint32_t n;
uint64_t n64;
JSString *p1;
int c, is_neg;
const uint8_t *p, *p_end;
p1 = get_string_ptr(ctx, &buf, val);
if (p1->len == 0 || p1->len > 11 || !p1->is_ascii)
return FALSE;
p = p1->buf;
p_end = p + p1->len;
c = *p++;
is_neg = 0;
if (c == '-') {
if (p >= p_end)
return FALSE;
is_neg = 1;
c = *p++;
}
if (!is_num(c))
return FALSE;
if (c == '0') {
if (p != p_end || is_neg)
return FALSE;
n = 0;
} else {
n = c - '0';
while (p < p_end) {
c = *p++;
if (!is_num(c))
return FALSE;
n64 = (uint64_t)n * 10 + (c - '0');
if (n64 > (JS_SHORTINT_MAX + is_neg))
return FALSE;
n = n64;
}
if (is_neg)
n = -n;
}
*pval = n;
return TRUE;
}
static BOOL JS_IsNumericProperty(JSContext *ctx, JSValue val)
{
JSString *p;
if (!JS_IsPtr(val))
return FALSE;
p = JS_VALUE_TO_PTR(val);
return p->is_numeric;
}
static JSValueArray *js_alloc_value_array(JSContext *ctx, int init_base, int new_size)
{
JSValueArray *arr;
int i;
if (new_size > JS_VALUE_ARRAY_SIZE_MAX) {
JS_ThrowOutOfMemory(ctx);
return NULL;
}
arr = js_malloc(ctx, sizeof(JSValueArray) + new_size * sizeof(JSValue), JS_MTAG_VALUE_ARRAY);
if (!arr)
return NULL;
arr->size = new_size;
for(i = init_base; i < new_size; i++)
arr->arr[i] = JS_UNDEFINED;
return arr;
}
static JSValue js_resize_value_array2(JSContext *ctx, JSValue val, int new_size, int prop_base)
{
JSValueArray *slots, *new_slots;
int old_size, new_size1;
JSGCRef val_ref;
if (val == JS_NULL) {
slots = NULL;
old_size = 0;
} else {
slots = JS_VALUE_TO_PTR(val);
old_size = slots->size;
}
if (unlikely(new_size > old_size)) {
new_size1 = old_size + old_size / 2;
if (new_size1 > new_size) {
new_size = new_size1;
if (prop_base != 0) {
int align = (new_size - prop_base) % 3;
if (align != 0)
new_size += 3 - align;
}
}
new_size = max_int(new_size, old_size + old_size / 2);
JS_PUSH_VALUE(ctx, val);
new_slots = js_alloc_value_array(ctx, old_size, new_size);
JS_POP_VALUE(ctx, val);
if (!new_slots)
return JS_EXCEPTION;
if (old_size > 0) {
slots = JS_VALUE_TO_PTR(val);
memcpy(new_slots->arr, slots->arr, old_size * sizeof(JSValue));
}
val = JS_VALUE_FROM_PTR(new_slots);
}
return val;
}
static JSValue js_resize_value_array(JSContext *ctx, JSValue val, int new_size)
{
return js_resize_value_array2(ctx, val, new_size, 0);
}
static void js_shrink_value_array(JSContext *ctx, JSValue *pval, int new_size)
{
JSValueArray *arr;
if (*pval == JS_NULL)
return;
arr = JS_VALUE_TO_PTR(*pval);
assert(new_size <= arr->size);
if (new_size == 0) {
js_free(ctx, arr);
*pval = JS_NULL;
} else {
arr = js_shrink(ctx, arr, sizeof(JSValueArray) + new_size * sizeof(JSValue));
arr->size = new_size;
}
}
static JSByteArray *js_alloc_byte_array(JSContext *ctx, int size)
{
JSByteArray *arr;
if (size > JS_BYTE_ARRAY_SIZE_MAX) {
JS_ThrowOutOfMemory(ctx);
return NULL;
}
arr = js_malloc(ctx, sizeof(JSByteArray) + size, JS_MTAG_BYTE_ARRAY);
if (!arr)
return NULL;
arr->size = size;
return arr;
}
static JSValue js_resize_byte_array(JSContext *ctx, JSValue val, int new_size)
{
JSByteArray *arr, *new_arr;
int old_size;
JSGCRef val_ref;
if (val == JS_NULL) {
arr = NULL;
old_size = 0;
} else {
arr = JS_VALUE_TO_PTR(val);
old_size = arr->size;
}
if (unlikely(new_size > old_size)) {
new_size = max_int(new_size, old_size + old_size / 2);
JS_PUSH_VALUE(ctx, val);
new_arr = js_alloc_byte_array(ctx, new_size);
JS_POP_VALUE(ctx, val);
if (!new_arr)
return JS_EXCEPTION;
if (old_size > 0) {
arr = JS_VALUE_TO_PTR(val);
memcpy(new_arr->buf, arr->buf, old_size);
}
val = JS_VALUE_FROM_PTR(new_arr);
}
return val;
}
static void js_shrink_byte_array(JSContext *ctx, JSValue *pval, int new_size)
{
JSByteArray *arr;
if (*pval == JS_NULL)
return;
arr = JS_VALUE_TO_PTR(*pval);
assert(new_size <= arr->size);
if (new_size == 0) {
js_free(ctx, arr);
*pval = JS_NULL;
} else {
arr = js_shrink(ctx, arr, sizeof(JSByteArray) + new_size);
arr->size = new_size;
}
}
static JSObject *JS_NewObjectProtoClass1(JSContext *ctx, JSValue proto,
int class_id, int extra_size)
{
JSObject *p;
JSGCRef proto_ref;
extra_size = (unsigned)(extra_size + JSW - 1) / JSW;
JS_PUSH_VALUE(ctx, proto);
p = js_malloc(ctx, offsetof(JSObject, u) + extra_size * JSW, JS_MTAG_OBJECT);
JS_POP_VALUE(ctx, proto);
if (!p)
return NULL;
p->class_id = class_id;
p->extra_size = extra_size;
p->proto = proto;
p->props = ctx->empty_props;
return p;
}
static JSValue JS_NewObjectProtoClass(JSContext *ctx, JSValue proto, int class_id, int extra_size)
{
JSObject *p;
p = JS_NewObjectProtoClass1(ctx, proto, class_id, extra_size);
if (!p)
return JS_EXCEPTION;
else
return JS_VALUE_FROM_PTR(p);
}
static JSValue JS_NewObjectClass(JSContext *ctx, int class_id, int extra_size)
{
return JS_NewObjectProtoClass(ctx, ctx->class_proto[class_id], class_id, extra_size);
}
JSValue JS_NewObjectClassUser(JSContext *ctx, int class_id)
{
JSObject *p;
assert(class_id >= JS_CLASS_USER);
p = JS_NewObjectProtoClass1(ctx, ctx->class_proto[class_id], class_id,
sizeof(JSObjectUserData));
if (!p)
return JS_EXCEPTION;
p->u.user.opaque = NULL;
return JS_VALUE_FROM_PTR(p);
}
JSValue JS_NewObject(JSContext *ctx)
{
return JS_NewObjectClass(ctx, JS_CLASS_OBJECT, 0);
}
JSValue JS_NewObjectPrealloc(JSContext *ctx, int n)
{
JSValue obj;
JSValueArray *arr;
JSObject *p;
JSGCRef obj_ref;
obj = JS_NewObjectClass(ctx, JS_CLASS_OBJECT, 0);
if (JS_IsException(obj) || n <= 0)
return obj;
JS_PUSH_VALUE(ctx, obj);
arr = js_alloc_props(ctx, n);
JS_POP_VALUE(ctx, obj);
if (!arr)
return JS_EXCEPTION;
p = JS_VALUE_TO_PTR(obj);
p->props = JS_VALUE_FROM_PTR(arr);
return obj;
}
JSValue JS_NewArray(JSContext *ctx, int initial_len)
{
JSObject *p;
JSValue val;
JSGCRef val_ref;
val = JS_NewObjectClass(ctx, JS_CLASS_ARRAY, sizeof(JSArrayData));
if (JS_IsException(val))
return val;
p = JS_VALUE_TO_PTR(val);
p->u.array.tab = JS_NULL;
p->u.array.len = 0;
if (initial_len > 0) {
JSValueArray *arr;
JS_PUSH_VALUE(ctx, val);
arr = js_alloc_value_array(ctx, 0, initial_len);
JS_POP_VALUE(ctx, val);
if (!arr)
return JS_EXCEPTION;
p = JS_VALUE_TO_PTR(val);
p->u.array.tab = JS_VALUE_FROM_PTR(arr);
p->u.array.len = initial_len;
}
return val;
}
static inline uint32_t hash_prop(JSValue prop)
{
return (prop / JSW) ^ (prop % JSW);
}
static force_inline JSProperty *find_own_property_inlined(JSContext *ctx,
JSObject *p, JSValue prop)
{
JSValueArray *arr;
JSProperty *pr;
uint32_t hash_mask, h, idx;
arr = JS_VALUE_TO_PTR(p->props);
hash_mask = JS_VALUE_GET_INT(arr->arr[1]);
h = hash_prop(prop) & hash_mask;
idx = arr->arr[2 + h];
while (idx != 0) {
pr = (JSProperty *)((uint8_t *)arr->arr + idx * (sizeof(JSValue) / 2));
if (pr->key == prop)
return pr;
idx = pr->hash_next;
}
return NULL;
}
static inline JSProperty *find_own_property(JSContext *ctx,
JSObject *p, JSValue prop)
{
return find_own_property_inlined(ctx, p, prop);
}
static JSValue get_special_prop(JSContext *ctx, JSValue val)
{
int idx;
idx = JS_VALUE_GET_INT(val);
if (idx >= 0)
return ctx->class_proto[idx];
else
return ctx->class_obj[-idx - 1];
}
static JSValue JS_GetPropertyInternal(JSContext *ctx, JSValue obj, JSValue prop,
BOOL allow_tail_call)
{
JSObject *p;
JSValue proto;
JSProperty *pr;
if (unlikely(!JS_IsPtr(obj))) {
if (JS_IsIntOrShortFloat(obj)) {
p = JS_VALUE_TO_PTR(ctx->class_proto[JS_CLASS_NUMBER]);
} else {
switch(JS_VALUE_GET_SPECIAL_TAG(obj)) {
case JS_TAG_BOOL:
p = JS_VALUE_TO_PTR(ctx->class_proto[JS_CLASS_BOOLEAN]);
break;
case JS_TAG_SHORT_FUNC:
p = JS_VALUE_TO_PTR(ctx->class_proto[JS_CLASS_CLOSURE]);
break;
case JS_TAG_STRING_CHAR:
goto string_proto;
case JS_TAG_NULL:
return JS_ThrowTypeError(ctx, "cannot read property '%"JSValue_PRI"' of null", prop);
case JS_TAG_UNDEFINED:
return JS_ThrowTypeError(ctx, "cannot read property '%"JSValue_PRI"' of undefined", prop);
default:
goto no_prop;
}
}
} else {
p = JS_VALUE_TO_PTR(obj);
}
if (unlikely(p->mtag != JS_MTAG_OBJECT)) {
switch(p->mtag) {
case JS_MTAG_FLOAT64:
p = JS_VALUE_TO_PTR(ctx->class_proto[JS_CLASS_NUMBER]);
break;
case JS_MTAG_STRING:
string_proto:
{
if (JS_IsInt(prop)) {
JSValue ret;
ret = js_string_charAt(ctx, &obj, 1, &prop, magic_internalAt);
if (!JS_IsUndefined(ret))
return ret;
}
p = JS_VALUE_TO_PTR(ctx->class_proto[JS_CLASS_STRING]);
}
break;
default:
no_prop:
return JS_ThrowTypeError(ctx, "cannot read property '%"JSValue_PRI"' of value", prop);
}
}
for(;;) {
if (p->class_id == JS_CLASS_ARRAY) {
if (JS_IsInt(prop)) {
uint32_t idx = JS_VALUE_GET_INT(prop);
if (idx < p->u.array.len) {
JSValueArray *arr = JS_VALUE_TO_PTR(p->u.array.tab);
return arr->arr[idx];
}
} else if (JS_IsNumericProperty(ctx, prop)) {
return JS_UNDEFINED;
}
} else if (p->class_id >= JS_CLASS_UINT8C_ARRAY &&
p->class_id <= JS_CLASS_FLOAT64_ARRAY) {
if (JS_IsInt(prop)) {
uint32_t idx = JS_VALUE_GET_INT(prop);
JSObject *pbuffer;
JSByteArray *arr;
if (idx < p->u.typed_array.len) {
idx += p->u.typed_array.offset;
pbuffer = JS_VALUE_TO_PTR(p->u.typed_array.buffer);
arr = JS_VALUE_TO_PTR(pbuffer->u.array_buffer.byte_buffer);
switch(p->class_id) {
default:
case JS_CLASS_UINT8C_ARRAY:
case JS_CLASS_UINT8_ARRAY:
return JS_NewShortInt(*((uint8_t *)arr->buf + idx));
case JS_CLASS_INT8_ARRAY:
return JS_NewShortInt(*((int8_t *)arr->buf + idx));
case JS_CLASS_INT16_ARRAY:
return JS_NewShortInt(*((int16_t *)arr->buf + idx));
case JS_CLASS_UINT16_ARRAY:
return JS_NewShortInt(*((uint16_t *)arr->buf + idx));
case JS_CLASS_INT32_ARRAY:
return JS_NewInt32(ctx, *((int32_t *)arr->buf + idx));
case JS_CLASS_UINT32_ARRAY:
return JS_NewUint32(ctx, *((uint32_t *)arr->buf + idx));
case JS_CLASS_FLOAT32_ARRAY:
return JS_NewFloat64(ctx, *((float *)arr->buf + idx));
case JS_CLASS_FLOAT64_ARRAY:
return JS_NewFloat64(ctx, *((double *)arr->buf + idx));
}
}
} else if (JS_IsNumericProperty(ctx, prop)) {
return JS_UNDEFINED;
}
}
pr = find_own_property(ctx, p, prop);
if (pr) {
if (likely(pr->prop_type == JS_PROP_NORMAL)) {
return pr->value;
} else if (pr->prop_type == JS_PROP_VARREF) {
JSVarRef *pv = JS_VALUE_TO_PTR(pr->value);
return pv->u.value;
} else if (pr->prop_type == JS_PROP_SPECIAL) {
return get_special_prop(ctx, pr->value);
} else {
JSValueArray *arr = JS_VALUE_TO_PTR(pr->value);
JSValue getter = arr->arr[0];
if (getter == JS_UNDEFINED)
return JS_UNDEFINED;
if (allow_tail_call) {
ctx->sp[-1] = ctx->sp[0];
ctx->sp[0] = getter;
ctx->sp--;
return JS_NewTailCall(0);
} else {
JSGCRef getter_ref, obj_ref;
int err;
JS_PUSH_VALUE(ctx, getter);
JS_PUSH_VALUE(ctx, obj);
err = JS_StackCheck(ctx, 2);
JS_POP_VALUE(ctx, obj);
JS_POP_VALUE(ctx, getter);
if (err)
return JS_EXCEPTION;
JS_PushArg(ctx, getter);
JS_PushArg(ctx, obj);
return JS_Call(ctx, 0);
}
}
}
proto = p->proto;
if (proto == JS_NULL)
break;
p = JS_VALUE_TO_PTR(proto);
}
return JS_UNDEFINED;
}
static JSValue JS_GetProperty(JSContext *ctx, JSValue obj, JSValue prop)
{
return JS_GetPropertyInternal(ctx, obj, prop, FALSE);
}
JSValue JS_GetPropertyStr(JSContext *ctx, JSValue this_obj, const char *str)
{
JSValue prop;
JSGCRef this_obj_ref;
JS_PUSH_VALUE(ctx, this_obj);
prop = JS_NewString(ctx, str);
if (!JS_IsException(prop)) {
prop = JS_ToPropertyKey(ctx, prop);
}
JS_POP_VALUE(ctx, this_obj);
if (JS_IsException(prop))
return prop;
return JS_GetProperty(ctx, this_obj, prop);
}
JSValue JS_GetPropertyUint32(JSContext *ctx, JSValue obj, uint32_t idx)
{
if (idx > JS_SHORTINT_MAX)
return JS_ThrowRangeError(ctx, "invalid array index");
return JS_GetProperty(ctx, obj, JS_NewInt32(ctx, idx));
}
static BOOL JS_HasProperty(JSContext *ctx, JSValue obj, JSValue prop)
{
JSObject *p;
JSProperty *pr;
if (!JS_IsPtr(obj))
return FALSE;
p = JS_VALUE_TO_PTR(obj);
if (p->mtag != JS_MTAG_OBJECT)
return FALSE;
for(;;) {
pr = find_own_property(ctx, p, prop);
if (pr)
return TRUE;
obj = p->proto;
if (obj == JS_NULL)
break;
p = JS_VALUE_TO_PTR(obj);
}
return FALSE;
}
static int get_prop_hash_size_log2(int prop_count)
{
if (prop_count <= 1)
return 0;
else
return (32 - clz32(prop_count - 1)) - 1;
}
static JSValueArray *js_alloc_props(JSContext *ctx, int n)
{
int hash_size_log2, hash_mask, size, i, first_free;
JSValueArray *arr;
JSProperty *pr;
hash_size_log2 = get_prop_hash_size_log2(n);
hash_mask = (1 << hash_size_log2) - 1;
first_free = 2 + hash_mask + 1;
size = first_free + 3 * n;
arr = js_alloc_value_array(ctx, 0, size);
if (!arr)
return NULL;
arr->arr[0] = JS_NewShortInt(0);
arr->arr[1] = JS_NewShortInt(hash_mask);
for(i = 0; i <= hash_mask; i++)
arr->arr[2 + i] = 0;
pr = NULL;
for(i = 0; i < n; i++) {
pr = (JSProperty *)&arr->arr[2 + hash_mask + 1 + 3 * i];
pr->key = JS_UNINITIALIZED;
}
pr->hash_next = first_free << 1;
return arr;
}
static void js_rehash_props(JSContext *ctx, JSObject *p, BOOL gc_rehash)
{
JSValueArray *arr;
int prop_count, hash_mask, h, idx, i, j;
JSProperty *pr;
arr = JS_VALUE_TO_PTR(p->props);
if (JS_IS_ROM_PTR(ctx, arr))
return;
hash_mask = JS_VALUE_GET_INT(arr->arr[1]);
if (hash_mask == 0 && gc_rehash)
return;
prop_count = JS_VALUE_GET_INT(arr->arr[0]);
for(i = 0; i <= hash_mask; i++) {
arr->arr[2 + i] = JS_NewShortInt(0);
}
for(i = 0, j = 0; j < prop_count; i++) {
idx = 2 + (hash_mask + 1) + 3 * i;
pr = (JSProperty *)&arr->arr[idx];
if (pr->key != JS_UNINITIALIZED) {
h = hash_prop(pr->key) & hash_mask;
pr->hash_next = arr->arr[2 + h];
arr->arr[2 + h] = JS_NewShortInt(idx);
j++;
}
}
}
static void js_compact_props(JSContext *ctx, JSObject *p)
{
JSValueArray *arr;
int prop_count, hash_mask, i, j, hash_size_log2;
int new_size, new_hash_mask;
JSProperty *pr, *pr1;
arr = JS_VALUE_TO_PTR(p->props);
prop_count = JS_VALUE_GET_INT(arr->arr[0]);
if (prop_count == 0) {
if (p->props != ctx->empty_props) {
p->props = ctx->empty_props;
}
return;
}
hash_mask = JS_VALUE_GET_INT(arr->arr[1]);
hash_size_log2 = get_prop_hash_size_log2(prop_count);
new_hash_mask = min_int(hash_mask, (1 << hash_size_log2) - 1);
new_size = 2 + new_hash_mask + 1 + 3 * prop_count;
if (new_size >= arr->size)
return;
arr->arr[1] = JS_NewShortInt(new_hash_mask);
for(i = 0, j = 0; j < prop_count; i++) {
pr = (JSProperty *)&arr->arr[2 + (hash_mask + 1) + 3 * i];
if (pr->key != JS_UNINITIALIZED) {
pr1 = (JSProperty *)&arr->arr[2 + (new_hash_mask + 1) + 3 * j];
*pr1 = *pr;
j++;
}
}
js_shrink_value_array(ctx, &p->props, new_size);
js_rehash_props(ctx, p, FALSE);
}
static int js_update_props(JSContext *ctx, JSValue obj)
{
JSObject *p;
JSValueArray *arr, *arr1;
JSGCRef obj_ref;
int i, idx, prop_count, hash_mask;
JSProperty *pr;
p = JS_VALUE_TO_PTR(obj);
arr = JS_VALUE_TO_PTR(p->props);
if (!JS_IS_ROM_PTR(ctx, arr))
return 0;
JS_PUSH_VALUE(ctx, obj);
arr1 = js_alloc_value_array(ctx, 0, arr->size);
JS_POP_VALUE(ctx, obj);
if (!arr1)
return -1;
memcpy(arr1->arr, arr->arr, arr->size * sizeof(JSValue));
prop_count = JS_VALUE_GET_INT(arr1->arr[0]);
hash_mask = JS_VALUE_GET_INT(arr1->arr[1]);
assert(arr1->size == 2 + (hash_mask + 1) + 3 * prop_count);
for(i = 0; i < prop_count; i++) {
idx = 2 + (hash_mask + 1) + 3 * i;
pr = (JSProperty *)&arr1->arr[idx];
if (pr->prop_type == JS_PROP_SPECIAL) {
pr->value = get_special_prop(ctx, pr->value);
pr->prop_type = JS_PROP_NORMAL;
}
}
p = JS_VALUE_TO_PTR(obj);
p->props = JS_VALUE_FROM_PTR(arr1);
return 0;
}
static int get_first_free(JSValueArray *arr)
{
JSProperty *pr1;
int first_free;
pr1 = (JSProperty *)&arr->arr[arr->size - 3];
if (pr1->key == JS_UNINITIALIZED)
first_free = pr1->hash_next >> 1;
else
first_free = arr->size;
return first_free;
}
static JSProperty *js_create_property(JSContext *ctx, JSValue obj,
JSValue prop)
{
JSObject *p;
JSValueArray *arr;
int prop_count, hash_mask, new_size, h, first_free, new_hash_mask;
JSProperty *pr, *pr1;
JSValue new_props;
JSGCRef obj_ref, prop_ref;
p = JS_VALUE_TO_PTR(obj);
arr = JS_VALUE_TO_PTR(p->props);
prop_count = JS_VALUE_GET_INT(arr->arr[0]);
hash_mask = JS_VALUE_GET_INT(arr->arr[1]);
pr1 = (JSProperty *)&arr->arr[arr->size - 3];
if (pr1->key != JS_UNINITIALIZED) {
if (p->props == ctx->empty_props) {
JS_PUSH_VALUE(ctx, obj);
JS_PUSH_VALUE(ctx, prop);
arr = js_alloc_props(ctx, 1);
JS_POP_VALUE(ctx, prop);
JS_POP_VALUE(ctx, obj);
if (!arr)
return NULL;
p = JS_VALUE_TO_PTR(obj);
p->props = JS_VALUE_FROM_PTR(arr);
first_free = 3;
} else {
first_free = arr->size;
new_size = first_free + 3;
new_hash_mask = hash_mask;
if ((prop_count + 1) > 2 * (hash_mask + 1)) {
new_hash_mask = 2 * (hash_mask + 1) - 1;
new_size += new_hash_mask - hash_mask;
}
JS_PUSH_VALUE(ctx, obj);
JS_PUSH_VALUE(ctx, prop);
new_props = js_resize_value_array2(ctx, p->props, new_size, 2 + new_hash_mask + 1);
JS_POP_VALUE(ctx, prop);
JS_POP_VALUE(ctx, obj);
if (JS_IsException(new_props))
return NULL;
p = JS_VALUE_TO_PTR(obj);
p->props = new_props;
arr = JS_VALUE_TO_PTR(p->props);
if (new_hash_mask != hash_mask) {
memmove(&arr->arr[2 + (new_hash_mask + 1)],
&arr->arr[2 + (hash_mask + 1)],
(first_free - (2 + hash_mask + 1)) * sizeof(JSValue));
first_free += new_hash_mask - hash_mask;
hash_mask = new_hash_mask;
arr->arr[1] = JS_NewShortInt(hash_mask);
js_rehash_props(ctx, p, FALSE);
}
}
pr1 = (JSProperty *)&arr->arr[arr->size - 3];
pr1->key = JS_UNINITIALIZED;
} else {
first_free = pr1->hash_next >> 1;
}
pr = (JSProperty *)&arr->arr[first_free];
pr->key = prop;
pr->value = JS_UNDEFINED;
pr->prop_type = JS_PROP_NORMAL;
h = hash_prop(prop) & hash_mask;
pr->hash_next = arr->arr[2 + h];
arr->arr[2 + h] = JS_NewShortInt(first_free);
arr->arr[0] = JS_NewShortInt(prop_count + 1);
first_free += 3;
if (first_free < arr->size) {
pr1 = (JSProperty *)&arr->arr[arr->size - 3];
pr1->hash_next = first_free << 1;
}
return pr;
}
#define JS_DEF_PROP_LOOKUP (1 << 0)
#define JS_DEF_PROP_RET_VAL (1 << 1)
#define JS_DEF_PROP_HAS_VALUE (1 << 2)
#define JS_DEF_PROP_HAS_GET (1 << 3)
#define JS_DEF_PROP_HAS_SET (1 << 4)
static JSValue JS_DefinePropertyInternal(JSContext *ctx, JSValue obj,
JSValue prop, JSValue val,
JSValue setter, int flags)
{
JSProperty *pr;
JSValueArray *arr;
JSGCRef obj_ref, prop_ref, val_ref, setter_ref;
int ret, prop_type;
JS_PUSH_VALUE(ctx, obj);
JS_PUSH_VALUE(ctx, prop);
JS_PUSH_VALUE(ctx, val);
JS_PUSH_VALUE(ctx, setter);
ret = js_update_props(ctx, obj);
JS_POP_VALUE(ctx, setter);
JS_POP_VALUE(ctx, val);
JS_POP_VALUE(ctx, prop);
JS_POP_VALUE(ctx, obj);
if (ret)
return JS_EXCEPTION;
if (flags & JS_DEF_PROP_LOOKUP) {
pr = find_own_property(ctx, JS_VALUE_TO_PTR(obj), prop);
if (pr) {
if (flags & JS_DEF_PROP_HAS_VALUE) {
if (pr->prop_type == JS_PROP_NORMAL) {
pr->value = val;
} else if (pr->prop_type == JS_PROP_VARREF) {
JSVarRef *pv = JS_VALUE_TO_PTR(pr->value);
pv->u.value = val;
} else {
goto error_modify;
}
} else if (flags & (JS_DEF_PROP_HAS_GET | JS_DEF_PROP_HAS_SET)) {
if (pr->prop_type != JS_PROP_GETSET) {
error_modify:
return JS_ThrowTypeError(ctx, "cannot modify getter/setter/value kind");
}
arr = JS_VALUE_TO_PTR(pr->value);
if (unlikely(JS_IS_ROM_PTR(ctx, arr))) {
JSValueArray *arr2;
JS_PUSH_VALUE(ctx, obj);
JS_PUSH_VALUE(ctx, prop);
JS_PUSH_VALUE(ctx, val);
JS_PUSH_VALUE(ctx, setter);
arr2 = js_alloc_value_array(ctx, 0, 2);
JS_POP_VALUE(ctx, setter);
JS_POP_VALUE(ctx, val);
JS_POP_VALUE(ctx, prop);
JS_POP_VALUE(ctx, obj);
if (!arr2)
return JS_EXCEPTION;
pr = find_own_property(ctx, JS_VALUE_TO_PTR(obj), prop);
arr = JS_VALUE_TO_PTR(pr->value);
arr2->arr[0] = arr->arr[0];
arr2->arr[1] = arr->arr[1];
pr->value = JS_VALUE_FROM_PTR(arr2);
arr = arr2;
}
if (flags & JS_DEF_PROP_HAS_GET)
arr->arr[0] = val;
if (flags & JS_DEF_PROP_HAS_SET)
arr->arr[1] = setter;
}
goto done;
}
}
if (flags & (JS_DEF_PROP_HAS_GET | JS_DEF_PROP_HAS_SET)) {
prop_type = JS_PROP_GETSET;
JS_PUSH_VALUE(ctx, obj);
JS_PUSH_VALUE(ctx, prop);
JS_PUSH_VALUE(ctx, val);
JS_PUSH_VALUE(ctx, setter);
arr = js_alloc_value_array(ctx, 0, 2);
JS_POP_VALUE(ctx, setter);
JS_POP_VALUE(ctx, val);
JS_POP_VALUE(ctx, prop);
JS_POP_VALUE(ctx, obj);
if (!arr)
return JS_EXCEPTION;
arr->arr[0] = val;
arr->arr[1] = setter;
val = JS_VALUE_FROM_PTR(arr);
} else if (obj == ctx->global_obj) {
JSVarRef *pv;
prop_type = JS_PROP_VARREF;
JS_PUSH_VALUE(ctx, obj);
JS_PUSH_VALUE(ctx, prop);
JS_PUSH_VALUE(ctx, val);
pv = js_malloc(ctx, sizeof(JSVarRef) - sizeof(JSValue), JS_MTAG_VARREF);
JS_POP_VALUE(ctx, val);
JS_POP_VALUE(ctx, prop);
JS_POP_VALUE(ctx, obj);
if (!pv)
return JS_EXCEPTION;
pv->is_detached = TRUE;
pv->u.value = val;
val = JS_VALUE_FROM_PTR(pv);
} else {
prop_type = JS_PROP_NORMAL;
}
JS_PUSH_VALUE(ctx, val);
pr = js_create_property(ctx, obj, prop);
JS_POP_VALUE(ctx, val);
if (!pr)
return JS_EXCEPTION;
pr->prop_type = prop_type;
pr->value = val;
done:
if (flags & JS_DEF_PROP_RET_VAL) {
return pr->value;
} else {
return JS_UNDEFINED;
}
}
static JSValue JS_DefinePropertyValue(JSContext *ctx, JSValue obj,
JSValue prop, JSValue val)
{
return JS_DefinePropertyInternal(ctx, obj, prop, val, JS_NULL,
JS_DEF_PROP_LOOKUP | JS_DEF_PROP_HAS_VALUE);
}
static JSValue JS_DefinePropertyGetSet(JSContext *ctx, JSValue obj,
JSValue prop, JSValue getter,
JSValue setter, int flags)
{
return JS_DefinePropertyInternal(ctx, obj, prop, getter, setter,
JS_DEF_PROP_LOOKUP | flags);
}
static JSValue add_global_var(JSContext *ctx, JSValue prop, BOOL define_flag)
{
JSObject *p;
JSProperty *pr;
p = JS_VALUE_TO_PTR(ctx->global_obj);
pr = find_own_property(ctx, p, prop);
if (pr) {
if (pr->prop_type != JS_PROP_VARREF)
return JS_ThrowReferenceError(ctx, "global variable '%"JSValue_PRI"' must be a reference", prop);
if (define_flag) {
JSVarRef *pv = JS_VALUE_TO_PTR(pr->value);
if (pv->u.value == JS_UNINITIALIZED)
pv->u.value = JS_UNDEFINED;
}
return pr->value;
}
return JS_DefinePropertyInternal(ctx, ctx->global_obj, prop,
define_flag ? JS_UNDEFINED : JS_UNINITIALIZED, JS_NULL,
JS_DEF_PROP_RET_VAL | JS_DEF_PROP_HAS_VALUE);
}
static JSValue JS_SetPropertyInternal(JSContext *ctx, JSValue this_obj,
JSValue prop, JSValue val,
BOOL allow_tail_call)
{
JSValue proto;
JSObject *p;
JSProperty *pr;
BOOL is_obj;
if (unlikely(!JS_IsPtr(this_obj))) {
is_obj = FALSE;
if (JS_IsIntOrShortFloat(this_obj)) {
p = JS_VALUE_TO_PTR(ctx->class_proto[JS_CLASS_NUMBER]);
goto prototype_lookup;
} else {
switch(JS_VALUE_GET_SPECIAL_TAG(this_obj)) {
case JS_TAG_BOOL:
p = JS_VALUE_TO_PTR(ctx->class_proto[JS_CLASS_BOOLEAN]);
goto prototype_lookup;
case JS_TAG_SHORT_FUNC:
p = JS_VALUE_TO_PTR(ctx->class_proto[JS_CLASS_CLOSURE]);
goto prototype_lookup;
case JS_TAG_STRING_CHAR:
p = JS_VALUE_TO_PTR(ctx->class_proto[JS_CLASS_STRING]);
goto prototype_lookup;
case JS_TAG_NULL:
return JS_ThrowTypeError(ctx, "cannot set property '%"JSValue_PRI"' of null", prop);
case JS_TAG_UNDEFINED:
return JS_ThrowTypeError(ctx, "cannot set property '%"JSValue_PRI"' of undefined", prop);
default:
goto no_prop;
}
}
} else {
is_obj = TRUE;
p = JS_VALUE_TO_PTR(this_obj);
}
if (unlikely(p->mtag != JS_MTAG_OBJECT)) {
is_obj = FALSE;
switch(p->mtag) {
case JS_MTAG_FLOAT64:
p = JS_VALUE_TO_PTR(ctx->class_proto[JS_CLASS_NUMBER]);
goto prototype_lookup;
case JS_MTAG_STRING:
p = JS_VALUE_TO_PTR(ctx->class_proto[JS_CLASS_STRING]);
goto prototype_lookup;
default:
no_prop:
return JS_ThrowTypeError(ctx, "cannot set property '%"JSValue_PRI"' of value", prop);
}
}
if (p->class_id == JS_CLASS_ARRAY) {
if (JS_IsInt(prop)) {
JSValueArray *arr;
uint32_t idx = JS_VALUE_GET_INT(prop);
if (idx < p->u.array.len) {
arr = JS_VALUE_TO_PTR(p->u.array.tab);
arr->arr[idx] = val;
return JS_UNDEFINED;
} else if (idx == p->u.array.len) {
JSValue new_tab;
JSGCRef this_obj_ref, val_ref;
JS_PUSH_VALUE(ctx, this_obj);
JS_PUSH_VALUE(ctx, val);
new_tab = js_resize_value_array(ctx, p->u.array.tab, idx + 1);
JS_POP_VALUE(ctx, val);
JS_POP_VALUE(ctx, this_obj);
if (JS_IsException(new_tab))
return JS_EXCEPTION;
p = JS_VALUE_TO_PTR(this_obj);
p->u.array.tab = new_tab;
arr = JS_VALUE_TO_PTR(p->u.array.tab);
arr->arr[idx] = val;
p->u.array.len++;
return JS_UNDEFINED;
} else {
goto invalid_array_subscript;
}
} else if (JS_IsNumericProperty(ctx, prop)) {
goto invalid_array_subscript;
}
} else if (p->class_id >= JS_CLASS_UINT8C_ARRAY &&
p->class_id <= JS_CLASS_FLOAT64_ARRAY) {
if (JS_IsInt(prop)) {
uint32_t idx = JS_VALUE_GET_INT(prop);
int v, conv_ret;
double d;
JSObject *pbuffer;
JSByteArray *arr;
JSGCRef val_ref, this_obj_ref;
JS_PUSH_VALUE(ctx, this_obj);
JS_PUSH_VALUE(ctx, val);
switch(p->class_id) {
case JS_CLASS_UINT8C_ARRAY:
conv_ret = JS_ToUint8Clamp(ctx, &v, val);
break;
case JS_CLASS_FLOAT32_ARRAY:
case JS_CLASS_FLOAT64_ARRAY:
conv_ret = JS_ToNumber(ctx, &d, val);
break;
default:
conv_ret = JS_ToInt32(ctx, &v, val);
break;
}
JS_POP_VALUE(ctx, val);
JS_POP_VALUE(ctx, this_obj);
if (conv_ret)
return JS_EXCEPTION;
p = JS_VALUE_TO_PTR(this_obj);
if (idx >= p->u.typed_array.len)
goto invalid_array_subscript;
idx += p->u.typed_array.offset;
pbuffer = JS_VALUE_TO_PTR(p->u.typed_array.buffer);
arr = JS_VALUE_TO_PTR(pbuffer->u.array_buffer.byte_buffer);
switch(p->class_id) {
default:
case JS_CLASS_UINT8C_ARRAY:
case JS_CLASS_INT8_ARRAY:
case JS_CLASS_UINT8_ARRAY:
*((uint8_t *)arr->buf + idx) = v;
break;
case JS_CLASS_INT16_ARRAY:
case JS_CLASS_UINT16_ARRAY:
*((uint16_t *)arr->buf + idx) = v;
break;
case JS_CLASS_INT32_ARRAY:
case JS_CLASS_UINT32_ARRAY:
*((uint32_t *)arr->buf + idx) = v;
break;
case JS_CLASS_FLOAT32_ARRAY:
*((float *)arr->buf + idx) = d;
break;
case JS_CLASS_FLOAT64_ARRAY:
*((double *)arr->buf + idx) = d;
break;
}
return JS_UNDEFINED;
} else if (JS_IsNumericProperty(ctx, prop)) {
invalid_array_subscript:
return JS_ThrowTypeError(ctx, "invalid array subscript");
}
}
redo:
pr = find_own_property(ctx, p, prop);
if (pr) {
if (likely(pr->prop_type == JS_PROP_NORMAL)) {
if (unlikely(JS_IS_ROM_PTR(ctx, pr)))
goto convert_to_ram;
pr->value = val;
return JS_UNDEFINED;
} else if (pr->prop_type == JS_PROP_VARREF) {
JSVarRef *pv = JS_VALUE_TO_PTR(pr->value);
pv->u.value = val;
return JS_UNDEFINED;
} else if (pr->prop_type == JS_PROP_SPECIAL) {
JSGCRef val_ref, prop_ref, this_obj_ref;
int err;
convert_to_ram:
JS_PUSH_VALUE(ctx, this_obj);
JS_PUSH_VALUE(ctx, prop);
JS_PUSH_VALUE(ctx, val);
err = js_update_props(ctx, this_obj);
JS_POP_VALUE(ctx, val);
JS_POP_VALUE(ctx, prop);
JS_POP_VALUE(ctx, this_obj);
if (err)
return JS_EXCEPTION;
p = JS_VALUE_TO_PTR(this_obj);
goto redo;
} else {
goto getset;
}
}
for(;;) {
proto = p->proto;
if (proto == JS_NULL)
break;
p = JS_VALUE_TO_PTR(proto);
prototype_lookup:
pr = find_own_property(ctx, p, prop);
if (pr) {
if (unlikely(pr->prop_type == JS_PROP_GETSET)) {
JSValueArray *arr;
JSValue setter;
getset:
arr = JS_VALUE_TO_PTR(pr->value);
setter = arr->arr[1];
if (allow_tail_call) {
ctx->sp[-2] = ctx->sp[0];
ctx->sp[-1] = setter;
ctx->sp[0] = val;
ctx->sp -= 2;
return JS_NewTailCall(1 | FRAME_CF_POP_RET);
} else {
JSGCRef val_ref, setter_ref, this_obj_ref;
int err;
JS_PUSH_VALUE(ctx, val);
JS_PUSH_VALUE(ctx, setter);
JS_PUSH_VALUE(ctx, this_obj);
err = JS_StackCheck(ctx, 3);
JS_POP_VALUE(ctx, this_obj);
JS_POP_VALUE(ctx, setter);
JS_POP_VALUE(ctx, val);
if (err)
return JS_EXCEPTION;
JS_PushArg(ctx, val);
JS_PushArg(ctx, setter);
JS_PushArg(ctx, this_obj);
return JS_Call(ctx, 1);
}
} else {
break;
}
}
}
if (!is_obj)
return JS_ThrowTypeErrorNotAnObject(ctx);
return JS_DefinePropertyInternal(ctx, this_obj, prop, val, JS_UNDEFINED,
JS_DEF_PROP_HAS_VALUE);
}
JSValue JS_SetPropertyStr(JSContext *ctx, JSValue this_obj,
const char *str, JSValue val)
{
JSValue prop;
JSGCRef this_obj_ref, val_ref;
JS_PUSH_VALUE(ctx, this_obj);
JS_PUSH_VALUE(ctx, val);
prop = JS_NewString(ctx, str);
if (!JS_IsException(prop)) {
prop = JS_ToPropertyKey(ctx, prop);
}
JS_POP_VALUE(ctx, val);
JS_POP_VALUE(ctx, this_obj);
if (JS_IsException(prop))
return prop;
return JS_SetPropertyInternal(ctx, this_obj, prop, val, FALSE);
}
JSValue JS_SetPropertyUint32(JSContext *ctx, JSValue this_obj,
uint32_t idx, JSValue val)
{
if (idx > JS_SHORTINT_MAX)
return JS_ThrowRangeError(ctx, "invalid array index");
return JS_SetPropertyInternal(ctx, this_obj, JS_NewShortInt(idx), val, FALSE);
}
static JSValue JS_DeleteProperty(JSContext *ctx, JSValue this_obj,
JSValue prop)
{
JSObject *p;
JSProperty *pr, *pr1;
JSValueArray *arr;
int h, idx, hash_mask, last_idx, prop_count, first_free;
JSGCRef this_obj_ref;
JS_PUSH_VALUE(ctx, this_obj);
prop = JS_ToPropertyKey(ctx, prop);
JS_POP_VALUE(ctx, this_obj);
if (JS_IsException(prop))
return prop;
if (!JS_IsPtr(this_obj))
return JS_TRUE;
p = JS_VALUE_TO_PTR(this_obj);
if (p->mtag != JS_MTAG_OBJECT)
return JS_TRUE;
arr = JS_VALUE_TO_PTR(p->props);
hash_mask = JS_VALUE_GET_INT(arr->arr[1]);
h = hash_prop(prop) & hash_mask;
idx = JS_VALUE_GET_INT(arr->arr[2 + h]);
last_idx = -1;
while (idx != 0) {
pr = (JSProperty *)(arr->arr + idx);
if (pr->key == prop) {
if (JS_IS_ROM_PTR(ctx, arr)) {
JSGCRef this_obj_ref;
int ret;
JS_PUSH_VALUE(ctx, this_obj);
ret = js_update_props(ctx, this_obj);
JS_POP_VALUE(ctx, this_obj);
if (ret)
return JS_EXCEPTION;
p = JS_VALUE_TO_PTR(this_obj);
arr = JS_VALUE_TO_PTR(p->props);
pr = (JSProperty *)(arr->arr + idx);
}
if (last_idx >= 0) {
JSProperty *lpr = (JSProperty *)(arr->arr + last_idx);
lpr->hash_next = pr->hash_next;
} else {
arr->arr[2 + h] = pr->hash_next;
}
first_free = get_first_free(arr);
prop_count = JS_VALUE_GET_INT(arr->arr[0]);
arr->arr[0] = JS_NewShortInt(prop_count - 1);
pr->prop_type = JS_PROP_NORMAL;
pr->key = JS_UNINITIALIZED;
pr->value = JS_UNDEFINED;
while (first_free > 2 + hash_mask + 1) {
pr1 = (JSProperty *)&arr->arr[first_free - 3];
if (pr1->key != JS_UNINITIALIZED)
break;
first_free -= 3;
}
if (first_free < arr->size) {
pr1 = (JSProperty *)&arr->arr[arr->size - 3];
pr1->hash_next = first_free << 1;
}
if ((2 + hash_mask + 1 + 3 * prop_count) < arr->size / 2)
js_compact_props(ctx, p);
return JS_TRUE;
}
last_idx = idx;
idx = pr->hash_next >> 1;
}
return JS_TRUE;
}
static JSValue stdlib_init_class(JSContext *ctx, const JSROMClass *class_def)
{
JSValue obj, proto, parent_class, parent_proto;
JSGCRef parent_class_ref;
JSObject *p;
int ctor_idx = class_def->ctor_idx;
if (ctor_idx >= 0) {
int class_id = ctx->c_function_table[ctor_idx].magic;
obj = ctx->class_obj[class_id];
if (!JS_IsNull(obj))
return obj;
if (!JS_IsNull(class_def->parent_class)) {
JSROMClass *parent_class_def = JS_VALUE_TO_PTR(class_def->parent_class);
int parent_class_id;
parent_class = stdlib_init_class(ctx, parent_class_def);
parent_class_id = ctx->c_function_table[parent_class_def->ctor_idx].magic;
parent_proto = ctx->class_proto[parent_class_id];
} else {
parent_class = JS_NULL;
parent_proto = ctx->class_proto[JS_CLASS_OBJECT];
}
proto = ctx->class_proto[class_id];
if (JS_IsNull(proto)) {
JS_PUSH_VALUE(ctx, parent_class);
proto = JS_NewObjectProtoClass(ctx, parent_proto, JS_CLASS_OBJECT, 0);
JS_POP_VALUE(ctx, parent_class);
ctx->class_proto[class_id] = proto;
}
p = JS_VALUE_TO_PTR(proto);
if (!JS_IsNull(class_def->proto_props))
p->props = class_def->proto_props;
if (JS_IsNull(parent_class))
parent_class = ctx->class_proto[JS_CLASS_CLOSURE];
obj = js_new_c_function_proto(ctx, ctor_idx, parent_class, FALSE, JS_NULL);
ctx->class_obj[class_id] = obj;
} else {
obj = JS_NewObject(ctx);
}
p = JS_VALUE_TO_PTR(obj);
if (!JS_IsNull(class_def->props)) {
p->props = class_def->props;
}
return obj;
}
static void stdlib_init(JSContext *ctx, const JSValueArray *arr)
{
JSValue name, val;
int i;
for(i = 0; i < arr->size; i += 2) {
name = arr->arr[i];
val = arr->arr[i + 1];
if (JS_IsObject(ctx, val)) {
val = stdlib_init_class(ctx, JS_VALUE_TO_PTR(val));
} else if (val == JS_NULL) {
val = ctx->global_obj;
}
JS_DefinePropertyInternal(ctx, ctx->global_obj, name,
val, JS_NULL,
JS_DEF_PROP_HAS_VALUE);
}
}
static void dummy_write_func(void *opaque, const void *buf, size_t buf_len)
{
}
JSContext *JS_NewContext2(void *mem_start, size_t mem_size, const JSSTDLibraryDef *stdlib_def, BOOL prepare_compilation)
{
JSContext *ctx;
JSValueArray *arr;
int i, mem_align;
#ifdef JS_PTR64
mem_align = 8;
#else
mem_align = 4;
#endif
mem_size = mem_size & ~(mem_align - 1);
assert(mem_size >= 1024);
assert(((uintptr_t)mem_start & (mem_align - 1)) == 0);
ctx = mem_start;
memset(ctx, 0, sizeof(*ctx));
ctx->class_count = stdlib_def->class_count;
ctx->class_obj = ctx->class_proto + ctx->class_count;
ctx->heap_base = (void *)(ctx->class_proto + 2 * ctx->class_count);
ctx->heap_free = ctx->heap_base;
ctx->stack_top = mem_start + mem_size;
ctx->sp = (JSValue *)ctx->stack_top;
ctx->stack_bottom = ctx->sp;
ctx->fp = ctx->sp;
ctx->min_free_size = JS_MIN_FREE_SIZE;
#ifdef DEBUG_GC
ctx->dummy_block = JS_NULL;
ctx->unique_strings = JS_NULL;
#endif
ctx->random_state = 1;
ctx->write_func = dummy_write_func;
for(i = 0; i < JS_STRING_POS_CACHE_SIZE; i++)
ctx->string_pos_cache[i].str = JS_NULL;
if (prepare_compilation) {
int atom_table_len;
JSValueArray *arr, *arr1;
uint8_t *ptr;
ctx->atom_table = (JSWord *)ctx->heap_free;
atom_table_len = stdlib_def->sorted_atoms_offset;
memcpy(ctx->heap_free, stdlib_def->stdlib_table,
atom_table_len * sizeof(JSWord));
ctx->heap_free += atom_table_len * sizeof(JSWord);
arr1 = (JSValueArray *)(stdlib_def->stdlib_table + atom_table_len);
arr = js_alloc_value_array(ctx, 0, arr1->size);
ctx->unique_strings = JS_VALUE_FROM_PTR(arr);
for(i = 0; i < arr1->size; i++) {
ptr = JS_VALUE_TO_PTR(arr1->arr[i]);
ptr = ptr - (uint8_t *)stdlib_def->stdlib_table +
(uint8_t *)ctx->atom_table;
arr->arr[i] = JS_VALUE_FROM_PTR(ptr);
}
ctx->unique_strings_len = arr1->size;
} else {
ctx->atom_table = stdlib_def->stdlib_table;
ctx->rom_atom_tables[0] = (JSValueArray *)(stdlib_def->stdlib_table +
stdlib_def->sorted_atoms_offset);
ctx->n_rom_atom_tables = 1;
ctx->c_function_table = stdlib_def->c_function_table;
ctx->c_finalizer_table = stdlib_def->c_finalizer_table;
ctx->unique_strings = JS_NULL;
ctx->unique_strings_len = 0;
}
ctx->current_exception = JS_UNDEFINED;
#ifdef DEBUG_GC
{
JSByteArray *barr;
barr = js_alloc_byte_array(ctx, (min_int(mem_size / 2, 1 << 17)) & ~(JSW - 1));
ctx->dummy_block = JS_VALUE_FROM_PTR(barr);
}
#endif
arr = js_alloc_value_array(ctx, 0, 3);
arr->arr[0] = JS_NewShortInt(0);
arr->arr[1] = JS_NewShortInt(0);
arr->arr[2] = JS_NewShortInt(0);
ctx->empty_props = JS_VALUE_FROM_PTR(arr);
for(i = 0; i < ctx->class_count; i++)
ctx->class_proto[i] = JS_NULL;
for(i = 0; i < ctx->class_count; i++)
ctx->class_obj[i] = JS_NULL;
ctx->class_proto[JS_CLASS_OBJECT] = JS_NewObject(ctx);
ctx->class_proto[JS_CLASS_CLOSURE] = JS_NewObject(ctx);
ctx->global_obj = JS_NewObject(ctx);
ctx->minus_zero = js_alloc_float64(ctx, -0.0);
if (!prepare_compilation) {
stdlib_init(ctx, (JSValueArray *)(stdlib_def->stdlib_table + stdlib_def->global_object_offset));
}
return ctx;
}
JSContext *JS_NewContext(void *mem_start, size_t mem_size, const JSSTDLibraryDef *stdlib_def)
{
return JS_NewContext2(mem_start, mem_size, stdlib_def, FALSE);
}
void JS_FreeContext(JSContext *ctx)
{
uint8_t *ptr;
int size;
JSObject *p;
ptr = ctx->heap_base;
while (ptr < ctx->heap_free) {
size = get_mblock_size(ptr);
p = (JSObject *)ptr;
if (p->mtag == JS_MTAG_OBJECT && p->class_id >= JS_CLASS_USER &&
ctx->c_finalizer_table[p->class_id - JS_CLASS_USER] != NULL) {
ctx->c_finalizer_table[p->class_id - JS_CLASS_USER](ctx, p->u.user.opaque);
}
ptr += size;
}
}
void JS_SetContextOpaque(JSContext *ctx, void *opaque)
{
ctx->opaque = opaque;
}
void JS_SetInterruptHandler(JSContext *ctx, JSInterruptHandler *interrupt_handler)
{
ctx->interrupt_handler = interrupt_handler;
}
void JS_SetLogFunc(JSContext *ctx, JSWriteFunc *write_func)
{
ctx->write_func = write_func;
}
void JS_SetRandomSeed(JSContext *ctx, uint64_t seed)
{
ctx->random_state = seed;
}
JSValue JS_GetGlobalObject(JSContext *ctx)
{
return ctx->global_obj;
}
static JSValue get_var_ref(JSContext *ctx, JSValue *pfirst_var_ref, JSValue *pval)
{
JSValue val;
JSVarRef *p;
val = *pfirst_var_ref;
for(;;) {
if (val == JS_NULL)
break;
p = JS_VALUE_TO_PTR(val);
assert(!p->is_detached);
if (p->u.pvalue == pval)
return val;
val = p->u.next;
}
p = js_malloc(ctx, sizeof(JSVarRef), JS_MTAG_VARREF);
if (!p)
return JS_EXCEPTION;
p->is_detached = FALSE;
p->u.pvalue = pval;
p->u.next = *pfirst_var_ref;
val = JS_VALUE_FROM_PTR(p);
*pfirst_var_ref = val;
return val;
}
#define FRAME_OFFSET_ARG0 4
#define FRAME_OFFSET_FUNC_OBJ 3
#define FRAME_OFFSET_THIS_OBJ 2
#define FRAME_OFFSET_CALL_FLAGS 1
#define FRAME_OFFSET_SAVED_FP 0
#define FRAME_OFFSET_CUR_PC (-1)
#define FRAME_OFFSET_FIRST_VARREF (-2)
#define FRAME_OFFSET_VAR0 (-3)
#define SP_TO_VALUE(ctx, fp) JS_NewShortInt((uint8_t *)(fp) - (uint8_t *)ctx)
#define VALUE_TO_SP(ctx, val) (void *)((uint8_t *)ctx + JS_VALUE_GET_INT(val))
static __js_printf_like(3, 4) void cprintf(char **pp, char *buf_end, const char *fmt, ...)
{
char *p;
va_list ap;
p = *pp;
if ((p + 1) >= buf_end)
return;
va_start(ap, fmt);
js_vsnprintf(p, buf_end - p, fmt, ap);
va_end(ap);
p += strlen(p);
*pp = p;
}
static JSValue reloc_c_func_name(JSContext *ctx, JSValue val)
{
return val;
}
static JSValue js_function_get_length_name1(JSContext *ctx, JSValue *this_val,
int is_name, JSFunctionBytecode **pb)
{
int short_func_idx;
const JSCFunctionDef *fd;
JSValue ret;
if (!JS_IsPtr(*this_val)) {
if (JS_VALUE_GET_SPECIAL_TAG(*this_val) != JS_TAG_SHORT_FUNC)
goto fail;
short_func_idx = JS_VALUE_GET_SPECIAL_VALUE(*this_val);
goto short_func;
} else {
JSObject *p = JS_VALUE_TO_PTR(*this_val);
JSFunctionBytecode *b;
if (p->mtag != JS_MTAG_OBJECT)
goto fail;
if (p->class_id == JS_CLASS_CLOSURE) {
b = JS_VALUE_TO_PTR(p->u.closure.func_bytecode);
if (is_name) {
if (b->func_name == JS_NULL)
ret = js_get_atom(ctx, JS_ATOM_empty);
else
ret = b->func_name;
} else {
ret = JS_NewShortInt(b->arg_count);
}
*pb = b;
return ret;
} else if (p->class_id == JS_CLASS_C_FUNCTION) {
short_func_idx = p->u.cfunc.idx;
short_func:
fd = &ctx->c_function_table[short_func_idx];
if (is_name) {
ret = reloc_c_func_name(ctx, fd->name);
} else {
ret = JS_NewShortInt(fd->arg_count);
}
*pb = NULL;
return ret;
} else {
fail:
*pb = NULL;
return JS_NULL;
}
}
}
static uint32_t get_bit(const uint8_t *buf, uint32_t index)
{
return (buf[index >> 3] >> (7 - (index & 7))) & 1;
}
static uint32_t get_bits_slow(const uint8_t *buf, uint32_t index, int n)
{
int i;
uint32_t val;
val = 0;
for(i = 0; i < n; i++)
val |= get_bit(buf, index + i) << (n - 1 - i);
return val;
}
static uint32_t get_bits(const uint8_t *buf, uint32_t buf_len,
uint32_t index, int n)
{
uint32_t val, pos;
pos = index >> 3;
if (unlikely(n > 25 || (pos + 3) >= buf_len)) {
return get_bits_slow(buf, index, n);
} else {
val = get_be32(buf + pos);
return (val >> (32 - (index & 7) - n)) & ((1 << n) - 1);
}
}
static uint32_t get_ugolomb(const uint8_t *buf, uint32_t buf_len,
uint32_t *pindex)
{
uint32_t index = *pindex;
int i;
uint32_t v;
i = 0;
for(;;) {
if (get_bit(buf, index++))
break;
i++;
if (i == 32) {
*pindex = index;
return 0xffffffff;
}
}
if (i == 0) {
v = 0;
} else {
v = ((1 << i) | get_bits(buf, buf_len, index, i)) - 1;
index += i;
}
*pindex = index;
return v;
}
static int32_t get_sgolomb(const uint8_t *buf, uint32_t buf_len,
uint32_t *pindex)
{
uint32_t val;
val = get_ugolomb(buf, buf_len, pindex);
return (val >> 1) ^ -(val & 1);
}
static int get_pc2line_hoisted_code_len(const uint8_t *buf, size_t buf_len)
{
size_t i = buf_len;
int v = 0;
while (i > 0) {
i--;
v = (v << 7) | (buf[i] & 0x7f);
if ((buf[i] & 0x80) == 0)
break;
}
return v;
}
static void get_pc2line(int *pline_num, int *pcol_num, const uint8_t *buf,
uint32_t buf_len, uint32_t *pindex, BOOL has_column)
{
int line_delta, line_num, col_num, col_delta;
line_num = *pline_num;
col_num = *pcol_num;
line_delta = get_sgolomb(buf, buf_len, pindex);
line_num += line_delta;
if (has_column) {
if (line_delta == 0) {
col_delta = get_sgolomb(buf, buf_len, pindex);
col_num += col_delta;
} else {
col_num = get_ugolomb(buf, buf_len, pindex) + 1;
}
} else {
col_num = 0;
}
*pline_num = line_num;
*pcol_num = col_num;
}
static int find_line_col(int *pcol_num, JSFunctionBytecode *b, uint32_t pc)
{
JSByteArray *arr, *pc2line;
int pos, op, line_num, col_num;
uint32_t pc2line_pos;
if (b->pc2line == JS_NULL)
goto fail;
arr = JS_VALUE_TO_PTR(b->byte_code);
pc2line = JS_VALUE_TO_PTR(b->pc2line);
pos = get_pc2line_hoisted_code_len(pc2line->buf, pc2line->size);
if (pc < pos)
pc = pos;
pc2line_pos = 0;
line_num = 1;
col_num = 1;
while (pos < arr->size) {
get_pc2line(&line_num, &col_num, pc2line->buf, pc2line->size,
&pc2line_pos, b->has_column);
if (pos == pc) {
*pcol_num = col_num;
return line_num;
}
op = arr->buf[pos];
pos += opcode_info[op].size;
}
fail:
*pcol_num = 0;
return 0;
}
static const char *get_func_name(JSContext *ctx, JSValue func_obj,
JSCStringBuf *str_buf, JSFunctionBytecode **pb)
{
JSValue val;
val = js_function_get_length_name1(ctx, &func_obj, 1, pb);
if (JS_IsNull(val))
return NULL;
return JS_ToCString(ctx, val, str_buf);
}
static void build_backtrace(JSContext *ctx, JSValue error_obj,
const char *filename, int line_num, int col_num, int skip_level)
{
JSObject *p1;
char buf[128], *p, *buf_end, *line_start;
const char *str;
JSValue *fp, stack_str;
JSCStringBuf str_buf;
JSFunctionBytecode *b;
int level;
JSGCRef error_obj_ref;
if (!JS_IsError(ctx, error_obj))
return;
p = buf;
buf_end = buf + sizeof(buf);
p[0] = '\0';
if (filename) {
cprintf(&p, buf_end, " at %s:%d:%d\n", filename, line_num, col_num);
}
fp = ctx->fp;
level = 0;
while (fp != (JSValue *)ctx->stack_top && level < 10) {
if (skip_level != 0) {
skip_level--;
} else {
line_start = p;
str = get_func_name(ctx, fp[FRAME_OFFSET_FUNC_OBJ], &str_buf, &b);
if (!str)
str = "<anonymous>";
cprintf(&p, buf_end, " at %s", str);
if (b) {
int pc, line_num, col_num;
const char *filename;
filename = JS_ToCString(ctx, b->filename, &str_buf);
pc = JS_VALUE_GET_INT(fp[FRAME_OFFSET_CUR_PC]) - 1;
line_num = find_line_col(&col_num, b, pc);
cprintf(&p, buf_end, " (%s", filename);
if (line_num != 0) {
cprintf(&p, buf_end, ":%d", line_num);
if (col_num != 0)
cprintf(&p, buf_end, ":%d", col_num);
}
cprintf(&p, buf_end, ")");
} else {
cprintf(&p, buf_end, " (native)");
}
cprintf(&p, buf_end, "\n");
if ((p + 1) >= buf_end) {
*line_start = '\0';
break;
}
level++;
}
fp = VALUE_TO_SP(ctx, fp[FRAME_OFFSET_SAVED_FP]);
}
JS_PUSH_VALUE(ctx, error_obj);
stack_str = JS_NewString(ctx, buf);
JS_POP_VALUE(ctx, error_obj);
p1 = JS_VALUE_TO_PTR(error_obj);
p1->u.error.stack = stack_str;
}
#define HINT_STRING 0
#define HINT_NUMBER 1
#define HINT_NONE HINT_NUMBER
static JSValue JS_ToPrimitive(JSContext *ctx, JSValue val, int hint)
{
int i, atom;
JSValue method, ret;
JSGCRef val_ref, method_ref;
if (JS_IsPrimitive(ctx, val))
return val;
for(i = 0; i < 2; i++) {
if ((i ^ hint) == 0) {
atom = JS_ATOM_toString;
} else {
atom = JS_ATOM_valueOf;
}
JS_PUSH_VALUE(ctx, val);
method = JS_GetProperty(ctx, val, js_get_atom(ctx, atom));
JS_POP_VALUE(ctx, val);
if (JS_IsException(method))
return method;
if (JS_IsFunction(ctx, method)) {
int err;
JS_PUSH_VALUE(ctx, method);
JS_PUSH_VALUE(ctx, val);
err = JS_StackCheck(ctx, 2);
JS_POP_VALUE(ctx, val);
JS_POP_VALUE(ctx, method);
if (err)
return JS_EXCEPTION;
JS_PushArg(ctx, method);
JS_PushArg(ctx, val);
JS_PUSH_VALUE(ctx, val);
ret = JS_Call(ctx, 0);
JS_POP_VALUE(ctx, val);
if (JS_IsException(ret))
return ret;
if (!JS_IsObject(ctx, ret))
return ret;
}
}
return JS_ThrowTypeError(ctx, "toPrimitive");
}
static JSValue js_dtoa2(JSContext *ctx, double d, int radix, int n_digits, int flags)
{
int len_max, len;
JSValue str;
JSGCRef str_ref;
JSByteArray *tmp_arr, *p;
len_max = js_dtoa_max_len(d, radix, n_digits, flags);
p = js_alloc_byte_array(ctx, len_max + 1);
if (!p)
return JS_EXCEPTION;
str = JS_VALUE_FROM_PTR(p);
JS_PUSH_VALUE(ctx, str);
tmp_arr = js_alloc_byte_array(ctx, sizeof(JSDTOATempMem));
JS_POP_VALUE(ctx, str);
if (!tmp_arr)
return JS_EXCEPTION;
p = JS_VALUE_TO_PTR(str);
len = js_dtoa((char *)p->buf, d, radix, n_digits, flags, (JSDTOATempMem *)tmp_arr->buf);
js_free(ctx, tmp_arr);
return js_byte_array_to_string(ctx, str, len, TRUE);
}
JSValue JS_ToString(JSContext *ctx, JSValue val)
{
char buf[128];
int atom;
const char *str;
redo:
if (JS_IsInt(val)) {
int len;
len = i32toa(buf, JS_VALUE_GET_INT(val));
buf[len] = '\0';
goto ret_buf;
} else
#ifdef JS_USE_SHORT_FLOAT
if (JS_IsShortFloat(val)) {
return js_dtoa2(ctx, js_get_short_float(val), 10, 0, JS_DTOA_FORMAT_FREE);
} else
#endif
if (JS_IsPtr(val)) {
void *ptr = JS_VALUE_TO_PTR(val);
int mtag = js_get_mtag(ptr);
switch(mtag) {
case JS_MTAG_OBJECT:
to_primitive:
val = JS_ToPrimitive(ctx, val, HINT_STRING);
if (JS_IsException(val))
return val;
goto redo;
case JS_MTAG_STRING:
return val;
case JS_MTAG_FLOAT64:
{
JSFloat64 *p = ptr;
return js_dtoa2(ctx, p->u.dval, 10, 0, JS_DTOA_FORMAT_FREE);
}
default:
js_snprintf(buf, sizeof(buf), "[mtag %d]", mtag);
goto ret_buf;
}
} else {
switch(JS_VALUE_GET_SPECIAL_TAG(val)) {
case JS_TAG_NULL:
atom = JS_ATOM_null;
goto ret_atom;
case JS_TAG_UNDEFINED:
atom = JS_ATOM_undefined;
goto ret_atom;
case JS_TAG_BOOL:
if (JS_VALUE_GET_SPECIAL_VALUE(val))
atom = JS_ATOM_true;
else
atom = JS_ATOM_false;
ret_atom:
return js_get_atom(ctx, atom);
case JS_TAG_STRING_CHAR:
return val;
case JS_TAG_SHORT_FUNC:
goto to_primitive;
default:
str = "?";
goto ret_str;
ret_buf:
str = buf;
ret_str:
return JS_NewString(ctx, str);
}
}
}
static JSValue JS_ToPropertyKey(JSContext *ctx, JSValue val)
{
int32_t n;
if (JS_IsInt(val))
return val;
val = JS_ToString(ctx, val);
if (JS_IsException(val))
return val;
if (is_num_string(ctx, &n, val))
return JS_NewShortInt(n);
else
return JS_MakeUniqueString(ctx, val);
}
static int skip_spaces(const char *p1)
{
const char *p = p1;
int c;
for(;;) {
c = *p;
if (!((c >= 0x09 && c <= 0x0d) || (c == 0x20)))
break;
p++;
}
return p - p1;
}
#define JS_ATOD_TOSTRING (1 << 8)
static int js_atod1(JSContext *ctx, double *pres, JSValue val,
int radix, int flags)
{
JSString *p;
JSByteArray *tmp_arr;
double d;
JSGCRef val_ref;
const char *p1;
if (JS_VALUE_GET_SPECIAL_TAG(val) == JS_TAG_STRING_CHAR) {
int c = JS_VALUE_GET_SPECIAL_VALUE(val);
if (c >= '0' && c <= '9') {
*pres = c - '0';
} else {
*pres = NAN;
}
return 0;
}
JS_PUSH_VALUE(ctx, val);
tmp_arr = js_alloc_byte_array(ctx, sizeof(JSATODTempMem));
JS_POP_VALUE(ctx, val);
if (!tmp_arr) {
*pres = NAN;
return -1;
}
p = JS_VALUE_TO_PTR(val);
p1 = (char *)p->buf;
p1 += skip_spaces(p1);
if ((p1 - (char *)p->buf) == p->len) {
if (flags & JS_ATOD_TOSTRING)
d = 0;
else
d = NAN;
goto done;
}
d = js_atod(p1, &p1, radix, flags, (JSATODTempMem *)tmp_arr->buf);
js_free(ctx, tmp_arr);
if (flags & JS_ATOD_TOSTRING) {
p1 += skip_spaces(p1);
if ((p1 - (char *)p->buf) < p->len)
d = NAN;
}
done:
*pres = d;
return 0;
}
int JS_ToNumber(JSContext *ctx, double *pres, JSValue val)
{
redo:
if (JS_IsInt(val)) {
*pres = (double)JS_VALUE_GET_INT(val);
return 0;
} else
#ifdef JS_USE_SHORT_FLOAT
if (JS_IsShortFloat(val)) {
*pres = js_get_short_float(val);
return 0;
} else
#endif
if (JS_IsPtr(val)) {
void *ptr = JS_VALUE_TO_PTR(val);
switch(js_get_mtag(ptr)) {
case JS_MTAG_STRING:
goto atod;
case JS_MTAG_FLOAT64:
{
JSFloat64 *p = ptr;
*pres = p->u.dval;
return 0;
}
case JS_MTAG_OBJECT:
val = JS_ToPrimitive(ctx, val, HINT_NUMBER);
if (JS_IsException(val)) {
*pres = NAN;
return -1;
}
goto redo;
default:
*pres = NAN;
return 0;
}
} else {
switch(JS_VALUE_GET_SPECIAL_TAG(val)) {
case JS_TAG_NULL:
case JS_TAG_BOOL:
*pres = (double)JS_VALUE_GET_SPECIAL_VALUE(val);
return 0;
case JS_TAG_UNDEFINED:
*pres = NAN;
return 0;
case JS_TAG_STRING_CHAR:
atod:
return js_atod1(ctx, pres, val, 0,
JS_ATOD_ACCEPT_BIN_OCT | JS_ATOD_TOSTRING);
default:
*pres = NAN;
return 0;
}
}
}
static int JS_ToInt32Internal(JSContext *ctx, int *pres, JSValue val, BOOL sat_flag)
{
int32_t ret;
double d;
if (JS_IsInt(val)) {
ret = JS_VALUE_GET_INT(val);
} else
#ifdef JS_USE_SHORT_FLOAT
if (JS_IsShortFloat(val)) {
d = js_get_short_float(val);
goto handle_float64;
} else
#endif
if (JS_IsPtr(val)) {
uint64_t u;
int e;
handle_number:
if (JS_ToNumber(ctx, &d, val)) {
*pres = 0;
return -1;
}
#ifdef JS_USE_SHORT_FLOAT
handle_float64:
#endif
u = float64_as_uint64(d);
e = (u >> 52) & 0x7ff;
if (likely(e <= (1023 + 30))) {
ret = (int32_t)d;
} else if (!sat_flag) {
if (e <= (1023 + 30 + 53)) {
uint64_t v;
v = (u & (((uint64_t)1 << 52) - 1)) | ((uint64_t)1 << 52);
v = v << ((e - 1023) - 52 + 32);
ret = v >> 32;
if (u >> 63)
ret = -ret;
} else {
ret = 0;
}
} else {
if (e == 2047 && (u & (((uint64_t)1 << 52) - 1)) != 0) {
ret = 0;
} else {
if (u >> 63)
ret = 0x80000000;
else
ret = 0x7fffffff;
}
}
} else {
switch(JS_VALUE_GET_SPECIAL_TAG(val)) {
case JS_TAG_BOOL:
case JS_TAG_NULL:
case JS_TAG_UNDEFINED:
ret = JS_VALUE_GET_SPECIAL_VALUE(val);
break;
default:
goto handle_number;
}
}
*pres = ret;
return 0;
}
int JS_ToInt32(JSContext *ctx, int *pres, JSValue val)
{
return JS_ToInt32Internal(ctx, pres, val, FALSE);
}
int JS_ToUint32(JSContext *ctx, uint32_t *pres, JSValue val)
{
return JS_ToInt32Internal(ctx, (int *)pres, val, FALSE);
}
int JS_ToInt32Sat(JSContext *ctx, int *pres, JSValue val)
{
return JS_ToInt32Internal(ctx, pres, val, TRUE);
}
static int JS_ToInt32Clamp(JSContext *ctx, int *pres, JSValue val,
int min, int max, int min_offset)
{
int res = JS_ToInt32Sat(ctx, pres, val);
if (res == 0) {
if (*pres < min) {
*pres += min_offset;
if (*pres < min)
*pres = min;
} else {
if (*pres > max)
*pres = max;
}
}
return res;
}
static int JS_ToUint8Clamp(JSContext *ctx, int *pres, JSValue val)
{
int32_t ret;
double d;
if (JS_IsInt(val)) {
ret = JS_VALUE_GET_INT(val);
if (ret < 0)
ret = 0;
else if (ret > 255)
ret = 255;
} else
#ifdef JS_USE_SHORT_FLOAT
if (JS_IsShortFloat(val)) {
d = js_get_short_float(val);
goto handle_float64;
} else
#endif
if (JS_IsPtr(val)) {
handle_number:
if (JS_ToNumber(ctx, &d, val)) {
*pres = 0;
return -1;
}
#ifdef JS_USE_SHORT_FLOAT
handle_float64:
#endif
if (d < 0 || isnan(d))
ret = 0;
else if (d > 255)
ret = 255;
else
ret = js_lrint(d);
} else {
switch(JS_VALUE_GET_SPECIAL_TAG(val)) {
case JS_TAG_BOOL:
case JS_TAG_NULL:
case JS_TAG_UNDEFINED:
ret = JS_VALUE_GET_SPECIAL_VALUE(val);
break;
default:
goto handle_number;
}
}
*pres = ret;
return 0;
}
static int js_get_length32(JSContext *ctx, uint32_t *pres, JSValue obj)
{
JSValue len_val;
len_val = JS_GetProperty(ctx, obj, js_get_atom(ctx, JS_ATOM_length));
if (JS_IsException(len_val)) {
*pres = 0;
return -1;
}
return JS_ToUint32(ctx, pres, len_val);
}
static no_inline JSValue js_add_slow(JSContext *ctx)
{
JSValue *op1, *op2;
op1 = &ctx->sp[1];
op2 = &ctx->sp[0];
*op1 = JS_ToPrimitive(ctx, *op1, HINT_NONE);
if (JS_IsException(*op1))
return JS_EXCEPTION;
*op2 = JS_ToPrimitive(ctx, *op2, HINT_NONE);
if (JS_IsException(*op2))
return JS_EXCEPTION;
if (JS_IsString(ctx, *op1) || JS_IsString(ctx, *op2)) {
*op1 = JS_ToString(ctx, *op1);
if (JS_IsException(*op1))
return JS_EXCEPTION;
*op2 = JS_ToString(ctx, *op2);
if (JS_IsException(*op2))
return JS_EXCEPTION;
return JS_ConcatString(ctx, *op1, *op2);
} else {
double d1, d2, r;
if (JS_ToNumber(ctx, &d1, *op1))
return JS_EXCEPTION;
if (JS_ToNumber(ctx, &d2, *op2))
return JS_EXCEPTION;
r = d1 + d2;
return JS_NewFloat64(ctx, r);
}
}
static no_inline JSValue js_binary_arith_slow(JSContext *ctx, OPCodeEnum op)
{
double d1, d2, r;
if (JS_ToNumber(ctx, &d1, ctx->sp[1]))
return JS_EXCEPTION;
if (JS_ToNumber(ctx, &d2, ctx->sp[0]))
return JS_EXCEPTION;
switch(op) {
case OP_sub:
r = d1 - d2;
break;
case OP_mul:
r = d1 * d2;
break;
case OP_div:
r = d1 / d2;
break;
case OP_mod:
r = js_fmod(d1, d2);
break;
case OP_pow:
r = js_pow(d1, d2);
break;
default:
abort();
}
return JS_NewFloat64(ctx, r);
}
static no_inline JSValue js_unary_arith_slow(JSContext *ctx, OPCodeEnum op)
{
double d;
if (JS_ToNumber(ctx, &d, ctx->sp[0]))
return JS_EXCEPTION;
switch(op) {
case OP_inc:
d++;
break;
case OP_dec:
d--;
break;
case OP_plus:
break;
case OP_neg:
d = -d;
break;
default:
abort();
}
return JS_NewFloat64(ctx, d);
}
static no_inline JSValue js_post_inc_slow(JSContext *ctx, OPCodeEnum op)
{
JSValue val;
double d, r;
if (JS_ToNumber(ctx, &d, ctx->sp[0]))
return JS_EXCEPTION;
r = d + 2 * (op - OP_post_dec) - 1;
val = JS_NewFloat64(ctx, d);
if (JS_IsException(val))
return val;
ctx->sp[0] = val;
return JS_NewFloat64(ctx, r);
}
static no_inline JSValue js_binary_logic_slow(JSContext *ctx, OPCodeEnum op)
{
uint32_t v1, v2, r;
if (JS_ToUint32(ctx, &v1, ctx->sp[1]))
return JS_EXCEPTION;
if (JS_ToUint32(ctx, &v2, ctx->sp[0]))
return JS_EXCEPTION;
switch(op) {
case OP_shl:
r = v1 << (v2 & 0x1f);
break;
case OP_sar:
r = (int)v1 >> (v2 & 0x1f);
break;
case OP_shr:
r = v1 >> (v2 & 0x1f);
return JS_NewUint32(ctx, r);
case OP_and:
r = v1 & v2;
break;
case OP_or:
r = v1 | v2;
break;
case OP_xor:
r = v1 ^ v2;
break;
default:
abort();
}
return JS_NewInt32(ctx, r);
}
static no_inline JSValue js_not_slow(JSContext *ctx)
{
uint32_t r;
if (JS_ToUint32(ctx, &r, ctx->sp[0]))
return JS_EXCEPTION;
return JS_NewInt32(ctx, ~r);
}
static no_inline JSValue js_relational_slow(JSContext *ctx, OPCodeEnum op)
{
JSValue *op1, *op2;
int res;
double d1, d2;
op1 = &ctx->sp[1];
op2 = &ctx->sp[0];
*op1 = JS_ToPrimitive(ctx, *op1, HINT_NUMBER);
if (JS_IsException(*op1))
return JS_EXCEPTION;
*op2 = JS_ToPrimitive(ctx, *op2, HINT_NUMBER);
if (JS_IsException(*op2))
return JS_EXCEPTION;
if (JS_IsString(ctx, *op1) && JS_IsString(ctx, *op2)) {
res = js_string_compare(ctx, *op1, *op2);
switch(op) {
case OP_lt:
res = (res < 0);
break;
case OP_lte:
res = (res <= 0);
break;
case OP_gt:
res = (res > 0);
break;
default:
case OP_gte:
res = (res >= 0);
break;
}
} else {
if (JS_ToNumber(ctx, &d1, *op1))
return JS_EXCEPTION;
if (JS_ToNumber(ctx, &d2, *op2))
return JS_EXCEPTION;
switch(op) {
case OP_lt:
res = (d1 < d2);
break;
case OP_lte:
res = (d1 <= d2);
break;
case OP_gt:
res = (d1 > d2);
break;
default:
case OP_gte:
res = (d1 >= d2);
break;
}
}
return JS_NewBool(res);
}
static BOOL js_strict_eq(JSContext *ctx, JSValue op1, JSValue op2)
{
BOOL res;
if (JS_IsNumber(ctx, op1)) {
if (!JS_IsNumber(ctx, op2)) {
res = FALSE;
} else {
double d1, d2;
JS_ToNumber(ctx, &d1, op1);
JS_ToNumber(ctx, &d2, op2);
res = (d1 == d2);
}
} else if (JS_IsString(ctx, op1)) {
if (!JS_IsString(ctx, op2)) {
res = FALSE;
} else {
res = js_string_eq(ctx, op1, op2);
}
} else {
res = (op1 == op2);
}
return res;
}
static JSValue js_strict_eq_slow(JSContext *ctx, BOOL is_neq)
{
BOOL res;
res = js_strict_eq(ctx, ctx->sp[1], ctx->sp[0]);
return JS_NewBool(res ^ is_neq);
}
enum {
JS_ETAG_NUMBER = JS_TAG_SPECIAL | (8 << 2),
JS_ETAG_STRING = JS_TAG_SPECIAL | (9 << 2),
JS_ETAG_OBJECT = JS_TAG_SPECIAL | (10 << 2),
};
static int js_eq_get_type(JSContext *ctx, JSValue val)
{
if (JS_IsIntOrShortFloat(val)) {
return JS_ETAG_NUMBER;
} else if (JS_IsPtr(val)) {
void *ptr = JS_VALUE_TO_PTR(val);
switch(js_get_mtag(ptr)) {
case JS_MTAG_FLOAT64:
return JS_ETAG_NUMBER;
case JS_MTAG_STRING:
return JS_ETAG_STRING;
default:
case JS_MTAG_OBJECT:
return JS_ETAG_OBJECT;
}
} else {
int tag = JS_VALUE_GET_SPECIAL_TAG(val);
switch(tag) {
case JS_TAG_STRING_CHAR:
return JS_ETAG_STRING;
case JS_TAG_SHORT_FUNC:
return JS_ETAG_OBJECT;
default:
return tag;
}
}
}
static no_inline JSValue js_eq_slow(JSContext *ctx, BOOL is_neq)
{
JSValue op1, op2;
int tag1, tag2;
BOOL res;
redo:
op1 = ctx->sp[1];
op2 = ctx->sp[0];
tag1 = js_eq_get_type(ctx, op1);
tag2 = js_eq_get_type(ctx, op2);
if (tag1 == tag2) {
res = js_strict_eq(ctx, op1, op2);
} else if ((tag1 == JS_TAG_NULL && tag2 == JS_TAG_UNDEFINED) ||
(tag2 == JS_TAG_NULL && tag1 == JS_TAG_UNDEFINED)) {
res = TRUE;
} else if ((tag1 == JS_ETAG_STRING && tag2 == JS_ETAG_NUMBER) ||
(tag2 == JS_ETAG_STRING && tag1 == JS_ETAG_NUMBER)) {
double d1;
double d2;
if (JS_ToNumber(ctx, &d1, ctx->sp[1]))
return JS_EXCEPTION;
if (JS_ToNumber(ctx, &d2, ctx->sp[0]))
return JS_EXCEPTION;
res = (d1 == d2);
} else if (tag1 == JS_TAG_BOOL) {
ctx->sp[1] = JS_NewShortInt(JS_VALUE_GET_SPECIAL_VALUE(op1));
goto redo;
} else if (tag2 == JS_TAG_BOOL) {
ctx->sp[0] = JS_NewShortInt(JS_VALUE_GET_SPECIAL_VALUE(op2));
goto redo;
} else if (tag1 == JS_ETAG_OBJECT &&
(tag2 == JS_ETAG_NUMBER || tag2 == JS_ETAG_STRING)) {
ctx->sp[1] = JS_ToPrimitive(ctx, op1, HINT_NONE);
if (JS_IsException(ctx->sp[1]))
return JS_EXCEPTION;
goto redo;
} else if (tag2 == JS_ETAG_OBJECT &&
(tag1 == JS_ETAG_NUMBER || tag1 == JS_ETAG_STRING)) {
ctx->sp[0] = JS_ToPrimitive(ctx, op2, HINT_NONE);
if (JS_IsException(ctx->sp[0]))
return JS_EXCEPTION;
goto redo;
} else {
res = FALSE;
}
return JS_NewBool(res ^ is_neq);
}
static JSValue js_operator_in(JSContext *ctx)
{
JSValue prop;
int res;
if (js_eq_get_type(ctx, ctx->sp[0]) != JS_ETAG_OBJECT)
return JS_ThrowTypeError(ctx, "invalid 'in' operand");
prop = JS_ToPropertyKey(ctx, ctx->sp[1]);
if (JS_IsException(prop))
return prop;
res = JS_HasProperty(ctx, ctx->sp[0], prop);
return JS_NewBool(res);
}
static JSValue js_operator_instanceof(JSContext *ctx)
{
JSValue op1, op2, proto;
JSObject *p;
op1 = ctx->sp[1];
op2 = ctx->sp[0];
if (!JS_IsFunctionObject(ctx, op2))
return JS_ThrowTypeError(ctx, "invalid 'instanceof' right operand");
proto = JS_GetProperty(ctx, op2, js_get_atom(ctx, JS_ATOM_prototype));
if (JS_IsException(proto))
return proto;
if (!JS_IsObject(ctx, op1))
return JS_NewBool(FALSE);
p = JS_VALUE_TO_PTR(op1);
for(;;) {
if (p->proto == JS_NULL)
return JS_NewBool(FALSE);
if (p->proto == proto)
return JS_NewBool(TRUE);
p = JS_VALUE_TO_PTR(p->proto);
}
return JS_NewBool(FALSE);
}
static JSValue js_operator_typeof(JSContext *ctx, JSValue val)
{
int tag, atom;
tag = js_eq_get_type(ctx, val);
switch(tag) {
case JS_ETAG_NUMBER:
atom = JS_ATOM_number;
break;
case JS_ETAG_STRING:
atom = JS_ATOM_string;
break;
case JS_TAG_BOOL:
atom = JS_ATOM_boolean;
break;
case JS_ETAG_OBJECT:
if (JS_IsFunction(ctx, val))
atom = JS_ATOM_function;
else
atom = JS_ATOM_object;
break;
case JS_TAG_NULL:
atom = JS_ATOM_object;
break;
default:
case JS_TAG_UNDEFINED:
atom = JS_ATOM_undefined;
break;
}
return js_get_atom(ctx, atom);
}
static void js_reverse_val(JSValue *tab, int n)
{
int i;
JSValue tmp;
for(i = 0; i < n / 2; i++) {
tmp = tab[i];
tab[i] = tab[n - 1 - i];
tab[n - 1 - i] = tmp;
}
}
static JSValue js_closure(JSContext *ctx, JSValue bfunc, JSValue *fp)
{
JSFunctionBytecode *b;
JSObject *p;
JSGCRef bfunc_ref, closure_ref;
JSValueArray *ext_vars;
JSValue closure;
int ext_vars_len;
b = JS_VALUE_TO_PTR(bfunc);
if (b->ext_vars != JS_NULL) {
ext_vars = JS_VALUE_TO_PTR(b->ext_vars);
ext_vars_len = ext_vars->size / 2;
} else {
ext_vars_len = 0;
}
JS_PUSH_VALUE(ctx, bfunc);
closure = JS_NewObjectProtoClass(ctx, ctx->class_proto[JS_CLASS_CLOSURE], JS_CLASS_CLOSURE,
sizeof(JSClosureData) + ext_vars_len * sizeof(JSValue));
JS_POP_VALUE(ctx, bfunc);
if (JS_IsException(closure))
return JS_EXCEPTION;
p = JS_VALUE_TO_PTR(closure);
p->u.closure.func_bytecode = bfunc;
if (ext_vars_len > 0) {
JSValue *pfirst_var_ref, val;
int i, var_idx, var_kind, decl;
memset(p->u.closure.var_refs, 0, sizeof(JSValue) * ext_vars_len);
if (fp) {
pfirst_var_ref = &fp[FRAME_OFFSET_FIRST_VARREF];
} else {
pfirst_var_ref = NULL;
}
for(i = 0; i < ext_vars_len; i++) {
b = JS_VALUE_TO_PTR(bfunc);
ext_vars = JS_VALUE_TO_PTR(b->ext_vars);
decl = JS_VALUE_GET_INT(ext_vars->arr[2 * i + 1]);
var_kind = decl >> 16;
var_idx = decl & 0xffff;
JS_PUSH_VALUE(ctx, bfunc);
JS_PUSH_VALUE(ctx, closure);
switch(var_kind) {
case JS_VARREF_KIND_ARG:
val = get_var_ref(ctx, pfirst_var_ref,
&fp[FRAME_OFFSET_ARG0 + var_idx]);
break;
case JS_VARREF_KIND_VAR:
val = get_var_ref(ctx, pfirst_var_ref,
&fp[FRAME_OFFSET_VAR0 - var_idx]);
break;
case JS_VARREF_KIND_VAR_REF:
{
JSObject *p;
p = JS_VALUE_TO_PTR(fp[FRAME_OFFSET_FUNC_OBJ]);
val = p->u.closure.var_refs[var_idx];
}
break;
case JS_VARREF_KIND_GLOBAL:
val = add_global_var(ctx, ext_vars->arr[2 * i], (var_idx != 0));
break;
default:
abort();
}
JS_POP_VALUE(ctx, closure);
JS_POP_VALUE(ctx, bfunc);
if (JS_IsException(val))
return val;
p = JS_VALUE_TO_PTR(closure);
p->u.closure.var_refs[i] = val;
}
}
return closure;
}
static JSValue js_for_of_start(JSContext *ctx, BOOL is_for_in)
{
JSValueArray *arr;
if (is_for_in) {
ctx->sp[0] = js_object_keys(ctx, NULL, 1, &ctx->sp[0]);
if (JS_IsException(ctx->sp[0]))
return JS_EXCEPTION;
}
if (!js_get_object_class(ctx, ctx->sp[0], JS_CLASS_ARRAY))
return JS_ThrowTypeError(ctx, "unsupported type in for...of");
arr = js_alloc_value_array(ctx, 0, 2);
if (!arr)
return JS_EXCEPTION;
arr->arr[0] = ctx->sp[0];
arr->arr[1] = JS_NewShortInt(0);
return JS_VALUE_FROM_PTR(arr);
}
static JSValue js_for_of_next(JSContext *ctx)
{
JSValueArray *arr, *arr1;
JSObject *p;
int pos;
arr = JS_VALUE_TO_PTR(ctx->sp[0]);
pos = JS_VALUE_GET_INT(arr->arr[1]);
p = JS_VALUE_TO_PTR(arr->arr[0]);
if (pos >= p->u.array.len) {
ctx->sp[-2] = JS_TRUE;
ctx->sp[-1] = JS_UNDEFINED;
} else {
ctx->sp[-2] = JS_FALSE;
arr1 = JS_VALUE_TO_PTR(p->u.array.tab);
ctx->sp[-1] = arr1->arr[pos];
arr->arr[1] = JS_NewShortInt(pos + 1);
}
return JS_UNDEFINED;
}
static JSValue js_new_c_function_proto(JSContext *ctx, int func_idx, JSValue proto, BOOL has_params,
JSValue params)
{
JSObject *p;
JSGCRef params_ref;
JS_PUSH_VALUE(ctx, params);
p = JS_NewObjectProtoClass1(ctx, proto, JS_CLASS_C_FUNCTION,
sizeof(JSCFunctionData) - (!has_params ? sizeof(JSValue) : 0));
JS_POP_VALUE(ctx, params);
if (!p)
return JS_EXCEPTION;
p->u.cfunc.idx = func_idx;
if (has_params)
p->u.cfunc.params = params;
return JS_VALUE_FROM_PTR(p);
}
JSValue JS_NewCFunctionParams(JSContext *ctx, int func_idx, JSValue params)
{
return js_new_c_function_proto(ctx, func_idx, ctx->class_proto[JS_CLASS_CLOSURE], TRUE, params);
}
static JSValue js_call_constructor_start(JSContext *ctx, JSValue func)
{
JSValue proto;
proto = JS_GetProperty(ctx, func, js_get_atom(ctx, JS_ATOM_prototype));
if (JS_IsException(proto))
return proto;
if (!JS_IsObject(ctx, proto))
proto = ctx->class_proto[JS_CLASS_OBJECT];
return JS_NewObjectProtoClass(ctx, proto, JS_CLASS_OBJECT, 0);
}
#define SAVE() do { \
fp[FRAME_OFFSET_CUR_PC] = JS_NewShortInt(pc - ((JSByteArray *)JS_VALUE_TO_PTR(b->byte_code))->buf); \
ctx->sp = sp; \
ctx->fp = fp; \
} while (0)
#define RESTORE() do { \
b = JS_VALUE_TO_PTR(((JSObject *)JS_VALUE_TO_PTR(fp[FRAME_OFFSET_FUNC_OBJ]))->u.closure.func_bytecode); \
pc = ((JSByteArray *)JS_VALUE_TO_PTR(b->byte_code))->buf + JS_VALUE_GET_INT(fp[FRAME_OFFSET_CUR_PC]); \
} while (0)
static JSValue __js_poll_interrupt(JSContext *ctx)
{
ctx->interrupt_counter = JS_INTERRUPT_COUNTER_INIT;
if (ctx->interrupt_handler && ctx->interrupt_handler(ctx, ctx->opaque)) {
JS_ThrowInternalError(ctx, "interrupted");
ctx->current_exception_is_uncatchable = TRUE;
return JS_EXCEPTION;
}
return JS_UNDEFINED;
}
#define POLL_INTERRUPT() do { \
if (unlikely(--ctx->interrupt_counter <= 0)) { \
SAVE(); \
val = __js_poll_interrupt(ctx); \
RESTORE(); \
if (JS_IsException(val)) \
goto exception; \
} \
} while(0)
void JS_PushArg(JSContext *ctx, JSValue val)
{
#ifdef DEBUG_GC
assert((ctx->sp - 1) >= ctx->stack_bottom);
#endif
*--ctx->sp = val;
}
JSValue JS_Call(JSContext *ctx, int call_flags)
{
JSValue *fp, *sp, val = JS_UNDEFINED, *initial_fp;
uint8_t *pc;
int opcode = OP_invalid, i;
JSFunctionBytecode *b;
#ifdef JS_USE_SHORT_FLOAT
double dr;
#endif
if (ctx->js_call_rec_count >= JS_MAX_CALL_RECURSE)
return JS_ThrowInternalError(ctx, "C stack overflow");
ctx->js_call_rec_count++;
sp = ctx->sp;
fp = ctx->fp;
initial_fp = fp;
b = NULL;
pc = NULL;
goto function_call;
#define CASE(op) case op
#define DEFAULT default
#define BREAK break
for(;;) {
opcode = *pc++;
#ifdef DUMP_EXEC
{
JSByteArray *arr;
arr = JS_VALUE_TO_PTR(b->byte_code);
js_printf(ctx, " sp=%d\n", (int)(sp - fp));
js_printf(ctx, "%4d: %s\n", (int)(pc - arr->buf - 1),
opcode_info[opcode].name);
}
#endif
switch(opcode) {
CASE(OP_push_minus1):
CASE(OP_push_0):
CASE(OP_push_1):
CASE(OP_push_2):
CASE(OP_push_3):
CASE(OP_push_4):
CASE(OP_push_5):
CASE(OP_push_6):
CASE(OP_push_7):
*--sp = JS_NewShortInt(opcode - OP_push_0);
BREAK;
CASE(OP_push_i8):
*--sp = JS_NewShortInt(get_i8(pc));
pc += 1;
BREAK;
CASE(OP_push_i16):
*--sp = JS_NewShortInt(get_i16(pc));
pc += 2;
BREAK;
CASE(OP_push_value):
*--sp = get_u32(pc);
pc += 4;
BREAK;
CASE(OP_push_const):
{
JSValueArray *cpool = JS_VALUE_TO_PTR(b->cpool);
*--sp = cpool->arr[get_u16(pc)];
pc += 2;
}
BREAK;
CASE(OP_undefined):
*--sp = JS_UNDEFINED;
BREAK;
CASE(OP_null):
*--sp = JS_NULL;
BREAK;
CASE(OP_push_this):
*--sp = fp[FRAME_OFFSET_THIS_OBJ];
BREAK;
CASE(OP_push_false):
*--sp = JS_FALSE;
BREAK;
CASE(OP_push_true):
*--sp = JS_TRUE;
BREAK;
CASE(OP_object):
{
int n = get_u16(pc);
SAVE();
val = JS_NewObjectPrealloc(ctx, n);
RESTORE();
if (JS_IsException(val))
goto exception;
*--sp = val;
pc += 2;
}
BREAK;
CASE(OP_regexp):
{
JSObject *p;
SAVE();
val = JS_NewObjectClass(ctx, JS_CLASS_REGEXP, sizeof(JSRegExp));
RESTORE();
if (JS_IsException(val))
goto exception;
p = JS_VALUE_TO_PTR(val);
p->u.regexp.source = sp[1];
p->u.regexp.byte_code = sp[0];
p->u.regexp.last_index = 0;
sp[1] = val;
sp++;
}
BREAK;
CASE(OP_array_from):
{
JSObject *p;
JSValueArray *arr;
int i, argc;
argc = get_u16(pc);
SAVE();
val = JS_NewArray(ctx, argc);
RESTORE();
if (JS_IsException(val))
goto exception;
pc += 2;
p = JS_VALUE_TO_PTR(val);
arr = JS_VALUE_TO_PTR(p->u.array.tab);
for(i = 0; i < argc; i++) {
arr->arr[i] = sp[argc - 1 - i];
}
sp += argc;
*--sp = val;
}
BREAK;
CASE(OP_this_func):
*--sp = fp[FRAME_OFFSET_FUNC_OBJ];
BREAK;
CASE(OP_arguments):
{
JSObject *p;
JSValueArray *arr;
int i, argc;
argc = JS_VALUE_GET_INT(fp[FRAME_OFFSET_CALL_FLAGS]) & FRAME_CF_ARGC_MASK;
SAVE();
val = JS_NewArray(ctx, argc);
RESTORE();
if (JS_IsException(val))
goto exception;
p = JS_VALUE_TO_PTR(val);
arr = JS_VALUE_TO_PTR(p->u.array.tab);
for(i = 0; i < argc; i++) {
arr->arr[i] = fp[FRAME_OFFSET_ARG0 + i];
}
*--sp = val;
}
BREAK;
CASE(OP_new_target):
call_flags = JS_VALUE_GET_INT(fp[FRAME_OFFSET_CALL_FLAGS]);
if (call_flags & FRAME_CF_CTOR) {
*--sp = fp[FRAME_OFFSET_FUNC_OBJ];
} else {
*--sp = JS_UNDEFINED;
}
BREAK;
CASE(OP_drop):
sp++;
BREAK;
CASE(OP_nip):
sp[1] = sp[0];
sp++;
BREAK;
CASE(OP_dup):
sp--;
sp[0] = sp[1];
BREAK;
CASE(OP_dup2):
sp -= 2;
sp[0] = sp[2];
sp[1] = sp[3];
BREAK;
CASE(OP_insert2):
sp[-1] = sp[0];
sp[0] = sp[1];
sp[1] = sp[-1];
sp--;
BREAK;
CASE(OP_insert3):
sp[-1] = sp[0];
sp[0] = sp[1];
sp[1] = sp[2];
sp[2] = sp[-1];
sp--;
BREAK;
CASE(OP_perm3):
{
JSValue tmp;
tmp = sp[1];
sp[1] = sp[2];
sp[2] = tmp;
}
BREAK;
CASE(OP_rot3l):
{
JSValue tmp;
tmp = sp[2];
sp[2] = sp[1];
sp[1] = sp[0];
sp[0] = tmp;
}
BREAK;
CASE(OP_perm4):
{
JSValue tmp;
tmp = sp[1];
sp[1] = sp[2];
sp[2] = sp[3];
sp[3] = tmp;
}
BREAK;
CASE(OP_swap):
{
JSValue tmp;
tmp = sp[1];
sp[1] = sp[0];
sp[0] = tmp;
}
BREAK;
CASE(OP_fclosure):
{
int idx;
JSValueArray *cpool = JS_VALUE_TO_PTR(b->cpool);
idx = get_u16(pc);
SAVE();
val = js_closure(ctx, cpool->arr[idx], fp);
RESTORE();
if (unlikely(JS_IsException(val)))
goto exception;
pc += 2;
*--sp = val;
}
BREAK;
CASE(OP_call_constructor):
call_flags = get_u16(pc) | FRAME_CF_CTOR;
goto global_function_call;
CASE(OP_call):
call_flags = get_u16(pc);
global_function_call:
js_reverse_val(sp, (call_flags & FRAME_CF_ARGC_MASK) + 1);
*--sp = JS_UNDEFINED;
goto generic_function_call;
CASE(OP_call_method):
{
int n, argc, short_func_idx;
JSValue func_obj;
JSObject *p;
JSByteArray *byte_code;
call_flags = get_u16(pc);
n = (call_flags & FRAME_CF_ARGC_MASK) + 2;
js_reverse_val(sp, n);
generic_function_call:
POLL_INTERRUPT();
byte_code = JS_VALUE_TO_PTR(b->byte_code);
fp[FRAME_OFFSET_CUR_PC] = JS_NewShortInt(pc - byte_code->buf);
function_call:
*--sp = JS_NewShortInt(call_flags);
*--sp = SP_TO_VALUE(ctx, fp);
func_obj = sp[FRAME_OFFSET_FUNC_OBJ];
#if defined(DUMP_EXEC)
JS_DumpValue(ctx, "calling", func_obj);
#endif
if (!JS_IsPtr(func_obj)) {
if (JS_VALUE_GET_SPECIAL_TAG(func_obj) != JS_TAG_SHORT_FUNC)
goto not_a_function;
short_func_idx = JS_VALUE_GET_SPECIAL_VALUE(func_obj);
p = NULL;
goto c_function;
} else {
p = JS_VALUE_TO_PTR(func_obj);
if (p->mtag != JS_MTAG_OBJECT)
goto not_a_function;
if (p->class_id == JS_CLASS_C_FUNCTION) {
const JSCFunctionDef *fd;
int pushed_argc;
short_func_idx = p->u.cfunc.idx;
c_function:
fd = &ctx->c_function_table[short_func_idx];
call_flags = JS_VALUE_GET_INT(sp[FRAME_OFFSET_CALL_FLAGS]);
if ((call_flags & FRAME_CF_CTOR) &&
(fd->def_type != JS_CFUNC_constructor &&
fd->def_type != JS_CFUNC_constructor_magic)) {
sp += 2;
ctx->sp = sp;
ctx->fp = fp;
val = JS_ThrowTypeError(ctx, "not a constructor");
goto call_exception;
}
argc = call_flags & FRAME_CF_ARGC_MASK;
ctx->sp = sp;
ctx->fp = fp;
n = JS_StackCheck(ctx, max_int(fd->arg_count - argc, 0));
if (n) {
sp += 2;
val = JS_EXCEPTION;
goto call_exception;
}
pushed_argc = argc;
if (fd->arg_count > argc) {
n = fd->arg_count - argc;
sp -= n;
for(i = 0; i < FRAME_OFFSET_ARG0 + argc; i++)
sp[i] = sp[i + n];
for(i = 0; i < n; i++)
sp[FRAME_OFFSET_ARG0 + argc + i] = JS_UNDEFINED;
pushed_argc = fd->arg_count;
}
fp = sp;
ctx->sp = sp;
ctx->fp = fp;
switch(fd->def_type) {
case JS_CFUNC_generic:
case JS_CFUNC_constructor:
val = fd->func.generic(ctx, &fp[FRAME_OFFSET_THIS_OBJ],
call_flags & (FRAME_CF_CTOR | FRAME_CF_ARGC_MASK),
fp + FRAME_OFFSET_ARG0);
break;
case JS_CFUNC_generic_magic:
case JS_CFUNC_constructor_magic:
val = fd->func.generic_magic(ctx, &fp[FRAME_OFFSET_THIS_OBJ],
call_flags & (FRAME_CF_CTOR | FRAME_CF_ARGC_MASK),
fp + FRAME_OFFSET_ARG0, fd->magic);
break;
case JS_CFUNC_generic_params:
p = JS_VALUE_TO_PTR(fp[FRAME_OFFSET_FUNC_OBJ]);
val = fd->func.generic_params(ctx, &fp[FRAME_OFFSET_THIS_OBJ],
call_flags & (FRAME_CF_CTOR | FRAME_CF_ARGC_MASK),
fp + FRAME_OFFSET_ARG0, p->u.cfunc.params);
break;
case JS_CFUNC_f_f:
{
double d;
if (JS_ToNumber(ctx, &d, fp[FRAME_OFFSET_ARG0])) {
val = JS_EXCEPTION;
} else {
d = fd->func.f_f(d);
}
val = JS_NewFloat64(ctx, d);
}
break;
default:
assert(0);
}
if (JS_IsExceptionOrTailCall(val) &&
JS_VALUE_GET_SPECIAL_VALUE(val) >= JS_EX_CALL) {
JSValue *fp1, *sp1;
call_flags = JS_VALUE_GET_SPECIAL_VALUE(val) - JS_EX_CALL;
sp = ctx->sp;
fp1 = VALUE_TO_SP(ctx, fp[FRAME_OFFSET_SAVED_FP]);
argc = (call_flags & FRAME_CF_ARGC_MASK) + 2;
sp1 = fp + FRAME_OFFSET_ARG0 + pushed_argc - argc;
memmove(sp1, sp, sizeof(*sp) * (argc));
sp = sp1;
fp = fp1;
goto function_call;
} else {
sp = fp + FRAME_OFFSET_ARG0 + pushed_argc;
goto return_call;
}
} else if (p->class_id == JS_CLASS_CLOSURE) {
int n_vars;
call_flags = JS_VALUE_GET_INT(sp[FRAME_OFFSET_CALL_FLAGS]);
if (call_flags & FRAME_CF_CTOR) {
ctx->sp = sp;
ctx->fp = fp;
val = js_call_constructor_start(ctx, func_obj);
if (JS_IsException(val))
goto call_exception;
sp[FRAME_OFFSET_THIS_OBJ] = val;
func_obj = sp[FRAME_OFFSET_FUNC_OBJ];
p = JS_VALUE_TO_PTR(func_obj);
}
b = JS_VALUE_TO_PTR(p->u.closure.func_bytecode);
if (b->vars != JS_NULL) {
JSValueArray *vars = JS_VALUE_TO_PTR(b->vars);
n_vars = vars->size - b->arg_count;
} else {
n_vars = 0;
}
argc = call_flags & FRAME_CF_ARGC_MASK;
ctx->sp = sp;
ctx->fp = fp;
n = JS_StackCheck(ctx, max_int(b->arg_count - argc, 0) + 2 + n_vars +
b->stack_size);
if (n) {
val = JS_EXCEPTION;
goto call_exception;
}
func_obj = sp[FRAME_OFFSET_FUNC_OBJ];
p = JS_VALUE_TO_PTR(func_obj);
b = JS_VALUE_TO_PTR(p->u.closure.func_bytecode);
if (unlikely(b->arg_count > argc)) {
n = b->arg_count - argc;
sp -= n;
for(i = 0; i < FRAME_OFFSET_ARG0 + argc; i++)
sp[i] = sp[i + n];
for(i = 0; i < n; i++)
sp[FRAME_OFFSET_ARG0 + argc + i] = JS_UNDEFINED;
}
fp = sp;
*--sp = JS_NewShortInt(0);
*--sp = JS_NULL;
sp -= n_vars;
for(i = 0; i < n_vars; i++)
sp[i] = JS_UNDEFINED;
byte_code = JS_VALUE_TO_PTR(b->byte_code);
pc = byte_code->buf;
} else {
not_a_function:
sp += 2;
ctx->sp = sp;
ctx->fp = fp;
val = JS_ThrowTypeError(ctx, "not a function");
call_exception:
if (!pc) {
goto done;
} else {
RESTORE();
goto exception;
}
}
}
}
BREAK;
exception:
{
JSValue *stack_top, val2;
JSValueArray *vars;
int v;
if (!pc)
goto done;
v = JS_VALUE_GET_SPECIAL_VALUE(val);
if (v >= JS_EX_CALL) {
call_flags = JS_VALUE_GET_SPECIAL_VALUE(val) - JS_EX_CALL;
if (opcode == OP_get_length ||
opcode == OP_get_length2 ||
opcode == OP_get_array_el ||
opcode == OP_get_array_el2 ||
opcode == OP_put_array_el) {
call_flags |= FRAME_CF_PC_ADD1;
}
goto generic_function_call;
}
stack_top = fp + FRAME_OFFSET_VAR0 + 1;
if (b->vars != JS_NULL) {
vars = JS_VALUE_TO_PTR(b->vars);
stack_top -= (vars->size - b->arg_count);
}
if (ctx->current_exception_is_uncatchable) {
sp = stack_top;
} else {
while (sp < stack_top) {
val2 = *sp++;
if (JS_VALUE_GET_SPECIAL_TAG(val2) == JS_TAG_CATCH_OFFSET) {
JSByteArray *byte_code;
*--sp = ctx->current_exception;
ctx->current_exception = JS_NULL;
byte_code = JS_VALUE_TO_PTR(b->byte_code);
pc = byte_code->buf + JS_VALUE_GET_SPECIAL_VALUE(val2);
goto restart;
}
}
}
}
goto generic_return;
CASE(OP_return_undef):
val = JS_UNDEFINED;
goto generic_return;
CASE(OP_return):
val = sp[0];
generic_return:
{
JSObject *p;
int argc, pc_offset;
JSValue val2;
JSVarRef *pv;
JSByteArray *byte_code;
val2 = fp[FRAME_OFFSET_FIRST_VARREF];
while (val2 != JS_NULL) {
pv = JS_VALUE_TO_PTR(val2);
val2 = pv->u.next;
assert(!pv->is_detached);
pv->u.value = *pv->u.pvalue;
pv->is_detached = TRUE;
set_free_block((uint8_t *)pv + sizeof(JSVarRef) - sizeof(JSValue), sizeof(JSValue));
}
call_flags = JS_VALUE_GET_INT(fp[FRAME_OFFSET_CALL_FLAGS]);
if (unlikely(call_flags & FRAME_CF_CTOR)) {
if (!JS_IsException(val) && !JS_IsObject(ctx, val)) {
val = fp[FRAME_OFFSET_THIS_OBJ];
}
}
argc = call_flags & FRAME_CF_ARGC_MASK;
argc = max_int(argc, b->arg_count);
sp = fp + FRAME_OFFSET_ARG0 + argc;
return_call:
call_flags = JS_VALUE_GET_INT(fp[FRAME_OFFSET_CALL_FLAGS]);
fp = VALUE_TO_SP(ctx, fp[FRAME_OFFSET_SAVED_FP]);
if (fp == initial_fp)
goto done;
pc_offset = JS_VALUE_GET_INT(fp[FRAME_OFFSET_CUR_PC]);
p = JS_VALUE_TO_PTR(fp[FRAME_OFFSET_FUNC_OBJ]);
b = JS_VALUE_TO_PTR(p->u.closure.func_bytecode);
byte_code = JS_VALUE_TO_PTR(b->byte_code);
pc = byte_code->buf + pc_offset;
if (JS_IsException(val))
goto exception;
if (!(call_flags & FRAME_CF_POP_RET))
*--sp = val;
if (!(call_flags & FRAME_CF_PC_ADD1))
pc += 2;
}
BREAK;
CASE(OP_catch):
{
int32_t diff;
JSByteArray *byte_code = JS_VALUE_TO_PTR(b->byte_code);
diff = get_u32(pc);
*--sp = JS_VALUE_MAKE_SPECIAL(JS_TAG_CATCH_OFFSET, pc + diff - byte_code->buf);
pc += 4;
}
BREAK;
CASE(OP_throw):
val = *sp++;
SAVE();
val = JS_Throw(ctx, val);
RESTORE();
goto exception;
CASE(OP_gosub):
{
int32_t diff;
JSByteArray *byte_code = JS_VALUE_TO_PTR(b->byte_code);
diff = get_u32(pc);
*--sp = JS_NewShortInt(pc + 4 - byte_code->buf);
pc += diff;
}
BREAK;
CASE(OP_ret):
{
JSByteArray *byte_code = JS_VALUE_TO_PTR(b->byte_code);
uint32_t pos;
if (unlikely(!JS_IsInt(sp[0])))
goto ret_fail;
pos = JS_VALUE_GET_INT(sp[0]);
if (unlikely(pos >= byte_code->size)) {
ret_fail:
SAVE();
val = JS_ThrowInternalError(ctx, "invalid ret value");
RESTORE();
goto exception;
}
sp++;
pc = byte_code->buf + pos;
}
BREAK;
CASE(OP_get_loc):
{
int idx;
idx = get_u16(pc);
pc += 2;
*--sp = fp[FRAME_OFFSET_VAR0 - idx];
}
BREAK;
CASE(OP_put_loc):
{
int idx;
idx = get_u16(pc);
pc += 2;
fp[FRAME_OFFSET_VAR0 - idx] = sp[0];
sp++;
}
BREAK;
CASE(OP_get_arg):
{
int idx;
idx = get_u16(pc);
pc += 2;
*--sp = fp[FRAME_OFFSET_ARG0 + idx];
}
BREAK;
CASE(OP_put_arg):
{
int idx;
idx = get_u16(pc);
pc += 2;
fp[FRAME_OFFSET_ARG0 + idx] = sp[0];
sp++;
}
BREAK;
CASE(OP_get_loc0): *--sp = fp[FRAME_OFFSET_VAR0 - 0]; BREAK;
CASE(OP_get_loc1): *--sp = fp[FRAME_OFFSET_VAR0 - 1]; BREAK;
CASE(OP_get_loc2): *--sp = fp[FRAME_OFFSET_VAR0 - 2]; BREAK;
CASE(OP_get_loc3): *--sp = fp[FRAME_OFFSET_VAR0 - 3]; BREAK;
CASE(OP_get_loc8): *--sp = fp[FRAME_OFFSET_VAR0 - *pc++]; BREAK;
CASE(OP_put_loc0): fp[FRAME_OFFSET_VAR0 - 0] = *sp++; BREAK;
CASE(OP_put_loc1): fp[FRAME_OFFSET_VAR0 - 1] = *sp++; BREAK;
CASE(OP_put_loc2): fp[FRAME_OFFSET_VAR0 - 2] = *sp++; BREAK;
CASE(OP_put_loc3): fp[FRAME_OFFSET_VAR0 - 3] = *sp++; BREAK;
CASE(OP_put_loc8): fp[FRAME_OFFSET_VAR0 - *pc++] = *sp++; BREAK;
CASE(OP_get_arg0): *--sp = fp[FRAME_OFFSET_ARG0 + 0]; BREAK;
CASE(OP_get_arg1): *--sp = fp[FRAME_OFFSET_ARG0 + 1]; BREAK;
CASE(OP_get_arg2): *--sp = fp[FRAME_OFFSET_ARG0 + 2]; BREAK;
CASE(OP_get_arg3): *--sp = fp[FRAME_OFFSET_ARG0 + 3]; BREAK;
CASE(OP_put_arg0): fp[FRAME_OFFSET_ARG0 + 0] = *sp++; BREAK;
CASE(OP_put_arg1): fp[FRAME_OFFSET_ARG0 + 1] = *sp++; BREAK;
CASE(OP_put_arg2): fp[FRAME_OFFSET_ARG0 + 2] = *sp++; BREAK;
CASE(OP_put_arg3): fp[FRAME_OFFSET_ARG0 + 3] = *sp++; BREAK;
CASE(OP_get_var_ref):
CASE(OP_get_var_ref_nocheck):
{
int idx;
JSObject *p;
JSVarRef *pv;
idx = get_u16(pc);
p = JS_VALUE_TO_PTR(fp[FRAME_OFFSET_FUNC_OBJ]);
pv = JS_VALUE_TO_PTR(p->u.closure.var_refs[idx]);
if (pv->is_detached)
val = pv->u.value;
else
val = *pv->u.pvalue;
if (unlikely(val == JS_TAG_UNINITIALIZED) &&
opcode == OP_get_var_ref) {
JSValueArray *ext_vars = JS_VALUE_TO_PTR(b->ext_vars);
SAVE();
val = JS_ThrowReferenceError(ctx, "variable '%"JSValue_PRI"' is not defined", ext_vars->arr[2 * idx]);
RESTORE();
goto exception;
}
pc += 2;
*--sp = val;
}
BREAK;
CASE(OP_put_var_ref):
CASE(OP_put_var_ref_nocheck):
{
int idx;
JSObject *p;
JSVarRef *pv;
JSValue *pval;
idx = get_u16(pc);
p = JS_VALUE_TO_PTR(fp[FRAME_OFFSET_FUNC_OBJ]);
pv = JS_VALUE_TO_PTR(p->u.closure.var_refs[idx]);
if (pv->is_detached)
pval = &pv->u.value;
else
pval = pv->u.pvalue;
if (unlikely(*pval == JS_TAG_UNINITIALIZED) &&
opcode == OP_put_var_ref) {
JSValueArray *ext_vars = JS_VALUE_TO_PTR(b->ext_vars);
SAVE();
val = JS_ThrowReferenceError(ctx, "variable '%"JSValue_PRI"' is not defined", ext_vars->arr[2 * idx]);
RESTORE();
goto exception;
}
*pval = *sp++;
pc += 2;
}
BREAK;
CASE(OP_goto):
pc += (int32_t)get_u32(pc);
POLL_INTERRUPT();
BREAK;
CASE(OP_if_false):
CASE(OP_if_true):
{
int res;
pc += 4;
res = JS_ToBool(ctx, *sp++);
if (res ^ (OP_if_true - opcode)) {
pc += (int32_t)get_u32(pc - 4) - 4;
}
POLL_INTERRUPT();
}
BREAK;
CASE(OP_lnot):
{
int res;
res = JS_ToBool(ctx, sp[0]);
sp[0] = JS_NewBool(!res);
}
BREAK;
CASE(OP_get_field2):
sp--;
sp[0] = sp[1];
goto get_field_common;
CASE(OP_get_field):
get_field_common:
{
int idx;
JSValue prop, obj;
JSValueArray *cpool = JS_VALUE_TO_PTR(b->cpool);
idx = get_u16(pc);
prop = cpool->arr[idx];
obj = sp[0];
if (likely(JS_IsPtr(obj))) {
JSObject *p = JS_VALUE_TO_PTR(obj);
JSProperty *pr;
if (unlikely(p->mtag != JS_MTAG_OBJECT))
goto get_field_slow;
for(;;) {
pr = find_own_property_inlined(ctx, p, prop);
if (pr) {
if (unlikely(pr->prop_type != JS_PROP_NORMAL)) {
goto get_field_slow;
} else {
val = pr->value;
break;
}
}
obj = p->proto;
if (obj == JS_NULL) {
val = JS_UNDEFINED;
break;
}
p = JS_VALUE_TO_PTR(obj);
}
} else {
get_field_slow:
SAVE();
val = JS_GetPropertyInternal(ctx, obj, prop, TRUE);
RESTORE();
if (unlikely(JS_IsExceptionOrTailCall(val))) {
sp = ctx->sp;
goto exception;
}
}
pc += 2;
sp[0] = val;
}
BREAK;
CASE(OP_get_length2):
sp--;
sp[0] = sp[1];
goto get_length_common;
CASE(OP_get_length):
get_length_common:
{
JSValue obj;
obj = sp[0];
if (likely(JS_IsPtr(obj))) {
JSObject *p = JS_VALUE_TO_PTR(obj);
if (p->mtag == JS_MTAG_OBJECT) {
if (p->class_id == JS_CLASS_ARRAY) {
if (unlikely(p->proto != ctx->class_proto[JS_CLASS_ARRAY] ||
p->props != ctx->empty_props))
goto get_length_slow;
val = JS_NewShortInt(p->u.array.len);
} else {
goto get_length_slow;
}
} else if (p->mtag == JS_MTAG_STRING) {
JSString *ps = (JSString *)p;
if (likely(ps->is_ascii))
val = JS_NewShortInt(ps->len);
else
val = JS_NewShortInt(js_string_utf8_to_utf16_pos(ctx, obj, ps->len * 2));
} else {
goto get_length_slow;
}
} else if (JS_VALUE_GET_SPECIAL_TAG(val) == JS_TAG_STRING_CHAR) {
val = JS_NewShortInt(JS_VALUE_GET_SPECIAL_VALUE(val) >= 0x10000 ? 2 : 1);
} else {
get_length_slow:
SAVE();
val = JS_GetPropertyInternal(ctx, obj, js_get_atom(ctx, JS_ATOM_length), TRUE);
RESTORE();
if (unlikely(JS_IsExceptionOrTailCall(val))) {
sp = ctx->sp;
goto exception;
}
}
sp[0] = val;
}
BREAK;
CASE(OP_put_field):
{
int idx;
JSValue prop, obj;
JSValueArray *cpool = JS_VALUE_TO_PTR(b->cpool);
idx = get_u16(pc);
prop = cpool->arr[idx];
obj = sp[1];
if (likely(JS_IsPtr(obj))) {
JSObject *p = JS_VALUE_TO_PTR(obj);
JSProperty *pr;
if (unlikely(p->mtag != JS_MTAG_OBJECT))
goto put_field_slow;
pr = find_own_property_inlined(ctx, p, prop);
if (unlikely(!pr))
goto put_field_slow;
if (unlikely(pr->prop_type != JS_PROP_NORMAL))
goto put_field_slow;
if (unlikely(JS_IS_ROM_PTR(ctx, pr)))
goto put_field_slow;
pr->value = sp[0];
sp += 2;
} else {
put_field_slow:
val = *sp++;
SAVE();
val = JS_SetPropertyInternal(ctx, sp[0], prop, val, TRUE);
RESTORE();
if (unlikely(JS_IsExceptionOrTailCall(val))) {
sp = ctx->sp;
goto exception;
}
sp++;
}
pc += 2;
}
BREAK;
CASE(OP_get_array_el2):
val = sp[0];
sp[0] = sp[1];
goto get_array_el_common;
CASE(OP_get_array_el):
val = sp[0];
sp++;
get_array_el_common:
{
JSValue prop = val, obj;
obj = sp[0];
if (JS_IsPtr(obj) && JS_IsInt(prop)) {
JSObject *p = JS_VALUE_TO_PTR(obj);
uint32_t idx;
JSValueArray *arr;
if (unlikely(p->mtag != JS_MTAG_OBJECT))
goto get_array_el_slow;
if (unlikely(p->class_id != JS_CLASS_ARRAY))
goto get_array_el_slow;
idx = JS_VALUE_GET_INT(prop);
if (unlikely(idx >= p->u.array.len))
goto get_array_el_slow;
arr = JS_VALUE_TO_PTR(p->u.array.tab);
val = arr->arr[idx];
} else {
get_array_el_slow:
SAVE();
prop = JS_ToPropertyKey(ctx, prop);
RESTORE();
if (JS_IsException(prop)) {
val = prop;
goto exception;
}
SAVE();
val = JS_GetPropertyInternal(ctx, sp[0], prop, TRUE);
RESTORE();
if (unlikely(JS_IsExceptionOrTailCall(val))) {
sp = ctx->sp;
goto exception;
}
}
sp[0] = val;
}
BREAK;
CASE(OP_put_array_el):
{
JSValue prop, obj;
obj = sp[2];
prop = sp[1];
if (JS_IsPtr(obj) && JS_IsInt(prop)) {
JSObject *p = JS_VALUE_TO_PTR(obj);
uint32_t idx;
JSValueArray *arr;
if (unlikely(p->mtag != JS_MTAG_OBJECT))
goto put_array_el_slow;
if (unlikely(p->class_id != JS_CLASS_ARRAY))
goto put_array_el_slow;
idx = JS_VALUE_GET_INT(prop);
arr = JS_VALUE_TO_PTR(p->u.array.tab);
if (unlikely(idx >= p->u.array.len)) {
if (idx == p->u.array.len &&
p->u.array.tab != JS_NULL &&
idx < arr->size) {
arr->arr[idx] = sp[0];
p->u.array.len = idx + 1;
} else {
goto put_array_el_slow;
}
} else {
arr->arr[idx] = sp[0];
}
sp += 3;
} else {
put_array_el_slow:
SAVE();
sp[1] = JS_ToPropertyKey(ctx, sp[1]);
RESTORE();
if (JS_IsException(sp[1])) {
val = sp[1];
goto exception;
}
val = *sp++;
prop = *sp++;
SAVE();
val = JS_SetPropertyInternal(ctx, sp[0], prop, val, TRUE);
RESTORE();
if (unlikely(JS_IsExceptionOrTailCall(val))) {
sp = ctx->sp;
goto exception;
}
sp++;
}
}
BREAK;
CASE(OP_define_field):
CASE(OP_define_getter):
CASE(OP_define_setter):
{
int idx;
JSValue prop;
JSValueArray *cpool = JS_VALUE_TO_PTR(b->cpool);
idx = get_u16(pc);
prop = cpool->arr[idx];
SAVE();
if (opcode == OP_define_field) {
val = JS_DefinePropertyValue(ctx, sp[1], prop, sp[0]);
} else if (opcode == OP_define_getter)
val = JS_DefinePropertyGetSet(ctx, sp[1], prop, sp[0], JS_UNDEFINED, JS_DEF_PROP_HAS_GET);
else
val = JS_DefinePropertyGetSet(ctx, sp[1], prop, JS_UNDEFINED, sp[0], JS_DEF_PROP_HAS_SET);
RESTORE();
if (unlikely(JS_IsException(val)))
goto exception;
pc += 2;
sp++;
}
BREAK;
CASE(OP_set_proto):
{
if (JS_IsObject(ctx, sp[0]) || JS_IsNull(sp[0])) {
SAVE();
val = js_set_prototype_internal(ctx, sp[1], sp[0]);
RESTORE();
if (unlikely(JS_IsException(val)))
goto exception;
}
sp++;
}
BREAK;
CASE(OP_add):
{
JSValue op1, op2;
op1 = sp[1];
op2 = sp[0];
if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) {
int r;
if (unlikely(__builtin_add_overflow((int)op1, (int)op2, &r)))
goto add_slow;
sp[1] = (uint32_t)r;
} else
#ifdef JS_USE_SHORT_FLOAT
if (JS_VALUE_IS_BOTH_SHORT_FLOAT(op1, op2)) {
double d1, d2;
d1 = js_get_short_float(op1);
d2 = js_get_short_float(op2);
dr = d1 + d2;
sp++;
goto float_result;
} else
#endif
{
add_slow:
SAVE();
val = js_add_slow(ctx);
RESTORE();
if (JS_IsException(val))
goto exception;
sp[1] = val;
}
sp++;
}
BREAK;
CASE(OP_sub):
{
JSValue op1, op2;
op1 = sp[1];
op2 = sp[0];
if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) {
int r;
if (unlikely(__builtin_sub_overflow((int)op1, (int)op2, &r)))
goto binary_arith_slow;
sp[1] = (uint32_t)r;
} else
#ifdef JS_USE_SHORT_FLOAT
if (JS_VALUE_IS_BOTH_SHORT_FLOAT(op1, op2)) {
double d1, d2;
d1 = js_get_short_float(op1);
d2 = js_get_short_float(op2);
dr = d1 - d2;
sp++;
goto float_result;
} else
#endif
{
goto binary_arith_slow;
}
sp++;
}
BREAK;
CASE(OP_mul):
{
JSValue op1, op2;
op1 = sp[1];
op2 = sp[0];
if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) {
int v1, v2;
int64_t r;
v1 = (int)op1;
v2 = (int)op2 >> 1;
r = (int64_t)v1 * (int64_t)v2;
if (unlikely(r != (int)r)) {
#if defined(JS_USE_SHORT_FLOAT)
dr = (double)(r >> 1);
sp++;
goto float_result;
#else
goto binary_arith_slow;
#endif
}
if (unlikely(r == 0 && (v1 | v2) < 0)) {
sp[1] = ctx->minus_zero;
} else {
sp[1] = (uint32_t)r;
}
} else
#ifdef JS_USE_SHORT_FLOAT
if (JS_VALUE_IS_BOTH_SHORT_FLOAT(op1, op2)) {
double d1, d2;
d1 = js_get_short_float(op1);
d2 = js_get_short_float(op2);
dr = d1 * d2;
sp++;
goto float_result;
} else
#endif
{
goto binary_arith_slow;
}
sp++;
}
BREAK;
CASE(OP_div):
{
JSValue op1, op2;
op1 = sp[1];
op2 = sp[0];
if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) {
int v1, v2;
v1 = JS_VALUE_GET_INT(op1);
v2 = JS_VALUE_GET_INT(op2);
SAVE();
val = JS_NewFloat64(ctx, (double)v1 / (double)v2);
RESTORE();
if (JS_IsException(val))
goto exception;
sp[1] = val;
sp++;
} else {
goto binary_arith_slow;
}
}
BREAK;
CASE(OP_mod):
{
JSValue op1, op2;
op1 = sp[1];
op2 = sp[0];
if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) {
int v1, v2, r;
v1 = JS_VALUE_GET_INT(op1);
v2 = JS_VALUE_GET_INT(op2);
if (unlikely(v1 < 0 || v2 <= 0))
goto binary_arith_slow;
r = v1 % v2;
sp[1] = JS_NewShortInt(r);
sp++;
} else {
goto binary_arith_slow;
}
}
BREAK;
CASE(OP_pow):
binary_arith_slow:
SAVE();
val = js_binary_arith_slow(ctx, opcode);
RESTORE();
if (JS_IsException(val))
goto exception;
sp[1] = val;
sp++;
BREAK;
CASE(OP_plus):
{
JSValue op1;
op1 = sp[0];
if (JS_IsIntOrShortFloat(op1) ||
(JS_IsPtr(op1) && js_get_mtag(JS_VALUE_TO_PTR(op1)) == JS_MTAG_FLOAT64)) {
} else {
goto unary_arith_slow;
}
}
BREAK;
CASE(OP_neg):
{
JSValue op1;
int v1;
op1 = sp[0];
if (JS_IsInt(op1)) {
v1 = op1;
if (v1 == 0) {
sp[0] = ctx->minus_zero;
} else if (v1 == INT32_MIN) {
#if defined(JS_USE_SHORT_FLOAT)
dr = -(double)JS_SHORTINT_MIN;
goto float_result;
#else
goto unary_arith_slow;
#endif
} else {
sp[0] = -v1;
}
} else
#if defined(JS_USE_SHORT_FLOAT)
if (JS_IsShortFloat(op1)) {
dr = -js_get_short_float(op1);
float_result:
if (likely(fabs(dr) >= 0x1p-127 && fabs(dr) <= 0x1p+128)) {
val = js_to_short_float(dr);
} else if (dr == 0.0) {
if (float64_as_uint64(dr) != 0) {
val = ctx->minus_zero;
} else {
val = JS_NewShortInt(0);
}
} else {
SAVE();
val = js_alloc_float64(ctx, dr);
RESTORE();
if (JS_IsException(val))
goto exception;
}
sp[0] = val;
} else
#endif
{
goto unary_arith_slow;
}
}
BREAK;
CASE(OP_inc):
{
JSValue op1;
int v1;
op1 = sp[0];
if (JS_IsInt(op1)) {
v1 = JS_VALUE_GET_INT(op1);
if (unlikely(v1 == JS_SHORTINT_MAX))
goto unary_arith_slow;
sp[0] = JS_NewShortInt(v1 + 1);
} else {
goto unary_arith_slow;
}
}
BREAK;
CASE(OP_dec):
{
JSValue op1;
int v1;
op1 = sp[0];
if (JS_IsInt(op1)) {
v1 = JS_VALUE_GET_INT(op1);
if (unlikely(v1 == JS_SHORTINT_MIN))
goto unary_arith_slow;
sp[0] = JS_NewShortInt(v1 - 1);
} else {
unary_arith_slow:
SAVE();
val = js_unary_arith_slow(ctx, opcode);
RESTORE();
if (JS_IsException(val))
goto exception;
sp[0] = val;
}
}
BREAK;
CASE(OP_post_inc):
CASE(OP_post_dec):
{
JSValue op1;
int v1;
op1 = sp[0];
if (JS_IsInt(op1)) {
v1 = JS_VALUE_GET_INT(op1) + 2 * (opcode - OP_post_dec) - 1;
if (v1 < JS_SHORTINT_MIN || v1 > JS_SHORTINT_MAX)
goto slow_post_inc_dec;
val = JS_NewShortInt(v1);
} else {
slow_post_inc_dec:
SAVE();
val = js_post_inc_slow(ctx, opcode);
RESTORE();
if (JS_IsException(val))
goto exception;
}
*--sp = val;
}
BREAK;
CASE(OP_not):
{
JSValue op1;
op1 = sp[0];
if (JS_IsInt(op1)) {
sp[0] = (~op1) & (~1);
} else {
SAVE();
val = js_not_slow(ctx);
RESTORE();
if (JS_IsException(val))
goto exception;
sp[0] = val;
}
}
BREAK;
CASE(OP_shl):
{
JSValue op1, op2;
op1 = sp[1];
op2 = sp[0];
if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) {
int32_t r;
r = JS_VALUE_GET_INT(op1) << (JS_VALUE_GET_INT(op2) & 0x1f);
if (unlikely(r < JS_SHORTINT_MIN || r > JS_SHORTINT_MAX)) {
#if defined(JS_USE_SHORT_FLOAT)
dr = (double)r;
sp++;
goto float_result;
#else
goto binary_logic_slow;
#endif
}
sp[1] = JS_NewShortInt(r);
sp++;
} else {
goto binary_logic_slow;
}
}
BREAK;
CASE(OP_shr):
{
JSValue op1, op2;
op1 = sp[1];
op2 = sp[0];
if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) {
uint32_t r;
r = (uint32_t)JS_VALUE_GET_INT(op1) >>
((uint32_t)JS_VALUE_GET_INT(op2) & 0x1f);
if (unlikely(r > JS_SHORTINT_MAX)) {
#if defined(JS_USE_SHORT_FLOAT)
dr = (double)r;
sp++;
goto float_result;
#else
goto binary_logic_slow;
#endif
}
sp[1] = JS_NewShortInt(r);
sp++;
} else {
goto binary_logic_slow;
}
}
BREAK;
CASE(OP_sar):
{
JSValue op1, op2;
op1 = sp[1];
op2 = sp[0];
if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) {
sp[1] = ((int)op1 >> ((uint32_t)JS_VALUE_GET_INT(op2) & 0x1f)) & ~1;
sp++;
} else {
goto binary_logic_slow;
}
}
BREAK;
CASE(OP_and):
{
JSValue op1, op2;
op1 = sp[1];
op2 = sp[0];
if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) {
sp[1] = op1 & op2;
sp++;
} else {
goto binary_logic_slow;
}
}
BREAK;
CASE(OP_or):
{
JSValue op1, op2;
op1 = sp[1];
op2 = sp[0];
if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) {
sp[1] = op1 | op2;
sp++;
} else {
goto binary_logic_slow;
}
}
BREAK;
CASE(OP_xor):
{
JSValue op1, op2;
op1 = sp[1];
op2 = sp[0];
if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) {
sp[1] = op1 ^ op2;
sp++;
} else {
binary_logic_slow:
SAVE();
val = js_binary_logic_slow(ctx, opcode);
RESTORE();
if (JS_IsException(val))
goto exception;
sp[1] = val;
sp++;
}
}
BREAK;
#define OP_CMP(opcode, binary_op, slow_call) \
CASE(opcode): \
{ \
JSValue op1, op2; \
op1 = sp[1]; \
op2 = sp[0]; \
if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { \
sp[1] = JS_NewBool(JS_VALUE_GET_INT(op1) binary_op JS_VALUE_GET_INT(op2)); \
sp++; \
} else { \
SAVE(); \
val = slow_call; \
RESTORE(); \
if (JS_IsException(val)) \
goto exception; \
sp[1] = val; \
sp++; \
} \
} \
BREAK;
OP_CMP(OP_lt, <, js_relational_slow(ctx, opcode));
OP_CMP(OP_lte, <=, js_relational_slow(ctx, opcode));
OP_CMP(OP_gt, >, js_relational_slow(ctx, opcode));
OP_CMP(OP_gte, >=, js_relational_slow(ctx, opcode));
OP_CMP(OP_eq, ==, js_eq_slow(ctx, 0));
OP_CMP(OP_neq, !=, js_eq_slow(ctx, 1));
OP_CMP(OP_strict_eq, ==, js_strict_eq_slow(ctx, 0));
OP_CMP(OP_strict_neq, !=, js_strict_eq_slow(ctx, 1));
CASE(OP_in):
SAVE();
val = js_operator_in(ctx);
RESTORE();
if (unlikely(JS_IsException(val)))
goto exception;
sp[1] = val;
sp++;
BREAK;
CASE(OP_instanceof):
SAVE();
val = js_operator_instanceof(ctx);
RESTORE();
if (unlikely(JS_IsException(val)))
goto exception;
sp[1] = val;
sp++;
BREAK;
CASE(OP_typeof):
SAVE();
val = js_operator_typeof(ctx, sp[0]);
RESTORE();
if (unlikely(JS_IsException(val)))
goto exception;
sp[0] = val;
BREAK;
CASE(OP_delete):
SAVE();
val = JS_DeleteProperty(ctx, sp[1], sp[0]);
RESTORE();
if (unlikely(JS_IsException(val)))
goto exception;
sp[1] = val;
sp++;
BREAK;
CASE(OP_for_in_start):
CASE(OP_for_of_start):
SAVE();
val = js_for_of_start(ctx, (opcode == OP_for_in_start));
RESTORE();
if (unlikely(JS_IsException(val)))
goto exception;
sp[0] = val;
BREAK;
CASE(OP_for_of_next):
SAVE();
val = js_for_of_next(ctx);
RESTORE();
if (unlikely(JS_IsException(val)))
goto exception;
sp -= 2;
BREAK;
default:
{
JSByteArray *byte_code = JS_VALUE_TO_PTR(b->byte_code);
SAVE();
val = JS_ThrowInternalError(ctx, "invalid opcode: pc=%u opcode=0x%02x",
(int)(pc - byte_code->buf - 1), opcode);
RESTORE();
}
goto exception;
}
restart: ;
}
done:
ctx->sp = sp;
ctx->fp = fp;
ctx->js_call_rec_count--;
return val;
}
#undef SAVE
#undef RESTORE
static inline int is_ident_first(int c)
{
return (c >= 'a' && c <= 'z') ||
(c >= 'A' && c <= 'Z') ||
c == '_' || c == '$';
}
static inline int is_ident_next(int c)
{
return is_ident_first(c) || is_num(c);
}
#ifdef JS_DUMP
static void js_dump_array(JSContext *ctx, JSValueArray *arr, int len)
{
int i;
js_printf(ctx, "[ ");
for(i = 0; i < len; i++) {
if (i != 0)
js_printf(ctx, ", ");
JS_PrintValue(ctx, arr->arr[i]);
}
js_printf(ctx, " ]");
}
static JSValue js_find_class_name(JSContext *ctx, int class_id)
{
const JSCFunctionDef *fd;
fd = ctx->c_function_table;
while ((fd->def_type != JS_CFUNC_constructor_magic &&
fd->def_type != JS_CFUNC_constructor) ||
fd->magic != class_id) {
fd++;
}
return reloc_c_func_name(ctx, fd->name);
}
static void js_dump_float64(JSContext *ctx, double d)
{
char buf[32];
JSDTOATempMem tmp_mem;
js_dtoa(buf, d, 10, 0, JS_DTOA_FORMAT_FREE | JS_DTOA_MINUS_ZERO, &tmp_mem);
js_printf(ctx, "%s", buf);
}
static void dump_regexp(JSContext *ctx, JSObject *p);
static void js_dump_error(JSContext *ctx, JSObject *p)
{
JSObject *p1;
JSProperty *pr;
JSValue name;
p1 = p;
if (p->proto != JS_NULL)
p1 = JS_VALUE_TO_PTR(p->proto);
pr = find_own_property(ctx, p1, js_get_atom(ctx, JS_ATOM_name));
if (!pr || !JS_IsString(ctx, pr->value))
name = js_get_atom(ctx, JS_ATOM_Error);
else
name = pr->value;
js_printf(ctx, "%" JSValue_PRI, name);
if (p->u.error.message != JS_NULL) {
js_printf(ctx, ": %" JSValue_PRI, p->u.error.message);
}
if (p->u.error.stack != JS_NULL) {
js_printf(ctx, "\n%#" JSValue_PRI, p->u.error.stack);
}
}
static void js_dump_object(JSContext *ctx, JSObject *p, int flags)
{
if (flags & JS_DUMP_LONG) {
switch(p->class_id) {
case JS_CLASS_CLOSURE:
{
JSFunctionBytecode *b = JS_VALUE_TO_PTR(p->u.closure.func_bytecode);
js_printf(ctx, "function ");
JS_PrintValueF(ctx, b->func_name, JS_DUMP_NOQUOTE);
js_printf(ctx, "()");
}
break;
case JS_CLASS_C_FUNCTION:
js_printf(ctx, "function ");
JS_PrintValueF(ctx, reloc_c_func_name(ctx, ctx->c_function_table[p->u.cfunc.idx].name), JS_DUMP_NOQUOTE);
js_printf(ctx, "()");
break;
case JS_CLASS_ERROR:
js_dump_error(ctx, p);
break;
case JS_CLASS_REGEXP:
dump_regexp(ctx, p);
break;
default:
case JS_CLASS_ARRAY:
case JS_CLASS_OBJECT:
if (p->class_id >= JS_CLASS_UINT8C_ARRAY &&
p->class_id <= JS_CLASS_FLOAT64_ARRAY) {
int i, idx;
uint32_t v;
double d;
JSObject *pbuffer;
JSByteArray *arr;
JS_PrintValueF(ctx, js_find_class_name(ctx, p->class_id),
JS_DUMP_NOQUOTE);
js_printf(ctx, "([ ");
pbuffer = JS_VALUE_TO_PTR(p->u.typed_array.buffer);
arr = JS_VALUE_TO_PTR(pbuffer->u.array_buffer.byte_buffer);
for(i = 0; i < p->u.typed_array.len; i++) {
if (i != 0)
js_printf(ctx, ", ");
idx = i + p->u.typed_array.offset;
switch(p->class_id) {
default:
case JS_CLASS_UINT8C_ARRAY:
case JS_CLASS_UINT8_ARRAY:
v = *((uint8_t *)arr->buf + idx);
goto ta_i32;
case JS_CLASS_INT8_ARRAY:
v = *((int8_t *)arr->buf + idx);
goto ta_i32;
case JS_CLASS_INT16_ARRAY:
v = *((int16_t *)arr->buf + idx);
goto ta_i32;
case JS_CLASS_UINT16_ARRAY:
v = *((uint16_t *)arr->buf + idx);
goto ta_i32;
case JS_CLASS_INT32_ARRAY:
v = *((int32_t *)arr->buf + idx);
ta_i32:
js_printf(ctx, "%d", v);
break;
case JS_CLASS_UINT32_ARRAY:
v = *((uint32_t *)arr->buf + idx);
js_printf(ctx, "%u", v);
break;
case JS_CLASS_FLOAT32_ARRAY:
d = *((float *)arr->buf + idx);
goto ta_d;
case JS_CLASS_FLOAT64_ARRAY:
d = *((double *)arr->buf + idx);
ta_d:
js_dump_float64(ctx, d);
break;
}
}
js_printf(ctx, " ])");
} else {
int i, j, prop_count, hash_mask;
JSProperty *pr;
JSValueArray *arr;
BOOL is_first = TRUE;
arr = JS_VALUE_TO_PTR(p->props);
prop_count = JS_VALUE_GET_INT(arr->arr[0]);
hash_mask = JS_VALUE_GET_INT(arr->arr[1]);
if (p->class_id == JS_CLASS_ARRAY) {
JSValueArray *tab = JS_VALUE_TO_PTR(p->u.array.tab);
js_printf(ctx, "[ ");
for(i = 0; i < p->u.array.len; i++) {
if (!is_first)
js_printf(ctx, ", ");
JS_PrintValue(ctx, tab->arr[i]);
is_first = FALSE;
}
} else {
if (p->class_id != JS_CLASS_OBJECT) {
JSValue class_name = js_find_class_name(ctx, p->class_id);
if (!JS_IsNull(class_name))
JS_PrintValueF(ctx, class_name, JS_DUMP_NOQUOTE);
js_putchar(ctx, ' ');
}
js_printf(ctx, "{ ");
}
for(i = 0, j = 0; j < prop_count; i++) {
pr = (JSProperty *)&arr->arr[2 + (hash_mask + 1) + 3 * i];
if (pr->key != JS_UNINITIALIZED) {
if (!is_first)
js_printf(ctx, ", ");
JS_PrintValueF(ctx, pr->key, JS_DUMP_NOQUOTE);
js_printf(ctx, ": ");
if (!(flags & JS_DUMP_RAW) && pr->prop_type == JS_PROP_SPECIAL) {
JS_PrintValue(ctx, get_special_prop(ctx, pr->value));
} else {
JS_PrintValue(ctx, pr->value);
}
is_first = FALSE;
j++;
}
}
js_printf(ctx, " %c",
p->class_id == JS_CLASS_ARRAY ? ']' : '}');
}
break;
}
} else {
const char *str;
if (p->class_id == JS_CLASS_ARRAY)
str = "Array";
else if (p->class_id == JS_CLASS_ERROR)
str = "Error";
else if (p->class_id == JS_CLASS_CLOSURE ||
p->class_id == JS_CLASS_C_FUNCTION) {
str = "Function";
} else {
str = "Object";
}
js_printf(ctx, "[object %s]", str);
}
}
static void dump_string(JSContext *ctx, int sep, const uint8_t *buf, size_t len,
int flags)
{
BOOL use_quote;
const uint8_t *p, *p_end;
size_t i, clen;
int c;
use_quote = TRUE;
if (flags & JS_DUMP_NOQUOTE) {
if (len >= 1 && is_ident_first(buf[0])) {
for(i = 1; i < len; i++) {
if (!is_ident_next(buf[i]))
goto need_quote;
}
use_quote = FALSE;
}
need_quote: ;
}
if (!(flags & JS_DUMP_RAW))
sep = '"';
if (use_quote)
js_putchar(ctx, sep);
p = buf;
p_end = buf + len;
while (p < p_end) {
c = utf8_get(p, &clen);
switch(c) {
case '\t':
c = 't';
goto quote;
case '\r':
c = 'r';
goto quote;
case '\n':
c = 'n';
goto quote;
case '\b':
c = 'b';
goto quote;
case '\f':
c = 'f';
goto quote;
case '\"':
case '\\':
quote:
js_putchar(ctx, '\\');
js_putchar(ctx, c);
break;
default:
if (c < 32 || (c >= 0xd800 && c < 0xe000)) {
js_printf(ctx, "\\u%04x", c);
} else {
ctx->write_func(ctx->opaque, p, clen);
}
break;
}
p += clen;
}
if (use_quote)
js_putchar(ctx, sep);
}
void JS_PrintValueF(JSContext *ctx, JSValue val, int flags)
{
if (JS_IsInt(val)) {
js_printf(ctx, "%d", JS_VALUE_GET_INT(val));
} else
#ifdef JS_USE_SHORT_FLOAT
if (JS_IsShortFloat(val)) {
js_dump_float64(ctx, js_get_short_float(val));
} else
#endif
if (!JS_IsPtr(val)) {
switch(JS_VALUE_GET_SPECIAL_TAG(val)) {
case JS_TAG_NULL:
case JS_TAG_UNDEFINED:
case JS_TAG_UNINITIALIZED:
case JS_TAG_BOOL:
js_printf(ctx, "%"JSValue_PRI"", val);
break;
case JS_TAG_EXCEPTION:
js_printf(ctx, "[exception %d]", JS_VALUE_GET_SPECIAL_VALUE(val));
break;
case JS_TAG_CATCH_OFFSET:
js_printf(ctx, "[catch_offset %d]", JS_VALUE_GET_SPECIAL_VALUE(val));
break;
case JS_TAG_SHORT_FUNC:
{
int idx = JS_VALUE_GET_SPECIAL_VALUE(val);
js_printf(ctx, "function ");
JS_PrintValueF(ctx, reloc_c_func_name(ctx, ctx->c_function_table[idx].name), JS_DUMP_NOQUOTE);
js_printf(ctx, "()");
}
break;
case JS_TAG_STRING_CHAR:
{
uint8_t buf[UTF8_CHAR_LEN_MAX + 1];
int len;
len = get_short_string(buf, val);
dump_string(ctx, '`', buf, len, flags);
}
break;
default:
js_printf(ctx, "[tag %d]", (int)JS_VALUE_GET_SPECIAL_TAG(val));
break;
}
} else {
void *ptr = JS_VALUE_TO_PTR(val);
int mtag = ((JSMemBlockHeader *)ptr)->mtag;
switch(mtag) {
case JS_MTAG_FLOAT64:
{
JSFloat64 *p = ptr;
js_dump_float64(ctx, p->u.dval);
}
break;
case JS_MTAG_OBJECT:
js_dump_object(ctx, ptr, flags);
break;
case JS_MTAG_STRING:
{
JSString *p = ptr;
int sep;
sep = p->is_unique ? '\'' : '\"';
dump_string(ctx, sep, p->buf, p->len, flags);
}
break;
case JS_MTAG_VALUE_ARRAY:
{
JSValueArray *arr = ptr;
js_dump_array(ctx, arr, arr->size);
}
break;
case JS_MTAG_BYTE_ARRAY:
{
JSByteArray *arr = ptr;
js_printf(ctx, "byte_array(%" PRIu64 ")", (uint64_t)arr->size);
}
break;
case JS_MTAG_FUNCTION_BYTECODE:
{
JSFunctionBytecode *b = ptr;
js_printf(ctx, "bytecode_function ");
JS_PrintValueF(ctx, b->func_name, JS_DUMP_NOQUOTE);
js_printf(ctx, "()");
}
break;
case JS_MTAG_VARREF:
{
JSVarRef *pv = ptr;
js_printf(ctx, "var_ref(");
if (pv->is_detached)
JS_PrintValue(ctx, pv->u.value);
else
JS_PrintValue(ctx, *pv->u.pvalue);
js_printf(ctx, ")");
}
break;
default:
js_printf(ctx, "[mtag %d]", mtag);
break;
}
}
}
void JS_PrintValue(JSContext *ctx, JSValue val)
{
return JS_PrintValueF(ctx, val, 0);
}
static const char *get_mtag_name(unsigned int mtag)
{
if (mtag >= countof(js_mtag_name))
return "?";
else
return js_mtag_name[mtag];
}
static uint32_t val_to_offset(JSContext *ctx, JSValue val)
{
if (!JS_IsPtr(val))
return 0;
else
return (uint8_t *)JS_VALUE_TO_PTR(val) - ctx->heap_base;
}
void JS_DumpMemory(JSContext *ctx, BOOL is_long)
{
uint8_t *ptr;
uint32_t mtag_mem_size[JS_MTAG_COUNT];
uint32_t mtag_count[JS_MTAG_COUNT];
uint32_t tot_size, i;
if (is_long) {
js_printf(ctx, "%10s %s %8s %15s %10s %10s %s\n", "OFFSET", "M", "SIZE", "TAG", "PROTO", "PROPS", "EXTRA");
}
for(i = 0; i < JS_MTAG_COUNT; i++) {
mtag_mem_size[i] = 0;
mtag_count[i] = 0;
}
tot_size = 0;
ptr = ctx->heap_base;
while (ptr < ctx->heap_free) {
int mtag, size, gc_mark;
mtag = ((JSMemBlockHeader *)ptr)->mtag;
gc_mark = ((JSMemBlockHeader *)ptr)->gc_mark;
size = get_mblock_size(ptr);
mtag_mem_size[mtag] += size;
mtag_count[mtag]++;
tot_size += size;
if (is_long) {
js_printf(ctx, "0x%08x %c %8u %15s",
(unsigned int)((uint8_t *)ptr - ctx->heap_base),
gc_mark ? '*' : ' ',
size,
get_mtag_name(mtag));
if (mtag != JS_MTAG_FREE) {
if (mtag == JS_MTAG_OBJECT) {
JSObject *p = (JSObject *)ptr;
js_printf(ctx, " 0x%08x 0x%08x",
val_to_offset(ctx, p->proto), val_to_offset(ctx, p->props));
} else {
js_printf(ctx, " %10s %10s", "", "");
}
js_printf(ctx, " ");
JS_PrintValueF(ctx, JS_VALUE_FROM_PTR(ptr), JS_DUMP_RAW);
}
js_printf(ctx, "\n");
}
ptr += size;
}
js_printf(ctx, "%15s %8s %8s %8s %8s\n", "TAG", "COUNT", "AVG_SIZE", "SIZE", "RATIO");
for(i = 0; i < JS_MTAG_COUNT; i++) {
if (mtag_count[i] != 0) {
js_printf(ctx, "%15s %8u %8d %8u %7d%%\n",
get_mtag_name(i),
(unsigned int)mtag_count[i],
(int)js_lrint((double)mtag_mem_size[i] / (double)mtag_count[i]),
(unsigned int)mtag_mem_size[i],
(int)js_lrint((double)mtag_mem_size[i] / (double)tot_size * 100.0));
}
}
js_printf(ctx, "heap size=%u/%u stack_size=%u\n",
(unsigned int)(ctx->heap_free - ctx->heap_base),
(unsigned int)(ctx->stack_top - ctx->heap_base),
(unsigned int)(ctx->stack_top - (uint8_t *)ctx->sp));
}
static __maybe_unused void JS_DumpUniqueStrings(JSContext *ctx)
{
int i;
JSValueArray *arr;
arr = JS_VALUE_TO_PTR( ctx->unique_strings);
js_printf(ctx, "%5s %s\n", "N", "UNIQUE_STRING");
for(i = 0; i < ctx->unique_strings_len; i++) {
js_printf(ctx, "%5d ", i);
JS_PrintValue(ctx, arr->arr[i]);
js_printf(ctx, "\n");
}
}
#else
void JS_PrintValueF(JSContext *ctx, JSValue val, int flags)
{
}
void JS_PrintValue(JSContext *ctx, JSValue val)
{
return JS_PrintValueF(ctx, val, 0);
}
void JS_DumpMemory(JSContext *ctx, BOOL is_long)
{
}
static __maybe_unused void JS_DumpUniqueStrings(JSContext *ctx)
{
}
#endif
void JS_DumpValueF(JSContext *ctx, const char *str,
JSValue val, int flags)
{
js_printf(ctx, "%s=", str);
JS_PrintValueF(ctx, val, flags);
js_printf(ctx, "\n");
}
void JS_DumpValue(JSContext *ctx, const char *str,
JSValue val)
{
JS_DumpValueF(ctx, str, val, 0);
}
enum {
TOK_NUMBER = 128,
TOK_STRING,
TOK_IDENT,
TOK_REGEXP,
TOK_MUL_ASSIGN,
TOK_DIV_ASSIGN,
TOK_MOD_ASSIGN,
TOK_PLUS_ASSIGN,
TOK_MINUS_ASSIGN,
TOK_SHL_ASSIGN,
TOK_SAR_ASSIGN,
TOK_SHR_ASSIGN,
TOK_AND_ASSIGN,
TOK_XOR_ASSIGN,
TOK_OR_ASSIGN,
TOK_POW_ASSIGN,
TOK_DEC,
TOK_INC,
TOK_SHL,
TOK_SAR,
TOK_SHR,
TOK_LT,
TOK_LTE,
TOK_GT,
TOK_GTE,
TOK_EQ,
TOK_STRICT_EQ,
TOK_NEQ,
TOK_STRICT_NEQ,
TOK_LAND,
TOK_LOR,
TOK_POW,
TOK_EOF,
TOK_FIRST_KEYWORD,
TOK_NULL = TOK_FIRST_KEYWORD + JS_ATOM_null,
TOK_FALSE = TOK_FIRST_KEYWORD + JS_ATOM_false,
TOK_TRUE = TOK_FIRST_KEYWORD + JS_ATOM_true,
TOK_IF = TOK_FIRST_KEYWORD + JS_ATOM_if,
TOK_ELSE = TOK_FIRST_KEYWORD + JS_ATOM_else,
TOK_RETURN = TOK_FIRST_KEYWORD + JS_ATOM_return,
TOK_VAR = TOK_FIRST_KEYWORD + JS_ATOM_var,
TOK_THIS = TOK_FIRST_KEYWORD + JS_ATOM_this,
TOK_DELETE = TOK_FIRST_KEYWORD + JS_ATOM_delete,
TOK_VOID = TOK_FIRST_KEYWORD + JS_ATOM_void,
TOK_TYPEOF = TOK_FIRST_KEYWORD + JS_ATOM_typeof,
TOK_NEW = TOK_FIRST_KEYWORD + JS_ATOM_new,
TOK_IN = TOK_FIRST_KEYWORD + JS_ATOM_in,
TOK_INSTANCEOF = TOK_FIRST_KEYWORD + JS_ATOM_instanceof,
TOK_DO = TOK_FIRST_KEYWORD + JS_ATOM_do,
TOK_WHILE = TOK_FIRST_KEYWORD + JS_ATOM_while,
TOK_FOR = TOK_FIRST_KEYWORD + JS_ATOM_for,
TOK_BREAK = TOK_FIRST_KEYWORD + JS_ATOM_break,
TOK_CONTINUE = TOK_FIRST_KEYWORD + JS_ATOM_continue,
TOK_SWITCH = TOK_FIRST_KEYWORD + JS_ATOM_switch,
TOK_CASE = TOK_FIRST_KEYWORD + JS_ATOM_case,
TOK_DEFAULT = TOK_FIRST_KEYWORD + JS_ATOM_default,
TOK_THROW = TOK_FIRST_KEYWORD + JS_ATOM_throw,
TOK_TRY = TOK_FIRST_KEYWORD + JS_ATOM_try,
TOK_CATCH = TOK_FIRST_KEYWORD + JS_ATOM_catch,
TOK_FINALLY = TOK_FIRST_KEYWORD + JS_ATOM_finally,
TOK_FUNCTION = TOK_FIRST_KEYWORD + JS_ATOM_function,
TOK_DEBUGGER = TOK_FIRST_KEYWORD + JS_ATOM_debugger,
TOK_WITH = TOK_FIRST_KEYWORD + JS_ATOM_with,
TOK_CLASS = TOK_FIRST_KEYWORD + JS_ATOM_class,
TOK_CONST = TOK_FIRST_KEYWORD + JS_ATOM_const,
TOK_ENUM = TOK_FIRST_KEYWORD + JS_ATOM_enum,
TOK_EXPORT = TOK_FIRST_KEYWORD + JS_ATOM_export,
TOK_EXTENDS = TOK_FIRST_KEYWORD + JS_ATOM_extends,
TOK_IMPORT = TOK_FIRST_KEYWORD + JS_ATOM_import,
TOK_SUPER = TOK_FIRST_KEYWORD + JS_ATOM_super,
TOK_IMPLEMENTS = TOK_FIRST_KEYWORD + JS_ATOM_implements,
TOK_INTERFACE = TOK_FIRST_KEYWORD + JS_ATOM_interface,
TOK_LET = TOK_FIRST_KEYWORD + JS_ATOM_let,
TOK_PACKAGE = TOK_FIRST_KEYWORD + JS_ATOM_package,
TOK_PRIVATE = TOK_FIRST_KEYWORD + JS_ATOM_private,
TOK_PROTECTED = TOK_FIRST_KEYWORD + JS_ATOM_protected,
TOK_PUBLIC = TOK_FIRST_KEYWORD + JS_ATOM_public,
TOK_STATIC = TOK_FIRST_KEYWORD + JS_ATOM_static,
TOK_YIELD = TOK_FIRST_KEYWORD + JS_ATOM_yield,
};
typedef struct BlockEnv {
JSValue prev;
JSValue label_name;
JSValue label_break;
JSValue label_cont;
JSValue label_finally;
JSValue drop_count;
} BlockEnv;
typedef uint32_t JSSourcePos;
typedef struct JSToken {
int val;
JSSourcePos source_pos;
union {
double d;
struct {
uint32_t re_flags;
uint32_t re_end_pos;
} regexp;
} u;
JSValue value;
} JSToken;
typedef struct JSParseState {
JSContext *ctx;
JSToken token;
BOOL got_lf : 8;
BOOL is_eval : 8;
BOOL has_retval : 8;
BOOL is_repl : 8;
BOOL has_column : 8;
BOOL dropped_result : 8;
JSValue source_str;
JSValue filename_str;
const uint8_t *source_buf;
uint32_t buf_pos;
uint32_t buf_len;
JSValue cur_func;
JSValue byte_code;
uint32_t byte_code_len;
int last_opcode_pos;
int last_pc2line_pos;
JSSourcePos last_pc2line_source_pos;
uint32_t pc2line_bit_len;
JSSourcePos pc2line_source_pos;
uint16_t cpool_len;
uint32_t hoisted_code_len;
uint16_t local_vars_len;
int eval_ret_idx;
JSValue top_break;
uint8_t capture_count;
uint8_t re_in_js: 1;
uint8_t multi_line : 1;
uint8_t dotall : 1;
uint8_t ignore_case : 1;
uint8_t is_unicode : 1;
jmp_buf jmp_env;
char error_msg[64];
} JSParseState;
static int js_parse_json_value(JSParseState *s, int state, int dummy_param);
static JSValue js_parse_regexp(JSParseState *s, int eval_flags);
static size_t js_parse_regexp_flags(int *pre_flags, const uint8_t *buf);
static int re_parse_alternative(JSParseState *s, int state, int dummy_param);
static int re_parse_disjunction(JSParseState *s, int state, int dummy_param);
#ifdef DUMP_BYTECODE
static __maybe_unused void dump_byte_code(JSContext *ctx, JSFunctionBytecode *b)
{
JSByteArray *arr, *pc2line;
JSValueArray *cpool, *vars, *ext_vars;
const JSOpCode *oi;
int pos, op, size, addr, idx, arg_count, len, i, line_num, col_num;
int line_num1, col_num1, hoisted_code_len;
uint8_t *tab;
uint32_t pc2line_pos;
arr = JS_VALUE_TO_PTR(b->byte_code);
if (b->cpool != JS_NULL)
cpool = JS_VALUE_TO_PTR(b->cpool);
else
cpool = NULL;
if (b->vars != JS_NULL)
vars = JS_VALUE_TO_PTR(b->vars);
else
vars = NULL;
if (b->ext_vars != JS_NULL)
ext_vars = JS_VALUE_TO_PTR(b->ext_vars);
else
ext_vars = NULL;
if (b->pc2line != JS_NULL)
pc2line = JS_VALUE_TO_PTR(b->pc2line);
else
pc2line = NULL;
arg_count = b->arg_count;
JS_PrintValueF(ctx, b->filename, JS_DUMP_NOQUOTE);
js_printf(ctx, ": function ");
JS_PrintValueF(ctx, b->func_name, JS_DUMP_NOQUOTE);
js_printf(ctx, ":\n");
if (b->arg_count && vars) {
js_printf(ctx, " args:");
for(i = 0; i < b->arg_count; i++) {
js_printf(ctx, " ");
JS_PrintValue(ctx, vars->arr[i]);
}
js_printf(ctx, "\n");
}
if (vars) {
js_printf(ctx, " locals:");
for(i = 0; i < vars->size - b->arg_count; i++) {
js_printf(ctx, " ");
JS_PrintValue(ctx, vars->arr[i + b->arg_count]);
}
js_printf(ctx, "\n");
}
if (ext_vars) {
js_printf(ctx, " refs:");
for(i = 0; i < b->ext_vars_len; i++) {
int var_kind, var_idx, decl;
static const char *var_kind_str[] = { "arg", "var", "ref", "global" };
js_printf(ctx, " ");
JS_PrintValue(ctx, ext_vars->arr[2 * i]);
decl = JS_VALUE_GET_INT(ext_vars->arr[2 * i + 1]);
var_kind = decl >> 16;
var_idx = decl & 0xffff;
js_printf(ctx, " (%s:%d)", var_kind_str[var_kind], var_idx);
}
js_printf(ctx, "\n");
}
js_printf(ctx, " cpool_size: %d\n", cpool ? (int)cpool->size : 0);
js_printf(ctx, " stack_size: %d\n", b->stack_size);
js_printf(ctx, " opcodes:\n");
tab = arr->buf;
len = arr->size;
pos = 0;
pc2line_pos = 0;
hoisted_code_len = 0;
if (pc2line)
hoisted_code_len = get_pc2line_hoisted_code_len(pc2line->buf, pc2line->size);
line_num = 1;
col_num = 1;
line_num1 = 0;
col_num1 = 0;
while (pos < len) {
if (pc2line && pos >= hoisted_code_len) {
get_pc2line(&line_num, &col_num, pc2line->buf, pc2line->size,
&pc2line_pos, b->has_column);
if (line_num != line_num1 || col_num != col_num1) {
js_printf(ctx, " # %d", line_num);
if (b->has_column)
js_printf(ctx, ", %d", col_num);
js_printf(ctx, "\n");
line_num1 = line_num;
col_num1 = col_num;
}
}
op = tab[pos];
js_printf(ctx, "%5d: ", pos);
if (op >= OP_COUNT) {
js_printf(ctx, "invalid opcode (0x%02x)\n", op);
pos++;
continue;
}
oi = &opcode_info[op];
size = oi->size;
if ((pos + size) > len) {
js_printf(ctx, "truncated opcode (0x%02x)\n", op);
break;
}
js_printf(ctx, "%s", oi->name);
pos++;
switch(oi->fmt) {
case OP_FMT_u8:
js_printf(ctx, " %u", (int)get_u8(tab + pos));
break;
case OP_FMT_i8:
js_printf(ctx, " %d", (int)get_i8(tab + pos));
break;
case OP_FMT_u16:
case OP_FMT_npop:
js_printf(ctx, " %u", (int)get_u16(tab + pos));
break;
case OP_FMT_i16:
js_printf(ctx, " %d", (int)get_i16(tab + pos));
break;
case OP_FMT_i32:
js_printf(ctx, " %d", (int)get_i32(tab + pos));
break;
case OP_FMT_u32:
js_printf(ctx, " %u", (int)get_u32(tab + pos));
break;
case OP_FMT_none_int:
js_printf(ctx, " %d", op - OP_push_0);
break;
#if 0#endif
case OP_FMT_label8:
addr = get_i8(tab + pos);
goto has_addr1;
case OP_FMT_label16:
addr = get_i16(tab + pos);
goto has_addr1;
case OP_FMT_label:
addr = get_u32(tab + pos);
goto has_addr1;
has_addr1:
js_printf(ctx, " %u", addr + pos);
break;
case OP_FMT_const8:
idx = get_u8(tab + pos);
goto has_pool_idx;
case OP_FMT_const16:
idx = get_u16(tab + pos);
goto has_pool_idx;
has_pool_idx:
js_printf(ctx, " %u: ", idx);
if (idx < cpool->size) {
JS_PrintValue(ctx, cpool->arr[idx]);
}
break;
case OP_FMT_none_loc:
idx = (op - OP_get_loc0) % 4;
goto has_loc;
case OP_FMT_loc8:
idx = get_u8(tab + pos);
goto has_loc;
case OP_FMT_loc:
idx = get_u16(tab + pos);
has_loc:
js_printf(ctx, " %d: ", idx);
idx += arg_count;
if (idx < vars->size) {
JS_PrintValue(ctx, vars->arr[idx]);
}
break;
case OP_FMT_none_arg:
idx = (op - OP_get_arg0) % 4;
goto has_arg;
case OP_FMT_arg:
idx = get_u16(tab + pos);
has_arg:
js_printf(ctx, " %d: ", idx);
if (idx < vars->size) {
JS_PrintValue(ctx, vars->arr[idx]);
}
break;
#if 0#endif
case OP_FMT_var_ref:
idx = get_u16(tab + pos);
js_printf(ctx, " %d: ", idx);
if (2 * idx < ext_vars->size) {
JS_PrintValue(ctx, ext_vars->arr[2 * idx]);
}
break;
case OP_FMT_value:
js_printf(ctx, " ");
idx = get_u32(tab + pos);
JS_PrintValue(ctx, idx);
break;
default:
break;
}
js_printf(ctx, "\n");
pos += oi->size - 1;
}
}
#endif
static void next_token(JSParseState *s);
static void __attribute((unused)) dump_token(JSParseState *s,
const JSToken *token)
{
JSContext *ctx = s->ctx;
switch(token->val) {
case TOK_NUMBER:
js_printf(ctx, "number: %d\n", (int)token->u.d);
break;
case TOK_IDENT:
{
js_printf(ctx, "ident: ");
JS_PrintValue(s->ctx, token->value);
js_printf(ctx, "\n");
}
break;
case TOK_STRING:
{
js_printf(ctx, "string: ");
JS_PrintValue(s->ctx, token->value);
js_printf(ctx, "\n");
}
break;
case TOK_REGEXP:
{
js_printf(ctx, "regexp: ");
JS_PrintValue(s->ctx, token->value);
js_printf(ctx, "\n");
}
break;
case TOK_EOF:
js_printf(ctx, "eof\n");
break;
default:
if (s->token.val >= TOK_FIRST_KEYWORD) {
js_printf(ctx, "token: ");
JS_PrintValue(s->ctx, token->value);
js_printf(ctx, "\n");
} else if (s->token.val >= 128) {
js_printf(ctx, "token: %d\n", token->val);
} else {
js_printf(ctx, "token: '%c'\n", token->val);
}
break;
}
}
static int get_line_col(int *pcol_num, const uint8_t *buf, size_t len)
{
int line_num, col_num, c;
size_t i;
line_num = 0;
col_num = 0;
for(i = 0; i < len; i++) {
c = buf[i];
if (c == '\n') {
line_num++;
col_num = 0;
} else if (c < 0x80 || c >= 0xc0) {
col_num++;
}
}
*pcol_num = col_num;
return line_num;
}
static void __attribute__((format(printf, 2, 3), noreturn)) js_parse_error(JSParseState *s, const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
js_vsnprintf(s->error_msg, sizeof(s->error_msg), fmt, ap);
va_end(ap);
longjmp(s->jmp_env, 1);
}
static void js_parse_error_mem(JSParseState *s)
{
return js_parse_error(s, "not enough memory");
}
static void js_parse_error_stack_overflow(JSParseState *s)
{
return js_parse_error(s, "stack overflow");
}
static void js_parse_expect1(JSParseState *s, int ch)
{
if (s->token.val != ch)
js_parse_error(s, "expecting '%c'", ch);
}
static void js_parse_expect(JSParseState *s, int ch)
{
js_parse_expect1(s, ch);
next_token(s);
}
static void js_parse_expect_semi(JSParseState *s)
{
if (s->token.val != ';') {
if (s->token.val == TOK_EOF || s->token.val == '}' || s->got_lf) {
return;
}
js_parse_error(s, "expecting '%c'", ';');
}
next_token(s);
}
#define SKIP_HAS_ARGUMENTS (1 << 0)
#define SKIP_HAS_FUNC_NAME (1 << 1)
#define SKIP_HAS_SEMI (1 << 2)
static int js_skip_parens(JSParseState *s, JSValue *pfunc_name)
{
uint8_t state[128];
int level, c, bits = 0;
level = 0;
state[level++] = 0;
for (;;) {
switch(s->token.val) {
case '(':
c = ')';
goto add_level;
case '[':
c = ']';
goto add_level;
case '{':
c = '}';
add_level:
if (level >= sizeof(state)) {
js_parse_error(s, "too many nested blocks");
}
state[level++] = c;
break;
case ')':
case ']':
case '}':
c = state[--level];
if (s->token.val != c)
js_parse_error(s, "expecting '%c'", c);
break;
case TOK_EOF:
js_parse_error(s, "expecting '%c'", state[level - 1]);
case TOK_IDENT:
if (s->token.value == js_get_atom(s->ctx, JS_ATOM_arguments))
bits |= SKIP_HAS_ARGUMENTS;
if (pfunc_name && s->token.value == *pfunc_name)
bits |= SKIP_HAS_FUNC_NAME;
break;
case ';':
if (level == 2)
bits |= SKIP_HAS_SEMI;
break;
}
next_token(s);
if (level <= 1)
break;
}
return bits;
}
static void js_skip_expr(JSParseState *s)
{
for(;;) {
switch(s->token.val) {
case ')':
return;
case ';':
case TOK_EOF:
js_parse_error(s, "expecting '%c'", ')');
case '(':
case '[':
case '{':
js_skip_parens(s, NULL);
break;
default:
next_token(s);
break;
}
}
}
typedef struct JSParsePos {
BOOL got_lf : 8;
BOOL regexp_allowed : 8;
uint32_t source_pos;
} JSParsePos;
static BOOL is_regexp_allowed(int tok)
{
switch (tok) {
case TOK_NUMBER:
case TOK_STRING:
case TOK_REGEXP:
case TOK_DEC:
case TOK_INC:
case TOK_NULL:
case TOK_FALSE:
case TOK_TRUE:
case TOK_THIS:
case TOK_IF:
case TOK_WHILE:
case TOK_FOR:
case TOK_DO:
case TOK_CASE:
case TOK_CATCH:
case ')':
case ']':
case TOK_IDENT:
return FALSE;
default:
return TRUE;
}
}
static void js_parse_get_pos(JSParseState *s, JSParsePos *sp)
{
sp->source_pos = s->token.source_pos;
sp->got_lf = s->got_lf;
sp->regexp_allowed = is_regexp_allowed(s->token.val);
}
static void js_parse_seek_token(JSParseState *s, const JSParsePos *sp)
{
s->buf_pos = sp->source_pos;
s->got_lf = sp->got_lf;
s->token.val = sp->regexp_allowed ? ' ' : ')';
next_token(s);
}
static int js_parse_skip_parens_token(JSParseState *s)
{
JSParsePos pos;
int bits;
js_parse_get_pos(s, &pos);
bits = js_skip_parens(s, NULL);
js_parse_seek_token(s, &pos);
return bits;
}
static int js_parse_escape(const uint8_t *buf, size_t *plen)
{
int c;
const uint8_t *p = buf;
c = *p++;
switch(c) {
case 'b':
c = '\b';
break;
case 'f':
c = '\f';
break;
case 'n':
c = '\n';
break;
case 'r':
c = '\r';
break;
case 't':
c = '\t';
break;
case 'v':
c = '\v';
break;
case '\'':
case '\"':
case '\\':
break;
case 'x':
{
int h0, h1;
h0 = from_hex(*p++);
if (h0 < 0)
return -1;
h1 = from_hex(*p++);
if (h1 < 0)
return -1;
c = (h0 << 4) | h1;
}
break;
case 'u':
{
int h, i;
if (*p == '{') {
p++;
c = 0;
for(;;) {
h = from_hex(*p++);
if (h < 0)
return -1;
c = (c << 4) | h;
if (c > 0x10FFFF)
return -1;
if (*p == '}')
break;
}
p++;
} else {
c = 0;
for(i = 0; i < 4; i++) {
h = from_hex(*p++);
if (h < 0) {
return -1;
}
c = (c << 4) | h;
}
}
}
break;
case '0':
c -= '0';
if (c != 0 || is_num(*p))
return -1;
break;
default:
return -2;
}
*plen = p - buf;
return c;
}
static JSValue js_parse_string(JSParseState *s, uint32_t *ppos, int sep)
{
JSContext *ctx = s->ctx;
JSValue res;
const uint8_t *buf;
uint32_t pos;
uint32_t c;
size_t escape_len = 0;
StringBuffer b_s, *b = &b_s;
if (string_buffer_push(ctx, b, 16))
js_parse_error_mem(s);
buf = s->source_buf;
pos = *ppos;
for(;;) {
c = buf[pos];
if (c == '\0' || c == '\n' || c == '\r') {
js_parse_error(s, "unexpected end of string");
}
pos++;
if (c == sep)
break;
if (c == '\\') {
if (buf[pos] == '\n') {
pos++;
continue;
}
c = js_parse_escape(buf + pos, &escape_len);
if (c == -1) {
js_parse_error(s, "invalid escape sequence");
} else if (c == -2) {
continue;
}
pos += escape_len;
} else if (c >= 0x80) {
size_t clen;
pos--;
c = unicode_from_utf8(buf + pos, UTF8_CHAR_LEN_MAX, &clen);
pos += clen;
if (c == -1) {
js_parse_error(s, "invalid UTF-8 sequence");
}
}
if (string_buffer_putc(ctx, b, c))
break;
buf = s->source_buf;
}
*ppos = pos;
res = string_buffer_pop(ctx, b);
if (JS_IsException(res))
js_parse_error_mem(s);
return res;
}
static void js_parse_ident(JSParseState *s, JSToken *token,
uint32_t *ppos, int c)
{
JSContext *ctx = s->ctx;
uint32_t pos;
JSValue val, val2;
JSGCRef val2_ref;
const uint8_t *buf;
StringBuffer b_s, *b = &b_s;
if (string_buffer_push(ctx, b, 16))
js_parse_error_mem(s);
string_buffer_putc(ctx, b, c);
buf = s->source_buf;
pos = *ppos;
while (pos < s->buf_len) {
c = buf[pos];
if (!is_ident_next(c))
break;
pos++;
if (string_buffer_putc(ctx, b, c))
break;
buf = s->source_buf;
}
token->val = TOK_IDENT;
val2 = string_buffer_pop(ctx, b);
JS_PUSH_VALUE(ctx, val2);
val = JS_MakeUniqueString(ctx, val2);
JS_POP_VALUE(ctx, val2);
if (JS_IsException(val))
js_parse_error_mem(s);
if (val != val2)
js_free(ctx, JS_VALUE_TO_PTR(val2));
token->value = val;
if (JS_IsPtr(val)) {
const JSWord *atom_start, *atom_last, *ptr;
atom_start = ctx->atom_table;
atom_last = atom_start + JS_ATOM_yield;
ptr = JS_VALUE_TO_PTR(val);
if (ptr >= atom_start && ptr <= atom_last) {
token->val = TOK_NULL + (ptr - atom_start);
}
}
*ppos = pos;
}
static void js_parse_regexp_token(JSParseState *s, uint32_t *ppos)
{
JSContext *ctx = s->ctx;
uint32_t pos;
uint32_t c;
BOOL in_class;
size_t clen;
int re_flags, end_pos, start_pos;
JSString *p;
in_class = FALSE;
pos = *ppos;
start_pos = pos;
for(;;) {
c = unicode_from_utf8(s->source_buf + pos, UTF8_CHAR_LEN_MAX, &clen);
if (c == -1)
js_parse_error(s, "invalid UTF-8 sequence");
pos += clen;
if (c == '\0' || c == '\n' || c == '\r') {
goto invalid_char;
} else if (c == '/') {
if (!in_class)
break;
} else if (c == '[') {
in_class = TRUE;
} else if (c == ']') {
in_class = FALSE;
} else if (c == '\\') {
c = unicode_from_utf8(s->source_buf + pos, UTF8_CHAR_LEN_MAX, &clen);
if (c == -1)
js_parse_error(s, "invalid UTF-8 sequence");
if (c == '\0' || c == '\n' || c == '\r') {
invalid_char:
js_parse_error(s, "unexpected line terminator in regexp");
}
pos += clen;
}
}
end_pos = pos - 1;
clen = js_parse_regexp_flags(&re_flags, s->source_buf + pos);
pos += clen;
if (is_ident_next(s->source_buf[pos]))
js_parse_error(s, "invalid regular expression flags");
p = js_alloc_string(ctx, end_pos - start_pos);
if (!p)
js_parse_error_mem(s);
p->is_ascii = is_ascii_string((char *)(s->source_buf + start_pos), end_pos - start_pos);
memcpy(p->buf, s->source_buf + start_pos, end_pos - start_pos);
*ppos = pos;
s->token.val = TOK_REGEXP;
s->token.value = JS_VALUE_FROM_PTR(p);
s->token.u.regexp.re_flags = re_flags;
s->token.u.regexp.re_end_pos = end_pos;
}
static void next_token(JSParseState *s)
{
uint32_t pos;
const uint8_t *p;
int c;
pos = s->buf_pos;
s->got_lf = FALSE;
s->token.value = JS_NULL;
p = s->source_buf + s->buf_pos;
redo:
s->token.source_pos = p - s->source_buf;
c = *p;
switch(c) {
case 0:
s->token.val = TOK_EOF;
break;
case '\"':
case '\'':
p++;
pos = p - s->source_buf;
s->token.value = js_parse_string(s, &pos, c);
s->token.val = TOK_STRING;
p = s->source_buf + pos;
break;
case '\n':
s->got_lf = TRUE;
p++;
goto redo;
case ' ':
case '\t':
case '\f':
case '\v':
case '\r':
p++;
goto redo;
case '/':
if (p[1] == '*') {
p += 2;
for(;;) {
if (*p == '\0')
js_parse_error(s, "unexpected end of comment");
if (p[0] == '*' && p[1] == '/') {
p += 2;
break;
}
p++;
}
goto redo;
} else if (p[1] == '/') {
p += 2;
for(;;) {
if (*p == '\0' || *p == '\n')
break;
p++;
}
goto redo;
} else if (is_regexp_allowed(s->token.val)) {
p++;
pos = p - s->source_buf;
js_parse_regexp_token(s, &pos);
p = s->source_buf + pos;
} else if (p[1] == '=') {
p += 2;
s->token.val = TOK_DIV_ASSIGN;
} else {
p++;
s->token.val = c;
}
break;
case 'a' ... 'z':
case 'A' ... 'Z':
case '_':
case '$':
p++;
pos = p - s->source_buf;
js_parse_ident(s, &s->token, &pos, c);
p = s->source_buf + pos;
break;
case '.':
if (is_digit(p[1]))
goto parse_number;
else
goto def_token;
case '0':
if (is_digit(p[1]))
goto invalid_number;
goto parse_number;
case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8':
case '9':
parse_number:
{
double d;
JSByteArray *tmp_arr;
pos = p - s->source_buf;
tmp_arr = js_alloc_byte_array(s->ctx, sizeof(JSATODTempMem));
if (!tmp_arr)
js_parse_error_mem(s);
p = s->source_buf + pos;
d = js_atod((const char *)p, (const char **)&p, 0,
JS_ATOD_ACCEPT_BIN_OCT | JS_ATOD_ACCEPT_UNDERSCORES,
(JSATODTempMem *)tmp_arr->buf);
js_free(s->ctx, tmp_arr);
if (isnan(d)) {
invalid_number:
js_parse_error(s, "invalid number literal");
}
s->token.val = TOK_NUMBER;
s->token.u.d = d;
}
break;
case '*':
if (p[1] == '=') {
p += 2;
s->token.val = TOK_MUL_ASSIGN;
} else if (p[1] == '*') {
if (p[2] == '=') {
p += 3;
s->token.val = TOK_POW_ASSIGN;
} else {
p += 2;
s->token.val = TOK_POW;
}
} else {
goto def_token;
}
break;
case '%':
if (p[1] == '=') {
p += 2;
s->token.val = TOK_MOD_ASSIGN;
} else {
goto def_token;
}
break;
case '+':
if (p[1] == '=') {
p += 2;
s->token.val = TOK_PLUS_ASSIGN;
} else if (p[1] == '+') {
p += 2;
s->token.val = TOK_INC;
} else {
goto def_token;
}
break;
case '-':
if (p[1] == '=') {
p += 2;
s->token.val = TOK_MINUS_ASSIGN;
} else if (p[1] == '-') {
p += 2;
s->token.val = TOK_DEC;
} else {
goto def_token;
}
break;
case '<':
if (p[1] == '=') {
p += 2;
s->token.val = TOK_LTE;
} else if (p[1] == '<') {
if (p[2] == '=') {
p += 3;
s->token.val = TOK_SHL_ASSIGN;
} else {
p += 2;
s->token.val = TOK_SHL;
}
} else {
goto def_token;
}
break;
case '>':
if (p[1] == '=') {
p += 2;
s->token.val = TOK_GTE;
} else if (p[1] == '>') {
if (p[2] == '>') {
if (p[3] == '=') {
p += 4;
s->token.val = TOK_SHR_ASSIGN;
} else {
p += 3;
s->token.val = TOK_SHR;
}
} else if (p[2] == '=') {
p += 3;
s->token.val = TOK_SAR_ASSIGN;
} else {
p += 2;
s->token.val = TOK_SAR;
}
} else {
goto def_token;
}
break;
case '=':
if (p[1] == '=') {
if (p[2] == '=') {
p += 3;
s->token.val = TOK_STRICT_EQ;
} else {
p += 2;
s->token.val = TOK_EQ;
}
} else {
goto def_token;
}
break;
case '!':
if (p[1] == '=') {
if (p[2] == '=') {
p += 3;
s->token.val = TOK_STRICT_NEQ;
} else {
p += 2;
s->token.val = TOK_NEQ;
}
} else {
goto def_token;
}
break;
case '&':
if (p[1] == '=') {
p += 2;
s->token.val = TOK_AND_ASSIGN;
} else if (p[1] == '&') {
p += 2;
s->token.val = TOK_LAND;
} else {
goto def_token;
}
break;
case '^':
if (p[1] == '=') {
p += 2;
s->token.val = TOK_XOR_ASSIGN;
} else {
goto def_token;
}
break;
case '|':
if (p[1] == '=') {
p += 2;
s->token.val = TOK_OR_ASSIGN;
} else if (p[1] == '|') {
p += 2;
s->token.val = TOK_LOR;
} else {
goto def_token;
}
break;
default:
if (c >= 128) {
js_parse_error(s, "unexpected character");
}
def_token:
s->token.val = c;
p++;
break;
}
s->buf_pos = p - s->source_buf;
#if defined(DUMP_TOKEN)
dump_token(s, &s->token);
#endif
}
static BOOL is_label(JSParseState *s)
{
return (s->token.val == TOK_IDENT && s->source_buf[s->buf_pos] == ':');
}
static inline uint8_t *get_byte_code(JSParseState *s)
{
JSByteArray *arr;
arr = JS_VALUE_TO_PTR(s->byte_code);
return arr->buf;
}
static void emit_claim_size(JSParseState *s, int n)
{
JSValue val;
val = js_resize_byte_array(s->ctx, s->byte_code, s->byte_code_len + n);
if (JS_IsException(val))
js_parse_error_mem(s);
s->byte_code = val;
}
static void emit_u8(JSParseState *s, uint8_t val)
{
JSByteArray *arr;
emit_claim_size(s, 1);
arr = JS_VALUE_TO_PTR(s->byte_code);
arr->buf[s->byte_code_len++] = val;
}
static void emit_u16(JSParseState *s, uint16_t val)
{
JSByteArray *arr;
emit_claim_size(s, 2);
arr = JS_VALUE_TO_PTR(s->byte_code);
put_u16(arr->buf + s->byte_code_len, val);
s->byte_code_len += 2;
}
static void emit_u32(JSParseState *s, uint32_t val)
{
JSByteArray *arr;
emit_claim_size(s, 4);
arr = JS_VALUE_TO_PTR(s->byte_code);
put_u32(arr->buf + s->byte_code_len, val);
s->byte_code_len += 4;
}
static void pc2line_put_bits_short(JSParseState *s, int n, uint32_t bits)
{
JSFunctionBytecode *b;
JSValue val1;
JSByteArray *arr;
uint32_t index, pos;
unsigned int val;
int shift;
uint8_t *p;
index = s->pc2line_bit_len;
pos = index >> 3;
b = JS_VALUE_TO_PTR(s->cur_func);
val1 = js_resize_byte_array(s->ctx, b->pc2line, pos + 4);
if (JS_IsException(val1))
js_parse_error_mem(s);
b = JS_VALUE_TO_PTR(s->cur_func);
b->pc2line = val1;
arr = JS_VALUE_TO_PTR(val1);
p = arr->buf + pos;
val = get_be32(p);
shift = (32 - (index & 7) - n);
val &= ~(((1U << n) - 1) << shift);
val |= bits << shift;
put_be32(p, val);
s->pc2line_bit_len = index + n;
}
static void pc2line_put_bits(JSParseState *s, int n, uint32_t bits)
{
int n_max = 25;
if (unlikely(n > n_max)) {
pc2line_put_bits_short(s, n - n_max, bits >> n_max);
bits &= (1 << n_max) - 1;
n = n_max;
}
pc2line_put_bits_short(s, n, bits);
}
static void put_ugolomb(JSParseState *s, uint32_t v)
{
int n;
v++;
n = 32 - clz32(v);
if (n > 1)
pc2line_put_bits(s, n - 1, 0);
pc2line_put_bits(s, n, v);
}
static void put_sgolomb(JSParseState *s, int32_t v1)
{
uint32_t v = v1;
put_ugolomb(s, (2 * v) ^ -(v >> 31));
}
#ifdef DUMP_PC2LINE_STATS
static int pc2line_freq[256];
static int pc2line_freq_tot;
#endif
static int get_line_col_delta(int *pcol_num, const uint8_t *buf,
int pos1, int pos2)
{
int line_num, col_num, c, i;
line_num = 0;
col_num = 0;
if (pos2 >= pos1) {
line_num = get_line_col(&col_num, buf + pos1, pos2 - pos1);
} else {
line_num = get_line_col(&col_num, buf + pos2, pos1 - pos2);
line_num = -line_num;
col_num = -col_num;
if (line_num != 0) {
col_num = 0;
for(i = pos2 - 1; i >= 0; i--) {
c = buf[i];
if (c == '\n') {
break;
} else if (c < 0x80 || c >= 0xc0) {
col_num++;
}
}
}
}
*pcol_num = col_num;
return line_num;
}
static void emit_pc2line(JSParseState *s, JSSourcePos pos)
{
int line_delta, col_delta;
line_delta = get_line_col_delta(&col_delta, s->source_buf,
s->pc2line_source_pos, pos);
put_sgolomb(s, line_delta);
if (s->has_column) {
if (line_delta == 0) {
#ifdef DUMP_PC2LINE_STATS
pc2line_freq[min_int(max_int(col_delta + 128, 0), 255)]++;
pc2line_freq_tot++;
#endif
put_sgolomb(s, col_delta);
} else {
put_ugolomb(s, col_delta);
}
}
s->pc2line_source_pos = pos;
}
#ifdef DUMP_PC2LINE_STATS
void dump_pc2line(void)
{
int i;
for(i = 0; i < 256; i++) {
if (pc2line_freq[i] != 0) {
printf("%d: %d %0.2f\n",
i - 128, pc2line_freq[i],
-log2((double)pc2line_freq[i] / pc2line_freq_tot));
}
}
}
#endif
static void emit_op_pos(JSParseState *s, uint8_t op, JSSourcePos source_pos)
{
s->last_opcode_pos = s->byte_code_len;
s->last_pc2line_pos = s->pc2line_bit_len;
s->last_pc2line_source_pos = s->pc2line_source_pos;
emit_pc2line(s, source_pos);
emit_u8(s, op);
}
static void emit_op(JSParseState *s, uint8_t op)
{
emit_op_pos(s, op, s->pc2line_source_pos);
}
static void emit_op_param(JSParseState *s, uint8_t op, uint32_t param,
JSSourcePos source_pos)
{
const JSOpCode *oi;
emit_op_pos(s, op, source_pos);
oi = &opcode_info[op];
switch(oi->fmt) {
case OP_FMT_none:
break;
case OP_FMT_npop:
emit_u16(s, param);
break;
default:
assert(0);
}
}
static void emit_insert(JSParseState *s, int pos, int n)
{
JSByteArray *arr;
emit_claim_size(s, n);
arr = JS_VALUE_TO_PTR(s->byte_code);
memmove(arr->buf + pos + n, arr->buf + pos, s->byte_code_len - pos);
s->byte_code_len += n;
}
static inline int get_prev_opcode(JSParseState *s)
{
if (s->last_opcode_pos < 0) {
return OP_invalid;
} else {
uint8_t *byte_code = get_byte_code(s);
return byte_code[s->last_opcode_pos];
}
}
static BOOL js_is_live_code(JSParseState *s) {
switch (get_prev_opcode(s)) {
case OP_return:
case OP_return_undef:
case OP_throw:
case OP_goto:
case OP_ret:
return FALSE;
default:
return TRUE;
}
}
static void remove_last_op(JSParseState *s)
{
s->byte_code_len = s->last_opcode_pos;
s->pc2line_bit_len = s->last_pc2line_pos;
s->pc2line_source_pos = s->last_pc2line_source_pos;
s->last_opcode_pos = -1;
}
static void emit_push_short_int(JSParseState *s, int val)
{
if (val >= -1 && val <= 7) {
emit_op(s, OP_push_0 + val);
} else if (val == (int8_t)val) {
emit_op(s, OP_push_i8);
emit_u8(s, val);
} else if (val == (int16_t)val) {
emit_op(s, OP_push_i16);
emit_u16(s, val);
} else {
emit_op(s, OP_push_value);
emit_u32(s, JS_NewShortInt(val));
}
}
static void emit_var(JSParseState *s, int opcode, int var_idx,
JSSourcePos source_pos)
{
switch(opcode) {
case OP_get_loc:
if (var_idx < 4) {
emit_op_pos(s, OP_get_loc0 + var_idx, source_pos);
return;
} else if (var_idx < 256) {
emit_op_pos(s, OP_get_loc8, source_pos);
emit_u8(s, var_idx);
return;
}
break;
case OP_put_loc:
if (var_idx < 4) {
emit_op_pos(s, OP_put_loc0 + var_idx, source_pos);
return;
} else if (var_idx < 256) {
emit_op_pos(s, OP_put_loc8, source_pos);
emit_u8(s, var_idx);
return;
}
break;
case OP_get_arg:
if (var_idx < 4) {
emit_op_pos(s, OP_get_arg0 + var_idx, source_pos);
return;
}
break;
case OP_put_arg:
if (var_idx < 4) {
emit_op_pos(s, OP_put_arg0 + var_idx, source_pos);
return;
}
break;
}
emit_op_pos(s, opcode, source_pos);
emit_u16(s, var_idx);
}
typedef enum {
JS_PARSE_FUNC_STATEMENT,
JS_PARSE_FUNC_EXPR,
JS_PARSE_FUNC_METHOD,
} JSParseFunctionEnum;
static void js_parse_function_decl(JSParseState *s,
JSParseFunctionEnum func_type, JSValue func_name);
#define LABEL_RESOLVED_FLAG (1 << 29)
#define LABEL_OFFSET_MASK ((1 << 29) - 1)
#define LABEL_NONE JS_NewShortInt(-1)
static BOOL label_is_none(JSValue label)
{
return JS_VALUE_GET_INT(label) < 0;
}
static JSValue new_label(JSParseState *s)
{
return JS_NewShortInt(LABEL_OFFSET_MASK);
}
static void emit_label_pos(JSParseState *s, JSValue *plabel, int pos)
{
int label;
JSByteArray *arr;
int next;
label = JS_VALUE_GET_INT(*plabel);
assert(!(label & LABEL_RESOLVED_FLAG));
arr = JS_VALUE_TO_PTR(s->byte_code);
while (label != LABEL_OFFSET_MASK) {
next = get_u32(arr->buf + label);
put_u32(arr->buf + label, pos - label);
label = next;
}
*plabel = JS_NewShortInt(pos | LABEL_RESOLVED_FLAG);
}
static void emit_label(JSParseState *s, JSValue *plabel)
{
emit_label_pos(s, plabel, s->byte_code_len);
s->last_opcode_pos = -1;
}
static void emit_goto(JSParseState *s, int opcode, JSValue *plabel)
{
int label;
emit_op(s, opcode);
label = JS_VALUE_GET_INT(*plabel);
if (label & LABEL_RESOLVED_FLAG) {
emit_u32(s, (label & LABEL_OFFSET_MASK) - s->byte_code_len);
} else {
emit_u32(s, label);
*plabel = JS_NewShortInt(s->byte_code_len - 4);
}
}
static int cpool_add(JSParseState *s, JSValue val)
{
JSFunctionBytecode *b;
JSValueArray *arr;
int i;
JSValue new_cpool;
JSGCRef val_ref;
b = JS_VALUE_TO_PTR(s->cur_func);
arr = JS_VALUE_TO_PTR(b->cpool);
for(i = 0; i < s->cpool_len; i++) {
if (arr->arr[i] == val)
return i;
}
if (s->cpool_len > 65535)
js_parse_error(s, "too many constants");
JS_PUSH_VALUE(s->ctx, val);
new_cpool = js_resize_value_array(s->ctx, b->cpool, max_int(s->cpool_len + 1, 4));
JS_POP_VALUE(s->ctx, val);
if (JS_IsException(new_cpool))
js_parse_error_mem(s);
b = JS_VALUE_TO_PTR(s->cur_func);
b->cpool = new_cpool;
arr = JS_VALUE_TO_PTR(b->cpool);
arr->arr[s->cpool_len++] = val;
return s->cpool_len - 1;
}
static void js_emit_push_const(JSParseState *s, JSValue val)
{
int idx;
if (JS_IsPtr(val)
#ifdef JS_USE_SHORT_FLOAT
|| JS_IsShortFloat(val)
#endif
) {
idx = cpool_add(s, val);
emit_op(s, OP_push_const);
emit_u16(s, idx);
} else {
emit_op(s, OP_push_value);
emit_u32(s, val);
}
}
static int find_func_var(JSContext *ctx, JSValue func, JSValue name)
{
JSFunctionBytecode *b;
JSValueArray *arr;
int i;
b = JS_VALUE_TO_PTR(func);
if (b->vars == JS_NULL)
return -1;
arr = JS_VALUE_TO_PTR(b->vars);
for(i = 0; i < arr->size; i++) {
if (arr->arr[i] == name)
return i;
}
return -1;
}
static int find_var(JSParseState *s, JSValue name)
{
JSFunctionBytecode *b;
JSValueArray *arr;
int i;
b = JS_VALUE_TO_PTR(s->cur_func);
arr = JS_VALUE_TO_PTR(b->vars);
for(i = 0; i < s->local_vars_len; i++) {
if (arr->arr[i] == name)
return i;
}
return -1;
}
static JSValue get_ext_var_name(JSParseState *s, int var_idx)
{
JSFunctionBytecode *b;
JSValueArray *arr;
b = JS_VALUE_TO_PTR(s->cur_func);
arr = JS_VALUE_TO_PTR(b->ext_vars);
return arr->arr[2 * var_idx];
}
static int find_func_ext_var(JSParseState *s, JSValue func, JSValue name)
{
JSFunctionBytecode *b;
JSValueArray *arr;
int i;
b = JS_VALUE_TO_PTR(func);
arr = JS_VALUE_TO_PTR(b->ext_vars);
for(i = 0; i < b->ext_vars_len; i++) {
if (arr->arr[2 * i] == name)
return i;
}
return -1;
}
static int find_ext_var(JSParseState *s, JSValue name)
{
return find_func_ext_var(s, s->cur_func, name);
}
static int add_func_ext_var(JSParseState *s, JSValue func, JSValue name, int decl)
{
JSFunctionBytecode *b;
JSValueArray *arr;
JSValue new_ext_vars;
JSGCRef name_ref, func_ref;
b = JS_VALUE_TO_PTR(func);
if (b->ext_vars_len >= JS_MAX_LOCAL_VARS)
js_parse_error(s, "too many variable references");
JS_PUSH_VALUE(s->ctx, func);
JS_PUSH_VALUE(s->ctx, name);
new_ext_vars = js_resize_value_array(s->ctx, b->ext_vars, max_int(b->ext_vars_len + 1, 2) * 2);
JS_POP_VALUE(s->ctx, name);
JS_POP_VALUE(s->ctx, func);
if (JS_IsException(new_ext_vars))
js_parse_error_mem(s);
b = JS_VALUE_TO_PTR(func);
b->ext_vars = new_ext_vars;
arr = JS_VALUE_TO_PTR(b->ext_vars);
arr->arr[2 * b->ext_vars_len] = name;
arr->arr[2 * b->ext_vars_len + 1] = JS_NewShortInt(decl);
b->ext_vars_len++;
return b->ext_vars_len - 1;
}
static int add_ext_var(JSParseState *s, JSValue name, int decl)
{
return add_func_ext_var(s, s->cur_func, name, decl);
}
static int add_var(JSParseState *s, JSValue name)
{
JSFunctionBytecode *b;
JSValueArray *arr;
JSValue new_vars;
JSGCRef name_ref;
b = JS_VALUE_TO_PTR(s->cur_func);
if (s->local_vars_len >= JS_MAX_LOCAL_VARS)
js_parse_error(s, "too many local variables");
JS_PUSH_VALUE(s->ctx, name);
new_vars = js_resize_value_array(s->ctx, b->vars, max_int(s->local_vars_len + 1, 4));
JS_POP_VALUE(s->ctx, name);
if (JS_IsException(new_vars))
js_parse_error_mem(s);
b = JS_VALUE_TO_PTR(s->cur_func);
b->vars = new_vars;
arr = JS_VALUE_TO_PTR(b->vars);
arr->arr[s->local_vars_len++] = name;
return s->local_vars_len - 1;
}
static void get_lvalue(JSParseState *s, int *popcode,
int *pvar_idx, JSSourcePos *psource_pos, BOOL keep)
{
int opcode, var_idx;
JSSourcePos source_pos;
opcode = get_prev_opcode(s);
switch(opcode) {
case OP_get_loc0:
case OP_get_loc1:
case OP_get_loc2:
case OP_get_loc3:
var_idx = opcode - OP_get_loc0;
opcode = OP_get_loc;
break;
case OP_get_arg0:
case OP_get_arg1:
case OP_get_arg2:
case OP_get_arg3:
var_idx = opcode - OP_get_arg0;
opcode = OP_get_arg;
break;
case OP_get_loc8:
var_idx = get_u8(get_byte_code(s) + s->last_opcode_pos + 1);
opcode = OP_get_loc;
break;
case OP_get_loc:
case OP_get_arg:
case OP_get_var_ref:
case OP_get_field:
var_idx = get_u16(get_byte_code(s) + s->last_opcode_pos + 1);
break;
case OP_get_array_el:
case OP_get_length:
var_idx = -1;
break;
default:
js_parse_error(s, "invalid lvalue");
}
source_pos = s->pc2line_source_pos;
remove_last_op(s);
if (keep) {
switch(opcode) {
case OP_get_loc:
case OP_get_arg:
case OP_get_var_ref:
emit_var(s, opcode, var_idx, source_pos);
break;
case OP_get_field:
emit_op_pos(s, OP_get_field2, source_pos);
emit_u16(s, var_idx);
break;
case OP_get_length:
emit_op_pos(s, OP_get_length2, source_pos);
break;
case OP_get_array_el:
emit_op(s, OP_dup2);
emit_op_pos(s, OP_get_array_el, source_pos);
break;
default:
abort();
}
}
*popcode = opcode;
*pvar_idx = var_idx;
*psource_pos = source_pos;
}
typedef enum {
PUT_LVALUE_KEEP_TOP,
PUT_LVALUE_NOKEEP_TOP,
PUT_LVALUE_KEEP_SECOND,
PUT_LVALUE_NOKEEP_BOTTOM,
} PutLValueEnum;
static void put_lvalue(JSParseState *s, int opcode,
int var_idx, JSSourcePos source_pos,
PutLValueEnum special)
{
switch(opcode) {
case OP_get_loc:
case OP_get_arg:
case OP_get_var_ref:
if (special == PUT_LVALUE_KEEP_TOP)
emit_op(s, OP_dup);
if (opcode == OP_get_var_ref && s->is_repl)
opcode = OP_put_var_ref_nocheck;
else
opcode++;
emit_var(s, opcode, var_idx, source_pos);
break;
case OP_get_field:
case OP_get_length:
switch(special) {
case PUT_LVALUE_KEEP_TOP:
emit_op(s, OP_insert2);
break;
case PUT_LVALUE_NOKEEP_TOP:
break;
case PUT_LVALUE_NOKEEP_BOTTOM:
emit_op(s, OP_swap);
break;
default:
case PUT_LVALUE_KEEP_SECOND:
emit_op(s, OP_perm3);
break;
}
emit_op_pos(s, OP_put_field, source_pos);
if (opcode == OP_get_length) {
emit_u16(s, cpool_add(s, js_get_atom(s->ctx, JS_ATOM_length)));
} else {
emit_u16(s, var_idx);
}
break;
case OP_get_array_el:
switch(special) {
case PUT_LVALUE_KEEP_TOP:
emit_op(s, OP_insert3);
break;
case PUT_LVALUE_NOKEEP_TOP:
break;
case PUT_LVALUE_NOKEEP_BOTTOM:
emit_op(s, OP_rot3l);
break;
default:
case PUT_LVALUE_KEEP_SECOND:
emit_op(s, OP_perm4);
break;
}
emit_op_pos(s, OP_put_array_el, source_pos);
break;
default:
abort();
}
}
enum {
PARSE_PROP_FIELD,
PARSE_PROP_GET,
PARSE_PROP_SET,
PARSE_PROP_METHOD,
};
static int js_parse_property_name(JSParseState *s, JSValue *pname)
{
JSContext *ctx = s->ctx;
JSValue name;
JSGCRef name_ref;
int prop_type;
prop_type = PARSE_PROP_FIELD;
if (s->token.val == TOK_IDENT) {
int is_set;
if (s->token.value == js_get_atom(ctx, JS_ATOM_get))
is_set = 0;
else if (s->token.value == js_get_atom(ctx, JS_ATOM_set))
is_set = 1;
else
is_set = -1;
if (is_set >= 0) {
next_token(s);
if (s->token.val == ':' || s->token.val == ',' ||
s->token.val == '}' || s->token.val == '(') {
name = js_get_atom(ctx, is_set ? JS_ATOM_set : JS_ATOM_get);
goto done;
}
prop_type = PARSE_PROP_GET + is_set;
}
}
if (s->token.val == TOK_IDENT || s->token.val >= TOK_FIRST_KEYWORD) {
name = s->token.value;
} else if (s->token.val == TOK_STRING) {
name = s->token.value;
} else if (s->token.val == TOK_NUMBER) {
name = JS_NewFloat64(s->ctx, s->token.u.d);
if (JS_IsException(name))
js_parse_error_mem(s);
} else {
js_parse_error(s, "invalid property name");
}
name = JS_ToPropertyKey(s->ctx, name);
if (JS_IsException(name))
js_parse_error_mem(s);
JS_PUSH_VALUE(ctx, name);
next_token(s);
JS_POP_VALUE(ctx, name);
done:
if (prop_type == PARSE_PROP_FIELD && s->token.val == '(')
prop_type = PARSE_PROP_METHOD;
*pname = name;
return prop_type;
}
#define PF_NO_IN (1 << 0)
#define PF_DROP (1 << 1)
#define PF_ACCEPT_LPAREN (1 << 2)
#define PF_LEVEL_SHIFT 4
#define PF_LEVEL_MASK (0xf << PF_LEVEL_SHIFT)
typedef enum {
PARSE_FUNC_js_parse_expr_comma,
PARSE_FUNC_js_parse_assign_expr,
PARSE_FUNC_js_parse_cond_expr,
PARSE_FUNC_js_parse_logical_and_or,
PARSE_FUNC_js_parse_expr_binary,
PARSE_FUNC_js_parse_unary,
PARSE_FUNC_js_parse_postfix_expr,
PARSE_FUNC_js_parse_statement,
PARSE_FUNC_js_parse_block,
PARSE_FUNC_js_parse_json_value,
PARSE_FUNC_re_parse_alternative,
PARSE_FUNC_re_parse_disjunction,
} ParseExprFuncEnum;
typedef int JSParseFunc(JSParseState *s, int state, int param);
#define PARSE_STATE_INIT 0xfe
#define PARSE_STATE_RET 0xff
static JSValue parse_stack_alloc(JSParseState *s, JSValue val)
{
JSGCRef val_ref;
JS_PUSH_VALUE(s->ctx, val);
if (JS_StackCheck(s->ctx, 1))
js_parse_error_stack_overflow(s);
JS_POP_VALUE(s->ctx, val);
return val;
}
static void js_parse_push_val(JSParseState *s, JSValue val)
{
JSContext *ctx = s->ctx;
if (unlikely(ctx->sp <= ctx->stack_bottom)) {
val = parse_stack_alloc(s, val);
}
*--(ctx->sp) = val;
}
static JSValue js_parse_pop_val(JSParseState *s)
{
JSContext *ctx = s->ctx;
JSValue val;
val = *(ctx->sp)++;
if (unlikely(ctx->sp - JS_STACK_SLACK > ctx->stack_bottom))
ctx->stack_bottom = ctx->sp - JS_STACK_SLACK;
return val;
}
#define PARSE_PUSH_VAL(s, v) js_parse_push_val(s, v)
#define PARSE_POP_VAL(s, v) v = js_parse_pop_val(s)
#define PARSE_PUSH_INT(s, v) js_parse_push_val(s, JS_NewShortInt(v))
#define PARSE_POP_INT(s, v) v = JS_VALUE_GET_INT(js_parse_pop_val(s))
#define PARSE_START1() \
switch(state) {\
case PARSE_STATE_INIT: break;\
default: abort();\
case 0: goto parse_state0;\
}
#define PARSE_START2() \
switch(state) {\
case PARSE_STATE_INIT: break;\
default: abort();\
case 0: goto parse_state0;\
case 1: goto parse_state1;\
}
#define PARSE_START3() \
switch(state) {\
case PARSE_STATE_INIT: break;\
default: abort();\
case 0: goto parse_state0;\
case 1: goto parse_state1;\
case 2: goto parse_state2;\
}
#define PARSE_START7() \
switch(state) {\
case PARSE_STATE_INIT: break;\
default: abort();\
case 0: goto parse_state0;\
case 1: goto parse_state1;\
case 2: goto parse_state2;\
case 3: goto parse_state3; \
case 4: goto parse_state4;\
case 5: goto parse_state5;\
case 6: goto parse_state6;\
}
#define PARSE_START12() \
switch(state) {\
case PARSE_STATE_INIT: break;\
default: abort();\
case 0: goto parse_state0;\
case 1: goto parse_state1;\
case 2: goto parse_state2;\
case 3: goto parse_state3; \
case 4: goto parse_state4;\
case 5: goto parse_state5;\
case 6: goto parse_state6;\
case 7: goto parse_state7; \
case 8: goto parse_state8;\
case 9: goto parse_state9;\
case 10: goto parse_state10;\
case 11: goto parse_state11;\
}
#define PARSE_CALL(s, cur_state, func, param) return (cur_state | (PARSE_FUNC_ ## func << 8) | ((param) << 16)); parse_state ## cur_state : ;
#define PARSE_CALL_SAVE1(s, cur_state, func, param, var1) \
PARSE_PUSH_INT(s, var1); \
PARSE_CALL(s, cur_state, func, param); \
PARSE_POP_INT(s, var1);
#define PARSE_CALL_SAVE2(s, cur_state, func, param, var1, var2) \
PARSE_PUSH_INT(s, var1); \
PARSE_PUSH_INT(s, var2); \
PARSE_CALL(s, cur_state, func, param); \
PARSE_POP_INT(s, var2); \
PARSE_POP_INT(s, var1);
#define PARSE_CALL_SAVE3(s, cur_state, func, param, var1, var2, var3) \
PARSE_PUSH_INT(s, var1); \
PARSE_PUSH_INT(s, var2); \
PARSE_PUSH_INT(s, var3); \
PARSE_CALL(s, cur_state, func, param); \
PARSE_POP_INT(s, var3); \
PARSE_POP_INT(s, var2); \
PARSE_POP_INT(s, var1);
#define PARSE_CALL_SAVE4(s, cur_state, func, param, var1, var2, var3, var4) \
PARSE_PUSH_INT(s, var1); \
PARSE_PUSH_INT(s, var2); \
PARSE_PUSH_INT(s, var3); \
PARSE_PUSH_INT(s, var4); \
PARSE_CALL(s, cur_state, func, param); \
PARSE_POP_INT(s, var4); \
PARSE_POP_INT(s, var3); \
PARSE_POP_INT(s, var2); \
PARSE_POP_INT(s, var1);
#define PARSE_CALL_SAVE5(s, cur_state, func, param, var1, var2, var3, var4, var5) \
PARSE_PUSH_INT(s, var1); \
PARSE_PUSH_INT(s, var2); \
PARSE_PUSH_INT(s, var3); \
PARSE_PUSH_INT(s, var4); \
PARSE_PUSH_INT(s, var5); \
PARSE_CALL(s, cur_state, func, param); \
PARSE_POP_INT(s, var5); \
PARSE_POP_INT(s, var4); \
PARSE_POP_INT(s, var3); \
PARSE_POP_INT(s, var2); \
PARSE_POP_INT(s, var1);
#define PARSE_CALL_SAVE6(s, cur_state, func, param, var1, var2, var3, var4, var5, var6) \
PARSE_PUSH_INT(s, var1); \
PARSE_PUSH_INT(s, var2); \
PARSE_PUSH_INT(s, var3); \
PARSE_PUSH_INT(s, var4); \
PARSE_PUSH_INT(s, var5); \
PARSE_PUSH_INT(s, var6); \
PARSE_CALL(s, cur_state, func, param); \
PARSE_POP_INT(s, var6); \
PARSE_POP_INT(s, var5); \
PARSE_POP_INT(s, var4); \
PARSE_POP_INT(s, var3); \
PARSE_POP_INT(s, var2); \
PARSE_POP_INT(s, var1);
static JSParseFunc *parse_func_table[];
static void js_parse_call(JSParseState *s, ParseExprFuncEnum func_idx,
int param)
{
JSContext *ctx = s->ctx;
int ret, state;
JSValue *stack_top;
stack_top = ctx->sp;
state = PARSE_STATE_INIT;
for(;;) {
ret = parse_func_table[func_idx](s, state, param);
state = ret & 0xff;
if (state == PARSE_STATE_RET) {
if (ctx->sp == stack_top)
break;
PARSE_POP_INT(s, ret);
state = ret & 0xff;
func_idx = (ret >> 8) & 0xff;
param = -1;
} else {
PARSE_PUSH_INT(s, state | (func_idx << 8));
state = PARSE_STATE_INIT;
func_idx = (ret >> 8) & 0xff;
param = (ret >> 16);
}
}
}
static BOOL may_drop_result(JSParseState *s, int parse_flags)
{
return ((parse_flags & PF_DROP) &&
(s->token.val == ';' || s->token.val == ')' ||
s->token.val == ','));
}
static void js_emit_push_number(JSParseState *s, double d)
{
JSValue val;
val = JS_NewFloat64(s->ctx, d);
if (JS_IsException(val))
js_parse_error_mem(s);
if (JS_IsInt(val)) {
emit_push_short_int(s, JS_VALUE_GET_INT(val));
} else {
js_emit_push_const(s, val);
}
}
static int js_parse_postfix_expr(JSParseState *s, int state, int parse_flags)
{
BOOL is_new = FALSE;
PARSE_START7();
switch(s->token.val) {
case TOK_NUMBER:
js_emit_push_number(s, s->token.u.d);
next_token(s);
break;
case TOK_STRING:
{
js_emit_push_const(s, s->token.value);
next_token(s);
}
break;
case TOK_REGEXP:
{
uint32_t saved_buf_pos, saved_buf_len;
uint32_t saved_byte_code_len;
JSValue byte_code;
JSFunctionBytecode *b;
js_emit_push_const(s, s->token.value);
saved_buf_pos = s->buf_pos;
saved_buf_len = s->buf_len;
b = JS_VALUE_TO_PTR(s->cur_func);
b->byte_code = s->byte_code;
saved_byte_code_len = s->byte_code_len;
s->buf_pos = s->token.source_pos + 1;
s->buf_len = s->token.u.regexp.re_end_pos;
byte_code = js_parse_regexp(s, s->token.u.regexp.re_flags);
s->buf_pos = saved_buf_pos;
s->buf_len = saved_buf_len;
b = JS_VALUE_TO_PTR(s->cur_func);
s->byte_code = b->byte_code;
s->byte_code_len = saved_byte_code_len;
js_emit_push_const(s, byte_code);
emit_op(s, OP_regexp);
next_token(s);
}
break;
case '(':
next_token(s);
PARSE_CALL_SAVE1(s, 0, js_parse_expr_comma, 0, parse_flags);
js_parse_expect(s, ')');
break;
case TOK_FUNCTION:
js_parse_function_decl(s, JS_PARSE_FUNC_EXPR, JS_NULL);
break;
case TOK_NULL:
emit_op(s, OP_null);
next_token(s);
break;
case TOK_THIS:
emit_op(s, OP_push_this);
next_token(s);
break;
case TOK_FALSE:
case TOK_TRUE:
emit_op(s, OP_push_false + (s->token.val == TOK_TRUE));
next_token(s);
break;
case TOK_IDENT:
{
JSFunctionBytecode *b;
JSValue name;
int var_idx, arg_count, opcode;
b = JS_VALUE_TO_PTR(s->cur_func);
arg_count = b->arg_count;
name = s->token.value;
var_idx = find_var(s, name);
if (var_idx >= 0) {
if (var_idx < arg_count) {
opcode = OP_get_arg;
} else {
opcode = OP_get_loc;
var_idx -= arg_count;
}
} else {
var_idx = find_ext_var(s, name);
if (var_idx < 0) {
var_idx = add_ext_var(s, name, (JS_VARREF_KIND_GLOBAL << 16) | 0);
}
opcode = OP_get_var_ref;
}
emit_var(s, opcode, var_idx, s->token.source_pos);
next_token(s);
}
break;
case '{':
{
JSValue name;
int prop_idx, prop_type, count_pos;
BOOL has_proto;
next_token(s);
emit_op(s, OP_object);
count_pos = s->byte_code_len;
emit_u16(s, 0);
has_proto = FALSE;
while (s->token.val != '}') {
prop_type = js_parse_property_name(s, &name);
if (prop_type == PARSE_PROP_FIELD &&
name == js_get_atom(s->ctx, JS_ATOM___proto__)) {
if (has_proto)
js_parse_error(s, "duplicate __proto__ property name");
has_proto = TRUE;
prop_idx = -1;
} else {
uint8_t *byte_code;
int count;
prop_idx = cpool_add(s, name);
byte_code = get_byte_code(s);
count = get_u16(byte_code + count_pos);
put_u16(byte_code + count_pos, min_int(count + 1, 0xffff));
}
if (prop_type == PARSE_PROP_FIELD) {
js_parse_expect(s, ':');
PARSE_CALL_SAVE4(s, 1, js_parse_assign_expr, 0, prop_idx, parse_flags, has_proto, count_pos);
if (prop_idx >= 0) {
emit_op(s, OP_define_field);
emit_u16(s, prop_idx);
} else {
emit_op(s, OP_set_proto);
}
} else {
js_parse_function_decl(s, JS_PARSE_FUNC_METHOD, name);
if (prop_type == PARSE_PROP_METHOD)
emit_op(s, OP_define_field);
else if (prop_type == PARSE_PROP_GET)
emit_op(s, OP_define_getter);
else
emit_op(s, OP_define_setter);
emit_u16(s, prop_idx);
}
if (s->token.val != ',')
break;
next_token(s);
}
js_parse_expect(s, '}');
}
break;
case '[':
{
uint32_t idx;
next_token(s);
idx = 0;
while (s->token.val != ']' && idx < 32) {
PARSE_CALL_SAVE2(s, 2, js_parse_assign_expr, 0, idx, parse_flags);
idx++;
if (s->token.val == ',') {
next_token(s);
} else if (s->token.val != ']') {
goto done;
}
}
emit_op_param(s, OP_array_from, idx, s->pc2line_source_pos);
while (s->token.val != ']') {
if (idx >= JS_SHORTINT_MAX)
js_parse_error(s, "too many elements");
emit_op(s, OP_dup);
emit_push_short_int(s, idx);
PARSE_CALL_SAVE2(s, 3, js_parse_assign_expr, 0, idx, parse_flags);
emit_op(s, OP_put_array_el);
idx++;
if (s->token.val == ',') {
next_token(s);
}
}
done:
js_parse_expect(s, ']');
}
break;
case TOK_NEW:
next_token(s);
if (s->token.val == '.') {
next_token(s);
if (s->token.val != TOK_IDENT ||
s->token.value != js_get_atom(s->ctx, JS_ATOM_target)) {
js_parse_error(s, "expecting target");
}
next_token(s);
emit_op(s, OP_new_target);
} else {
PARSE_CALL_SAVE1(s, 4, js_parse_postfix_expr, 0, parse_flags);
if (s->token.val != '(') {
emit_op_param(s, OP_call_constructor, 0, s->token.source_pos);
} else {
is_new = TRUE;
break;
}
}
break;
default:
js_parse_error(s, "unexpected character in expression");
}
for(;;) {
if (s->token.val == '(' && (parse_flags & PF_ACCEPT_LPAREN)) {
int opcode, arg_count;
uint8_t *byte_code;
JSSourcePos op_source_pos;
op_source_pos = s->token.source_pos;
next_token(s);
if (!is_new) {
opcode = get_prev_opcode(s);
byte_code = get_byte_code(s);
switch(opcode) {
case OP_get_field:
byte_code[s->last_opcode_pos] = OP_get_field2;
break;
case OP_get_length:
byte_code[s->last_opcode_pos] = OP_get_length2;
break;
case OP_get_array_el:
byte_code[s->last_opcode_pos] = OP_get_array_el2;
break;
case OP_get_var_ref:
{
int var_idx = get_u16(byte_code + s->last_opcode_pos + 1);
if (get_ext_var_name(s, var_idx) == js_get_atom(s->ctx, JS_ATOM_eval)) {
js_parse_error(s, "direct eval is not supported. Use (1,eval) instead for indirect eval");
}
}
default:
opcode = OP_invalid;
break;
}
} else {
opcode = OP_invalid;
}
arg_count = 0;
if (s->token.val != ')') {
for(;;) {
if (arg_count >= JS_MAX_ARGC)
js_parse_error(s, "too many call arguments");
arg_count++;
PARSE_CALL_SAVE5(s, 5, js_parse_assign_expr, 0,
parse_flags, arg_count, opcode, is_new, op_source_pos);
if (s->token.val == ')')
break;
js_parse_expect(s, ',');
}
}
next_token(s);
if (opcode == OP_get_field ||
opcode == OP_get_length ||
opcode == OP_get_array_el) {
emit_op_param(s, OP_call_method, arg_count, op_source_pos);
} else {
if (is_new) {
emit_op_param(s, OP_call_constructor, arg_count, op_source_pos);
} else {
emit_op_param(s, OP_call, arg_count, op_source_pos);
}
}
is_new = FALSE;
} else if (s->token.val == '.') {
JSSourcePos op_source_pos;
int prop_idx;
op_source_pos = s->token.source_pos;
next_token(s);
if (!(s->token.val == TOK_IDENT || s->token.val >= TOK_FIRST_KEYWORD)) {
js_parse_error(s, "expecting field name");
}
if (s->token.value == js_get_atom(s->ctx, JS_ATOM_NaN) ||
s->token.value == js_get_atom(s->ctx, JS_ATOM_Infinity)) {
js_emit_push_const(s, s->token.value);
emit_op_pos(s, OP_get_array_el, op_source_pos);
} else if (s->token.value == js_get_atom(s->ctx, JS_ATOM_length)) {
emit_op_pos(s, OP_get_length, op_source_pos);
} else {
prop_idx = cpool_add(s, s->token.value);
emit_op_pos(s, OP_get_field, op_source_pos);
emit_u16(s, prop_idx);
}
next_token(s);
} else if (s->token.val == '[') {
JSSourcePos op_source_pos;
op_source_pos = s->token.source_pos;
next_token(s);
PARSE_CALL_SAVE3(s, 6, js_parse_expr_comma, 0,
parse_flags, is_new, op_source_pos);
js_parse_expect(s, ']');
emit_op_pos(s, OP_get_array_el, op_source_pos);
} else if (!s->got_lf && (s->token.val == TOK_DEC || s->token.val == TOK_INC)) {
int opcode, op, var_idx;
JSSourcePos op_source_pos, source_pos;
op = s->token.val;
op_source_pos = s->token.source_pos;
next_token(s);
get_lvalue(s, &opcode, &var_idx, &source_pos, TRUE);
if (may_drop_result(s, parse_flags)) {
s->dropped_result = TRUE;
emit_op_pos(s, OP_dec + op - TOK_DEC, op_source_pos);
put_lvalue(s, opcode, var_idx, source_pos, PUT_LVALUE_NOKEEP_TOP);
} else {
emit_op_pos(s, OP_post_dec + op - TOK_DEC, op_source_pos);
put_lvalue(s, opcode, var_idx, source_pos, PUT_LVALUE_KEEP_SECOND);
}
} else {
break;
}
}
return PARSE_STATE_RET;
}
static void js_emit_delete(JSParseState *s)
{
int opcode;
opcode = get_prev_opcode(s);
switch(opcode) {
case OP_get_field:
{
JSByteArray *byte_code;
int prop_idx;
byte_code = JS_VALUE_TO_PTR(s->byte_code);
prop_idx = get_u16(byte_code->buf + s->last_opcode_pos + 1);
remove_last_op(s);
emit_op(s, OP_push_const);
emit_u16(s, prop_idx);
}
break;
case OP_get_length:
remove_last_op(s);
js_emit_push_const(s, js_get_atom(s->ctx, JS_ATOM_length));
break;
case OP_get_array_el:
remove_last_op(s);
break;
default:
js_parse_error(s, "invalid lvalue for delete");
}
emit_op(s, OP_delete);
}
static int js_parse_unary(JSParseState *s, int state, int parse_flags)
{
PARSE_START7();
switch(s->token.val) {
case '+':
case '-':
case '!':
case '~':
{
int op;
JSSourcePos op_source_pos;
op = s->token.val;
op_source_pos = s->token.source_pos;
next_token(s);
if (s->token.val == TOK_NUMBER && (op == '-' || op == '+')) {
double d = s->token.u.d;
if (op == '-')
d = -d;
js_emit_push_number(s, d);
next_token(s);
} else {
PARSE_CALL_SAVE2(s, 0, js_parse_unary, 0, op, op_source_pos);
switch(op) {
case '-':
emit_op_pos(s, OP_neg, op_source_pos);
break;
case '+':
emit_op_pos(s, OP_plus, op_source_pos);
break;
case '!':
emit_op_pos(s, OP_lnot, op_source_pos);
break;
case '~':
emit_op_pos(s, OP_not, op_source_pos);
break;
default:
abort();
}
}
}
break;
case TOK_VOID:
next_token(s);
PARSE_CALL(s, 1, js_parse_unary, 0);
emit_op(s, OP_drop);
emit_op(s, OP_undefined);
break;
case TOK_DEC:
case TOK_INC:
{
int opcode, op, var_idx;
PutLValueEnum special;
JSSourcePos op_source_pos, source_pos;
op = s->token.val;
op_source_pos = s->token.source_pos;
next_token(s);
PARSE_CALL_SAVE3(s, 2, js_parse_unary, 0, op, parse_flags, op_source_pos);
get_lvalue(s, &opcode, &var_idx, &source_pos, TRUE);
emit_op_pos(s, OP_dec + op - TOK_DEC, op_source_pos);
if (may_drop_result(s, parse_flags)) {
special = PUT_LVALUE_NOKEEP_TOP;
s->dropped_result = TRUE;
} else {
special = PUT_LVALUE_KEEP_TOP;
}
put_lvalue(s, opcode, var_idx, source_pos, special);
}
break;
case TOK_TYPEOF:
{
next_token(s);
PARSE_CALL(s, 3, js_parse_unary, 0);
if (get_prev_opcode(s) == OP_get_var_ref) {
uint8_t *byte_code = get_byte_code(s);
byte_code[s->last_opcode_pos] = OP_get_var_ref_nocheck;
}
emit_op(s, OP_typeof);
}
break;
case TOK_DELETE:
next_token(s);
PARSE_CALL(s, 4, js_parse_unary, 0);
js_emit_delete(s);
break;
default:
PARSE_CALL(s, 5, js_parse_postfix_expr, parse_flags | PF_ACCEPT_LPAREN);
if (s->token.val == TOK_POW) {
JSSourcePos op_source_pos;
op_source_pos = s->token.source_pos;
next_token(s);
PARSE_CALL_SAVE1(s, 6, js_parse_unary, 0, op_source_pos);
emit_op_pos(s, OP_pow, op_source_pos);
}
break;
}
return PARSE_STATE_RET;
}
static int js_parse_expr_binary(JSParseState *s, int state, int parse_flags)
{
int op, opcode, level;
JSSourcePos op_source_pos;
PARSE_START3();
level = (parse_flags & PF_LEVEL_MASK) >> PF_LEVEL_SHIFT;
if (level == 0) {
PARSE_CALL(s, 0, js_parse_unary, parse_flags);
return PARSE_STATE_RET;
}
PARSE_CALL_SAVE1(s, 1, js_parse_expr_binary, parse_flags - (1 << PF_LEVEL_SHIFT), parse_flags);
parse_flags &= ~PF_DROP;
for(;;) {
op = s->token.val;
op_source_pos = s->token.source_pos;
level = (parse_flags & PF_LEVEL_MASK) >> PF_LEVEL_SHIFT;
switch(level) {
case 1:
switch(op) {
case '*':
opcode = OP_mul;
break;
case '/':
opcode = OP_div;
break;
case '%':
opcode = OP_mod;
break;
default:
return PARSE_STATE_RET;
}
break;
case 2:
switch(op) {
case '+':
opcode = OP_add;
break;
case '-':
opcode = OP_sub;
break;
default:
return PARSE_STATE_RET;
}
break;
case 3:
switch(op) {
case TOK_SHL:
opcode = OP_shl;
break;
case TOK_SAR:
opcode = OP_sar;
break;
case TOK_SHR:
opcode = OP_shr;
break;
default:
return PARSE_STATE_RET;
}
break;
case 4:
switch(op) {
case '<':
opcode = OP_lt;
break;
case '>':
opcode = OP_gt;
break;
case TOK_LTE:
opcode = OP_lte;
break;
case TOK_GTE:
opcode = OP_gte;
break;
case TOK_INSTANCEOF:
opcode = OP_instanceof;
break;
case TOK_IN:
if (!(parse_flags & PF_NO_IN)) {
opcode = OP_in;
} else {
return PARSE_STATE_RET;
}
break;
default:
return PARSE_STATE_RET;
}
break;
case 5:
switch(op) {
case TOK_EQ:
opcode = OP_eq;
break;
case TOK_NEQ:
opcode = OP_neq;
break;
case TOK_STRICT_EQ:
opcode = OP_strict_eq;
break;
case TOK_STRICT_NEQ:
opcode = OP_strict_neq;
break;
default:
return PARSE_STATE_RET;
}
break;
case 6:
switch(op) {
case '&':
opcode = OP_and;
break;
default:
return PARSE_STATE_RET;
}
break;
case 7:
switch(op) {
case '^':
opcode = OP_xor;
break;
default:
return PARSE_STATE_RET;
}
break;
case 8:
switch(op) {
case '|':
opcode = OP_or;
break;
default:
return PARSE_STATE_RET;
}
break;
default:
abort();
}
next_token(s);
PARSE_CALL_SAVE3(s, 2, js_parse_expr_binary, parse_flags - (1 << PF_LEVEL_SHIFT), parse_flags, opcode, op_source_pos);
emit_op_pos(s, opcode, op_source_pos);
}
return PARSE_STATE_RET;
}
static int js_parse_logical_and_or(JSParseState *s, int state, int parse_flags)
{
JSValue label1;
int level, op;
PARSE_START3();
level = (parse_flags & PF_LEVEL_MASK) >> PF_LEVEL_SHIFT;
if (level == 0) {
PARSE_CALL(s, 0, js_parse_expr_binary, (parse_flags & ~PF_LEVEL_MASK) | (8 << PF_LEVEL_SHIFT));
return PARSE_STATE_RET;
}
PARSE_CALL_SAVE1(s, 1, js_parse_logical_and_or, parse_flags - (1 << PF_LEVEL_SHIFT), parse_flags);
level = (parse_flags & PF_LEVEL_MASK) >> PF_LEVEL_SHIFT;
if (level == 1)
op = TOK_LAND;
else
op = TOK_LOR;
parse_flags &= ~PF_DROP;
if (s->token.val == op) {
label1 = new_label(s);
for(;;) {
next_token(s);
emit_op(s, OP_dup);
emit_goto(s, op == TOK_LAND ? OP_if_false : OP_if_true, &label1);
emit_op(s, OP_drop);
PARSE_PUSH_VAL(s, label1);
PARSE_CALL_SAVE1(s, 2, js_parse_logical_and_or, parse_flags - (1 << PF_LEVEL_SHIFT), parse_flags);
PARSE_POP_VAL(s, label1);
level = (parse_flags & PF_LEVEL_MASK) >> PF_LEVEL_SHIFT;
if (level == 1)
op = TOK_LAND;
else
op = TOK_LOR;
if (s->token.val != op)
break;
}
emit_label(s, &label1);
}
return PARSE_STATE_RET;
}
static int js_parse_cond_expr(JSParseState *s, int state, int parse_flags)
{
JSValue label1, label2;
PARSE_START3();
PARSE_CALL_SAVE1(s, 2, js_parse_logical_and_or, parse_flags | (2 << PF_LEVEL_SHIFT), parse_flags);
parse_flags &= ~PF_DROP;
if (s->token.val == '?') {
next_token(s);
label1 = new_label(s);
emit_goto(s, OP_if_false, &label1);
PARSE_PUSH_VAL(s, label1);
PARSE_CALL_SAVE1(s, 0, js_parse_assign_expr, parse_flags,
parse_flags);
PARSE_POP_VAL(s, label1);
label2 = new_label(s);
emit_goto(s, OP_goto, &label2);
js_parse_expect(s, ':');
emit_label(s, &label1);
PARSE_PUSH_VAL(s, label2);
PARSE_CALL_SAVE1(s, 1, js_parse_assign_expr, parse_flags,
parse_flags);
PARSE_POP_VAL(s, label2);
emit_label(s, &label2);
}
return PARSE_STATE_RET;
}
static int js_parse_assign_expr(JSParseState *s, int state, int parse_flags)
{
int opcode, op, var_idx;
PutLValueEnum special;
JSSourcePos op_source_pos, source_pos;
PARSE_START2();
PARSE_CALL_SAVE1(s, 1, js_parse_cond_expr, parse_flags, parse_flags);
op = s->token.val;
if (op == '=' || (op >= TOK_MUL_ASSIGN && op <= TOK_OR_ASSIGN)) {
op_source_pos = s->token.source_pos;
next_token(s);
get_lvalue(s, &opcode, &var_idx, &source_pos, (op != '='));
PARSE_CALL_SAVE6(s, 0, js_parse_assign_expr, parse_flags & ~PF_DROP,
op, opcode, var_idx, parse_flags,
op_source_pos, source_pos);
if (op != '=') {
static const uint8_t assign_opcodes[] = {
OP_mul, OP_div, OP_mod, OP_add, OP_sub,
OP_shl, OP_sar, OP_shr, OP_and, OP_xor, OP_or,
OP_pow,
};
emit_op_pos(s, assign_opcodes[op - TOK_MUL_ASSIGN], op_source_pos);
}
if (may_drop_result(s, parse_flags)) {
special = PUT_LVALUE_NOKEEP_TOP;
s->dropped_result = TRUE;
} else {
special = PUT_LVALUE_KEEP_TOP;
}
put_lvalue(s, opcode, var_idx, source_pos, special);
}
return PARSE_STATE_RET;
}
static int js_parse_expr_comma(JSParseState *s, int state, int parse_flags)
{
BOOL comma = FALSE;
PARSE_START1();
for(;;) {
s->dropped_result = FALSE;
PARSE_CALL_SAVE2(s, 0, js_parse_assign_expr, parse_flags,
comma, parse_flags);
if (comma) {
s->last_opcode_pos = -1;
}
if (s->token.val != ',')
break;
comma = TRUE;
if (!s->dropped_result)
emit_op(s, OP_drop);
next_token(s);
}
if ((parse_flags & PF_DROP) && !s->dropped_result) {
emit_op(s, OP_drop);
}
return PARSE_STATE_RET;
}
static void js_parse_assign_expr2(JSParseState *s, int parse_flags)
{
js_parse_call(s, PARSE_FUNC_js_parse_assign_expr, parse_flags);
}
static void js_parse_expr2(JSParseState *s, int parse_flags)
{
js_parse_call(s, PARSE_FUNC_js_parse_expr_comma, parse_flags);
}
static void js_parse_expr(JSParseState *s)
{
js_parse_expr2(s, 0);
}
static void js_parse_expr_paren(JSParseState *s)
{
js_parse_expect(s, '(');
js_parse_expr(s);
js_parse_expect(s, ')');
}
static BlockEnv *push_break_entry(JSParseState *s, JSValue label_name,
JSValue label_break, JSValue label_cont,
int drop_count)
{
JSContext *ctx = s->ctx;
JSGCRef label_name_ref;
int ret, block_env_len;
BlockEnv *be;
block_env_len = sizeof(BlockEnv) / sizeof(JSValue);
JS_PUSH_VALUE(ctx, label_name);
ret = JS_StackCheck(ctx, block_env_len);
JS_POP_VALUE(ctx, label_name);
if (ret)
js_parse_error_stack_overflow(s);
ctx->sp -= block_env_len;
be = (BlockEnv *)ctx->sp;
be->prev = s->top_break;
s->top_break = SP_TO_VALUE(ctx, be);
be->label_name = label_name;
be->label_break = label_break;
be->label_cont = label_cont;
be->label_finally = LABEL_NONE;
be->drop_count = JS_NewShortInt(drop_count);
return be;
}
static void pop_break_entry(JSParseState *s)
{
JSContext *ctx = s->ctx;
BlockEnv *be;
be = VALUE_TO_SP(ctx, s->top_break);
s->top_break = be->prev;
ctx->sp += sizeof(BlockEnv) / sizeof(JSValue);
ctx->stack_bottom = ctx->sp;
}
static void emit_return(JSParseState *s, BOOL hasval, JSSourcePos source_pos)
{
JSValue top_val;
BlockEnv *top;
int i, drop_count;
drop_count = 0;
top_val = s->top_break;
while (!JS_IsNull(top_val)) {
top = VALUE_TO_SP(s->ctx, top_val);
drop_count += JS_VALUE_GET_INT(top->drop_count);
if (!label_is_none(top->label_finally)) {
if (!hasval) {
emit_op(s, OP_undefined);
hasval = TRUE;
}
for(i = 0; i < drop_count; i++)
emit_op(s, OP_nip);
drop_count = 0;
emit_goto(s, OP_gosub, &top->label_finally);
}
top_val = top->prev;
}
emit_op_pos(s, hasval ? OP_return : OP_return_undef, source_pos);
}
static void emit_break(JSParseState *s, JSValue label_name, int is_cont)
{
JSValue top_val;
BlockEnv *top;
int i;
JSValue *plabel;
JSGCRef label_name_ref;
BOOL is_labelled_stmt;
top_val = s->top_break;
while (!JS_IsNull(top_val)) {
top = VALUE_TO_SP(s->ctx, top_val);
is_labelled_stmt = (top->label_cont == LABEL_NONE &&
JS_VALUE_GET_INT(top->drop_count) == 0);
if ((label_name == JS_NULL && !is_labelled_stmt) ||
top->label_name == label_name) {
if (is_cont)
plabel = &top->label_cont;
else
plabel = &top->label_break;
if (!label_is_none(*plabel)) {
emit_goto(s, OP_goto, plabel);
return;
}
}
JS_PUSH_VALUE(s->ctx, label_name);
for(i = 0; i < JS_VALUE_GET_INT(top->drop_count); i++)
emit_op(s, OP_drop);
if (!label_is_none(top->label_finally)) {
emit_op(s, OP_undefined);
emit_goto(s, OP_gosub, &top->label_finally);
emit_op(s, OP_drop);
}
JS_POP_VALUE(s->ctx, label_name);
top_val = top->prev;
}
if (label_name == JS_NULL) {
if (is_cont)
js_parse_error(s, "continue must be inside loop");
else
js_parse_error(s, "break must be inside loop or switch");
} else {
js_parse_error(s, "break/continue label not found");
}
}
static int define_var(JSParseState *s, JSVarRefKindEnum *pvar_kind, JSValue name)
{
JSVarRefKindEnum var_kind;
int var_idx;
if (s->is_eval) {
var_idx = find_ext_var(s, name);
if (var_idx < 0) {
var_idx = add_ext_var(s, name, (JS_VARREF_KIND_GLOBAL << 16) | 1);
} else {
JSFunctionBytecode *b = JS_VALUE_TO_PTR(s->cur_func);
JSValueArray *arr = JS_VALUE_TO_PTR(b->ext_vars);
arr->arr[2 * var_idx + 1] = JS_NewShortInt((JS_VARREF_KIND_GLOBAL << 16) | 1);
}
var_kind = JS_VARREF_KIND_VAR_REF;
} else {
JSFunctionBytecode *b;
int arg_count;
b = JS_VALUE_TO_PTR(s->cur_func);
arg_count = b->arg_count;
var_idx = find_var(s, name);
if (var_idx >= 0) {
if (var_idx < arg_count) {
var_kind = JS_VARREF_KIND_ARG;
} else {
var_kind = JS_VARREF_KIND_VAR;
var_idx -= arg_count;
}
} else {
var_idx = add_var(s, name);
var_kind = JS_VARREF_KIND_VAR;
var_idx -= arg_count;
}
}
*pvar_kind = var_kind;
return var_idx;
}
static void put_var(JSParseState *s, JSVarRefKindEnum var_kind, int var_idx, JSSourcePos source_pos)
{
int opcode;
if (var_kind == JS_VARREF_KIND_ARG)
opcode = OP_put_arg;
else if (var_kind == JS_VARREF_KIND_VAR)
opcode = OP_put_loc;
else
opcode = OP_put_var_ref_nocheck;
emit_var(s, opcode, var_idx, source_pos);
}
static void js_parse_var(JSParseState *s, BOOL in_accepted)
{
JSVarRefKindEnum var_kind;
int var_idx;
JSSourcePos ident_source_pos;
for(;;) {
ident_source_pos = s->token.source_pos;
if (s->token.val != TOK_IDENT)
js_parse_error(s, "variable name expected");
if (s->token.value == js_get_atom(s->ctx, JS_ATOM_arguments))
js_parse_error(s, "invalid variable name");
var_idx = define_var(s, &var_kind, s->token.value);
next_token(s);
if (s->token.val == '=') {
next_token(s);
js_parse_assign_expr2(s, in_accepted ? 0 : PF_NO_IN);
put_var(s, var_kind, var_idx, ident_source_pos);
}
if (s->token.val != ',')
break;
next_token(s);
}
}
static void set_eval_ret_undefined(JSParseState *s)
{
if (s->eval_ret_idx >= 0) {
emit_op(s, OP_undefined);
emit_var(s, OP_put_loc, s->eval_ret_idx, s->pc2line_source_pos);
}
}
static int js_parse_block(JSParseState *s, int state, int dummy_param)
{
PARSE_START1();
js_parse_expect(s, '{');
if (s->token.val != '}') {
for(;;) {
PARSE_CALL(s, 0, js_parse_statement, 0);
if (s->token.val == '}')
break;
}
}
next_token(s);
return PARSE_STATE_RET;
}
static int js_parse_statement(JSParseState *s, int state, int dummy_param)
{
JSValue label_name;
JSGCRef label_name_ref;
PARSE_START12();
if (is_label(s)) {
JSValue top_val;
BlockEnv *top;
label_name = s->token.value;
JS_PUSH_VALUE(s->ctx, label_name);
next_token(s);
js_parse_expect(s, ':');
JS_POP_VALUE(s->ctx, label_name);
for(top_val = s->top_break; !JS_IsNull(top_val); top_val = top->prev) {
top = VALUE_TO_SP(s->ctx, top_val);
if (top->label_name == label_name)
js_parse_error(s, "duplicate label name");
}
if (s->token.val != TOK_FOR &&
s->token.val != TOK_DO &&
s->token.val != TOK_WHILE) {
BlockEnv *be;
push_break_entry(s, label_name, new_label(s), LABEL_NONE, 0);
PARSE_CALL(s, 11, js_parse_statement, 0);
be = VALUE_TO_SP(s->ctx, s->top_break);
emit_label(s, &be->label_break);
pop_break_entry(s);
goto done;
}
} else {
label_name = JS_NULL;
}
switch(s->token.val) {
case '{':
PARSE_CALL(s, 0, js_parse_block, 0);
break;
case TOK_RETURN:
{
BOOL has_val;
JSSourcePos op_source_pos;
if (s->is_eval)
js_parse_error(s, "return not in a function");
op_source_pos = s->token.source_pos;
next_token(s);
if (s->token.val != ';' && s->token.val != '}' && !s->got_lf) {
js_parse_expr(s);
has_val = TRUE;
} else {
has_val = FALSE;
}
emit_return(s, has_val, op_source_pos);
js_parse_expect_semi(s);
}
break;
case TOK_THROW:
{
JSSourcePos op_source_pos;
op_source_pos = s->token.source_pos;
next_token(s);
if (s->got_lf)
js_parse_error(s, "line terminator not allowed after throw");
js_parse_expr(s);
emit_op_pos(s, OP_throw, op_source_pos);
js_parse_expect_semi(s);
}
break;
case TOK_VAR:
next_token(s);
js_parse_var(s, TRUE);
js_parse_expect_semi(s);
break;
case TOK_IF:
{
JSValue label1, label2;
next_token(s);
set_eval_ret_undefined(s);
js_parse_expr_paren(s);
label1 = new_label(s);
emit_goto(s, OP_if_false, &label1);
PARSE_PUSH_VAL(s, label1);
PARSE_CALL(s, 1, js_parse_statement, 0);
PARSE_POP_VAL(s, label1);
if (s->token.val == TOK_ELSE) {
next_token(s);
label2 = new_label(s);
emit_goto(s, OP_goto, &label2);
emit_label(s, &label1);
PARSE_PUSH_VAL(s, label2);
PARSE_CALL(s, 2, js_parse_statement, 0);
PARSE_POP_VAL(s, label2);
label1 = label2;
}
emit_label(s, &label1);
}
break;
case TOK_WHILE:
{
BlockEnv *be;
be = push_break_entry(s, label_name, new_label(s), new_label(s), 0);
next_token(s);
set_eval_ret_undefined(s);
emit_label(s, &be->label_cont);
js_parse_expr_paren(s);
emit_goto(s, OP_if_false, &be->label_break);
PARSE_CALL(s, 3, js_parse_statement, 0);
be = VALUE_TO_SP(s->ctx, s->top_break);
emit_goto(s, OP_goto, &be->label_cont);
emit_label(s, &be->label_break);
pop_break_entry(s);
}
break;
case TOK_DO:
{
JSValue label1;
BlockEnv *be;
be = push_break_entry(s, label_name, new_label(s), new_label(s), 0);
label1 = new_label(s);
next_token(s);
set_eval_ret_undefined(s);
emit_label(s, &label1);
PARSE_PUSH_VAL(s, label1);
PARSE_CALL(s, 4, js_parse_statement, 0);
PARSE_POP_VAL(s, label1);
be = VALUE_TO_SP(s->ctx, s->top_break);
emit_label(s, &be->label_cont);
js_parse_expect(s, TOK_WHILE);
js_parse_expr_paren(s);
if (s->token.val == ';') {
next_token(s);
}
emit_goto(s, OP_if_true, &label1);
emit_label(s, &be->label_break);
pop_break_entry(s);
}
break;
case TOK_FOR:
{
int bits;
BlockEnv *be;
be = push_break_entry(s, label_name, new_label(s), new_label(s), 0);
next_token(s);
set_eval_ret_undefined(s);
js_parse_expect1(s, '(');
bits = js_parse_skip_parens_token(s);
next_token(s);
if (!(bits & SKIP_HAS_SEMI)) {
JSValue label_expr, label_body, label_next;
int opcode, var_idx;
be->drop_count = JS_NewShortInt(1);
label_expr = new_label(s);
label_body = new_label(s);
label_next = new_label(s);
emit_goto(s, OP_goto, &label_expr);
emit_label(s, &label_next);
if (s->token.val == TOK_VAR) {
JSVarRefKindEnum var_kind;
next_token(s);
var_idx = define_var(s, &var_kind, s->token.value);
put_var(s, var_kind, var_idx, s->pc2line_source_pos);
next_token(s);
} else {
JSSourcePos source_pos;
js_parse_assign_expr2(s, PF_NO_IN);
get_lvalue(s, &opcode, &var_idx, &source_pos, FALSE);
put_lvalue(s, opcode, var_idx, source_pos,
PUT_LVALUE_NOKEEP_BOTTOM);
}
emit_goto(s, OP_goto, &label_body);
if (s->token.val == TOK_IN) {
opcode = OP_for_in_start;
} else if (s->token.val == TOK_IDENT &&
s->token.value == js_get_atom(s->ctx, JS_ATOM_of)) {
opcode = OP_for_of_start;
} else {
js_parse_error(s, "expected 'of' or 'in' in for control expression");
}
next_token(s);
emit_label(s, &label_expr);
js_parse_expr(s);
emit_op(s, opcode);
emit_goto(s, OP_goto, &be->label_cont);
js_parse_expect(s, ')');
emit_label(s, &label_body);
PARSE_PUSH_VAL(s, label_next);
PARSE_CALL(s, 5, js_parse_statement, 0);
PARSE_POP_VAL(s, label_next);
be = VALUE_TO_SP(s->ctx, s->top_break);
emit_label(s, &be->label_cont);
emit_op(s, OP_for_of_next);
emit_goto(s, OP_if_false, &label_next);
emit_op(s, OP_drop);
emit_label(s, &be->label_break);
emit_op(s, OP_drop);
} else {
JSValue label_test;
JSParsePos expr3_pos;
int tmp_val;
if (s->token.val != ';') {
if (s->token.val == TOK_VAR) {
next_token(s);
js_parse_var(s, FALSE);
} else {
js_parse_expr2(s, PF_NO_IN | PF_DROP);
}
}
js_parse_expect(s, ';');
label_test = new_label(s);
emit_label(s, &label_test);
if (s->token.val != ';') {
js_parse_expr(s);
emit_goto(s, OP_if_false, &be->label_break);
}
js_parse_expect(s, ';');
if (s->token.val != ')') {
js_parse_get_pos(s, &expr3_pos);
js_skip_expr(s);
} else {
expr3_pos.source_pos = -1;
expr3_pos.got_lf = 0;
expr3_pos.regexp_allowed = 0;
}
js_parse_expect(s, ')');
PARSE_PUSH_VAL(s, label_test);
PARSE_PUSH_INT(s, expr3_pos.got_lf | (expr3_pos.regexp_allowed << 1));
PARSE_PUSH_INT(s, expr3_pos.source_pos);
PARSE_CALL(s, 6, js_parse_statement, 0);
PARSE_POP_INT(s, expr3_pos.source_pos);
PARSE_POP_INT(s, tmp_val);
expr3_pos.got_lf = tmp_val & 1;
expr3_pos.regexp_allowed = tmp_val >> 1;
PARSE_POP_VAL(s, label_test);
be = VALUE_TO_SP(s->ctx, s->top_break);
emit_label(s, &be->label_cont);
if (expr3_pos.source_pos != -1) {
JSParsePos end_pos;
js_parse_get_pos(s, &end_pos);
js_parse_seek_token(s, &expr3_pos);
js_parse_expr2(s, PF_DROP);
js_parse_seek_token(s, &end_pos);
}
emit_goto(s, OP_goto, &label_test);
be = VALUE_TO_SP(s->ctx, s->top_break);
emit_label(s, &be->label_break);
}
pop_break_entry(s);
}
break;
case TOK_BREAK:
case TOK_CONTINUE:
{
int is_cont = (s->token.val == TOK_CONTINUE);
JSValue label_name;
next_token(s);
if (!s->got_lf && s->token.val == TOK_IDENT)
label_name = s->token.value;
else
label_name = JS_NULL;
emit_break(s, label_name, is_cont);
if (label_name != JS_NULL) {
next_token(s);
}
js_parse_expect_semi(s);
}
break;
case TOK_SWITCH:
{
JSValue label_case;
int default_label_pos;
BlockEnv *be;
be = push_break_entry(s, label_name, new_label(s), LABEL_NONE, 1);
next_token(s);
set_eval_ret_undefined(s);
js_parse_expr_paren(s);
js_parse_expect(s, '{');
default_label_pos = -1;
label_case = LABEL_NONE;
while (s->token.val != '}') {
if (s->token.val == TOK_CASE) {
JSValue label1 = LABEL_NONE;
if (!label_is_none(label_case)) {
label1 = new_label(s);
emit_goto(s, OP_goto, &label1);
emit_label(s, &label_case);
label_case = LABEL_NONE;
}
for (;;) {
next_token(s);
emit_op(s, OP_dup);
js_parse_expr(s);
js_parse_expect(s, ':');
emit_op(s, OP_strict_eq);
if (s->token.val == TOK_CASE) {
if (label_is_none(label1))
label1 = new_label(s);
emit_goto(s, OP_if_true, &label1);
} else {
label_case = new_label(s);
emit_goto(s, OP_if_false, &label_case);
if (!label_is_none(label1))
emit_label(s, &label1);
break;
}
}
} else if (s->token.val == TOK_DEFAULT) {
next_token(s);
js_parse_expect(s, ':');
if (default_label_pos >= 0)
js_parse_error(s, "duplicate default");
if (label_is_none(label_case)) {
label_case = new_label(s);
emit_goto(s, OP_goto, &label_case);
}
default_label_pos = s->byte_code_len;
} else {
if (label_is_none(label_case))
js_parse_error(s, "invalid switch statement");
PARSE_PUSH_VAL(s, label_case);
PARSE_CALL_SAVE1(s, 7, js_parse_statement, 0,
default_label_pos);
PARSE_POP_VAL(s, label_case);
}
}
js_parse_expect(s, '}');
if (default_label_pos >= 0) {
emit_label_pos(s, &label_case, default_label_pos);
} else if (!label_is_none(label_case)) {
emit_label(s, &label_case);
}
be = VALUE_TO_SP(s->ctx, s->top_break);
emit_label(s, &be->label_break);
emit_op(s, OP_drop);
pop_break_entry(s);
}
break;
case TOK_TRY:
{
JSValue label_catch, label_finally, label_end;
BlockEnv *be;
set_eval_ret_undefined(s);
next_token(s);
label_catch = new_label(s);
label_finally = new_label(s);
emit_goto(s, OP_catch, &label_catch);
be = push_break_entry(s, JS_NULL, LABEL_NONE, LABEL_NONE, 1);
be->label_finally = label_finally;
PARSE_PUSH_VAL(s, label_catch);
PARSE_CALL(s, 8, js_parse_block, 0);
PARSE_POP_VAL(s, label_catch);
be = VALUE_TO_SP(s->ctx, s->top_break);
label_finally = be->label_finally;
pop_break_entry(s);
emit_op(s, OP_drop);
emit_op(s, OP_undefined);
emit_goto(s, OP_gosub, &label_finally);
emit_op(s, OP_drop);
label_end = new_label(s);
emit_goto(s, OP_goto, &label_end);
if (s->token.val == TOK_CATCH) {
JSValue label_catch2;
int var_idx;
JSValue name;
label_catch2 = new_label(s);
next_token(s);
js_parse_expect(s, '(');
if (s->token.val != TOK_IDENT)
js_parse_error(s, "identifier expected");
name = s->token.value;
if (find_var(s, name) >= 0 || find_ext_var(s, name) >= 0) {
js_parse_error(s, "catch variable already exists");
}
var_idx = add_var(s, name);
next_token(s);
js_parse_expect(s, ')');
emit_label(s, &label_catch);
{
JSFunctionBytecode *b = JS_VALUE_TO_PTR(s->cur_func);
emit_var(s, OP_put_loc, var_idx - b->arg_count, s->pc2line_source_pos);
}
emit_goto(s, OP_catch, &label_catch2);
be = push_break_entry(s, JS_NULL, LABEL_NONE, LABEL_NONE, 1);
be->label_finally = label_finally;
PARSE_PUSH_VAL(s, label_end);
PARSE_PUSH_VAL(s, label_catch2);
PARSE_CALL(s, 9, js_parse_block, 0);
PARSE_POP_VAL(s, label_catch2);
PARSE_POP_VAL(s, label_end);
be = VALUE_TO_SP(s->ctx, s->top_break);
label_finally = be->label_finally;
pop_break_entry(s);
emit_op(s, OP_drop);
emit_op(s, OP_undefined);
emit_goto(s, OP_gosub, &label_finally);
emit_op(s, OP_drop);
emit_goto(s, OP_goto, &label_end);
emit_label(s, &label_catch2);
emit_goto(s, OP_gosub, &label_finally);
emit_op(s, OP_throw);
} else if (s->token.val == TOK_FINALLY) {
emit_label(s, &label_catch);
emit_goto(s, OP_gosub, &label_finally);
emit_op(s, OP_throw);
} else {
js_parse_error(s, "expecting catch or finally");
}
emit_label(s, &label_finally);
if (s->token.val == TOK_FINALLY) {
next_token(s);
push_break_entry(s, JS_NULL, LABEL_NONE, LABEL_NONE, 2);
PARSE_PUSH_VAL(s, label_end);
PARSE_CALL(s, 10, js_parse_block, 0);
PARSE_POP_VAL(s, label_end);
pop_break_entry(s);
}
emit_op(s, OP_ret);
emit_label(s, &label_end);
}
break;
case ';':
next_token(s);
break;
default:
if (s->eval_ret_idx >= 0) {
js_parse_expr(s);
emit_var(s, OP_put_loc, s->eval_ret_idx, s->pc2line_source_pos);
} else {
js_parse_expr2(s, PF_DROP);
}
js_parse_expect_semi(s);
break;
}
done:
return PARSE_STATE_RET;
}
static JSParseFunc *parse_func_table[] = {
js_parse_expr_comma,
js_parse_assign_expr,
js_parse_cond_expr,
js_parse_logical_and_or,
js_parse_expr_binary,
js_parse_unary,
js_parse_postfix_expr,
js_parse_statement,
js_parse_block,
js_parse_json_value,
re_parse_alternative,
re_parse_disjunction,
};
static void js_parse_source_element(JSParseState *s)
{
if (s->token.val == TOK_FUNCTION) {
js_parse_function_decl(s, JS_PARSE_FUNC_STATEMENT, JS_NULL);
} else {
js_parse_call(s, PARSE_FUNC_js_parse_statement, 0);
}
}
static JSFunctionBytecode *js_alloc_function_bytecode(JSContext *ctx)
{
JSFunctionBytecode *b;
b = js_mallocz(ctx, sizeof(JSFunctionBytecode), JS_MTAG_FUNCTION_BYTECODE);
if (!b)
return NULL;
b->func_name = JS_NULL;
b->byte_code = JS_NULL;
b->cpool = JS_NULL;
b->vars = JS_NULL;
b->ext_vars = JS_NULL;
b->filename = JS_NULL;
b->pc2line = JS_NULL;
return b;
}
static void js_parse_function_decl(JSParseState *s,
JSParseFunctionEnum func_type, JSValue func_name)
{
JSContext *ctx = s->ctx;
BOOL is_expr;
JSFunctionBytecode *b;
int idx, skip_bits;
JSVarRefKindEnum var_kind;
JSValue bfunc;
JSGCRef func_name_ref, bfunc_ref;
is_expr = (func_type != JS_PARSE_FUNC_STATEMENT);
if (func_type == JS_PARSE_FUNC_STATEMENT ||
func_type == JS_PARSE_FUNC_EXPR) {
next_token(s);
if (s->token.val != TOK_IDENT && !is_expr)
js_parse_error(s, "function name expected");
if (s->token.val == TOK_IDENT) {
func_name = s->token.value;
JS_PUSH_VALUE(ctx, func_name);
next_token(s);
JS_POP_VALUE(ctx, func_name);
}
}
JS_PUSH_VALUE(ctx, func_name);
b = js_alloc_function_bytecode(s->ctx);
if (!b)
js_parse_error_mem(s);
bfunc = JS_VALUE_FROM_PTR(b);
JS_PUSH_VALUE(ctx, bfunc);
b->filename = s->filename_str;
b->func_name = func_name_ref.val;
b->source_pos = s->token.source_pos;
b->has_column = s->has_column;
js_parse_expect1(s, '(');
js_skip_parens(s, NULL);
js_parse_expect1(s, '{');
skip_bits = js_skip_parens(s, is_expr ? &func_name_ref.val : NULL);
b = JS_VALUE_TO_PTR(bfunc_ref.val);
b->has_arguments = ((skip_bits & SKIP_HAS_ARGUMENTS) != 0);
b->has_local_func_name = ((skip_bits & SKIP_HAS_FUNC_NAME) != 0);
idx = cpool_add(s, bfunc_ref.val);
if (is_expr) {
emit_op(s, OP_fclosure);
emit_u16(s, idx);
} else {
idx = define_var(s, &var_kind, func_name_ref.val);
s->hoisted_code_len += 3 + 3;
if (var_kind == JS_VARREF_KIND_VAR) {
b = JS_VALUE_TO_PTR(s->cur_func);
idx += b->arg_count;
}
b = JS_VALUE_TO_PTR(bfunc_ref.val);
b->arg_count = idx + 1;
}
JS_POP_VALUE(ctx, bfunc);
JS_POP_VALUE(ctx, func_name);
}
static void define_hoisted_functions(JSParseState *s, BOOL is_eval)
{
JSValueArray *cpool;
JSValue val;
JSFunctionBytecode *b;
int idx, saved_byte_code_len, arg_count, i, op;
b = JS_VALUE_TO_PTR(s->cur_func);
if (b->pc2line != JS_NULL) {
int h, n;
n = (-s->pc2line_bit_len) & 7;
if (n != 0)
pc2line_put_bits(s, n, 0);
n = s->hoisted_code_len;
h = 0;
for(;;) {
pc2line_put_bits(s, 8, (n & 0x7f) | h);
n >>= 7;
if (n == 0)
break;
h |= 0x80;
}
}
if (s->hoisted_code_len == 0)
return;
emit_insert(s, 0, s->hoisted_code_len);
b = JS_VALUE_TO_PTR(s->cur_func);
arg_count = b->arg_count;
saved_byte_code_len = s->byte_code_len;
s->byte_code_len = 0;
cpool = JS_VALUE_TO_PTR(b->cpool);
for(i = 0; i < s->cpool_len; i++) {
val = cpool->arr[i];
if (JS_IsPtr(val)) {
b = JS_VALUE_TO_PTR(val);
if (b->mtag == JS_MTAG_FUNCTION_BYTECODE &&
b->arg_count != 0) {
idx = b->arg_count - 1;
if (is_eval) {
op = OP_put_var_ref_nocheck;
} else if (idx < arg_count) {
op = OP_put_arg;
} else {
idx -= arg_count;
op = OP_put_loc;
}
emit_u8(s, OP_fclosure);
emit_u16(s, i);
emit_u8(s, op);
emit_u16(s, idx);
}
}
}
s->byte_code_len = saved_byte_code_len;
}
static void js_parse_function(JSParseState *s)
{
JSFunctionBytecode *b;
int arg_count;
next_token(s);
js_parse_expect(s, '(');
while (s->token.val != ')') {
JSValue name;
if (s->token.val != TOK_IDENT)
js_parse_error(s, "missing formal parameter");
name = s->token.value;
if (name == js_get_atom(s->ctx, JS_ATOM_eval) ||
name == js_get_atom(s->ctx, JS_ATOM_arguments)) {
js_parse_error(s, "invalid argument name");
}
if (find_var(s, name) >= 0)
js_parse_error(s, "duplicate argument name");
add_var(s, name);
next_token(s);
if (s->token.val == ')')
break;
js_parse_expect(s, ',');
}
b = JS_VALUE_TO_PTR(s->cur_func);
arg_count = b->arg_count = s->local_vars_len;
next_token(s);
js_parse_expect(s, '{');
b = JS_VALUE_TO_PTR(s->cur_func);
if (b->has_arguments) {
int var_idx;
var_idx = add_var(s, js_get_atom(s->ctx, JS_ATOM_arguments));
emit_op(s, OP_arguments);
put_var(s, JS_VARREF_KIND_VAR, var_idx - arg_count, s->pc2line_source_pos);
}
b = JS_VALUE_TO_PTR(s->cur_func);
if (b->has_local_func_name) {
int var_idx;
var_idx = add_var(s, b->func_name);
emit_op(s, OP_this_func);
put_var(s, JS_VARREF_KIND_VAR, var_idx - arg_count, s->pc2line_source_pos);
}
while (s->token.val != '}') {
js_parse_source_element(s);
}
if (js_is_live_code(s))
emit_op(s, OP_return_undef);
next_token(s);
define_hoisted_functions(s, FALSE);
b = JS_VALUE_TO_PTR(s->cur_func);
b->byte_code = s->byte_code;
}
static void js_parse_program(JSParseState *s)
{
JSFunctionBytecode *b;
next_token(s);
if (s->has_retval) {
s->eval_ret_idx = add_var(s, js_get_atom(s->ctx, JS_ATOM__ret_));
}
while (s->token.val != TOK_EOF) {
js_parse_source_element(s);
}
if (s->eval_ret_idx >= 0) {
emit_var(s, OP_get_loc, s->eval_ret_idx, s->pc2line_source_pos);
emit_op(s, OP_return);
} else {
emit_op(s, OP_return_undef);
}
define_hoisted_functions(s, TRUE);
b = JS_VALUE_TO_PTR(s->cur_func);
b->byte_code = s->byte_code;
}
#define CVT_VAR_SIZE_MAX 16
typedef struct {
uint16_t new_var_idx;
uint8_t is_local;
} ConvertVarEntry;
static void convert_ext_vars_to_local_vars_bytecode(JSParseState *s,
uint8_t *byte_code, int byte_code_len,
int var_start, const ConvertVarEntry *cvt_tab,
int tab_len)
{
int pos, var_end, j, op, var_idx;
const JSOpCode *oi;
var_end = var_start + tab_len;
pos = 0;
while (pos < byte_code_len) {
op = byte_code[pos];
oi = &opcode_info[op];
switch(op) {
case OP_get_var_ref:
case OP_put_var_ref:
case OP_get_var_ref_nocheck:
case OP_put_var_ref_nocheck:
var_idx = get_u16(byte_code + pos + 1);
if (var_idx >= var_start && var_idx < var_end) {
j = var_idx - var_start;
put_u16(byte_code + pos + 1, cvt_tab[j].new_var_idx);
if (cvt_tab[j].is_local) {
if (op == OP_get_var_ref || op == OP_get_var_ref_nocheck) {
byte_code[pos] = OP_get_loc;
} else {
byte_code[pos] = OP_put_loc;
}
}
}
break;
default:
break;
}
pos += oi->size;
}
}
static void convert_ext_vars_to_local_vars(JSParseState *s)
{
JSValueArray *ext_vars;
JSFunctionBytecode *b;
JSByteArray *bc_arr;
JSValue var_name, decl;
int i0, i, j, var_idx, l;
ConvertVarEntry cvt_tab[CVT_VAR_SIZE_MAX];
b = JS_VALUE_TO_PTR(s->cur_func);
if (s->local_vars_len == 0 || b->ext_vars_len == 0)
return;
bc_arr = JS_VALUE_TO_PTR(b->byte_code);
ext_vars = JS_VALUE_TO_PTR(b->ext_vars);
j = 0;
for(i0 = 0; i0 < b->ext_vars_len; i0 += CVT_VAR_SIZE_MAX) {
l = min_int(b->ext_vars_len - i0, CVT_VAR_SIZE_MAX);
for(i = 0; i < l; i++) {
var_name = ext_vars->arr[2 * (i0 + i)];
decl = ext_vars->arr[2 * (i0 + i) + 1];
var_idx = find_var(s, var_name);
if (var_idx >= b->arg_count) {
cvt_tab[i].new_var_idx = var_idx - b->arg_count;
cvt_tab[i].is_local = TRUE;
} else {
cvt_tab[i].new_var_idx = j;
cvt_tab[i].is_local = FALSE;
ext_vars->arr[2 * j] = var_name;
ext_vars->arr[2 * j + 1] = decl;
j++;
}
}
if (j != (i0 + l)) {
convert_ext_vars_to_local_vars_bytecode(s, bc_arr->buf, s->byte_code_len,
i0, cvt_tab, l);
}
}
b->ext_vars_len = j;
}
static void compute_stack_size_push(JSParseState *s,
JSByteArray *arr,
uint8_t *explore_tab,
uint32_t pos, int stack_len)
{
int short_stack_len;
#if 0#endif
if (pos >= (uint32_t)arr->size)
js_parse_error(s, "bytecode buffer overflow (pc=%d)", pos);
short_stack_len = 1 + ((unsigned)stack_len % 255);
if (explore_tab[pos] != 0) {
if (explore_tab[pos] != short_stack_len) {
js_parse_error(s, "inconsistent stack size: %d %d (pc=%d)", explore_tab[pos] - 1, short_stack_len - 1, (int)pos);
}
} else {
explore_tab[pos] = short_stack_len;
PARSE_PUSH_INT(s, pos);
PARSE_PUSH_INT(s, stack_len);
}
}
static void compute_stack_size(JSParseState *s, JSValue *pfunc)
{
JSContext *ctx = s->ctx;
JSByteArray *explore_arr, *arr;
JSFunctionBytecode *b;
uint8_t *explore_tab;
JSValue *stack_top, explore_arr_val;
uint32_t pos;
int op, op_len, pos1, n_pop, stack_len;
const JSOpCode *oi;
JSGCRef explore_arr_val_ref;
b = JS_VALUE_TO_PTR(*pfunc);
arr = JS_VALUE_TO_PTR(b->byte_code);
explore_arr = js_alloc_byte_array(s->ctx, arr->size);
if (!explore_arr)
js_parse_error_mem(s);
b = JS_VALUE_TO_PTR(*pfunc);
arr = JS_VALUE_TO_PTR(b->byte_code);
explore_arr_val = JS_VALUE_FROM_PTR(explore_arr);
explore_tab = explore_arr->buf;
memset(explore_tab, 0, arr->size);
JS_PUSH_VALUE(ctx, explore_arr_val);
stack_top = ctx->sp;
compute_stack_size_push(s, arr, explore_tab, 0, 0);
while (ctx->sp < stack_top) {
PARSE_POP_INT(s, stack_len);
PARSE_POP_INT(s, pos);
b = JS_VALUE_TO_PTR(*pfunc);
arr = JS_VALUE_TO_PTR(b->byte_code);
explore_arr = JS_VALUE_TO_PTR(explore_arr_val_ref.val);
explore_tab = explore_arr->buf;
op = arr->buf[pos++];
if (op == OP_invalid || op >= OP_COUNT)
js_parse_error(s, "invalid opcode (pc=%d)", (int)(pos - 1));
oi = &opcode_info[op];
op_len = oi->size;
if ((pos + op_len - 1) > arr->size) {
js_parse_error(s, "bytecode buffer overflow (pc=%d)", (int)(pos - 1));
}
n_pop = oi->n_pop;
if (oi->fmt == OP_FMT_npop)
n_pop += get_u16(arr->buf + pos);
if (stack_len < n_pop) {
js_parse_error(s, "stack underflow (pc=%d)", (int)(pos - 1));
}
stack_len += oi->n_push - n_pop;
if (stack_len > b->stack_size) {
if (stack_len > JS_MAX_FUNC_STACK_SIZE)
js_parse_error(s, "stack overflow (pc=%d)", (int)(pos - 1));
b->stack_size = stack_len;
}
switch(op) {
case OP_return:
case OP_return_undef:
case OP_throw:
case OP_ret:
goto done;
case OP_goto:
pos += get_u32(arr->buf + pos);
break;
case OP_if_true:
case OP_if_false:
pos1 = pos + get_u32(arr->buf + pos);
compute_stack_size_push(s, arr, explore_tab, pos1, stack_len);
pos += op_len - 1;
break;
case OP_gosub:
pos1 = pos + get_u32(arr->buf + pos);
compute_stack_size_push(s, arr, explore_tab, pos1, stack_len + 1);
pos += op_len - 1;
break;
default:
pos += op_len - 1;
break;
}
compute_stack_size_push(s, arr, explore_tab, pos, stack_len);
done: ;
}
JS_POP_VALUE(ctx, explore_arr_val);
explore_arr = JS_VALUE_TO_PTR(explore_arr_val);
js_free(s->ctx, explore_arr);
}
static void resolve_var_refs(JSParseState *s, JSValue *pfunc, JSValue *pparent_func)
{
JSContext *ctx = s->ctx;
int i, decl, var_idx, arg_count, ext_vars_len;
JSValueArray *ext_vars;
JSValue var_name;
JSFunctionBytecode *b1, *b;
b = JS_VALUE_TO_PTR(*pfunc);
if (b->ext_vars_len == 0)
return;
b1 = JS_VALUE_TO_PTR(*pparent_func);
arg_count = b1->arg_count;
ext_vars = JS_VALUE_TO_PTR(b->ext_vars);
ext_vars_len = b->ext_vars_len;
for(i = 0; i < ext_vars_len; i++) {
b = JS_VALUE_TO_PTR(*pfunc);
ext_vars = JS_VALUE_TO_PTR(b->ext_vars);
var_name = ext_vars->arr[2 * i];
var_idx = find_func_var(ctx, *pparent_func, var_name);
if (var_idx >= 0) {
if (var_idx < arg_count) {
decl = (JS_VARREF_KIND_ARG << 16) | var_idx;
} else {
decl = (JS_VARREF_KIND_VAR << 16) | (var_idx - arg_count);
}
} else {
var_idx = find_func_ext_var(s, *pparent_func, var_name);
if (var_idx < 0) {
var_idx = add_func_ext_var(s, *pparent_func, var_name,
(JS_VARREF_KIND_GLOBAL << 16));
}
decl = (JS_VARREF_KIND_VAR_REF << 16) | var_idx;
}
b = JS_VALUE_TO_PTR(*pfunc);
ext_vars = JS_VALUE_TO_PTR(b->ext_vars);
ext_vars->arr[2 * i + 1] = JS_NewShortInt(decl);
}
}
static void reset_parse_state(JSParseState *s, uint32_t input_pos,
JSValue cur_func)
{
s->buf_pos = input_pos;
s->token.val = ' ';
s->cur_func = cur_func;
s->byte_code = JS_NULL;
s->byte_code_len = 0;
s->last_opcode_pos = -1;
s->pc2line_bit_len = 0;
s->pc2line_source_pos = 0;
s->cpool_len = 0;
s->hoisted_code_len = 0;
s->local_vars_len = 0;
s->eval_ret_idx = -1;
}
static void js_parse_local_functions(JSParseState *s, JSValue *pfunc)
{
JSContext *ctx = s->ctx;
JSValue *pparent_func;
JSValueArray *cpool;
int err, cpool_pos;
JSValue func;
JSFunctionBytecode *b, *b1;
JSGCRef func_ref;
JSValue *stack_top;
err = JS_StackCheck(ctx, 3);
if (err)
js_parse_error_stack_overflow(s);
stack_top = ctx->sp;
*--ctx->sp = JS_NULL;
*--ctx->sp = *pfunc;
*--ctx->sp = JS_NewShortInt(0);
while (ctx->sp < stack_top) {
pparent_func = &ctx->sp[2];
pfunc = &ctx->sp[1];
cpool_pos = JS_VALUE_GET_INT(ctx->sp[0]);
#if 0#endif
if (cpool_pos == 0) {
b = JS_VALUE_TO_PTR(*pfunc);
convert_ext_vars_to_local_vars(s);
js_shrink_byte_array(ctx, &b->byte_code, s->byte_code_len);
js_shrink_value_array(ctx, &b->cpool, s->cpool_len);
js_shrink_value_array(ctx, &b->vars, s->local_vars_len);
js_shrink_byte_array(ctx, &b->pc2line, (s->pc2line_bit_len + 7) / 8);
compute_stack_size(s, pfunc);
}
b = JS_VALUE_TO_PTR(*pfunc);
if (b->cpool != JS_NULL) {
int cpool_size;
cpool = JS_VALUE_TO_PTR(b->cpool);
cpool_size = cpool->size;
for(; cpool_pos < cpool_size; cpool_pos++) {
b = JS_VALUE_TO_PTR(*pfunc);
cpool = JS_VALUE_TO_PTR(b->cpool);
func = cpool->arr[cpool_pos];
if (!JS_IsPtr(func))
continue;
b1 = JS_VALUE_TO_PTR(func);
if (b1->mtag != JS_MTAG_FUNCTION_BYTECODE)
continue;
reset_parse_state(s, b1->source_pos, func);
s->is_eval = FALSE;
s->is_repl = FALSE;
s->has_retval = FALSE;
JS_PUSH_VALUE(ctx, func);
js_parse_function(s);
err = JS_StackCheck(ctx, 3);
JS_POP_VALUE(ctx, func);
if (err)
js_parse_error_stack_overflow(s);
*ctx->sp = JS_NewShortInt(cpool_pos + 1);
*--ctx->sp = *pfunc;
*--ctx->sp = func;
*--ctx->sp = JS_NewShortInt(0);
goto next;
}
}
if (*pparent_func != JS_NULL) {
resolve_var_refs(s, pfunc, pparent_func);
}
b = JS_VALUE_TO_PTR(*pfunc);
js_shrink_value_array(ctx, &b->ext_vars, 2 * b->ext_vars_len);
#ifdef DUMP_FUNC_BYTECODE
dump_byte_code(ctx, b);
#endif
ctx->sp += 3;
ctx->stack_bottom = ctx->sp;
next: ;
}
}
static int js_parse_json_value(JSParseState *s, int state, int dummy_param)
{
JSContext *ctx = s->ctx;
const uint8_t *p;
JSValue val;
PARSE_START2();
p = s->source_buf + s->buf_pos;
p += skip_spaces((const char *)p);
s->buf_pos = p - s->source_buf;
if ((*p >= '0' && *p <= '9') || *p == '-') {
double d;
JSByteArray *tmp_arr;
tmp_arr = js_alloc_byte_array(s->ctx, sizeof(JSATODTempMem));
if (!tmp_arr)
js_parse_error_mem(s);
p = s->source_buf + s->buf_pos;
d = js_atod((const char *)p, (const char **)&p, 10, 0,
(JSATODTempMem *)tmp_arr->buf);
js_free(s->ctx, tmp_arr);
if (isnan(d))
js_parse_error(s, "invalid number literal");
val = JS_NewFloat64(s->ctx, d);
} else if (*p == 't' &&
p[1] == 'r' && p[2] == 'u' && p[3] == 'e') {
p += 4;
val = JS_TRUE;
} else if (*p == 'f' &&
p[1] == 'a' && p[2] == 'l' && p[3] == 's' && p[4] == 'e') {
p += 5;
val = JS_FALSE;
} else if (*p == 'n' &&
p[1] == 'u' && p[2] == 'l' && p[3] == 'l') {
p += 4;
val = JS_NULL;
} else if (*p == '\"') {
uint32_t pos;
pos = p + 1 - s->source_buf;
val = js_parse_string(s, &pos, '\"');
p = s->source_buf + pos;
} else if (*p == '[') {
JSValue val2;
uint32_t idx;
val = JS_NewArray(ctx, 0);
if (JS_IsException(val))
js_parse_error_mem(s);
PARSE_PUSH_VAL(s, val);
p = s->source_buf + s->buf_pos + 1;
p += skip_spaces((const char *)p);
if (*p != ']') {
idx = 0;
for(;;) {
s->buf_pos = p - s->source_buf;
PARSE_PUSH_INT(s, idx);
PARSE_CALL(s, 0, js_parse_json_value, 0);
PARSE_POP_INT(s, idx);
val2 = s->token.value;
val2 = JS_SetPropertyUint32(ctx, *ctx->sp, idx, val2);
if (JS_IsException(val2))
js_parse_error_mem(s);
idx++;
p = s->source_buf + s->buf_pos;
p += skip_spaces((const char *)p);
if (*p != ',')
break;
p++;
}
}
if (*p != ']')
js_parse_error(s, "expecting ']'");
p++;
PARSE_POP_VAL(s, val);
} else if (*p == '{') {
JSValue val2, prop;
uint32_t pos;
val = JS_NewObject(ctx);
if (JS_IsException(val))
js_parse_error_mem(s);
PARSE_PUSH_VAL(s, val);
p = s->source_buf + s->buf_pos + 1;
p += skip_spaces((const char *)p);
if (*p != '}') {
for(;;) {
p += skip_spaces((const char *)p);
s->buf_pos = p - s->source_buf;
if (*p != '\"')
js_parse_error(s, "expecting '\"'");
pos = p + 1 - s->source_buf;
prop = js_parse_string(s, &pos, '\"');
prop = JS_ToPropertyKey(ctx, prop);
if (JS_IsException(prop))
js_parse_error_mem(s);
p = s->source_buf + pos;
p += skip_spaces((const char *)p);
if (*p != ':')
js_parse_error(s, "expecting ':'");
p++;
s->buf_pos = p - s->source_buf;
PARSE_PUSH_VAL(s, prop);
PARSE_CALL(s, 1, js_parse_json_value, 0);
val2 = s->token.value;
PARSE_POP_VAL(s, prop);
val2 = JS_DefinePropertyValue(ctx, *ctx->sp, prop, val2);
if (JS_IsException(val2))
js_parse_error_mem(s);
p = s->source_buf + s->buf_pos;
p += skip_spaces((const char *)p);
if (*p != ',')
break;
p++;
}
}
if (*p != '}')
js_parse_error(s, "expecting '}'");
p++;
PARSE_POP_VAL(s, val);
} else {
js_parse_error(s, "unexpected character");
}
s->buf_pos = p - s->source_buf;
s->token.value = val;
return PARSE_STATE_RET;
}
static JSValue js_parse_json(JSParseState *s)
{
s->buf_pos = 0;
js_parse_call(s, PARSE_FUNC_js_parse_json_value, 0);
s->buf_pos += skip_spaces((const char *)(s->source_buf + s->buf_pos));
if (s->buf_pos != s->buf_len) {
js_parse_error(s, "unexpected character");
}
return s->token.value;
}
static JSValue JS_Parse2(JSContext *ctx, JSValue source_str,
const char *input, size_t input_len,
const char *filename, int eval_flags)
{
JSParseState parse_state, *s;
JSFunctionBytecode *b;
JSValue top_func, *saved_sp;
JSGCRef top_func_ref, *saved_top_gc_ref;
uint8_t str_buf[5];
s = &parse_state;
memset(s, 0, sizeof(*s));
s->ctx = ctx;
ctx->parse_state = s;
s->source_str = JS_NULL;
s->filename_str = JS_NULL;
s->has_column = ((eval_flags & JS_EVAL_STRIP_COL) == 0);
if (JS_IsPtr(source_str)) {
JSString *p = JS_VALUE_TO_PTR(source_str);
s->source_str = source_str;
s->buf_len = p->len;
s->source_buf = p->buf;
} else if (JS_VALUE_GET_SPECIAL_TAG(source_str) == JS_TAG_STRING_CHAR) {
s->buf_len = get_short_string(str_buf, source_str);
s->source_buf = str_buf;
} else {
s->buf_len = input_len;
s->source_buf = (const uint8_t *)input;
}
s->top_break = JS_NULL;
saved_top_gc_ref = ctx->top_gc_ref;
saved_sp = ctx->sp;
if (setjmp(s->jmp_env)) {
int line_num, col_num;
JSValue val;
ctx->parse_state = NULL;
ctx->top_gc_ref = saved_top_gc_ref;
ctx->sp = saved_sp;
ctx->stack_bottom = ctx->sp;
line_num = get_line_col(&col_num, s->source_buf,
(eval_flags & (JS_EVAL_JSON | JS_EVAL_REGEXP)) ?
s->buf_pos : s->token.source_pos);
val = JS_ThrowError(ctx, JS_CLASS_SYNTAX_ERROR, "%s", s->error_msg);
build_backtrace(ctx, ctx->current_exception, filename, line_num + 1, col_num + 1, 0);
return val;
}
if (eval_flags & JS_EVAL_JSON) {
top_func = js_parse_json(s);
} else if (eval_flags & JS_EVAL_REGEXP) {
top_func = js_parse_regexp(s, eval_flags >> JS_EVAL_REGEXP_FLAGS_SHIFT);
} else {
s->filename_str = JS_NewString(ctx, filename);
if (JS_IsException(s->filename_str))
js_parse_error_mem(s);
b = js_alloc_function_bytecode(ctx);
if (!b)
js_parse_error_mem(s);
b->filename = s->filename_str;
b->func_name = js_get_atom(ctx, JS_ATOM__eval_);
b->has_column = s->has_column;
top_func = JS_VALUE_FROM_PTR(b);
reset_parse_state(s, 0, top_func);
s->is_eval = TRUE;
s->has_retval = ((eval_flags & JS_EVAL_RETVAL) != 0);
s->is_repl = ((eval_flags & JS_EVAL_REPL) != 0);
JS_PUSH_VALUE(ctx, top_func);
js_parse_program(s);
js_parse_local_functions(s, &top_func_ref.val);
JS_POP_VALUE(ctx, top_func);
}
ctx->parse_state = NULL;
return top_func;
}
JSValue JS_Parse(JSContext *ctx, const char *input, size_t input_len,
const char *filename, int eval_flags)
{
return JS_Parse2(ctx, JS_NULL, input, input_len, filename, eval_flags);
}
JSValue JS_Run(JSContext *ctx, JSValue val)
{
JSFunctionBytecode *b;
JSGCRef val_ref;
int err;
if (!JS_IsPtr(val))
goto fail;
b = JS_VALUE_TO_PTR(val);
if (b->mtag != JS_MTAG_FUNCTION_BYTECODE) {
fail:
return JS_ThrowTypeError(ctx, "bytecode function expected");
}
val = js_closure(ctx, val, NULL);
if (JS_IsException(val))
return val;
JS_PUSH_VALUE(ctx, val);
err = JS_StackCheck(ctx, 2);
JS_POP_VALUE(ctx, val);
if (err)
return JS_EXCEPTION;
JS_PushArg(ctx, val);
JS_PushArg(ctx, JS_NULL);
val = JS_Call(ctx, 0);
return val;
}
JSValue JS_Eval(JSContext *ctx, const char *input, size_t input_len,
const char *filename, int eval_flags)
{
JSValue val;
val = JS_Parse(ctx, input, input_len, filename, eval_flags);
if (JS_IsException(val))
return val;
return JS_Run(ctx, val);
}
static int get_mblock_size(const void *ptr)
{
int mtag = ((JSMemBlockHeader *)ptr)->mtag;
int size;
switch(mtag) {
case JS_MTAG_OBJECT:
{
const JSObject *p = ptr;
size = offsetof(JSObject, u) + p->extra_size * JSW;
}
break;
case JS_MTAG_FLOAT64:
size = sizeof(JSFloat64);
break;
case JS_MTAG_STRING:
{
const JSString *p = ptr;
size = sizeof(JSString) + ((p->len + JSW) & ~(JSW - 1));
}
break;
case JS_MTAG_BYTE_ARRAY:
{
const JSByteArray *p = ptr;
size = sizeof(JSByteArray) + ((p->size + JSW - 1) & ~(JSW - 1));
}
break;
case JS_MTAG_VALUE_ARRAY:
{
const JSValueArray *p = ptr;
size = sizeof(JSValueArray) + p->size * sizeof(p->arr[0]);
}
break;
case JS_MTAG_FREE:
{
const JSFreeBlock *p = ptr;
size = sizeof(JSFreeBlock) + p->size * sizeof(JSWord);
}
break;
case JS_MTAG_VARREF:
{
const JSVarRef *p = ptr;
size = sizeof(JSVarRef);
if (p->is_detached)
size -= sizeof(JSValue);
}
break;
case JS_MTAG_FUNCTION_BYTECODE:
size = sizeof(JSFunctionBytecode);
break;
default:
size = 0;
assert(0);
}
return size;
}
typedef struct {
JSContext *ctx;
JSValue *gsp;
JSValue *gs_bottom;
JSValue *gs_top;
BOOL overflow;
} GCMarkState;
static BOOL mtag_has_references(int mtag)
{
return (mtag == JS_MTAG_OBJECT ||
mtag == JS_MTAG_VALUE_ARRAY ||
mtag == JS_MTAG_VARREF ||
mtag == JS_MTAG_FUNCTION_BYTECODE);
}
static void gc_mark(GCMarkState *s, JSValue val)
{
JSContext *ctx = s->ctx;
void *ptr;
JSMemBlockHeader *mb;
if (!JS_IsPtr(val))
return;
ptr = JS_VALUE_TO_PTR(val);
if (JS_IS_ROM_PTR(ctx, ptr))
return;
mb = ptr;
if (mb->gc_mark)
return;
mb->gc_mark = 1;
if (mtag_has_references(mb->mtag)) {
if (mb->mtag == JS_MTAG_VALUE_ARRAY) {
if ((s->gsp - s->gs_bottom) < 2) {
s->overflow = TRUE;
} else {
*--s->gsp = 0;
*--s->gsp = val;
}
} else {
if ((s->gsp - s->gs_bottom) < 1) {
s->overflow = TRUE;
} else {
*--s->gsp = val;
}
}
}
}
static void gc_mark_flush(GCMarkState *s)
{
void *ptr;
JSMemBlockHeader *mb;
JSValue val;
while (s->gsp < s->gs_top) {
val = *s->gsp++;
ptr = JS_VALUE_TO_PTR(val);
mb = ptr;
switch(mb->mtag) {
case JS_MTAG_OBJECT:
{
const JSObject *p = ptr;
gc_mark(s, p->proto);
gc_mark(s, p->props);
switch(p->class_id) {
case JS_CLASS_CLOSURE:
{
int i;
gc_mark(s, p->u.closure.func_bytecode);
for(i = 0; i < p->extra_size - 1; i++)
gc_mark(s, p->u.closure.var_refs[i]);
}
break;
case JS_CLASS_C_FUNCTION:
if (p->extra_size > 1)
gc_mark(s, p->u.cfunc.params);
break;
case JS_CLASS_ARRAY:
gc_mark(s, p->u.array.tab);
break;
case JS_CLASS_ERROR:
gc_mark(s, p->u.error.message);
gc_mark(s, p->u.error.stack);
break;
case JS_CLASS_ARRAY_BUFFER:
gc_mark(s, p->u.array_buffer.byte_buffer);
break;
case JS_CLASS_UINT8C_ARRAY:
case JS_CLASS_INT8_ARRAY:
case JS_CLASS_UINT8_ARRAY:
case JS_CLASS_INT16_ARRAY:
case JS_CLASS_UINT16_ARRAY:
case JS_CLASS_INT32_ARRAY:
case JS_CLASS_UINT32_ARRAY:
case JS_CLASS_FLOAT32_ARRAY:
case JS_CLASS_FLOAT64_ARRAY:
gc_mark(s, p->u.typed_array.buffer);
break;
case JS_CLASS_REGEXP:
gc_mark(s, p->u.regexp.source);
gc_mark(s, p->u.regexp.byte_code);
break;
}
}
break;
case JS_MTAG_VALUE_ARRAY:
{
const JSValueArray *p = ptr;
int pos;
pos = *s->gsp++;
while (pos < p->size && !JS_IsPtr(p->arr[pos]))
pos++;
if (pos < p->size) {
if ((pos + 1) < p->size) {
*--s->gsp = pos + 1;
*--s->gsp = val;
}
gc_mark(s, p->arr[pos]);
}
}
break;
case JS_MTAG_VARREF:
{
const JSVarRef *p = ptr;
gc_mark(s, p->u.value);
}
break;
case JS_MTAG_FUNCTION_BYTECODE:
{
const JSFunctionBytecode *b = ptr;
gc_mark(s, b->func_name);
gc_mark(s, b->byte_code);
gc_mark(s, b->cpool);
gc_mark(s, b->vars);
gc_mark(s, b->ext_vars);
gc_mark(s, b->filename);
gc_mark(s, b->pc2line);
}
break;
default:
break;
}
}
}
static void gc_mark_root(GCMarkState *s, JSValue val)
{
gc_mark(s, val);
gc_mark_flush(s);
}
static BOOL gc_mb_is_marked(JSValue val)
{
JSFreeBlock *b;
if (!JS_IsPtr(val))
return FALSE;
b = (JSFreeBlock *)JS_VALUE_TO_PTR(val);
return b->gc_mark;
}
static void gc_mark_all(JSContext *ctx, BOOL keep_atoms)
{
GCMarkState s_s, *s = &s_s;
JSValue *sp, *sp_end;
s->ctx = ctx;
s->overflow = FALSE;
s->gs_top = ctx->sp;
s->gsp = s->gs_top;
#if 1
s->gs_bottom = (JSValue *)ctx->heap_free;
#else#endif
if ((uint8_t *)ctx->atom_table == ctx->heap_base &&
keep_atoms) {
uint8_t *ptr;
for(ptr = (uint8_t *)ctx->atom_table;
ptr < (uint8_t *)(ctx->atom_table + JS_ATOM_END);
ptr += get_mblock_size(ptr)) {
gc_mark_root(s, JS_VALUE_FROM_PTR(ptr));
}
}
sp_end = ctx->class_proto + 2 * ctx->class_count;
for(sp = &ctx->current_exception; sp < sp_end; sp++) {
gc_mark_root(s, *sp);
}
for(sp = ctx->sp; sp < (JSValue *)ctx->stack_top; sp++) {
gc_mark_root(s, *sp);
}
{
JSGCRef *ref;
for(ref = ctx->top_gc_ref; ref != NULL; ref = ref->prev) {
gc_mark_root(s, ref->val);
}
for(ref = ctx->last_gc_ref; ref != NULL; ref = ref->prev) {
gc_mark_root(s, ref->val);
}
}
if (ctx->parse_state) {
JSParseState *ps = ctx->parse_state;
gc_mark_root(s, ps->source_str);
gc_mark_root(s, ps->filename_str);
gc_mark_root(s, ps->token.value);
gc_mark_root(s, ps->cur_func);
gc_mark_root(s, ps->byte_code);
}
while (s->overflow) {
uint8_t *ptr;
int size;
JSMemBlockHeader *mb;
s->overflow = FALSE;
ptr = ctx->heap_base;
while (ptr < ctx->heap_free) {
size = get_mblock_size(ptr);
mb = (JSMemBlockHeader *)ptr;
if (mb->gc_mark && mtag_has_references(mb->mtag)) {
if (mb->mtag == JS_MTAG_VALUE_ARRAY)
*--s->gsp = 0;
*--s->gsp = JS_VALUE_FROM_PTR(ptr);
gc_mark_flush(s);
}
ptr += size;
}
}
if (!JS_IsNull(ctx->unique_strings)) {
JSValueArray *arr = JS_VALUE_TO_PTR(ctx->unique_strings);
int i, j;
j = 0;
for(i = 0; i < arr->size; i++) {
if (gc_mb_is_marked(arr->arr[i])) {
arr->arr[j++] = arr->arr[i];
}
}
ctx->unique_strings_len = j;
if (j > 0) {
arr->gc_mark = 1;
if (j < arr->size) {
set_free_block(&arr->arr[j], (arr->size - j) * sizeof(JSValue));
arr->size = j;
}
} else {
arr->gc_mark = 0;
ctx->unique_strings = JS_NULL;
}
}
{
int i;
JSStringPosCacheEntry *ce;
for(i = 0; i < JS_STRING_POS_CACHE_SIZE; i++) {
ce = &ctx->string_pos_cache[i];
if (!gc_mb_is_marked(ce->str))
ce->str = JS_NULL;
}
}
{
uint8_t *ptr, *ptr1;
int size;
JSFreeBlock *b;
ptr = ctx->heap_base;
while (ptr < ctx->heap_free) {
size = get_mblock_size(ptr);
b = (JSFreeBlock *)ptr;
if (b->gc_mark) {
b->gc_mark = 0;
} else {
JSObject *p = (void *)ptr;
if (p->mtag == JS_MTAG_OBJECT && p->class_id >= JS_CLASS_USER &&
ctx->c_finalizer_table[p->class_id - JS_CLASS_USER] != NULL) {
ctx->c_finalizer_table[p->class_id - JS_CLASS_USER](ctx, p->u.user.opaque);
}
ptr1 = ptr + size;
while (ptr1 < ctx->heap_free && ((JSFreeBlock *)ptr1)->gc_mark == 0) {
ptr1 += get_mblock_size(ptr1);
}
size = ptr1 - ptr;
set_free_block(b, size);
}
ptr += size;
}
}
}
static JSValue js_value_from_pval(JSContext *ctx, JSValue *pval)
{
return JS_VALUE_FROM_PTR(pval);
}
static JSValue *js_value_to_pval(JSContext *ctx, JSValue val)
{
return JS_VALUE_TO_PTR(val);
}
static void gc_thread_pointer(JSContext *ctx, JSValue *pval)
{
JSValue val;
JSValue *ptr;
val = *pval;
if (!JS_IsPtr(val))
return;
ptr = JS_VALUE_TO_PTR(val);
if (JS_IS_ROM_PTR(ctx, ptr))
return;
*pval = *ptr;
*ptr = js_value_from_pval(ctx, pval);
}
static void gc_update_threaded_pointers(JSContext *ctx,
void *ptr, void *new_ptr)
{
JSValue val, *pv;
val = *(JSValue *)ptr;
if (JS_IsPtr(val)) {
for(;;) {
pv = js_value_to_pval(ctx, val);
val = *pv;
*pv = JS_VALUE_FROM_PTR(new_ptr);
if (!JS_IsPtr(val))
break;
}
*(JSValue *)ptr = val;
}
}
static void gc_thread_block(JSContext *ctx, void *ptr)
{
int mtag;
mtag = ((JSMemBlockHeader *)ptr)->mtag;
switch(mtag) {
case JS_MTAG_OBJECT:
{
JSObject *p = ptr;
gc_thread_pointer(ctx, &p->proto);
gc_thread_pointer(ctx, &p->props);
switch(p->class_id) {
case JS_CLASS_CLOSURE:
{
int i;
gc_thread_pointer(ctx, &p->u.closure.func_bytecode);
for(i = 0; i < p->extra_size - 1; i++)
gc_thread_pointer(ctx, &p->u.closure.var_refs[i]);
}
break;
case JS_CLASS_C_FUNCTION:
if (p->extra_size > 1)
gc_thread_pointer(ctx, &p->u.cfunc.params);
break;
case JS_CLASS_ARRAY:
gc_thread_pointer(ctx, &p->u.array.tab);
break;
case JS_CLASS_ERROR:
gc_thread_pointer(ctx, &p->u.error.message);
gc_thread_pointer(ctx, &p->u.error.stack);
break;
case JS_CLASS_ARRAY_BUFFER:
gc_thread_pointer(ctx, &p->u.array_buffer.byte_buffer);
break;
case JS_CLASS_UINT8C_ARRAY:
case JS_CLASS_INT8_ARRAY:
case JS_CLASS_UINT8_ARRAY:
case JS_CLASS_INT16_ARRAY:
case JS_CLASS_UINT16_ARRAY:
case JS_CLASS_INT32_ARRAY:
case JS_CLASS_UINT32_ARRAY:
case JS_CLASS_FLOAT32_ARRAY:
case JS_CLASS_FLOAT64_ARRAY:
gc_thread_pointer(ctx, &p->u.typed_array.buffer);
break;
case JS_CLASS_REGEXP:
gc_thread_pointer(ctx, &p->u.regexp.source);
gc_thread_pointer(ctx, &p->u.regexp.byte_code);
break;
}
}
break;
case JS_MTAG_VALUE_ARRAY:
{
JSValueArray *p = ptr;
int i;
for(i = 0; i < p->size; i++) {
gc_thread_pointer(ctx, &p->arr[i]);
}
}
break;
case JS_MTAG_VARREF:
{
JSVarRef *p = ptr;
gc_thread_pointer(ctx, &p->u.value);
}
break;
case JS_MTAG_FUNCTION_BYTECODE:
{
JSFunctionBytecode *b = ptr;
gc_thread_pointer(ctx, &b->func_name);
gc_thread_pointer(ctx, &b->byte_code);
gc_thread_pointer(ctx, &b->cpool);
gc_thread_pointer(ctx, &b->vars);
gc_thread_pointer(ctx, &b->ext_vars);
gc_thread_pointer(ctx, &b->filename);
gc_thread_pointer(ctx, &b->pc2line);
}
break;
default:
break;
}
}
static void gc_compact_heap(JSContext *ctx)
{
uint8_t *ptr, *new_ptr;
int size;
JSValue *sp, *sp_end;
sp_end = ctx->class_proto + 2 * ctx->class_count;
for(sp = &ctx->unique_strings; sp < sp_end; sp++) {
gc_thread_pointer(ctx, sp);
}
{
int i;
JSStringPosCacheEntry *ce;
for(i = 0; i < JS_STRING_POS_CACHE_SIZE; i++) {
ce = &ctx->string_pos_cache[i];
gc_thread_pointer(ctx, &ce->str);
}
}
for(sp = ctx->sp; sp < (JSValue *)ctx->stack_top; sp++) {
gc_thread_pointer(ctx, sp);
}
{
JSGCRef *ref;
for(ref = ctx->top_gc_ref; ref != NULL; ref = ref->prev) {
gc_thread_pointer(ctx, &ref->val);
}
for(ref = ctx->last_gc_ref; ref != NULL; ref = ref->prev) {
gc_thread_pointer(ctx, &ref->val);
}
}
if (ctx->parse_state) {
JSParseState *ps = ctx->parse_state;
gc_thread_pointer(ctx, &ps->source_str);
gc_thread_pointer(ctx, &ps->filename_str);
gc_thread_pointer(ctx, &ps->token.value);
gc_thread_pointer(ctx, &ps->cur_func);
gc_thread_pointer(ctx, &ps->byte_code);
}
new_ptr = ctx->heap_base;
ptr = ctx->heap_base;
while (ptr < ctx->heap_free) {
gc_update_threaded_pointers(ctx, ptr, new_ptr);
size = get_mblock_size(ptr);
if (js_get_mtag(ptr) != JS_MTAG_FREE) {
gc_thread_block(ctx, ptr);
new_ptr += size;
}
ptr += size;
}
new_ptr = ctx->heap_base;
ptr = ctx->heap_base;
while (ptr < ctx->heap_free) {
gc_update_threaded_pointers(ctx, ptr, new_ptr);
size = get_mblock_size(ptr);
if (js_get_mtag(ptr) != JS_MTAG_FREE) {
if (new_ptr != ptr) {
memmove(new_ptr, ptr, size);
}
new_ptr += size;
}
ptr += size;
}
ctx->heap_free = new_ptr;
if (ctx->parse_state) {
JSParseState *ps = ctx->parse_state;
if (JS_IsPtr(ps->source_str)) {
JSString *p = JS_VALUE_TO_PTR(ps->source_str);
ps->source_buf = p->buf;
}
}
ptr = ctx->heap_base;
while (ptr < ctx->heap_free) {
size = get_mblock_size(ptr);
if (js_get_mtag(ptr) == JS_MTAG_OBJECT) {
js_rehash_props(ctx, (JSObject *)ptr, TRUE);
}
ptr += size;
}
}
static void JS_GC2(JSContext *ctx, BOOL keep_atoms)
{
#ifdef DUMP_GC
js_printf(ctx, "GC : heap size=%u/%u stack_size=%u\n",
(uint32_t)(ctx->heap_free - ctx->heap_base),
(uint32_t)(ctx->stack_top - ctx->heap_base),
(uint32_t)(ctx->stack_top - (uint8_t *)ctx->sp));
#endif
#if defined(DEBUG_GC)
{
JSByteArray *arr;
if (JS_IsPtr(ctx->dummy_block)) {
arr = JS_VALUE_TO_PTR(ctx->dummy_block);
if (arr->size >= 8) {
js_shrink_byte_array(ctx, &ctx->dummy_block, arr->size - 4);
if (arr->size == 4) {
js_printf(ctx, "WARNING: debug GC: no longer modifying the addresses\n");
}
}
}
}
#endif
gc_mark_all(ctx, keep_atoms);
gc_compact_heap(ctx);
#ifdef DUMP_GC
js_printf(ctx, "AFTER: heap size=%u/%u stack_size=%u\n",
(uint32_t)(ctx->heap_free - ctx->heap_base),
(uint32_t)(ctx->stack_top - ctx->heap_base),
(uint32_t)(ctx->stack_top - (uint8_t *)ctx->sp));
#endif
}
void JS_GC(JSContext *ctx)
{
JS_GC2(ctx, TRUE);
}
#define JS_BYTECODE_VERSION_32 0x0001
#define JS_BYTECODE_VERSION (JS_BYTECODE_VERSION_32 | ((JSW & 8) << 12))
void JS_PrepareBytecode(JSContext *ctx,
JSBytecodeHeader *hdr,
const uint8_t **pdata_buf, uint32_t *pdata_len,
JSValue eval_code)
{
JSGCRef eval_code_ref;
int i;
ctx->empty_props = JS_NULL;
for(i = 0; i < ctx->class_count; i++) {
ctx->class_proto[i] = JS_NULL;
ctx->class_obj[i] = JS_NULL;
}
ctx->global_obj = JS_NULL;
#ifdef DEBUG_GC
ctx->dummy_block = JS_NULL;
#endif
JS_PUSH_VALUE(ctx, eval_code);
JS_GC2(ctx, FALSE);
JS_POP_VALUE(ctx, eval_code);
hdr->magic = JS_BYTECODE_MAGIC;
hdr->version = JS_BYTECODE_VERSION;
hdr->base_addr = (uintptr_t)ctx->heap_base;
hdr->unique_strings = ctx->unique_strings;
hdr->main_func = eval_code;
*pdata_buf = ctx->heap_base;
*pdata_len = ctx->heap_free - ctx->heap_base;
}
#if JSW == 8
typedef uint32_t JSValue_32;
typedef uint32_t JSWord_32;
#define JS_MB_HEADER_32 \
JSWord_32 gc_mark: 1; \
JSWord_32 mtag: (JS_MTAG_BITS - 1)
#define JS_MB_PAD_32(n) (32 - (n))
typedef struct {
JS_MB_HEADER_32;
JSWord_32 dummy: JS_MB_PAD_32(JS_MTAG_BITS);
} JSMemBlockHeader_32;
typedef struct {
JS_MB_HEADER_32;
JSWord_32 size: JS_MB_PAD_32(JS_MTAG_BITS);
JSValue_32 arr[];
} JSValueArray_32;
typedef struct {
JS_MB_HEADER_32;
JSWord_32 size: JS_MB_PAD_32(JS_MTAG_BITS);
uint8_t buf[];
} JSByteArray_32;
typedef struct {
JS_MB_HEADER_32;
JSWord_32 dummy: JS_MB_PAD_32(JS_MTAG_BITS);
struct __attribute__((packed)) {
double dval;
} u;
} JSFloat64_32;
#define JS_STRING_LEN_MAX_32 ((1 << (32 - JS_MTAG_BITS - 3)) - 1)
typedef struct {
JS_MB_HEADER_32;
JSWord_32 is_unique: 1;
JSWord_32 is_ascii: 1;
JSWord_32 is_numeric: 1;
JSWord_32 len: JS_MB_PAD_32(JS_MTAG_BITS + 3);
uint8_t buf[];
} JSString_32;
typedef struct {
JS_MB_HEADER_32;
JSWord_32 has_arguments : 1;
JSWord_32 has_local_func_name : 1;
JSWord_32 has_column : 1;
JSWord_32 arg_count : 16;
JSWord_32 dummy: JS_MB_PAD_32(JS_MTAG_BITS + 3 + 16);
JSValue_32 func_name;
JSValue_32 byte_code;
JSValue_32 cpool;
JSValue_32 vars;
JSValue_32 ext_vars;
uint16_t stack_size;
uint16_t ext_vars_len;
JSValue_32 filename;
JSValue_32 pc2line;
uint32_t source_pos;
} JSFunctionBytecode_32;
static int convert_mblock_64to32(void *ptr1, const void *ptr)
{
int mtag, i;
mtag = ((JSMemBlockHeader*)ptr)->mtag;
switch(mtag) {
case JS_MTAG_FUNCTION_BYTECODE:
{
const JSFunctionBytecode *b = ptr;
JSFunctionBytecode_32 *b1 = ptr1;
b1->gc_mark = b->gc_mark;
b1->mtag = b->mtag;
b1->has_arguments = b->has_arguments;
b1->has_local_func_name = b->has_local_func_name;
b1->has_column = b->has_column;
b1->arg_count = b->arg_count;
b1->dummy = 0;
b1->func_name = b->func_name;
b1->byte_code = b->byte_code;
b1->cpool = b->cpool;
b1->vars = b->vars;
b1->ext_vars = b->ext_vars;
b1->stack_size = b->stack_size;
b1->ext_vars_len = b->ext_vars_len;
b1->filename = b->filename;
b1->pc2line = b->pc2line;
b1->source_pos = b->source_pos;
}
break;
case JS_MTAG_FLOAT64:
{
const JSFloat64 *b = ptr;
JSFloat64_32 *b1 = ptr1;
b1->gc_mark = b->gc_mark;
b1->mtag = b->mtag;
b1->dummy = 0;
b1->u.dval = b->u.dval;
}
break;
case JS_MTAG_VALUE_ARRAY:
{
const JSValueArray *b = ptr;
JSValueArray_32 *b1 = ptr1;
b1->gc_mark = b->gc_mark;
b1->mtag = b->mtag;
b1->size = b->size;
for(i = 0; i < b1->size; i++)
b1->arr[i] = b->arr[i];
}
break;
case JS_MTAG_BYTE_ARRAY:
{
const JSByteArray *b = ptr;
JSByteArray_32 *b1 = ptr1;
b1->gc_mark = b->gc_mark;
b1->mtag = b->mtag;
b1->size = b->size;
memmove(b1->buf, b->buf, b1->size);
}
break;
case JS_MTAG_STRING:
{
const JSString *b = ptr;
JSString_32 *b1 = ptr1;
if (b->len > JS_STRING_LEN_MAX_32)
return -1;
b1->gc_mark = b->gc_mark;
b1->mtag = b->mtag;
b1->is_unique = b->is_unique;
b1->is_ascii = b->is_ascii;
b1->is_numeric = b->is_numeric;
b1->len = b->len;
memmove(b1->buf, b->buf, b1->len + 1);
}
break;
default:
abort();
}
return 0;
}
static int get_mblock_size_32(const void *ptr)
{
int mtag = ((JSMemBlockHeader_32 *)ptr)->mtag;
int size;
switch(mtag) {
case JS_MTAG_FLOAT64:
size = sizeof(JSFloat64_32);
break;
case JS_MTAG_STRING:
{
const JSString_32 *p = ptr;
size = sizeof(JSString_32) + ((p->len + 4) & ~(4 - 1));
}
break;
case JS_MTAG_BYTE_ARRAY:
{
const JSByteArray_32 *p = ptr;
size = sizeof(JSByteArray_32) + ((p->size + 4 - 1) & ~(4 - 1));
}
break;
case JS_MTAG_VALUE_ARRAY:
{
const JSValueArray_32 *p = ptr;
size = sizeof(JSValueArray_32) + p->size * sizeof(p->arr[0]);
}
break;
case JS_MTAG_FUNCTION_BYTECODE:
size = sizeof(JSFunctionBytecode_32);
break;
default:
size = 0;
assert(0);
}
return size;
}
static int gc_compact_heap_64to32(JSContext *ctx)
{
uint8_t *ptr;
int size, size_32;
uintptr_t new_offset;
gc_thread_pointer(ctx, &ctx->unique_strings);
{
JSGCRef *ref;
for(ref = ctx->top_gc_ref; ref != NULL; ref = ref->prev) {
gc_thread_pointer(ctx, &ref->val);
}
}
new_offset = 0;
ptr = ctx->heap_base;
while (ptr < ctx->heap_free) {
gc_update_threaded_pointers(ctx, ptr, (uint8_t *)new_offset);
size = get_mblock_size(ptr);
if (js_get_mtag(ptr) != JS_MTAG_FREE) {
gc_thread_block(ctx, ptr);
size_32 = get_mblock_size_32(ptr);
new_offset += size_32;
}
ptr += size;
}
new_offset = 0;
ptr = ctx->heap_base;
while (ptr < ctx->heap_free) {
gc_update_threaded_pointers(ctx, ptr, (uint8_t *)new_offset);
size = get_mblock_size(ptr);
if (js_get_mtag(ptr) != JS_MTAG_FREE) {
size_32 = get_mblock_size_32(ptr);
if (convert_mblock_64to32(ctx->heap_base + new_offset, ptr))
return -1;
new_offset += size_32;
}
ptr += size;
}
ctx->heap_free = ctx->heap_base + new_offset;
return 0;
}
#ifdef JS_USE_SHORT_FLOAT
static int expand_short_float(JSContext *ctx, JSValue *pval)
{
JSFloat64 *f;
if (JS_IsShortFloat(*pval)) {
f = js_malloc(ctx, sizeof(JSFloat64), JS_MTAG_FLOAT64);
if (!f)
return -1;
f->u.dval = js_get_short_float(*pval);
*pval = JS_VALUE_FROM_PTR(f);
}
return 0;
}
static int expand_short_floats(JSContext *ctx)
{
uint8_t *ptr, *p_end;
int mtag, size;
ptr = ctx->heap_base;
p_end = ctx->heap_free;
while (ptr < p_end) {
size = get_mblock_size(ptr);
mtag = ((JSMemBlockHeader *)ptr)->mtag;
switch(mtag) {
case JS_MTAG_FUNCTION_BYTECODE:
break;
case JS_MTAG_VALUE_ARRAY:
{
JSValueArray *p = (JSValueArray *)ptr;
int i;
for(i = 0; i < p->size; i++) {
if (expand_short_float(ctx, &p->arr[i]))
return -1;
}
}
break;
case JS_MTAG_STRING:
case JS_MTAG_FLOAT64:
case JS_MTAG_BYTE_ARRAY:
break;
default:
abort();
}
ptr += size;
}
return 0;
}
#endif
int JS_PrepareBytecode64to32(JSContext *ctx,
JSBytecodeHeader32 *hdr,
const uint8_t **pdata_buf, uint32_t *pdata_len,
JSValue eval_code)
{
JSGCRef eval_code_ref;
int i;
ctx->empty_props = JS_NULL;
for(i = 0; i < ctx->class_count; i++) {
ctx->class_proto[i] = JS_NULL;
ctx->class_obj[i] = JS_NULL;
}
ctx->global_obj = JS_NULL;
#ifdef DEBUG_GC
ctx->dummy_block = JS_NULL;
#endif
JS_PUSH_VALUE(ctx, eval_code);
#ifdef JS_USE_SHORT_FLOAT
JS_GC2(ctx, FALSE);
if (expand_short_floats(ctx))
return -1;
#else
gc_mark_all(ctx, FALSE);
#endif
if (gc_compact_heap_64to32(ctx))
return -1;
JS_POP_VALUE(ctx, eval_code);
hdr->magic = JS_BYTECODE_MAGIC;
hdr->version = JS_BYTECODE_VERSION_32;
hdr->base_addr = 0;
hdr->unique_strings = ctx->unique_strings;
hdr->main_func = eval_code;
*pdata_buf = ctx->heap_base;
*pdata_len = ctx->heap_free - ctx->heap_base;
ctx->heap_free = ctx->heap_base;
return 0;
}
#endif
BOOL JS_IsBytecode(const uint8_t *buf, size_t buf_len)
{
const JSBytecodeHeader *hdr = (const JSBytecodeHeader *)buf;
return (buf_len >= sizeof(*hdr) && hdr->magic == JS_BYTECODE_MAGIC);
}
typedef struct {
JSContext *ctx;
uintptr_t offset;
BOOL update_atoms;
} BCRelocState;
static void bc_reloc_value(BCRelocState *s, JSValue *pval)
{
JSContext *ctx = s->ctx;
JSString *p;
JSValue val, str;
val = *pval;
if (JS_IsPtr(val)) {
val += s->offset;
if (s->update_atoms) {
p = JS_VALUE_TO_PTR(val);
if (p->mtag == JS_MTAG_STRING && p->is_unique) {
const JSValueArray *arr1;
int a, i;
for(i = 0; i < ctx->n_rom_atom_tables; i++) {
arr1 = ctx->rom_atom_tables[i];
str = find_atom(ctx, &a, arr1, arr1->size, val);
if (!JS_IsNull(str)) {
val = str;
break;
}
}
}
}
*pval = val;
}
}
int JS_RelocateBytecode2(JSContext *ctx, JSBytecodeHeader *hdr,
uint8_t *buf, uint32_t buf_len,
uintptr_t new_base_addr, BOOL update_atoms)
{
uint8_t *ptr, *p_end;
int size, mtag;
BCRelocState ss, *s = &ss;
if (hdr->magic != JS_BYTECODE_MAGIC)
return -1;
if (hdr->version != JS_BYTECODE_VERSION)
return -1;
s->ctx = ctx;
s->offset = new_base_addr - hdr->base_addr;
s->update_atoms = update_atoms;
bc_reloc_value(s, &hdr->unique_strings);
bc_reloc_value(s, &hdr->main_func);
ptr = buf;
p_end = buf + buf_len;
while (ptr < p_end) {
size = get_mblock_size(ptr);
mtag = ((JSMemBlockHeader *)ptr)->mtag;
switch(mtag) {
case JS_MTAG_FUNCTION_BYTECODE:
{
JSFunctionBytecode *b = (JSFunctionBytecode *)ptr;
bc_reloc_value(s, &b->func_name);
bc_reloc_value(s, &b->byte_code);
bc_reloc_value(s, &b->cpool);
bc_reloc_value(s, &b->vars);
bc_reloc_value(s, &b->ext_vars);
bc_reloc_value(s, &b->filename);
bc_reloc_value(s, &b->pc2line);
}
break;
case JS_MTAG_VALUE_ARRAY:
{
JSValueArray *p = (JSValueArray *)ptr;
int i;
for(i = 0; i < p->size; i++) {
bc_reloc_value(s, &p->arr[i]);
}
}
break;
case JS_MTAG_STRING:
case JS_MTAG_FLOAT64:
case JS_MTAG_BYTE_ARRAY:
break;
default:
abort();
}
ptr += size;
}
hdr->base_addr = new_base_addr;
return 0;
}
int JS_RelocateBytecode(JSContext *ctx,
uint8_t *buf, uint32_t buf_len)
{
uint8_t *data_ptr;
if (buf_len < sizeof(JSBytecodeHeader))
return -1;
data_ptr = buf + sizeof(JSBytecodeHeader);
return JS_RelocateBytecode2(ctx, (JSBytecodeHeader *)buf,
data_ptr,
buf_len - sizeof(JSBytecodeHeader),
(uintptr_t)data_ptr, TRUE);
}
JSValue JS_LoadBytecode(JSContext *ctx, const uint8_t *buf)
{
const JSBytecodeHeader *hdr = (const JSBytecodeHeader *)buf;
if (ctx->unique_strings_len != 0)
return JS_ThrowInternalError(ctx, "no atom must be defined in RAM");
if (ctx->n_rom_atom_tables >= N_ROM_ATOM_TABLES_MAX)
return JS_ThrowInternalError(ctx, "too many rom atom tables");
if (hdr->magic != JS_BYTECODE_MAGIC)
return JS_ThrowInternalError(ctx, "invalid bytecode magic");
if ((hdr->version & 0x8000) != (JS_BYTECODE_VERSION & 0x8000))
return JS_ThrowInternalError(ctx, "bytecode not saved for %d-bit", JSW * 8);
if (hdr->version != JS_BYTECODE_VERSION)
return JS_ThrowInternalError(ctx, "invalid bytecode version");
if (hdr->base_addr != (uintptr_t)(hdr + 1))
return JS_ThrowInternalError(ctx, "bytecode not relocated");
ctx->rom_atom_tables[ctx->n_rom_atom_tables++] = (JSValueArray *)JS_VALUE_TO_PTR(hdr->unique_strings);
return hdr->main_func;
}
JSValue js_function_constructor(JSContext *ctx, JSValue *this_val,
int argc, JSValue *argv)
{
StringBuffer b_s, *b = &b_s;
JSValue val;
int i, n;
argc &= ~FRAME_CF_CTOR;
string_buffer_push(ctx, b, 0);
string_buffer_puts(ctx, b, "(function anonymous(");
n = argc - 1;
for(i = 0; i < n; i++) {
if (i != 0) {
string_buffer_putc(ctx, b, ',');
}
if (string_buffer_concat(ctx, b, argv[i]))
goto done;
}
string_buffer_puts(ctx, b, "\n) {\n");
if (n >= 0) {
if (string_buffer_concat(ctx, b, argv[n]))
goto done;
}
string_buffer_puts(ctx, b, "\n})");
done:
val = string_buffer_pop(ctx, b);
if (JS_IsException(val))
return val;
val = JS_Parse2(ctx, val, NULL, 0, "<input>", JS_EVAL_RETVAL);
if (JS_IsException(val))
return val;
return JS_Run(ctx, val);
}
JSValue js_function_get_prototype(JSContext *ctx, JSValue *this_val,
int argc, JSValue *argv)
{
JSValue obj;
JSGCRef obj_ref;
if (!JS_IsPtr(*this_val)) {
if (JS_VALUE_GET_SPECIAL_TAG(*this_val) != JS_TAG_SHORT_FUNC)
goto fail;
return JS_UNDEFINED;
} else {
JSObject *p = JS_VALUE_TO_PTR(*this_val);
if (p->mtag != JS_MTAG_OBJECT)
goto fail;
if (p->class_id == JS_CLASS_CLOSURE) {
obj = JS_NewObject(ctx);
if (JS_IsException(obj))
return obj;
} else if (p->class_id == JS_CLASS_C_FUNCTION) {
return JS_UNDEFINED;
} else {
fail:
return JS_ThrowTypeError(ctx, "not a function");
}
JS_PUSH_VALUE(ctx, obj);
JS_DefinePropertyValue(ctx, obj, js_get_atom(ctx, JS_ATOM_constructor),
*this_val);
JS_POP_VALUE(ctx, obj);
JS_PUSH_VALUE(ctx, obj);
JS_DefinePropertyValue(ctx, *this_val, js_get_atom(ctx, JS_ATOM_prototype),
obj);
JS_POP_VALUE(ctx, obj);
}
return obj;
}
JSValue js_function_set_prototype(JSContext *ctx, JSValue *this_val,
int argc, JSValue *argv)
{
if (!JS_IsFunctionObject(ctx, *this_val))
return JS_ThrowTypeError(ctx, "not a function");
JS_DefinePropertyValue(ctx, *this_val, js_get_atom(ctx, JS_ATOM_prototype),
argv[0]);
return JS_UNDEFINED;
}
JSValue js_function_get_length_name(JSContext *ctx, JSValue *this_val,
int argc, JSValue *argv, int is_name)
{
JSFunctionBytecode *b;
JSValue ret = js_function_get_length_name1(ctx, this_val, is_name, &b);
if (JS_IsNull(ret))
return JS_ThrowTypeError(ctx, "not a function");
return ret;
}
JSValue js_function_toString(JSContext *ctx, JSValue *this_val,
int argc, JSValue *argv)
{
JSValue str, val;
JSGCRef str_ref;
str = js_function_get_length_name(ctx, this_val, 0, NULL, 1);
if (JS_IsException(str))
return str;
JS_PUSH_VALUE(ctx, str);
val = JS_NewString(ctx, "function ");
JS_POP_VALUE(ctx, str);
str = JS_ConcatString(ctx, val, str);
JS_PUSH_VALUE(ctx, str);
val = JS_NewString(ctx, "() {\n [native code]\n}");
JS_POP_VALUE(ctx, str);
return JS_ConcatString(ctx, str, val);
}
JSValue js_function_call(JSContext *ctx, JSValue *this_val,
int argc, JSValue *argv)
{
int i;
argc = max_int(argc, 1);
if (JS_StackCheck(ctx, argc + 1))
return JS_EXCEPTION;
for(i = 0; i < argc - 1; i++)
JS_PushArg(ctx, argv[argc - 1 - i]);
JS_PushArg(ctx, *this_val);
JS_PushArg(ctx, argv[0]);
return JS_NewTailCall(argc - 1);
}
JSValue js_function_apply(JSContext *ctx, JSValue *this_val,
int argc, JSValue *argv)
{
JSValueArray *arr;
JSObject *p;
int len, i;
p = js_get_object_class(ctx, argv[1], JS_CLASS_ARRAY);
if (!p)
return JS_ThrowTypeError(ctx, "not an array");
arr = JS_VALUE_TO_PTR(p->u.array.tab);
len = p->u.array.len;
if (len > JS_MAX_ARGC)
return JS_ThrowTypeError(ctx, "too many call arguments");
if (JS_StackCheck(ctx, len + 2))
return JS_EXCEPTION;
p = JS_VALUE_TO_PTR(argv[1]);
arr = JS_VALUE_TO_PTR(p->u.array.tab);
for(i = 0; i < len; i++)
JS_PushArg(ctx, arr->arr[len - 1 - i]);
JS_PushArg(ctx, *this_val);
JS_PushArg(ctx, argv[0]);
return JS_NewTailCall(len);
}
JSValue js_function_bind(JSContext *ctx, JSValue *this_val,
int argc, JSValue *argv)
{
int arg_count;
JSValueArray *arr;
int i;
arg_count = max_int(argc - 1, 0);
arr = js_alloc_value_array(ctx, 0, 2 + arg_count);
if (!arr)
return JS_EXCEPTION;
arr->arr[0] = *this_val;
for(i = 0; i < arg_count + 1; i++)
arr->arr[1 + i] = argv[i];
return JS_NewCFunctionParams(ctx, JS_CFUNCTION_bound, JS_VALUE_FROM_PTR(arr));
}
JSValue js_function_bound(JSContext *ctx, JSValue *this_val,
int argc, JSValue *argv, JSValue params)
{
JSValueArray *arr;
JSGCRef params_ref;
int i, err, size, argc2;
arr = JS_VALUE_TO_PTR(params);
size = arr->size;
JS_PUSH_VALUE(ctx, params);
err = JS_StackCheck(ctx, size + argc);
JS_POP_VALUE(ctx, params);
if (err)
return JS_EXCEPTION;
argc2 = size - 2 + argc;
if (argc2 > JS_MAX_ARGC)
return JS_ThrowTypeError(ctx, "too many call arguments");
arr = JS_VALUE_TO_PTR(params);
for(i = argc - 1; i >= 0; i--)
JS_PushArg(ctx, argv[i]);
for(i = size - 1; i >= 2; i--) {
JS_PushArg(ctx, arr->arr[i]);
}
JS_PushArg(ctx, arr->arr[0]);
JS_PushArg(ctx, arr->arr[1]);
return JS_NewTailCall(argc2);
}
JSValue js_number_constructor(JSContext *ctx, JSValue *this_val,
int argc, JSValue *argv)
{
double d;
if (argc & FRAME_CF_CTOR)
return JS_ThrowTypeError(ctx, "number constructor not supported");
if (argc == 0) {
return JS_NewShortInt(0);
} else {
if (JS_ToNumber(ctx, &d, argv[0]))
return JS_EXCEPTION;
return JS_NewFloat64(ctx, d);
}
}
static int js_thisNumberValue(JSContext *ctx, double *pres, JSValue val)
{
if (!JS_IsNumber(ctx, val)) {
JS_ThrowTypeError(ctx, "not a number");
return -1;
}
return JS_ToNumber(ctx, pres, val);
}
JSValue js_number_toString(JSContext *ctx, JSValue *this_val,
int argc, JSValue *argv)
{
int radix, flags;
double d;
if (js_thisNumberValue(ctx, &d, *this_val))
return JS_EXCEPTION;
if (JS_IsUndefined(argv[0])) {
radix = 10;
} else {
if (JS_ToInt32Sat(ctx, &radix, argv[0]))
return JS_EXCEPTION;
if (radix < 2 || radix > 36)
return JS_ThrowRangeError(ctx, "radix must be between 2 and 36");
}
flags = JS_DTOA_FORMAT_FREE;
if (radix != 10)
flags |= JS_DTOA_EXP_DISABLED;
return js_dtoa2(ctx, d, radix, 0, flags);
}
JSValue js_number_toFixed(JSContext *ctx, JSValue *this_val,
int argc, JSValue *argv)
{
int f, flags;
double d;
if (js_thisNumberValue(ctx, &d, *this_val))
return JS_EXCEPTION;
if (JS_ToInt32Sat(ctx, &f, argv[0]))
return JS_EXCEPTION;
if (f < 0 || f > 100)
return JS_ThrowRangeError(ctx, "invalid number of digits");
if (fabs(d) >= 1e21) {
flags = JS_DTOA_FORMAT_FREE;
} else {
flags = JS_DTOA_FORMAT_FRAC;
}
return js_dtoa2(ctx, d, 10, f, flags);
}
JSValue js_number_toExponential(JSContext *ctx, JSValue *this_val,
int argc, JSValue *argv)
{
int f, flags;
double d;
if (js_thisNumberValue(ctx, &d, *this_val))
return JS_EXCEPTION;
if (JS_ToInt32Sat(ctx, &f, argv[0]))
return JS_EXCEPTION;
if (JS_IsUndefined(argv[0]) || !isfinite(d)) {
f = 0;
flags = JS_DTOA_FORMAT_FREE;
} else {
if (f < 0 || f > 100)
return JS_ThrowRangeError(ctx, "invalid number of digits");
f++;
flags = JS_DTOA_FORMAT_FIXED;
}
return js_dtoa2(ctx, d, 10, f, flags | JS_DTOA_EXP_ENABLED);
}
JSValue js_number_toPrecision(JSContext *ctx, JSValue *this_val,
int argc, JSValue *argv)
{
int p, flags;
double d;
if (js_thisNumberValue(ctx, &d, *this_val))
return JS_EXCEPTION;
if (JS_IsUndefined(argv[0])) {
flags = JS_DTOA_FORMAT_FREE;
p = 0;
} else {
if (JS_ToInt32Sat(ctx, &p, argv[0]))
return JS_EXCEPTION;
if (!isfinite(d)) {
flags = JS_DTOA_FORMAT_FREE;
} else {
if (p < 1 || p > 100)
return JS_ThrowRangeError(ctx, "invalid number of digits");
flags = JS_DTOA_FORMAT_FIXED;
}
}
return js_dtoa2(ctx, d, 10, p, flags);
}
JSValue js_number_parseInt(JSContext *ctx, JSValue *this_val,
int argc, JSValue *argv)
{
int radix;
double d;
argv[0] = JS_ToString(ctx, argv[0]);
if (JS_IsException(argv[0]))
return JS_EXCEPTION;
if (JS_ToInt32(ctx, &radix, argv[1]))
return JS_EXCEPTION;
if (radix != 0 && (radix < 2 || radix > 36)) {
d = NAN;
} else {
if (js_atod1(ctx, &d, argv[0], radix, JS_ATOD_INT_ONLY))
return JS_EXCEPTION;
}
return JS_NewFloat64(ctx, d);
}
JSValue js_number_parseFloat(JSContext *ctx, JSValue *this_val,
int argc, JSValue *argv)
{
double d;
argv[0] = JS_ToString(ctx, argv[0]);
if (JS_IsException(argv[0]))
return JS_EXCEPTION;
if (js_atod1(ctx, &d, argv[0], 10, 0))
return JS_EXCEPTION;
return JS_NewFloat64(ctx, d);
}
JSValue js_boolean_constructor(JSContext *ctx, JSValue *this_val,
int argc, JSValue *argv)
{
if (argc & FRAME_CF_CTOR)
return JS_ThrowTypeError(ctx, "Boolean constructor not supported");
return JS_NewBool(JS_ToBool(ctx, argv[0]));
}
JSValue js_string_get_length(JSContext *ctx, JSValue *this_val,
int argc, JSValue *argv)
{
int len;
if (!JS_IsString(ctx, *this_val))
return JS_ThrowTypeError(ctx, "not a string");
len = js_string_len(ctx, *this_val);
return JS_NewShortInt(len);
}
JSValue js_string_set_length(JSContext *ctx, JSValue *this_val,
int argc, JSValue *argv)
{
return JS_UNDEFINED;
}
JSValue js_string_slice(JSContext *ctx, JSValue *this_val,
int argc, JSValue *argv)
{
int len, start, end;
*this_val = JS_ToStringCheckObject(ctx, *this_val);
if (JS_IsException(*this_val))
return JS_EXCEPTION;
len = js_string_len(ctx, *this_val);
if (JS_ToInt32Clamp(ctx, &start, argv[0], 0, len, len))
return JS_EXCEPTION;
end = len;
if (!JS_IsUndefined(argv[1])) {
if (JS_ToInt32Clamp(ctx, &end, argv[1], 0, len, len))
return JS_EXCEPTION;
}
return js_sub_string(ctx, *this_val, start, max_int(end, start));
}
JSValue js_string_substring(JSContext *ctx, JSValue *this_val,
int argc, JSValue *argv)
{
int a, b, start, end, len;
*this_val = JS_ToStringCheckObject(ctx, *this_val);
if (JS_IsException(*this_val))
return JS_EXCEPTION;
len = js_string_len(ctx, *this_val);
if (JS_ToInt32Clamp(ctx, &a, argv[0], 0, len, 0))
return JS_EXCEPTION;
b = len;
if (!JS_IsUndefined(argv[1])) {
if (JS_ToInt32Clamp(ctx, &b, argv[1], 0, len, 0))
return JS_EXCEPTION;
}
if (a < b) {
start = a;
end = b;
} else {
start = b;
end = a;
}
return js_sub_string(ctx, *this_val, start, end);
}
JSValue js_string_charAt(JSContext *ctx, JSValue *this_val,
int argc, JSValue *argv, int magic)
{
JSValue ret;
int idx, c;
*this_val = JS_ToStringCheckObject(ctx, *this_val);
if (JS_IsException(*this_val))
return JS_EXCEPTION;
if (JS_ToInt32Sat(ctx, &idx, argv[0]))
return JS_EXCEPTION;
if (idx < 0)
goto ret_undef;
c = string_getcp(ctx, *this_val, idx, (magic == magic_codePointAt));
if (c == -1) {
ret_undef:
if (magic == magic_charCodeAt)
ret = JS_NewFloat64(ctx, NAN);
else if (magic == magic_charAt)
ret = js_get_atom(ctx, JS_ATOM_empty);
else
ret = JS_UNDEFINED;
} else {
if (magic == magic_charCodeAt || magic == magic_codePointAt)
ret = JS_NewShortInt(c);
else
ret = JS_NewStringChar(c);
}
return ret;
}
JSValue js_string_constructor(JSContext *ctx, JSValue *this_val,
int argc, JSValue *argv)
{
if (argc & FRAME_CF_CTOR)
return JS_ThrowTypeError(ctx, "string constructor not supported");
if (argc <= 0) {
return js_get_atom(ctx, JS_ATOM_empty);
} else {
return JS_ToString(ctx, argv[0]);
}
}
JSValue js_string_fromCharCode(JSContext *ctx, JSValue *this_val,
int argc, JSValue *argv, int is_fromCodePoint)
{
int i;
StringBuffer b_s, *b = &b_s;
string_buffer_push(ctx, b, 0);
for(i = 0; i < argc; i++) {
int c;
if (JS_ToInt32(ctx, &c, argv[i]))
goto fail;
if (is_fromCodePoint) {
if (c < 0 || c > 0x10ffff) {
JS_ThrowRangeError(ctx, "invalid code point");
goto fail;
}
} else {
c &= 0xffff;
}
if (string_buffer_putc(ctx, b, c))
break;
}
return string_buffer_pop(ctx, b);
fail:
string_buffer_pop(ctx, b);
return JS_EXCEPTION;
}
JSValue js_string_concat(JSContext *ctx, JSValue *this_val,
int argc, JSValue *argv)
{
int i;
StringBuffer b_s, *b = &b_s;
JSValue r;
r = JS_ToStringCheckObject(ctx, *this_val);
if (JS_IsException(r))
return JS_EXCEPTION;
string_buffer_push(ctx, b, 0);
if (string_buffer_concat(ctx, b, r))
goto done;
for (i = 0; i < argc; i++) {
if (string_buffer_concat(ctx, b, argv[i]))
goto done;
}
done:
return string_buffer_pop(ctx, b);
}
JSValue js_string_indexOf(JSContext *ctx, JSValue *this_val,
int argc, JSValue *argv, int lastIndexOf)
{
int i, len, v_len, pos, start, stop, ret, inc, j;
*this_val = JS_ToStringCheckObject(ctx, *this_val);
if (JS_IsException(*this_val))
return JS_EXCEPTION;
argv[0] = JS_ToString(ctx, argv[0]);
if (JS_IsException(argv[0]))
return JS_EXCEPTION;
len = js_string_len(ctx, *this_val);
v_len = js_string_len(ctx, argv[0]);
if (lastIndexOf) {
pos = len - v_len;
if (argc > 1) {
double d;
if (JS_ToNumber(ctx, &d, argv[1]))
goto fail;
if (!isnan(d)) {
if (d <= 0)
pos = 0;
else if (d < pos)
pos = d;
}
}
start = pos;
stop = 0;
inc = -1;
} else {
pos = 0;
if (argc > 1) {
if (JS_ToInt32Clamp(ctx, &pos, argv[1], 0, len, 0))
goto fail;
}
start = pos;
stop = len - v_len;
inc = 1;
}
ret = -1;
if (len >= v_len && inc * (stop - start) >= 0) {
for (i = start;; i += inc) {
for(j = 0; j < v_len; j++) {
if (string_getc(ctx, *this_val, i + j) != string_getc(ctx, argv[0], j)) {
goto next;
}
}
ret = i;
break;
next:
if (i == stop)
break;
}
}
return JS_NewShortInt(ret);
fail:
return JS_EXCEPTION;
}
static int js_string_indexof(JSContext *ctx, JSValue str, JSValue needle,
int start, int str_len, int needle_len)
{
int i, j;
for(i = start; i <= str_len - needle_len; i++) {
for(j = 0; j < needle_len; j++) {
if (string_getc(ctx, str, i + j) !=
string_getc(ctx, needle, j)) {
goto next;
}
}
return i;
next: ;
}
return -1;
}
JSValue js_string_toLowerCase(JSContext *ctx, JSValue *this_val,
int argc, JSValue *argv, int to_lower)
{
StringBuffer b_s, *b = &b_s;
int i, c, len;
*this_val = JS_ToStringCheckObject(ctx, *this_val);
if (JS_IsException(*this_val))
return *this_val;
len = js_string_len(ctx, *this_val);
if (string_buffer_push(ctx, b, len))
return JS_EXCEPTION;
for(i = 0; i < len; i++) {
c = string_getc(ctx, *this_val, i);
if (to_lower) {
if (c >= 'A' && c <= 'Z')
c += 'a' - 'A';
} else {
if (c >= 'a' && c <= 'z')
c += 'A' - 'a';
}
string_buffer_putc(ctx, b, c);
}
return string_buffer_pop(ctx, b);
}
static force_inline BOOL unicode_is_space_ascii(uint32_t c)
{
return (c >= 0x0009 && c <= 0x000D) || (c == 0x0020);
}
static BOOL unicode_is_space_non_ascii(uint32_t c)
{
return (c == 0x00A0 ||
c == 0x1680 ||
(c >= 0x2000 && c <= 0x200A) ||
(c >= 0x2028 && c <= 0x2029) ||
c == 0x202F ||
c == 0x205F ||
c == 0x3000 ||
c == 0xFEFF);
}
static force_inline BOOL unicode_is_space(uint32_t c)
{
if (likely(c < 128)) {
return unicode_is_space_ascii(c);
} else {
return unicode_is_space_non_ascii(c);
}
}
JSValue js_string_trim(JSContext *ctx, JSValue *this_val,
int argc, JSValue *argv, int magic)
{
int a, b, len;
*this_val = JS_ToStringCheckObject(ctx, *this_val);
if (JS_IsException(*this_val))
return *this_val;
len = js_string_len(ctx, *this_val);
a = 0;
b = len;
if (magic & 1) {
while (a < len && unicode_is_space(string_getc(ctx, *this_val, a)))
a++;
}
if (magic & 2) {
while (b > a && unicode_is_space(string_getc(ctx, *this_val, b - 1)))
b--;
}
return js_sub_string(ctx, *this_val, a, b);
}
JSValue js_string_toString(JSContext *ctx, JSValue *this_val,
int argc, JSValue *argv)
{
if (!JS_IsString(ctx, *this_val))
return JS_ThrowTypeError(ctx, "not a string");
return *this_val;
}
JSValue js_string_repeat(JSContext *ctx, JSValue *this_val,
int argc, JSValue *argv)
{
StringBuffer b_s, *b = &b_s;
JSStringCharBuf buf;
JSString *p;
int n;
int64_t len;
if (!JS_IsString(ctx, *this_val))
return JS_ThrowTypeError(ctx, "not a string");
if (JS_ToInt32Sat(ctx, &n, argv[0]))
return -1;
p = get_string_ptr(ctx, &buf, *this_val);
if (n < 0 || (len = (int64_t)n * p->len) > JS_STRING_LEN_MAX)
return JS_ThrowRangeError(ctx, "invalid repeat count");
if (p->len == 0 || n == 1)
return *this_val;
if (string_buffer_push(ctx, b, len))
return JS_EXCEPTION;
while (n-- > 0) {
string_buffer_concat_str(ctx, b, *this_val);
}
return string_buffer_pop(ctx, b);
}
JSValue js_object_constructor(JSContext *ctx, JSValue *this_val,
int argc, JSValue *argv)
{
argc &= ~FRAME_CF_CTOR;
if (argc <= 0) {
return JS_NewObject(ctx);
} else {
return argv[0];
}
}
JSValue js_object_defineProperty(JSContext *ctx, JSValue *this_val,
int argc, JSValue *argv)
{
JSValue *pobj, *pprop, *pdesc;
JSValue val, getter, setter;
JSGCRef val_ref, getter_ref;
int flags;
pobj = &argv[0];
pprop = &argv[1];
pdesc = &argv[2];
if (!JS_IsObject(ctx, *pobj))
return JS_ThrowTypeErrorNotAnObject(ctx);
*pprop = JS_ToPropertyKey(ctx, *pprop);
if (JS_IsException(*pprop))
return JS_EXCEPTION;
val = JS_UNDEFINED;
getter = JS_UNDEFINED;
setter = JS_UNDEFINED;
flags = 0;
if (JS_HasProperty(ctx, *pdesc, js_get_atom(ctx, JS_ATOM_value))) {
flags |= JS_DEF_PROP_HAS_VALUE;
val = JS_GetProperty(ctx, *pdesc, js_get_atom(ctx, JS_ATOM_value));
if (JS_IsException(val))
return JS_EXCEPTION;
}
if (JS_HasProperty(ctx, *pdesc, js_get_atom(ctx, JS_ATOM_get))) {
flags |= JS_DEF_PROP_HAS_GET;
JS_PUSH_VALUE(ctx, val);
getter = JS_GetProperty(ctx, *pdesc, js_get_atom(ctx, JS_ATOM_get));
JS_POP_VALUE(ctx, val);
if (JS_IsException(getter))
return JS_EXCEPTION;
if (!JS_IsUndefined(getter) && !JS_IsFunction(ctx, getter))
goto bad_getset;
}
if (JS_HasProperty(ctx, *pdesc, js_get_atom(ctx, JS_ATOM_set))) {
flags |= JS_DEF_PROP_HAS_SET;
JS_PUSH_VALUE(ctx, val);
JS_PUSH_VALUE(ctx, getter);
setter = JS_GetProperty(ctx, *pdesc, js_get_atom(ctx, JS_ATOM_set));
JS_POP_VALUE(ctx, getter);
JS_POP_VALUE(ctx, val);
if (JS_IsException(setter))
return JS_EXCEPTION;
if (!JS_IsUndefined(setter) && !JS_IsFunction(ctx, setter)) {
bad_getset:
return JS_ThrowTypeError(ctx, "invalid getter or setter");
}
}
if (flags & (JS_DEF_PROP_HAS_GET | JS_DEF_PROP_HAS_SET)) {
if (flags & JS_DEF_PROP_HAS_VALUE)
return JS_ThrowTypeError(ctx, "cannot have both value and get/set");
val = getter;
}
val = JS_DefinePropertyInternal(ctx, *pobj, *pprop, val, setter,
flags | JS_DEF_PROP_LOOKUP);
if (JS_IsException(val))
return val;
return *pobj;
}
JSValue js_object_getPrototypeOf(JSContext *ctx, JSValue *this_val,
int argc, JSValue *argv)
{
JSObject *p;
if (!JS_IsObject(ctx, argv[0]))
return JS_ThrowTypeErrorNotAnObject(ctx);
p = JS_VALUE_TO_PTR(argv[0]);
return p->proto;
}
static JSValue js_set_prototype_internal(JSContext *ctx, JSValue obj, JSValue proto)
{
JSObject *p, *p1;
p = JS_VALUE_TO_PTR(obj);
if (p->proto != proto) {
if (proto != JS_NULL) {
p1 = JS_VALUE_TO_PTR(proto);
for(;;) {
if (p1 == p)
return JS_ThrowTypeError(ctx, "circular prototype chain");
if (p1->proto == JS_NULL)
break;
p1 = JS_VALUE_TO_PTR(p1->proto);
}
}
p->proto = proto;
}
return JS_UNDEFINED;
}
JSValue js_object_setPrototypeOf(JSContext *ctx, JSValue *this_val,
int argc, JSValue *argv)
{
JSValue proto;
if (!JS_IsObject(ctx, argv[0]))
return JS_ThrowTypeErrorNotAnObject(ctx);
proto = argv[1];
if (proto != JS_NULL && !JS_IsObject(ctx, proto))
return JS_ThrowTypeError(ctx, "not a prototype");
if (JS_IsException(js_set_prototype_internal(ctx, argv[0], proto)))
return JS_EXCEPTION;
return argv[0];
}
JSValue js_object_create(JSContext *ctx, JSValue *this_val,
int argc, JSValue *argv)
{
JSValue proto;
proto = argv[0];
if (proto != JS_NULL && !JS_IsObject(ctx, proto))
return JS_ThrowTypeError(ctx, "not a prototype");
if (argc >= 2)
return JS_ThrowTypeError(ctx, "unsupported additional properties");
return JS_NewObjectProtoClass(ctx, proto, JS_CLASS_OBJECT, 0);
}
JSValue js_object_keys(JSContext *ctx, JSValue *this_val,
int argc, JSValue *argv)
{
JSObject *p, *pret;
JSValue ret, str;
JSValueArray *arr, *ret_arr;
int array_len, prop_count, hash_mask, alloc_size, i, j, pos;
JSGCRef ret_ref;
if (!JS_IsObject(ctx, argv[0]))
return JS_ThrowTypeErrorNotAnObject(ctx);
p = JS_VALUE_TO_PTR(argv[0]);
if (p->class_id == JS_CLASS_ARRAY) {
array_len = p->u.array.len;
} else if (p->class_id >= JS_CLASS_UINT8C_ARRAY && p->class_id <= JS_CLASS_FLOAT64_ARRAY) {
array_len = p->u.typed_array.len;
} else {
array_len = 0;
}
arr = JS_VALUE_TO_PTR(p->props);
prop_count = JS_VALUE_GET_INT(arr->arr[0]);
hash_mask = JS_VALUE_GET_INT(arr->arr[1]);
alloc_size = array_len + prop_count;
ret = JS_NewArray(ctx, alloc_size);
if (JS_IsException(ret))
return ret;
pos = 0;
for(i = 0; i < array_len; i++) {
JS_PUSH_VALUE(ctx, ret);
str = JS_ToString(ctx, JS_NewShortInt(i));
JS_POP_VALUE(ctx, ret);
if (JS_IsException(str))
return str;
pret = JS_VALUE_TO_PTR(ret);
ret_arr = JS_VALUE_TO_PTR(pret->u.array.tab);
ret_arr->arr[pos++] = str;
}
for(i = 0, j = 0; j < prop_count; i++) {
JSProperty *pr;
p = JS_VALUE_TO_PTR(argv[0]);
arr = JS_VALUE_TO_PTR(p->props);
pr = (JSProperty *)&arr->arr[2 + hash_mask + 1 + 3 * i];
if (pr->key != JS_UNINITIALIZED) {
JS_PUSH_VALUE(ctx, ret);
str = JS_ToString(ctx, pr->key);
JS_POP_VALUE(ctx, ret);
if (JS_IsException(str))
return str;
pret = JS_VALUE_TO_PTR(ret);
ret_arr = JS_VALUE_TO_PTR(pret->u.array.tab);
ret_arr->arr[pos++] = str;
j++;
}
}
pret = JS_VALUE_TO_PTR(ret);
pret->u.array.len = pos;
return ret;
}
JSValue js_object_hasOwnProperty(JSContext *ctx, JSValue *this_val,
int argc, JSValue *argv)
{
JSObject *p;
JSValue prop;
int array_len, idx;
if (JS_IsNull(*this_val) || JS_IsUndefined(*this_val))
return JS_ThrowTypeError(ctx, "cannot convert to object");
if (!JS_IsObject(ctx, *this_val))
return JS_FALSE;
prop = JS_ToPropertyKey(ctx, argv[0]);
p = JS_VALUE_TO_PTR(*this_val);
if (p->class_id == JS_CLASS_ARRAY) {
array_len = p->u.array.len;
goto check_array;
} else if (p->class_id >= JS_CLASS_UINT8C_ARRAY && p->class_id <= JS_CLASS_FLOAT64_ARRAY) {
array_len = p->u.typed_array.len;
check_array:
if (JS_IsInt(prop)) {
idx = JS_VALUE_GET_INT(prop);
return JS_NewBool((idx >= 0 && idx < array_len));
}
}
return JS_NewBool((find_own_property(ctx, p, prop) != NULL));
}
JSValue js_object_toString(JSContext *ctx, JSValue *this_val,
int argc, JSValue *argv)
{
const char *str;
char buf[64];
if (JS_IsIntOrShortFloat(*this_val)) {
goto number;
} else if (!JS_IsPtr(*this_val)) {
switch(JS_VALUE_GET_SPECIAL_TAG(*this_val)) {
case JS_TAG_NULL:
str = "Null";
break;
case JS_TAG_UNDEFINED:
str = "Undefined";
break;
case JS_TAG_SHORT_FUNC:
str = "Function";
break;
case JS_TAG_BOOL:
str = "Boolean";
break;
case JS_TAG_STRING_CHAR:
goto string;
default:
goto object;
}
} else {
JSObject *p = JS_VALUE_TO_PTR(*this_val);
switch(p->mtag) {
case JS_MTAG_OBJECT:
switch(p->class_id) {
case JS_CLASS_ARRAY:
str = "Array";
break;
case JS_CLASS_ERROR:
str = "Error";
break;
case JS_CLASS_CLOSURE:
case JS_CLASS_C_FUNCTION:
str = "Function";
break;
default:
object:
str = "Object";
break;
}
break;
case JS_MTAG_STRING:
string:
str = "String";
break;
case JS_MTAG_FLOAT64:
number:
str = "Number";
break;
default:
goto object;
}
}
js_snprintf(buf, sizeof(buf), "[object %s]", str);
return JS_NewString(ctx, buf);
}
JSValue js_error_constructor(JSContext *ctx, JSValue *this_val,
int argc, JSValue *argv, int magic)
{
JSValue obj, msg;
JSObject *p;
JSGCRef obj_ref;
argc &= ~FRAME_CF_CTOR;
obj = JS_NewObjectProtoClass(ctx, ctx->class_proto[magic], JS_CLASS_ERROR,
sizeof(JSErrorData));
if (JS_IsException(obj))
return obj;
p = JS_VALUE_TO_PTR(obj);
p->u.error.message = JS_NULL;
p->u.error.stack = JS_NULL;
if (!JS_IsUndefined(argv[0])) {
JS_PUSH_VALUE(ctx, obj);
msg = JS_ToString(ctx, argv[0]);
JS_POP_VALUE(ctx, obj);
if (JS_IsException(msg))
return msg;
p = JS_VALUE_TO_PTR(obj);
p->u.error.message = msg;
} else {
p = JS_VALUE_TO_PTR(obj);
p->u.error.message = js_get_atom(ctx, JS_ATOM_empty);
}
JS_PUSH_VALUE(ctx, obj);
build_backtrace(ctx, obj, NULL, 0, 0, 1);
JS_POP_VALUE(ctx, obj);
return obj;
}
JSValue js_error_toString(JSContext *ctx, JSValue *this_val,
int argc, JSValue *argv)
{
JSObject *p;
JSValue name;
StringBuffer b_s, *b = &b_s;
if (!JS_IsError(ctx, *this_val))
return JS_ThrowTypeError(ctx, "not an Error object");
name = JS_GetProperty(ctx, *this_val, js_get_atom(ctx, JS_ATOM_name));
if (JS_IsException(name))
return name;
if (JS_IsUndefined(name))
name = js_get_atom(ctx, JS_ATOM_Error);
else
name = JS_ToString(ctx, name);
if (JS_IsException(name))
return name;
string_buffer_push(ctx, b, 0);
string_buffer_concat(ctx, b, name);
p = JS_VALUE_TO_PTR(*this_val);
if (p->u.error.message != JS_NULL) {
string_buffer_puts(ctx, b, ": ");
p = JS_VALUE_TO_PTR(*this_val);
string_buffer_concat(ctx, b, p->u.error.message);
}
return string_buffer_pop(ctx, b);
}
JSValue js_error_get_message(JSContext *ctx, JSValue *this_val,
int argc, JSValue *argv, int magic)
{
JSObject *p;
if (!JS_IsError(ctx, *this_val))
return JS_ThrowTypeError(ctx, "not an Error object");
p = JS_VALUE_TO_PTR(*this_val);
if (magic == 0)
return p->u.error.message;
else
return p->u.error.stack;
}
static JSObject *js_get_array(JSContext *ctx, JSValue obj)
{
JSObject *p;
p = js_get_object_class(ctx, obj, JS_CLASS_ARRAY);
if (!p) {
JS_ThrowTypeError(ctx, "not an array");
return NULL;
}
return p;
}
JSValue js_array_get_length(JSContext *ctx, JSValue *this_val,
int argc, JSValue *argv)
{
JSObject *p;
p = js_get_array(ctx, *this_val);
if (!p)
return JS_EXCEPTION;
return JS_NewShortInt(p->u.array.len);
}
static int js_array_resize(JSContext *ctx, JSValue *this_val, int new_len)
{
JSObject *p;
int i;
if (new_len < 0 || new_len > JS_SHORTINT_MAX) {
JS_ThrowTypeError(ctx, "invalid array length");
return -1;
}
p = JS_VALUE_TO_PTR(*this_val);
if (new_len < p->u.array.len) {
JSValueArray *arr = JS_VALUE_TO_PTR(p->u.array.tab);
if (new_len < (arr->size / 2) && arr->size >= 4) {
js_shrink_value_array(ctx, &p->u.array.tab, new_len);
p = JS_VALUE_TO_PTR(*this_val);
} else {
for(i = new_len; i < p->u.array.len; i++)
arr->arr[i] = JS_UNDEFINED;
}
} else if (new_len > p->u.array.len) {
JSValueArray *arr;
JSValue new_tab;
new_tab = js_resize_value_array(ctx, p->u.array.tab, new_len);
if (JS_IsException(new_tab))
return -1;
p = JS_VALUE_TO_PTR(*this_val);
p->u.array.tab = new_tab;
arr = JS_VALUE_TO_PTR(p->u.array.tab);
for(i = p->u.array.len; i < new_len; i++)
arr->arr[i] = JS_UNDEFINED;
}
p->u.array.len = new_len;
return 0;
}
JSValue js_array_set_length(JSContext *ctx, JSValue *this_val,
int argc, JSValue *argv)
{
int new_len;
if (!js_get_array(ctx, *this_val))
return JS_EXCEPTION;
if (JS_ToInt32(ctx, &new_len, argv[0]))
return JS_EXCEPTION;
if (js_array_resize(ctx, this_val, new_len))
return JS_EXCEPTION;
return JS_UNDEFINED;
}
JSValue js_array_constructor(JSContext *ctx, JSValue *this_val,
int argc, JSValue *argv)
{
JSValue obj;
JSObject *p;
int len, i;
BOOL has_init;
argc &= ~FRAME_CF_CTOR;
if (argc == 1 && JS_IsNumber(ctx, argv[0])) {
if (JS_ToInt32(ctx, &len, argv[0]))
return JS_EXCEPTION;
has_init = FALSE;
} else {
len = argc;
has_init = TRUE;
}
if (len < 0 || len > JS_SHORTINT_MAX)
return JS_ThrowRangeError(ctx, "invalid array length");
obj = JS_NewArray(ctx, len);
if (JS_IsException(obj))
return obj;
p = JS_VALUE_TO_PTR(obj);
p->u.array.len = len;
if (has_init) {
JSValueArray *arr = JS_VALUE_TO_PTR(p->u.array.tab);
for(i = 0; i < argc; i++) {
arr->arr[i] = argv[i];
}
}
return obj;
}
JSValue js_array_push(JSContext *ctx, JSValue *this_val,
int argc, JSValue *argv, int is_unshift)
{
JSObject *p;
int new_len, i, from;
JSValueArray *arr;
JSValue new_tab;
p = js_get_array(ctx, *this_val);
if (!p)
return JS_EXCEPTION;
from = p->u.array.len;
new_len = from + argc;
if (new_len > JS_SHORTINT_MAX)
return JS_ThrowRangeError(ctx, "invalid array length");
new_tab = js_resize_value_array(ctx, p->u.array.tab, new_len);
if (JS_IsException(new_tab))
return JS_EXCEPTION;
p = JS_VALUE_TO_PTR(*this_val);
p->u.array.tab = new_tab;
p->u.array.len = new_len;
arr = JS_VALUE_TO_PTR(p->u.array.tab);
if (is_unshift && argc > 0) {
memmove(arr->arr + argc, arr->arr, from * sizeof(JSValue));
from = 0;
}
for(i = 0; i < argc; i++) {
arr->arr[from + i] = argv[i];
}
return JS_NewShortInt(new_len);
}
JSValue js_array_pop(JSContext *ctx, JSValue *this_val,
int argc, JSValue *argv)
{
JSObject *p;
JSValue ret;
p = js_get_array(ctx, *this_val);
if (!p)
return JS_EXCEPTION;
if (p->u.array.len > 0) {
JSValueArray *arr = JS_VALUE_TO_PTR(p->u.array.tab);
ret = arr->arr[--p->u.array.len];
} else {
ret = JS_UNDEFINED;
}
return ret;
}
JSValue js_array_shift(JSContext *ctx, JSValue *this_val,
int argc, JSValue *argv)
{
JSObject *p;
JSValue ret;
p = js_get_array(ctx, *this_val);
if (!p)
return JS_EXCEPTION;
if (p->u.array.len > 0) {
JSValueArray *arr = JS_VALUE_TO_PTR(p->u.array.tab);
ret = arr->arr[0];
p->u.array.len--;
memmove(arr->arr, arr->arr + 1, p->u.array.len * sizeof(JSValue));
} else {
ret = JS_UNDEFINED;
}
return ret;
}
JSValue js_array_join(JSContext *ctx, JSValue *this_val,
int argc, JSValue *argv)
{
uint32_t i, len;
BOOL is_array;
JSValue sep, val;
JSGCRef sep_ref;
JSObject *p;
JSValueArray *arr;
StringBuffer b_s, *b = &b_s;
if (!JS_IsObject(ctx, *this_val))
return JS_ThrowTypeErrorNotAnObject(ctx);
p = JS_VALUE_TO_PTR(*this_val);
is_array = (p->class_id == JS_CLASS_ARRAY);
if (is_array) {
len = p->u.array.len;
} else {
if (js_get_length32(ctx, &len, *this_val))
return JS_EXCEPTION;
}
if (argc > 0 && !JS_IsUndefined(argv[0])) {
sep = JS_ToString(ctx, argv[0]);
if (JS_IsException(sep))
return sep;
} else {
sep = JS_NewStringChar(',');
}
JS_PUSH_VALUE(ctx, sep);
string_buffer_push(ctx, b, 0);
for(i = 0; i < len; i++) {
if (i > 0) {
if (string_buffer_concat(ctx, b, sep_ref.val))
goto exception;
}
if (is_array) {
p = JS_VALUE_TO_PTR(*this_val);
arr = JS_VALUE_TO_PTR(p->u.array.tab);
if (i < p->u.array.len)
val = arr->arr[i];
else
val = JS_UNDEFINED;
} else {
val = JS_GetPropertyUint32(ctx, *this_val, i);
if (JS_IsException(val))
goto exception;
}
if (!JS_IsUndefined(val) && !JS_IsNull(val)) {
if (string_buffer_concat(ctx, b, val))
goto exception;
}
}
val = string_buffer_pop(ctx, b);
JS_POP_VALUE(ctx, sep);
return val;
exception:
string_buffer_pop(ctx, b);
JS_POP_VALUE(ctx, sep);
return JS_EXCEPTION;
}
JSValue js_array_toString(JSContext *ctx, JSValue *this_val,
int argc, JSValue *argv)
{
return js_array_join(ctx, this_val, 0, NULL);
}
JSValue js_array_isArray(JSContext *ctx, JSValue *this_val,
int argc, JSValue *argv)
{
JSObject *p;
p = js_get_object_class(ctx, argv[0], JS_CLASS_ARRAY);
return JS_NewBool(p != NULL);
}
JSValue js_array_reverse(JSContext *ctx, JSValue *this_val,
int argc, JSValue *argv)
{
int len;
JSObject *p;
JSValueArray *arr;
p = js_get_array(ctx, *this_val);
if (!p)
return JS_EXCEPTION;
len = p->u.array.len;
arr = JS_VALUE_TO_PTR(p->u.array.tab);
js_reverse_val(arr->arr, len);
return *this_val;
}
JSValue js_array_concat(JSContext *ctx, JSValue *this_val,
int argc, JSValue *argv)
{
JSObject *p;
int len, i, j, pos;
int64_t len64;
JSValue obj, val;
JSValueArray *arr, *arr1;
p = js_get_array(ctx, *this_val);
if (!p)
return JS_EXCEPTION;
len64 = p->u.array.len;
for(i = 0; i < argc; i++) {
p = js_get_object_class(ctx, argv[i], JS_CLASS_ARRAY);
if (p) {
len64 += p->u.array.len;
} else {
len64++;
}
}
if (len64 > JS_SHORTINT_MAX)
return JS_ThrowTypeError(ctx, "Array loo long");
len = len64;
obj = JS_NewArray(ctx, len);
if (JS_IsException(obj))
return obj;
p = JS_VALUE_TO_PTR(obj);
arr = JS_VALUE_TO_PTR(p->u.array.tab);
pos = 0;
for(i = -1; i < argc; i++) {
val = i == -1 ? *this_val : argv[i];
p = js_get_object_class(ctx, val, JS_CLASS_ARRAY);
if (p) {
arr1 = JS_VALUE_TO_PTR(p->u.array.tab);
for(j = 0; j < p->u.array.len; j++)
arr->arr[pos + j] = arr1->arr[j];
pos += p->u.array.len;
} else {
arr->arr[pos++] = val;
}
}
return obj;
}
JSValue js_array_indexOf(JSContext *ctx, JSValue *this_val,
int argc, JSValue *argv, int is_lastIndexOf)
{
JSObject *p;
int len, n, res;
JSValueArray *arr;
p = js_get_array(ctx, *this_val);
if (!p)
return JS_EXCEPTION;
len = p->u.array.len;
if (is_lastIndexOf) {
n = len - 1;
} else {
n = 0;
}
if (argc > 1) {
if (JS_ToInt32Clamp(ctx, &n, argv[1],
-is_lastIndexOf, len - is_lastIndexOf, len))
return JS_EXCEPTION;
}
p = JS_VALUE_TO_PTR(*this_val);
len = p->u.array.len;
arr = JS_VALUE_TO_PTR(p->u.array.tab);
res = -1;
if (is_lastIndexOf) {
n = min_int(n, len - 1);
for(;n >= 0; n--) {
if (js_strict_eq(ctx, argv[0], arr->arr[n])) {
res = n;
break;
}
}
} else {
for(;n < len; n++) {
if (js_strict_eq(ctx, argv[0], arr->arr[n])) {
res = n;
break;
}
}
}
return JS_NewShortInt(res);
}
JSValue js_array_slice(JSContext *ctx, JSValue *this_val,
int argc, JSValue *argv)
{
JSObject *p, *p1;
int len, start, final, k;
JSValueArray *arr, *arr1;
JSValue obj;
p = js_get_array(ctx, *this_val);
if (!p)
return JS_EXCEPTION;
len = p->u.array.len;
if (JS_ToInt32Clamp(ctx, &start, argv[0], 0, len, len))
return JS_EXCEPTION;
final = len;
if (!JS_IsUndefined(argv[1])) {
if (JS_ToInt32Clamp(ctx, &final, argv[1], 0, len, len))
return JS_EXCEPTION;
}
p = JS_VALUE_TO_PTR(*this_val);
len = p->u.array.len;
final = min_int(final, len);
obj = JS_NewArray(ctx, max_int(final - start, 0));
if (JS_IsException(obj))
return obj;
p = JS_VALUE_TO_PTR(*this_val);
arr = JS_VALUE_TO_PTR(p->u.array.tab);
p1 = JS_VALUE_TO_PTR(obj);
arr1 = JS_VALUE_TO_PTR(p1->u.array.tab);
for(k = start; k < final; k++) {
arr1->arr[k - start] = arr->arr[k];
}
return obj;
}
JSValue js_array_splice(JSContext *ctx, JSValue *this_val,
int argc, JSValue *argv)
{
JSObject *p, *p1;
int start, len, item_count, del_count, new_len, i, ret;
JSValueArray *arr, *arr1;
JSValue obj;
JSGCRef obj_ref;
p = js_get_array(ctx, *this_val);
if (!p)
return JS_EXCEPTION;
len = p->u.array.len;
if (JS_ToInt32Clamp(ctx, &start, argv[0], 0, len, len))
return JS_EXCEPTION;
if (argc == 0) {
item_count = 0;
del_count = 0;
} else if (argc == 1) {
item_count = 0;
del_count = len - start;
} else {
item_count = argc - 2;
if (JS_ToInt32Clamp(ctx, &del_count, argv[1], 0, len - start, 0))
return JS_EXCEPTION;
}
new_len = len + item_count - del_count;
obj = JS_NewArray(ctx, del_count);
if (JS_IsException(obj))
return obj;
p = JS_VALUE_TO_PTR(*this_val);
if (p->u.array.len != len)
return JS_ThrowTypeError(ctx, "array length was modified");
arr = JS_VALUE_TO_PTR(p->u.array.tab);
p1 = JS_VALUE_TO_PTR(obj);
arr1 = JS_VALUE_TO_PTR(p1->u.array.tab);
for(i = 0; i < del_count; i++) {
arr1->arr[i] = arr->arr[start + i];
}
if (item_count != del_count) {
if (del_count > item_count) {
memmove(arr->arr + start + item_count,
arr->arr + start + del_count,
(len - (start + del_count)) * sizeof(JSValue));
}
JS_PUSH_VALUE(ctx, obj);
ret = js_array_resize(ctx, this_val, new_len);
JS_POP_VALUE(ctx, obj);
if (ret)
return JS_EXCEPTION;
p = JS_VALUE_TO_PTR(*this_val);
arr = JS_VALUE_TO_PTR(p->u.array.tab);
if (del_count < item_count) {
memmove(arr->arr + start + item_count,
arr->arr + start + del_count,
(len - (start + del_count)) * sizeof(JSValue));
}
}
for(i = 0; i < item_count; i++)
arr->arr[start + i] = argv[2 + i];
return obj;
}
JSValue js_array_every(JSContext *ctx, JSValue *this_val,
int argc, JSValue *argv, int special)
{
JSObject *p;
JSValueArray *arr;
JSValue res, ret, val;
JSValue *pfunc, *pthis_arg;
JSGCRef val_ref, ret_ref;
int len, k, n;
p = js_get_array(ctx, *this_val);
if (!p)
return JS_EXCEPTION;
len = p->u.array.len;
pfunc = &argv[0];
pthis_arg = NULL;
if (argc > 1)
pthis_arg = &argv[1];
if (!JS_IsFunction(ctx, *pfunc))
return JS_ThrowTypeError(ctx, "not a function");
switch (special) {
case js_special_every:
ret = JS_TRUE;
break;
case js_special_some:
ret = JS_FALSE;
break;
case js_special_map:
ret = JS_NewArray(ctx, len);
if (JS_IsException(ret))
return JS_EXCEPTION;
break;
case js_special_filter:
ret = JS_NewArray(ctx, 0);
if (JS_IsException(ret))
return JS_EXCEPTION;
break;
case js_special_forEach:
default:
ret = JS_UNDEFINED;
break;
}
n = 0;
JS_PUSH_VALUE(ctx, ret);
for(k = 0; k < len; k++) {
if (JS_StackCheck(ctx, 5))
goto exception;
p = JS_VALUE_TO_PTR(*this_val);
arr = JS_VALUE_TO_PTR(p->u.array.tab);
if (k >= p->u.array.len)
break;
val = arr->arr[k];
JS_PushArg(ctx, *this_val);
JS_PushArg(ctx, JS_NewShortInt(k));
JS_PushArg(ctx, val);
JS_PushArg(ctx, *pfunc);
JS_PushArg(ctx, pthis_arg ? *pthis_arg : JS_UNDEFINED);
JS_PUSH_VALUE(ctx, val);
res = JS_Call(ctx, 3);
JS_POP_VALUE(ctx, val);
if (JS_IsException(res))
goto exception;
switch (special) {
case js_special_every:
if (!JS_ToBool(ctx, res)) {
ret_ref.val = JS_FALSE;
goto done;
}
break;
case js_special_some:
if (JS_ToBool(ctx, res)) {
ret_ref.val = JS_TRUE;
goto done;
}
break;
case js_special_map:
res = JS_SetPropertyUint32(ctx, ret_ref.val, k, res);
if (JS_IsException(res))
goto exception;
break;
case js_special_filter:
if (JS_ToBool(ctx, res)) {
res = JS_SetPropertyUint32(ctx, ret_ref.val, n++, val);
if (JS_IsException(res))
goto exception;
}
break;
case js_special_forEach:
default:
break;
}
}
done:
JS_POP_VALUE(ctx, ret);
return ret;
exception:
ret_ref.val = JS_EXCEPTION;
goto done;
}
JSValue js_array_reduce(JSContext *ctx, JSValue *this_val,
int argc, JSValue *argv, int special)
{
JSObject *p;
JSValueArray *arr;
JSValue acc, *pfunc;
JSGCRef acc_ref;
int len, k, k1, ret;
p = js_get_array(ctx, *this_val);
if (!p)
return JS_EXCEPTION;
len = p->u.array.len;
pfunc = &argv[0];
if (!JS_IsFunction(ctx, *pfunc))
return JS_ThrowTypeError(ctx, "not a function");
k = 0;
if (argc > 1) {
acc = argv[1];
} else {
if (len == 0)
return JS_ThrowTypeError(ctx, "empty array");
k1 = (special == js_special_reduceRight) ? len - k - 1 : k;
arr = JS_VALUE_TO_PTR(p->u.array.tab);
acc = arr->arr[k1];
k++;
}
for (; k < len; k++) {
JS_PUSH_VALUE(ctx, acc);
ret = JS_StackCheck(ctx, 6);
JS_POP_VALUE(ctx, acc);
if (ret)
return JS_EXCEPTION;
k1 = (special == js_special_reduceRight) ? len - k - 1 : k;
p = JS_VALUE_TO_PTR(*this_val);
arr = JS_VALUE_TO_PTR(p->u.array.tab);
if (k1 >= p->u.array.len)
break;
JS_PushArg(ctx, *this_val);
JS_PushArg(ctx, JS_NewShortInt(k1));
JS_PushArg(ctx, arr->arr[k1]);
JS_PushArg(ctx, acc);
JS_PushArg(ctx, *pfunc);
JS_PushArg(ctx, JS_UNDEFINED);
acc = JS_Call(ctx, 4);
if (JS_IsException(acc))
return JS_EXCEPTION;
}
return acc;
}
static void rqsort_idx(size_t nmemb,
int (*cmp)(size_t, size_t, void *),
void (*swap)(size_t, size_t, void *),
void *opaque)
{
size_t i, n, c, r, size;
size = 1;
if (nmemb > 1) {
i = (nmemb / 2) * size;
n = nmemb * size;
while (i > 0) {
i -= size;
for (r = i; (c = r * 2 + size) < n; r = c) {
if (c < n - size && cmp(c, c + size, opaque) <= 0)
c += size;
if (cmp(r, c, opaque) > 0)
break;
swap(r, c, opaque);
}
}
for (i = n - size; i > 0; i -= size) {
swap(0, i, opaque);
for (r = 0; (c = r * 2 + size) < i; r = c) {
if (c < i - size && cmp(c, c + size, opaque) <= 0)
c += size;
if (cmp(r, c, opaque) > 0)
break;
swap(r, c, opaque);
}
}
}
}
typedef struct {
JSContext *ctx;
BOOL exception;
JSValue *parr;
JSValue *pfunc;
} JSArraySortContext;
static int js_array_sort_cmp(size_t i1, size_t i2, void *opaque)
{
JSArraySortContext *s = opaque;
JSContext *ctx = s->ctx;
JSValueArray *arr;
int cmp, j1, j2;
if (s->exception)
return 0;
arr = JS_VALUE_TO_PTR(*s->parr);
if (s->pfunc) {
JSValue res;
if (arr->arr[2 * i1] == arr->arr[2 * i2])
goto cmp_same;
if (JS_StackCheck(ctx, 4))
goto exception;
arr = JS_VALUE_TO_PTR(*s->parr);
JS_PushArg(ctx, arr->arr[2 * i2]);
JS_PushArg(ctx, arr->arr[2 * i1]);
JS_PushArg(ctx, *s->pfunc);
JS_PushArg(ctx, JS_UNDEFINED);
res = JS_Call(ctx, 2);
if (JS_IsException(res))
return JS_EXCEPTION;
if (JS_IsInt(res)) {
int val = JS_VALUE_GET_INT(res);
cmp = (val > 0) - (val < 0);
} else {
double val;
if (JS_ToNumber(ctx, &val, res))
goto exception;
cmp = (val > 0) - (val < 0);
}
} else {
JSValue str1, str2;
JSGCRef str1_ref;
str1 = arr->arr[2 * i1];
if (!JS_IsString(ctx, str1)) {
str1 = JS_ToString(ctx, str1);
if (JS_IsException(str1))
goto exception;
arr = JS_VALUE_TO_PTR(*s->parr);
}
str2 = arr->arr[2 * i2];
if (!JS_IsString(ctx, str2)) {
JS_PUSH_VALUE(ctx, str1);
str2 = JS_ToString(ctx, str2);
JS_POP_VALUE(ctx, str1);
if (JS_IsException(str2))
goto exception;
}
cmp = js_string_compare(ctx, str1, str2);
}
if (cmp != 0)
return cmp;
cmp_same:
arr = JS_VALUE_TO_PTR(*s->parr);
j1 = JS_VALUE_GET_INT(arr->arr[2 * i1 + 1]);
j2 = JS_VALUE_GET_INT(arr->arr[2 * i2 + 1]);
return (j1 > j2) - (j1 < j2);
exception:
s->exception = TRUE;
return 0;
}
static void js_array_sort_swap(size_t i1, size_t i2, void *opaque)
{
JSArraySortContext *s = opaque;
JSValueArray *arr;
JSValue tmp, *tab;
arr = JS_VALUE_TO_PTR(*s->parr);
tab = arr->arr;
tmp = tab[2 * i1];
tab[2 * i1] = tab[2 * i2];
tab[2 * i2] = tmp;
tmp = tab[2 * i1 + 1];
tab[2 * i1 + 1] = tab[2 * i2 + 1];
tab[2 * i2 + 1] = tmp;
}
JSValue js_array_sort(JSContext *ctx, JSValue *this_val,
int argc, JSValue *argv)
{
JSValue *pfunc = &argv[0];
JSObject *p;
JSValue tab_val;
JSGCRef tab_val_ref;
JSValueArray *tab, *arr;
int i, len, n;
JSArraySortContext ss, *s = &ss;
if (!JS_IsUndefined(*pfunc)) {
if (!JS_IsFunction(ctx, *pfunc))
return JS_ThrowTypeError(ctx, "not a function");
} else {
pfunc = NULL;
}
p = js_get_array(ctx, *this_val);
if (!p)
return JS_EXCEPTION;
len = p->u.array.len;
tab = js_alloc_value_array(ctx, 0, len * 2);
if (!tab)
return JS_EXCEPTION;
p = JS_VALUE_TO_PTR(*this_val);
arr = JS_VALUE_TO_PTR(p->u.array.tab);
n = 0;
for(i = 0; i < len; i++) {
if (!JS_IsUndefined(arr->arr[i])) {
tab->arr[2 * n] = arr->arr[i];
tab->arr[2 * n + 1] = JS_NewShortInt(i);
n++;
}
}
tab_val = JS_VALUE_FROM_PTR(tab);
JS_PUSH_VALUE(ctx, tab_val);
s->ctx = ctx;
s->exception = FALSE;
s->parr = &tab_val_ref.val;
s->pfunc = pfunc;
rqsort_idx(n, js_array_sort_cmp, js_array_sort_swap, s);
JS_POP_VALUE(ctx, tab_val);
tab = JS_VALUE_TO_PTR(tab_val);
if (s->exception) {
js_free(ctx, tab);
return JS_EXCEPTION;
}
p = JS_VALUE_TO_PTR(*this_val);
arr = JS_VALUE_TO_PTR(p->u.array.tab);
len = min_int(len, p->u.array.len);
for(i = 0; i < len; i++) {
arr->arr[i] = tab->arr[2 * i];
}
js_free(ctx, tab);
return *this_val;
}
static double js_fmin(double a, double b)
{
if (a == 0 && b == 0) {
return uint64_as_float64(float64_as_uint64(a) | float64_as_uint64(b));
} else if (a <= b) {
return a;
} else {
return b;
}
}
static double js_fmax(double a, double b)
{
if (a == 0 && b == 0) {
return uint64_as_float64(float64_as_uint64(a) & float64_as_uint64(b));
} else if (a >= b) {
return a;
} else {
return b;
}
}
JSValue js_math_min_max(JSContext *ctx, JSValue *this_val,
int argc, JSValue *argv, int magic)
{
BOOL is_max = magic;
double r, a;
int i;
if (unlikely(argc == 0)) {
return __JS_NewFloat64(ctx, is_max ? -1.0 / 0.0 : 1.0 / 0.0);
}
if (JS_IsInt(argv[0])) {
int a1, r1 = JS_VALUE_GET_INT(argv[0]);
for(i = 1; i < argc; i++) {
if (!JS_IsInt(argv[i])) {
r = r1;
goto generic_case;
}
a1 = JS_VALUE_GET_INT(argv[i]);
if (is_max)
r1 = max_int(r1, a1);
else
r1 = min_int(r1, a1);
}
return JS_NewShortInt(r1);
} else {
if (JS_ToNumber(ctx, &r, argv[0]))
return JS_EXCEPTION;
i = 1;
generic_case:
while (i < argc) {
if (JS_ToNumber(ctx, &a, argv[i]))
return JS_EXCEPTION;
if (!isnan(r)) {
if (isnan(a)) {
r = a;
} else {
if (is_max)
r = js_fmax(r, a);
else
r = js_fmin(r, a);
}
}
i++;
}
return JS_NewFloat64(ctx, r);
}
}
double js_math_sign(double a)
{
if (isnan(a) || a == 0.0)
return a;
if (a < 0)
return -1;
else
return 1;
}
double js_math_fround(double a)
{
return (float)a;
}
JSValue js_math_imul(JSContext *ctx, JSValue *this_val,
int argc, JSValue *argv)
{
int a, b;
if (JS_ToInt32(ctx, &a, argv[0]))
return JS_EXCEPTION;
if (JS_ToInt32(ctx, &b, argv[1]))
return JS_EXCEPTION;
return JS_NewInt32(ctx, (uint32_t)a * (uint32_t)b);
}
JSValue js_math_clz32(JSContext *ctx, JSValue *this_val,
int argc, JSValue *argv)
{
uint32_t a, r;
if (JS_ToUint32(ctx, &a, argv[0]))
return JS_EXCEPTION;
if (a == 0)
r = 32;
else
r = clz32(a);
return JS_NewInt32(ctx, r);
}
JSValue js_math_atan2(JSContext *ctx, JSValue *this_val,
int argc, JSValue *argv)
{
double y, x;
if (JS_ToNumber(ctx, &y, argv[0]))
return JS_EXCEPTION;
if (JS_ToNumber(ctx, &x, argv[1]))
return JS_EXCEPTION;
return JS_NewFloat64(ctx, js_atan2(y, x));
}
JSValue js_math_pow(JSContext *ctx, JSValue *this_val,
int argc, JSValue *argv)
{
double y, x;
if (JS_ToNumber(ctx, &x, argv[0]))
return JS_EXCEPTION;
if (JS_ToNumber(ctx, &y, argv[1]))
return JS_EXCEPTION;
return JS_NewFloat64(ctx, js_pow(x, y));
}
static uint64_t xorshift64star(uint64_t *pstate)
{
uint64_t x;
x = *pstate;
x ^= x >> 12;
x ^= x << 25;
x ^= x >> 27;
*pstate = x;
return x * 0x2545F4914F6CDD1D;
}
JSValue js_math_random(JSContext *ctx, JSValue *this_val,
int argc, JSValue *argv)
{
double d;
uint64_t v;
v = xorshift64star(&ctx->random_state);
d = uint64_as_float64(((uint64_t)0x3ff << 52) | (v >> 12));
return __JS_NewFloat64(ctx, d - 1.0);
}
#define JS_TYPED_ARRAY_COUNT (JS_CLASS_FLOAT64_ARRAY - JS_CLASS_UINT8C_ARRAY + 1)
static uint8_t typed_array_size_log2[JS_TYPED_ARRAY_COUNT] = {
0, 0, 0, 1, 1, 2, 2, 2, 3
};
static int JS_ToIndex(JSContext *ctx, uint64_t *plen, JSValue val)
{
int v;
if (JS_ToInt32Sat(ctx, &v, val))
return -1;
if (v < 0 || v > JS_SHORTINT_MAX) {
JS_ThrowRangeError(ctx, "invalid array index");
return -1;
}
*plen = v;
return 0;
}
JSValue js_array_buffer_alloc(JSContext *ctx, uint64_t len)
{
JSByteArray *arr;
JSValue buffer, obj;
JSGCRef buffer_ref;
JSObject *p;
if (len > JS_SHORTINT_MAX)
return JS_ThrowRangeError(ctx, "invalid array buffer length");
arr = js_alloc_byte_array(ctx, len);
if (!arr)
return JS_EXCEPTION;
memset(arr->buf, 0, len);
buffer = JS_VALUE_FROM_PTR(arr);
JS_PUSH_VALUE(ctx, buffer);
obj = JS_NewObjectClass(ctx, JS_CLASS_ARRAY_BUFFER, sizeof(JSArrayBuffer));
JS_POP_VALUE(ctx, buffer);
if (JS_IsException(obj))
return obj;
p = JS_VALUE_TO_PTR(obj);
p->u.array_buffer.byte_buffer = buffer;
return obj;
}
JSValue js_array_buffer_constructor(JSContext *ctx, JSValue *this_val,
int argc, JSValue *argv)
{
uint64_t len;
if (!(argc & FRAME_CF_CTOR))
return JS_ThrowTypeError(ctx, "must be called with new");
if (JS_ToIndex(ctx, &len, argv[0]))
return JS_EXCEPTION;
return js_array_buffer_alloc(ctx, len);
}
JSValue js_array_buffer_get_byteLength(JSContext *ctx, JSValue *this_val,
int argc, JSValue *argv)
{
JSObject *p = js_get_object_class(ctx, *this_val, JS_CLASS_ARRAY_BUFFER);
JSByteArray *arr;
if (!p)
return JS_ThrowTypeError(ctx, "expected an ArrayBuffer");
arr = JS_VALUE_TO_PTR(p->u.array_buffer.byte_buffer);
return JS_NewShortInt(arr->size);
}
JSValue js_typed_array_base_constructor(JSContext *ctx, JSValue *this_val,
int argc, JSValue *argv)
{
return JS_ThrowTypeError(ctx, "cannot be called");
}
static JSValue js_typed_array_constructor_obj(JSContext *ctx, JSValue *this_val,
int argc, JSValue *argv, int magic)
{
int i, len;
JSValue val, obj;
JSGCRef obj_ref;
JSObject *p;
p = JS_VALUE_TO_PTR(argv[0]);
if (p->class_id == JS_CLASS_ARRAY) {
len = p->u.array.len;
} else if (p->class_id >= JS_CLASS_UINT8C_ARRAY &&
p->class_id <= JS_CLASS_FLOAT64_ARRAY) {
len = p->u.typed_array.len;
} else {
return JS_ThrowTypeError(ctx, "unsupported object class");
}
val = JS_NewShortInt(len);
obj = js_typed_array_constructor(ctx, NULL, 1 | FRAME_CF_CTOR, &val, magic);
if (JS_IsException(obj))
return obj;
for(i = 0; i < len; i++) {
JS_PUSH_VALUE(ctx, obj);
val = JS_GetProperty(ctx, argv[0], JS_NewShortInt(i));
JS_POP_VALUE(ctx, obj);
if (JS_IsException(val))
return val;
JS_PUSH_VALUE(ctx, obj);
val = JS_SetPropertyInternal(ctx, obj, JS_NewShortInt(i), val, FALSE);
JS_POP_VALUE(ctx, obj);
if (JS_IsException(val))
return val;
}
return obj;
}
JSValue js_typed_array_constructor(JSContext *ctx, JSValue *this_val,
int argc, JSValue *argv, int magic)
{
int size_log2;
uint64_t len, offset, byte_length;
JSObject *p;
JSByteArray *arr;
JSValue buffer, obj;
JSGCRef buffer_ref;
if (!(argc & FRAME_CF_CTOR))
return JS_ThrowTypeError(ctx, "must be called with new");
size_log2 = typed_array_size_log2[magic - JS_CLASS_UINT8C_ARRAY];
if (!JS_IsObject(ctx, argv[0])) {
if (JS_ToIndex(ctx, &len, argv[0]))
return JS_EXCEPTION;
buffer = js_array_buffer_alloc(ctx, len << size_log2);
if (JS_IsException(buffer))
return JS_EXCEPTION;
offset = 0;
} else {
p = JS_VALUE_TO_PTR(argv[0]);
if (p->class_id == JS_CLASS_ARRAY_BUFFER) {
arr = JS_VALUE_TO_PTR(p->u.array_buffer.byte_buffer);
byte_length = arr->size;
if (JS_ToIndex(ctx, &offset, argv[1]))
return JS_EXCEPTION;
if ((offset & ((1 << size_log2) - 1)) != 0 ||
offset > byte_length)
return JS_ThrowRangeError(ctx, "invalid offset");
if (JS_IsUndefined(argv[2])) {
if ((byte_length & ((1 << size_log2) - 1)) != 0)
goto invalid_length;
len = (byte_length - offset) >> size_log2;
} else {
if (JS_ToIndex(ctx, &len, argv[2]))
return JS_EXCEPTION;
if ((offset + (len << size_log2)) > byte_length) {
invalid_length:
return JS_ThrowRangeError(ctx, "invalid length");
}
}
buffer = argv[0];
offset >>= size_log2;
} else {
return js_typed_array_constructor_obj(ctx, this_val,
argc, argv, magic);
}
}
JS_PUSH_VALUE(ctx, buffer);
obj = JS_NewObjectClass(ctx, magic, sizeof(JSTypedArray));
JS_POP_VALUE(ctx, buffer);
if (JS_IsException(obj))
return obj;
p = JS_VALUE_TO_PTR(obj);
p->u.typed_array.buffer = buffer;
p->u.typed_array.offset = offset;
p->u.typed_array.len = len;
return obj;
}
static JSObject *get_typed_array(JSContext *ctx, JSValue val)
{
JSObject *p;
if (!JS_IsObject(ctx, val))
goto fail;
p = JS_VALUE_TO_PTR(val);
if (!(p->class_id >= JS_CLASS_UINT8C_ARRAY && p->class_id <= JS_CLASS_FLOAT64_ARRAY)) {
fail:
JS_ThrowTypeError(ctx, "not a TypedArray");
return NULL;
}
return p;
}
JSValue js_typed_array_get_length(JSContext *ctx, JSValue *this_val,
int argc, JSValue *argv, int magic)
{
JSObject *p;
int size_log2;
p = get_typed_array(ctx, *this_val);
if (!p)
return JS_EXCEPTION;
size_log2 = typed_array_size_log2[p->class_id - JS_CLASS_UINT8C_ARRAY];
switch(magic) {
default:
case 0:
return JS_NewShortInt(p->u.typed_array.len);
case 1:
return JS_NewShortInt(p->u.typed_array.len << size_log2);
case 2:
return JS_NewShortInt(p->u.typed_array.offset << size_log2);
case 3:
return p->u.typed_array.buffer;
}
}
JSValue js_typed_array_subarray(JSContext *ctx, JSValue *this_val,
int argc, JSValue *argv)
{
JSObject *p, *p1;
JSByteArray *arr;
int start, final, len;
uint32_t offset, count;
JSValue obj;
p = get_typed_array(ctx, *this_val);
if (!p)
return JS_EXCEPTION;
len = p->u.typed_array.len;
if (JS_ToInt32Clamp(ctx, &start, argv[0], 0, len, len))
return JS_EXCEPTION;
if (JS_IsUndefined(argv[1])) {
final = len;
} else {
if (JS_ToInt32Clamp(ctx, &final, argv[1], 0, len, len))
return JS_EXCEPTION;
}
p = JS_VALUE_TO_PTR(*this_val);
offset = p->u.typed_array.offset + start;
count = max_int(final - start, 0);
p1 = JS_VALUE_TO_PTR(p->u.typed_array.buffer);
arr = JS_VALUE_TO_PTR(p1->u.array_buffer.byte_buffer);
if (offset + count > arr->size)
return JS_ThrowRangeError(ctx, "invalid length");
obj = JS_NewObjectClass(ctx, p->class_id, sizeof(JSTypedArray));
if (JS_IsException(obj))
return JS_EXCEPTION;
p = JS_VALUE_TO_PTR(*this_val);
p1 = JS_VALUE_TO_PTR(obj);
p1->u.typed_array.buffer = p->u.typed_array.buffer;
p1->u.typed_array.offset = offset;
p1->u.typed_array.len = count;
return obj;
}
JSValue js_typed_array_set(JSContext *ctx, JSValue *this_val,
int argc, JSValue *argv)
{
JSObject *p, *p1;
uint32_t dst_len, src_len, i;
int offset;
p = get_typed_array(ctx, *this_val);
if (!p)
return JS_EXCEPTION;
if (argc > 1) {
if (JS_ToInt32Sat(ctx, &offset, argv[1]))
return JS_EXCEPTION;
} else {
offset = 0;
}
if (offset < 0)
goto range_error;
if (!JS_IsObject(ctx, argv[0]))
return JS_ThrowTypeErrorNotAnObject(ctx);
p = JS_VALUE_TO_PTR(*this_val);
dst_len = p->u.typed_array.len;
p1 = JS_VALUE_TO_PTR(argv[0]);
if (p1->class_id >= JS_CLASS_UINT8C_ARRAY &&
p1->class_id <= JS_CLASS_FLOAT64_ARRAY) {
src_len = p1->u.typed_array.len;
if (src_len > dst_len || offset > dst_len - src_len)
goto range_error;
if (p1->class_id == p->class_id) {
JSObject *src_buffer, *dst_buffer;
JSByteArray *src_arr, *dst_arr;
int shift = typed_array_size_log2[p->class_id - JS_CLASS_UINT8C_ARRAY];
dst_buffer = JS_VALUE_TO_PTR(p->u.typed_array.buffer);
dst_arr = JS_VALUE_TO_PTR(dst_buffer->u.array_buffer.byte_buffer);
src_buffer = JS_VALUE_TO_PTR(p1->u.typed_array.buffer);
src_arr = JS_VALUE_TO_PTR(src_buffer->u.array_buffer.byte_buffer);
memmove(dst_arr->buf + ((p->u.typed_array.offset + offset) << shift),
src_arr->buf + (p1->u.typed_array.offset << shift),
src_len << shift);
goto done;
}
} else {
if (js_get_length32(ctx, (uint32_t *)&src_len, argv[0]))
return JS_EXCEPTION;
if (src_len > dst_len || offset > dst_len - src_len) {
range_error:
return JS_ThrowRangeError(ctx, "invalid array length");
}
}
for(i = 0; i < src_len; i++) {
JSValue val;
val = JS_GetPropertyUint32(ctx, argv[0], i);
if (JS_IsException(val))
return JS_EXCEPTION;
val = JS_SetPropertyUint32(ctx, *this_val, offset + i, val);
if (JS_IsException(val))
return JS_EXCEPTION;
}
done:
return JS_UNDEFINED;
}
JSValue js_date_constructor(JSContext *ctx, JSValue *this_val,
int argc, JSValue *argv)
{
return JS_ThrowTypeError(ctx, "only Date.now() is supported");
}
JSValue js_global_eval(JSContext *ctx, JSValue *this_val,
int argc, JSValue *argv)
{
JSValue val;
if (!JS_IsString(ctx, argv[0]))
return argv[0];
val = JS_Parse2(ctx, argv[0], NULL, 0, "<input>", JS_EVAL_RETVAL);
if (JS_IsException(val))
return val;
return JS_Run(ctx, val);
}
JSValue js_global_isNaN(JSContext *ctx, JSValue *this_val,
int argc, JSValue *argv)
{
double d;
if (unlikely(JS_ToNumber(ctx, &d, argv[0])))
return JS_EXCEPTION;
return JS_NewBool(isnan(d));
}
JSValue js_global_isFinite(JSContext *ctx, JSValue *this_val,
int argc, JSValue *argv)
{
double d;
if (unlikely(JS_ToNumber(ctx, &d, argv[0])))
return JS_EXCEPTION;
return JS_NewBool(isfinite(d));
}
JSValue js_json_parse(JSContext *ctx, JSValue *this_val,
int argc, JSValue *argv)
{
JSValue val;
val = JS_ToString(ctx, argv[0]);
if (JS_IsException(val))
return val;
return JS_Parse2(ctx, val, NULL, 0, "<input>", JS_EVAL_JSON);
}
static int js_to_quoted_string(JSContext *ctx, StringBuffer *b, JSValue str)
{
int i, c;
JSStringCharBuf buf;
JSString *p;
JSGCRef str_ref;
size_t clen;
JS_PUSH_VALUE(ctx, str);
string_buffer_putc(ctx, b, '\"');
i = 0;
for(;;) {
p = get_string_ptr(ctx, &buf, str_ref.val);
if (i >= p->len)
break;
c = utf8_get(p->buf + i, &clen);
i += clen;
switch(c) {
case '\t':
c = 't';
goto quote;
case '\r':
c = 'r';
goto quote;
case '\n':
c = 'n';
goto quote;
case '\b':
c = 'b';
goto quote;
case '\f':
c = 'f';
goto quote;
case '\"':
case '\\':
quote:
string_buffer_putc(ctx, b, '\\');
string_buffer_putc(ctx, b, c);
break;
default:
if (c < 32 || (c >= 0xd800 && c < 0xe000)) {
char buf[7];
js_snprintf(buf, sizeof(buf), "\\u%04x", c);
string_buffer_puts(ctx, b, buf);
} else {
string_buffer_putc(ctx, b, c);
}
break;
}
}
string_buffer_putc(ctx, b, '\"');
JS_POP_VALUE(ctx, str);
return 0;
}
#define JSON_REC_SIZE 3
static int check_circular_ref(JSContext *ctx, JSValue *stack_top, JSValue val)
{
JSValue *sp;
for(sp = ctx->sp; sp < stack_top; sp += JSON_REC_SIZE) {
if (sp[0] == val) {
JS_ThrowTypeError(ctx, "circular reference");
return -1;
}
}
return 0;
}
JSValue js_json_stringify(JSContext *ctx, JSValue *this_val,
int argc, JSValue *argv)
{
JSValue obj, *stack_top;
StringBuffer b_s, *b = &b_s;
int idx, ret;
#if 0#endif
string_buffer_push(ctx, b, 0);
stack_top = ctx->sp;
ret = JS_StackCheck(ctx, JSON_REC_SIZE);
if (ret)
goto fail;
*--ctx->sp = JS_NULL;
*--ctx->sp = JS_NewShortInt(0);
*--ctx->sp = argv[0];
while (ctx->sp < stack_top) {
obj = ctx->sp[0];
if (JS_IsFunction(ctx, obj)) {
goto output_null;
} else if (JS_IsObject(ctx, obj)) {
JSObject *p = JS_VALUE_TO_PTR(obj);
idx = JS_VALUE_GET_INT(ctx->sp[1]);
if (p->class_id == JS_CLASS_ARRAY) {
JSValueArray *arr;
JSValue val;
if (idx == 0)
string_buffer_putc(ctx, b, '[');
p = JS_VALUE_TO_PTR(ctx->sp[0]);
if (idx >= p->u.array.len) {
string_buffer_putc(ctx, b, ']');
ctx->sp += JSON_REC_SIZE;
} else {
if (idx != 0)
string_buffer_putc(ctx, b, ',');
ctx->sp[1] = JS_NewShortInt(idx + 1);
ret = JS_StackCheck(ctx, JSON_REC_SIZE);
if (ret)
goto fail;
p = JS_VALUE_TO_PTR(ctx->sp[0]);
arr = JS_VALUE_TO_PTR(p->u.array.tab);
val = arr->arr[idx];
if (check_circular_ref(ctx, stack_top, val))
goto fail;
*--ctx->sp = JS_NULL;
*--ctx->sp = JS_NewShortInt(0);
*--ctx->sp = val;
}
} else {
JSValueArray *arr;
JSValue val, prop;
JSGCRef val_ref;
int saved_idx;
if (idx == 0) {
string_buffer_putc(ctx, b, '{');
ctx->sp[2] = js_object_keys(ctx, NULL, 1, &ctx->sp[0]);
if (JS_IsException(ctx->sp[2]))
goto fail;
}
saved_idx = idx;
for(;;) {
p = JS_VALUE_TO_PTR(ctx->sp[2]);
if (idx >= p->u.array.len) {
string_buffer_putc(ctx, b, '}');
ctx->sp += JSON_REC_SIZE;
goto end_obj;
} else {
arr = JS_VALUE_TO_PTR(p->u.array.tab);
prop = JS_ToPropertyKey(ctx, arr->arr[idx]);
val = JS_GetProperty(ctx, ctx->sp[0], prop);
if (JS_IsException(val))
goto fail;
if (!JS_IsUndefined(val))
break;
idx++;
}
}
JS_PUSH_VALUE(ctx, val);
if (saved_idx != 0)
string_buffer_putc(ctx, b, ',');
ctx->sp[1] = JS_NewShortInt(idx + 1);
p = JS_VALUE_TO_PTR(ctx->sp[2]);
arr = JS_VALUE_TO_PTR(p->u.array.tab);
ret = js_to_quoted_string(ctx, b, arr->arr[idx]);
string_buffer_putc(ctx, b, ':');
ret |= JS_StackCheck(ctx, JSON_REC_SIZE);
JS_POP_VALUE(ctx, val);
if (ret)
goto fail;
if (check_circular_ref(ctx, stack_top, val))
goto fail;
*--ctx->sp = JS_NULL;
*--ctx->sp = JS_NewShortInt(0);
*--ctx->sp = val;
end_obj: ;
}
} else if (JS_IsNumber(ctx, obj)) {
double d;
ret = JS_ToNumber(ctx, &d, obj);
if (ret)
goto fail;
if (!isfinite(d))
goto output_null;
goto to_string;
} else if (JS_IsBool(obj)) {
to_string:
if (string_buffer_concat(ctx, b, obj))
goto fail;
ctx->sp += JSON_REC_SIZE;
} else if (JS_IsString(ctx, obj)) {
if (js_to_quoted_string(ctx, b, obj))
goto fail;
ctx->sp += JSON_REC_SIZE;
} else {
output_null:
string_buffer_concat(ctx, b, js_get_atom(ctx, JS_ATOM_null));
ctx->sp += JSON_REC_SIZE;
}
}
return string_buffer_pop(ctx, b);
fail:
ctx->sp = stack_top;
string_buffer_pop(ctx, b);
return JS_EXCEPTION;
}
typedef enum {
#define REDEF(id, size) REOP_ ## id,
#include "mquickjs_opcode.h"
#undef REDEF
REOP_COUNT,
} REOPCodeEnum;
#define CAPTURE_COUNT_MAX 255
#define REGISTER_COUNT_MAX 255
typedef struct {
#ifdef DUMP_REOP
const char *name;
#endif
uint8_t size;
} REOpCode;
static const REOpCode reopcode_info[REOP_COUNT] = {
#ifdef DUMP_REOP
#define REDEF(id, size) { #id, size },
#else
#define REDEF(id, size) { size },
#endif
#include "mquickjs_opcode.h"
#undef REDEF
};
#define LRE_FLAG_GLOBAL (1 << 0)
#define LRE_FLAG_IGNORECASE (1 << 1)
#define LRE_FLAG_MULTILINE (1 << 2)
#define LRE_FLAG_DOTALL (1 << 3)
#define LRE_FLAG_UNICODE (1 << 4)
#define LRE_FLAG_STICKY (1 << 5)
#define RE_HEADER_FLAGS 0
#define RE_HEADER_CAPTURE_COUNT 2
#define RE_HEADER_REGISTER_COUNT 3
#define RE_HEADER_LEN 4
#define CLASS_RANGE_BASE 0x40000000
typedef enum {
CHAR_RANGE_d,
CHAR_RANGE_D,
CHAR_RANGE_s,
CHAR_RANGE_S,
CHAR_RANGE_w,
CHAR_RANGE_W,
} CharRangeEnum;
static int lre_get_capture_count(const uint8_t *bc_buf)
{
return bc_buf[RE_HEADER_CAPTURE_COUNT];
}
static int lre_get_alloc_count(const uint8_t *bc_buf)
{
return bc_buf[RE_HEADER_CAPTURE_COUNT] * 2 + bc_buf[RE_HEADER_REGISTER_COUNT];
}
static int lre_get_flags(const uint8_t *bc_buf)
{
return get_u16(bc_buf + RE_HEADER_FLAGS);
}
#ifdef DUMP_REOP
static __maybe_unused void lre_dump_bytecode(const uint8_t *buf,
int buf_len)
{
int pos, len, opcode, bc_len, re_flags;
uint32_t val, val2;
assert(buf_len >= RE_HEADER_LEN);
re_flags = lre_get_flags(buf);
bc_len = buf_len - RE_HEADER_LEN;
printf("flags: 0x%x capture_count=%d reg_count=%d bytecode_len=%d\n",
re_flags, buf[RE_HEADER_CAPTURE_COUNT], buf[RE_HEADER_REGISTER_COUNT],
bc_len);
buf += RE_HEADER_LEN;
pos = 0;
while (pos < bc_len) {
printf("%5u: ", pos);
opcode = buf[pos];
len = reopcode_info[opcode].size;
if (opcode >= REOP_COUNT) {
printf(" invalid opcode=0x%02x\n", opcode);
break;
}
if ((pos + len) > bc_len) {
printf(" buffer overflow (opcode=0x%02x)\n", opcode);
break;
}
printf("%s", reopcode_info[opcode].name);
switch(opcode) {
case REOP_char1:
case REOP_char2:
case REOP_char3:
case REOP_char4:
{
int i, n;
n = opcode - REOP_char1 + 1;
for(i = 0; i < n; i++) {
val = buf[pos + 1 + i];
if (val >= ' ' && val <= 126)
printf(" '%c'", val);
else
printf(" 0x%2x", val);
}
}
break;
case REOP_goto:
case REOP_split_goto_first:
case REOP_split_next_first:
case REOP_lookahead:
case REOP_negative_lookahead:
val = get_u32(buf + pos + 1);
val += (pos + 5);
printf(" %u", val);
break;
case REOP_loop:
val2 = buf[pos + 1];
val = get_u32(buf + pos + 2);
val += (pos + 6);
printf(" r%u, %u", val2, val);
break;
case REOP_loop_split_goto_first:
case REOP_loop_split_next_first:
case REOP_loop_check_adv_split_goto_first:
case REOP_loop_check_adv_split_next_first:
{
uint32_t limit;
val2 = buf[pos + 1];
limit = get_u32(buf + pos + 2);
val = get_u32(buf + pos + 6);
val += (pos + 10);
printf(" r%u, %u, %u", val2, limit, val);
}
break;
case REOP_save_start:
case REOP_save_end:
case REOP_back_reference:
case REOP_back_reference_i:
printf(" %u", buf[pos + 1]);
break;
case REOP_save_reset:
printf(" %u %u", buf[pos + 1], buf[pos + 2]);
break;
case REOP_set_i32:
val = buf[pos + 1];
val2 = get_u32(buf + pos + 2);
printf(" r%u, %d", val, val2);
break;
case REOP_set_char_pos:
case REOP_check_advance:
val = buf[pos + 1];
printf(" r%u", val);
break;
case REOP_range8:
{
int n, i;
n = buf[pos + 1];
len += n * 2;
for(i = 0; i < n * 2; i++) {
val = buf[pos + 2 + i];
printf(" 0x%02x", val);
}
}
break;
case REOP_range:
{
int n, i;
n = get_u16(buf + pos + 1);
len += n * 8;
for(i = 0; i < n * 2; i++) {
val = get_u32(buf + pos + 3 + i * 4);
printf(" 0x%05x", val);
}
}
break;
default:
break;
}
printf("\n");
pos += len;
}
}
#endif
static void re_emit_op(JSParseState *s, int op)
{
emit_u8(s, op);
}
static void re_emit_op_u8(JSParseState *s, int op, uint32_t val)
{
emit_u8(s, op);
emit_u8(s, val);
}
static void re_emit_op_u16(JSParseState *s, int op, uint32_t val)
{
emit_u8(s, op);
emit_u16(s, val);
}
static int re_emit_op_u32(JSParseState *s, int op, uint32_t val)
{
int pos;
emit_u8(s, op);
pos = s->byte_code_len;
emit_u32(s, val);
return pos;
}
static int re_emit_goto(JSParseState *s, int op, uint32_t val)
{
int pos;
emit_u8(s, op);
pos = s->byte_code_len;
emit_u32(s, val - (pos + 4));
return pos;
}
static int re_emit_goto_u8(JSParseState *s, int op, uint32_t arg, uint32_t val)
{
int pos;
emit_u8(s, op);
emit_u8(s, arg);
pos = s->byte_code_len;
emit_u32(s, val - (pos + 4));
return pos;
}
static int re_emit_goto_u8_u32(JSParseState *s, int op, uint32_t arg0, uint32_t arg1, uint32_t val)
{
int pos;
emit_u8(s, op);
emit_u8(s, arg0);
emit_u32(s, arg1);
pos = s->byte_code_len;
emit_u32(s, val - (pos + 4));
return pos;
}
static void re_emit_char(JSParseState *s, int c)
{
uint8_t buf[4];
size_t n, i;
n = unicode_to_utf8(buf, c);
re_emit_op(s, REOP_char1 + n - 1);
for(i = 0; i < n; i++)
emit_u8(s, buf[i]);
}
static void re_parse_expect(JSParseState *s, int c)
{
if (s->source_buf[s->buf_pos] != c)
return js_parse_error(s, "expecting '%c'", c);
s->buf_pos++;
}
static int parse_digits(const uint8_t **pp)
{
const uint8_t *p;
uint64_t v;
int c;
p = *pp;
v = 0;
for(;;) {
c = *p;
if (c < '0' || c > '9')
break;
v = v * 10 + c - '0';
if (v >= JS_SHORTINT_MAX)
v = JS_SHORTINT_MAX;
p++;
}
*pp = p;
return v;
}
static BOOL re_need_check_adv_and_capture_init(BOOL *pneed_capture_init,
const uint8_t *bc_buf, int bc_buf_len)
{
int pos, opcode, len;
uint32_t val;
BOOL need_check_adv, need_capture_init;
need_check_adv = TRUE;
need_capture_init = FALSE;
pos = 0;
while (pos < bc_buf_len) {
opcode = bc_buf[pos];
len = reopcode_info[opcode].size;
switch(opcode) {
case REOP_range8:
val = bc_buf[pos + 1];
len += val * 2;
need_check_adv = FALSE;
break;
case REOP_range:
val = get_u16(bc_buf + pos + 1);
len += val * 8;
need_check_adv = FALSE;
break;
case REOP_char1:
case REOP_char2:
case REOP_char3:
case REOP_char4:
case REOP_dot:
case REOP_any:
case REOP_space:
case REOP_not_space:
need_check_adv = FALSE;
break;
case REOP_line_start:
case REOP_line_start_m:
case REOP_line_end:
case REOP_line_end_m:
case REOP_set_i32:
case REOP_set_char_pos:
case REOP_word_boundary:
case REOP_not_word_boundary:
break;
case REOP_save_start:
case REOP_save_end:
case REOP_save_reset:
break;
default:
need_capture_init = TRUE;
goto done;
}
pos += len;
}
done:
*pneed_capture_init = need_capture_init;
return need_check_adv;
}
static int get_class_atom(JSParseState *s, BOOL inclass)
{
const uint8_t *p;
uint32_t c;
int ret;
size_t len;
p = s->source_buf + s->buf_pos;
c = *p;
switch(c) {
case '\\':
p++;
c = *p++;
switch(c) {
case 'd':
c = CHAR_RANGE_d;
goto class_range;
case 'D':
c = CHAR_RANGE_D;
goto class_range;
case 's':
c = CHAR_RANGE_s;
goto class_range;
case 'S':
c = CHAR_RANGE_S;
goto class_range;
case 'w':
c = CHAR_RANGE_w;
goto class_range;
case 'W':
c = CHAR_RANGE_W;
class_range:
c += CLASS_RANGE_BASE;
break;
case 'c':
c = *p;
if ((c >= 'a' && c <= 'z') ||
(c >= 'A' && c <= 'Z') ||
(((c >= '0' && c <= '9') || c == '_') &&
inclass && !s->is_unicode)) {
c &= 0x1f;
p++;
} else if (s->is_unicode) {
goto invalid_escape;
} else {
p--;
c = '\\';
}
break;
case '-':
if (!inclass && s->is_unicode)
goto invalid_escape;
break;
case '^':
case '$':
case '\\':
case '.':
case '*':
case '+':
case '?':
case '(':
case ')':
case '[':
case ']':
case '{':
case '}':
case '|':
case '/':
break;
default:
p--;
ret = js_parse_escape(p, &len);
if (ret < 0) {
if (s->is_unicode) {
invalid_escape:
s->buf_pos = p - s->source_buf;
js_parse_error(s, "invalid escape sequence in regular expression");
} else {
goto normal_char;
}
}
p += len;
c = ret;
break;
}
break;
case '\0':
case '/':
if ((p - s->source_buf) >= s->buf_len)
js_parse_error(s, "unexpected end");
goto normal_char;
default:
normal_char:
ret = unicode_from_utf8(p, UTF8_CHAR_LEN_MAX, &len);
if (ret < 0)
js_parse_error(s, "malformed unicode char");
p += len;
c = ret;
break;
}
s->buf_pos = p - s->source_buf;
return c;
}
static const uint16_t char_range_s[] = {
0x0009, 0x000D + 1,
0x0020, 0x0020 + 1,
0x00A0, 0x00A0 + 1,
0x1680, 0x1680 + 1,
0x2000, 0x200A + 1,
0x2028, 0x2029 + 1,
0x202F, 0x202F + 1,
0x205F, 0x205F + 1,
0x3000, 0x3000 + 1,
0xFEFF, 0xFEFF + 1,
};
static const uint16_t char_range_w[] = {
0x0030, 0x0039 + 1,
0x0041, 0x005A + 1,
0x005F, 0x005F + 1,
0x0061, 0x007A + 1,
};
static void re_emit_range_base1(JSParseState *s, const uint16_t *tab, int n)
{
int i;
for(i = 0; i < n; i++)
emit_u32(s, tab[i]);
}
static void re_emit_range_base(JSParseState *s, int c)
{
BOOL invert;
invert = c & 1;
if (invert)
emit_u32(s, 0);
switch(c & ~1) {
case CHAR_RANGE_d:
emit_u32(s, 0x30);
emit_u32(s, 0x39 + 1);
break;
case CHAR_RANGE_s:
re_emit_range_base1(s, char_range_s, countof(char_range_s));
break;
case CHAR_RANGE_w:
re_emit_range_base1(s, char_range_w, countof(char_range_w));
break;
default:
abort();
}
if (invert)
emit_u32(s, 0x110000);
}
static int range_sort_cmp(size_t i1, size_t i2, void *opaque)
{
uint8_t *tab = opaque;
return get_u32(&tab[8 * i1]) - get_u32(&tab[8 * i2]);
}
static void range_sort_swap(size_t i1, size_t i2, void *opaque)
{
uint8_t *tab = opaque;
uint64_t tmp;
tmp = get_u64(&tab[8 * i1]);
put_u64(&tab[8 * i1], get_u64(&tab[8 * i2]));
put_u64(&tab[8 * i2], tmp);
}
static int range_compress(uint8_t *tab, int len)
{
int i, j;
uint32_t start, end, start2, end2;
i = 0;
j = 0;
while (i < len) {
start = get_u32(&tab[8 * i]);
end = get_u32(&tab[8 * i + 4]);
if (start == end) {
} else if ((i + 1) < len) {
start2 = get_u32(&tab[8 * i + 8]);
end2 = get_u32(&tab[8 * i + 12]);
if (end < start2) {
goto copy;
} else {
put_u32(&tab[8 * i + 8], start);
put_u32(&tab[8 * i + 12], max_uint32(end, end2));
}
} else {
copy:
put_u32(&tab[8 * j], start);
put_u32(&tab[8 * j + 4], end);
j++;
}
i++;
}
return j;
}
static void re_range_optimize(JSParseState *s, int range_start, BOOL invert)
{
int n, n1;
JSByteArray *arr;
n = (unsigned)(s->byte_code_len - range_start) / 8;
arr = JS_VALUE_TO_PTR(s->byte_code);
rqsort_idx(n, range_sort_cmp, range_sort_swap, arr->buf + range_start);
n1 = range_compress(arr->buf + range_start, n);
s->byte_code_len -= (n - n1) * 8;
if (invert) {
emit_insert(s, range_start, 4);
arr = JS_VALUE_TO_PTR(s->byte_code);
put_u32(arr->buf + range_start, 0);
emit_u32(s, 0x110000);
arr = JS_VALUE_TO_PTR(s->byte_code);
n = n1 + 1;
n1 = range_compress(arr->buf + range_start, n);
s->byte_code_len -= (n - n1) * 8;
}
n = n1;
if (n > 65534)
js_parse_error(s, "range too big");
if (n > 0 && n < 16) {
uint8_t *tab = arr->buf + range_start;
int c, i;
c = get_u32(&tab[8 * (n - 1) + 4]);
if (c < 254 || (c == 0x110000 &&
get_u32(&tab[8 * (n - 1)]) < 254)) {
s->byte_code_len = range_start - 3;
re_emit_op_u8(s, REOP_range8, n);
for(i = 0; i < 2 * n; i++) {
c = get_u32(&tab[4 * i]);
if (c == 0x110000)
c = 0xff;
emit_u8(s, c);
}
goto done;
}
}
put_u16(arr->buf + range_start - 2, n);
done: ;
}
static void add_interval_intersect(JSParseState *s,
uint32_t start, uint32_t end,
uint32_t start1, uint32_t end1,
int offset)
{
start = max_uint32(start, start1);
end = min_uint32(end, end1);
if (start < end) {
emit_u32(s, start);
emit_u32(s, end);
if (offset != 0) {
emit_u32(s, start + offset);
emit_u32(s, end + offset);
}
}
}
static void re_parse_char_class(JSParseState *s)
{
uint32_t c1, c2;
BOOL invert;
int range_start;
s->buf_pos++;
invert = FALSE;
if (s->source_buf[s->buf_pos] == '^') {
s->buf_pos++;
invert = TRUE;
}
re_emit_op_u16(s, REOP_range, 0);
range_start = s->byte_code_len;
for(;;) {
if (s->source_buf[s->buf_pos] == ']')
break;
c1 = get_class_atom(s, TRUE);
if (s->source_buf[s->buf_pos] == '-' && s->source_buf[s->buf_pos + 1] != ']') {
s->buf_pos++;
if (c1 >= CLASS_RANGE_BASE)
goto invalid_class_range;
c2 = get_class_atom(s, TRUE);
if (c2 >= CLASS_RANGE_BASE)
goto invalid_class_range;
if (c2 < c1) {
invalid_class_range:
js_parse_error(s, "invalid class range");
}
goto add_range;
} else {
if (c1 >= CLASS_RANGE_BASE) {
re_emit_range_base(s, c1 - CLASS_RANGE_BASE);
} else {
c2 = c1;
add_range:
c2++;
if (s->ignore_case) {
add_interval_intersect(s, c1, c2, 0, 'A', 0);
add_interval_intersect(s, c1, c2, 'Z' + 1, 'a', 0);
add_interval_intersect(s, c1, c2, 'z' + 1, INT32_MAX, 0);
add_interval_intersect(s, c1, c2, 'A', 'Z' + 1, 32);
add_interval_intersect(s, c1, c2, 'a', 'z' + 1, -32);
} else {
emit_u32(s, c1);
emit_u32(s, c2);
}
}
}
}
s->buf_pos++;
re_range_optimize(s, range_start, invert);
}
static void re_parse_quantifier(JSParseState *s, int last_atom_start, int last_capture_count)
{
int c, quant_min, quant_max;
JSByteArray *arr;
BOOL greedy;
const uint8_t *p;
p = s->source_buf + s->buf_pos;
c = *p;
switch(c) {
case '*':
p++;
quant_min = 0;
quant_max = JS_SHORTINT_MAX;
goto quantifier;
case '+':
p++;
quant_min = 1;
quant_max = JS_SHORTINT_MAX;
goto quantifier;
case '?':
p++;
quant_min = 0;
quant_max = 1;
goto quantifier;
case '{':
{
if (!is_digit(p[1]))
goto invalid_quant_count;
p++;
quant_min = parse_digits(&p);
quant_max = quant_min;
if (*p == ',') {
p++;
if (is_digit(*p)) {
quant_max = parse_digits(&p);
if (quant_max < quant_min) {
invalid_quant_count:
js_parse_error(s, "invalid repetition count");
}
} else {
quant_max = JS_SHORTINT_MAX;
}
}
s->buf_pos = p - s->source_buf;
re_parse_expect(s, '}');
p = s->source_buf + s->buf_pos;
}
quantifier:
greedy = TRUE;
if (*p == '?') {
p++;
greedy = FALSE;
}
s->buf_pos = p - s->source_buf;
if (last_atom_start < 0)
js_parse_error(s, "nothing to repeat");
{
BOOL need_capture_init, add_zero_advance_check;
int len, pos;
arr = JS_VALUE_TO_PTR(s->byte_code);
add_zero_advance_check =
re_need_check_adv_and_capture_init(&need_capture_init,
arr->buf + last_atom_start,
s->byte_code_len - last_atom_start);
if (need_capture_init && last_capture_count != s->capture_count) {
emit_insert(s, last_atom_start, 3);
int pos = last_atom_start;
arr = JS_VALUE_TO_PTR(s->byte_code);
arr->buf[pos++] = REOP_save_reset;
arr->buf[pos++] = last_capture_count;
arr->buf[pos++] = s->capture_count - 1;
}
len = s->byte_code_len - last_atom_start;
if (quant_min == 0) {
if (!need_capture_init && last_capture_count != s->capture_count) {
emit_insert(s, last_atom_start, 3);
arr = JS_VALUE_TO_PTR(s->byte_code);
arr->buf[last_atom_start++] = REOP_save_reset;
arr->buf[last_atom_start++] = last_capture_count;
arr->buf[last_atom_start++] = s->capture_count - 1;
}
if (quant_max == 0) {
s->byte_code_len = last_atom_start;
} else if (quant_max == 1 || quant_max == JS_SHORTINT_MAX) {
BOOL has_goto = (quant_max == JS_SHORTINT_MAX);
emit_insert(s, last_atom_start, 5 + add_zero_advance_check * 2);
arr = JS_VALUE_TO_PTR(s->byte_code);
arr->buf[last_atom_start] = REOP_split_goto_first +
greedy;
put_u32(arr->buf + last_atom_start + 1,
len + 5 * has_goto + add_zero_advance_check * 2 * 2);
if (add_zero_advance_check) {
arr->buf[last_atom_start + 1 + 4] = REOP_set_char_pos;
arr->buf[last_atom_start + 1 + 4 + 1] = 0;
re_emit_op_u8(s, REOP_check_advance, 0);
}
if (has_goto)
re_emit_goto(s, REOP_goto, last_atom_start);
} else {
emit_insert(s, last_atom_start, 11 + add_zero_advance_check * 2);
pos = last_atom_start;
arr = JS_VALUE_TO_PTR(s->byte_code);
arr->buf[pos++] = REOP_split_goto_first + greedy;
put_u32(arr->buf + pos, 6 + add_zero_advance_check * 2 + len + 10);
pos += 4;
arr->buf[pos++] = REOP_set_i32;
arr->buf[pos++] = 0;
put_u32(arr->buf + pos, quant_max);
pos += 4;
last_atom_start = pos;
if (add_zero_advance_check) {
arr->buf[pos++] = REOP_set_char_pos;
arr->buf[pos++] = 0;
}
re_emit_goto_u8_u32(s, (add_zero_advance_check ? REOP_loop_check_adv_split_next_first : REOP_loop_split_next_first) - greedy, 0, quant_max, last_atom_start);
}
} else if (quant_min == 1 && quant_max == JS_SHORTINT_MAX &&
!add_zero_advance_check) {
re_emit_goto(s, REOP_split_next_first - greedy,
last_atom_start);
} else {
if (quant_min == quant_max)
add_zero_advance_check = FALSE;
emit_insert(s, last_atom_start, 6 + add_zero_advance_check * 2);
pos = last_atom_start;
arr = JS_VALUE_TO_PTR(s->byte_code);
arr->buf[pos++] = REOP_set_i32;
arr->buf[pos++] = 0;
put_u32(arr->buf + pos, quant_max);
pos += 4;
last_atom_start = pos;
if (add_zero_advance_check) {
arr->buf[pos++] = REOP_set_char_pos;
arr->buf[pos++] = 0;
}
if (quant_min == quant_max) {
re_emit_goto_u8(s, REOP_loop, 0, last_atom_start);
} else {
re_emit_goto_u8_u32(s, (add_zero_advance_check ? REOP_loop_check_adv_split_next_first : REOP_loop_split_next_first) - greedy, 0, quant_max - quant_min, last_atom_start);
}
}
last_atom_start = -1;
}
break;
default:
break;
}
}
static int re_is_char(const uint8_t *buf, int start, int end)
{
int n;
if (!(buf[start] >= REOP_char1 && buf[start] <= REOP_char4))
return 0;
n = buf[start] - REOP_char1 + 1;
if ((end - start) != (n + 1))
return 0;
return n;
}
static int re_parse_alternative(JSParseState *s, int state, int dummy_param)
{
int term_start, last_term_start, last_atom_start, last_capture_count, c, n1, n2, i;
JSByteArray *arr;
PARSE_START3();
last_term_start = -1;
for(;;) {
if (s->buf_pos >= s->buf_len)
break;
term_start = s->byte_code_len;
last_atom_start = -1;
last_capture_count = 0;
c = s->source_buf[s->buf_pos];
switch(c) {
case '|':
case ')':
goto done;
case '^':
s->buf_pos++;
re_emit_op(s, s->multi_line ? REOP_line_start_m : REOP_line_start);
break;
case '$':
s->buf_pos++;
re_emit_op(s, s->multi_line ? REOP_line_end_m : REOP_line_end);
break;
case '.':
s->buf_pos++;
last_atom_start = s->byte_code_len;
last_capture_count = s->capture_count;
re_emit_op(s, s->dotall ? REOP_any : REOP_dot);
break;
case '{':
if (!s->is_unicode && !is_digit(s->source_buf[s->buf_pos + 1]))
goto parse_class_atom;
case '*':
case '+':
case '?':
js_parse_error(s, "nothing to repeat");
case '(':
if (s->source_buf[s->buf_pos + 1] == '?') {
c = s->source_buf[s->buf_pos + 2];
if (c == ':') {
s->buf_pos += 3;
last_atom_start = s->byte_code_len;
last_capture_count = s->capture_count;
PARSE_CALL_SAVE4(s, 0, re_parse_disjunction, 0,
last_term_start, term_start, last_atom_start, last_capture_count);
re_parse_expect(s, ')');
} else if ((c == '=' || c == '!')) {
int is_neg, pos;
is_neg = (c == '!');
s->buf_pos += 3;
pos = re_emit_op_u32(s, REOP_lookahead + is_neg, 0);
PARSE_CALL_SAVE6(s, 1, re_parse_disjunction, 0,
last_term_start, term_start, last_atom_start, last_capture_count,
is_neg, pos);
re_parse_expect(s, ')');
re_emit_op(s, REOP_lookahead_match + is_neg);
arr = JS_VALUE_TO_PTR(s->byte_code);
put_u32(arr->buf + pos, s->byte_code_len - (pos + 4));
} else {
js_parse_error(s, "invalid group");
}
} else {
int capture_index;
s->buf_pos++;
if (s->capture_count >= CAPTURE_COUNT_MAX)
js_parse_error(s, "too many captures");
last_atom_start = s->byte_code_len;
last_capture_count = s->capture_count;
capture_index = s->capture_count++;
re_emit_op_u8(s, REOP_save_start, capture_index);
PARSE_CALL_SAVE5(s, 2, re_parse_disjunction, 0,
last_term_start, term_start, last_atom_start, last_capture_count,
capture_index);
re_emit_op_u8(s, REOP_save_end, capture_index);
re_parse_expect(s, ')');
}
break;
case '\\':
switch(s->source_buf[s->buf_pos + 1]) {
case 'b':
case 'B':
if (s->source_buf[s->buf_pos + 1] != 'b') {
re_emit_op(s, REOP_not_word_boundary);
} else {
re_emit_op(s, REOP_word_boundary);
}
s->buf_pos += 2;
break;
case '0':
s->buf_pos += 2;
c = 0;
if (is_digit(s->source_buf[s->buf_pos]))
js_parse_error(s, "invalid decimal escape in regular expression");
goto normal_char;
case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8':
case '9':
{
const uint8_t *p;
p = s->source_buf + s->buf_pos + 1;
c = parse_digits(&p);
s->buf_pos = p - s->source_buf;
if (c > CAPTURE_COUNT_MAX)
js_parse_error(s, "back reference is out of range");
last_atom_start = s->byte_code_len;
last_capture_count = s->capture_count;
re_emit_op_u8(s, REOP_back_reference + s->ignore_case, c);
}
break;
default:
goto parse_class_atom;
}
break;
case '[':
last_atom_start = s->byte_code_len;
last_capture_count = s->capture_count;
re_parse_char_class(s);
break;
case ']':
case '}':
if (s->is_unicode)
js_parse_error(s, "syntax error");
goto parse_class_atom;
default:
parse_class_atom:
c = get_class_atom(s, FALSE);
normal_char:
last_atom_start = s->byte_code_len;
last_capture_count = s->capture_count;
if (c >= CLASS_RANGE_BASE) {
int range_start;
c -= CLASS_RANGE_BASE;
if (c == CHAR_RANGE_s || c == CHAR_RANGE_S) {
re_emit_op(s, REOP_space + c - CHAR_RANGE_s);
} else {
re_emit_op_u16(s, REOP_range, 0);
range_start = s->byte_code_len;
re_emit_range_base(s, c);
re_range_optimize(s, range_start, FALSE);
}
} else {
if (s->ignore_case &&
((c >= 'A' && c <= 'Z') ||
(c >= 'a' && c <= 'z'))) {
if (c >= 'a')
c -= 32;
re_emit_op_u8(s, REOP_range8, 2);
emit_u8(s, c);
emit_u8(s, c + 1);
emit_u8(s, c + 32);
emit_u8(s, c + 32 + 1);
} else {
re_emit_char(s, c);
}
}
break;
}
if (last_atom_start >= 0) {
re_parse_quantifier(s, last_atom_start, last_capture_count);
}
arr = JS_VALUE_TO_PTR(s->byte_code);
if (last_term_start >= 0 &&
(n1 = re_is_char(arr->buf, last_term_start, term_start)) > 0 &&
(n2 = re_is_char(arr->buf, term_start, s->byte_code_len)) > 0 &&
(n1 + n2) <= 4) {
n1 += n2;
arr->buf[last_term_start] = REOP_char1 + n1 - 1;
for(i = 0; i < n2; i++)
arr->buf[last_term_start + n1 + i] = arr->buf[last_term_start + n1 + i + 1];
s->byte_code_len--;
} else {
last_term_start = term_start;
}
}
done:
return PARSE_STATE_RET;
}
static int re_parse_disjunction(JSParseState *s, int state, int dummy_param)
{
int start, len, pos;
JSByteArray *arr;
PARSE_START2();
start = s->byte_code_len;
PARSE_CALL_SAVE1(s, 0, re_parse_alternative, 0, start);
while (s->source_buf[s->buf_pos] == '|') {
s->buf_pos++;
len = s->byte_code_len - start;
emit_insert(s, start, 5);
arr = JS_VALUE_TO_PTR(s->byte_code);
arr->buf[start] = REOP_split_next_first;
put_u32(arr->buf + start + 1, len + 5);
pos = re_emit_op_u32(s, REOP_goto, 0);
PARSE_CALL_SAVE2(s, 1, re_parse_alternative, 0, start, pos);
len = s->byte_code_len - (pos + 4);
arr = JS_VALUE_TO_PTR(s->byte_code);
put_u32(arr->buf + pos, len);
}
return PARSE_STATE_RET;
}
static int re_compute_register_count(JSParseState *s, uint8_t *bc_buf, int bc_buf_len)
{
int stack_size, stack_size_max, pos, opcode, len;
uint32_t val;
stack_size = 0;
stack_size_max = 0;
pos = 0;
while (pos < bc_buf_len) {
opcode = bc_buf[pos];
len = reopcode_info[opcode].size;
assert(opcode < REOP_COUNT);
assert((pos + len) <= bc_buf_len);
switch(opcode) {
case REOP_set_i32:
case REOP_set_char_pos:
bc_buf[pos + 1] = stack_size;
stack_size++;
if (stack_size > stack_size_max) {
if (stack_size > REGISTER_COUNT_MAX)
js_parse_error(s, "too many regexp registers");
stack_size_max = stack_size;
}
break;
case REOP_check_advance:
case REOP_loop:
case REOP_loop_split_goto_first:
case REOP_loop_split_next_first:
assert(stack_size > 0);
stack_size--;
bc_buf[pos + 1] = stack_size;
break;
case REOP_loop_check_adv_split_goto_first:
case REOP_loop_check_adv_split_next_first:
assert(stack_size >= 2);
stack_size -= 2;
bc_buf[pos + 1] = stack_size;
break;
case REOP_range8:
val = bc_buf[pos + 1];
len += val * 2;
break;
case REOP_range:
val = get_u16(bc_buf + pos + 1);
len += val * 8;
break;
case REOP_back_reference:
case REOP_back_reference_i:
if (bc_buf[pos + 1] >= s->capture_count)
js_parse_error(s, "back reference is out of range");
break;
}
pos += len;
}
return stack_size_max;
}
static JSValue js_parse_regexp(JSParseState *s, int re_flags)
{
JSByteArray *arr;
int register_count;
s->multi_line = ((re_flags & LRE_FLAG_MULTILINE) != 0);
s->dotall = ((re_flags & LRE_FLAG_DOTALL) != 0);
s->ignore_case = ((re_flags & LRE_FLAG_IGNORECASE) != 0);
s->is_unicode = ((re_flags & LRE_FLAG_UNICODE) != 0);
s->byte_code = JS_NULL;
s->byte_code_len = 0;
s->capture_count = 1;
emit_u16(s, re_flags);
emit_u8(s, 0);
emit_u8(s, 0);
if (!(re_flags & LRE_FLAG_STICKY)) {
re_emit_op_u32(s, REOP_split_goto_first, 1 + 5);
re_emit_op(s, REOP_any);
re_emit_op_u32(s, REOP_goto, -(5 + 1 + 5));
}
re_emit_op_u8(s, REOP_save_start, 0);
js_parse_call(s, PARSE_FUNC_re_parse_disjunction, 0);
re_emit_op_u8(s, REOP_save_end, 0);
re_emit_op(s, REOP_match);
if (s->buf_pos != s->buf_len)
js_parse_error(s, "extraneous characters at the end");
arr = JS_VALUE_TO_PTR(s->byte_code);
arr->buf[RE_HEADER_CAPTURE_COUNT] = s->capture_count;
register_count =
re_compute_register_count(s, arr->buf + RE_HEADER_LEN,
s->byte_code_len - RE_HEADER_LEN);
arr->buf[RE_HEADER_REGISTER_COUNT] = register_count;
js_shrink_byte_array(s->ctx, &s->byte_code, s->byte_code_len);
#ifdef DUMP_REOP
arr = JS_VALUE_TO_PTR(s->byte_code);
lre_dump_bytecode(arr->buf, arr->size);
#endif
return s->byte_code;
}
#define CP_LS 0x2028
#define CP_PS 0x2029
static BOOL is_line_terminator(uint32_t c)
{
return (c == '\n' || c == '\r' || c == CP_LS || c == CP_PS);
}
static BOOL is_word_char(uint32_t c)
{
return ((c >= '0' && c <= '9') ||
(c >= 'a' && c <= 'z') ||
(c >= 'A' && c <= 'Z') ||
(c == '_'));
}
static int lre_canonicalize(uint32_t c)
{
if (c >= 'A' && c <= 'Z') {
c = c - 'A' + 'a';
}
return c;
}
#define GET_CHAR(c, cptr, cbuf_end) \
do { \
size_t clen; \
c = utf8_get(cptr, &clen); \
cptr += clen; \
} while (0)
#define PEEK_CHAR(c, cptr, cbuf_end) \
do { \
size_t clen; \
c = utf8_get(cptr, &clen); \
} while (0)
#define PEEK_PREV_CHAR(c, cptr, cbuf_start) \
do { \
const uint8_t *cptr1 = cptr - 1; \
size_t clen; \
while ((*cptr1 & 0xc0) == 0x80) \
cptr1--; \
c = utf8_get(cptr1, &clen); \
} while (0)
typedef enum {
RE_EXEC_STATE_SPLIT,
RE_EXEC_STATE_LOOKAHEAD,
RE_EXEC_STATE_NEGATIVE_LOOKAHEAD,
} REExecStateEnum;
static int lre_exec(JSContext *ctx, JSValue capture_buf,
JSValue byte_code, JSValue str, int cindex)
{
const uint8_t *pc, *cptr, *cbuf;
uint32_t *capture;
int opcode, capture_count;
uint32_t val, c, idx;
const uint8_t *cbuf_end;
JSValue *sp, *bp, *initial_sp, *saved_stack_bottom;
JSByteArray *arr;
JSString *ps;
JSGCRef capture_buf_ref, byte_code_ref, str_ref;
arr = JS_VALUE_TO_PTR(byte_code);
pc = arr->buf;
arr = JS_VALUE_TO_PTR(capture_buf);
capture = (uint32_t *)arr->buf;
capture_count = lre_get_capture_count(pc);
pc += RE_HEADER_LEN;
ps = JS_VALUE_TO_PTR(str);
cbuf = ps->buf;
cbuf_end = cbuf + ps->len;
cptr = cbuf + cindex;
saved_stack_bottom = ctx->stack_bottom;
initial_sp = ctx->sp;
sp = initial_sp;
bp = initial_sp;
#define LRE_POLL_INTERRUPT() do { \
if (unlikely(--ctx->interrupt_counter <= 0)) { \
JSValue ret; \
int saved_pc, saved_cptr; \
arr = JS_VALUE_TO_PTR(byte_code); \
saved_pc = pc - arr->buf; \
saved_cptr = cptr - cbuf; \
JS_PUSH_VALUE(ctx, capture_buf); \
JS_PUSH_VALUE(ctx, byte_code); \
JS_PUSH_VALUE(ctx, str); \
ctx->sp = sp; \
ret = __js_poll_interrupt(ctx); \
JS_POP_VALUE(ctx, str); \
JS_POP_VALUE(ctx, byte_code); \
JS_POP_VALUE(ctx, capture_buf); \
if (JS_IsException(ret)) { \
ctx->sp = initial_sp; \
ctx->stack_bottom = saved_stack_bottom; \
return -1; \
} \
arr = JS_VALUE_TO_PTR(byte_code); \
pc = arr->buf + saved_pc; \
ps = JS_VALUE_TO_PTR(str); \
cbuf = ps->buf; \
cbuf_end = cbuf + ps->len; \
cptr = cbuf + saved_cptr; \
arr = JS_VALUE_TO_PTR(capture_buf); \
capture = (uint32_t *)arr->buf; \
} \
} while(0)
#define CHECK_STACK_SPACE(n) \
{ \
if (unlikely((sp - ctx->stack_bottom) < (n))) { \
int ret, saved_pc, saved_cptr; \
arr = JS_VALUE_TO_PTR(byte_code); \
saved_pc = pc - arr->buf; \
saved_cptr = cptr - cbuf; \
JS_PUSH_VALUE(ctx, capture_buf); \
JS_PUSH_VALUE(ctx, byte_code); \
JS_PUSH_VALUE(ctx, str); \
ctx->sp = sp; \
ret = JS_StackCheck(ctx, n); \
JS_POP_VALUE(ctx, str); \
JS_POP_VALUE(ctx, byte_code); \
JS_POP_VALUE(ctx, capture_buf); \
if (ret < 0) { \
ctx->sp = initial_sp; \
ctx->stack_bottom = saved_stack_bottom; \
return -1; \
} \
arr = JS_VALUE_TO_PTR(byte_code); \
pc = arr->buf + saved_pc; \
ps = JS_VALUE_TO_PTR(str); \
cbuf = ps->buf; \
cbuf_end = cbuf + ps->len; \
cptr = cbuf + saved_cptr; \
arr = JS_VALUE_TO_PTR(capture_buf); \
capture = (uint32_t *)arr->buf; \
} \
}
#define SAVE_CAPTURE(idx, value) \
{ \
int __v = (value); \
CHECK_STACK_SPACE(2); \
sp[-2] = JS_NewShortInt(idx); \
sp[-1] = JS_NewShortInt(capture[idx]); \
sp -= 2; \
capture[idx] = __v; \
}
#define SAVE_CAPTURE_CHECK(idx, value) \
{ \
int __v = (value); \
JSValue *sp1; \
sp1 = sp; \
for(;;) { \
if (sp1 < bp) { \
if (JS_VALUE_GET_INT(sp1[0]) == (idx)) \
break; \
sp1 += 2; \
} else { \
CHECK_STACK_SPACE(2); \
sp[-2] = JS_NewShortInt(idx); \
sp[-1] = JS_NewShortInt(capture[idx]); \
sp -= 2; \
break; \
} \
} \
capture[idx] = __v; \
}
#define RE_PC_TYPE_TO_VALUE(pc, type) (((type) << 1) | (((pc) - ((JSByteArray *)JS_VALUE_TO_PTR(byte_code))->buf) << 3))
#define RE_VALUE_TO_PC(val) (((val) >> 3) + ((JSByteArray *)JS_VALUE_TO_PTR(byte_code))->buf)
#define RE_VALUE_TO_TYPE(val) (((val) >> 1) & 3)
#ifdef DUMP_REEXEC
printf("%5s %5s %5s %5s %s\n", "PC", "CP", "BP", "SP", "OPCODE");
#endif
for(;;) {
opcode = *pc++;
#ifdef DUMP_REEXEC
printf("%5ld %5ld %5ld %5ld %s\n",
pc - 1 - ((JSByteArray *)JS_VALUE_TO_PTR(byte_code))->buf - RE_HEADER_LEN,
cptr - cbuf,
bp - initial_sp,
sp - initial_sp,
reopcode_info[opcode].name);
#endif
switch(opcode) {
case REOP_match:
ctx->sp = initial_sp;
ctx->stack_bottom = saved_stack_bottom;
return 1;
no_match:
for(;;) {
REExecStateEnum type;
if (bp == initial_sp) {
ctx->sp = initial_sp;
ctx->stack_bottom = saved_stack_bottom;
return 0;
}
while (sp < bp) {
int idx2 = JS_VALUE_GET_INT(sp[0]);
capture[idx2] = JS_VALUE_GET_INT(sp[1]);
sp += 2;
}
pc = RE_VALUE_TO_PC(sp[0]);
type = RE_VALUE_TO_TYPE(sp[0]);
cptr = JS_VALUE_GET_INT(sp[1]) + cbuf;
bp = VALUE_TO_SP(ctx, sp[2]);
sp += 3;
if (type != RE_EXEC_STATE_LOOKAHEAD)
break;
}
LRE_POLL_INTERRUPT();
break;
case REOP_lookahead_match:
{
JSValue *sp1, *sp_start, *next_sp;
REExecStateEnum type;
sp_start = sp;
for(;;) {
sp1 = sp;
sp = bp;
pc = RE_VALUE_TO_PC(sp[0]);
type = RE_VALUE_TO_TYPE(sp[0]);
cptr = JS_VALUE_GET_INT(sp[1]) + cbuf;
bp = VALUE_TO_SP(ctx, sp[2]);
sp[2] = SP_TO_VALUE(ctx, sp1);
sp += 3;
if (type == RE_EXEC_STATE_LOOKAHEAD)
break;
}
if (sp != initial_sp) {
sp1 = sp;
while (sp1 != sp_start) {
sp1 -= 3;
next_sp = VALUE_TO_SP(ctx, sp1[2]);
while (sp1 != next_sp) {
*--sp = *--sp1;
}
}
}
}
break;
case REOP_negative_lookahead_match:
for(;;) {
REExecStateEnum type;
type = RE_VALUE_TO_TYPE(bp[0]);
while (sp < bp) {
int idx2 = JS_VALUE_GET_INT(sp[0]);
capture[idx2] = JS_VALUE_GET_INT(sp[1]);
sp += 2;
}
pc = RE_VALUE_TO_PC(sp[0]);
type = RE_VALUE_TO_TYPE(sp[0]);
cptr = JS_VALUE_GET_INT(sp[1]) + cbuf;
bp = VALUE_TO_SP(ctx, sp[2]);
sp += 3;
if (type == RE_EXEC_STATE_NEGATIVE_LOOKAHEAD)
break;
}
goto no_match;
case REOP_char1:
if ((cbuf_end - cptr) < 1)
goto no_match;
if (pc[0] != cptr[0])
goto no_match;
pc++;
cptr++;
break;
case REOP_char2:
if ((cbuf_end - cptr) < 2)
goto no_match;
if (get_u16(pc) != get_u16(cptr))
goto no_match;
pc += 2;
cptr += 2;
break;
case REOP_char3:
if ((cbuf_end - cptr) < 3)
goto no_match;
if (get_u16(pc) != get_u16(cptr) || pc[2] != cptr[2])
goto no_match;
pc += 3;
cptr += 3;
break;
case REOP_char4:
if ((cbuf_end - cptr) < 4)
goto no_match;
if (get_u32(pc) != get_u32(cptr))
goto no_match;
pc += 4;
cptr += 4;
break;
case REOP_split_goto_first:
case REOP_split_next_first:
{
const uint8_t *pc1;
val = get_u32(pc);
pc += 4;
CHECK_STACK_SPACE(3);
if (opcode == REOP_split_next_first) {
pc1 = pc + (int)val;
} else {
pc1 = pc;
pc = pc + (int)val;
}
sp -= 3;
sp[0] = RE_PC_TYPE_TO_VALUE(pc1, RE_EXEC_STATE_SPLIT);
sp[1] = JS_NewShortInt(cptr - cbuf);
sp[2] = SP_TO_VALUE(ctx, bp);
bp = sp;
}
break;
case REOP_lookahead:
case REOP_negative_lookahead:
val = get_u32(pc);
pc += 4;
CHECK_STACK_SPACE(3);
sp -= 3;
sp[0] = RE_PC_TYPE_TO_VALUE(pc + (int)val,
RE_EXEC_STATE_LOOKAHEAD + opcode - REOP_lookahead);
sp[1] = JS_NewShortInt(cptr - cbuf);
sp[2] = SP_TO_VALUE(ctx, bp);
bp = sp;
break;
case REOP_goto:
val = get_u32(pc);
pc += 4 + (int)val;
LRE_POLL_INTERRUPT();
break;
case REOP_line_start:
case REOP_line_start_m:
if (cptr == cbuf)
break;
if (opcode == REOP_line_start)
goto no_match;
PEEK_PREV_CHAR(c, cptr, cbuf);
if (!is_line_terminator(c))
goto no_match;
break;
case REOP_line_end:
case REOP_line_end_m:
if (cptr == cbuf_end)
break;
if (opcode == REOP_line_end)
goto no_match;
PEEK_CHAR(c, cptr, cbuf_end);
if (!is_line_terminator(c))
goto no_match;
break;
case REOP_dot:
if (cptr == cbuf_end)
goto no_match;
GET_CHAR(c, cptr, cbuf_end);
if (is_line_terminator(c))
goto no_match;
break;
case REOP_any:
if (cptr == cbuf_end)
goto no_match;
GET_CHAR(c, cptr, cbuf_end);
break;
case REOP_space:
case REOP_not_space:
{
BOOL v1;
if (cptr == cbuf_end)
goto no_match;
c = cptr[0];
if (c < 128) {
cptr++;
v1 = unicode_is_space_ascii(c);
} else {
size_t clen;
c = __utf8_get(cptr, &clen);
cptr += clen;
v1 = unicode_is_space_non_ascii(c);
}
v1 ^= (opcode - REOP_space);
if (!v1)
goto no_match;
}
break;
case REOP_save_start:
case REOP_save_end:
val = *pc++;
assert(val < capture_count);
idx = 2 * val + opcode - REOP_save_start;
SAVE_CAPTURE(idx, cptr - cbuf);
break;
case REOP_save_reset:
{
uint32_t val2;
val = pc[0];
val2 = pc[1];
pc += 2;
assert(val2 < capture_count);
CHECK_STACK_SPACE(2 * (val2 - val + 1));
while (val <= val2) {
idx = 2 * val;
SAVE_CAPTURE(idx, 0);
idx = 2 * val + 1;
SAVE_CAPTURE(idx, 0);
val++;
}
}
break;
case REOP_set_i32:
idx = pc[0];
val = get_u32(pc + 1);
pc += 5;
SAVE_CAPTURE_CHECK(2 * capture_count + idx, val);
break;
case REOP_loop:
{
uint32_t val2;
idx = pc[0];
val = get_u32(pc + 1);
pc += 5;
val2 = capture[2 * capture_count + idx] - 1;
SAVE_CAPTURE_CHECK(2 * capture_count + idx, val2);
if (val2 != 0) {
pc += (int)val;
LRE_POLL_INTERRUPT();
}
}
break;
case REOP_loop_split_goto_first:
case REOP_loop_split_next_first:
case REOP_loop_check_adv_split_goto_first:
case REOP_loop_check_adv_split_next_first:
{
const uint8_t *pc1;
uint32_t val2, limit;
idx = pc[0];
limit = get_u32(pc + 1);
val = get_u32(pc + 5);
pc += 9;
val2 = capture[2 * capture_count + idx] - 1;
SAVE_CAPTURE_CHECK(2 * capture_count + idx, val2);
if (val2 > limit) {
pc += (int)val;
LRE_POLL_INTERRUPT();
} else {
if ((opcode == REOP_loop_check_adv_split_goto_first ||
opcode == REOP_loop_check_adv_split_next_first) &&
capture[2 * capture_count + idx + 1] == (cptr - cbuf) &&
val2 != limit) {
goto no_match;
}
if (val2 != 0) {
CHECK_STACK_SPACE(3);
if (opcode == REOP_loop_split_next_first ||
opcode == REOP_loop_check_adv_split_next_first) {
pc1 = pc + (int)val;
} else {
pc1 = pc;
pc = pc + (int)val;
}
sp -= 3;
sp[0] = RE_PC_TYPE_TO_VALUE(pc1, RE_EXEC_STATE_SPLIT);
sp[1] = JS_NewShortInt(cptr - cbuf);
sp[2] = SP_TO_VALUE(ctx, bp);
bp = sp;
}
}
}
break;
case REOP_set_char_pos:
idx = pc[0];
pc++;
SAVE_CAPTURE_CHECK(2 * capture_count + idx, cptr - cbuf);
break;
case REOP_check_advance:
idx = pc[0];
pc++;
if (capture[2 * capture_count + idx] == cptr - cbuf)
goto no_match;
break;
case REOP_word_boundary:
case REOP_not_word_boundary:
{
BOOL v1, v2;
BOOL is_boundary = (opcode == REOP_word_boundary);
if (cptr == cbuf) {
v1 = FALSE;
} else {
PEEK_PREV_CHAR(c, cptr, cbuf);
v1 = is_word_char(c);
}
if (cptr >= cbuf_end) {
v2 = FALSE;
} else {
PEEK_CHAR(c, cptr, cbuf_end);
v2 = is_word_char(c);
}
if (v1 ^ v2 ^ is_boundary)
goto no_match;
}
break;
case REOP_range8:
{
int n, i;
n = pc[0];
pc++;
if (cptr >= cbuf_end)
goto no_match;
GET_CHAR(c, cptr, cbuf_end);
for(i = 0; i < n - 1; i++) {
if (c >= pc[2 * i] && c < pc[2 * i + 1])
goto range8_match;
}
if (c >= pc[2 * i] &&
(c < pc[2 * i + 1] || pc[2 * i + 1] == 0xff))
goto range8_match;
goto no_match;
range8_match:
pc += 2 * n;
}
break;
case REOP_range:
{
int n;
uint32_t low, high, idx_min, idx_max, idx;
n = get_u16(pc);
pc += 2;
if (cptr >= cbuf_end || n == 0)
goto no_match;
GET_CHAR(c, cptr, cbuf_end);
idx_min = 0;
low = get_u32(pc + 0 * 8);
if (c < low)
goto no_match;
idx_max = n - 1;
high = get_u32(pc + idx_max * 8 + 4);
if (c >= high)
goto no_match;
while (idx_min <= idx_max) {
idx = (idx_min + idx_max) / 2;
low = get_u32(pc + idx * 8);
high = get_u32(pc + idx * 8 + 4);
if (c < low)
idx_max = idx - 1;
else if (c >= high)
idx_min = idx + 1;
else
goto range_match;
}
goto no_match;
range_match:
pc += 8 * n;
}
break;
case REOP_back_reference:
case REOP_back_reference_i:
val = pc[0];
pc++;
if (capture[2 * val] != -1 && capture[2 * val + 1] != -1) {
const uint8_t *cptr1, *cptr1_end;
int c1, c2;
cptr1 = cbuf + capture[2 * val];
cptr1_end = cbuf + capture[2 * val + 1];
while (cptr1 < cptr1_end) {
if (cptr >= cbuf_end)
goto no_match;
GET_CHAR(c1, cptr1, cptr1_end);
GET_CHAR(c2, cptr, cbuf_end);
if (opcode == REOP_back_reference_i) {
c1 = lre_canonicalize(c1);
c2 = lre_canonicalize(c2);
}
if (c1 != c2)
goto no_match;
}
}
break;
default:
#ifdef DUMP_REEXEC
printf("unknown opcode pc=%ld\n", pc - 1 - ((JSByteArray *)JS_VALUE_TO_PTR(byte_code))->buf - RE_HEADER_LEN);
#endif
abort();
}
}
}
static size_t js_parse_regexp_flags(int *pre_flags, const uint8_t *buf)
{
const uint8_t *p = buf;
int mask, re_flags;
re_flags = 0;
while (*p != '\0') {
switch(*p) {
#if 0#endif
case 'g':
mask = LRE_FLAG_GLOBAL;
break;
case 'i':
mask = LRE_FLAG_IGNORECASE;
break;
case 'm':
mask = LRE_FLAG_MULTILINE;
break;
case 's':
mask = LRE_FLAG_DOTALL;
break;
case 'u':
mask = LRE_FLAG_UNICODE;
break;
#if 0#endif
case 'y':
mask = LRE_FLAG_STICKY;
break;
default:
goto done;
}
if ((re_flags & mask) != 0)
break;
re_flags |= mask;
p++;
}
done:
*pre_flags = re_flags;
return p - buf;
}
static JSValue js_compile_regexp(JSContext *ctx, JSValue pattern, JSValue flags)
{
int re_flags;
re_flags = 0;
if (!JS_IsUndefined(flags)) {
JSString *ps;
JSStringCharBuf buf;
size_t len;
ps = get_string_ptr(ctx, &buf, flags);
len = js_parse_regexp_flags(&re_flags, ps->buf);
if (len != ps->len)
return JS_ThrowSyntaxError(ctx, "invalid regular expression flags");
}
return JS_Parse2(ctx, pattern, NULL, 0, "<regexp>",
JS_EVAL_REGEXP | (re_flags << JS_EVAL_REGEXP_FLAGS_SHIFT));
}
static JSRegExp *js_get_regexp(JSContext *ctx, JSValue obj)
{
JSObject *p;
p = js_get_object_class(ctx, obj, JS_CLASS_REGEXP);
if (!p) {
JS_ThrowTypeError(ctx, "not a regular expression");
return NULL;
}
return &p->u.regexp;
}
JSValue js_regexp_get_lastIndex(JSContext *ctx, JSValue *this_val,
int argc, JSValue *argv)
{
JSRegExp *re = js_get_regexp(ctx, *this_val);
if (!re)
return JS_EXCEPTION;
return JS_NewInt32(ctx, re->last_index);
}
JSValue js_regexp_get_source(JSContext *ctx, JSValue *this_val,
int argc, JSValue *argv)
{
JSRegExp *re = js_get_regexp(ctx, *this_val);
if (!re)
return JS_EXCEPTION;
return re->source;
}
JSValue js_regexp_set_lastIndex(JSContext *ctx, JSValue *this_val,
int argc, JSValue *argv)
{
JSRegExp *re;
int last_index;
if (JS_ToInt32(ctx, &last_index, argv[0]))
return JS_EXCEPTION;
re = js_get_regexp(ctx, *this_val);
if (!re)
return JS_EXCEPTION;
re->last_index = last_index;
return JS_UNDEFINED;
}
#define RE_FLAG_COUNT 6
static size_t js_regexp_flags_str(char *buf, int re_flags)
{
static const char flag_char[RE_FLAG_COUNT] = { 'g', 'i', 'm', 's', 'u', 'y' };
char *p = buf;
int i;
for(i = 0; i < RE_FLAG_COUNT; i++) {
if ((re_flags >> i) & 1)
*p++ = flag_char[i];
}
*p = '\0';
return p - buf;
}
static void dump_regexp(JSContext *ctx, JSObject *p)
{
JSStringCharBuf buf;
JSString *ps;
char buf2[RE_FLAG_COUNT + 1];
JSByteArray *arr;
js_putchar(ctx, '/');
ps = get_string_ptr(ctx, &buf, p->u.regexp.source);
if (ps->len == 0) {
js_printf(ctx, "(?:)");
} else {
js_printf(ctx, "%" JSValue_PRI, p->u.regexp.source);
}
arr = JS_VALUE_TO_PTR(p->u.regexp.byte_code);
js_regexp_flags_str(buf2, lre_get_flags(arr->buf));
js_printf(ctx, "/%s", buf2);
}
JSValue js_regexp_get_flags(JSContext *ctx, JSValue *this_val,
int argc, JSValue *argv)
{
JSRegExp *re;
JSByteArray *arr;
size_t len;
char buf[RE_FLAG_COUNT + 1];
re = js_get_regexp(ctx, *this_val);
if (!re)
return JS_EXCEPTION;
arr = JS_VALUE_TO_PTR(re->byte_code);
len = js_regexp_flags_str(buf, lre_get_flags(arr->buf));
return JS_NewStringLen(ctx, buf, len);
}
JSValue js_regexp_constructor(JSContext *ctx, JSValue *this_val,
int argc, JSValue *argv)
{
JSValue obj, byte_code;
JSObject *p;
JSGCRef byte_code_ref;
argc &= ~FRAME_CF_CTOR;
argv[0] = JS_ToString(ctx, argv[0]);
if (JS_IsException(argv[0]))
return JS_EXCEPTION;
if (!JS_IsUndefined(argv[1])) {
argv[1] = JS_ToString(ctx, argv[1]);
if (JS_IsException(argv[1]))
return JS_EXCEPTION;
}
byte_code = js_compile_regexp(ctx, argv[0], argv[1]);
if (JS_IsException(byte_code))
return JS_EXCEPTION;
JS_PUSH_VALUE(ctx, byte_code);
obj = JS_NewObjectClass(ctx, JS_CLASS_REGEXP, sizeof(JSRegExp));
JS_POP_VALUE(ctx, byte_code);
if (JS_IsException(obj))
return obj;
p = JS_VALUE_TO_PTR(obj);
p->u.regexp.source = argv[0];
p->u.regexp.byte_code = byte_code;
p->u.regexp.last_index = 0;
return obj;
}
enum {
MAGIC_REGEXP_EXEC,
MAGIC_REGEXP_TEST,
MAGIC_REGEXP_SEARCH,
MAGIC_REGEXP_FORCE_GLOBAL,
};
JSValue js_regexp_exec(JSContext *ctx, JSValue *this_val,
int argc, JSValue *argv, int magic)
{
JSObject *p;
JSRegExp *re;
JSValue obj, *capture_buf, res;
uint32_t *capture, last_index_utf8;
int rc, capture_count, i, re_flags, last_index;
JSByteArray *bc_arr, *carr;
JSGCRef capture_buf_ref, obj_ref;
JSString *str;
JSStringCharBuf str_buf;
re = js_get_regexp(ctx, *this_val);
if (!re)
return JS_EXCEPTION;
argv[0] = JS_ToString(ctx, argv[0]);
if (JS_IsException(argv[0]))
return JS_EXCEPTION;
p = JS_VALUE_TO_PTR(*this_val);
re = &p->u.regexp;
last_index = max_int(re->last_index, 0);
bc_arr = JS_VALUE_TO_PTR(re->byte_code);
re_flags = lre_get_flags(bc_arr->buf);
if (magic == MAGIC_REGEXP_FORCE_GLOBAL)
re_flags |= MAGIC_REGEXP_FORCE_GLOBAL;
if ((re_flags & (LRE_FLAG_GLOBAL | LRE_FLAG_STICKY)) == 0 ||
magic == MAGIC_REGEXP_SEARCH) {
last_index = 0;
}
capture_count = lre_get_capture_count(bc_arr->buf);
carr = js_alloc_byte_array(ctx, sizeof(uint32_t) * lre_get_alloc_count(bc_arr->buf));
if (!carr)
goto fail;
capture_buf = JS_PushGCRef(ctx, &capture_buf_ref);
*capture_buf = JS_VALUE_FROM_PTR(carr);
capture = (uint32_t *)carr->buf;
for(i = 0; i < 2 * capture_count; i++)
capture[i] = -1;
if (last_index <= 0)
last_index_utf8 = 0;
else
last_index_utf8 = js_string_utf16_to_utf8_pos(ctx, argv[0], last_index) / 2;
if (last_index_utf8 > js_string_byte_len(ctx, argv[0])) {
rc = 2;
} else {
p = JS_VALUE_TO_PTR(*this_val);
re = &p->u.regexp;
str = get_string_ptr(ctx, &str_buf, argv[0]);
rc = lre_exec(ctx, *capture_buf, re->byte_code, JS_VALUE_FROM_PTR(str),
last_index_utf8);
}
if (rc != 1) {
if (rc >= 0) {
if (re_flags & (LRE_FLAG_GLOBAL | LRE_FLAG_STICKY)) {
p = JS_VALUE_TO_PTR(*this_val);
re = &p->u.regexp;
re->last_index = 0;
}
if (magic == MAGIC_REGEXP_SEARCH)
obj = JS_NewShortInt(-1);
else if (magic == MAGIC_REGEXP_TEST)
obj = JS_FALSE;
else
obj = JS_NULL;
} else {
goto fail;
}
} else {
capture = (uint32_t *)((JSByteArray *)JS_VALUE_TO_PTR(*capture_buf))->buf;
if (magic == MAGIC_REGEXP_SEARCH) {
obj = JS_NewShortInt(js_string_utf8_to_utf16_pos(ctx, argv[0], capture[0] * 2));
goto done;
}
if (re_flags & (LRE_FLAG_GLOBAL | LRE_FLAG_STICKY)) {
p = JS_VALUE_TO_PTR(*this_val);
re = &p->u.regexp;
re->last_index = js_string_utf8_to_utf16_pos(ctx, argv[0], capture[1] * 2);
}
if (magic == MAGIC_REGEXP_TEST) {
obj = JS_TRUE;
} else {
obj = JS_NewArray(ctx, capture_count);
if (JS_IsException(obj))
goto fail;
JS_PUSH_VALUE(ctx, obj);
capture = (uint32_t *)((JSByteArray *)JS_VALUE_TO_PTR(*capture_buf))->buf;
res = JS_DefinePropertyValue(ctx, obj, js_get_atom(ctx, JS_ATOM_index),
JS_NewShortInt(js_string_utf8_to_utf16_pos(ctx, argv[0], capture[0] * 2)));
JS_POP_VALUE(ctx, obj);
if (JS_IsException(res))
goto fail;
JS_PUSH_VALUE(ctx, obj);
res = JS_DefinePropertyValue(ctx, obj, js_get_atom(ctx, JS_ATOM_input),
argv[0]);
JS_POP_VALUE(ctx, obj);
if (JS_IsException(res))
goto fail;
for(i = 0; i < capture_count; i++) {
int start, end;
JSValue val;
capture = (uint32_t *)((JSByteArray *)JS_VALUE_TO_PTR(*capture_buf))->buf;
start = capture[2 * i];
end = capture[2 * i + 1];
if (start != -1 && end != -1) {
JSValueArray *arr;
JS_PUSH_VALUE(ctx, obj);
val = js_sub_string_utf8(ctx, argv[0], 2 * start, 2 * end);
JS_POP_VALUE(ctx, obj);
if (JS_IsException(val))
goto fail;
p = JS_VALUE_TO_PTR(obj);
arr = JS_VALUE_TO_PTR(p->u.array.tab);
arr->arr[i] = val;
}
}
}
}
done:
JS_PopGCRef(ctx, &capture_buf_ref);
return obj;
fail:
obj = JS_EXCEPTION;
goto done;
}
static int js_string_concat_subst(JSContext *ctx, StringBuffer *b,
JSValue *str, JSValue *rep,
uint32_t pos, uint32_t end_of_match,
JSValue *capture_buf, uint32_t captures_len,
JSValue *needle)
{
JSStringCharBuf buf_rep;
JSString *p;
int rep_len, i, j, j0, c, k;
if (JS_IsFunction(ctx, *rep)) {
JSValue res, val;
JSGCRef val_ref;
int ret;
if (JS_StackCheck(ctx, 4 + captures_len))
return -1;
JS_PushArg(ctx, *str);
JS_PushArg(ctx, JS_NewShortInt(pos));
if (capture_buf) {
for(k = captures_len - 1; k >= 0; k--) {
uint32_t *captures = (uint32_t *)((JSByteArray *)JS_VALUE_TO_PTR(*capture_buf))->buf;
if (captures[2 * k] != -1 && captures[2 * k + 1] != -1) {
val = js_sub_string_utf8(ctx, *str, captures[2 * k] * 2, captures[2 * k + 1] * 2);
if (JS_IsException(val))
return -1;
JS_PUSH_VALUE(ctx, val);
ret = JS_StackCheck(ctx, 3 + k);
JS_POP_VALUE(ctx, val);
if (ret)
return -1;
} else {
val = JS_UNDEFINED;
}
JS_PushArg(ctx, val);
}
} else {
JS_PushArg(ctx, *needle);
}
JS_PushArg(ctx, *rep);
JS_PushArg(ctx, JS_UNDEFINED);
res = JS_Call(ctx, 2 + captures_len);
if (JS_IsException(res))
return -1;
return string_buffer_concat(ctx, b, res);
}
p = get_string_ptr(ctx, &buf_rep, *rep);
rep_len = p->len;
i = 0;
for(;;) {
p = get_string_ptr(ctx, &buf_rep, *rep);
j = i;
while (j < rep_len && p->buf[j] != '$')
j++;
if (j + 1 >= rep_len)
break;
j0 = j++;
c = p->buf[j++];
string_buffer_concat_utf8(ctx, b, *rep, 2 * i, 2 * j0);
if (c == '$') {
string_buffer_putc(ctx, b, '$');
} else if (c == '&') {
if (capture_buf) {
string_buffer_concat_utf16(ctx, b, *str, pos, end_of_match);
} else {
string_buffer_concat_str(ctx, b, *needle);
}
} else if (c == '`') {
string_buffer_concat_utf16(ctx, b, *str, 0, pos);
} else if (c == '\'') {
string_buffer_concat_utf16(ctx, b, *str, end_of_match, js_string_len(ctx, *str));
} else if (c >= '0' && c <= '9') {
k = c - '0';
if (j < rep_len) {
c = p->buf[j];
if (c >= '0' && c <= '9') {
k = k * 10 + c - '0';
j++;
}
}
if (k >= 1 && k < captures_len) {
uint32_t *captures = (uint32_t *)((JSByteArray *)JS_VALUE_TO_PTR(*capture_buf))->buf;
if (captures[2 * k] != -1 && captures[2 * k + 1] != -1) {
string_buffer_concat_utf8(ctx, b, *str,
captures[2 * k] * 2, captures[2 * k + 1] * 2);
}
} else {
goto no_rep;
}
} else {
no_rep:
string_buffer_concat_utf8(ctx, b, *rep, 2 * j0, 2 * j);
}
i = j;
}
return string_buffer_concat_utf8(ctx, b, *rep, 2 * i, 2 * rep_len);
}
JSValue js_string_replace(JSContext *ctx, JSValue *this_val,
int argc, JSValue *argv, int is_replaceAll)
{
StringBuffer b_s, *b = &b_s;
int pos, endOfLastMatch, needle_len, input_len;
BOOL is_first, is_regexp;
*this_val = JS_ToString(ctx, *this_val);
if (JS_IsException(*this_val))
return JS_EXCEPTION;
is_regexp = (JS_GetClassID(ctx, argv[0]) == JS_CLASS_REGEXP);
if (!is_regexp) {
argv[0] = JS_ToString(ctx, argv[0]);
if (JS_IsException(argv[0]))
return JS_EXCEPTION;
}
if (!JS_IsFunction(ctx, argv[1])) {
argv[1] = JS_ToString(ctx, argv[1]);
if (JS_IsException(argv[1]))
return JS_EXCEPTION;
}
input_len = js_string_len(ctx, *this_val);
endOfLastMatch = 0;
string_buffer_push(ctx, b, 0);
if (is_regexp) {
int start, end, last_index, ret, re_flags, i, capture_count;
JSObject *p;
JSByteArray *bc_arr, *carr;
JSValue *capture_buf;
uint32_t *capture;
JSGCRef capture_buf_ref;
p = JS_VALUE_TO_PTR(argv[0]);
bc_arr = JS_VALUE_TO_PTR(p->u.regexp.byte_code);
re_flags = lre_get_flags(bc_arr->buf);
capture_count = lre_get_capture_count(bc_arr->buf);
if (re_flags & LRE_FLAG_GLOBAL)
p->u.regexp.last_index = 0;
if ((re_flags & (LRE_FLAG_GLOBAL | LRE_FLAG_STICKY)) == 0) {
last_index = 0;
} else {
last_index = max_int(p->u.regexp.last_index, 0);
}
carr = js_alloc_byte_array(ctx, sizeof(uint32_t) * lre_get_alloc_count(bc_arr->buf));
if (!carr) {
string_buffer_pop(ctx, b);
return JS_EXCEPTION;
}
capture_buf = JS_PushGCRef(ctx, &capture_buf_ref);
*capture_buf = JS_VALUE_FROM_PTR(carr);
capture = (uint32_t *)carr->buf;
for(i = 0; i < 2 * capture_count; i++)
capture[i] = -1;
for(;;) {
if (last_index > input_len) {
ret = 0;
} else {
JSString *str;
JSStringCharBuf str_buf;
p = JS_VALUE_TO_PTR(argv[0]);
str = get_string_ptr(ctx, &str_buf, *this_val);
ret = lre_exec(ctx, *capture_buf, p->u.regexp.byte_code,
JS_VALUE_FROM_PTR(str),
js_string_utf16_to_utf8_pos(ctx, *this_val, last_index) / 2);
}
if (ret < 0) {
JS_PopGCRef(ctx, &capture_buf_ref);
string_buffer_pop(ctx, b);
return JS_EXCEPTION;
}
if (ret == 0) {
if (re_flags & (LRE_FLAG_GLOBAL | LRE_FLAG_STICKY)) {
p = JS_VALUE_TO_PTR(argv[0]);
p->u.regexp.last_index = 0;
}
break;
}
capture = (uint32_t *)((JSByteArray *)JS_VALUE_TO_PTR(*capture_buf))->buf;
start = js_string_utf8_to_utf16_pos(ctx, *this_val, capture[0] * 2);
end = js_string_utf8_to_utf16_pos(ctx, *this_val, capture[1] * 2);
string_buffer_concat_utf16(ctx, b, *this_val, endOfLastMatch, start);
js_string_concat_subst(ctx, b, this_val, &argv[1],
start, end, capture_buf, capture_count, NULL);
endOfLastMatch = end;
if (!(re_flags & LRE_FLAG_GLOBAL)) {
if (re_flags & LRE_FLAG_STICKY) {
p = JS_VALUE_TO_PTR(argv[0]);
p->u.regexp.last_index = end;
}
break;
}
if (end == start) {
int c = string_getcp(ctx, *this_val, end, TRUE);
end += 1 + (c >= 0x10000);
}
last_index = end;
}
JS_PopGCRef(ctx, &capture_buf_ref);
} else {
needle_len = js_string_len(ctx, argv[0]);
is_first = TRUE;
for(;;) {
if (unlikely(needle_len == 0)) {
if (is_first)
pos = 0;
else if (endOfLastMatch >= input_len)
pos = -1;
else
pos = endOfLastMatch + 1;
} else {
pos = js_string_indexof(ctx, *this_val, argv[0], endOfLastMatch,
input_len, needle_len);
}
if (pos < 0) {
if (is_first) {
string_buffer_pop(ctx, b);
return *this_val;
} else {
break;
}
}
string_buffer_concat_utf16(ctx, b, *this_val, endOfLastMatch, pos);
js_string_concat_subst(ctx, b, this_val, &argv[1],
pos, pos + needle_len, NULL, 1, &argv[0]);
endOfLastMatch = pos + needle_len;
is_first = FALSE;
if (!is_replaceAll)
break;
}
}
string_buffer_concat_utf16(ctx, b, *this_val, endOfLastMatch, input_len);
return string_buffer_pop(ctx, b);
}
JSValue js_string_split(JSContext *ctx, JSValue *this_val,
int argc, JSValue *argv)
{
JSValue *A, T, ret, *z;
uint32_t lim, lengthA;
int p, q, s, e;
BOOL undef_sep;
JSGCRef A_ref, z_ref;
BOOL is_regexp;
*this_val = JS_ToString(ctx, *this_val);
if (JS_IsException(*this_val))
return JS_EXCEPTION;
if (JS_IsUndefined(argv[1])) {
lim = 0xffffffff;
} else {
if (JS_ToUint32(ctx, &lim, argv[1]) < 0)
return JS_EXCEPTION;
}
is_regexp = (JS_GetClassID(ctx, argv[0]) == JS_CLASS_REGEXP);
if (!is_regexp) {
undef_sep = JS_IsUndefined(argv[0]);
argv[0] = JS_ToString(ctx, argv[0]);
if (JS_IsException(argv[0]))
return JS_EXCEPTION;
} else {
undef_sep = FALSE;
}
A = JS_PushGCRef(ctx, &A_ref);
z = JS_PushGCRef(ctx, &z_ref);
*A = JS_NewArray(ctx, 0);
if (JS_IsException(*A))
goto exception;
lengthA = 0;
s = js_string_len(ctx, *this_val);
p = 0;
if (lim == 0)
goto done;
if (undef_sep)
goto add_tail;
if (is_regexp) {
int numberOfCaptures, i, re_flags;
JSObject *p1;
JSValueArray *arr;
JSByteArray *bc_arr;
p1 = JS_VALUE_TO_PTR(argv[0]);
bc_arr = JS_VALUE_TO_PTR(p1->u.regexp.byte_code);
re_flags = lre_get_flags(bc_arr->buf);
if (s == 0) {
p1 = JS_VALUE_TO_PTR(argv[0]);
p1->u.regexp.last_index = 0;
*z = js_regexp_exec(ctx, &argv[0], 1, this_val, MAGIC_REGEXP_FORCE_GLOBAL);
if (JS_IsException(*z))
goto exception;
if (JS_IsNull(*z))
goto add_tail;
goto done;
}
q = 0;
while (q < s) {
p1 = JS_VALUE_TO_PTR(argv[0]);
p1->u.regexp.last_index = q;
*z = js_regexp_exec(ctx, &argv[0], 1, this_val, MAGIC_REGEXP_FORCE_GLOBAL);
if (JS_IsException(*z))
goto exception;
if (JS_IsNull(*z)) {
if (!(re_flags & LRE_FLAG_STICKY)) {
break;
} else {
int c = string_getcp(ctx, *this_val, q, TRUE);
q += 1 + (c >= 0x10000);
}
} else {
if (!(re_flags & LRE_FLAG_STICKY)) {
JSValue res;
res = JS_GetProperty(ctx, *z, js_get_atom(ctx, JS_ATOM_index));
if (JS_IsException(res))
goto exception;
q = JS_VALUE_GET_INT(res);
}
p1 = JS_VALUE_TO_PTR(argv[0]);
e = p1->u.regexp.last_index;
if (e > s)
e = s;
if (e == p) {
int c = string_getcp(ctx, *this_val, q, TRUE);
q += 1 + (c >= 0x10000);
} else {
T = js_sub_string(ctx, *this_val, p, q);
if (JS_IsException(T))
goto exception;
ret = JS_SetPropertyUint32(ctx, *A, lengthA++, T);
if (JS_IsException(ret))
goto exception;
if (lengthA == lim)
goto done;
p1 = JS_VALUE_TO_PTR(*z);
numberOfCaptures = p1->u.array.len;
for(i = 1; i < numberOfCaptures; i++) {
p1 = JS_VALUE_TO_PTR(*z);
arr = JS_VALUE_TO_PTR(p1->u.array.tab);
T = arr->arr[i];
ret = JS_SetPropertyUint32(ctx, *A, lengthA++, T);
if (JS_IsException(ret))
goto exception;
}
q = p = e;
}
}
}
} else {
int r = js_string_len(ctx, argv[0]);
if (s == 0) {
if (r != 0)
goto add_tail;
goto done;
}
for (q = 0; (q += !r) <= s - r - !r; q = p = e + r) {
e = js_string_indexof(ctx, *this_val, argv[0], q, s, r);
if (e < 0)
break;
T = js_sub_string(ctx, *this_val, p, e);
if (JS_IsException(T))
goto exception;
ret = JS_SetPropertyUint32(ctx, *A, lengthA++, T);
if (JS_IsException(ret))
goto exception;
if (lengthA == lim)
goto done;
}
}
add_tail:
T = js_sub_string(ctx, *this_val, p, s);
if (JS_IsException(T))
goto exception;
ret = JS_SetPropertyUint32(ctx, *A, lengthA++, T);
if (JS_IsException(ret))
goto exception;
done:
JS_PopGCRef(ctx, &z_ref);
return JS_PopGCRef(ctx, &A_ref);
exception:
JS_PopGCRef(ctx, &z_ref);
JS_PopGCRef(ctx, &A_ref);
return JS_EXCEPTION;
}
JSValue js_string_match(JSContext *ctx, JSValue *this_val,
int argc, JSValue *argv)
{
JSRegExp *re;
int global, n;
JSValue *A, *result, ret;
JSObject *p;
JSValueArray *arr;
JSByteArray *barr;
JSGCRef A_ref, result_ref;
re = js_get_regexp(ctx, argv[0]);
if (!re)
return JS_EXCEPTION;
barr = JS_VALUE_TO_PTR(re->byte_code);
global = lre_get_flags(barr->buf) & LRE_FLAG_GLOBAL;
if (!global)
return js_regexp_exec(ctx, &argv[0], 1, this_val, 0);
p = JS_VALUE_TO_PTR(argv[0]);
re = &p->u.regexp;
re->last_index = 0;
A = JS_PushGCRef(ctx, &A_ref);
result = JS_PushGCRef(ctx, &result_ref);
*A = JS_NULL;
n = 0;
for(;;) {
*result = js_regexp_exec(ctx, &argv[0], 1, this_val, 0);
if (JS_IsException(*result))
goto fail;
if (*result == JS_NULL)
break;
if (*A == JS_NULL) {
*A = JS_NewArray(ctx, 1);
if (JS_IsException(*A))
goto fail;
}
p = JS_VALUE_TO_PTR(*result);
arr = JS_VALUE_TO_PTR(p->u.array.tab);
ret = JS_SetPropertyUint32(ctx, *A, n++, arr->arr[0]);
if (JS_IsException(ret)) {
fail:
*A = JS_EXCEPTION;
break;
}
}
JS_PopGCRef(ctx, &result_ref);
return JS_PopGCRef(ctx, &A_ref);
}
JSValue js_string_search(JSContext *ctx, JSValue *this_val,
int argc, JSValue *argv)
{
return js_regexp_exec(ctx, &argv[0], 1, this_val, MAGIC_REGEXP_SEARCH);
}