mirror of https://github.com/svaarala/duktape.git
Sami Vaarala
12 years ago
4 changed files with 886 additions and 0 deletions
@ -0,0 +1,216 @@ |
|||||
|
/*
|
||||
|
* Array built-ins |
||||
|
* |
||||
|
* Note that most Array built-ins are intentionally generic and work even |
||||
|
* when the 'this' binding is not an Array instance. To ensure this, |
||||
|
* Array algorithms do not assume "magical" Array behavior for the "length" |
||||
|
* property, for instance. |
||||
|
*/ |
||||
|
|
||||
|
#include "duk_internal.h" |
||||
|
|
||||
|
int duk_builtin_array_constructor(duk_context *ctx) { |
||||
|
if (duk_is_constructor_call(ctx)) { |
||||
|
return DUK_RET_UNIMPLEMENTED_ERROR; /*FIXME*/ |
||||
|
} else { |
||||
|
return DUK_RET_UNIMPLEMENTED_ERROR; /*FIXME*/ |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
int duk_builtin_array_constructor_is_array(duk_context *ctx) { |
||||
|
return DUK_RET_UNIMPLEMENTED_ERROR; |
||||
|
} |
||||
|
|
||||
|
int duk_builtin_array_prototype_to_string(duk_context *ctx) { |
||||
|
duk_hthread *thr = (duk_hthread *) ctx; |
||||
|
|
||||
|
duk_push_this(ctx); |
||||
|
DUK_DDDPRINT("this: %!T", duk_get_tval(ctx, -1)); |
||||
|
|
||||
|
duk_to_object(ctx, -1); |
||||
|
duk_get_prop_stridx(ctx, -1, DUK_HEAP_STRIDX_JOIN); |
||||
|
|
||||
|
/* FIXME: uneven stack would reduce code size */ |
||||
|
|
||||
|
/* [ ... this func ] */ |
||||
|
if (!duk_is_callable(ctx, -1)) { |
||||
|
DUK_DDDPRINT("this.join is not callable, fall back to Object.toString"); |
||||
|
duk_pop(ctx); |
||||
|
duk_push_hobject(ctx, thr->builtins[DUK_BIDX_OBJECT_PROTOTYPE]); |
||||
|
duk_get_prop_stridx(ctx, -1, DUK_HEAP_STRIDX_TO_STRING); |
||||
|
duk_remove(ctx, -2); |
||||
|
} |
||||
|
|
||||
|
/* [ ... this func ] */ |
||||
|
|
||||
|
duk_insert(ctx, -2); |
||||
|
|
||||
|
/* [ ... func this ] */ |
||||
|
|
||||
|
DUK_DDDPRINT("calling: func=%!iT, this=%!iT", duk_get_tval(ctx, -2), duk_get_tval(ctx, -1)); |
||||
|
duk_call_method(ctx, 0); |
||||
|
|
||||
|
return 1; |
||||
|
} |
||||
|
|
||||
|
int duk_builtin_array_prototype_to_locale_string(duk_context *ctx) { |
||||
|
return DUK_RET_UNIMPLEMENTED_ERROR; /*FIXME*/ |
||||
|
} |
||||
|
|
||||
|
int duk_builtin_array_prototype_concat(duk_context *ctx) { |
||||
|
return DUK_RET_UNIMPLEMENTED_ERROR; /*FIXME*/ |
||||
|
} |
||||
|
|
||||
|
/* Note: checking valstack is necessary, but only in the per-element loop */ |
||||
|
|
||||
|
/* FIXME: This placeholder does not work for a large number of elements.
|
||||
|
* Provide proper hierarchical concat/join primitives in the API and use |
||||
|
* them here. |
||||
|
*/ |
||||
|
|
||||
|
int duk_builtin_array_prototype_join(duk_context *ctx) { |
||||
|
duk_u32 len; |
||||
|
duk_u32 i; |
||||
|
|
||||
|
DUK_ASSERT(duk_get_top(ctx) == 1); /* nargs is 1 */ |
||||
|
if (duk_is_undefined(ctx, 0)) { |
||||
|
duk_pop(ctx); |
||||
|
duk_push_hstring_stridx(ctx, DUK_HEAP_STRIDX_COMMA); |
||||
|
} else { |
||||
|
duk_to_string(ctx, 0); |
||||
|
} |
||||
|
|
||||
|
duk_push_this(ctx); |
||||
|
duk_to_object(ctx, -1); /* FIXME: common enough to warrant an internal helper? push_this_to_object */ |
||||
|
|
||||
|
/* [ sep ToObject(this) ] */ |
||||
|
|
||||
|
duk_get_prop_stridx(ctx, -1, DUK_HEAP_STRIDX_LENGTH); |
||||
|
len = duk_to_uint32(ctx, -1); |
||||
|
|
||||
|
DUK_DDDPRINT("sep=%!T, this=%!T, len=%d", duk_get_tval(ctx, 0), duk_get_tval(ctx, 1), len); |
||||
|
|
||||
|
if (len == 0) { |
||||
|
duk_push_hstring_stridx(ctx, DUK_HEAP_STRIDX_EMPTY_STRING); |
||||
|
return 1; |
||||
|
} |
||||
|
|
||||
|
duk_require_stack(ctx, 2*len - 1); |
||||
|
|
||||
|
for (i = 0; i < len; i++) { |
||||
|
/* FIXME: primitive C API call for join */ |
||||
|
if (i > 0) { |
||||
|
duk_dup(ctx, 0); |
||||
|
} |
||||
|
|
||||
|
duk_get_prop_index(ctx, 1, i); |
||||
|
if (duk_is_null_or_undefined(ctx, -1)) { |
||||
|
duk_pop(ctx); |
||||
|
duk_push_hstring_stridx(ctx, DUK_HEAP_STRIDX_EMPTY_STRING); |
||||
|
} else { |
||||
|
duk_to_string(ctx, -1); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/* [ sep ToObject(this) str0 sep str1 sep ... sep str(len-1) ] */ |
||||
|
|
||||
|
duk_concat(ctx, 2*len - 1); |
||||
|
return 1; |
||||
|
} |
||||
|
|
||||
|
int duk_builtin_array_prototype_pop(duk_context *ctx) { |
||||
|
return DUK_RET_UNIMPLEMENTED_ERROR; /*FIXME*/ |
||||
|
} |
||||
|
|
||||
|
int duk_builtin_array_prototype_push(duk_context *ctx) { |
||||
|
/* Note: 'this' is not necessarily an Array object. The push()
|
||||
|
* algorithm is supposed to work for other kinds of objects too, |
||||
|
* so the algorithm has e.g. an explicit update for the 'length' |
||||
|
* property which is normally "magical" in arrays. |
||||
|
*/ |
||||
|
duk_u32 len; |
||||
|
int i, n; |
||||
|
|
||||
|
n = duk_get_top(ctx); |
||||
|
|
||||
|
duk_push_this(ctx); |
||||
|
duk_to_object(ctx, -1); |
||||
|
duk_get_prop_stridx(ctx, -1, DUK_HEAP_STRIDX_LENGTH); |
||||
|
len = duk_to_uint32(ctx, -1); |
||||
|
|
||||
|
/* [ arg1 ... argN obj length ] */ |
||||
|
|
||||
|
for (i = 0; i < n; i++) { |
||||
|
duk_dup(ctx, i); |
||||
|
duk_put_prop_index(ctx, -3, len); /* FIXME: "Throw" is true for this [[Put]] call, needs API support */ |
||||
|
len++; |
||||
|
} |
||||
|
|
||||
|
duk_push_number(ctx, (double) len); /* FIXME: duk_push_u32 */ |
||||
|
duk_dup_top(ctx); |
||||
|
duk_put_prop_stridx(ctx, -3, DUK_HEAP_STRIDX_LENGTH); |
||||
|
|
||||
|
/* [ arg1 ... argN obj length new_length ] */ |
||||
|
return 1; |
||||
|
} |
||||
|
|
||||
|
int duk_builtin_array_prototype_reverse(duk_context *ctx) { |
||||
|
return DUK_RET_UNIMPLEMENTED_ERROR; /*FIXME*/ |
||||
|
} |
||||
|
|
||||
|
int duk_builtin_array_prototype_shift(duk_context *ctx) { |
||||
|
return DUK_RET_UNIMPLEMENTED_ERROR; /*FIXME*/ |
||||
|
} |
||||
|
|
||||
|
int duk_builtin_array_prototype_slice(duk_context *ctx) { |
||||
|
return DUK_RET_UNIMPLEMENTED_ERROR; /*FIXME*/ |
||||
|
} |
||||
|
|
||||
|
int duk_builtin_array_prototype_sort(duk_context *ctx) { |
||||
|
return DUK_RET_UNIMPLEMENTED_ERROR; /*FIXME*/ |
||||
|
} |
||||
|
|
||||
|
int duk_builtin_array_prototype_splice(duk_context *ctx) { |
||||
|
return DUK_RET_UNIMPLEMENTED_ERROR; /*FIXME*/ |
||||
|
} |
||||
|
|
||||
|
int duk_builtin_array_prototype_unshift(duk_context *ctx) { |
||||
|
return DUK_RET_UNIMPLEMENTED_ERROR; /*FIXME*/ |
||||
|
} |
||||
|
|
||||
|
int duk_builtin_array_prototype_index_of(duk_context *ctx) { |
||||
|
return DUK_RET_UNIMPLEMENTED_ERROR; /*FIXME*/ |
||||
|
} |
||||
|
|
||||
|
int duk_builtin_array_prototype_last_index_of(duk_context *ctx) { |
||||
|
return DUK_RET_UNIMPLEMENTED_ERROR; /*FIXME*/ |
||||
|
} |
||||
|
|
||||
|
int duk_builtin_array_prototype_every(duk_context *ctx) { |
||||
|
return DUK_RET_UNIMPLEMENTED_ERROR; /*FIXME*/ |
||||
|
} |
||||
|
|
||||
|
int duk_builtin_array_prototype_some(duk_context *ctx) { |
||||
|
return DUK_RET_UNIMPLEMENTED_ERROR; /*FIXME*/ |
||||
|
} |
||||
|
|
||||
|
int duk_builtin_array_prototype_for_each(duk_context *ctx) { |
||||
|
return DUK_RET_UNIMPLEMENTED_ERROR; /*FIXME*/ |
||||
|
} |
||||
|
|
||||
|
int duk_builtin_array_prototype_map(duk_context *ctx) { |
||||
|
return DUK_RET_UNIMPLEMENTED_ERROR; /*FIXME*/ |
||||
|
} |
||||
|
|
||||
|
int duk_builtin_array_prototype_filter(duk_context *ctx) { |
||||
|
return DUK_RET_UNIMPLEMENTED_ERROR; /*FIXME*/ |
||||
|
} |
||||
|
|
||||
|
int duk_builtin_array_prototype_reduce(duk_context *ctx) { |
||||
|
return DUK_RET_UNIMPLEMENTED_ERROR; /*FIXME*/ |
||||
|
} |
||||
|
|
||||
|
int duk_builtin_array_prototype_reduce_right(duk_context *ctx) { |
||||
|
return DUK_RET_UNIMPLEMENTED_ERROR; /*FIXME*/ |
||||
|
} |
||||
|
|
@ -0,0 +1,412 @@ |
|||||
|
/*
|
||||
|
* __duk__ built-ins |
||||
|
*/ |
||||
|
|
||||
|
#include <sys/time.h> |
||||
|
|
||||
|
#include "duk_internal.h" |
||||
|
|
||||
|
int duk_builtin_duk_object_addr(duk_context *ctx) { |
||||
|
duk_tval *tv; |
||||
|
void *p; |
||||
|
|
||||
|
tv = duk_get_tval(ctx, 0); |
||||
|
if (!tv || !DUK_TVAL_IS_HEAP_ALLOCATED(tv)) { |
||||
|
return 0; /* undefined */ |
||||
|
} |
||||
|
p = (void *) DUK_TVAL_GET_HEAPHDR(tv); |
||||
|
|
||||
|
/* any heap allocated value (string, object, buffer) has a stable pointer */ |
||||
|
duk_push_sprintf(ctx, "%p", p); |
||||
|
return 1; |
||||
|
} |
||||
|
|
||||
|
int duk_builtin_duk_object_refc(duk_context *ctx) { |
||||
|
#ifdef DUK_USE_REFERENCE_COUNTING |
||||
|
duk_tval *tv = duk_get_tval(ctx, 0); |
||||
|
duk_heaphdr *h; |
||||
|
if (!tv) { |
||||
|
return 0; |
||||
|
} |
||||
|
if (!DUK_TVAL_IS_HEAP_ALLOCATED(tv)) { |
||||
|
return 0; |
||||
|
} |
||||
|
h = DUK_TVAL_GET_HEAPHDR(tv); |
||||
|
duk_push_int(ctx, DUK_HEAPHDR_GET_REFCOUNT(h)); |
||||
|
return 1; |
||||
|
#else |
||||
|
return 0; |
||||
|
#endif |
||||
|
} |
||||
|
|
||||
|
int duk_builtin_duk_object_gc(duk_context *ctx) { |
||||
|
#ifdef DUK_USE_MARK_AND_SWEEP |
||||
|
duk_hthread *thr = (duk_hthread *) ctx; |
||||
|
int flags; |
||||
|
int rc; |
||||
|
|
||||
|
flags = duk_get_int(ctx, 0); |
||||
|
rc = duk_heap_mark_and_sweep(thr->heap, flags); |
||||
|
duk_push_int(ctx, rc); |
||||
|
return 1; |
||||
|
#else |
||||
|
return 0; |
||||
|
#endif |
||||
|
} |
||||
|
|
||||
|
int duk_builtin_duk_object_get_finalizer(duk_context *ctx) { |
||||
|
(void) duk_require_hobject(ctx, 0); |
||||
|
duk_get_prop_stridx(ctx, 0, DUK_HEAP_STRIDX_INT_FINALIZER); |
||||
|
return 1; |
||||
|
} |
||||
|
|
||||
|
int duk_builtin_duk_object_set_finalizer(duk_context *ctx) { |
||||
|
DUK_ASSERT(duk_get_top(ctx) == 2); |
||||
|
(void) duk_put_prop_stridx(ctx, 0, DUK_HEAP_STRIDX_INT_FINALIZER); /* XXX: check value? */ |
||||
|
return 0; |
||||
|
} |
||||
|
|
||||
|
/*
|
||||
|
* Spawn a thread. |
||||
|
*/ |
||||
|
|
||||
|
int duk_builtin_duk_object_spawn(duk_context *ctx) { |
||||
|
duk_hthread *new_thr; |
||||
|
duk_hobject *func; |
||||
|
|
||||
|
if (!duk_is_callable(ctx, 0)) { |
||||
|
return DUK_RET_TYPE_ERROR; |
||||
|
} |
||||
|
func = duk_get_hobject(ctx, 0); |
||||
|
DUK_ASSERT(func != NULL); |
||||
|
|
||||
|
duk_push_new_thread(ctx); |
||||
|
new_thr = (duk_hthread *) duk_get_hobject(ctx, -1); |
||||
|
DUK_ASSERT(new_thr != NULL); |
||||
|
new_thr->state = DUK_HTHREAD_STATE_INACTIVE; |
||||
|
|
||||
|
/* push initial function call to new thread stack; this is
|
||||
|
* picked up by resume(). |
||||
|
*/ |
||||
|
duk_push_hobject((duk_context *) new_thr, func); |
||||
|
|
||||
|
return 1; /* return thread */ |
||||
|
} |
||||
|
|
||||
|
/*
|
||||
|
* Resume a thread. |
||||
|
* |
||||
|
* The thread must be in resumable state, either (a) new thread which hasn't |
||||
|
* yet started, or (b) a thread which has previously yielded. This method |
||||
|
* must be called from an Ecmascript function. |
||||
|
* |
||||
|
* Args: |
||||
|
* - thread |
||||
|
* - value |
||||
|
* - isError (defaults to false) |
||||
|
* |
||||
|
* Note: yield and resume handling is currently asymmetric. |
||||
|
*/ |
||||
|
|
||||
|
int duk_builtin_duk_object_resume(duk_context *ctx) { |
||||
|
duk_hthread *thr = (duk_hthread *) ctx; |
||||
|
duk_hthread *thr_resume; |
||||
|
duk_tval tv_tmp; |
||||
|
duk_tval *tv; |
||||
|
duk_hobject *func; |
||||
|
int is_error; |
||||
|
|
||||
|
DUK_DDDPRINT("__duk__.resume(): thread=%!T, value=%!T, is_error=%!T", |
||||
|
duk_get_tval(ctx, 0), |
||||
|
duk_get_tval(ctx, 1), |
||||
|
duk_get_tval(ctx, 2)); |
||||
|
|
||||
|
DUK_ASSERT(thr->state == DUK_HTHREAD_STATE_RUNNING); |
||||
|
DUK_ASSERT(thr->heap->curr_thread == thr); |
||||
|
|
||||
|
thr_resume = duk_require_hthread(ctx, 0); |
||||
|
is_error = duk_to_boolean(ctx, 2); |
||||
|
|
||||
|
/*
|
||||
|
* Thread state and calling context checks |
||||
|
*/ |
||||
|
|
||||
|
if (thr->callstack_top < 2) { |
||||
|
DUK_DDPRINT("resume state invalid: callstack should contain at least 2 entries (caller and __duk__.resume)"); |
||||
|
goto state_error; |
||||
|
} |
||||
|
DUK_ASSERT((thr->callstack + thr->callstack_top - 1)->func != NULL); /* us */ |
||||
|
DUK_ASSERT(DUK_HOBJECT_IS_NATIVEFUNCTION((thr->callstack + thr->callstack_top - 1)->func)); |
||||
|
DUK_ASSERT((thr->callstack + thr->callstack_top - 2)->func != NULL); /* caller */ |
||||
|
|
||||
|
if (!DUK_HOBJECT_IS_COMPILEDFUNCTION((thr->callstack + thr->callstack_top - 2)->func)) { |
||||
|
DUK_DDPRINT("resume state invalid: caller must be Ecmascript code"); |
||||
|
goto state_error; |
||||
|
} |
||||
|
|
||||
|
/* Note: there is no requirement that: 'thr->callstack_preventcount == 1'
|
||||
|
* like for yield. |
||||
|
*/ |
||||
|
|
||||
|
if (thr_resume->state != DUK_HTHREAD_STATE_INACTIVE && |
||||
|
thr_resume->state != DUK_HTHREAD_STATE_YIELDED) { |
||||
|
DUK_DDPRINT("resume state invalid: target thread must be INACTIVE or YIELDED"); |
||||
|
goto state_error; |
||||
|
} |
||||
|
|
||||
|
DUK_ASSERT(thr_resume->state == DUK_HTHREAD_STATE_INACTIVE || |
||||
|
thr_resume->state == DUK_HTHREAD_STATE_YIELDED); |
||||
|
|
||||
|
/* Further state-dependent pre-checks */ |
||||
|
|
||||
|
if (thr_resume->state == DUK_HTHREAD_STATE_YIELDED) { |
||||
|
/* no pre-checks now, assume a previous yield() has left things in
|
||||
|
* tip-top shape (longjmp handler will assert for these). |
||||
|
*/ |
||||
|
} else { |
||||
|
DUK_ASSERT(thr_resume->state == DUK_HTHREAD_STATE_INACTIVE); |
||||
|
|
||||
|
if ((thr_resume->callstack_top != 0) || |
||||
|
(thr_resume->valstack_top - thr_resume->valstack != 1)) { |
||||
|
goto state_invalid_initial; |
||||
|
} |
||||
|
tv = &thr_resume->valstack_top[-1]; |
||||
|
DUK_ASSERT(tv >= thr_resume->valstack && tv < thr_resume->valstack_top); |
||||
|
if (!DUK_TVAL_IS_OBJECT(tv)) { |
||||
|
goto state_invalid_initial; |
||||
|
} |
||||
|
func = DUK_TVAL_GET_OBJECT(tv); |
||||
|
DUK_ASSERT(func != NULL); |
||||
|
if (!DUK_HOBJECT_IS_COMPILEDFUNCTION(func)) { |
||||
|
/* Note: cannot be a bound function either right now,
|
||||
|
* this would be easy to relax though. |
||||
|
*/ |
||||
|
goto state_invalid_initial; |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
|
||||
|
/*
|
||||
|
* The error object could conceivably contain either the resumer |
||||
|
* or the resumee callstack. Here we use the resumee's callstack |
||||
|
* which is more logical for code catching the error in the resumee. |
||||
|
*/ |
||||
|
#ifdef DUK_USE_AUGMENT_ERRORS |
||||
|
if (is_error) { |
||||
|
/* Note: may throw an error */ |
||||
|
duk_err_augment_error(thr, thr_resume, 1); |
||||
|
} |
||||
|
#endif |
||||
|
|
||||
|
#ifdef DUK_USE_DEBUG /* debug logging */ |
||||
|
if (is_error) { |
||||
|
DUK_DDDPRINT("RESUME ERROR: thread=%!T, value=%!T", |
||||
|
duk_get_tval(ctx, 0), |
||||
|
duk_get_tval(ctx, 1)); |
||||
|
} else if (thr_resume->state == DUK_HTHREAD_STATE_YIELDED) { |
||||
|
DUK_DDDPRINT("RESUME NORMAL: thread=%!T, value=%!T", |
||||
|
duk_get_tval(ctx, 0), |
||||
|
duk_get_tval(ctx, 1)); |
||||
|
} else { |
||||
|
DUK_DDDPRINT("RESUME INITIAL: thread=%!T, value=%!T", |
||||
|
duk_get_tval(ctx, 0), |
||||
|
duk_get_tval(ctx, 1)); |
||||
|
} |
||||
|
#endif |
||||
|
|
||||
|
thr->heap->lj.type = DUK_LJ_TYPE_RESUME; |
||||
|
|
||||
|
/* lj value2: thread */ |
||||
|
DUK_ASSERT(thr->valstack_bottom < thr->valstack_top); |
||||
|
DUK_TVAL_SET_TVAL(&tv_tmp, &thr->heap->lj.value2); |
||||
|
DUK_TVAL_SET_TVAL(&thr->heap->lj.value2, &thr->valstack_bottom[0]); |
||||
|
DUK_TVAL_INCREF(thr, &thr->heap->lj.value2); |
||||
|
DUK_TVAL_DECREF(thr, &tv_tmp); |
||||
|
|
||||
|
/* lj value1: value */ |
||||
|
DUK_ASSERT(thr->valstack_bottom + 1 < thr->valstack_top); |
||||
|
DUK_TVAL_SET_TVAL(&tv_tmp, &thr->heap->lj.value1); |
||||
|
DUK_TVAL_SET_TVAL(&thr->heap->lj.value1, &thr->valstack_bottom[1]); |
||||
|
DUK_TVAL_INCREF(thr, &thr->heap->lj.value1); |
||||
|
DUK_TVAL_DECREF(thr, &tv_tmp); |
||||
|
|
||||
|
thr->heap->lj.iserror = is_error; |
||||
|
|
||||
|
DUK_ASSERT(thr->heap->lj.jmpbuf_ptr != NULL); /* call is from executor, so we know we have a jmpbuf */ |
||||
|
duk_err_longjmp(thr); /* execution resumes in bytecode executor */ |
||||
|
return 0; /* never here */ |
||||
|
|
||||
|
state_invalid_initial: |
||||
|
DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, "invalid initial thread state/stack"); |
||||
|
return 0; /* never here */ |
||||
|
|
||||
|
state_error: |
||||
|
DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, "invalid state for resume"); |
||||
|
return 0; /* never here */ |
||||
|
} |
||||
|
|
||||
|
/*
|
||||
|
* Yield the current thread. |
||||
|
* |
||||
|
* The thread must be in yieldable state: it must have a resumer, and there |
||||
|
* must not be any yield-preventing calls (native calls and constructor calls, |
||||
|
* currently) in the thread's call stack (otherwise a resume would not be |
||||
|
* possible later). This method must be called from an Ecmascript function. |
||||
|
* |
||||
|
* Args: |
||||
|
* - value |
||||
|
* - isError (defaults to false) |
||||
|
* |
||||
|
* Note: yield and resume handling is currently asymmetric. |
||||
|
*/ |
||||
|
|
||||
|
int duk_builtin_duk_object_yield(duk_context *ctx) { |
||||
|
duk_hthread *thr = (duk_hthread *) ctx; |
||||
|
duk_tval tv_tmp; |
||||
|
int is_error; |
||||
|
|
||||
|
DUK_DDDPRINT("__duk__.yield(): value=%!T, is_error=%!T", |
||||
|
duk_get_tval(ctx, 0), |
||||
|
duk_get_tval(ctx, 1)); |
||||
|
|
||||
|
DUK_ASSERT(thr->state == DUK_HTHREAD_STATE_RUNNING); |
||||
|
DUK_ASSERT(thr->heap->curr_thread == thr); |
||||
|
|
||||
|
is_error = duk_to_boolean(ctx, 1); |
||||
|
|
||||
|
/*
|
||||
|
* Thread state and calling context checks |
||||
|
*/ |
||||
|
|
||||
|
if (!thr->resumer) { |
||||
|
DUK_DDPRINT("yield state invalid: current thread must have a resumer"); |
||||
|
goto state_error; |
||||
|
} |
||||
|
DUK_ASSERT(thr->resumer->state == DUK_HTHREAD_STATE_RESUMED); |
||||
|
|
||||
|
if (thr->callstack_top < 2) { |
||||
|
DUK_DDPRINT("yield state invalid: callstack should contain at least 2 entries (caller and __duk__.yield)"); |
||||
|
goto state_error; |
||||
|
} |
||||
|
DUK_ASSERT((thr->callstack + thr->callstack_top - 1)->func != NULL); /* us */ |
||||
|
DUK_ASSERT(DUK_HOBJECT_IS_NATIVEFUNCTION((thr->callstack + thr->callstack_top - 1)->func)); |
||||
|
DUK_ASSERT((thr->callstack + thr->callstack_top - 2)->func != NULL); /* caller */ |
||||
|
|
||||
|
if (!DUK_HOBJECT_IS_COMPILEDFUNCTION((thr->callstack + thr->callstack_top - 2)->func)) { |
||||
|
DUK_DDPRINT("yield state invalid: caller must be Ecmascript code"); |
||||
|
goto state_error; |
||||
|
} |
||||
|
|
||||
|
DUK_ASSERT(thr->callstack_preventcount >= 1); /* should never be zero, because we (__duk__.yield) are on the stack */ |
||||
|
if (thr->callstack_preventcount != 1) { |
||||
|
/* Note: the only yield-preventing call is __duk__.yield(), hence check for 1, not 0 */ |
||||
|
DUK_DDPRINT("yield state invalid: there must be no yield-preventing calls in current thread callstack (preventcount is %d)", |
||||
|
thr->callstack_preventcount); |
||||
|
goto state_error; |
||||
|
} |
||||
|
|
||||
|
/*
|
||||
|
* The error object could conceivably contain either the yielder |
||||
|
* or the resumer callstack. Here we use the resumer's callstack. |
||||
|
*/ |
||||
|
#ifdef DUK_USE_AUGMENT_ERRORS |
||||
|
if (is_error) { |
||||
|
/* Note: may throw an error */ |
||||
|
DUK_ASSERT(thr->resumer != NULL); |
||||
|
duk_err_augment_error(thr, thr->resumer, 0); |
||||
|
} |
||||
|
#endif |
||||
|
|
||||
|
#ifdef DUK_USE_DEBUG |
||||
|
if (is_error) { |
||||
|
DUK_DDDPRINT("YIELD ERROR: value=%!T", |
||||
|
duk_get_tval(ctx, 0)); |
||||
|
} else { |
||||
|
DUK_DDDPRINT("YIELD NORMAL: value=%!T", |
||||
|
duk_get_tval(ctx, 0)); |
||||
|
} |
||||
|
#endif |
||||
|
|
||||
|
/*
|
||||
|
* Process yield |
||||
|
* |
||||
|
* After longjmp(), processing continues in bytecode executor longjmp |
||||
|
* handler, which will e.g. update thr->resumer to NULL. |
||||
|
*/ |
||||
|
|
||||
|
thr->heap->lj.type = DUK_LJ_TYPE_YIELD; |
||||
|
|
||||
|
/* lj value1: value */ |
||||
|
DUK_ASSERT(thr->valstack_bottom < thr->valstack_top); |
||||
|
DUK_TVAL_SET_TVAL(&tv_tmp, &thr->heap->lj.value1); |
||||
|
DUK_TVAL_SET_TVAL(&thr->heap->lj.value1, &thr->valstack_bottom[0]); |
||||
|
DUK_TVAL_INCREF(thr, &thr->heap->lj.value1); |
||||
|
DUK_TVAL_DECREF(thr, &tv_tmp); |
||||
|
|
||||
|
thr->heap->lj.iserror = is_error; |
||||
|
|
||||
|
DUK_ASSERT(thr->heap->lj.jmpbuf_ptr != NULL); /* call is from executor, so we know we have a jmpbuf */ |
||||
|
duk_err_longjmp(thr); /* execution resumes in bytecode executor */ |
||||
|
return 0; /* never here */ |
||||
|
|
||||
|
state_error: |
||||
|
DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, "invalid state for yield"); |
||||
|
return 0; /* never here */ |
||||
|
} |
||||
|
|
||||
|
int duk_builtin_duk_object_curr(duk_context *ctx) { |
||||
|
duk_push_current_thread(ctx); |
||||
|
return 1; |
||||
|
} |
||||
|
|
||||
|
int duk_builtin_duk_object_print(duk_context *ctx) { |
||||
|
return DUK_RET_UNIMPLEMENTED_ERROR; /*FIXME*/ |
||||
|
} |
||||
|
|
||||
|
int duk_builtin_duk_object_time(duk_context *ctx) { |
||||
|
struct timeval tv; |
||||
|
|
||||
|
memset(&tv, 0, sizeof(tv)); |
||||
|
if (gettimeofday(&tv, NULL) != 0) { |
||||
|
return DUK_RET_ERROR; |
||||
|
} |
||||
|
duk_push_number(ctx, (double) tv.tv_sec + ((double) tv.tv_usec) / 1000000.0); |
||||
|
return 1; |
||||
|
} |
||||
|
|
||||
|
int duk_builtin_duk_object_enc(duk_context *ctx) { |
||||
|
duk_hthread *thr = (duk_hthread *) ctx; |
||||
|
duk_hstring *h_str; |
||||
|
|
||||
|
h_str = duk_to_hstring(ctx, 0); |
||||
|
if (h_str == DUK_HTHREAD_STRING_HEX(thr)) { |
||||
|
duk_hex_encode(ctx, 1); |
||||
|
DUK_ASSERT_TOP(ctx, 2); |
||||
|
return 1; |
||||
|
} else if (h_str == DUK_HTHREAD_STRING_BASE64(thr)) { |
||||
|
duk_base64_encode(ctx, 1); |
||||
|
DUK_ASSERT_TOP(ctx, 2); |
||||
|
return 1; |
||||
|
} else { |
||||
|
return DUK_RET_TYPE_ERROR; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
int duk_builtin_duk_object_dec(duk_context *ctx) { |
||||
|
duk_hthread *thr = (duk_hthread *) ctx; |
||||
|
duk_hstring *h_str; |
||||
|
|
||||
|
h_str = duk_to_hstring(ctx, 0); |
||||
|
if (h_str == DUK_HTHREAD_STRING_HEX(thr)) { |
||||
|
duk_hex_decode(ctx, 1); |
||||
|
DUK_ASSERT_TOP(ctx, 2); |
||||
|
return 1; |
||||
|
} else if (h_str == DUK_HTHREAD_STRING_BASE64(thr)) { |
||||
|
duk_base64_decode(ctx, 1); |
||||
|
DUK_ASSERT_TOP(ctx, 2); |
||||
|
return 1; |
||||
|
} else { |
||||
|
return DUK_RET_TYPE_ERROR; |
||||
|
} |
||||
|
} |
||||
|
|
@ -0,0 +1,214 @@ |
|||||
|
/*
|
||||
|
* Function built-ins |
||||
|
*/ |
||||
|
|
||||
|
#include "duk_internal.h" |
||||
|
|
||||
|
int duk_builtin_function_constructor(duk_context *ctx) { |
||||
|
if (duk_is_constructor_call(ctx)) { |
||||
|
return DUK_RET_UNIMPLEMENTED_ERROR; |
||||
|
} else { |
||||
|
return DUK_RET_UNIMPLEMENTED_ERROR; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
int duk_builtin_function_prototype(duk_context *ctx) { |
||||
|
/* ignore arguments, return undefined (E5 Section 15.3.4) */ |
||||
|
return 0; |
||||
|
} |
||||
|
|
||||
|
int duk_builtin_function_prototype_to_string(duk_context *ctx) { |
||||
|
duk_tval *tv; |
||||
|
|
||||
|
/* FIXME: faster internal way to get this */ |
||||
|
duk_push_this(ctx); |
||||
|
tv = duk_get_tval(ctx, -1); |
||||
|
DUK_ASSERT(tv != NULL); |
||||
|
|
||||
|
if (DUK_TVAL_IS_OBJECT(tv)) { |
||||
|
duk_hobject *obj = DUK_TVAL_GET_OBJECT(tv); |
||||
|
if (DUK_HOBJECT_HAS_COMPILEDFUNCTION(obj)) { |
||||
|
/* FIXME: actual source, if available */ |
||||
|
/* FIXME: function name */ |
||||
|
duk_push_string(ctx, "function name() {/* source code */}"); |
||||
|
} else if (DUK_HOBJECT_HAS_NATIVEFUNCTION(obj)) { |
||||
|
/* FIXME: function name */ |
||||
|
duk_push_string(ctx, "function name() {/* native code */}"); |
||||
|
} else if (DUK_HOBJECT_HAS_BOUND(obj)) { |
||||
|
duk_push_string(ctx, "function name() {/* bound */}"); |
||||
|
} else { |
||||
|
goto type_error; |
||||
|
} |
||||
|
} else { |
||||
|
goto type_error; |
||||
|
} |
||||
|
|
||||
|
return 1; |
||||
|
|
||||
|
type_error: |
||||
|
return DUK_RET_TYPE_ERROR; |
||||
|
} |
||||
|
|
||||
|
int duk_builtin_function_prototype_apply(duk_context *ctx) { |
||||
|
unsigned int len; |
||||
|
unsigned int i; |
||||
|
|
||||
|
/* FIXME: stack checks */ |
||||
|
|
||||
|
DUK_ASSERT(duk_get_top(ctx) == 2); /* not a vararg function */ |
||||
|
|
||||
|
duk_push_this(ctx); |
||||
|
if (!duk_is_callable(ctx, -1)) { |
||||
|
DUK_DDDPRINT("func is not callable"); |
||||
|
goto type_error; |
||||
|
} |
||||
|
duk_insert(ctx, 0); |
||||
|
DUK_ASSERT(duk_get_top(ctx) == 3); |
||||
|
|
||||
|
DUK_DDDPRINT("func=%!iT, thisArg=%!iT, argArray=%!iT", |
||||
|
duk_get_tval(ctx, 0), duk_get_tval(ctx, 1), duk_get_tval(ctx, 2)); |
||||
|
|
||||
|
/* [ func thisArg argArray ] */ |
||||
|
|
||||
|
if (duk_is_null_or_undefined(ctx, 2)) { |
||||
|
DUK_DDDPRINT("argArray is null/undefined, no args"); |
||||
|
len = 0; |
||||
|
} else if (!duk_is_object(ctx, 2)) { |
||||
|
goto type_error; |
||||
|
} else { |
||||
|
DUK_DDDPRINT("argArray is an object"); |
||||
|
|
||||
|
/* FIXME: make this an internal helper */ |
||||
|
duk_get_prop_stridx(ctx, 2, DUK_HEAP_STRIDX_LENGTH); |
||||
|
len = duk_to_uint32(ctx, -1); |
||||
|
duk_pop(ctx); |
||||
|
|
||||
|
duk_require_stack(ctx, len); /* FIXME: more? */ |
||||
|
|
||||
|
DUK_DDDPRINT("argArray length is %d", len); |
||||
|
for (i = 0; i < len; i++) { |
||||
|
duk_get_prop_index(ctx, 2, i); |
||||
|
} |
||||
|
} |
||||
|
duk_remove(ctx, 2); |
||||
|
DUK_ASSERT(duk_get_top(ctx) == 2 + len); |
||||
|
|
||||
|
/* [ func thisArg arg1 ... argN ] */ |
||||
|
|
||||
|
DUK_DDDPRINT("apply, func=%!iT, thisArg=%!iT, len=%d", |
||||
|
duk_get_tval(ctx, 0), duk_get_tval(ctx, 1), len); |
||||
|
duk_call_method(ctx, len); |
||||
|
return 1; |
||||
|
|
||||
|
type_error: |
||||
|
return DUK_RET_TYPE_ERROR; |
||||
|
} |
||||
|
|
||||
|
int duk_builtin_function_prototype_call(duk_context *ctx) { |
||||
|
int nargs; |
||||
|
|
||||
|
/* Step 1 is not necessary because duk_call_method() will take
|
||||
|
* care of it. |
||||
|
*/ |
||||
|
|
||||
|
/* vararg function, thisArg needs special handling */ |
||||
|
nargs = duk_get_top(ctx); /* = 1 + arg count */ |
||||
|
if (nargs == 0) { |
||||
|
duk_push_undefined(ctx); |
||||
|
nargs++; |
||||
|
} |
||||
|
DUK_ASSERT(nargs >= 1); |
||||
|
|
||||
|
/* [ thisArg arg1 ... argN ] */ |
||||
|
|
||||
|
duk_push_this(ctx); /* 'func' in the algorithm */ |
||||
|
duk_insert(ctx, 0); |
||||
|
|
||||
|
/* [ func thisArg arg1 ... argN ] */ |
||||
|
|
||||
|
DUK_DDDPRINT("func=%!iT, thisArg=%!iT, argcount=%d, top=%d", |
||||
|
duk_get_tval(ctx, 0), duk_get_tval(ctx, 1), nargs - 1, duk_get_top(ctx)); |
||||
|
duk_call_method(ctx, nargs - 1); |
||||
|
return 1; |
||||
|
} |
||||
|
|
||||
|
/* FIXME: the implementation now assumes "chained" bound functions,
|
||||
|
* whereas "collapsed" bound functions (where there is ever only |
||||
|
* one bound function which directly points to a non-bound, final |
||||
|
* function) would require a "collapsing" implementation which |
||||
|
* merges argument lists etc here. |
||||
|
*/ |
||||
|
int duk_builtin_function_prototype_bind(duk_context *ctx) { |
||||
|
duk_hobject *h_target; |
||||
|
int nargs; |
||||
|
int i; |
||||
|
|
||||
|
/* FIXME: stack checks */ |
||||
|
|
||||
|
/* vararg function, careful arg handling (e.g. thisArg may not be present) */ |
||||
|
nargs = duk_get_top(ctx); /* = 1 + arg count */ |
||||
|
if (nargs == 0) { |
||||
|
duk_push_undefined(ctx); |
||||
|
nargs++; |
||||
|
} |
||||
|
DUK_ASSERT(nargs >= 1); |
||||
|
|
||||
|
duk_push_this(ctx); |
||||
|
if (!duk_is_callable(ctx, -1)) { |
||||
|
DUK_DDDPRINT("func is not callable"); |
||||
|
goto type_error; |
||||
|
} |
||||
|
|
||||
|
/* [ thisArg arg1 ... argN func ] (thisArg+args == nargs total) */ |
||||
|
DUK_ASSERT(duk_get_top(ctx) == nargs + 1); |
||||
|
|
||||
|
/* create bound function object */ |
||||
|
duk_push_new_object_helper(ctx, |
||||
|
DUK_HOBJECT_FLAG_EXTENSIBLE | |
||||
|
DUK_HOBJECT_FLAG_BOUND | |
||||
|
DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_FUNCTION), |
||||
|
DUK_BIDX_FUNCTION_PROTOTYPE); |
||||
|
|
||||
|
/* FIXME: check hobject flags (e.g. strict) */ |
||||
|
|
||||
|
/* [ thisArg arg1 ... argN func boundFunc ] */ |
||||
|
duk_dup(ctx, -2); /* func */ |
||||
|
duk_def_prop_stridx(ctx, -2, DUK_HEAP_STRIDX_INT_TARGET, DUK_PROPDESC_FLAGS_NONE); |
||||
|
|
||||
|
duk_dup(ctx, 0); /* thisArg */ |
||||
|
duk_def_prop_stridx(ctx, -2, DUK_HEAP_STRIDX_INT_THIS, DUK_PROPDESC_FLAGS_NONE); |
||||
|
|
||||
|
duk_push_new_array(ctx); |
||||
|
|
||||
|
/* [ thisArg arg1 ... argN func boundFunc argArray ] */ |
||||
|
|
||||
|
for (i = 0; i < nargs - 1; i++) { |
||||
|
duk_dup(ctx, 1 + i); |
||||
|
duk_put_prop_index(ctx, -2, i); |
||||
|
} |
||||
|
duk_def_prop_stridx(ctx, -2, DUK_HEAP_STRIDX_INT_ARGS, DUK_PROPDESC_FLAGS_NONE); |
||||
|
|
||||
|
/* [ thisArg arg1 ... argN func boundFunc ] */ |
||||
|
|
||||
|
/* bound function 'length' property is interesting */ |
||||
|
h_target = duk_get_hobject(ctx, -2); |
||||
|
DUK_ASSERT(h_target != NULL); |
||||
|
if (DUK_HOBJECT_GET_CLASS_NUMBER(h_target) == DUK_HOBJECT_CLASS_FUNCTION) { |
||||
|
int tmp; |
||||
|
duk_get_prop_stridx(ctx, -2, DUK_HEAP_STRIDX_LENGTH); |
||||
|
tmp = duk_to_int(ctx, -1) - (nargs - 1); /* step 15.a */ |
||||
|
duk_pop(ctx); |
||||
|
duk_push_int(ctx, (tmp < 0 ? 0 : tmp)); |
||||
|
} else { |
||||
|
duk_push_int(ctx, 0); |
||||
|
} |
||||
|
duk_def_prop_stridx(ctx, -2, DUK_HEAP_STRIDX_LENGTH, DUK_PROPDESC_FLAGS_NONE); /* attrs in E5 Section 15.3.5.1 */ |
||||
|
|
||||
|
DUK_DDDPRINT("created bound function: %!iT", duk_get_tval(ctx, -1)); |
||||
|
|
||||
|
return 1; |
||||
|
|
||||
|
type_error: |
||||
|
return DUK_RET_TYPE_ERROR; |
||||
|
} |
||||
|
|
@ -0,0 +1,44 @@ |
|||||
|
/*
|
||||
|
* JSON built-ins |
||||
|
*/ |
||||
|
|
||||
|
/*
|
||||
|
* Serialization notes: |
||||
|
* |
||||
|
* - It would be nice to change the standard algorithm to be based around |
||||
|
* a "serializeValue()" primitive. The standard algorithm assumes access |
||||
|
* to the "holder" of the value, especially in E5 Section 15.12.3, Str() |
||||
|
* algoritm, step 3.a: the holder is passed to the ReplacerFunction. |
||||
|
* So, the implementation here is based on the standard algorithm set. |
||||
|
* |
||||
|
* - Similarly, serialization of a value 'val' begins from a dummy wrapper |
||||
|
* object: { "": val }. This seems to be quite awkward and unnecessary. |
||||
|
* However, the wrapper object is accessible to the ReplacerFunction! |
||||
|
* |
||||
|
* - String serialization should be fast for pure ASCII strings as they |
||||
|
* are very common. Unfortunately we may still need to escape characters |
||||
|
* in them, so there is no explicit fast path now. We could use ordinary |
||||
|
* character lookups during serialization (note that ASCII string lookups |
||||
|
* would not affect the stringcache). This would be quite slow, so we |
||||
|
* decode the extended UTF-8 directly instead. |
||||
|
* |
||||
|
* - Strings may contain non-BMP characters. These don't really need to be |
||||
|
* supported from a specification standpoint. We could encode them as |
||||
|
* surrogate pairs, but that would only work up to U+10FFFF, whereas e.g. |
||||
|
* regexp bytecode may contain much higher values. |
||||
|
* |
||||
|
* FIXME: surrogate pair + \uXXXX ? |
||||
|
* FIXME: custom escape: \UXXXXXXXX ? |
||||
|
* FIXME: non-escape hack: <U+XXXXXXXX> or something (not decodable) |
||||
|
*/ |
||||
|
|
||||
|
#include "duk_internal.h" |
||||
|
|
||||
|
int duk_builtin_json_object_parse(duk_context *ctx) { |
||||
|
return DUK_RET_UNIMPLEMENTED_ERROR; /*FIXME*/ |
||||
|
} |
||||
|
|
||||
|
int duk_builtin_json_object_stringify(duk_context *ctx) { |
||||
|
return DUK_RET_UNIMPLEMENTED_ERROR; /*FIXME*/ |
||||
|
} |
||||
|
|
Loading…
Reference in new issue