Xteink-X4-crosspoint-reader/lib/mquickjs/mquickjs.c
Xuan Son Nguyen 49d2c5eba8 init version
2026-01-31 22:01:10 +01:00

18325 lines
558 KiB
C

/*
* Micro QuickJS Javascript Engine
*
* Copyright (c) 2017-2025 Fabrice Bellard
* Copyright (c) 2017-2025 Charlie Gordon
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#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"
/*
TODO:
- regexp: better error position info
- use a specific MTAG for short functions instead of an immediate value
- use hash table for atoms
- set the length accessors as non configurable so that the
'get_length' instruction optimizations are always safe.
- memory:
- fix stack_bottom logic
- launch gc at regular intervals
- only launch compaction when needed (handle free blocks in malloc())
- avoid pass to rehash the properties
- ensure no undefined bytes (e.g. at end of JSString) in
saved bytecode ?
- reduced memory usage:
- reduce JSFunctionBytecode size (remove source_pos)
- do not explicitly store function names for get/set/bound
- use JSSTDLibraryDef fields instead of copying them to JSContext ?
*/
#define __exception __attribute__((warn_unused_result))
#define JS_STACK_SLACK 16 /* additional free space on the stack */
/* min free size in bytes between heap_free and the bottom of the stack */
#define JS_MIN_FREE_SIZE 512
/* minimum free size in bytes to create the out of memory object */
#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
/* maximum number of recursing JS_Call() */
#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",
};
/* function call flags (max 31 bits) */
#define FRAME_CF_ARGC_MASK 0xffff
/* FRAME_CF_CTOR */
#define FRAME_CF_POP_RET (1 << 17) /* pop the return value */
#define FRAME_CF_PC_ADD1 (1 << 18) /* increment the PC by 1 instead of 3 */
#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;
/* in JSWords excluding the header. Free blocks of JSW bytes
are only generated by js_shrink() and may not be always
compacted */
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;
/* true if the string content represents a number, only meaningful
is is_unique = true */
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)]; /* for JSString */
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; /* is_detached = true */
struct {
JSValue next; /* is_detached = false: JS_NULL or JSVarRef,
must be at the same address as 'value' */
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
/* unaligned 64 bit access in 32-bit mode */
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; /* -1 if defining a normal object */
JSValue proto_props;
JSValue parent_class; /* JSROMClass or JS_NULL */
} JSROMClass;
#define N_ROM_ATOM_TABLES_MAX 2
/* must be large enough to have a negligible runtime cost and small
enough to call the interrupt callback often. */
#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; /* JS_NULL or weak reference to a JSString. It
contains at least JS_STRING_POS_CACHE_MIN_LEN
bytes and is a non ascii string */
uint32_t str_pos[2]; /* 0 = UTF-8 pos (in bytes), 1 = UTF-16 pos */
} JSStringPosCacheEntry;
struct JSContext {
/* memory map:
Stack
Free area
Heap
JSContext
*/
uint8_t *heap_base;
uint8_t *heap_free; /* first free area */
uint8_t *stack_top;
JSValue *stack_bottom; /* sp must always be higher than stack_bottom */
JSValue *sp; /* current stack pointer */
JSValue *fp; /* current frame pointer, stack_top if none */
uint32_t min_free_size; /* min free size between heap_free and the
bottom of the stack */
BOOL in_out_of_memory : 8; /* != 0 if generating the out of memory object */
uint8_t n_rom_atom_tables;
uint8_t string_pos_cache_counter; /* used for string_pos_cache[] update */
uint16_t class_count; /* number of classes including user classes */
int16_t interrupt_counter;
BOOL current_exception_is_uncatchable : 8;
struct JSParseState *parse_state; /* != NULL during JS_Eval() */
int unique_strings_len;
int js_call_rec_count; /* number of recursing JS_Call() */
JSGCRef *top_gc_ref; /* used to reference temporary GC roots (stack top) */
JSGCRef *last_gc_ref; /* used to reference temporary GC roots (list) */
const JSWord *atom_table; /* constant atom table */
/* 'n_rom_atom_tables' atom tables from code loaded from rom */
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; /* for the various dump functions */
void *opaque;
JSValue *class_obj; /* same as class_proto + class_count */
JSStringPosCacheEntry string_pos_cache[JS_STRING_POS_CACHE_SIZE];
/* must only contain JSValue from this point (see JS_GC()) */
JSValue unique_strings; /* JSValueArray of sorted strings or JS_NULL */
JSValue current_exception; /* currently pending exception, must
come after unique_strings */
#ifdef DEBUG_GC
JSValue dummy_block; /* dummy memory block near the start of the memory */
#endif
JSValue empty_props; /* empty prop list, for objects with no properties */
JSValue global_obj;
JSValue minus_zero; /* minus zero float64 value */
JSValue class_proto[]; /* prototype for each class (class_count
element, then class_count elements for
class_obj */
};
typedef enum {
JS_VARREF_KIND_ARG, /* var_idx is an argument of the parent function */
JS_VARREF_KIND_VAR, /* var_idx is a local variable of the parent function */
JS_VARREF_KIND_VAR_REF, /* var_idx is a var ref of the parent function */
JS_VARREF_KIND_GLOBAL, /* to debug */
} JSVarRefKindEnum;
typedef struct JSObject JSObject;
typedef struct {
/* string, short integer or JS_UNINITIALIZED if no property. If
the last property is uninitialized, hash_next = 2 *
first_free. */
JSValue key;
/* JS_PROP_GETSET: JSValueArray of two elements
JS_PROP_VARREF: JSVarRef */
JSValue value;
/* XXX: when JSW = 8, could use 32 bits for hash_next (faster) */
uint32_t hash_next : 30; /* low bit at zero */
uint32_t prop_type : 2;
} JSProperty;
typedef struct {
JSValue func_bytecode; /* JSFunctionBytecode */
JSValue var_refs[]; /* JSValueArray */
} JSClosureData;
typedef struct {
uint32_t idx;
JSValue params; /* optional associated parameters */
} JSCFunctionData;
typedef struct {
JSValue tab; /* JS_NULL or JSValueArray */
uint32_t len; /* maximum value: 2^30-1 */
} JSArrayData;
typedef struct {
JSValue message; /* string or JS_NULL */
JSValue stack; /* string or JS_NULL */
} JSErrorData;
typedef struct {
JSValue byte_buffer; /* JSByteBuffer */
} JSArrayBuffer;
typedef struct {
JSValue buffer; /* corresponding array buffer */
uint32_t len; /* in elements */
uint32_t offset; /* in elements */
} 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); /* object additional size, in JSValue */
JSValue proto; /* JSObject or JS_NULL */
/* JSValueArray. structure:
prop_count (number of properties excluding deleted ones)
hash_mask (= hash_size - 1)
hash_table[hash_size] (0 = end of list or offset in array)
JSProperty props[]
*/
JSValue props;
/* number of additional fields depends on the object */
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; /* only used during parsing */
JSWord has_local_func_name : 1; /* only used during parsing */
JSWord has_column : 1; /* column debug info is present */
/* during parse: variable index + 1 of hoisted function, 0 otherwise */
JSWord arg_count : 16;
JSWord dummy: JS_MB_PAD(JS_MTAG_BITS + 3 + 16);
JSValue func_name; /* JS_NULL if anonymous function */
JSValue byte_code; /* JS_NULL if the function is not parsed yet */
JSValue cpool; /* constant pool */
JSValue vars; /* only for debug */
JSValue ext_vars; /* records of (var_name, var_kind (2 bits) var_idx (16 bits)) */
uint16_t stack_size; /* maximum stack size */
uint16_t ext_vars_len; /* XXX: only used during parsing */
JSValue filename; /* filename in which the function is defined */
JSValue pc2line; /* JSByteArray or JS_NULL if not initialized */
uint32_t source_pos; /* only used during parsing (XXX: shrink) */
} 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; /* in bytes */
/* the opcodes remove n_pop items from the top of the stack, then
pushes n_pusch items */
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);
/* don't start the GC before dummy_block is allocated */
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;
}
/* check that 'len' values can be pushed on the stack. Return 0 if OK,
-1 if not enough space. May trigger a GC(). */
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;
}
/* currently only free the last element */
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;
}
/* 'size' is in bytes and must be multiple of JSW and > 0 */
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);
}
/* 'ptr' must be != NULL. new_size must be less or equal to the
current block size. */
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);
/* add a new free block after 'ptr' */
return ptr;
}
JSValue JS_Throw(JSContext *ctx, JSValue obj)
{
ctx->current_exception = obj;
ctx->current_exception_is_uncatchable = FALSE;
return JS_EXCEPTION;
}
/* return the byte length. 'buf' must contain UTF8_CHAR_LEN_MAX + 1 bytes */
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;
}
/* printf utility */
#define PF_ZERO_PAD (1 << 0) /* 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) /* l/ll */
static BOOL is_digit(int c)
{
return (c >= '0' && c <= '9');
}
/* pad with chars 'c' */
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;
}
}
/* The 'o' format can be used to print a JSValue. Only short int,
bool, null, undefined and string types are supported. */
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++;
/* get the flags */
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++;
}
}
}
/* modifiers */
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++;
/* XXX: not complete, just enough for our needs */
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)) {
/* XXX: print it */
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;
}
}
/* remove the trailing '\n' if any (used in error output) */
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) {
/* XXX: incorrect with prefix */
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;
}
/* used for the debug output */
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;
/* in case of syntax error, the backtrace is added later */
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)
/* 1 <= n <= 63 */
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 /* JS_USE_SHORT_FLOAT */
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);
}
/* create a new float64 value which is known not to be a short integer */
static JSValue __JS_NewFloat64(JSContext *ctx, double d)
{
if (float64_as_uint64(d) == 0x8000000000000000) {
/* minus zero often happens, so it is worth having a constant
value */
return ctx->minus_zero;
} else
#ifdef JS_USE_SHORT_FLOAT
/* Note: this test is false for NaN */
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);
/* test if exact integer */
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;
/* -0 cannot be represented as integer, so we compare the bit
representation */
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);
}
}
/* Note: short functions are not considered as objects by this function */
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);
}
}
/* return -1 if not an 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;
}
/* 0 <= c <= 0x10ffff */
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); /* right surrogate */
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); /* left surrogate */
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);
}
/* Warning: the string must be a valid WTF-8 string (= UTF-8 +
unpaired surrogates). */
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);
}
/* Warning: the string must be a valid UTF-8 string. */
JSValue JS_NewString(JSContext *ctx, const char *buf)
{
return JS_NewStringLen(ctx, buf, strlen(buf));
}
/* the byte array must be zero terminated. */
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;
}
}
/* in bytes */
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;
}
}
/* assuming that utf8_next() returns 4, validate the corresponding UTF-8 sequence */
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]);
}
}
}
/* an UTF-8 position is the byte position multiplied by 2. One is
added when the corresponding UTF-16 character represents the right
surrogate if the code is >= 0x10000.
*/
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) {
/* "random" replacement */
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);
}
/* Testing the third byte is not needed as the UTF-8 encoding must be
correct */
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; /* string, JSByteBuffer or JS_EXCEPTION */
int len; /* current string length (in bytes) */
BOOL is_ascii;
} StringBuffer;
/* return 0 if OK, -1 in case of exception (exception possible if len > 0) */
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;
}
/* val2 must be a string. Return 0 if OK, -1 in case of exception */
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) {
/* empty string in buffer: just keep 'val2' */
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;
/* contract the two surrogates to 4 bytes */
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;
}
/* 'str' must be a string */
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;
/* XXX: avoid explicitly constructing the substring */
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);
}
/* XXX: could optimize */
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;
/* XXX: avoid this allocation */
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) {
/* add the trailing '\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;
}
/* val1 and val2 must be strings or exception */
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); /* no memory allocation */
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);
}
/* Return the unicode character containing the byte at position
'i'. Return -1 in case of error. */
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;
/* if valid UTF-8, the strings cannot be equal at this point */
/* Note: UTF-16 does not preserve unicode order like UTF-8 */
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) {
/* p1 < p2 if same first UTF-16 char */
c2 = 0xd800 + ((c2 - 0x10000) >> 10);
if (c1 <= c2)
res = -1;
else
res = 1;
} else {
/* p1 > p2 if same first UTF-16 char */
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;
}
/* return the string length in UTF16 characters. 'val' must be a
string char or a string */
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);
}
}
/* return the UTF-16 code or the unicode character at a given UTF-8
position or -1 if outside the string */
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); /* left surrogate */
else
return 0xdc00 + (c & 0x3ff); /* right surrogate */
}
}
static int string_getc(JSContext *ctx, JSValue str, uint32_t utf16_pos)
{
return string_getcp(ctx, str, utf16_pos, FALSE);
}
/* precondition: 0 <= start <= end <= string length */
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';
}
/* return TRUE if the property 'val' represents a numeric property. -1
is returned in case of exception. 'val' must be a string. It is
assumed that NaN and infinities have already been handled. */
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]; /* enough for js_dtoa() */
p = JS_VALUE_TO_PTR(val);
/* the fast case is when the string is not a number */
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));
}
/* return JS_NULL if not found */
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) {
/* found */
*pidx = m;
return val1;
} else if (r < 0) {
b = m - 1;
} else {
a = m + 1;
}
}
*pidx = a;
return JS_NULL;
}
/* if 'val' is not a string, it is returned */
/* XXX: use hash table */
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;
/* not unique: find it in the ROM or RAM sorted unique string table */
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;
/* not found: add it in the table */
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;
}
}
}
/* plen can be NULL. No memory allocation is done if 'val' already is
a string. */
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");
}
/* 'val' must be a string. return TRUE if the string represents a
short integer */
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;
/* XXX: simplify ? */
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;
}
/* return TRUE if the property 'val' represent a numeric property. It
is assumed that the shortint case has been tested before */
static BOOL JS_IsNumericProperty(JSContext *ctx, JSValue val)
{
JSString *p;
if (!JS_IsPtr(val))
return FALSE; /* JS_TAG_STRING_CHAR */
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;
}
/* val can be JS_NULL (zero size). 'prop_base' is non zero only when
* resizing the property arrays so that the property array has a size
* which is a multiple of 3 */
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;
/* ensure that the property array has a size which is a
* multiple of 3 */
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);
}
/* no allocation is done */
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;
}
}
/* extra_size is in bytes */
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);
}
/* same as JS_NewObject() but preallocate for 'n' properties */
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); /* XXX: improve */
}
/* return NULL if not found */
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]; /* JSValue, hence idx * 2 */
while (idx != 0) {
pr = (JSProperty *)((uint8_t *)arr->arr + idx * (sizeof(JSValue) / 2));
if (pr->key == prop)
return pr;
idx = pr->hash_next; /* JSValue, hence idx * 2 */
}
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;
/* 'prototype' or 'constructor' property in ROM */
idx = JS_VALUE_GET_INT(val);
if (idx >= 0)
return ctx->class_proto[idx];
else
return ctx->class_obj[-idx - 1];
}
/* return the value or:
- exception
- tail call : returned in case of getter and handle_getset =
true. The function is put on the stack
*/
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);
/* always detached */
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) {
/* It is assumed 'this_obj' is on the stack and
that the stack has some slack to add one element. */
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);
}
}
}
/* look in the prototype */
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)
{
/* XXX: adjust ? */
if (prop_count <= 1)
return 0;
else
return (32 - clz32(prop_count - 1)) - 1;
}
/* allocate 'n' properties, assuming n >= 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); /* no property is allocated yet */
arr->arr[1] = JS_NewShortInt(hash_mask);
for(i = 0; i <= hash_mask; i++)
arr->arr[2 + i] = 0;
pr = NULL; /* avoid warning */
for(i = 0; i < n; i++) {
pr = (JSProperty *)&arr->arr[2 + hash_mask + 1 + 3 * i];
pr->key = JS_UNINITIALIZED;
}
/* last property */
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; /* no need to rehash if single hash entry */
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++;
}
}
}
/* Compact the properties. No memory allocation is done */
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]);
/* no property */
if (prop_count == 0) {
if (p->props != ctx->empty_props) {
//js_free(ctx, p->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; /* nothing to do */
// printf("compact_props: new_size=%d size=%d hash=%d\n", new_size, arr->size, new_hash_mask);
arr->arr[1] = JS_NewShortInt(new_hash_mask);
/* move the properties, skipping the deleted ones */
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);
}
/* if the existing properties are in ROM, copy them to RAM. Return non zero if error */
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;
/* no rehashing is needed because all the atoms are in ROM */
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]);
/* no deleted properties in ROM */
assert(arr1->size == 2 + (hash_mask + 1) + 3 * prop_count);
/* convert JS_PROP_SPECIAL properties ("prototype" and "constructor") */
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;
}
/* compute 'first_free' in a property list */
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;
}
/* It is assumed that the property does not already exists. */
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);
// JS_DumpValue(ctx, "create", prop);
prop_count = JS_VALUE_GET_INT(arr->arr[0]);
hash_mask = JS_VALUE_GET_INT(arr->arr[1]);
/* extend the array if no space left (this single test is valid
even if the property list is empty) */
pr1 = (JSProperty *)&arr->arr[arr->size - 3];
if (pr1->key != JS_UNINITIALIZED) {
if (p->props == ctx->empty_props) {
/* XXX: remove and move empty_props to ROM */
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)) {
/* resize the hash table if too many properties */
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);
// printf("resize_props: new_size=%d hash=%d %d\n", new_size, new_hash_mask, hash_mask);
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) {
/* rebuild the hash table */
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);
}
}
/* ensure the last element is marked as uninitialized to store 'first_free' */
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);
/* update first_free */
first_free += 3;
if (first_free < arr->size) {
pr1 = (JSProperty *)&arr->arr[arr->size - 3];
pr1->hash_next = first_free << 1;
}
return pr;
}
/* don't do property lookup if not present */
#define JS_DEF_PROP_LOOKUP (1 << 0)
/* return the raw property value */
#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)
/* XXX: handle arrays and typed arrays */
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;
/* move to RAM if needed */
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))) {
/* move to RAM */
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);
}
/* return a JSVarRef or an exception. */
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);
/* define the variable if needed */
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);
}
/* return JS_UNDEFINED in the normal case. Otherwise:
- exception
- tail call : returned in case of getter and handle_getset =
true. The function is put on the stack
*/
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);
}
}
/* search if the property is already present */
if (p->class_id == JS_CLASS_ARRAY) {
if (JS_IsInt(prop)) {
JSValueArray *arr;
uint32_t idx = JS_VALUE_GET_INT(prop);
/* not standard: we refuse to add properties to object
except at the last position */
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);
/* always detached */
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;
}
}
/* search in the prototype chain (getter/setters) */
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) {
/* It is assumed "this_obj" already is on the stack
and that the stack has some slack to add one
element. */
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 {
/* stop prototype chain lookup */
break;
}
}
}
/* add the property in the object */
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);
}
/* return JS_FALSE, JS_TRUE or JS_EXCEPTION. Return false only if the
property is not configurable which is never the case here. */
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;
/* XXX: check return value */
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);
}
/* found: remove it */
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;
/* update first_free if needed */
while (first_free > 2 + hash_mask + 1) {
pr1 = (JSProperty *)&arr->arr[first_free - 3];
if (pr1->key != JS_UNINITIALIZED)
break;
first_free -= 3;
}
/* update first_free */
if (first_free < arr->size) {
pr1 = (JSProperty *)&arr->arr[arr->size - 3];
pr1->hash_next = first_free << 1;
}
/* compact the properties if needed */
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;
}
/* not found */
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; /* already defined */
/* initialize the parent class if necessary */
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];
}
/* initialize the prototype before. It is already defined only
for Object and Function */
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 {
/* normal object */
obj = JS_NewObject(ctx);
}
p = JS_VALUE_TO_PTR(obj);
if (!JS_IsNull(class_def->props)) {
/* set the properties from the ROM. They are copied to RAM
when modified */
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)
{
// fwrite(buf, 1, buf_len, stdout);
}
/* if prepare_compilation is true, the context will be used to compile
to a binary file. It is not expected to be used in the embedded
version */
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;
/* for compilation, no stdlib is needed. Only the atoms
corresponding to JS_ATOM_x are needed and they are stored
in RAM. */
/* copy the atoms to a fixed location at the beginning of the
heap */
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);
/* allocate the sorted atom table and populate it */
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
/* set the dummy block at the start of the memory */
{
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); /* prop_count */
arr->arr[1] = JS_NewShortInt(0); /* hash_mark */
arr->arr[2] = JS_NewShortInt(0); /* hash_table[1] */
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;
/* must be done first so that the prototype of Object.prototype is
JS_NULL */
ctx->class_proto[JS_CLASS_OBJECT] = JS_NewObject(ctx);
/* must be done for proper function init */
ctx->class_proto[JS_CLASS_CLOSURE] = JS_NewObject(ctx);
ctx->global_obj = JS_NewObject(ctx);
ctx->minus_zero = js_alloc_float64(ctx, -0.0); /* XXX: use a ROM value instead */
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;
/* call the user C finalizers */
/* XXX: could disable it when prepare_compilation = true */
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) /* current pc_offset */
#define FRAME_OFFSET_FIRST_VARREF (-2)
#define FRAME_OFFSET_VAR0 (-3)
/* stack layout:
padded_args (padded_argc - argc)
args (argc)
func_obj fp[3]
this_obj fp[2]
call_flags (int) fp[1]
saved_fp (int) fp[0]
cur_pc (int) fp[-1]
first_var_ref (val) fp[-2]
vars (var_count)
temp stack (pointed by sp)
*/
#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))
/* buf_end points to the end of the buffer (after the final '\0') */
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;
}
/* no memory allocation is done */
/* XXX: handle bound functions */
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) {
/* XXX: directly set func_name to the empty string ? */
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)) {
/* slow case */
return get_bits_slow(buf, index, n);
} else {
/* fast case */
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) {
/* error */
*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;
// printf("get_ugolomb: v=%d\n", v);
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;
}
/* line_num, col_num and index are updated */
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;
}
/* return 0 if line/col number info */
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);
/* skip the hoisted code */
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 truncated line, remove it and stop */
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");
}
/* return a string or an exception */
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;
/* allocate the temporary buffer */
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);
/* Note: tmp_arr->buf is always 32 bit aligned */
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);
}
}
}
/* return either a unique string or an integer. Strings representing
a short integer are converted to short integer */
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;
}
/* JS_ToString() specific behaviors */
#define JS_ATOD_TOSTRING (1 << 8)
/* 'val' must be a string */
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;
}
/* Note: can fail due to memory allocation even if primitive type */
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))) {
/* fast case */
ret = (int32_t)d;
} else if (!sat_flag) {
if (e <= (1023 + 30 + 53)) {
uint64_t v;
/* remainder modulo 2^32 */
v = (u & (((uint64_t)1 << 52) - 1)) | ((uint64_t)1 << 52);
v = v << ((e - 1023) - 52 + 32);
ret = v >> 32;
/* take the sign into account */
if (u >> 63)
ret = -ret;
} else {
ret = 0; /* also handles NaN and +inf */
}
} else {
if (e == 2047 && (u & (((uint64_t)1 << 52) - 1)) != 0) {
/* nan */
ret = 0;
} else {
/* take the sign into account */
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;
/* cannot fail */
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);
}
/* specific case necessary for correct return value semantics */
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); /* if NaN return false */
break;
case OP_lte:
res = (d1 <= d2); /* if NaN return false */
break;
case OP_gt:
res = (d1 > d2); /* if NaN return false */
break;
default:
case OP_gte:
res = (d1 >= d2); /* if NaN return false */
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;
/* cannot fail */
JS_ToNumber(ctx, &d1, op1);
JS_ToNumber(ctx, &d2, op2);
res = (d1 == d2); /* if NaN return false */
}
} else if (JS_IsString(ctx, op1)) {
if (!JS_IsString(ctx, op2)) {
res = FALSE;
} else {
res = js_string_eq(ctx, op1, op2);
}
} else {
/* special value or object */
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 {
/* special tags to simplify the comparison */
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;
/* initialize the var_refs in case of exception */
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; /* not used */
}
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:
/* only for eval code */
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) {
/* XXX: not spec compliant and slow. We return only the own
object keys. */
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)
/* only need to restore PC */
#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;
}
/* handle user interruption */
#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)
/* must use JS_StackCheck() before using it */
void JS_PushArg(JSContext *ctx, JSValue val)
{
#ifdef DEBUG_GC
assert((ctx->sp - 1) >= ctx->stack_bottom);
#endif
*--ctx->sp = val;
}
/* Usage:
if (JS_StackCheck(ctx, n + 2)) ...
JS_PushArg(ctx, arg[n - 1]);
...
JS_PushArg(ctx, arg[0]);
JS_PushArg(ctx, func);
JS_PushArg(ctx, this_obj);
res = JS_Call(ctx, n);
*/
JSValue JS_Call(JSContext *ctx, int call_flags)
{
JSValue *fp, *sp, val = JS_UNDEFINED, *initial_fp;
uint8_t *pc;
/* temporary variables */
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): /* obj a b -> a obj b (213) */
{
JSValue tmp;
tmp = sp[1];
sp[1] = sp[2];
sp[2] = tmp;
}
BREAK;
CASE(OP_rot3l): /* x a b -> a b x (231) */
{
JSValue tmp;
tmp = sp[2];
sp[2] = sp[1];
sp[1] = sp[0];
sp[0] = tmp;
}
BREAK;
CASE(OP_perm4): /* obj prop a b -> a obj prop b */
{
JSValue tmp;
tmp = sp[1];
sp[1] = sp[2];
sp[2] = sp[3];
sp[3] = tmp;
}
BREAK;
CASE(OP_swap): /* a b -> b a */
{
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);
/* save pc + 1 of the current call */
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];
/* add undefined arguments if the caller did not
provide enough arguments */
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; /* go back to the caller frame */
ctx->sp = sp;
ctx->fp = fp;
val = JS_ThrowTypeError(ctx, "not a constructor");
goto call_exception;
}
argc = call_flags & FRAME_CF_ARGC_MASK;
/* JS_StackCheck may trigger a gc */
ctx->sp = sp;
ctx->fp = fp;
n = JS_StackCheck(ctx, max_int(fd->arg_count - argc, 0));
if (n) {
sp += 2; /* go back to the caller frame */
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;
/* tail call: equivalent to calling the
function after the C function */
/* XXX: handle the call flags of the caller ? */
call_flags = JS_VALUE_GET_SPECIAL_VALUE(val) - JS_EX_CALL;
sp = ctx->sp;
/* pop the frame */
fp1 = VALUE_TO_SP(ctx, fp[FRAME_OFFSET_SAVED_FP]);
/* move the new arguments at the correct stack position */
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;
/* Note: can recurse at this point */
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;
/* JS_StackCheck may trigger a gc */
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);
/* add undefined arguments if the caller did not
provide enough arguments */
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); /* FRAME_OFFSET_CUR_PC */
*--sp = JS_NULL; /* FRAME_OFFSET_FIRST_VARREF */
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; /* go back to the caller frame */
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:
/* 'val' must contain the exception */
{
JSValue *stack_top, val2;
JSValueArray *vars;
int v;
/* exception before entering in the first function ?
(XXX: remove this test) */
if (!pc)
goto done;
v = JS_VALUE_GET_SPECIAL_VALUE(val);
if (v >= JS_EX_CALL) {
/* tail call */
call_flags = JS_VALUE_GET_SPECIAL_VALUE(val) - JS_EX_CALL;
/* the opcode has only one byte, hence the PC must
be updated accordingly after the function
returns */
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;
}
// js_printf(ctx, "tail call: 0x%x\n", call_flags);
goto generic_function_call;
}
/* XXX: start gc in case of JS_EXCEPTION_MEM */
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;
/* exception caught by a 'catch' in the
current function */
*--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;
/* detach the variable references */
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;
/* shrink 'pv' */
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]);
/* XXX: restore stack_bottom to reduce memory usage */
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;
/* now we are in the calling function */
if (JS_IsException(val))
goto exception;
if (!(call_flags & FRAME_CF_POP_RET))
*--sp = val;
/* Note: if variable size call, can add a flag in call_flags */
if (!(call_flags & FRAME_CF_PC_ADD1))
pc += 2; /* skip the call arg or get_field/put_field arg */
}
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))) {
/* fast case */
JSObject *p = JS_VALUE_TO_PTR(obj);
JSProperty *pr;
if (unlikely(p->mtag != JS_MTAG_OBJECT))
goto get_field_slow;
for(;;) {
/* no array check is necessary because 'prop' is
guaranteed not to be a numeric property */
/* XXX: slow due to short ints */
pr = find_own_property_inlined(ctx, p, prop);
if (pr) {
if (unlikely(pr->prop_type != JS_PROP_NORMAL)) {
/* sp[0] is this_obj, obj is the current
object */
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))) {
/* fast case */
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))) {
/* fast case */
JSObject *p = JS_VALUE_TO_PTR(obj);
JSProperty *pr;
if (unlikely(p->mtag != JS_MTAG_OBJECT))
goto put_field_slow;
/* no array check is necessary because 'prop' is
guaranteed not to be a numeric property */
/* XXX: slow due to short ints */
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;
/* XXX: 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)) {
/* fast case with array */
/* XXX: optimize typed arrays too ? */
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)) {
/* fast case with array */
/* XXX: optimize typed arrays too ? */
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
}
/* -0 case */
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:
/* for efficiency, we don't try to store it as a short integer */
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) {
/* minus zero often happens, so it is worth having a constant
value */
val = ctx->minus_zero;
} else {
/* XXX: could have a short float
representation for zero and minus zero
so that the float fast case is still
used when they happen */
val = JS_NewShortInt(0);
}
} else {
/* slow case: need to allocate it */
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: ;
} /* switch */
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);
}
/**********************************************************************/
/* dump utilities */
#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, " ]");
}
/* put constructors into a separate table */
/* XXX: improve by using a table */
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; /* XXX: potentially large stack size */
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;
/* find the error name without side effect */
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) {
/* remove the trailing '\n' if any */
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);
}
/**************************************************/
/* JS parser */
enum {
TOK_NUMBER = 128,
TOK_STRING,
TOK_IDENT,
TOK_REGEXP,
/* warning: order matters (see js_parse_assign_expr) */
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,
/* keywords */
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,
};
/* this structure is pushed on the JS stack, so all members must be JSValue */
typedef struct BlockEnv {
JSValue prev; /* JS_NULL or stack index */
JSValue label_name; /* JS_NULL if none */
JSValue label_break;
JSValue label_cont;
JSValue label_finally;
JSValue drop_count; /* (int) number of stack elements to drop */
} BlockEnv;
typedef uint32_t JSSourcePos;
typedef struct JSToken {
int val;
JSSourcePos source_pos; /* position in source */
union {
double d; /* TOK_NUMBER */
struct {
uint32_t re_flags; /* regular expression flags */
uint32_t re_end_pos; /* at the final '/' */
} regexp;
} u;
JSValue value; /* associated value: string for TOK_STRING, TOK_REGEXP;
identifier for TOK_IDENT or keyword */
} JSToken;
typedef struct JSParseState {
JSContext *ctx;
JSToken token;
BOOL got_lf : 8; /* true if got line feed before the current token */
/* global eval: variables are defined as global */
BOOL is_eval : 8;
/* if true, return the last value. */
BOOL has_retval : 8;
/* if true, implicitly define global variables in an
assignment. */
BOOL is_repl : 8;
BOOL has_column : 8; /* column debug info is present */
/* TRUE if the expression result has been dropped (see PF_DROP) */
BOOL dropped_result : 8;
JSValue source_str; /* source string or JS_NULL */
JSValue filename_str; /* 'filename' converted to string */
/* zero terminated source buffer. Automatically updated by the GC
if source_str is a string */
const uint8_t *source_buf;
uint32_t buf_pos;
uint32_t buf_len;
/* current function */
JSValue cur_func;
JSValue byte_code;
uint32_t byte_code_len;
int last_opcode_pos; /* -1 if no last opcode */
int last_pc2line_pos; /* pc2line pos for the last opcode */
JSSourcePos last_pc2line_source_pos;
uint32_t pc2line_bit_len;
JSSourcePos pc2line_source_pos; /* last generated source pos */
uint16_t cpool_len;
/* size of the byte code necessary to define the hoisted functions */
uint32_t hoisted_code_len;
/* argument + defined local variable count */
uint16_t local_vars_len;
int eval_ret_idx; /* variable index for the eval return value, -1
if no return value */
JSValue top_break; /* JS_NULL or SP_TO_VALUE(BlockEnv *) */
/* regexp parsing only */
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;
/* error handling */
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) {
/* extract the debug info */
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
case OP_FMT_npopx:
js_printf(ctx, " %d", op - OP_call0);
break;
#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
case OP_FMT_none_var_ref:
idx = (op - OP_get_var_ref0) % 4;
goto has_var_ref;
#endif
case OP_FMT_var_ref:
idx = get_u16(tab + pos);
// has_var_ref:
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 /* DUMP_BYTECODE */
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:
/* XXX: TODO */
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;
}
}
/* return the zero based line and column number in the source. */
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 != ';') {
/* automatic insertion of ';' */
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) /* semicolon found inside the first level */
/* Skip parenthesis or blocks. The current token should be '(', '[' or
'{'. 'func_name' can be JS_NULL. */
static int js_skip_parens(JSParseState *s, JSValue *pfunc_name)
{
uint8_t state[128];
int level, c, bits = 0;
/* protect from underflow */
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;
}
/* skip an expression until ')' */
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;
/* return TRUE if a regexp literal is allowed after this token */
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;
/* the previous token value is only needed so that
is_regexp_allowed() returns the correct value */
s->token.val = sp->regexp_allowed ? ' ' : ')';
next_token(s);
}
/* same as js_skip_parens but go back to the current token */
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;
}
/* return the escape value or -1 */
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; /* avoid warning */
StringBuffer b_s, *b = &b_s;
if (string_buffer_push(ctx, b, 16))
js_parse_error_mem(s);
buf = s->source_buf;
/* string */
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') {
/* ignore escaped newline sequence */
pos++;
continue;
}
c = js_parse_escape(buf + pos, &escape_len);
if (c == -1) {
js_parse_error(s, "invalid escape sequence");
} else if (c == -2) {
/* ignore invalid escapes */
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; /* may be reallocated */
}
*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); /* no allocation */
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; /* may be reallocated */
}
/* convert to token if necessary */
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");
/* XXX: single char string is not optimized */
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] == '*') {
/* comment */
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] == '/') {
/* line comment */
p += 2;
for(;;) {
if (*p == '\0' || *p == '\n')
break;
p++;
}
goto redo;
} else if (is_regexp_allowed(s->token.val)) {
/* Note: we recognize regexps in the lexer. It does not
handle all the cases e.g. "({x:1} / 2)" or "a.void / 2" but
is consistent when we tokenize the input without
parsing it. */
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':
/* in strict mode, octal literals are not accepted */
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':
/* number */
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
}
/* test if the current token is a label. XXX: we assume there is no
space between the identifier and the ':' to avoid having to push
back a token */
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;
}
/* precondition: 1 <= n <= 25. */
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;
/* resize the array if needed */
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); /* reset the bits */
val |= bits << shift;
put_be32(p, val);
s->pc2line_bit_len = index + n;
}
/* precondition: 1 <= n <= 32 */
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);
}
/* 0 <= v < 2^32-1 */
static void put_ugolomb(JSParseState *s, uint32_t v)
{
int n;
// printf("put_ugolomb: %u\n", v);
v++;
n = 32 - clz32(v);
if (n > 1)
pc2line_put_bits(s, n - 1, 0);
pc2line_put_bits(s, n, v);
}
/* v != -2^31 */
static void put_sgolomb(JSParseState *s, int32_t v1)
{
uint32_t v = v1;
put_ugolomb(s, (2 * v) ^ -(v >> 31));
}
//#define DUMP_PC2LINE_STATS
#ifdef DUMP_PC2LINE_STATS
static int pc2line_freq[256];
static int pc2line_freq_tot;
#endif
/* return the difference between the line numbers from 'pos1' to
'pos2'. If the difference is zero, '*pcol_num' contains the
difference between the column numbers. Otherwise it contains the
zero based absolute column number.
*/
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) {
/* find the absolute column position */
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
/* warning: pc2line info must be associated to each generated opcode */
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);
}
}
/* insert 'n' bytes at position pos */
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);
/* labels are short integers so they can be used as JSValue. -1 is not
a valid label. */
#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);
/* prevent get_lvalue from using the last expression as an
lvalue. */
s->last_opcode_pos = -1;
}
static void emit_goto(JSParseState *s, int opcode, JSValue *plabel)
{
int label;
/* XXX: generate smaller gotos when possible */
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);
}
}
/* return the constant pool index. 'val' is not duplicated. */
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);
/* check if the value is already present */
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
) {
/* We use a constant pool to avoid scanning the bytecode
during the GC. XXX: is it a good choice ? */
idx = cpool_add(s, val);
emit_op(s, OP_push_const);
emit_u16(s, idx);
} else {
/* no GC mark */
emit_op(s, OP_push_value);
emit_u32(s, val);
}
}
/* return the local variable index or -1 if not found */
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;
}
/* return the external variable index or -1 if not found */
static int find_ext_var(JSParseState *s, JSValue name)
{
return find_func_ext_var(s, s->cur_func, name);
}
/* return the external variable index */
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;
}
/* return the external variable index */
static int add_ext_var(JSParseState *s, JSValue name, int decl)
{
return add_func_ext_var(s, s->cur_func, name, decl);
}
/* return the local variable index */
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;
/* we check the last opcode to get the lvalue type */
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 the last opcode */
remove_last_op(s);
if (keep) {
/* get the value but keep the object/fields on the stack */
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); /* XXX: add OP_get_array_el3 but need to modify tail call */
break;
default:
abort();
}
}
*popcode = opcode;
*pvar_idx = var_idx;
*psource_pos = source_pos;
}
typedef enum {
PUT_LVALUE_KEEP_TOP, /* [depth] v -> v */
PUT_LVALUE_NOKEEP_TOP, /* [depth] v -> */
PUT_LVALUE_KEEP_SECOND, /* [depth] v0 v -> v0 */
PUT_LVALUE_NOKEEP_BOTTOM, /* v [depth] -> */
} 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; /* an assignment defines the variable in the REPL */
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); /* obj a -> a obj a */
break;
case PUT_LVALUE_NOKEEP_TOP:
break;
case PUT_LVALUE_NOKEEP_BOTTOM:
emit_op(s, OP_swap); /* a obj -> obj a */
break;
default:
case PUT_LVALUE_KEEP_SECOND:
emit_op(s, OP_perm3); /* obj a b -> a obj b */
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); /* obj prop a -> a obj prop a */
break;
case PUT_LVALUE_NOKEEP_TOP:
break;
case PUT_LVALUE_NOKEEP_BOTTOM: /* a obj prop -> obj prop a */
emit_op(s, OP_rot3l); /* obj prop a b -> a obj prop b */
break;
default:
case PUT_LVALUE_KEEP_SECOND:
emit_op(s, OP_perm4); /* obj prop a b -> a obj prop b */
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 == '(') {
/* not a get set */
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;
}
/* recursion free parser definitions */
#define PF_NO_IN (1 << 0) /* the 'in' operator is not accepted*/
#define PF_DROP (1 << 1) /* drop result */
#define PF_ACCEPT_LPAREN (1 << 2) /* js_parse_postfix_expr only */
#define PF_LEVEL_SHIFT 4 /* optional level parameter */
#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
/* may trigger a gc */
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;
}
/* WARNING: 'val' may be modified after this val if it is a pointer */
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;
}
/* update the stack bottom when there is a large stack space */
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;\
}
/* WARNING: local variables are not preserved across PARSE_CALL(). So
they must be explicitly saved and restored */
#define PARSE_CALL(s, cur_state, func, param) return (cur_state | (PARSE_FUNC_ ## func << 8) | ((param) << 16)); parse_state ## cur_state : ;
/* preserve var1, ... across the call */
#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) {
/* the function terminated: go back to the calling
function if any */
if (ctx->sp == stack_top)
break;
PARSE_POP_INT(s, ret);
state = ret & 0xff;
func_idx = (ret >> 8) & 0xff;
param = -1; /* the parameter is not saved */
} else {
/* push the call position and call another function */
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); /* regexp source */
saved_buf_pos = s->buf_pos;
saved_buf_len = s->buf_len;
/* save the current bytecode back to the function */
b = JS_VALUE_TO_PTR(s->cur_func);
b->byte_code = s->byte_code;
saved_byte_code_len = s->byte_code_len;
/* modify the parser to parse the regexp. This way we
avoid instantiating a new JSParseState */
/* XXX: find a better way as it relies on the regexp
parser to correctly handle the end of regexp */
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);
/* increment the count */
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 {
/* getter/setter/method */
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);
/* small regular arrays are created on the stack */
idx = 0;
while (s->token.val != ']' && idx < 32) {
/* SPEC: we don't accept empty elements */
PARSE_CALL_SAVE2(s, 2, js_parse_assign_expr, 0, idx, parse_flags);
idx++;
/* accept trailing comma */
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++;
/* accept trailing comma */
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 != '(') {
/* new operator on an object */
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;
/* function call */
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");
}
}
/* fall thru */
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");
}
/* we ensure that no numeric property is used with
OP_get_field to enable some optimizations. The only
possible identifiers are NaN and Infinity */
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);
/* XXX: could handle more cases */
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);
/* access to undefined variable should not return an
exception, so we patch the get_var */
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);
/* XXX: we do not follow the ES7 grammar in order to have a
* more natural expression */
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) {
/* prevent get_lvalue from using the last expression as an
lvalue. */
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);
/* no need to drop if no "finally" */
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); /* must keep the stack stop */
drop_count = 0;
/* execute the "finally" block */
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)) {
/* must push dummy value to keep same stack depth */
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;
}
/* The statement parser assumes that the stack contains the result of
the last statement. Note: if not in eval code, the return value of
a statement does not matter */
static int js_parse_statement(JSParseState *s, int state, int dummy_param)
{
JSValue label_name;
JSGCRef label_name_ref;
PARSE_START12();
/* specific label handling */
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) {
/* labelled regular statement */
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);
/* Insert semicolon if missing */
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;
/* XXX: js_parse_left_hand_side_expr */
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);
/* on stack: enum_rec / enum_obj value bool */
emit_goto(s, OP_if_false, &label_next);
/* drop the undefined value from for_xx_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;
/* initial expression */
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);
/* test expression */
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 != ')') {
/* skip the third expression if present */
js_parse_get_pos(s, &expr3_pos);
js_skip_expr(s);
} else {
expr3_pos.source_pos = -1;
expr3_pos.got_lf = 0; /* avoid warning */
expr3_pos.regexp_allowed = 0; /* avoid warning */
}
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);
/* parse the third expression, if present, after the
statement */
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; /* label to the next case */
while (s->token.val != '}') {
if (s->token.val == TOK_CASE) {
JSValue label1 = LABEL_NONE;
if (!label_is_none(label_case)) {
/* skip the case if needed */
label1 = new_label(s);
emit_goto(s, OP_goto, &label1);
emit_label(s, &label_case);
label_case = LABEL_NONE;
}
for (;;) {
/* parse a sequence of case clauses */
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)) {
/* falling thru direct from switch expression */
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) {
/* patch the default label */
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); /* drop the switch expression */
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);
/* drop the catch offset */
emit_op(s, OP_drop);
/* must push dummy value to keep same stack size */
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;
/* XXX: the local scope is not implemented, so we add
a normal variable */
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, ')');
/* store the exception value in the variable */
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);
/* drop the catch2 offset */
emit_op(s, OP_drop);
/* must push dummy value to keep same stack size */
emit_op(s, OP_undefined);
emit_goto(s, OP_gosub, &label_finally);
emit_op(s, OP_drop);
emit_goto(s, OP_goto, &label_end);
/* catch exceptions thrown in the catch block to execute the
* finally clause and rethrow the exception */
emit_label(s, &label_catch2);
/* catch value is at TOS, no need to push undefined */
emit_goto(s, OP_gosub, &label_finally);
emit_op(s, OP_throw);
} else if (s->token.val == TOK_FINALLY) {
/* finally without catch : execute the finally clause
* and rethrow the exception */
emit_label(s, &label_catch);
/* catch value is at TOS, no need to push undefined */
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);
/* XXX: we don't return the correct value in eval() */
/* on the stack: ret_value gosub_ret_value */
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 ';':
/* empty statement */
next_token(s);
break;
default:
if (s->eval_ret_idx >= 0) {
/* store the expression value so that it can be returned
by eval() */
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;
}
/* the current token must be TOK_FUNCTION for JS_PARSE_FUNC_STATEMENT
or JS_PARSE_FUNC_EXPR. Otherwise it is '('. */
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, '(');
/* skip the arguments */
js_skip_parens(s, NULL);
js_parse_expect1(s, '{');
/* skip the code */
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) {
/* create the function object */
emit_op(s, OP_fclosure);
emit_u16(s, idx);
} else {
idx = define_var(s, &var_kind, func_name_ref.val);
/* size of hoisted for OP_fclosure + OP_put_loc/OP_put_arg/OP_put_ref */
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);
/* hoisted function definition: save the variable index to
define it at the start of the function */
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;
/* add pc2line info */
b = JS_VALUE_TO_PTR(s->cur_func);
if (b->pc2line != JS_NULL) {
int h, n;
/* byte align */
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;
/* XXX: could use smaller opcodes */
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;
}
/* no realloc possible here */
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;
/* XXX: gc */
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, '{');
/* initialize the arguments */
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);
}
/* XXX: initialize the function name */
b = JS_VALUE_TO_PTR(s->cur_func);
if (b->has_local_func_name) {
int var_idx;
/* XXX: */
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);
/* save the bytecode to the function */
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);
/* hidden variable for the return value */
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);
/* save the bytecode to the function */
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; /* new local var index */
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;
}
}
/* no allocation */
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);
/* do it by parts to save memory */
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);
/* fail safe: we avoid arguments even if they cannot appear */
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;
}
/* prepare the analysis of the code starting at position 'pos' */
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
js_printf(s->ctx, "%5d: %d\n", pos, stack_len);
#endif
if (pos >= (uint32_t)arr->size)
js_parse_error(s, "bytecode buffer overflow (pc=%d)", pos);
/* XXX: could avoid the division */
short_stack_len = 1 + ((unsigned)stack_len % 255);
if (explore_tab[pos] != 0) {
/* already explored: check that the stack size is consistent */
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;
/* may initiate a GC */
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);
/* compute_stack_size_push may have initiated a GC */
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; /* no code after */
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) {
/* the global type may be patched later */
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; /* parent_func */
*--ctx->sp = *pfunc; /* func */
*--ctx->sp = JS_NewShortInt(0); /* cpool_pos */
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
JS_DumpValue(ctx, "func", *pfunc);
JS_DumpValue(ctx, "parent", *pparent_func);
JS_DumpValue(ctx, "cpool_pos", ctx->sp[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);
/* parse a local function */
err = JS_StackCheck(ctx, 3);
JS_POP_VALUE(ctx, func);
if (err)
js_parse_error_stack_overflow(s);
/* set the next cpool position */
*ctx->sp = JS_NewShortInt(cpool_pos + 1);
*--ctx->sp = *pfunc; /* parent_func */
*--ctx->sp = func; /* func */
*--ctx->sp = JS_NewShortInt(0); /* cpool_pos */
goto next;
}
}
if (*pparent_func != JS_NULL) {
resolve_var_refs(s, pfunc, pparent_func);
}
/* now we can shrink the external vars */
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
/* remove the stack entry */
ctx->sp += 3;
ctx->stack_bottom = ctx->sp;
next: ;
}
}
/* return the parsed value in s->token.value */
/* XXX: use exact JSON white space definition */
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); /* 'val' is not usable after this call */
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); /* 'val' is not usable after this call */
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;
}
/* source_str must be a string or JS_NULL. (input, input_len) is
meaningful only if source_str is JS_NULL. */
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];
/* XXX: start gc at the start of parsing ? */
/* XXX: if the parse state is too large, move it to JSContext */
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;
}
/* warning: it is assumed that input[input_len] = '\0' */
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;
}
/* warning: it is assumed that input[input_len] = '\0' */
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);
}
/**********************************************************************/
/* garbage collector */
/* return the size in bytes */
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;
}
/* gc mark pass */
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) {
/* value array are handled specifically to save stack space */
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;
}
}
}
}
/* flush the GC mark stack */
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++;
/* fast path to skip non objects */
while (pos < p->size && !JS_IsPtr(p->arr[pos]))
pos++;
if (pos < p->size) {
if ((pos + 1) < p->size) {
/* the next element needs to be scanned */
*--s->gsp = pos + 1;
*--s->gsp = val;
}
/* mark the current element */
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);
}
/* return true if the memory block is marked i.e. it won't be freed by the GC */
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;
/* initialize the GC stack */
s->overflow = FALSE;
s->gs_top = ctx->sp;
s->gsp = s->gs_top;
#if 1
s->gs_bottom = (JSValue *)ctx->heap_free;
#else
s->gs_bottom = s->gs_top - 3; /* TEST small stack space */
#endif
/* keep the atoms if they are in RAM (only used when compiling to file) */
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));
}
}
/* mark all the memory blocks */
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);
}
/* if the mark stack overflowed, need to scan the heap */
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;
}
}
/* update the unique string table (its elements are considered as
weak string references) */
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) {
/* shrink the array */
set_free_block(&arr->arr[j], (arr->size - j) * sizeof(JSValue));
arr->size = j;
}
} else {
arr->gc_mark = 0;
ctx->unique_strings = JS_NULL;
}
}
/* update the weak references in the string position cache */
{
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;
}
}
/* reset the gc marks and mark the free blocks as free */
{
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;
/* call the user finalizer if needed */
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);
}
/* merge all the consecutive free blocks */
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;
/* gc_mark = 0 indicates a normal memory block header, gc_mark = 1
indicates a pointer to another element */
*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)) {
/* update the threaded pointers to the node 'ptr' and
unthread it. */
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;
}
}
/* Heap compaction using Jonkers algorithm */
static void gc_compact_heap(JSContext *ctx)
{
uint8_t *ptr, *new_ptr;
int size;
JSValue *sp, *sp_end;
/* thread all the external pointers */
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);
}
/* pass 1: thread the pointers and update the previous ones */
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;
}
/* pass 2: update the threaded pointers and move the block to its
final position */
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;
/* update the source pointer in the parser */
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;
}
}
/* rehash the object properties */
/* XXX: try to do it in the previous pass (add a specific tag ?) */
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)
/* reduce the dummy block size at each GC to change the addresses
after compaction */
/* XXX: only works a finite number of times */
{
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);
}
/* bytecode saving and loading */
#define JS_BYTECODE_VERSION_32 0x0001
/* bit 15 of bytecode version is a 64-bit indicator */
#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;
/* remove all the objects except the compiled code */
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);
/* unaligned 64 bit access in 32-bit mode */
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;
/* true if the string content represents a number, only meaningful
is is_unique = true */
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; /* only used during parsing */
JSWord_32 has_local_func_name : 1; /* only used during parsing */
JSWord_32 has_column : 1; /* column debug info is present */
JSWord_32 arg_count : 16;
JSWord_32 dummy: JS_MB_PAD_32(JS_MTAG_BITS + 3 + 16);
JSValue_32 func_name; /* JS_NULL if anonymous function */
JSValue_32 byte_code; /* JS_NULL if the function is not parsed yet */
JSValue_32 cpool; /* constant pool */
JSValue_32 vars; /* only for debug */
JSValue_32 ext_vars; /* records of (var_name, var_kind (2 bits) var_idx (16 bits)) */
uint16_t stack_size; /* maximum stack size */
uint16_t ext_vars_len; /* XXX: only used during parsing */
JSValue_32 filename; /* filename in which the function is defined */
JSValue_32 pc2line; /* JSByteArray or JS_NULL if not initialized */
uint32_t source_pos; /* only used during parsing (XXX: shrink) */
} JSFunctionBytecode_32;
/* warning: ptr1 and ptr may overlap. However there is always: ptr1 <= ptr. Return 0 if OK. */
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; /* no test needed as long as JS_VALUE_ARRAY_SIZE_MAX is identical */
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; /* no test needed as long as JS_BYTE_ARRAY_SIZE_MAX is identical */
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;
}
/* return the size in bytes */
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;
}
/* Compact and convert a 64 bit heap to a 32 bit heap at offset
0. Only used for code compilation. Return 0 if OK. */
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);
/* thread all the external pointers */
{
JSGCRef *ref;
/* necessary because JS_PUSH_VAL() is called before
gc_compact_heap_64to32() */
for(ref = ctx->top_gc_ref; ref != NULL; ref = ref->prev) {
gc_thread_pointer(ctx, &ref->val);
}
}
/* pass 1: thread the pointers and update the previous ones */
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;
}
/* pass 2: update the threaded pointers and move the block to its
final position */
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;
}
/* Expand all the short floats to JSFloat64 structures. Return < 0 if
not enough memory. */
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:
/* we assume no short floats here */
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 /* JS_USE_SHORT_FLOAT */
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;
/* remove all the objects except the compiled code */
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;
/* ensure that JS_FreeContext() will do nothing */
ctx->heap_free = ctx->heap_base;
return 0;
}
#endif /* JSW == 8 */
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;
/* unique strings must be unique, so modify the unique string
value if it already exists in the context */
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;
/* XXX: add atom checksum to avoid problems if the stdlib is
modified */
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;
}
/* Relocate the bytecode in 'buf' so that it can be executed
later. Return 0 if OK, != 0 if error */
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);
}
/* Load the precompiled bytecode from 'buf'. 'buf' must be allocated
as long as the JSContext exists. Use JS_Run() to execute
it. warning: the bytecode is not checked so it should come from a
trusted source. */
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");
/* XXX: could stack atom_tables */
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;
}
/**********************************************************************/
/* runtime */
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) {
/* for C constructors, the prototype property is already present */
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]);
/* we avoid recursing on the C stack */
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]);
/* we avoid recursing on the C stack */
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[0] = func, arr[1] = this */
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));
}
/* XXX: handle constructor case */
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]); /* func */
JS_PushArg(ctx, arr->arr[1]); /* this_val */
/* we avoid recursing on the C stack */
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");
}
/* cannot fail */
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; /* ignored */
}
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);
}
// dump_string_pos_cache(ctx);
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;
}
/* Note: ascii only */
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);
}
/* c < 128 */
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)
{
/* XXX: incomplete */
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;
}
/* 'obj' must be an object. 'proto' must be JS_NULL or an object */
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) {
/* check if there is a cycle */
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];
/* exclude deleted properties */
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; /* XXX: could improve for strings */
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];
/* XXX: not fully compliant */
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);
/* shrink the array if the new size is small enough */
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])) {
/* XXX: we create undefined properties instead of just setting the length */
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;
/* do a first pass to estimate the length */
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;
}
/* the array may be modified */
p = JS_VALUE_TO_PTR(*this_val);
len = p->u.array.len; /* the length may be modified */
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;
}
/* the array may have been modified */
p = JS_VALUE_TO_PTR(*this_val);
len = p->u.array.len; /* the length may be modified */
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);
/* handling this case has no practical use */
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) {
/* resize */
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);
/* the array length may have been modified by the function call*/
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); /* arg0 */
JS_PushArg(ctx, *pfunc); /* func */
JS_PushArg(ctx, pthis_arg ? *pthis_arg : JS_UNDEFINED); /* this */
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:
/* Note: same as defineProperty for arrays */
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);
/* Note: the array length may have been modified, hence the check */
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); /* arg0 */
JS_PushArg(ctx, *pfunc); /* func */
JS_PushArg(ctx, JS_UNDEFINED); /* this */
acc = JS_Call(ctx, 4);
if (JS_IsException(acc))
return JS_EXCEPTION;
}
return acc;
}
/* heapsort algorithm */
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;
/* return -1, 0, 1 */
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;
/* custom sort function is specified as returning 0 for identical
* objects: avoid method call overhead.
*/
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]); /* arg0 */
JS_PushArg(ctx, *s->pfunc); /* func */
JS_PushArg(ctx, JS_UNDEFINED); /* this */
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:
/* make sort stable: compare array offsets */
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;
/* create a temporary array for sorting */
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++;
}
}
/* the end of 'tab' is already filled with JS_UNDEFINED */
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);
/* XXX: could resize the array in case it was shrank by the compare function */
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;
}
/**********************************************************************/
/* precondition: a and b are not NaN */
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;
}
}
/* precondition: a and b are not NaN */
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;
/* purposely ignoring overflow */
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));
}
/* xorshift* random number generator by Marsaglia */
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);
/* 1.0 <= u.d < 2 */
d = uint64_as_float64(((uint64_t)0x3ff << 52) | (v >> 12));
return __JS_NewFloat64(ctx, d - 1.0);
}
/* typed array */
#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;
/* XXX: should support 53 bit inteers */
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);
/* check offset and count */
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);
/* same type: must copy to preserve float bits */
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;
}
/* Date */
JSValue js_date_constructor(JSContext *ctx, JSValue *this_val,
int argc, JSValue *argv)
{
return JS_ThrowTypeError(ctx, "only Date.now() is supported");
}
/* global */
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));
}
/* JSON */
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(;;) {
/* XXX: inefficient */
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;
}
/* XXX: no space nor replacer */
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
if (JS_IsNumber(ctx, *pspace)) {
int n;
if (JS_ToInt32Clamp(ctx, &n, *pspace, 0, 10, 0))
return JS_EXCEPTION;
*pspace = JS_NewStringLen(ctx, " ", n);
} else if (JS_IsString(ctx, *pspace)) {
*pspace = js_sub_string(ctx, *pspace, 0, 10);
} else {
*pspace = js_get_atom(ctx, JS_ATOM_empty);
}
#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; /* keys */
*--ctx->sp = JS_NewShortInt(0); /* prop index */
*--ctx->sp = argv[0]; /* object */
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;
/* array */
if (idx == 0)
string_buffer_putc(ctx, b, '[');
p = JS_VALUE_TO_PTR(ctx->sp[0]);
if (idx >= p->u.array.len) {
/* end of array */
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;
/* object */
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]); /* keys */
if (idx >= p->u.array.len) {
/* end of object */
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;
/* skip undefined properties */
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;
}
/**********************************************************************/
/* regexp */
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);
}
/* return the offset of the u32 value */
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++;
}
/* return JS_SHORTINT_MAX in case of overflow */
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;
}
/* need_check_adv: false if the opcodes always advance the char pointer
need_capture_init: true if all the captures in the atom are not set
*/
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:
/* no effect */
break;
case REOP_save_start:
case REOP_save_end:
case REOP_save_reset:
break;
default:
/* safe behavior: we cannot predict the outcome */
need_capture_init = TRUE;
goto done;
}
pos += len;
}
done:
*pneed_capture_init = need_capture_init;
return need_check_adv;
}
/* return the character or a class range (>= CLASS_RANGE_BASE) if inclass
= TRUE */
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)) { /* Annex B.1.4 */
c &= 0x1f;
p++;
} else if (s->is_unicode) {
goto invalid_escape;
} else {
/* otherwise return '\' and 'c' */
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 '/':
/* always valid to escape these characters */
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 '/': /* safety for end of regexp in JS parser */
if ((p - s->source_buf) >= s->buf_len)
js_parse_error(s, "unexpected end");
goto normal_char;
default:
normal_char:
/* normal char */
ret = unicode_from_utf8(p, UTF8_CHAR_LEN_MAX, &len);
/* Note: should not fail with normal JS strings */
if (ret < 0)
js_parse_error(s, "malformed unicode char");
p += len;
c = ret;
break;
}
s->buf_pos = p - s->source_buf;
return c;
}
/* code point ranges for Zs,Zl or Zp property */
static const uint16_t char_range_s[] = {
0x0009, 0x000D + 1,
0x0020, 0x0020 + 1,
0x00A0, 0x00A0 + 1,
0x1680, 0x1680 + 1,
0x2000, 0x200A + 1,
/* 2028;LINE SEPARATOR;Zl;0;WS;;;;;N;;;;; */
/* 2029;PARAGRAPH SEPARATOR;Zp;0;B;;;;;N;;;;; */
0x2028, 0x2029 + 1,
0x202F, 0x202F + 1,
0x205F, 0x205F + 1,
0x3000, 0x3000 + 1,
/* FEFF;ZERO WIDTH NO-BREAK SPACE;Cf;0;BN;;;;;N;BYTE ORDER MARK;;;; */
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);
}
/* merge consecutive intervals, remove empty intervals and handle overlapping intervals */
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) {
/* empty interval : remove */
} 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 {
/* union of the intervals */
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);
/* must compress before inverting */
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");
/* compress to 8 bit if possible */
/* XXX: adjust threshold */
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: ;
}
/* add the intersection of the two intervals and if offset != 0 the
translated interval */
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++; /* skip '[' */
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 the intervals exclude the cased characters */
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);
/* include all the possible cases */
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++; /* skip ']' */
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; /* infinity */
}
}
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;
/* the spec tells that if there is no advance when
running the atom after the first quant_min times,
then there is no match. We remove this test when we
are sure the atom always advances the position. */
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);
/* general case: need to reset the capture at each
iteration. We don't do it if there are no captures
in the atom or if we are sure all captures are
initialized in the atom. If quant_min = 0, we still
need to reset once the captures in case the atom
does not match. */
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) {
/* need to reset the capture in case the atom is
not executed */
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);
/* Note: we assume the string length is < JS_SHORTINT_MAX */
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) {
/* a simple loop is enough */
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;
}
}
/* return the number of bytes if char otherwise 0 */
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 '{':
/* As an extension (see ES6 annex B), we accept '{' not
followed by digits as a normal atom */
if (!s->is_unicode && !is_digit(s->source_buf[s->buf_pos + 1]))
goto parse_class_atom;
/* fall thru */
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;
/* lookahead */
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);
/* jump after the 'match' after the lookahead is successful */
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++;
/* capture without group name */
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");
/* the range is checked afterwards as we don't know the number of captures */
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'))) {
/* XXX: could add specific operation */
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;
}
/* quantifier */
if (last_atom_start >= 0) {
re_parse_quantifier(s, last_atom_start, last_capture_count);
}
/* combine several characters when possible */
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;
/* insert a split before the first alternative */
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);
/* patch the goto */
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;
}
/* Allocate the registers as a stack. The control flow is recursive so
the analysis can be linear. */
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:
/* validate back references */
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;
}
/* return a JSByteArray. 'source' must be a string */
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); /* number of captures */
emit_u8(s, 0); /* number of registers */
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;
}
/* regexp interpreter */
#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 == '_'));
}
/* Note: we canonicalize as in the unicode case, but only handle ASCII characters */
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;
//#define DUMP_REEXEC
/* return 1 if match, 0 if not match or < 0 if error. str must be a
JSString. capture_buf and byte_code are JSByteArray */
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; /* temporary use */
JSString *ps; /* temporary use */
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; \
}
/* avoid saving the previous value if already saved */
#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;
}
/* undo the modifications to capture[] and regs[] */
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:
/* pop all the saved states until reaching the start of
the lookahead and keep the updated captures and
variables and the corresponding undo info. */
{
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); /* save the next value for the copy step */
sp += 3;
if (type == RE_EXEC_STATE_LOOKAHEAD)
break;
}
if (sp != initial_sp) {
/* keep the undo info if there is a saved state */
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:
/* pop all the saved states until reaching start of the negative lookahead */
for(;;) {
REExecStateEnum type;
type = RE_VALUE_TO_TYPE(bp[0]);
/* undo the modifications to capture[] and regs[] */
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;
/* decrement the counter */
val2 = capture[2 * capture_count + idx] - 1;
SAVE_CAPTURE_CHECK(2 * capture_count + idx, val2);
if (val2 > limit) {
/* normal loop if counter > limit */
pc += (int)val;
LRE_POLL_INTERRUPT();
} else {
/* check advance */
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;
}
/* otherwise conditional split */
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);
/* char before */
if (cptr == cbuf) {
v1 = FALSE;
} else {
PEEK_PREV_CHAR(c, cptr, cbuf);
v1 = is_word_char(c);
}
/* current char */
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;
/* assumption: 8 bit and small number of ranges */
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;
}
/* 0xff = max code point value */
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); /* n must be >= 1 */
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();
}
}
}
/* regexp js interface */
/* return the length */
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
case 'd':
mask = LRE_FLAG_INDICES;
break;
#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
case 'v':
mask = LRE_FLAG_UNICODE_SETS;
break;
#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;
}
/* pattern and flags must be strings */
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;
/* XXX: not complete */
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
/* return the string length */
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, /* same as exec but force the global flag */
};
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]);
/* JS_VALUE_FROM_PTR(str) is acceptable here because the
GC ignores pointers outside the heap */
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;
}
/* if regexp replace: capture_buf != NULL, needle = NULL
if string replace: capture_buf = NULL, captures_len = 1, needle != NULL
*/
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); /* function */
JS_PushArg(ctx, JS_UNDEFINED); /* this_val */
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++; /* j0 = position of '$' */
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);
/* JS_VALUE_FROM_PTR(str) is acceptable here because the
GC ignores pointers outside the heap */
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);
/* since regexp are unicode by default, replace is also unicode by default */
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);
}
// split(sep, limit)
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;
/* XXX: need sticky behavior */
*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);
/* since regexp are unicode by default, split is also unicode by default */
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);
/* since regexp are unicode by default, split is also unicode by default */
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);
}