diff --git a/examples/npm/tar/index.js b/examples/npm/tar/index.js index a5eff40..47c5dbc 100644 --- a/examples/npm/tar/index.js +++ b/examples/npm/tar/index.js @@ -20,7 +20,7 @@ async function run() { console.log('FAIL — archive not created'); process.exit(1); } - console.log(' OK — archive created (%d bytes)', fs.statSync(archive).size); + console.log(` OK — archive created (${fs.statSync(archive).size} bytes)`); console.log('2. Extracting tarball...'); await tar.extract({ file: archive, cwd: dest }); diff --git a/include/ant.h b/include/ant.h index 67eae86..88652fb 100644 --- a/include/ant.h +++ b/include/ant.h @@ -90,6 +90,7 @@ const char *js_sym_key(ant_value_t sym); ant_value_t js_mksym_for(ant_t *, const char *key); ant_value_t js_symbol_to_string(ant_t *js, ant_value_t sym); ant_value_t js_get_sym(ant_t *, ant_value_t obj, ant_value_t sym); +ant_value_t js_get_sym_with_receiver(ant_t *, ant_value_t obj, ant_value_t sym, ant_value_t receiver); ant_value_t js_mkobj(ant_t *); ant_value_t js_mkobj_with_inobj_limit(ant_t *, uint8_t inobj_limit); @@ -142,9 +143,10 @@ typedef struct { } ant_iter_t; ant_iter_t js_prop_iter_begin(ant_t *js, ant_value_t obj); +void js_prop_iter_end(ant_iter_t *iter); bool js_prop_iter_next(ant_iter_t *iter, const char **key, size_t *key_len, ant_value_t *value); -void js_prop_iter_end(ant_iter_t *iter); +bool js_prop_iter_next_val(ant_iter_t *iter, ant_value_t *key_out, ant_value_t *value); ant_value_t js_obj_to_func(ant_value_t obj); ant_value_t js_obj_to_func_ex(ant_value_t obj, uint8_t flags); diff --git a/include/common.h b/include/common.h index d58bce5..94388f7 100644 --- a/include/common.h +++ b/include/common.h @@ -113,7 +113,8 @@ typedef enum { BRAND_WASM_MEMORY, BRAND_WASM_TABLE, BRAND_WASM_TAG, - BRAND_WASM_EXCEPTION + BRAND_WASM_EXCEPTION, + BRAND_DATE } object_brand_id_t; static inline void *mantissa_chk(void *p, const char *func) { diff --git a/include/gc/modules.h b/include/gc/modules.h index 6461fdf..cfb8126 100644 --- a/include/gc/modules.h +++ b/include/gc/modules.h @@ -30,6 +30,7 @@ void gc_mark_codec_streams(ant_t *js, gc_mark_fn mark); void gc_mark_compression_streams(ant_t *js, gc_mark_fn mark); void gc_mark_zlib(ant_t *js, gc_mark_fn mark); void gc_mark_wasm(ant_t *js, gc_mark_fn mark); +void gc_mark_napi(ant_t *js, gc_mark_fn mark); void gc_mark_abort_signal_object(ant_t *js, ant_value_t signal, gc_mark_fn mark); #endif diff --git a/include/modules/date.h b/include/modules/date.h index deeed4a..ed19c27 100644 --- a/include/modules/date.h +++ b/include/modules/date.h @@ -70,6 +70,7 @@ typedef struct { } date_method_entry_t; void init_date_module(void); +bool is_date_instance(ant_value_t value); ant_value_t get_date_string( ant_t *js, ant_value_t this_val, diff --git a/include/modules/events.h b/include/modules/events.h index b55b84a..8fe8388 100644 --- a/include/modules/events.h +++ b/include/modules/events.h @@ -15,10 +15,22 @@ bool eventemitter_add_listener( ant_value_t listener, bool once ); +bool eventemitter_add_listener_val( + ant_t *js, + ant_value_t target, ant_value_t key, + ant_value_t listener, bool once +); + bool eventemitter_emit_args( ant_t *js, ant_value_t target, const char *event_type, ant_value_t *args, int nargs ); +bool eventemitter_emit_args_val( + ant_t *js, + ant_value_t target, ant_value_t key, + ant_value_t *args, int nargs +); + #endif diff --git a/include/silver/opcode.h b/include/silver/opcode.h index c93f494..4735209 100644 --- a/include/silver/opcode.h +++ b/include/silver/opcode.h @@ -162,6 +162,7 @@ OP_DEF( TAIL_CALL, 3, 1, 0, npop) /* tail-position call */ OP_DEF( TAIL_CALL_METHOD, 3, 2, 0, npop) OP_DEF( NEW, 3, 2, 1, npop) /* func new.target args -> obj */ OP_DEF( APPLY, 3, 3, 1, u16) /* func this [args] -> result */ +OP_DEF( NEW_APPLY, 3, 2, 1, u16) /* func new.target [args] -> obj */ OP_DEF( EVAL, 5, 1, 1, npop) /* direct eval */ OP_DEF( RETURN, 1, 1, 0, none) OP_DEF( RETURN_UNDEF, 1, 0, 0, none) diff --git a/src/ant.c b/src/ant.c index a691753..a4210db 100644 --- a/src/ant.c +++ b/src/ant.c @@ -1140,9 +1140,7 @@ static bool is_small_object(ant_t *js, ant_value_t obj, int *prop_count) { // todo: split into smaller functions static size_t strobj(ant_t *js, ant_value_t obj, char *buf, size_t len) { - ant_value_t obj_proto = js_get_proto(js, obj); - ant_value_t date_proto = js_get_ctor_proto(js, "Date", 4); - if (obj_proto == date_proto) return strdate(js, obj, buf, len); + if (is_date_instance(obj)) return strdate(js, obj, buf, len); int ref = get_circular_ref(obj); if (ref) return ref > 0 ? (size_t) snprintf(buf, len, "[Circular *%d]", ref) : cpy(buf, len, "[Circular]", 10); @@ -2439,8 +2437,8 @@ static void intern_init(void) { ant_value_t mkprop(ant_t *js, ant_value_t obj, ant_value_t k, ant_value_t v, uint8_t attrs) { obj = js_as_obj(obj); ant_object_t *ptr = js_obj_ptr(obj); + if (!ptr || !ptr->shape) return js_mkerr(js, "invalid object"); - if (!attrs) attrs = ANT_PROP_ATTR_DEFAULT; uint32_t slot = 0; @@ -5432,6 +5430,37 @@ static ant_value_t for_in_keys_add_string_indices(ant_t *js, ant_value_t out, an return js_mkundef(); } +static inline ant_value_t for_in_keys_collect_chain( + ant_t *js, ant_value_t out, ant_value_t seen, ant_value_t obj +) { + ant_value_t cur = obj; + GC_ROOT_PIN(js, cur); + + for (int depth = 0; is_object_type(cur) && depth < MAX_PROTO_CHAIN_DEPTH; depth++) { + GC_ROOT_SAVE(iter_mark, js); + ant_value_t cur_keys = object_enum(js, cur, OBJ_ENUM_KEYS); + GC_ROOT_PIN(js, cur_keys); + + if (is_err(cur_keys)) { GC_ROOT_RESTORE(js, iter_mark); return cur_keys; } + if (vtype(cur_keys) != T_ARR) goto next; + + ant_offset_t len = js_arr_len(js, cur_keys); + for (ant_offset_t i = 0; i < len; i++) { + ant_value_t key = js_arr_get(js, cur_keys, i); + GC_ROOT_PIN(js, key); + ant_value_t r = for_in_keys_add(js, out, seen, key); + if (is_err(r)) { GC_ROOT_RESTORE(js, iter_mark); return r; } + } + +next: + GC_ROOT_RESTORE(js, iter_mark); + ant_value_t proto = js_get_proto(js, cur); + if (!is_object_type(proto)) break; + cur = proto; + } + return out; +} + ant_value_t js_for_in_keys(ant_t *js, ant_value_t obj) { GC_ROOT_SAVE(root_mark, js); uint8_t t = vtype(obj); @@ -5464,24 +5493,7 @@ ant_value_t js_for_in_keys(ant_t *js, ant_value_t obj) { } if (t != T_OBJ && t != T_ARR && t != T_FUNC) goto done; - ant_value_t own_keys = object_enum(js, obj, OBJ_ENUM_KEYS); - - GC_ROOT_PIN(js, own_keys); - if (is_err(own_keys)) { - result = own_keys; - goto done; - } - - if (vtype(own_keys) != T_ARR) goto done; - ant_offset_t own_len = js_arr_len(js, own_keys); - - for (ant_offset_t i = 0; i < own_len; i++) { - ant_value_t key = js_arr_get(js, own_keys, i); - GC_ROOT_PIN(js, key); - result = for_in_keys_add(js, out, seen, key); - if (is_err(result)) goto done; - result = out; - } + result = for_in_keys_collect_chain(js, out, seen, obj); done: GC_ROOT_RESTORE(js, root_mark); @@ -5684,6 +5696,7 @@ static ant_value_t builtin_object_groupBy(ant_t *js, ant_value_t *args, int narg return result; } +// TODO: decompose this huge function into small pieces static ant_value_t builtin_object_defineProperty(ant_t *js, ant_value_t *args, int nargs) { if (nargs < 3) return js_mkerr(js, "Object.defineProperty requires 3 arguments"); @@ -6003,6 +6016,24 @@ static ant_value_t builtin_object_defineProperties(ant_t *js, ant_value_t *args, return obj; } +static inline bool is_enumerable_prop( + ant_t *js, ant_value_t source, ant_object_t *source_ptr, + ant_value_t prop_key, uint32_t slot +) { + if (vtype(prop_key) == T_STR) { + size_t klen = 0; + + const char *kstr = js_getstr(js, prop_key, &klen); + if (is_internal_prop(kstr, klen)) return false; + + if (!source_ptr || source_ptr->is_exotic) { + descriptor_entry_t *desc = lookup_descriptor(js_as_obj(source), kstr, klen); + return !desc || desc->enumerable; + }} + + return (ant_shape_get_attrs(source_ptr->shape, slot) & ANT_PROP_ATTR_ENUMERABLE) != 0; +} + static ant_value_t builtin_object_assign(ant_t *js, ant_value_t *args, int nargs) { if (nargs == 0) return js_mkerr(js, "Object.assign requires at least 1 argument"); @@ -6027,33 +6058,15 @@ static ant_value_t builtin_object_assign(ant_t *js, ant_value_t *args, int nargs if (st != T_OBJ && st != T_ARR && st != T_FUNC) continue; ant_iter_t iter = js_prop_iter_begin(js, source); - const char *key = NULL; - size_t key_len = 0; + ant_object_t *source_ptr = js_obj_ptr(js_as_obj(source)); + + ant_value_t prop_key = js_mkundef(); ant_value_t val = js_mkundef(); - - while (js_prop_iter_next(&iter, &key, &key_len, &val)) { - if (is_internal_prop(key, key_len)) continue; - - ant_object_t *source_ptr = js_obj_ptr(js_as_obj(source)); - bool should_copy = true; - if (source_ptr && !source_ptr->is_exotic) { - const char *interned = intern_string(key, key_len); - if (interned && source_ptr->shape) { - int32_t slot = ant_shape_lookup_interned(source_ptr->shape, interned); - if (slot >= 0) { - should_copy = (ant_shape_get_attrs(source_ptr->shape, (uint32_t)slot) & ANT_PROP_ATTR_ENUMERABLE) != 0; - } - } - } else { - descriptor_entry_t *desc = lookup_descriptor(js_as_obj(source), key, key_len); - if (desc) should_copy = desc->enumerable; - } - - if (should_copy) { - ant_value_t key_str = js_mkstr(js, key, key_len); - js_setprop(js, as_obj, key_str, val); - } - } + + while (js_prop_iter_next_val(&iter, &prop_key, &val)) if ( + is_enumerable_prop(js, source, source_ptr, prop_key, (uint32_t)(iter.off - 1)) + ) js_setprop(js, as_obj, prop_key, val); + js_prop_iter_end(&iter); } @@ -7128,7 +7141,7 @@ static ant_value_t builtin_array_reduce(ant_t *js, ant_value_t *args, int nargs) return accumulator; } -static void flat_helper(ant_t *js, ant_value_t arr, ant_value_t result, ant_offset_t *result_idx, int depth) { +static inline void flat_helper(ant_t *js, ant_value_t arr, ant_value_t result, ant_offset_t *result_idx, int depth) { ant_offset_t len = get_array_length(js, arr); if (len == 0) return; @@ -7136,9 +7149,8 @@ static void flat_helper(ant_t *js, ant_value_t arr, ant_value_t result, ant_offs if (!arr_has(js, arr, i)) continue; ant_value_t val = arr_get(js, arr, i); - if (depth > 0 && (vtype(val) == T_ARR || vtype(val) == T_OBJ)) { - flat_helper(js, val, result, result_idx, depth - 1); - } else { arr_set(js, result, *result_idx, val); (*result_idx)++; } + if (depth > 0 && vtype(val) == T_ARR) flat_helper(js, val, result, result_idx, depth - 1); + else { arr_set(js, result, *result_idx, val); (*result_idx)++; } } } @@ -7352,18 +7364,8 @@ static ant_value_t builtin_array_flatMap(ant_t *js, ant_value_t *args, int nargs ant_value_t call_args[3] = { elem, tov((double)i), arr }; ant_value_t mapped = sv_vm_call(js->vm, js, callback, this_arg, call_args, 3, NULL, false); if (is_err(mapped)) return mapped; - - if (vtype(mapped) == T_ARR || vtype(mapped) == T_OBJ) { - ant_offset_t m_len = get_array_length(js, mapped); - for (ant_offset_t j = 0; j < m_len; j++) { - ant_value_t m_elem = arr_get(js, mapped, j); - arr_set(js, result, result_idx, m_elem); - result_idx++; - } - } else { - arr_set(js, result, result_idx, mapped); - result_idx++; - } + if (vtype(mapped) == T_ARR) flat_helper(js, mapped, result, &result_idx, 0); + else arr_set(js, result, result_idx++, mapped); } return mkval(T_ARR, vdata(result)); @@ -12858,30 +12860,28 @@ void js_set_sym(ant_t *js, ant_value_t obj, ant_value_t sym, ant_value_t val) { } else mkprop(js, obj, sym, val, 0); } -ant_value_t js_get_sym(ant_t *js, ant_value_t obj, ant_value_t sym) { +ant_value_t js_get_sym_with_receiver(ant_t *js, ant_value_t obj, ant_value_t sym, ant_value_t receiver) { if (vtype(sym) != T_SYMBOL) return js_mkundef(); ant_offset_t sym_off = (ant_offset_t)vdata(sym); - - ant_value_t receiver = obj; + if (vtype(obj) == T_FUNC) obj = js_func_obj(obj); uint8_t ot = vtype(obj); + if (!is_object_type(obj)) { if (ot == T_STR || ot == T_NUM || ot == T_BOOL || ot == T_BIGINT || ot == T_SYMBOL) { ant_value_t proto = get_prototype_for_type(js, ot); if (!is_object_type(proto)) return js_mkundef(); obj = js_as_obj(proto); - } else { - return js_mkundef(); - } - } else { - obj = js_as_obj(obj); - } - - if (is_proxy(obj)) return proxy_get_val(js, obj, sym); - + } else return js_mkundef(); + } else obj = js_as_obj(obj); + + if (is_proxy(obj)) + return proxy_get_val(js, obj, sym); + ant_value_t cur = obj; proto_overflow_guard_t guard; proto_overflow_guard_init(&guard); + while (is_object_type(cur)) { ant_value_t cur_obj = js_as_obj(cur); prop_meta_t meta; @@ -12907,6 +12907,10 @@ ant_value_t js_get_sym(ant_t *js, ant_value_t obj, ant_value_t sym) { return propref_load(js, off); } +ant_value_t js_get_sym(ant_t *js, ant_value_t obj, ant_value_t sym) { + return js_get_sym_with_receiver(js, obj, sym, obj); +} + static bool js_try_get(ant_t *js, ant_value_t obj, const char *key, ant_value_t *out) { size_t key_len = strlen(key); @@ -13291,39 +13295,37 @@ ant_value_t sv_call_native( return js_mkerr_typed(js, JS_ERR_TYPE, "%s is not a function", typestr(vtype(func))); } -ant_iter_t js_prop_iter_begin(ant_t *js, ant_value_t obj) { - typedef struct { - ant_t *js; - ant_object_t *obj; - uint32_t index; - } prop_iter_ctx_t; +typedef struct { + ant_t *js; + ant_object_t *obj; + uint32_t index; +} prop_iter_ctx_t; +ant_iter_t js_prop_iter_begin(ant_t *js, ant_value_t obj) { ant_iter_t iter = {.ctx = NULL, .off = 0}; uint8_t t = vtype(obj); if (t != T_OBJ && t != T_ARR && t != T_FUNC) return iter; prop_iter_ctx_t *ctx = calloc(1, sizeof(*ctx)); if (!ctx) return iter; + ctx->js = js; ctx->obj = js_obj_ptr(js_as_obj(obj)); ctx->index = 0; + if (!ctx->obj || !ctx->obj->shape) { free(ctx); return iter; } + iter.ctx = ctx; return iter; } bool js_prop_iter_next(ant_iter_t *iter, const char **key, size_t *key_len, ant_value_t *value) { - typedef struct { - ant_t *js; - ant_object_t *obj; - uint32_t index; - } prop_iter_ctx_t; - if (!iter || !iter->ctx) return false; prop_iter_ctx_t *ctx = (prop_iter_ctx_t *)iter->ctx; + ant_object_t *obj = ctx->obj; if (!obj || !obj->shape) return false; @@ -13339,8 +13341,39 @@ bool js_prop_iter_next(ant_iter_t *iter, const char **key, size_t *key_len, ant_ *key = prop->key.interned; if (key_len) *key_len = strlen(prop->key.interned); } + if (value) *value = ant_object_prop_get_unchecked(obj, i); iter->off = i + 1; + + return true; + } + + return false; +} + +bool js_prop_iter_next_val(ant_iter_t *iter, ant_value_t *key_out, ant_value_t *value) { + if (!iter || !iter->ctx) return false; + prop_iter_ctx_t *ctx = (prop_iter_ctx_t *)iter->ctx; + + ant_object_t *obj = ctx->obj; + if (!obj || !obj->shape) return false; + uint32_t count = ant_shape_count(obj->shape); + + while (ctx->index < count) { + uint32_t i = ctx->index++; + const ant_shape_prop_t *prop = ant_shape_prop_at(obj->shape, i); + + if (!prop) continue; + if (i >= obj->prop_count) continue; + + if (key_out) { + if (prop->type == ANT_SHAPE_KEY_SYMBOL) *key_out = mkval(T_SYMBOL, prop->key.sym_off); + else *key_out = js_mkstr(ctx->js, prop->key.interned, strlen(prop->key.interned)); + } + + if (value) *value = ant_object_prop_get_unchecked(obj, i); + iter->off = i + 1; + return true; } diff --git a/src/gc/objects.c b/src/gc/objects.c index c045900..a489f31 100644 --- a/src/gc/objects.c +++ b/src/gc/objects.c @@ -122,31 +122,10 @@ void gc_remember_add(ant_t *js, ant_object_t *obj) { } #define GC_MARK_STACK_INIT 4096 -void gc_mark_value(ant_t *js, ant_value_t v); - -static inline void gc_mark_promise_handler(ant_t *js, const promise_handler_t *h) { - if (!h) return; - gc_mark_value(js, h->onFulfilled); - gc_mark_value(js, h->onRejected); - gc_mark_value(js, h->nextPromise); -} - -static inline void gc_mark_promise_handlers(ant_t *js, ant_promise_state_t *pd) { - if (!pd) return; - - if (pd->handler_count == 1) { - gc_mark_promise_handler(js, &pd->inline_handler); - return; - } - - if (pd->handler_count <= 1 || !pd->handlers) return; - - promise_handler_t *h = NULL; - while ((h = (promise_handler_t *)utarray_next(pd->handlers, h))) - gc_mark_promise_handler(js, h); -} static ant_object_t **gc_mark_stack = NULL; +static void gc_mark_promise_handlers(ant_t *js, ant_promise_state_t *pd); + static size_t gc_mark_sp = 0; static size_t gc_mark_cap = 0; @@ -259,6 +238,14 @@ static void gc_scan_obj(ant_t *js, ant_object_t *obj) { gc_mark_value(js, e->key_val); gc_mark_value(js, e->value); }} + } else if (obj->type_tag == T_WEAKMAP) { + weakmap_entry_t **head = (weakmap_entry_t **)(uintptr_t)js_getnum(obj->u.data.value); + if (head) { + weakmap_entry_t *e, *tmp; + HASH_ITER(hh, *head, e, tmp) { + gc_mark_value(js, e->key_obj); + gc_mark_value(js, e->value); + }} } else if (obj->type_tag == T_SET) { set_entry_t **head = (set_entry_t **)(uintptr_t)js_getnum(obj->u.data.value); if (head) { @@ -433,6 +420,31 @@ static void gc_mark_coroutine(ant_t *js, coroutine_t *c) { gc_mark_value(js, c->new_target); } +static inline void gc_mark_promise_handler(ant_t *js, const promise_handler_t *h) { + if (!h) return; + + gc_mark_value(js, h->onFulfilled); + gc_mark_value(js, h->onRejected); + gc_mark_value(js, h->nextPromise); + + if (h->await_coro) gc_mark_coroutine(js, h->await_coro); +} + +static inline void gc_mark_promise_handlers(ant_t *js, ant_promise_state_t *pd) { + if (!pd) return; + + if (pd->handler_count == 1) { + gc_mark_promise_handler(js, &pd->inline_handler); + return; + } + + if (pd->handler_count <= 1 || !pd->handlers) return; + promise_handler_t *h = NULL; + + while ((h = (promise_handler_t *)utarray_next(pd->handlers, h))) + gc_mark_promise_handler(js, h); +} + static void gc_mark_roots(ant_t *js) { gc_scan_vm_stack(js, js->vm); @@ -486,6 +498,7 @@ static void gc_mark_roots(ant_t *js) { gc_mark_compression_streams(js, gc_mark_value); gc_mark_zlib(js, gc_mark_value); gc_mark_wasm(js, gc_mark_value); + gc_mark_napi(js, gc_mark_value); for ( ant_object_t *obj = g_pending_promises; diff --git a/src/modules/collections.c b/src/modules/collections.c index 0490636..0ec988c 100644 --- a/src/modules/collections.c +++ b/src/modules/collections.c @@ -565,6 +565,12 @@ static ant_value_t weakmap_set(ant_t *js, ant_value_t *args, int nargs) { HASH_ADD(hh, *wm_ptr, key_obj, sizeof(ant_value_t), entry); } + ant_object_t *wm_obj = js_obj_ptr(this_val); + if (wm_obj) { + gc_write_barrier(js, wm_obj, key_obj); + gc_write_barrier(js, wm_obj, args[1]); + } + return this_val; } diff --git a/src/modules/date.c b/src/modules/date.c index f1089ed..ba65b3e 100644 --- a/src/modules/date.c +++ b/src/modules/date.c @@ -80,18 +80,14 @@ static inline bool date_is_primitive(ant_value_t v) { || t == T_BIGINT; } -static bool is_date_instance(ant_t *js, ant_value_t value) { +bool is_date_instance(ant_value_t value) { if (!is_object_type(value)) return false; - ant_value_t date_proto = js_get_ctor_proto(js, "Date", 4); - - if (!is_object_type(date_proto)) return false; - if (value == date_proto) return true; - - return proto_chain_contains(js, value, date_proto); + ant_value_t brand = js_get_slot(js_as_obj(value), SLOT_BRAND); + return vtype(brand) == T_NUM && (int)js_getnum(brand) == BRAND_DATE; } static bool date_this_time_value(ant_t *js, ant_value_t this_val, double *out, ant_value_t *err) { - if (!is_date_instance(js, this_val)) { + if (!is_date_instance(this_val)) { if (err) *err = js_mkerr_typed(js, JS_ERR_TYPE, "not a Date object"); return false; } @@ -102,7 +98,7 @@ static bool date_this_time_value(ant_t *js, ant_value_t this_val, double *out, a } static ant_value_t date_set_this_time_value(ant_t *js, ant_value_t this_val, double v) { - if (!is_date_instance(js, this_val)) { + if (!is_date_instance(this_val)) { return js_mkerr_typed(js, JS_ERR_TYPE, "not a Date object"); } @@ -763,9 +759,11 @@ static bool date_parse_string_to_ms(ant_t *js, ant_value_t arg, double *out_ms) int fields[9]; date_fields_t fields1 = {0}; + bool is_local = false; - bool ok = parse_isostring((const uint8_t *)buf, fields, &is_local) - || parse_otherstring((const uint8_t *)buf, fields, &is_local); + bool ok = + parse_isostring((const uint8_t *)buf, fields, &is_local) || + parse_otherstring((const uint8_t *)buf, fields, &is_local); free(buf); @@ -795,6 +793,7 @@ static bool date_parse_string_to_ms(ant_t *js, ant_value_t arg, double *out_ms) fields1.second = (double)fields[5]; fields1.millisecond = (double)fields[6]; *out_ms = date_make_fields(&fields1, is_local ? 1 : 0) - (double)fields[8] * 60000.0; + return true; } @@ -818,8 +817,8 @@ static ant_value_t builtin_Date(ant_t *js, ant_value_t *args, int nargs) { val = (double)date_now(); } else if (nargs == 1) { ant_value_t input = args[0]; - - if (is_object_type(input) && is_date_instance(js, input)) { + + if (is_object_type(input) && is_date_instance(input)) { ant_value_t tv = js_get_slot(js_as_obj(input), SLOT_DATA); val = (vtype(tv) == T_NUM) ? tod(tv) : JS_NAN; val = date_timeclip(val); @@ -828,9 +827,7 @@ static ant_value_t builtin_Date(ant_t *js, ant_value_t *args, int nargs) { if (is_err(prim)) return prim; if (vtype(prim) == T_STR) { if (!date_parse_string_to_ms(js, prim, &val)) return js_mkerr_typed(js, JS_ERR_INTERNAL, "Date parse failed"); - } else { - val = js_to_number(js, prim); - } + } else val = js_to_number(js, prim); val = date_timeclip(val); } } else { @@ -852,6 +849,8 @@ static ant_value_t builtin_Date(ant_t *js, ant_value_t *args, int nargs) { } js_set_slot(js_as_obj(js->this_val), SLOT_DATA, tov(val)); + js_set_slot(js_as_obj(js->this_val), SLOT_BRAND, js_mknum(BRAND_DATE)); + return js->this_val; } diff --git a/src/modules/events.c b/src/modules/events.c index 6e8ef8d..9e3b7e5 100644 --- a/src/modules/events.c +++ b/src/modules/events.c @@ -56,9 +56,17 @@ static const UT_icd event_listener_icd = { }; typedef struct { - UT_array *listeners; - char *event_type; - UT_hash_handle hh; + unsigned char buf[48]; + unsigned char *ptr; + size_t len; +} evt_key_t; + +typedef struct { + UT_array *listeners; + ant_value_t js_key; + unsigned char *hash_key; + size_t hash_key_len; + UT_hash_handle hh; } EventType; typedef struct emitter_reg { @@ -69,16 +77,73 @@ typedef struct emitter_reg { static EventType *global_events = NULL; static emitter_reg_t *emitter_registry = NULL; -static EventType *make_event_type(const char *event_type) { - size_t etlen = strlen(event_type); - EventType *evt = ant_calloc(sizeof(EventType) + etlen + 1); +static EventType *make_event_type(ant_value_t js_key, const evt_key_t *ek) { + EventType *evt = ant_calloc(sizeof(EventType) + ek->len); if (!evt) return NULL; - evt->event_type = (char *)(evt + 1); - memcpy(evt->event_type, event_type, etlen + 1); + evt->js_key = js_key; + evt->hash_key = (unsigned char *)(evt + 1); + evt->hash_key_len = ek->len; + memcpy(evt->hash_key, ek->ptr, ek->len); utarray_new(evt->listeners, &event_listener_icd); return evt; } +static EventType *evt_find(EventType *table, const evt_key_t *ek) { + EventType *evt = NULL; + HASH_FIND(hh, table, ek->ptr, ek->len, evt); + return evt; +} + +static EventType *evt_find_or_create(EventType **table, ant_value_t js_key, const evt_key_t *ek) { + EventType *evt = evt_find(*table, ek); + if (!evt) { + evt = make_event_type(js_key, ek); + if (!evt) return NULL; + HASH_ADD_KEYPTR(hh, *table, evt->hash_key, evt->hash_key_len, evt); + } + return evt; +} + +static void evt_key_reset(evt_key_t *k) { + k->ptr = k->buf; + k->len = 0; +} + +static void evt_key_free(evt_key_t *k) { + if (k->ptr != k->buf) free(k->ptr); + evt_key_reset(k); +} + +static bool evt_key_init(ant_t *js, ant_value_t arg, evt_key_t *out) { + evt_key_reset(out); + uint8_t tag = (uint8_t)vtype(arg); + + if (tag == T_STR) { + size_t slen = 0; + const char *s = js_getstr(js, arg, &slen); + out->len = 1 + slen; + + if (out->len > sizeof(out->buf)) { + out->ptr = malloc(out->len); + if (!out->ptr) { evt_key_reset(out); return false; } + } + + out->ptr[0] = tag; + if (slen) memcpy(out->ptr + 1, s, slen); + + return true; + } + + if (tag == T_SYMBOL) { + out->len = 1 + sizeof(ant_value_t); + out->ptr[0] = tag; + memcpy(out->ptr + 1, &arg, sizeof(ant_value_t)); + return true; + } + + return false; +} + static EventType **get_or_create_emitter_events(ant_t *js, ant_value_t this_obj) { ant_value_t slot = js_get_slot(this_obj, SLOT_DATA); if (vtype(slot) == T_UNDEF) { @@ -98,44 +163,47 @@ static EventType **get_or_create_emitter_events(ant_t *js, ant_value_t this_obj) return (EventType **)(uintptr_t)js_getnum(slot); } -static EventType *find_or_create_global_event_type(const char *event_type) { - EventType *evt = NULL; - HASH_FIND_STR(global_events, event_type, evt); - if (!evt) { - evt = make_event_type(event_type); - if (!evt) return NULL; - HASH_ADD_KEYPTR(hh, global_events, evt->event_type, strlen(evt->event_type), evt); - } +static EventType *find_or_create_global_event_type(ant_t *js, ant_value_t js_key) { + evt_key_t ek; + if (!evt_key_init(js, js_key, &ek)) return NULL; + EventType *evt = evt_find_or_create(&global_events, js_key, &ek); + evt_key_free(&ek); return evt; } -static EventType *find_global_event_type(const char *event_type) { - EventType *evt = NULL; - HASH_FIND_STR(global_events, event_type, evt); +static EventType *find_global_event_type(ant_t *js, ant_value_t js_key) { + evt_key_t ek; + if (!evt_key_init(js, js_key, &ek)) return NULL; + EventType *evt = evt_find(global_events, &ek); + evt_key_free(&ek); return evt; } -static EventType *find_or_create_emitter_event_type(ant_t *js, ant_value_t this_obj, const char *event_type) { +static EventType *find_or_create_emitter_event_type(ant_t *js, ant_value_t this_obj, ant_value_t js_key) { EventType **events = get_or_create_emitter_events(js, this_obj); if (!events) return NULL; - EventType *evt = NULL; - HASH_FIND_STR(*events, event_type, evt); - if (!evt) { - evt = make_event_type(event_type); - if (!evt) return NULL; - HASH_ADD_KEYPTR(hh, *events, evt->event_type, strlen(evt->event_type), evt); - } + evt_key_t ek; + if (!evt_key_init(js, js_key, &ek)) return NULL; + EventType *evt = evt_find_or_create(events, js_key, &ek); + evt_key_free(&ek); return evt; } -static EventType *find_emitter_event_type(ant_t *js, ant_value_t this_obj, const char *event_type) { +static EventType *find_emitter_event_type(ant_t *js, ant_value_t this_obj, ant_value_t js_key) { EventType **events = get_or_create_emitter_events(js, this_obj); if (!events) return NULL; - EventType *evt = NULL; - HASH_FIND_STR(*events, event_type, evt); + evt_key_t ek; + if (!evt_key_init(js, js_key, &ek)) return NULL; + EventType *evt = evt_find(*events, &ek); + evt_key_free(&ek); return evt; } +static inline ant_value_t evt_key_from_arg(ant_value_t arg) { + uint8_t t = vtype(arg); + return (t == T_STR || t == T_SYMBOL) ? arg : 0; +} + static void js_init_event_obj(ant_t *js, ant_value_t obj, ant_value_t type_val, bool bubbles, bool cancelable) { js_set(js, obj, "type", type_val); js_set(js, obj, "target", js_mknull()); @@ -483,76 +551,73 @@ static ant_value_t dispatch_event_to(ant_t *js, ant_value_t event_obj, EventType static ant_value_t js_add_event_listener_method(ant_t *js, ant_value_t *args, int nargs) { if (nargs < 1) return js_mkundef(); - const char *event_type = js_getstr(js, args[0], NULL); - if (!event_type) return js_mkundef(); + ant_value_t key = evt_key_from_arg(args[0]); + if (!key) return js_mkundef(); return add_listener_to(js, args, nargs, - find_or_create_emitter_event_type(js, js_getthis(js), event_type)); + find_or_create_emitter_event_type(js, js_getthis(js), key)); } static ant_value_t js_add_event_listener(ant_t *js, ant_value_t *args, int nargs) { if (nargs < 1) return js_mkundef(); - const char *event_type = js_getstr(js, args[0], NULL); - if (!event_type) return js_mkundef(); - return add_listener_to(js, args, nargs, find_or_create_global_event_type(event_type)); + ant_value_t key = evt_key_from_arg(args[0]); + if (!key) return js_mkundef(); + return add_listener_to(js, args, nargs, find_or_create_global_event_type(js, key)); } static ant_value_t js_remove_event_listener_method(ant_t *js, ant_value_t *args, int nargs) { if (nargs < 1) return js_mkundef(); - const char *event_type = js_getstr(js, args[0], NULL); - if (!event_type) return js_mkundef(); + ant_value_t key = evt_key_from_arg(args[0]); + if (!key) return js_mkundef(); return remove_listener_from(js, args, nargs, - find_emitter_event_type(js, js_getthis(js), event_type)); + find_emitter_event_type(js, js_getthis(js), key)); } static ant_value_t js_remove_event_listener(ant_t *js, ant_value_t *args, int nargs) { if (nargs < 1) return js_mkundef(); - const char *event_type = js_getstr(js, args[0], NULL); - if (!event_type) return js_mkundef(); - return remove_listener_from(js, args, nargs, find_global_event_type(event_type)); + ant_value_t key = evt_key_from_arg(args[0]); + if (!key) return js_mkundef(); + return remove_listener_from(js, args, nargs, find_global_event_type(js, key)); } static ant_value_t js_dispatch_event_method(ant_t *js, ant_value_t *args, int nargs) { if (nargs < 1 || !is_object_type(args[0])) return js_false; ant_value_t this_obj = js_getthis(js); - ant_value_t type_val = js_get(js, args[0], "type"); - const char *event_type = js_getstr(js, type_val, NULL); - if (!event_type) return js_false; + ant_value_t key = js_get(js, args[0], "type"); + if (!evt_key_from_arg(key)) return js_false; return dispatch_event_to(js, args[0], - find_emitter_event_type(js, this_obj, event_type), this_obj); + find_emitter_event_type(js, this_obj, key), this_obj); } static ant_value_t js_dispatch_event(ant_t *js, ant_value_t *args, int nargs) { if (nargs < 1 || !is_object_type(args[0])) return js_false; - ant_value_t type_val = js_get(js, args[0], "type"); - const char *event_type = js_getstr(js, type_val, NULL); - if (!event_type) return js_false; + ant_value_t key = js_get(js, args[0], "type"); + if (!evt_key_from_arg(key)) return js_false; return dispatch_event_to(js, args[0], - find_global_event_type(event_type), js_glob(js)); + find_global_event_type(js, key), js_glob(js)); } void js_dispatch_global_event(ant_t *js, ant_value_t event_obj) { - ant_value_t type_val = js_get(js, event_obj, "type"); - const char *event_type = js_getstr(js, type_val, NULL); - if (!event_type) return; - EventType *evt = find_global_event_type(event_type); + ant_value_t key = js_get(js, event_obj, "type"); + if (!evt_key_from_arg(key)) return; + EventType *evt = find_global_event_type(js, key); if (!evt || utarray_len(evt->listeners) == 0) return; dispatch_event_to(js, event_obj, evt, js_glob(js)); } static bool eventemitter_add_listener_impl( ant_t *js, - ant_value_t target, const char *event_type, + ant_value_t target, ant_value_t key, ant_value_t listener, bool once, bool prepend ) { EventType *evt = NULL; EventListenerEntry entry = {0}; uint8_t t = 0; - if (!is_object_type(target) || !event_type) return false; + if (!is_object_type(target) || !key) return false; t = vtype(listener); if (t != T_FUNC && t != T_CFUNC) return false; - evt = find_or_create_emitter_event_type(js, target, event_type); + evt = find_or_create_emitter_event_type(js, target, key); if (!evt) return false; entry.callback = listener; @@ -574,14 +639,14 @@ static bool eventemitter_add_listener_impl( static ant_value_t js_eventemitter_off(ant_t *js, ant_value_t *args, int nargs) { if (nargs < 2) return js_mkerr(js, "off requires 2 arguments (event, listener)"); - const char *event_type = js_getstr(js, args[0], NULL); + ant_value_t key = evt_key_from_arg(args[0]); - if (!event_type) return js_mkerr(js, "event must be a string"); + if (!key) return js_mkerr(js, "event must be a string or Symbol"); ant_value_t this_obj = js_getthis(js); remove_listener_from( js, args, nargs, - find_emitter_event_type(js, this_obj, event_type) + find_emitter_event_type(js, this_obj, key) ); return this_obj; @@ -589,13 +654,13 @@ static ant_value_t js_eventemitter_off(ant_t *js, ant_value_t *args, int nargs) static bool eventemitter_emit_args_impl( ant_t *js, - ant_value_t target, const char *event_type, + ant_value_t target, ant_value_t key, ant_value_t *args, int nargs ) { EventType *evt = NULL; - if (!is_object_type(target) || !event_type) return false; - evt = find_emitter_event_type(js, target, event_type); + if (!is_object_type(target) || !key) return false; + evt = find_emitter_event_type(js, target, key); if (!evt || utarray_len(evt->listeners) == 0) return false; for (unsigned int i = 0; i < utarray_len(evt->listeners);) { @@ -610,31 +675,48 @@ static bool eventemitter_emit_args_impl( if (vtype(cb) != T_FUNC && vtype(cb) != T_CFUNC) continue; ant_value_t result = sv_vm_call(js->vm, js, cb, js_mkundef(), args, nargs, NULL, false); - if (vtype(result) == T_ERR) fprintf(stderr, "Error in event listener for '%s': %s\n", event_type, js_str(js, result)); + if (vtype(result) == T_ERR) { + if (vtype(key) == T_STR) fprintf(stderr, "Error in event listener for '%s': %s\n", js_str(js, key), js_str(js, result)); + else fprintf(stderr, "Error in event listener: %s\n", js_str(js, result)); + } } return true; } static ant_value_t js_eventemitter_emit(ant_t *js, ant_value_t *args, int nargs) { - const char *event_type = NULL; - if (nargs < 1) return js_mkerr(js, "emit requires at least 1 argument (event)"); - event_type = js_getstr(js, args[0], NULL); - if (!event_type) return js_mkerr(js, "event must be a string"); + ant_value_t key = evt_key_from_arg(args[0]); + if (!key) return js_mkerr(js, "event must be a string or Symbol"); return js_bool(eventemitter_emit_args_impl( - js, js_getthis(js), event_type, + js, js_getthis(js), key, nargs > 1 ? &args[1] : NULL, nargs - 1 )); } +bool eventemitter_emit_args_val( + ant_t *js, + ant_value_t target, ant_value_t key, + ant_value_t *args, int nargs +) { + return eventemitter_emit_args_impl(js, target, key, args, nargs); +} + bool eventemitter_emit_args( ant_t *js, ant_value_t target, const char *event_type, ant_value_t *args, int nargs ) { - return eventemitter_emit_args_impl(js, target, event_type, args, nargs); + return eventemitter_emit_args_val(js, target, js_mkstr(js, event_type, strlen(event_type)), args, nargs); +} + +bool eventemitter_add_listener_val( + ant_t *js, + ant_value_t target, ant_value_t key, + ant_value_t listener, bool once +) { + return eventemitter_add_listener_impl(js, target, key, listener, once, false); } bool eventemitter_add_listener( @@ -642,17 +724,15 @@ bool eventemitter_add_listener( ant_value_t target, const char *event_type, ant_value_t listener, bool once ) { - return eventemitter_add_listener_impl(js, target, event_type, listener, once, false); + return eventemitter_add_listener_val(js, target, js_mkstr(js, event_type, strlen(event_type)), listener, once); } static ant_value_t js_eventemitter_add(ant_t *js, ant_value_t *args, int nargs, bool once, bool prepend) { - const char *event_type = NULL; - if (nargs < 2) return js_mkerr(js, "requires 2 arguments (event, listener)"); - event_type = js_getstr(js, args[0], NULL); + ant_value_t key = evt_key_from_arg(args[0]); - if (!event_type) return js_mkerr(js, "event must be a string"); - if (!eventemitter_add_listener_impl(js, js_getthis(js), event_type, args[1], once, prepend)) + if (!key) return js_mkerr(js, "event must be a string or Symbol"); + if (!eventemitter_add_listener_impl(js, js_getthis(js), key, args[1], once, prepend)) return js_mkerr(js, "listener must be a function"); return js_getthis(js); @@ -677,18 +757,23 @@ static ant_value_t js_eventemitter_prepend_once_listener(ant_t *js, ant_value_t static ant_value_t js_eventemitter_removeAllListeners(ant_t *js, ant_value_t *args, int nargs) { ant_value_t this_obj = js_getthis(js); if (nargs < 1) return this_obj; - const char *event_type = js_getstr(js, args[0], NULL); - if (!event_type) return this_obj; - EventType *evt = find_emitter_event_type(js, this_obj, event_type); + + ant_value_t key = evt_key_from_arg(args[0]); + if (!key) return this_obj; + + EventType *evt = find_emitter_event_type(js, this_obj, key); if (evt) utarray_clear(evt->listeners); + return this_obj; } static ant_value_t js_eventemitter_listenerCount(ant_t *js, ant_value_t *args, int nargs) { if (nargs < 1) return js_mknum(0); - const char *event_type = js_getstr(js, args[0], NULL); - if (!event_type) return js_mknum(0); - EventType *evt = find_emitter_event_type(js, js_getthis(js), event_type); + ant_value_t key = evt_key_from_arg(args[0]); + + if (!key) return js_mknum(0); + EventType *evt = find_emitter_event_type(js, js_getthis(js), key); + if (!evt) return js_mknum(0); return js_mknum((double)utarray_len(evt->listeners)); } @@ -700,9 +785,8 @@ static ant_value_t js_eventemitter_eventNames(ant_t *js, ant_value_t *args, int EventType **events = get_or_create_emitter_events(js, this_obj); if (events && *events) { EventType *evt, *tmp; - HASH_ITER(hh, *events, evt, tmp) if (utarray_len(evt->listeners) > 0) js_arr_push( - js, result, js_mkstr(js, evt->event_type, strlen(evt->event_type)) - ); + HASH_ITER(hh, *events, evt, tmp) if (utarray_len(evt->listeners) > 0) + js_arr_push(js, result, evt->js_key); } return result; @@ -805,6 +889,7 @@ void init_events_module(void) { static void mark_event_type_listeners(ant_t *js, gc_mark_fn mark, EventType *events) { EventType *evt, *tmp; HASH_ITER(hh, events, evt, tmp) { + if (vtype(evt->js_key) == T_STR) mark(js, evt->js_key); for (unsigned int i = 0; i < utarray_len(evt->listeners); i++) { EventListenerEntry *e = (EventListenerEntry *)utarray_eltptr(evt->listeners, i); mark(js, e->callback); diff --git a/src/modules/fs.c b/src/modules/fs.c index 202cf80..95925c8 100644 --- a/src/modules/fs.c +++ b/src/modules/fs.c @@ -27,6 +27,7 @@ #include "silver/engine.h" #include "modules/fs.h" +#include "modules/date.h" #include "modules/buffer.h" #include "modules/events.h" #include "modules/symbol.h" @@ -61,28 +62,34 @@ typedef enum { FS_OP_ACCESS, FS_OP_REALPATH, FS_OP_WRITE_FD, + FS_OP_READ_FD, FS_OP_OPEN, - FS_OP_CLOSE + FS_OP_CLOSE, + FS_OP_MKDTEMP } fs_op_type_t; typedef struct fs_request_s { uv_fs_t uv_req; ant_t *js; ant_value_t promise; - + ant_value_t target_buffer; + ant_value_t callback_fn; + char *path; char *data; char *error_msg; size_t data_len; - + size_t buf_offset; + fs_op_type_t op_type; fs_encoding_t encoding; uv_file fd; - + int completed; int failed; int recursive; int error_code; + int with_file_types; } fs_request_t; typedef enum { @@ -127,6 +134,7 @@ typedef struct { ant_value_t listener; } fs_watchfile_options_t; +static ant_value_t g_dirent_proto = 0; static ant_value_t g_fswatcher_proto = 0; static ant_value_t g_fswatcher_ctor = 0; @@ -179,42 +187,89 @@ static ant_value_t fs_call_value( return result; } -static ant_value_t fs_stats_object_new( - ant_t *js, - mode_t mode, - double size, - double uid, - double gid -) { +typedef struct { + mode_t mode; + double size, uid, gid; + double atime_ms, mtime_ms, ctime_ms, birthtime_ms; +} fs_stat_fields_t; + +static ant_value_t fs_make_date(ant_t *js, double ms) { + ant_value_t obj = js_mkobj(js); + ant_value_t date_proto = js_get_ctor_proto(js, "Date", 4); + if (is_object_type(date_proto)) js_set_proto_init(obj, date_proto); + js_set_slot(obj, SLOT_DATA, tov(ms)); + js_set_slot(obj, SLOT_BRAND, js_mknum(BRAND_DATE)); + return obj; +} + +static ant_value_t fs_stats_object_new(ant_t *js, const fs_stat_fields_t *f) { ant_value_t stat_obj = js_mkobj(js); ant_value_t proto = js_get_ctor_proto(js, "Stats", 5); if (is_object_type(proto) || is_special_object(proto)) js_set_proto_init(stat_obj, proto); - js_set_slot(stat_obj, SLOT_DATA, js_mknum((double)mode)); - js_set(js, stat_obj, "size", js_mknum(size)); - js_set(js, stat_obj, "mode", js_mknum((double)mode)); - js_set(js, stat_obj, "uid", js_mknum(uid)); - js_set(js, stat_obj, "gid", js_mknum(gid)); + js_set_slot(stat_obj, SLOT_DATA, js_mknum((double)f->mode)); + js_set(js, stat_obj, "size", js_mknum(f->size)); + js_set(js, stat_obj, "mode", js_mknum((double)f->mode)); + js_set(js, stat_obj, "uid", js_mknum(f->uid)); + js_set(js, stat_obj, "gid", js_mknum(f->gid)); + + js_set(js, stat_obj, "atimeMs", js_mknum(f->atime_ms)); + js_set(js, stat_obj, "mtimeMs", js_mknum(f->mtime_ms)); + js_set(js, stat_obj, "ctimeMs", js_mknum(f->ctime_ms)); + js_set(js, stat_obj, "birthtimeMs", js_mknum(f->birthtime_ms)); + + js_set(js, stat_obj, "atime", fs_make_date(js, f->atime_ms)); + js_set(js, stat_obj, "mtime", fs_make_date(js, f->mtime_ms)); + js_set(js, stat_obj, "ctime", fs_make_date(js, f->ctime_ms)); + js_set(js, stat_obj, "birthtime", fs_make_date(js, f->birthtime_ms)); return stat_obj; } +static double uv_ts_to_ms(uv_timespec_t ts) { + return (double)ts.tv_sec * 1000.0 + (double)ts.tv_nsec / 1e6; +} + +#ifdef __APPLE__ + #define POSIX_TS_MS(st, field) \ + ((double)(st)->st_##field##spec.tv_sec * 1000.0 + (double)(st)->st_##field##spec.tv_nsec / 1e6) + #define POSIX_BIRTH_MS(st) POSIX_TS_MS(st, birthtime) +#else + #define POSIX_TS_MS(st, field) \ + ((double)(st)->st_##field.tv_sec * 1000.0 + (double)(st)->st_##field.tv_nsec / 1e6) + #define POSIX_BIRTH_MS(st) 0.0 +#endif + +static const fs_stat_fields_t fs_stat_fields_zero = {0}; + static ant_value_t fs_stats_object_from_uv(ant_t *js, const uv_stat_t *st) { - if (!st) return fs_stats_object_new(js, 0, 0.0, 0.0, 0.0); - return fs_stats_object_new( - js, (mode_t)st->st_mode, - (double)st->st_size, (double)st->st_uid, (double)st->st_gid - ); + if (!st) return fs_stats_object_new(js, &fs_stat_fields_zero); + return fs_stats_object_new(js, &(fs_stat_fields_t){ + .mode = (mode_t)st->st_mode, + .size = (double)st->st_size, + .uid = (double)st->st_uid, + .gid = (double)st->st_gid, + .atime_ms = uv_ts_to_ms(st->st_atim), + .mtime_ms = uv_ts_to_ms(st->st_mtim), + .ctime_ms = uv_ts_to_ms(st->st_ctim), + .birthtime_ms = uv_ts_to_ms(st->st_birthtim), + }); } static ant_value_t fs_stats_object_from_posix(ant_t *js, const struct stat *st) { - if (!st) return fs_stats_object_new(js, 0, 0.0, 0.0, 0.0); - return fs_stats_object_new( - js, st->st_mode, - (double)st->st_size, (double)st->st_uid, (double)st->st_gid - ); + if (!st) return fs_stats_object_new(js, &fs_stat_fields_zero); + return fs_stats_object_new(js, &(fs_stat_fields_t){ + .mode = st->st_mode, + .size = (double)st->st_size, + .uid = (double)st->st_uid, + .gid = (double)st->st_gid, + .atime_ms = POSIX_TS_MS(st, atime), + .mtime_ms = POSIX_TS_MS(st, mtime), + .ctime_ms = POSIX_TS_MS(st, ctime), + .birthtime_ms = POSIX_BIRTH_MS(st), + }); } static bool fs_stat_path_sync(const char *path, uv_stat_t *out) { @@ -883,6 +938,8 @@ static void complete_request(fs_request_t *req) { result = js_mkstr(req->js, req->data, req->data_len); else if (req->op_type == FS_OP_REALPATH && req->data) result = js_mkstr(req->js, req->data, req->data_len); + else if (req->op_type == FS_OP_MKDTEMP && req->data) + result = js_mkstr(req->js, req->data, req->data_len); js_resolve_promise(req->js, req->promise, result); } @@ -1087,6 +1144,41 @@ static void on_realpath_complete(uv_fs_t *uv_req) { complete_request(req); } +static ant_value_t create_dirent_object(ant_t *js, const char *name, size_t name_len, uv_dirent_type_t type) { + ant_value_t obj = js_newobj(js); + js_set_proto(obj, g_dirent_proto); + js_set(js, obj, "name", js_mkstr(js, name, name_len)); + js_set_slot(obj, SLOT_DATA, tov((double)type)); + return obj; +} + +static void on_mkdtemp_complete(uv_fs_t *uv_req) { + fs_request_t *req = (fs_request_t *)uv_req->data; + + if (uv_req->result < 0) { + fs_request_fail(req, (int)uv_req->result); + req->completed = 1; + complete_request(req); + uv_fs_req_cleanup(uv_req); + return; + } + + req->data = strdup(uv_req->path); + if (!req->data) { + req->failed = 1; + req->error_msg = strdup("Out of memory"); + req->completed = 1; + complete_request(req); + uv_fs_req_cleanup(uv_req); + return; + } + + req->data_len = strlen(req->data); + req->completed = 1; + uv_fs_req_cleanup(uv_req); + complete_request(req); +} + static void on_exists_complete(uv_fs_t *uv_req) { fs_request_t *req = (fs_request_t *)uv_req->data; ant_value_t result = js_bool(uv_req->result >= 0); @@ -1127,9 +1219,13 @@ static void on_readdir_complete(uv_fs_t *uv_req) { uv_dirent_t dirent; while (uv_fs_scandir_next(uv_req, &dirent) != UV_EOF) { + if (req->with_file_types) { + ant_value_t entry = create_dirent_object(req->js, dirent.name, strlen(dirent.name), dirent.type); + js_arr_push(req->js, arr, entry); + } else { ant_value_t name = js_mkstr(req->js, dirent.name, strlen(dirent.name)); js_arr_push(req->js, arr, name); - } + }} req->completed = 1; js_resolve_promise(req->js, req->promise, arr); @@ -1428,6 +1524,52 @@ static ant_value_t builtin_fs_renameSync(ant_t *js, ant_value_t *args, int nargs return js_mkundef(); } +static double fs_time_arg_to_seconds(ant_t *js, ant_value_t v) { + if (is_date_instance(v)) { + ant_value_t t = js_get_slot(js_as_obj(v), SLOT_DATA); + return (vtype(t) == T_NUM) ? js_getnum(t) / 1000.0 : 0.0; + } + return js_to_number(js, v); +} + +static ant_value_t builtin_fs_utimesSync(ant_t *js, ant_value_t *args, int nargs) { + if (nargs < 3) return js_mkerr(js, "utimesSync() requires path, atime, and mtime"); + if (vtype(args[0]) != T_STR) return js_mkerr(js, "utimesSync() path must be a string"); + + const char *path = js_str(js, args[0]); + double atime = fs_time_arg_to_seconds(js, args[1]); + double mtime = fs_time_arg_to_seconds(js, args[2]); + + uv_fs_t req; + int rc = uv_fs_utime(NULL, &req, path, atime, mtime, NULL); + uv_fs_req_cleanup(&req); + + if (rc < 0) return js_mkerr(js, "utimesSync: %s", uv_strerror(rc)); + return js_mkundef(); +} + +static ant_value_t builtin_fs_utimes(ant_t *js, ant_value_t *args, int nargs) { + return builtin_fs_utimesSync(js, args, nargs); +} + +static ant_value_t builtin_fs_futimesSync(ant_t *js, ant_value_t *args, int nargs) { + if (nargs < 3) return js_mkerr(js, "futimesSync() requires fd, atime, and mtime"); + int fd = (int)js_to_number(js, args[0]); + double atime = fs_time_arg_to_seconds(js, args[1]); + double mtime = fs_time_arg_to_seconds(js, args[2]); + + uv_fs_t req; + int rc = uv_fs_futime(NULL, &req, fd, atime, mtime, NULL); + uv_fs_req_cleanup(&req); + + if (rc < 0) return js_mkerr(js, "futimesSync: %s", uv_strerror(rc)); + return js_mkundef(); +} + +static ant_value_t builtin_fs_futimes(ant_t *js, ant_value_t *args, int nargs) { + return builtin_fs_futimesSync(js, args, nargs); +} + static ant_value_t builtin_fs_appendFileSync(ant_t *js, ant_value_t *args, int nargs) { if (nargs < 2) return js_mkerr(js, "appendFileSync() requires path and data arguments"); @@ -1664,6 +1806,70 @@ static ant_value_t builtin_fs_mkdir(ant_t *js, ant_value_t *args, int nargs) { return req->promise; } +static ant_value_t builtin_fs_mkdtempSync(ant_t *js, ant_value_t *args, int nargs) { + if (nargs < 1 || vtype(args[0]) != T_STR) + return js_mkerr(js, "mkdtempSync() requires a prefix string"); + + size_t prefix_len; + const char *prefix = js_getstr(js, args[0], &prefix_len); + if (!prefix) return js_mkerr(js, "Failed to get prefix string"); + + size_t tpl_len = prefix_len + 6; + char *tpl = malloc(tpl_len + 1); + if (!tpl) return js_mkerr(js, "Out of memory"); + + memcpy(tpl, prefix, prefix_len); + memcpy(tpl + prefix_len, "XXXXXX", 6); + tpl[tpl_len] = '\0'; + + char *result = mkdtemp(tpl); + if (!result) { + free(tpl); + return js_mkerr(js, "mkdtempSync failed: %s", strerror(errno)); + } + + ant_value_t ret = js_mkstr(js, result, strlen(result)); + free(tpl); + return ret; +} + +static ant_value_t builtin_fs_mkdtemp(ant_t *js, ant_value_t *args, int nargs) { + if (nargs < 1 || vtype(args[0]) != T_STR) + return js_mkerr(js, "mkdtemp() requires a prefix string"); + + size_t prefix_len; + const char *prefix = js_getstr(js, args[0], &prefix_len); + if (!prefix) return js_mkerr(js, "Failed to get prefix string"); + + size_t tpl_len = prefix_len + 6; + char *tpl = malloc(tpl_len + 1); + if (!tpl) return js_mkerr(js, "Out of memory"); + + memcpy(tpl, prefix, prefix_len); + memcpy(tpl + prefix_len, "XXXXXX", 6); + tpl[tpl_len] = '\0'; + + fs_request_t *req = calloc(1, sizeof(fs_request_t)); + if (!req) { free(tpl); return js_mkerr(js, "Out of memory"); } + + req->js = js; + req->op_type = FS_OP_MKDTEMP; + req->promise = js_mkpromise(js); + req->path = tpl; + req->uv_req.data = req; + + utarray_push_back(pending_requests, &req); + int result = uv_fs_mkdtemp(uv_default_loop(), &req->uv_req, req->path, on_mkdtemp_complete); + + if (result < 0) { + fs_request_fail(req, result); + req->completed = 1; + complete_request(req); + } + + return req->promise; +} + static ant_value_t builtin_fs_rmdirSync(ant_t *js, ant_value_t *args, int nargs) { if (nargs < 1) return js_mkerr(js, "rmdirSync() requires a path argument"); @@ -1731,6 +1937,55 @@ static ant_value_t builtin_fs_rm(ant_t *js, ant_value_t *args, int nargs) { return fs_rm_impl(js, args, nargs, true); } +static ant_value_t dirent_isFile(ant_t *js, ant_value_t *args, int nargs) { + ant_value_t this = js_getthis(js); + ant_value_t type_val = js_get_slot(this, SLOT_DATA); + if (vtype(type_val) != T_NUM) return js_false; + return js_bool((int)js_getnum(type_val) == UV_DIRENT_FILE); +} + +static ant_value_t dirent_isDirectory(ant_t *js, ant_value_t *args, int nargs) { + ant_value_t this = js_getthis(js); + ant_value_t type_val = js_get_slot(this, SLOT_DATA); + if (vtype(type_val) != T_NUM) return js_false; + return js_bool((int)js_getnum(type_val) == UV_DIRENT_DIR); +} + +static ant_value_t dirent_isSymbolicLink(ant_t *js, ant_value_t *args, int nargs) { + ant_value_t this = js_getthis(js); + ant_value_t type_val = js_get_slot(this, SLOT_DATA); + if (vtype(type_val) != T_NUM) return js_false; + return js_bool((int)js_getnum(type_val) == UV_DIRENT_LINK); +} + +static ant_value_t dirent_isBlockDevice(ant_t *js, ant_value_t *args, int nargs) { + ant_value_t this = js_getthis(js); + ant_value_t type_val = js_get_slot(this, SLOT_DATA); + if (vtype(type_val) != T_NUM) return js_false; + return js_bool((int)js_getnum(type_val) == UV_DIRENT_BLOCK); +} + +static ant_value_t dirent_isCharacterDevice(ant_t *js, ant_value_t *args, int nargs) { + ant_value_t this = js_getthis(js); + ant_value_t type_val = js_get_slot(this, SLOT_DATA); + if (vtype(type_val) != T_NUM) return js_false; + return js_bool((int)js_getnum(type_val) == UV_DIRENT_CHAR); +} + +static ant_value_t dirent_isFIFO(ant_t *js, ant_value_t *args, int nargs) { + ant_value_t this = js_getthis(js); + ant_value_t type_val = js_get_slot(this, SLOT_DATA); + if (vtype(type_val) != T_NUM) return js_false; + return js_bool((int)js_getnum(type_val) == UV_DIRENT_FIFO); +} + +static ant_value_t dirent_isSocket(ant_t *js, ant_value_t *args, int nargs) { + ant_value_t this = js_getthis(js); + ant_value_t type_val = js_get_slot(this, SLOT_DATA); + if (vtype(type_val) != T_NUM) return js_false; + return js_bool((int)js_getnum(type_val) == UV_DIRENT_SOCKET); +} + static ant_value_t stat_isFile(ant_t *js, ant_value_t *args, int nargs) { ant_value_t this = js_getthis(js); ant_value_t mode_val = js_get_slot(this, SLOT_DATA); @@ -2063,6 +2318,12 @@ static ant_value_t builtin_fs_readdirSync(ant_t *js, ant_value_t *args, int narg if (nargs < 1) return js_mkerr(js, "readdirSync() requires a path argument"); if (vtype(args[0]) != T_STR) return js_mkerr(js, "readdirSync() path must be a string"); + bool with_file_types = false; + if (nargs >= 2 && vtype(args[1]) == T_OBJ) { + ant_value_t wft = js_get(js, args[1], "withFileTypes"); + with_file_types = js_truthy(js, wft); + } + size_t path_len; char *path = js_getstr(js, args[0], &path_len); if (!path) return js_mkerr(js, "Failed to get path string"); @@ -2084,8 +2345,13 @@ static ant_value_t builtin_fs_readdirSync(ant_t *js, ant_value_t *args, int narg uv_dirent_t dirent; while (uv_fs_scandir_next(&req, &dirent) != UV_EOF) { - ant_value_t name = js_mkstr(js, dirent.name, strlen(dirent.name)); - js_arr_push(js, arr, name); + if (with_file_types) { + ant_value_t entry = create_dirent_object(js, dirent.name, strlen(dirent.name), dirent.type); + js_arr_push(js, arr, entry); + } else { + ant_value_t name = js_mkstr(js, dirent.name, strlen(dirent.name)); + js_arr_push(js, arr, name); + } } uv_fs_req_cleanup(&req); @@ -2096,6 +2362,12 @@ static ant_value_t builtin_fs_readdir(ant_t *js, ant_value_t *args, int nargs) { if (nargs < 1) return js_mkerr(js, "readdir() requires a path argument"); if (vtype(args[0]) != T_STR) return js_mkerr(js, "readdir() path must be a string"); + bool with_file_types = false; + if (nargs >= 2 && vtype(args[1]) == T_OBJ) { + ant_value_t wft = js_get(js, args[1], "withFileTypes"); + with_file_types = js_truthy(js, wft); + } + size_t path_len; char *path = js_getstr(js, args[0], &path_len); if (!path) return js_mkerr(js, "Failed to get path string"); @@ -2106,6 +2378,7 @@ static ant_value_t builtin_fs_readdir(ant_t *js, ant_value_t *args, int nargs) { req->js = js; req->op_type = FS_OP_READDIR; + req->with_file_types = with_file_types; req->promise = js_mkpromise(js); req->path = strndup(path, path_len); req->uv_req.data = req; @@ -2138,6 +2411,112 @@ static void on_write_fd_complete(uv_fs_t *uv_req) { free_fs_request(req); } +static void on_read_fd_complete(uv_fs_t *uv_req) { + fs_request_t *req = (fs_request_t *)uv_req->data; + + if (uv_req->result < 0) { + if (is_callable(req->callback_fn)) { + ant_value_t err = js_mkerr(req->js, "read failed: %s", uv_strerror((int)uv_req->result)); + ant_value_t cb_args[1] = { err }; + fs_call_value(req->js, req->callback_fn, js_mkundef(), cb_args, 1); + remove_pending_request(req); + free_fs_request(req); + return; + } + fs_request_fail(req, (int)uv_req->result); + req->completed = 1; + complete_request(req); + return; + } + + ssize_t bytes_read = uv_req->result; + + if (req->data && bytes_read > 0) { + ant_value_t ta_val = js_get_slot(req->target_buffer, SLOT_BUFFER); + TypedArrayData *ta = (TypedArrayData *)js_gettypedarray(ta_val); + if (ta && ta->buffer && ta->buffer->data) { + uint8_t *dest = ta->buffer->data + ta->byte_offset + req->buf_offset; + memcpy(dest, req->data, (size_t)bytes_read); + } + } + + if (is_callable(req->callback_fn)) { + ant_value_t cb_args[3] = { js_mknull(), js_mknum((double)bytes_read), req->target_buffer }; + fs_call_value(req->js, req->callback_fn, js_mkundef(), cb_args, 3); + remove_pending_request(req); + free_fs_request(req); + return; + } + + req->completed = 1; + js_resolve_promise(req->js, req->promise, js_mknum((double)bytes_read)); + remove_pending_request(req); + free_fs_request(req); +} + +static ant_value_t builtin_fs_read_fd(ant_t *js, ant_value_t *args, int nargs) { + if (nargs < 2) return js_mkerr(js, "read() requires fd and buffer arguments"); + if (vtype(args[0]) != T_NUM) return js_mkerr(js, "read() fd must be a number"); + + int fd = (int)js_getnum(args[0]); + + ant_value_t buf_arg = args[1]; + ant_value_t ta_data_val = js_get_slot(buf_arg, SLOT_BUFFER); + TypedArrayData *ta_data = (TypedArrayData *)js_gettypedarray(ta_data_val); + if (!ta_data || !ta_data->buffer || !ta_data->buffer->data) + return js_mkerr(js, "read() buffer argument must be a Buffer or TypedArray"); + + size_t buf_len = ta_data->byte_length; + size_t offset = 0; + size_t length = buf_len; + int64_t position = -1; + + ant_value_t callback = js_mkundef(); + int data_nargs = nargs; + if (nargs >= 3 && is_callable(args[nargs - 1])) { + callback = args[nargs - 1]; + data_nargs = nargs - 1; + } + + if (data_nargs >= 3 && vtype(args[2]) == T_NUM) offset = (size_t)js_getnum(args[2]); + if (data_nargs >= 4 && vtype(args[3]) == T_NUM) length = (size_t)js_getnum(args[3]); + if (data_nargs >= 5 && vtype(args[4]) == T_NUM) position = (int64_t)js_getnum(args[4]); + + if (offset > buf_len) return js_mkerr(js, "read() offset out of bounds"); + if (offset + length > buf_len) return js_mkerr(js, "read() length extends beyond buffer"); + + fs_request_t *req = calloc(1, sizeof(fs_request_t)); + if (!req) return js_mkerr(js, "Out of memory"); + + req->js = js; + req->op_type = FS_OP_READ_FD; + req->promise = js_mkpromise(js); + req->fd = fd; + req->target_buffer = buf_arg; + req->buf_offset = offset; + req->data_len = length; + req->callback_fn = callback; + req->data = malloc(length > 0 ? length : 1); + if (!req->data) { + free(req); + return js_mkerr(js, "Out of memory"); + } + req->uv_req.data = req; + + utarray_push_back(pending_requests, &req); + + uv_buf_t buf = uv_buf_init(req->data, (unsigned int)length); + int result = uv_fs_read(uv_default_loop(), &req->uv_req, req->fd, &buf, 1, position, on_read_fd_complete); + + if (result < 0) { + fs_request_fail(req, result); + req->completed = 1; + complete_request(req); + } + + return req->promise; +} + static ant_value_t builtin_fs_readSync(ant_t *js, ant_value_t *args, int nargs) { if (nargs < 2) return js_mkerr(js, "readSync() requires fd and buffer arguments"); if (vtype(args[0]) != T_NUM) return js_mkerr(js, "readSync() fd must be a number"); @@ -2723,6 +3102,17 @@ void init_fs_module(void) { js_set_descriptor(js, stats_ctor, "name", 4, 0); js_set(js, glob, "Stats", js_obj_to_func(stats_ctor)); + + g_dirent_proto = js_mkobj(js); + js_set(js, g_dirent_proto, "isFile", js_mkfun(dirent_isFile)); + js_set(js, g_dirent_proto, "isDirectory", js_mkfun(dirent_isDirectory)); + js_set(js, g_dirent_proto, "isSymbolicLink", js_mkfun(dirent_isSymbolicLink)); + js_set(js, g_dirent_proto, "isBlockDevice", js_mkfun(dirent_isBlockDevice)); + js_set(js, g_dirent_proto, "isCharacterDevice", js_mkfun(dirent_isCharacterDevice)); + js_set(js, g_dirent_proto, "isFIFO", js_mkfun(dirent_isFIFO)); + js_set(js, g_dirent_proto, "isSocket", js_mkfun(dirent_isSocket)); + js_set_sym(js, g_dirent_proto, get_toStringTag_sym(), js_mkstr(js, "Dirent", 6)); + gc_register_root(&g_dirent_proto); } static ant_value_t fs_callback_success_handler(ant_t *js, ant_value_t *args, int nargs) { @@ -2795,14 +3185,15 @@ static ant_value_t fs_callback_attach_promise( bool exists_style ) { GC_ROOT_SAVE(root_mark, js); - ant_value_t success_ctx = js_mkobj(js); - ant_value_t error_ctx = js_mkobj(js); ant_value_t success_fn = js_mkundef(); ant_value_t error_fn = js_mkundef(); GC_ROOT_PIN(js, promise); GC_ROOT_PIN(js, callback); + + ant_value_t success_ctx = js_mkobj(js); GC_ROOT_PIN(js, success_ctx); + ant_value_t error_ctx = js_mkobj(js); GC_ROOT_PIN(js, error_ctx); js_set_slot(success_ctx, SLOT_DATA, callback); @@ -2813,8 +3204,8 @@ static ant_value_t fs_callback_attach_promise( } success_fn = js_heavy_mkfun(js, fs_callback_success_handler, success_ctx); - error_fn = js_heavy_mkfun(js, fs_callback_error_handler, error_ctx); GC_ROOT_PIN(js, success_fn); + error_fn = js_heavy_mkfun(js, fs_callback_error_handler, error_ctx); GC_ROOT_PIN(js, error_fn); js_promise_then(js, promise, success_fn, error_fn); @@ -2871,8 +3262,9 @@ static ant_value_t fs_callback_wrapper_call(ant_t *js, ant_value_t *args, int na return js_mkundef(); } + ant_value_t attach_result = fs_callback_attach_promise(js, result, callback, exists_style); GC_ROOT_RESTORE(js, root_mark); - return fs_callback_attach_promise(js, result, callback, exists_style); + return attach_result; } static ant_value_t fs_make_callback_wrapper(ant_t *js, ant_value_t original, bool exists_style) { @@ -2893,9 +3285,12 @@ static void fs_set_promise_methods(ant_t *js, ant_value_t lib) { js_set(js, lib, "rm", js_mkfun(builtin_fs_rm)); js_set(js, lib, "unlink", js_mkfun(builtin_fs_unlink)); js_set(js, lib, "mkdir", js_mkfun(builtin_fs_mkdir)); + js_set(js, lib, "mkdtemp", js_mkfun(builtin_fs_mkdtemp)); js_set(js, lib, "rmdir", js_mkfun(builtin_fs_rmdir)); js_set(js, lib, "stat", js_mkfun(builtin_fs_stat)); js_set(js, lib, "lstat", js_mkfun(builtin_fs_lstat)); + js_set(js, lib, "utimes", js_mkfun(builtin_fs_utimes)); + js_set(js, lib, "futimes", js_mkfun(builtin_fs_futimes)); js_set(js, lib, "exists", js_mkfun(builtin_fs_exists)); js_set(js, lib, "access", js_mkfun(builtin_fs_access)); js_set(js, lib, "readdir", js_mkfun(builtin_fs_readdir)); @@ -2912,9 +3307,12 @@ static void fs_set_callback_compatible_methods(ant_t *js, ant_value_t lib) { js_set(js, lib, "rm", fs_make_callback_wrapper(js, js_mkfun(builtin_fs_rm), false)); js_set(js, lib, "unlink", fs_make_callback_wrapper(js, js_mkfun(builtin_fs_unlink), false)); js_set(js, lib, "mkdir", fs_make_callback_wrapper(js, js_mkfun(builtin_fs_mkdir), false)); + js_set(js, lib, "mkdtemp", fs_make_callback_wrapper(js, js_mkfun(builtin_fs_mkdtemp), false)); js_set(js, lib, "rmdir", fs_make_callback_wrapper(js, js_mkfun(builtin_fs_rmdir), false)); js_set(js, lib, "stat", fs_make_callback_wrapper(js, js_mkfun(builtin_fs_stat), false)); js_set(js, lib, "lstat", fs_make_callback_wrapper(js, js_mkfun(builtin_fs_lstat), false)); + js_set(js, lib, "utimes", fs_make_callback_wrapper(js, js_mkfun(builtin_fs_utimes), false)); + js_set(js, lib, "futimes", fs_make_callback_wrapper(js, js_mkfun(builtin_fs_futimes), false)); js_set(js, lib, "exists", fs_make_callback_wrapper(js, js_mkfun(builtin_fs_exists), true)); js_set(js, lib, "access", fs_make_callback_wrapper(js, js_mkfun(builtin_fs_access), false)); js_set(js, lib, "readdir", fs_make_callback_wrapper(js, js_mkfun(builtin_fs_readdir), false)); @@ -2949,11 +3347,12 @@ static ant_value_t builtin_fs_promises_getter(ant_t *js, ant_value_t *args, int ant_value_t fs_library(ant_t *js) { ant_value_t lib = js_mkobj(js); - ant_value_t realpath_sync = js_mkfun(builtin_fs_realpathSync); + ant_value_t realpath_sync = js_heavy_mkfun(js, builtin_fs_realpathSync, js_mkundef()); fs_set_callback_compatible_methods(js, lib); fs_init_watch_constructors(js); + js_set(js, lib, "read", js_mkfun(builtin_fs_read_fd)); js_set(js, lib, "readFileSync", js_mkfun(builtin_fs_readFileSync)); js_set(js, lib, "readSync", js_mkfun(builtin_fs_readSync)); js_set(js, lib, "stream", js_mkfun(builtin_fs_readBytes)); @@ -2968,9 +3367,12 @@ ant_value_t fs_library(ant_t *js) { js_set(js, lib, "rmSync", js_mkfun(builtin_fs_rmSync)); js_set(js, lib, "unlinkSync", js_mkfun(builtin_fs_unlinkSync)); js_set(js, lib, "mkdirSync", js_mkfun(builtin_fs_mkdirSync)); + js_set(js, lib, "mkdtempSync", js_mkfun(builtin_fs_mkdtempSync)); js_set(js, lib, "rmdirSync", js_mkfun(builtin_fs_rmdirSync)); js_set(js, lib, "statSync", js_mkfun(builtin_fs_statSync)); js_set(js, lib, "lstatSync", js_mkfun(builtin_fs_lstatSync)); + js_set(js, lib, "utimesSync", js_mkfun(builtin_fs_utimesSync)); + js_set(js, lib, "futimesSync", js_mkfun(builtin_fs_futimesSync)); js_set(js, lib, "existsSync", js_mkfun(builtin_fs_existsSync)); js_set(js, lib, "accessSync", js_mkfun(builtin_fs_accessSync)); js_set(js, lib, "readdirSync", js_mkfun(builtin_fs_readdirSync)); @@ -3018,8 +3420,11 @@ void gc_mark_fs(ant_t *js, gc_mark_fn mark) { unsigned int len = utarray_len(pending_requests); for (unsigned int i = 0; i < len; i++) { fs_request_t **reqp = (fs_request_t **)utarray_eltptr(pending_requests, i); - if (reqp && *reqp) { mark(js, (*reqp)->promise); } - } + if (reqp && *reqp) { + mark(js, (*reqp)->promise); + if (is_object_type((*reqp)->target_buffer)) mark(js, (*reqp)->target_buffer); + if (is_callable((*reqp)->callback_fn)) mark(js, (*reqp)->callback_fn); + }} for (watcher = active_watchers; watcher; watcher = watcher->next_active) { if (vtype(watcher->obj) == T_OBJ) mark(js, watcher->obj); diff --git a/src/modules/globals.c b/src/modules/globals.c index 1ba8bb3..07ecbdf 100644 --- a/src/modules/globals.c +++ b/src/modules/globals.c @@ -1,6 +1,7 @@ #include #include #include +#include #include "ant.h" #include "errors.h" @@ -100,10 +101,60 @@ void js_fire_rejection_handled(ant_t *js, ant_value_t promise_val, ant_value_t r sv_vm_call(js->vm, js, handler, global, call_args, 1, NULL, false); } +// stub: minimal Intl +static ant_value_t intl_dtf_format(ant_t *js, ant_value_t *args, int nargs) { + time_t t; + + if (nargs >= 1 && vtype(args[0]) == T_NUM) { + t = (time_t)(js_getnum(args[0]) / 1000.0); + } else t = time(NULL); + + struct tm local; + localtime_r(&t, &local); + + char buf[64]; + int hour12 = local.tm_hour % 12; + if (hour12 == 0) hour12 = 12; + + const char *ampm = local.tm_hour < 12 ? "AM" : "PM"; + snprintf(buf, sizeof(buf), "%d:%02d:%02d %s", hour12, local.tm_min, local.tm_sec, ampm); + + return js_mkstr(js, buf, strlen(buf)); +} + +static ant_value_t intl_dtf_resolvedOptions(ant_t *js, ant_value_t *args, int nargs) { + ant_value_t obj = js_mkobj(js); + js_set(js, obj, "locale", js_mkstr(js, "en-US", 5)); + js_set(js, obj, "timeZone", js_mkstr(js, "UTC", 3)); + return obj; +} + +static ant_value_t intl_dtf_formatToParts(ant_t *js, ant_value_t *args, int nargs) { + return js_mkarr(js); +} + +static ant_value_t intl_dtf_constructor(ant_t *js, ant_value_t *args, int nargs) { + ant_value_t this = js_getthis(js); + + ant_value_t format_fn = js_heavy_mkfun(js, intl_dtf_format, js_mkundef()); + js_set(js, this, "format", format_fn); + js_set(js, this, "resolvedOptions", js_mkfun(intl_dtf_resolvedOptions)); + js_set(js, this, "formatToParts", js_mkfun(intl_dtf_formatToParts)); + + return this; +} + void init_globals_module(void) { ant_t *js = rt->js; ant_value_t global = js_glob(js); js_set(js, global, "reportError", js_mkfun(js_report_error)); js_set_descriptor(js, global, "reportError", 11, JS_DESC_W | JS_DESC_C); + + ant_value_t intl = js_mkobj(js); + ant_value_t dtf_ctor = js_heavy_mkfun(js, intl_dtf_constructor, js_mkundef()); + + js_mark_constructor(dtf_ctor, true); + js_set(js, intl, "DateTimeFormat", dtf_ctor); + js_set(js, global, "Intl", intl); } diff --git a/src/modules/module.c b/src/modules/module.c index fb63c35..132a212 100644 --- a/src/modules/module.c +++ b/src/modules/module.c @@ -44,7 +44,25 @@ static ant_value_t builtin_createRequire_call(ant_t *js, ant_value_t *args, int return ns; } -// require.resolve(specifier) +static ant_value_t resolve_strip_file_url(ant_t *js, ant_value_t resolved) { + if (is_err(resolved) || vtype(resolved) != T_STR) return resolved; + + ant_offset_t len = 0; + ant_offset_t off = vstr(js, resolved, &len); + + const char *s = (const char *)(uintptr_t)(off); + static const char *prefix = "file://"; + + if ((size_t)len >= strlen(prefix) && strncmp(s, prefix, strlen(prefix)) == 0) { + const char *path_part = s + strlen(prefix); + size_t plen = (size_t)len - strlen(prefix); + return js_mkstr(js, path_part, plen); + } + + return resolved; +} + +// require.resolve(specifier, options?) static ant_value_t builtin_createRequire_resolve(ant_t *js, ant_value_t *args, int nargs) { if (nargs < 1 || vtype(args[0]) != T_STR) return js_mkerr(js, "require.resolve() expects a string specifier"); @@ -59,23 +77,28 @@ static ant_value_t builtin_createRequire_resolve(ant_t *js, ant_value_t *args, i base_path = (const char *)(uintptr_t)(doff); } - ant_value_t resolved = js_esm_resolve_specifier(js, args[0], base_path); - if (is_err(resolved)) return resolved; - if (vtype(resolved) != T_STR) return resolved; + ant_value_t paths_val = (nargs >= 2 && is_object_type(args[1])) + ? js_get(js, args[1], "paths") : js_mkundef(); - ant_offset_t len = 0; - ant_offset_t off = vstr(js, resolved, &len); - - const char *s = (const char *)(uintptr_t)(off); - static const char *prefix = "file://"; + if (vtype(paths_val) != T_ARR) { + ant_value_t resolved = js_esm_resolve_specifier(js, args[0], base_path); + return resolve_strip_file_url(js, resolved); + } - if ((size_t)len >= strlen(prefix) && strncmp(s, prefix, strlen(prefix)) == 0) { - const char *path_part = s + strlen(prefix); - size_t plen = (size_t)len - strlen(prefix); - return js_mkstr(js, path_part, plen); + ant_offset_t path_count = js_arr_len(js, paths_val); + for (ant_offset_t i = 0; i < path_count; i++) { + ant_value_t p = js_arr_get(js, paths_val, i); + if (vtype(p) != T_STR) continue; + + char *dir = js_getstr(js, p, NULL); + if (!dir) continue; + + ant_value_t resolved = js_esm_resolve_specifier(js, args[0], dir); + if (!is_err(resolved) && vtype(resolved) == T_STR) + return resolve_strip_file_url(js, resolved); } - return resolved; + return js_mkerr(js, "Cannot resolve module"); } // createRequire(filename) diff --git a/src/modules/structured-clone.c b/src/modules/structured-clone.c index 40a4d67..b1bd9e1 100644 --- a/src/modules/structured-clone.c +++ b/src/modules/structured-clone.c @@ -9,6 +9,7 @@ #include "internal.h" #include "descriptors.h" +#include "modules/date.h" #include "modules/buffer.h" #include "modules/collections.h" #include "modules/domexception.h" @@ -276,15 +277,13 @@ static ant_value_t sc_clone_rec(ant_t *js, ant_value_t val, sc_entry_t **seen, s return clone; } - ant_value_t date_proto = js_get_ctor_proto(js, "Date", 4); - if ( - vtype(date_proto) != T_UNDEF - && is_object_type(date_proto) - && js_is_prototype_of(js, date_proto, val) - ) { + if (is_date_instance(val)) { ant_value_t clone = js_mkobj(js); - js_set_proto_init(clone, date_proto); + ant_value_t date_proto = js_get_ctor_proto(js, "Date", 4); + if (is_object_type(date_proto)) js_set_proto_init(clone, date_proto); + js_set_slot(clone, SLOT_DATA, js_get_slot(val, SLOT_DATA)); + js_set_slot(clone, SLOT_BRAND, js_mknum(BRAND_DATE)); sc_add(seen, val, clone); return clone; diff --git a/src/modules/zlib.c b/src/modules/zlib.c index 6cc4527..0fdbb4d 100644 --- a/src/modules/zlib.c +++ b/src/modules/zlib.c @@ -119,6 +119,7 @@ static void zlib_emit_data(ant_t *js, ant_value_t obj, const uint8_t *data, size } typedef struct { ant_t *js; ant_value_t obj; } brotli_emit_ctx_t; +typedef struct { ant_t *js; ant_value_t arr; bool error; } brotli_collect_ctx_t; static int brotli_emit_cb(void *ctx, const uint8_t *chunk, size_t len) { brotli_emit_ctx_t *ec = (brotli_emit_ctx_t *)ctx; @@ -126,6 +127,14 @@ static int brotli_emit_cb(void *ctx, const uint8_t *chunk, size_t len) { return 0; } +static int brotli_collect_cb(void *ctx, const uint8_t *chunk, size_t len) { + brotli_collect_ctx_t *ec = (brotli_collect_ctx_t *)ctx; + ant_value_t buf = zlib_make_buffer(ec->js, chunk, len); + if (is_err(buf)) { ec->error = true; return -1; } + js_arr_push(ec->js, ec->arr, buf); + return 0; +} + static ant_value_t zlib_do_process( ant_t *js, zlib_stream_t *st, const uint8_t *input, size_t input_len, @@ -179,6 +188,73 @@ static ant_value_t zlib_do_process( return js_mkundef(); } +static ant_value_t zlib_process_chunk_sync( + ant_t *js, zlib_stream_t *st, + const uint8_t *input, size_t input_len, + int flush_flag +) { + ant_value_t arr = js_mkarr(js); + + if (st->brotli) { + brotli_collect_ctx_t ctx = { js, arr, false }; + int rc = 0; + if (input && input_len > 0) + rc = brotli_stream_process(st->brotli, input, input_len, brotli_collect_cb, &ctx); + if (rc >= 0 && flush_flag == Z_FINISH) + rc = brotli_stream_finish(st->brotli, brotli_collect_cb, &ctx); + if (rc < 0 || ctx.error) return js_mkerr(js, "brotli operation failed"); + return arr; + } + + bool compress = zlib_kind_is_compress(st->kind); + uint8_t out[ZLIB_CHUNK]; + + st->strm.next_in = input ? (Bytef *)input : NULL; + st->strm.avail_in = input ? (uInt)input_len : 0; + + int ret; + do { + st->strm.next_out = out; + st->strm.avail_out = ZLIB_CHUNK; + + if (compress) { + ret = deflate(&st->strm, flush_flag); + if (ret == Z_STREAM_ERROR) return js_mkerr(js, "zlib deflate error"); + } else { + ret = inflate(&st->strm, flush_flag); + if (ret == Z_NEED_DICT || ret == Z_DATA_ERROR || ret == Z_MEM_ERROR) + return js_mkerr(js, "zlib inflate error"); + } + + size_t have = ZLIB_CHUNK - st->strm.avail_out; + if (have > 0) { + ant_value_t buf = zlib_make_buffer(js, out, have); + if (is_err(buf)) return buf; + js_arr_push(js, arr, buf); + } + if (ret == Z_STREAM_END) break; + } while (st->strm.avail_out == 0); + + return arr; +} + +static ant_value_t js_zlib_process_chunk(ant_t *js, ant_value_t *args, int nargs) { + zlib_stream_t *st = zlib_stream_ptr(js_getthis(js)); + if (!st) return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid zlib stream"); + if (st->destroyed || st->ended) return js_mkarr(js); + + const uint8_t *input = NULL; + size_t input_len = 0; + if (nargs >= 1 && is_object_type(args[0])) + buffer_source_get_bytes(js, args[0], &input, &input_len); + + int flush_flag = Z_NO_FLUSH; + if (nargs >= 2 && vtype(args[1]) == T_NUM) + flush_flag = (int)js_getnum(args[1]); + + return zlib_process_chunk_sync(js, st, input, input_len, flush_flag); +} + static ant_value_t js_zlib_write(ant_t *js, ant_value_t *args, int nargs) { zlib_stream_t *st = zlib_stream_ptr(js_getthis(js)); if (!st) return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid zlib stream"); @@ -353,6 +429,11 @@ static ant_value_t zlib_create_stream(ant_t *js, zlib_kind_t kind) { js_set(js, obj, "readable", js_true); js_set(js, obj, "writable", js_true); + js_set(js, obj, "_processChunk", js_mkfun(js_zlib_process_chunk)); + + ant_value_t handle_obj = js_mkobj(js); + js_set(js, handle_obj, "close", js_mkfun(js_zlib_destroy)); + js_set(js, obj, "_handle", handle_obj); st->obj = obj; zlib_add_active(st); @@ -407,12 +488,15 @@ static int zbuf_append(zbuf_t *b, const uint8_t *chunk, size_t n) { size_t newcap = b->cap ? b->cap * 2 : 4096; while (newcap < b->len + n) newcap *= 2; uint8_t *p = realloc(b->data, newcap); + if (!p) { b->error = 1; return -1; } b->data = p; b->cap = newcap; } + memcpy(b->data + b->len, chunk, n); b->len += n; + return 0; } @@ -652,48 +736,68 @@ static void zlib_init_proto(ant_t *js) { js_mkfun(js_zlib_get_bytes_written), JS_DESC_C); } +static ant_value_t zlib_mkctor(ant_t *js, ant_value_t (*fn)(ant_t *, ant_value_t *, int)) { + ant_value_t ctor = js_heavy_mkfun(js, fn, js_mkundef()); + js_mark_constructor(ctor, true); + return ctor; +} + ant_value_t zlib_library(ant_t *js) { zlib_init_proto(js); ant_value_t lib = js_mkobj(js); ant_value_t consts = make_constants(js); - js_set(js, lib, "constants", consts); - - js_set(js, lib, "createGzip", js_mkfun(js_create_gzip)); - js_set(js, lib, "createGunzip", js_mkfun(js_create_gunzip)); - js_set(js, lib, "createDeflate", js_mkfun(js_create_deflate)); - js_set(js, lib, "createInflate", js_mkfun(js_create_inflate)); - js_set(js, lib, "createDeflateRaw", js_mkfun(js_create_deflate_raw)); - js_set(js, lib, "createInflateRaw", js_mkfun(js_create_inflate_raw)); - js_set(js, lib, "createUnzip", js_mkfun(js_create_unzip)); - js_set(js, lib, "createBrotliCompress", js_mkfun(js_create_brotli_compress)); - js_set(js, lib, "createBrotliDecompress", js_mkfun(js_create_brotli_decompress)); + js_set(js, lib, "gzip", js_mkfun(js_gzip)); + js_set(js, lib, "gzipSync", js_mkfun(js_gzipSync)); + js_set(js, lib, "Gzip", zlib_mkctor(js, js_create_gzip)); + js_set(js, lib, "createGzip", js_mkfun(js_create_gzip)); + + js_set(js, lib, "gunzip", js_mkfun(js_gunzip)); + js_set(js, lib, "gunzipSync", js_mkfun(js_gunzipSync)); + js_set(js, lib, "Gunzip", zlib_mkctor(js, js_create_gunzip)); + js_set(js, lib, "createGunzip", js_mkfun(js_create_gunzip)); + + js_set(js, lib, "deflate", js_mkfun(js_deflate)); + js_set(js, lib, "deflateSync", js_mkfun(js_deflateSync)); + js_set(js, lib, "Deflate", zlib_mkctor(js, js_create_deflate)); + js_set(js, lib, "createDeflate", js_mkfun(js_create_deflate)); + + js_set(js, lib, "inflate", js_mkfun(js_inflate)); + js_set(js, lib, "inflateSync", js_mkfun(js_inflateSync)); + js_set(js, lib, "Inflate", zlib_mkctor(js, js_create_inflate)); + js_set(js, lib, "createInflate", js_mkfun(js_create_inflate)); + + js_set(js, lib, "deflateRaw", js_mkfun(js_deflateRaw)); + js_set(js, lib, "deflateRawSync", js_mkfun(js_deflateRawSync)); + js_set(js, lib, "DeflateRaw", zlib_mkctor(js, js_create_deflate_raw)); + js_set(js, lib, "createDeflateRaw", js_mkfun(js_create_deflate_raw)); + + js_set(js, lib, "inflateRaw", js_mkfun(js_inflateRaw)); + js_set(js, lib, "inflateRawSync", js_mkfun(js_inflateRawSync)); + js_set(js, lib, "InflateRaw", zlib_mkctor(js, js_create_inflate_raw)); + js_set(js, lib, "createInflateRaw", js_mkfun(js_create_inflate_raw)); + + js_set(js, lib, "unzip", js_mkfun(js_unzip)); + js_set(js, lib, "unzipSync", js_mkfun(js_unzipSync)); + js_set(js, lib, "Unzip", zlib_mkctor(js, js_create_unzip)); + js_set(js, lib, "createUnzip", js_mkfun(js_create_unzip)); + + js_set(js, lib, "brotliCompress", js_mkfun(js_brotliCompress)); + js_set(js, lib, "brotliCompressSync", js_mkfun(js_brotliCompressSync)); + js_set(js, lib, "BrotliCompress", zlib_mkctor(js, js_create_brotli_compress)); + js_set(js, lib, "createBrotliCompress", js_mkfun(js_create_brotli_compress)); - js_set(js, lib, "gzip", js_mkfun(js_gzip)); - js_set(js, lib, "gunzip", js_mkfun(js_gunzip)); - js_set(js, lib, "deflate", js_mkfun(js_deflate)); - js_set(js, lib, "inflate", js_mkfun(js_inflate)); - js_set(js, lib, "deflateRaw", js_mkfun(js_deflateRaw)); - js_set(js, lib, "inflateRaw", js_mkfun(js_inflateRaw)); - js_set(js, lib, "unzip", js_mkfun(js_unzip)); - js_set(js, lib, "brotliCompress", js_mkfun(js_brotliCompress)); js_set(js, lib, "brotliDecompress", js_mkfun(js_brotliDecompress)); - - js_set(js, lib, "gzipSync", js_mkfun(js_gzipSync)); - js_set(js, lib, "gunzipSync", js_mkfun(js_gunzipSync)); - js_set(js, lib, "deflateSync", js_mkfun(js_deflateSync)); - js_set(js, lib, "inflateSync", js_mkfun(js_inflateSync)); - js_set(js, lib, "deflateRawSync", js_mkfun(js_deflateRawSync)); - js_set(js, lib, "inflateRawSync", js_mkfun(js_inflateRawSync)); - js_set(js, lib, "unzipSync", js_mkfun(js_unzipSync)); - js_set(js, lib, "brotliCompressSync", js_mkfun(js_brotliCompressSync)); js_set(js, lib, "brotliDecompressSync", js_mkfun(js_brotliDecompressSync)); - - js_set(js, lib, "crc32", js_mkfun(js_zlib_crc32)); + js_set(js, lib, "BrotliDecompress", zlib_mkctor(js, js_create_brotli_decompress)); + js_set(js, lib, "createBrotliDecompress", js_mkfun(js_create_brotli_decompress)); + js_set(js, lib, "default", lib); - + js_set(js, lib, "constants", consts); + js_set(js, lib, "crc32", js_mkfun(js_zlib_crc32)); js_set_sym(js, lib, get_toStringTag_sym(), js_mkstr(js, "zlib", 4)); + return lib; } diff --git a/src/silver/compiler.c b/src/silver/compiler.c index 616e8a1..0818151 100644 --- a/src/silver/compiler.c +++ b/src/silver/compiler.c @@ -1180,6 +1180,8 @@ static void hoist_lexical_pattern(sv_compiler_t *c, sv_ast_t *pat, if (!prop) continue; if (prop->type == N_PROPERTY) hoist_lexical_pattern(c, prop->right, is_const); + else if (prop->type == N_REST || prop->type == N_SPREAD) + hoist_lexical_pattern(c, prop->right, is_const); } break; default: @@ -2263,11 +2265,17 @@ static void compile_call(sv_compiler_t *c, sv_ast_t *node) { static void compile_new(sv_compiler_t *c, sv_ast_t *node) { compile_expr(c, node->left); emit_op(c, OP_DUP); - int argc = node->args.count; - for (int i = 0; i < argc; i++) - compile_expr(c, node->args.items[i]); - emit_op(c, OP_NEW); - emit_u16(c, (uint16_t)argc); + if (call_has_spread_arg(node)) { + compile_call_args_array(c, node); + emit_op(c, OP_NEW_APPLY); + emit_u16(c, 1); + } else { + int argc = node->args.count; + for (int i = 0; i < argc; i++) + compile_expr(c, node->args.items[i]); + emit_op(c, OP_NEW); + emit_u16(c, (uint16_t)argc); + } } static bool sv_node_has_optional_base(sv_ast_t *n) { diff --git a/src/silver/engine.c b/src/silver/engine.c index 2337da5..3a367a4 100644 --- a/src/silver/engine.c +++ b/src/silver/engine.c @@ -1079,9 +1079,10 @@ ant_value_t sv_execute_frame(sv_vm_t *vm, sv_func_t *func, ant_value_t this, ant DISPATCH(); } - L_NEW: { VM_CHECK(sv_op_new(vm, js, ip)); NEXT(3); } - L_APPLY: { VM_CHECK(sv_op_apply(vm, js, ip)); NEXT(3); } - L_EVAL: { VM_CHECK(sv_op_eval(vm, js, frame, ip)); NEXT(5); } + L_NEW: { VM_CHECK(sv_op_new(vm, js, ip)); NEXT(3); } + L_APPLY: { VM_CHECK(sv_op_apply(vm, js, ip)); NEXT(3); } + L_NEW_APPLY: { VM_CHECK(sv_op_new_apply(vm, js, ip)); NEXT(3); } + L_EVAL: { VM_CHECK(sv_op_eval(vm, js, frame, ip)); NEXT(5); } // TODO: make the methods below DRY L_RETURN: { diff --git a/src/silver/ops/async.h b/src/silver/ops/async.h index c6fa4ab..4188141 100644 --- a/src/silver/ops/async.h +++ b/src/silver/ops/async.h @@ -165,11 +165,14 @@ static inline ant_value_t sv_start_tla(ant_t *js, sv_func_t *func, ant_value_t t coro->active_parent = js->active_async_coro; js->active_async_coro = coro; - ant_value_t result = sv_execute_entry(async_vm, func, this_val, NULL, 0); - js->active_async_coro = coro->active_parent; - coro->active_parent = NULL; + ant_value_t result = sv_execute_entry( + async_vm, func, + this_val, NULL, 0 + ); if (async_vm->suspended) { + js->active_async_coro = coro->active_parent; + coro->active_parent = NULL; enqueue_coroutine(coro); GC_ROOT_RESTORE(js, root_mark); return promise; @@ -183,6 +186,9 @@ static inline ant_value_t sv_start_tla(ant_t *js, sv_func_t *func, ant_value_t t js_reject_promise(js, promise, reject_value); } else js_resolve_promise(js, promise, result); + js->active_async_coro = coro->active_parent; + coro->active_parent = NULL; + free_coroutine(coro); GC_ROOT_RESTORE(js, root_mark); @@ -365,13 +371,13 @@ static inline ant_value_t sv_start_async_closure( js->active_async_coro = coro; ant_value_t result = sv_execute_closure_entry( - async_vm, closure, callee_func, super_val, this_val, args, argc, NULL + async_vm, closure, callee_func, + super_val, this_val, args, argc, NULL ); - js->active_async_coro = coro->active_parent; - coro->active_parent = NULL; - if (async_vm->suspended) { + js->active_async_coro = coro->active_parent; + coro->active_parent = NULL; enqueue_coroutine(coro); GC_ROOT_RESTORE(js, root_mark); return promise; @@ -385,6 +391,9 @@ static inline ant_value_t sv_start_async_closure( js_reject_promise(js, promise, reject_value); } else js_resolve_promise(js, promise, result); + js->active_async_coro = coro->active_parent; + coro->active_parent = NULL; + free_coroutine(coro); GC_ROOT_RESTORE(js, root_mark); diff --git a/src/silver/ops/calls.h b/src/silver/ops/calls.h index f4a0258..f3e0536 100644 --- a/src/silver/ops/calls.h +++ b/src/silver/ops/calls.h @@ -119,6 +119,60 @@ static inline ant_value_t sv_op_apply(sv_vm_t *vm, ant_t *js, uint8_t *ip) { return result; } +static inline ant_value_t sv_op_new_apply(sv_vm_t *vm, ant_t *js, uint8_t *ip) { + uint16_t argc = sv_get_u16(ip + 1); + ant_value_t *args = &vm->stack[vm->sp - argc]; + ant_value_t new_target = vm->stack[vm->sp - argc - 1]; + ant_value_t func = vm->stack[vm->sp - argc - 2]; + ant_value_t record_func = func; + js->new_target = new_target; + + sv_call_args_t call; + sv_call_args_reset(&call, args, (int)argc); + ant_value_t norm = sv_apply_normalize_args(js, &call); + if (is_err(norm)) { vm->sp -= argc + 2; return norm; } + + if (vtype(func) == T_OBJ && is_proxy(func)) { + ant_value_t result = js_proxy_construct(js, func, call.args, call.argc, new_target); + sv_call_args_release(&call); + vm->sp -= argc + 2; + if (is_err(result)) return result; + vm->stack[vm->sp++] = result; + return result; + } + if (!js_is_constructor(js, func)) { + sv_call_args_release(&call); + vm->sp -= argc + 2; + return js_mkerr_typed(js, JS_ERR_TYPE, "not a constructor"); + } + + ant_value_t proto = js_mkundef(); + if (vtype(func) == T_FUNC) { + ant_value_t proto_source = func; + ant_value_t func_obj = js_func_obj(func); + ant_value_t target_func = js_get_slot(func_obj, SLOT_TARGET_FUNC); + if (vtype(target_func) == T_FUNC) { + proto_source = target_func; + record_func = target_func; + } + proto = js_getprop_fallback(js, proto_source, "prototype"); + } + + ant_value_t obj = js_mkobj_with_inobj_limit(js, sv_tfb_ctor_inobj_limit(record_func)); + if (is_object_type(proto)) js_set_proto_init(obj, proto); + ant_value_t ctor_this = obj; + ant_value_t result = sv_vm_call(vm, js, func, obj, call.args, call.argc, &ctor_this, true); + sv_call_args_release(&call); + vm->sp -= argc + 2; + if (is_err(result)) return result; + ant_value_t final_obj = + is_object_type(result) ? result + : (is_object_type(ctor_this) ? ctor_this : obj); + sv_tfb_record_ctor_prop_count(record_func, final_obj); + vm->stack[vm->sp++] = final_obj; + return result; +} + static inline ant_value_t sv_op_eval(sv_vm_t *vm, ant_t *js, sv_frame_t *frame, uint8_t *ip) { ant_value_t code = vm->stack[--vm->sp]; if (vtype(code) != T_STR) { diff --git a/src/silver/ops/objects.h b/src/silver/ops/objects.h index 7d88c38..7e5aaf6 100644 --- a/src/silver/ops/objects.h +++ b/src/silver/ops/objects.h @@ -48,7 +48,9 @@ static inline void sv_op_define_method_comp( ant_value_t desc_obj = js_as_obj(obj); bool is_getter = (flags & 1) != 0; bool is_setter = (flags & 2) != 0; - if (vtype(key) == T_SYMBOL && !is_getter && !is_setter) { + if (vtype(key) == T_SYMBOL) { + if (is_getter) { js_set_sym_getter_desc(js, desc_obj, key, fn, JS_DESC_E | JS_DESC_C); return; } + if (is_setter) { js_set_sym_setter_desc(js, desc_obj, key, fn, JS_DESC_E | JS_DESC_C); return; } js_set_sym(js, obj, key, fn); return; } diff --git a/src/silver/ops/super.h b/src/silver/ops/super.h index 7b916c2..e096c58 100644 --- a/src/silver/ops/super.h +++ b/src/silver/ops/super.h @@ -12,10 +12,21 @@ static inline void sv_op_get_super_val(sv_vm_t *vm, ant_t *js) { ant_value_t prop = vm->stack[--vm->sp]; ant_value_t obj = vm->stack[--vm->sp]; ant_value_t receiver = vm->stack[--vm->sp]; - ant_value_t proto = js_get_proto(js, obj); + ant_value_t proto; + + if (vtype(obj) == T_FUNC && vtype(receiver) != T_FUNC) { + proto = js_getprop_fallback(js, obj, "prototype"); + } else proto = js_get_proto(js, obj); + + if (vtype(prop) == T_SYMBOL) { + vm->stack[vm->sp++] = js_get_sym_with_receiver(js, proto, prop, receiver); + return; + } + ant_value_t key_str = coerce_to_str(js, prop); ant_offset_t klen; ant_offset_t koff = vstr(js, key_str, &klen); + const char *kptr = (const char *)(uintptr_t)(koff); vm->stack[vm->sp++] = js_getprop_super(js, proto, receiver, kptr); } diff --git a/src/silver/swarm.c b/src/silver/swarm.c index 62633f8..816ceaf 100644 --- a/src/silver/swarm.c +++ b/src/silver/swarm.c @@ -1675,7 +1675,7 @@ static jit_features_t jit_prescan_features(sv_func_t *func) { case OP_CALL: case OP_CALL_METHOD: case OP_TAIL_CALL: case OP_TAIL_CALL_METHOD: case OP_ARRAY: case OP_NEW: - case OP_APPLY: + case OP_APPLY: case OP_NEW_APPLY: f.needs_args_buf = true; if (op == OP_TAIL_CALL || op == OP_TAIL_CALL_METHOD) f.needs_tco_args = true; @@ -1741,7 +1741,7 @@ static bool jit_is_eligible(sv_func_t *func) { case OP_CALL: case OP_CALL_METHOD: case OP_CALL_IS_PROTO: case OP_TAIL_CALL: case OP_TAIL_CALL_METHOD: - case OP_APPLY: + case OP_APPLY: case OP_NEW_APPLY: case OP_GET_GLOBAL: case OP_GET_GLOBAL_UNDEF: case OP_PUT_GLOBAL: case OP_GET_FIELD: case OP_GET_FIELD2: case OP_PUT_FIELD: diff --git a/src/sugar.c b/src/sugar.c index 13b6488..1fac1fa 100644 --- a/src/sugar.c +++ b/src/sugar.c @@ -72,14 +72,16 @@ void free_coroutine(coroutine_t *coro) { static size_t calculate_coro_stack_size(void) { static size_t cached_size = 0; if (coro_stack_size_initialized) return cached_size; + coro_stack_size_initialized = true; const char *env_stack = getenv("ANT_CORO_STACK_SIZE"); + if (env_stack) { - size_t size = (size_t)atoi(env_stack) * 1024; - if (size >= 32 * 1024 && size <= 8 * 1024 * 1024) { - cached_size = size; return cached_size; - } - } + size_t size = (size_t)atoi(env_stack) * 1024; + if (size >= 32 * 1024 && size <= 8 * 1024 * 1024) { + cached_size = size; return cached_size; + }} + return cached_size; } @@ -105,13 +107,14 @@ static void resume_coroutine_if_suspended(ant_t *js, coroutine_t *coro) { js->active_async_coro = coro; ant_value_t result = sv_resume_suspended(coro->sv_vm); - js->active_async_coro = coro->active_parent; - coro->active_parent = NULL; - coro->is_settled = false; coro->awaited_promise = js_mkundef(); - if (coro->sv_vm->suspended) return; - remove_coroutine(coro); + + if (coro->sv_vm->suspended) { + js->active_async_coro = coro->active_parent; + coro->active_parent = NULL; + return; + } remove_coroutine(coro); if (is_err(result)) { ant_value_t reject_value = js->thrown_value; @@ -122,6 +125,9 @@ static void resume_coroutine_if_suspended(ant_t *js, coroutine_t *coro) { } else js_resolve_promise(js, coro->async_promise, result); js_maybe_drain_microtasks_after_async_settle(js); + js->active_async_coro = coro->active_parent; + + coro->active_parent = NULL; free_coroutine(coro); return; diff --git a/src/types/modules/fs.d.ts b/src/types/modules/fs.d.ts index e596a33..9c718a4 100644 --- a/src/types/modules/fs.d.ts +++ b/src/types/modules/fs.d.ts @@ -50,6 +50,7 @@ declare module 'fs' { function readFile(path: string): Promise; function readFileSync(path: string, encoding: Encoding | { encoding: Encoding }): string; function readFileSync(path: string): Uint8Array; + function read(fd: number, buffer: ArrayBufferView, offset?: number, length?: number, position?: number | null, callback?: (err: Error | null, bytesRead: number, buffer: ArrayBufferView) => void): Promise; function readSync(fd: number, buffer: ArrayBufferView, offset?: number, length?: number, position?: number | null): number; function stream(path: string): Promise; function open(path: string, flags?: string, mode?: number): Promise; @@ -71,6 +72,8 @@ declare module 'fs' { function unlinkSync(path: string): void; function mkdir(path: string, options?: { recursive?: boolean; mode?: number }): Promise; function mkdirSync(path: string, options?: number | { recursive?: boolean; mode?: number }): void; + function mkdtemp(prefix: string): Promise; + function mkdtempSync(prefix: string): string; function rmdir(path: string): Promise; function rmdirSync(path: string): void; function stat(path: string): Promise;