diff --git a/docs/02.API-REFERENCE.md b/docs/02.API-REFERENCE.md index c78562847..d493c6562 100644 --- a/docs/02.API-REFERENCE.md +++ b/docs/02.API-REFERENCE.md @@ -12011,3 +12011,72 @@ main (void) return 0; } ``` + +# Container Functions + +## jerry_get_array_from_container + +**Summary** + +Return a new array containing elements from a Container, or a Container Iterator. Sets the is_key_value_p to true. +if the container object contains key-value structure and false if not. + +*Notes* +- The return value will be an empty array if the Map/Set or Iterator object was empty or finished. +- This API function depends on a build option (`JERRY_BUILTIN_CONTAINER`) and can be checked + runtime with the `JERRY_FEATURE_MAP, JERRY_FEATURE_SET, JERRY_FEATURE_WEAKMAP, JERRY_FEATURE_WEAKSET` + feature enum values. + see: [jerry_is_feature_enabled](#jerry_is_feature_enabled). +- The es.next profile enables this by default. + +*New in version [[NEXT_RELEASE]]*. + +**Prototype** + +```c +jerry_value_t +jerry_get_array_from_container(jerry_value_t value, + bool *is_key_value_p); +``` + +- `value` - Map/Set or iterator object +- `is_key_value` - Will be set to `true` if the given container has key-value pairs, `false` otherwise. +- return value + - jerry_value_t containing an array of values from the Map/Set or iterator object + - Error if the `value` is nor a Container or a Container Iterator. + - `undefined` if the `value` is undefined/null. +**Example** + +[doctest]: # () + +```c +#include "jerryscript.h" +int +main (void) +{ + jerry_init (JERRY_INIT_EMPTY); + + jerry_char_t src[] = "var map = new Map(); map.set(1,2); map.entries()"; + jerry_value_t iterable = jerry_eval (src, sizeof (src) - 1, JERRY_PARSE_NO_OPTS); + + bool is_key_value_container = false; + jerry_value_t buffer_from_map = jerry_get_array_from_container (iterable, &is_key_value_container); + + /* + The buffer_from_map contains two elements: 1 and 2, which is the key/value pair of the only item in the set. + is_key_value set to true, as the original is a key-value structure (a Map Iterator) + */ + + jerry_release_value (iterable); + jerry_release_value (buffer_from_map); + + jerry_cleanup (); + + return 0; +} +``` + +**See also** + +- [jerry_create_container](#jerry_create_container) +- [jerry_container_type_t](#jerry_container_type_t) diff --git a/jerry-core/api/jerry.c b/jerry-core/api/jerry.c index 2906e3254..73488cb28 100644 --- a/jerry-core/api/jerry.c +++ b/jerry-core/api/jerry.c @@ -32,6 +32,7 @@ #include "ecma-gc.h" #include "ecma-helpers.h" #include "ecma-init-finalize.h" +#include "ecma-iterator-object.h" #include "ecma-lex-env.h" #include "lit-char-helpers.h" #include "ecma-literal-storage.h" @@ -6446,7 +6447,7 @@ jerry_create_container (jerry_container_type_t container_type, /**< Type of the JERRY_UNUSED (arguments_list_p); JERRY_UNUSED (arguments_list_len); JERRY_UNUSED (container_type); - return jerry_throw (ecma_raise_type_error (ECMA_ERR_MSG ("Containers are disabled"))); + return jerry_throw (ecma_raise_type_error (ECMA_ERR_MSG (ecma_error_container_not_supported_p))); #endif /* JERRY_BUILTIN_CONTAINER */ } /* jerry_create_container */ @@ -6507,6 +6508,119 @@ jerry_get_container_type (const jerry_value_t value) /**< the container object * return JERRY_CONTAINER_TYPE_INVALID; } /* jerry_get_container_type */ +/** + * Return a new array containing elements from a Container or a Container Iterator. + * Sets the boolean input value to `true` if the container object has key/value pairs. + * + * Note: + * the returned value must be freed with a jerry_release_value call + * + * @return an array of items for maps/sets or their iterators, error otherwise + */ +jerry_value_t +jerry_get_array_from_container (jerry_value_t value, /**< the container or iterator object */ + bool *is_key_value_p) /**< [out] is key-value structure */ +{ + jerry_assert_api_available (); + +#if JERRY_BUILTIN_CONTAINER + const char *container_needed = ECMA_ERR_MSG ("Value is not a Container or Iterator"); + + if (!ecma_is_value_object (value)) + { + return jerry_throw (ecma_raise_type_error (container_needed)); + } + + ecma_object_t *obj_p = ecma_get_object_from_value (value); + + if (ecma_get_object_type (obj_p) != ECMA_OBJECT_TYPE_CLASS) + { + return jerry_throw (ecma_raise_type_error (container_needed)); + } + + ecma_extended_object_t *ext_obj_p = (ecma_extended_object_t *) obj_p; + + uint32_t entry_count; + uint8_t entry_size; + + uint32_t index = 0; + uint8_t iterator_kind = ECMA_ITERATOR__COUNT; + ecma_value_t *start_p; + + *is_key_value_p = false; + + if (ext_obj_p->u.cls.type == ECMA_OBJECT_CLASS_MAP_ITERATOR + || ext_obj_p->u.cls.type == ECMA_OBJECT_CLASS_SET_ITERATOR) + { + ecma_value_t iterated_value = ext_obj_p->u.cls.u3.iterated_value; + + if (ecma_is_value_empty (iterated_value)) + { + return ecma_op_new_array_object_from_collection (ecma_new_collection (), false); + } + + ecma_extended_object_t *map_object_p = (ecma_extended_object_t *) (ecma_get_object_from_value (iterated_value)); + + ecma_collection_t *container_p = ECMA_GET_INTERNAL_VALUE_POINTER (ecma_collection_t, map_object_p->u.cls.u3.value); + entry_count = ECMA_CONTAINER_ENTRY_COUNT (container_p); + index = ext_obj_p->u.cls.u2.iterator_index; + + entry_size = ecma_op_container_entry_size (map_object_p->u.cls.u2.container_id); + start_p = ECMA_CONTAINER_START (container_p); + + iterator_kind = ext_obj_p->u.cls.u1.iterator_kind; + } + else if (jerry_get_container_type (value) != JERRY_CONTAINER_TYPE_INVALID) + { + ecma_collection_t *container_p = ECMA_GET_INTERNAL_VALUE_POINTER (ecma_collection_t, ext_obj_p->u.cls.u3.value); + entry_count = ECMA_CONTAINER_ENTRY_COUNT (container_p); + entry_size = ecma_op_container_entry_size (ext_obj_p->u.cls.u2.container_id); + + index = 0; + iterator_kind = ECMA_ITERATOR_KEYS; + start_p = ECMA_CONTAINER_START (container_p); + + if (ext_obj_p->u.cls.u2.container_id == LIT_MAGIC_STRING_MAP_UL + || ext_obj_p->u.cls.u2.container_id == LIT_MAGIC_STRING_WEAKMAP_UL) + { + iterator_kind = ECMA_ITERATOR_ENTRIES; + } + } + else + { + return jerry_throw (ecma_raise_type_error (container_needed)); + } + + *is_key_value_p = (iterator_kind == ECMA_ITERATOR_ENTRIES); + ecma_collection_t *collection_buffer = ecma_new_collection (); + + for (uint32_t i = index; i < entry_count; i += entry_size) + { + ecma_value_t *entry_p = start_p + i; + + if (ecma_is_value_empty (*entry_p)) + { + continue; + } + + if (iterator_kind != ECMA_ITERATOR_VALUES) + { + ecma_collection_push_back (collection_buffer, ecma_copy_value_if_not_object (entry_p[0])); + } + + if (iterator_kind != ECMA_ITERATOR_KEYS) + { + ecma_collection_push_back (collection_buffer, ecma_copy_value_if_not_object (entry_p[1])); + } + } + return ecma_op_new_array_object_from_collection (collection_buffer, false); +#else /* JERRY_BUILTIN_CONTAINER */ + JERRY_UNUSED (value); + JERRY_UNUSED (is_key_value_p); + return jerry_throw (ecma_raise_type_error (ECMA_ERR_MSG (ecma_error_container_not_supported_p))); +#endif +} /* jerry_get_array_from_container */ + /** * @} */ diff --git a/jerry-core/ecma/base/ecma-errors.c b/jerry-core/ecma/base/ecma-errors.c index a5bba8734..ea66b9dd7 100644 --- a/jerry-core/ecma/base/ecma-errors.c +++ b/jerry-core/ecma/base/ecma-errors.c @@ -76,6 +76,13 @@ const char * const ecma_error_data_view_not_supported_p = "DataView support is d const char * const ecma_error_bigint_not_supported_p = "BigInt support is disabled"; #endif /* !JERRY_BUILTIN_BIGINT */ +#if !JERRY_BUILTIN_CONTAINER +/** + * Error message, if Container support is disabled + */ +const char * const ecma_error_container_not_supported_p = "Container support is disabled"; +#endif /* JERRY_BUILTIN_CONTAINER */ + #if JERRY_MODULE_SYSTEM /** * Error message, if argument is not a module diff --git a/jerry-core/ecma/base/ecma-errors.h b/jerry-core/ecma/base/ecma-errors.h index cb66ff6e0..01eed06d2 100644 --- a/jerry-core/ecma/base/ecma-errors.h +++ b/jerry-core/ecma/base/ecma-errors.h @@ -51,6 +51,10 @@ extern const char * const ecma_error_data_view_not_supported_p; extern const char * const ecma_error_bigint_not_supported_p; #endif /* !JERRY_BUILTIN_BIGINT */ +#if !JERRY_BUILTIN_CONTAINER +extern const char * const ecma_error_container_not_supported_p; +#endif /* !JERRY_BUILTIN_CONTAINER */ + #if JERRY_MODULE_SYSTEM extern const char * const ecma_error_not_module_p; extern const char * const ecma_error_unknown_export_p; diff --git a/jerry-core/include/jerryscript-core.h b/jerry-core/include/jerryscript-core.h index 63f778032..56c15d789 100644 --- a/jerry-core/include/jerryscript-core.h +++ b/jerry-core/include/jerryscript-core.h @@ -422,6 +422,7 @@ jerry_value_t jerry_create_container (jerry_container_type_t container_type, const jerry_value_t *arguments_list_p, jerry_length_t arguments_list_len); jerry_container_type_t jerry_get_container_type (const jerry_value_t value); +jerry_value_t jerry_get_array_from_container (jerry_value_t value, bool *is_key_value_p); /** * @} diff --git a/tests/unit-core/test-container.c b/tests/unit-core/test-container.c index d76898a00..51bb0d992 100644 --- a/tests/unit-core/test-container.c +++ b/tests/unit-core/test-container.c @@ -34,6 +34,41 @@ static const jerry_object_native_info_t native_info = .offset_of_references = 0, }; +static jerry_value_t +create_array_from_container_handler (const jerry_call_info_t *call_info_p, + const jerry_value_t args_p[], + const jerry_length_t args_count) +{ + JERRY_UNUSED (call_info_p); + + if (args_count < 2) + { + return jerry_create_undefined (); + } + + bool is_key_value_pairs = false; + jerry_value_t result = jerry_get_array_from_container (args_p[0], &is_key_value_pairs); + + TEST_ASSERT (is_key_value_pairs == jerry_get_boolean_value (args_p[1])); + return result; +} /* create_array_from_container_handler */ + +static void +run_eval (const char *source_p) +{ + jerry_value_t result = jerry_eval ((const jerry_char_t *) source_p, strlen (source_p), 0); + + TEST_ASSERT (!jerry_value_is_error (result)); + jerry_release_value (result); +} /* run_eval */ + +static void +run_eval_error (const char *source_p) +{ + jerry_value_t result = jerry_eval ((const jerry_char_t *) source_p, strlen (source_p), 0); + jerry_release_value (result); +} /* run_eval_error */ + int main (void) { @@ -60,6 +95,15 @@ main (void) jerry_value_t global_weakmap = jerry_get_property (global, weakmap_str); jerry_value_t global_weakset = jerry_get_property (global, weakset_str); + jerry_value_t function = jerry_create_external_function (create_array_from_container_handler); + jerry_value_t name = jerry_create_string ((const jerry_char_t *) "create_array_from_container"); + jerry_value_t res = jerry_set_property (global, name, function); + TEST_ASSERT (!jerry_value_is_error (res)); + + jerry_release_value (res); + jerry_release_value (name); + jerry_release_value (function); + jerry_release_value (global); jerry_release_value (map_str); @@ -122,6 +166,87 @@ main (void) jerry_gc (JERRY_GC_PRESSURE_LOW); TEST_ASSERT (global_counter == 1); + run_eval ("function assert(v) {\n" + " if(v !== true)\n" + " throw 'Assertion failed!'\n" + "}"); + + run_eval ("function test_values(arr1, arr2) {\n" + " assert(Array.isArray(arr1));\n" + " assert(arr1.length == arr2.length);\n" + " for(let i = 0; i < arr1.length; i++) {\n" + " assert(arr1[i] === arr2[i]);\n" + " }\n" + "}\n"); + + run_eval ("var map = new Map();\n" + "map.set(1, 3.14);\n" + "map.set(2, true);\n" + "map.set(3, 'foo');\n" + "var set = new Set();\n" + "set.add(3.14);\n" + "set.add(true);\n" + "set.add('foo');\n" + "var obj = { x:3, y:'foo'};\n" + "var b_int = 1n;\n" + "var obj_bint_map = new Map();\n" + "obj_bint_map.set(1, obj);\n" + "obj_bint_map.set(2, b_int);\n"); + + run_eval ("var result = create_array_from_container(map, true);\n" + "test_values(result, [1, 3.14, 2, true, 3, 'foo']);"); + + run_eval ("var result = create_array_from_container(set, false);\n" + "test_values(result, [3.14, true, 'foo']);"); + + run_eval ("var result = create_array_from_container(map.entries(), true);\n" + "test_values(result, [1, 3.14, 2, true, 3, 'foo']);"); + + run_eval ("var result = create_array_from_container(map.keys(), false);\n" + "test_values(result, [1, 2, 3,]);"); + + run_eval ("var result = create_array_from_container(map.values(), false);\n" + "test_values(result, [3.14, true, 'foo']);"); + + run_eval ("var result = create_array_from_container(obj_bint_map, true)\n" + "test_values(result, [1, obj, 2, b_int]);"); + + run_eval ("var map = new Map();\n" + "map.set(1, 1);\n" + "var iter = map.entries();\n" + "iter.next();\n" + "var result = create_array_from_container(iter, true);\n" + "assert(Array.isArray(result));\n" + "assert(result.length == 0);"); + + run_eval ("var ws = new WeakSet();\n" + "var foo = {};\n" + "var bar = {};\n" + "ws.add(foo);\n" + "ws.add(bar);\n" + "var result = create_array_from_container(ws, false);\n" + "test_values(result, [foo, bar]);\n"); + + run_eval ("var ws = new WeakMap();\n" + "var foo = {};\n" + "var bar = {};\n" + "ws.set(foo, 37);\n" + "ws.set(bar, 'asd');\n" + "var result = create_array_from_container(ws, true);\n" + "test_values(result, [foo, 37, bar, 'asd']);\n"); + + run_eval_error ("var iter = null;\n" + "var result = create_array_from_container(iter, false);\n" + "assert(result instanceof Error);"); + + run_eval_error ("var iter = 3;\n" + "var result = create_array_from_container(iter, false);\n" + "assert(result instanceof Error);"); + + run_eval_error ("var iter = [3.14, true, 'foo'].entries();\n" + "var result = create_array_from_container(iter, false);\n" + "assert(result instanceof Error);"); + jerry_cleanup (); return 0; } /* main */