From 51bcd2ca1baca329ae515eb5e429a62101bd3189 Mon Sep 17 00:00:00 2001 From: Sami Vaarala Date: Sun, 3 Feb 2013 23:09:08 +0200 Subject: [PATCH] more incomplete builtins --- src/duk_builtin_array.c | 216 +++++++++++++++++++ src/duk_builtin_duk.c | 412 +++++++++++++++++++++++++++++++++++++ src/duk_builtin_function.c | 214 +++++++++++++++++++ src/duk_builtin_json.c | 44 ++++ 4 files changed, 886 insertions(+) create mode 100644 src/duk_builtin_array.c create mode 100644 src/duk_builtin_duk.c create mode 100644 src/duk_builtin_function.c create mode 100644 src/duk_builtin_json.c diff --git a/src/duk_builtin_array.c b/src/duk_builtin_array.c new file mode 100644 index 00000000..1d0f1e2f --- /dev/null +++ b/src/duk_builtin_array.c @@ -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*/ +} + diff --git a/src/duk_builtin_duk.c b/src/duk_builtin_duk.c new file mode 100644 index 00000000..36c4f13e --- /dev/null +++ b/src/duk_builtin_duk.c @@ -0,0 +1,412 @@ +/* + * __duk__ built-ins + */ + +#include + +#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; + } +} + diff --git a/src/duk_builtin_function.c b/src/duk_builtin_function.c new file mode 100644 index 00000000..3da30c23 --- /dev/null +++ b/src/duk_builtin_function.c @@ -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; +} + diff --git a/src/duk_builtin_json.c b/src/duk_builtin_json.c new file mode 100644 index 00000000..cc2638b1 --- /dev/null +++ b/src/duk_builtin_json.c @@ -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: 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*/ +} +