diff --git a/examples/spec/streams-queuing.js b/examples/spec/streams-queuing.js new file mode 100644 index 0000000..5554410 --- /dev/null +++ b/examples/spec/streams-queuing.js @@ -0,0 +1,82 @@ +import { test, testThrows, summary } from './helpers.js'; + +console.log('CountQueuingStrategy / ByteLengthQueuingStrategy Tests\n'); + +const cqs = new CountQueuingStrategy({ highWaterMark: 5 }); +test('CQS highWaterMark', cqs.highWaterMark, 5); +test('CQS size returns function', typeof cqs.size, 'function'); +test('CQS size() returns 1', cqs.size(), 1); +test('CQS size(anything) returns 1', cqs.size({ byteLength: 1024 }), 1); + +const cqs0 = new CountQueuingStrategy({ highWaterMark: 0 }); +test('CQS highWaterMark 0', cqs0.highWaterMark, 0); + +const cqs_large = new CountQueuingStrategy({ highWaterMark: 1024 }); +test('CQS highWaterMark 1024', cqs_large.highWaterMark, 1024); + +test('CQS size is same function each access', cqs.size === cqs.size, true); +test('CQS size is same across instances', cqs.size === cqs_large.size, true); +test('CQS size.name', cqs.size.name, 'size'); +test('CQS size.length', cqs.size.length, 0); + +testThrows('CQS requires new', () => CountQueuingStrategy({ highWaterMark: 1 })); +testThrows('CQS requires options', () => new CountQueuingStrategy()); +testThrows('CQS requires highWaterMark', () => new CountQueuingStrategy({})); +testThrows('CQS rejects null', () => new CountQueuingStrategy(null)); +testThrows('CQS rejects non-object', () => new CountQueuingStrategy(5)); + +test('CQS typeof', typeof CountQueuingStrategy, 'function'); + +const blqs = new ByteLengthQueuingStrategy({ highWaterMark: 1024 }); +test('BLQS highWaterMark', blqs.highWaterMark, 1024); +test('BLQS size returns function', typeof blqs.size, 'function'); + +test('BLQS size with byteLength', blqs.size({ byteLength: 256 }), 256); +test('BLQS size with byteLength 0', blqs.size({ byteLength: 0 }), 0); +test('BLQS size with no byteLength', blqs.size({}), undefined); +test('BLQS size.name', blqs.size.name, 'size'); +test('BLQS size.length', blqs.size.length, 1); + +testThrows('BLQS size with undefined', () => blqs.size()); +testThrows('BLQS size with null', () => blqs.size(null)); + +const blqs0 = new ByteLengthQueuingStrategy({ highWaterMark: 0 }); +test('BLQS highWaterMark 0', blqs0.highWaterMark, 0); + +test('BLQS size is same function each access', blqs.size === blqs.size, true); +test('BLQS size is same across instances', blqs.size === blqs0.size, true); + +testThrows('BLQS requires new', () => ByteLengthQueuingStrategy({ highWaterMark: 1 })); +testThrows('BLQS requires options', () => new ByteLengthQueuingStrategy()); +testThrows('BLQS requires highWaterMark', () => new ByteLengthQueuingStrategy({})); + +test('BLQS typeof', typeof ByteLengthQueuingStrategy, 'function'); +test('CQS size !== BLQS size', cqs.size !== blqs.size, true); + +test('CQS toStringTag', Object.prototype.toString.call(cqs), '[object CountQueuingStrategy]'); +test('BLQS toStringTag', Object.prototype.toString.call(blqs), '[object ByteLengthQueuingStrategy]'); + +const cqs_str = new CountQueuingStrategy({ highWaterMark: '10' }); +test('CQS highWaterMark string coercion', cqs_str.highWaterMark, 10); + +const blqs_str = new ByteLengthQueuingStrategy({ highWaterMark: '256' }); +test('BLQS highWaterMark string coercion', blqs_str.highWaterMark, 256); + +test('CQS highWaterMark false coercion', new CountQueuingStrategy({ highWaterMark: false }).highWaterMark, 0); +test('CQS highWaterMark true coercion', new CountQueuingStrategy({ highWaterMark: true }).highWaterMark, 1); + +const cqs_frac = new CountQueuingStrategy({ highWaterMark: 2.5 }); +test('CQS fractional highWaterMark', cqs_frac.highWaterMark, 2.5); + +const buf = new Uint8Array([1, 2, 3, 4]); +test('BLQS size with Uint8Array', blqs.size(buf), 4); + +testThrows('BLQS size propagates getter throw', () => { + blqs.size({ + get byteLength() { + throw new Error('boom'); + } + }); +}); + +summary(); diff --git a/include/internal.h b/include/internal.h index a865d02..6c24df7 100644 --- a/include/internal.h +++ b/include/internal.h @@ -5,6 +5,7 @@ #include "object.h" #include "pool.h" #include "arena.h" +#include "descriptors.h" #include "esm/loader.h" #include @@ -213,6 +214,19 @@ struct ant_isolate_t { bool thrown_exists; }; +typedef struct { + ant_offset_t len; + char bytes[]; +} ant_flat_string_t; + +typedef struct { + ant_offset_t len; + uint8_t depth; + ant_value_t left; + ant_value_t right; + ant_value_t cached; +} ant_rope_heap_t; + typedef struct { const char *ptr; size_t len; @@ -361,17 +375,18 @@ static inline bool str_is_heap_rope(ant_value_t value) { return vtype(value) == T_STR && ((vdata(value) & 1ULL) != 0); } -typedef struct { - ant_offset_t len; - char bytes[]; -} ant_flat_string_t; +static inline ant_value_t js_make_ctor(ant_t *js, ant_cfunc_t fn, ant_value_t proto, const char *name, size_t nlen) { + ant_value_t obj = js_mkobj(js); + js_set_slot(obj, SLOT_CFUNC, js_mkfun(fn)); + js_mkprop_fast(js, obj, "prototype", 9, proto); + js_mkprop_fast(js, obj, "name", 4, js_mkstr(js, name, nlen)); + js_set_descriptor(js, obj, "name", 4, 0); -typedef struct { - ant_offset_t len; - uint8_t depth; - ant_value_t left; - ant_value_t right; - ant_value_t cached; -} ant_rope_heap_t; + ant_value_t fn_val = js_obj_to_func(obj); + js_set(js, proto, "constructor", fn_val); + js_set_descriptor(js, proto, "constructor", 11, JS_DESC_W | JS_DESC_C); + + return fn_val; +} #endif diff --git a/include/streams/queuing.h b/include/streams/queuing.h new file mode 100644 index 0000000..dd399c5 --- /dev/null +++ b/include/streams/queuing.h @@ -0,0 +1,6 @@ +#ifndef STREAMS_QUEUING_H +#define STREAMS_QUEUING_H + +void init_queuing_strategies_module(void); + +#endif diff --git a/sources.json b/sources.json index b7ab1b7..04d7d62 100644 --- a/sources.json +++ b/sources.json @@ -1,10 +1,10 @@ { "engine": { - "patterns": ["src/*.c", "src/gc/*.c", "src/highlight/*.c", "src/esm/*.c", "src/cli/*.c", "src/modules/*.c", "src/silver/*.c"], + "patterns": ["src/*.c", "src/gc/*.c", "src/highlight/*.c", "src/esm/*.c", "src/cli/*.c", "src/modules/*.c", "src/streams/*.c", "src/silver/*.c"], "exclude": ["src/main.c", "src/watch.c"] }, "library": { - "patterns": ["src/*.c", "src/gc/*.c", "src/highlight/*.c", "src/esm/*.c", "src/modules/*.c", "src/silver/*.c"], + "patterns": ["src/*.c", "src/gc/*.c", "src/highlight/*.c", "src/esm/*.c", "src/modules/*.c", "src/streams/*.c", "src/silver/*.c"], "exclude": ["src/main.c", "src/watch.c"] }, "core": { diff --git a/src/main.c b/src/main.c index 63a0c01..f76d166 100644 --- a/src/main.c +++ b/src/main.c @@ -79,6 +79,7 @@ #include "modules/headers.h" #include "modules/blob.h" #include "modules/formdata.h" +#include "streams/queuing.h" int js_result = EXIT_SUCCESS; typedef int (*cmd_fn)(int argc, char **argv); @@ -595,6 +596,7 @@ int main(int argc, char *argv[]) { init_date_module(); init_regex_module(); init_collections_module(); + init_queuing_strategies_module(); init_builtin_module(); init_buffer_module(); init_fs_module(); diff --git a/src/modules/blob.c b/src/modules/blob.c index e23acad..6553d46 100644 --- a/src/modules/blob.c +++ b/src/modules/blob.c @@ -405,20 +405,6 @@ static ant_value_t js_file_ctor(ant_t *js, ant_value_t *args, int nargs) { return obj; } -static ant_value_t make_ctor(ant_t *js, ant_cfunc_t fn, ant_value_t proto, const char *name, size_t nlen) { - ant_value_t obj = js_mkobj(js); - js_set_slot(obj, SLOT_CFUNC, js_mkfun(fn)); - js_mkprop_fast(js, obj, "prototype", 9, proto); - js_mkprop_fast(js, obj, "name", 4, js_mkstr(js, name, nlen)); - js_set_descriptor(js, obj, "name", 4, 0); - - ant_value_t fn_val = js_obj_to_func(obj); - js_set(js, proto, "constructor", fn_val); - js_set_descriptor(js, proto, "constructor", 11, JS_DESC_W | JS_DESC_C); - - return fn_val; -} - void init_blob_module(void) { ant_t *js = rt->js; ant_value_t g = js_glob(js); @@ -434,7 +420,7 @@ void init_blob_module(void) { js_set(js, g_blob_proto, "stream", js_mkfun(js_blob_stream)); js_set_sym(js, g_blob_proto, get_toStringTag_sym(), js_mkstr(js, "Blob", 4)); - ant_value_t blob_ctor = make_ctor(js, js_blob_ctor, g_blob_proto, "Blob", 4); + ant_value_t blob_ctor = js_make_ctor(js, js_blob_ctor, g_blob_proto, "Blob", 4); js_set(js, g, "Blob", blob_ctor); js_set_descriptor(js, g, "Blob", 4, JS_DESC_W | JS_DESC_C); @@ -446,7 +432,7 @@ void init_blob_module(void) { js_set_getter_desc(js, g_file_proto, "lastModified", 12, js_mkfun(file_get_last_modified), JS_DESC_C); js_set_sym(js, g_file_proto, get_toStringTag_sym(), js_mkstr(js, "File", 4)); - ant_value_t file_ctor = make_ctor(js, js_file_ctor, g_file_proto, "File", 4); + ant_value_t file_ctor = js_make_ctor(js, js_file_ctor, g_file_proto, "File", 4); js_set(js, g, "File", file_ctor); js_set_descriptor(js, g, "File", 4, JS_DESC_W | JS_DESC_C); diff --git a/src/modules/events.c b/src/modules/events.c index 9a2e79d..b758e83 100644 --- a/src/modules/events.c +++ b/src/modules/events.c @@ -631,21 +631,6 @@ static ant_value_t js_eventemitter_eventNames(ant_t *js, ant_value_t *args, int return result; } -static ant_value_t make_event_ctor(ant_t *js, ant_cfunc_t fn, ant_value_t proto, const char *name, size_t nlen) { - ant_value_t ctor_obj = js_mkobj(js); - js_set_slot(ctor_obj, SLOT_CFUNC, js_mkfun(fn)); - - js_mkprop_fast(js, ctor_obj, "prototype", 9, proto); - js_mkprop_fast(js, ctor_obj, "name", 4, js_mkstr(js, name, nlen)); - js_set_descriptor(js, ctor_obj, "name", 4, 0); - - ant_value_t fn_val = js_obj_to_func(ctor_obj); - js_set(js, proto, "constructor", fn_val); - js_set_descriptor(js, proto, "constructor", 11, JS_DESC_W | JS_DESC_C); - - return fn_val; -} - ant_value_t events_library(ant_t *js) { ant_value_t lib = js_mkobj(js); ant_value_t eventemitter_ctor = js_mkobj(js); @@ -691,7 +676,7 @@ void init_events_module(void) { js_set(js, g_event_proto, "AT_TARGET", js_mknum(2)); js_set(js, g_event_proto, "BUBBLING_PHASE", js_mknum(3)); - ant_value_t event_fn = make_event_ctor(js, js_event_ctor, g_event_proto, "Event", 5); + ant_value_t event_fn = js_make_ctor(js, js_event_ctor, g_event_proto, "Event", 5); js_set(js, event_fn, "NONE", js_mknum(0)); js_set(js, event_fn, "CAPTURING_PHASE", js_mknum(1)); js_set(js, event_fn, "AT_TARGET", js_mknum(2)); @@ -702,21 +687,21 @@ void init_events_module(void) { js_set_proto_init(g_customevent_proto, g_event_proto); js_set_sym(js, g_customevent_proto, get_toStringTag_sym(), js_mkstr(js, "CustomEvent", 11)); - ant_value_t customevent_fn = make_event_ctor(js, js_customevent_ctor, g_customevent_proto, "CustomEvent", 11); + ant_value_t customevent_fn = js_make_ctor(js, js_customevent_ctor, g_customevent_proto, "CustomEvent", 11); js_set(js, global, "CustomEvent", customevent_fn); g_errorevent_proto = js_mkobj(js); js_set_proto_init(g_errorevent_proto, g_event_proto); js_set_sym(js, g_errorevent_proto, get_toStringTag_sym(), js_mkstr(js, "ErrorEvent", 10)); - ant_value_t errorevent_fn = make_event_ctor(js, js_errorevent_ctor, g_errorevent_proto, "ErrorEvent", 10); + ant_value_t errorevent_fn = js_make_ctor(js, js_errorevent_ctor, g_errorevent_proto, "ErrorEvent", 10); js_set(js, global, "ErrorEvent", errorevent_fn); g_promiserejectionevent_proto = js_mkobj(js); js_set_proto_init(g_promiserejectionevent_proto, g_event_proto); js_set_sym(js, g_promiserejectionevent_proto, get_toStringTag_sym(), js_mkstr(js, "PromiseRejectionEvent", 21)); - ant_value_t pre_fn = make_event_ctor(js, js_promiserejectionevent_ctor, g_promiserejectionevent_proto, "PromiseRejectionEvent", 21); + ant_value_t pre_fn = js_make_ctor(js, js_promiserejectionevent_ctor, g_promiserejectionevent_proto, "PromiseRejectionEvent", 21); js_set(js, global, "PromiseRejectionEvent", pre_fn); ant_value_t eventtarget_proto = js_mkobj(js); diff --git a/src/modules/textcodec.c b/src/modules/textcodec.c index 97de419..2fe0f44 100644 --- a/src/modules/textcodec.c +++ b/src/modules/textcodec.c @@ -111,20 +111,6 @@ static bool get_buffer_source(ant_t *js, ant_value_t arg, const uint8_t **out, s return false; } -static ant_value_t make_ctor(ant_t *js, ant_cfunc_t fn, ant_value_t proto, const char *name, size_t nlen) { - ant_value_t obj = js_mkobj(js); - js_set_slot(obj, SLOT_CFUNC, js_mkfun(fn)); - js_mkprop_fast(js, obj, "prototype", 9, proto); - js_mkprop_fast(js, obj, "name", 4, js_mkstr(js, name, nlen)); - js_set_descriptor(js, obj, "name", 4, 0); - - ant_value_t fn_val = js_obj_to_func(obj); - js_set(js, proto, "constructor", fn_val); - js_set_descriptor(js, proto, "constructor", 11, JS_DESC_W | JS_DESC_C); - - return fn_val; -} - static ant_value_t js_textencoder_get_encoding(ant_t *js, ant_value_t *args, int nargs) { return js_mkstr(js, "utf-8", 5); } @@ -441,7 +427,7 @@ void init_textcodec_module(void) { js_set(js, g_textencoder_proto, "encodeInto", js_mkfun(js_textencoder_encode_into)); js_set_sym(js, g_textencoder_proto, get_toStringTag_sym(), js_mkstr(js, "TextEncoder", 11)); - ant_value_t te_ctor = make_ctor(js, js_textencoder_ctor, g_textencoder_proto, "TextEncoder", 11); + ant_value_t te_ctor = js_make_ctor(js, js_textencoder_ctor, g_textencoder_proto, "TextEncoder", 11); js_set(js, g, "TextEncoder", te_ctor); js_set_descriptor(js, g, "TextEncoder", 11, JS_DESC_W | JS_DESC_C); @@ -452,7 +438,7 @@ void init_textcodec_module(void) { js_set(js, g_textdecoder_proto, "decode", js_mkfun(js_textdecoder_decode)); js_set_sym(js, g_textdecoder_proto, get_toStringTag_sym(), js_mkstr(js, "TextDecoder", 11)); - ant_value_t td_ctor = make_ctor(js, js_textdecoder_ctor, g_textdecoder_proto, "TextDecoder", 11); + ant_value_t td_ctor = js_make_ctor(js, js_textdecoder_ctor, g_textdecoder_proto, "TextDecoder", 11); js_set(js, g, "TextDecoder", td_ctor); js_set_descriptor(js, g, "TextDecoder", 11, JS_DESC_W | JS_DESC_C); } diff --git a/src/streams/queuing.c b/src/streams/queuing.c new file mode 100644 index 0000000..ee22242 --- /dev/null +++ b/src/streams/queuing.c @@ -0,0 +1,111 @@ +#include + +#include "ant.h" +#include "errors.h" +#include "runtime.h" +#include "internal.h" +#include "descriptors.h" + +#include "modules/symbol.h" +#include "streams/queuing.h" + +static ant_value_t g_count_qs_proto; +static ant_value_t g_bytelength_qs_proto; + +static ant_value_t js_count_size(ant_t *js, ant_value_t *args, int nargs) { + return js_mknum(1); +} + +static ant_value_t js_bytelength_size(ant_t *js, ant_value_t *args, int nargs) { + if (nargs < 1 || vtype(args[0]) == T_UNDEF || is_null(args[0])) + return js_mkerr_typed(js, JS_ERR_TYPE, "Cannot get property 'byteLength' of undefined or null"); + if (!is_object_type(args[0])) return js_mkundef(); + + ant_value_t bl = js_get(js, args[0], "byteLength"); + if (is_err(bl)) return bl; + if (vtype(bl) == T_NUM) return bl; + + return js_mkundef(); +} + +static ant_value_t js_qs_get_highwatermark(ant_t *js, ant_value_t *args, int nargs) { + ant_value_t s = js_get_slot(js->this_val, SLOT_DATA); + if (vtype(s) == T_NUM) return s; + return js_mkundef(); +} + +static ant_value_t js_qs_get_size(ant_t *js, ant_value_t *args, int nargs) { + ant_value_t proto = js_get_proto(js, js->this_val); + return js_get_slot(proto, SLOT_DATA); +} + +static ant_value_t qs_ctor(ant_t *js, ant_value_t *args, int nargs, ant_value_t proto, const char *name) { + if (vtype(js->new_target) == T_UNDEF) + return js_mkerr_typed(js, JS_ERR_TYPE, "%s constructor requires 'new'", name); + + if (nargs < 1 || vtype(args[0]) == T_UNDEF || is_null(args[0])) + return js_mkerr_typed(js, JS_ERR_TYPE, "Failed to construct '%s': 1 argument required", name); + + if (!is_object_type(args[0])) + return js_mkerr_typed(js, JS_ERR_TYPE, "Failed to construct '%s': argument is not an object", name); + + ant_value_t hv = js_get(js, args[0], "highWaterMark"); + if (is_err(hv)) return hv; + if (vtype(hv) == T_UNDEF) + return js_mkerr_typed(js, JS_ERR_TYPE, "Failed to construct '%s': member highWaterMark is required", name); + + double hwm = js_to_number(js, hv); + + ant_value_t obj = js_mkobj(js); + ant_value_t resolved = js_instance_proto_from_new_target(js, proto); + + if (is_object_type(resolved)) js_set_proto_init(obj, resolved); + js_set_slot(obj, SLOT_DATA, js_mknum(hwm)); + + return obj; +} + +static ant_value_t js_count_qs_ctor(ant_t *js, ant_value_t *args, int nargs) { + return qs_ctor(js, args, nargs, g_count_qs_proto, "CountQueuingStrategy"); +} + +static ant_value_t js_bytelength_qs_ctor(ant_t *js, ant_value_t *args, int nargs) { + return qs_ctor(js, args, nargs, g_bytelength_qs_proto, "ByteLengthQueuingStrategy"); +} + +static ant_value_t make_size_fn(ant_t *js, ant_cfunc_t cfunc, int length) { + ant_value_t obj = js_mkobj(js); + + js_set_slot(obj, SLOT_CFUNC, js_mkfun(cfunc)); + js_mkprop_fast(js, obj, "name", 4, js_mkstr(js, "size", 4)); + js_set_descriptor(js, obj, "name", 4, 0); + js_mkprop_fast(js, obj, "length", 6, js_mknum(length)); + js_set_descriptor(js, obj, "length", 6, 0); + + return js_obj_to_func(obj); +} + +void init_queuing_strategies_module(void) { + ant_t *js = rt->js; + ant_value_t g = js_glob(js); + + g_count_qs_proto = js_mkobj(js); + js_set_slot(g_count_qs_proto, SLOT_DATA, make_size_fn(js, js_count_size, 0)); + js_set_getter_desc(js, g_count_qs_proto, "highWaterMark", 13, js_mkfun(js_qs_get_highwatermark), JS_DESC_C); + js_set_getter_desc(js, g_count_qs_proto, "size", 4, js_mkfun(js_qs_get_size), JS_DESC_C); + js_set_sym(js, g_count_qs_proto, get_toStringTag_sym(), js_mkstr(js, "CountQueuingStrategy", 20)); + + ant_value_t cqs_ctor = js_make_ctor(js, js_count_qs_ctor, g_count_qs_proto, "CountQueuingStrategy", 20); + js_set(js, g, "CountQueuingStrategy", cqs_ctor); + js_set_descriptor(js, g, "CountQueuingStrategy", 20, JS_DESC_W | JS_DESC_C); + + g_bytelength_qs_proto = js_mkobj(js); + js_set_slot(g_bytelength_qs_proto, SLOT_DATA, make_size_fn(js, js_bytelength_size, 1)); + js_set_getter_desc(js, g_bytelength_qs_proto, "highWaterMark", 13, js_mkfun(js_qs_get_highwatermark), JS_DESC_C); + js_set_getter_desc(js, g_bytelength_qs_proto, "size", 4, js_mkfun(js_qs_get_size), JS_DESC_C); + js_set_sym(js, g_bytelength_qs_proto, get_toStringTag_sym(), js_mkstr(js, "ByteLengthQueuingStrategy", 25)); + + ant_value_t blqs_ctor = js_make_ctor(js, js_bytelength_qs_ctor, g_bytelength_qs_proto, "ByteLengthQueuingStrategy", 25); + js_set(js, g, "ByteLengthQueuingStrategy", blqs_ctor); + js_set_descriptor(js, g, "ByteLengthQueuingStrategy", 25, JS_DESC_W | JS_DESC_C); +}