diff --git a/docs/09.EXT-REFERENCE-ARG.md b/docs/09.EXT-REFERENCE-ARG.md index bd17f9c01..00136f20c 100644 --- a/docs/09.EXT-REFERENCE-ARG.md +++ b/docs/09.EXT-REFERENCE-ARG.md @@ -56,6 +56,27 @@ typedef struct - [jerryx_arg_object_properties](#jerryx_arg_object_properties) +## jerryx_arg_array_items_t + +**Summary** + +The structure is used in `jerryx_arg_array`. It provides the array items' corresponding +JS-to-C mappings and count. + +**Prototype** + +```c +typedef struct +{ + const jerryx_arg_t *c_arg_p; /**< points to the array of transformation steps */ + jerry_length_t c_arg_cnt; /**< the count of the `c_arg_p` array */ +} jerryx_arg_array_items_t; +``` + +**See also** + +- [jerryx_arg_array](#jerryx_arg_array) + ## jerryx_arg_transform_func_t **Summary** @@ -310,6 +331,35 @@ jerryx_arg_transform_object_properties (const jerry_value_t obj_val, - [jerryx_arg_object_properties](#jerryx_arg_object_properties) +## jerryx_arg_transform_array + +**Summary** + +Validate the JS array and assign its items to the native arguments. + +*Note*: This function transforms items of a single JS array into native C values. +To transform multiple JS arguments in one pass, please use `jerryx_arg_array` together with +`jerryx_arg_transform_this_and_args` or `jerryx_arg_transform_args`. + +**Prototype** + +```c +jerry_value_t +jerryx_arg_transform_array (const jerry_value_t array_val, + const jerryx_arg_t *c_arg_p, + jerry_length_t c_arg_cnt); + +``` + + - `array_val` - the JS array. + - `c_arg_p` - points to the array of validation/transformation steps + - `c_arg_cnt` - the count of the `c_arg_p` array. + - return value - a `jerry_value_t` representing `undefined` if all validators passed or an `Error` if a validator failed. + +**See also** + +- [jerryx_arg_array](#jerryx_arg_array) + # Helpers for commonly used validations @@ -580,6 +630,97 @@ my_external_handler (const jerry_value_t function_obj, - [jerryx_arg_transform_this_and_args](#jerryx_arg_transform_this_and_args) - [jerryx_arg_transform_object_properties](#jerryx_arg_transform_object_properties) +## jerryx_arg_array + +**Summary** + +Create a validation/transformation step (`jerryx_arg_t`) that expects to +consume one `array` JS argument and call `jerryx_arg_transform_array_items` inside +to transform its items to native arguments. +User should prepare the `jerryx_arg_array_items_t` instance, and pass it to this function. + +**Prototype** + +```c +static inline jerryx_arg_t +jerryx_arg_array (const jerryx_arg_array_items_t *array_items_p, jerryx_arg_optional_t opt_flag); +``` + - return value - the created `jerryx_arg_t` instance. + - `array_items_p` - provides items information for transform. + - `opt_flag` - whether the argument is optional. + +**Example** + +[doctest]: # (test="compile") + +```c +#include "jerryscript.h" +#include "jerryscript-ext/arg.h" + +/** + * The binding function expects args_p[0] is an array, which has 3 items: + * first: boolean + * second: number + * third: number, optional + */ +static jerry_value_t +my_external_handler (const jerry_value_t function_obj, + const jerry_value_t this_val, + const jerry_value_t args_p[], + const jerry_length_t args_count) +{ + bool required_bool; + double required_num; + double optional_num = 1234.567; // default value + + /* "item_mapping" defines the steps to transform array items to C variables. */ + const jerryx_arg_t item_mapping[] = + { + jerryx_arg_boolean (&required_bool, JERRYX_ARG_COERCE, JERRYX_ARG_REQUIRED), + jerryx_arg_number (&required_num, JERRYX_ARG_COERCE, JERRYX_ARG_REQUIRED), + jerryx_arg_number (&optional_num, JERRYX_ARG_COERCE, JERRYX_ARG_OPTIONAL) + }; + + /* Prepare the jerryx_arg_array_items_t instance. */ + const jerryx_arg_array_items_t array_info = + { + .c_arg_p = item_mapping, + .c_arg_cnt = 3 + }; + + /* It is the mapping used in the jerryx_arg_transform_args. */ + const jerryx_arg_t mapping[] = + { + jerryx_arg_array (&array_info, JERRYX_ARG_REQUIRED) + }; + + /* Validate and transform. */ + const jerry_value_t rv = jerryx_arg_transform_args (args_p, + args_count, + mapping, + 1); + + if (jerry_value_has_error_flag (rv)) + { + /* Handle error. */ + return rv; + } + + /* + * Validated and transformed successfully! + * required_bool, required_num and optional_num can now be used. + */ + + return jerry_create_undefined (); /* Or return something more meaningful. */ +} + +``` + + **See also** + +- [jerryx_arg_transform_this_and_args](#jerryx_arg_transform_this_and_args) +- [jerryx_arg_transform_object_properties](#jerryx_arg_transform_object_properties) + # Functions to create custom validations ## jerryx_arg_custom diff --git a/jerry-ext/arg/arg-transform-functions.c b/jerry-ext/arg/arg-transform-functions.c index a06487c1a..bc566d854 100644 --- a/jerry-ext/arg/arg-transform-functions.c +++ b/jerry-ext/arg/arg-transform-functions.c @@ -404,6 +404,25 @@ jerryx_arg_transform_object_props (jerryx_arg_js_iterator_t *js_arg_iter_p, /**< object_props->c_arg_cnt); } /* jerryx_arg_transform_object_props */ +/** + * Check whether the JS array's items have expected types, and transform them into native args. + * + * @return jerry undefined: the transformer passes, + * jerry error: the transformer fails. + */ +jerry_value_t +jerryx_arg_transform_array_items (jerryx_arg_js_iterator_t *js_arg_iter_p, /**< available JS args */ + const jerryx_arg_t *c_arg_p) /**< the native arg */ +{ + jerry_value_t js_arg = jerryx_arg_js_iterator_pop (js_arg_iter_p); + + const jerryx_arg_array_items_t *array_items_p = (const jerryx_arg_array_items_t *) c_arg_p->extra_info; + + return jerryx_arg_transform_array (js_arg, + array_items_p->c_arg_p, + array_items_p->c_arg_cnt); +} /* jerryx_arg_transform_array_items */ + /** * Define transformer for optional argument. */ @@ -424,6 +443,7 @@ JERRYX_ARG_TRANSFORM_OPTIONAL (string_strict) JERRYX_ARG_TRANSFORM_OPTIONAL (function) JERRYX_ARG_TRANSFORM_OPTIONAL (native_pointer) JERRYX_ARG_TRANSFORM_OPTIONAL (object_props) +JERRYX_ARG_TRANSFORM_OPTIONAL (array_items) JERRYX_ARG_TRANSFORM_OPTIONAL (uint8) JERRYX_ARG_TRANSFORM_OPTIONAL (uint16) diff --git a/jerry-ext/arg/arg.c b/jerry-ext/arg/arg.c index 9c932e124..1f587a6fd 100644 --- a/jerry-ext/arg/arg.c +++ b/jerry-ext/arg/arg.c @@ -143,3 +143,46 @@ jerryx_arg_transform_object_properties (const jerry_value_t obj_val,/**< the JS return ret; } /* jerryx_arg_transform_object_properties */ + +/** + * Validate the items in the JS array and assign them to the native arguments. + * + * @return jerry undefined: all validators passed, + * jerry error: a validator failed. + */ +jerry_value_t +jerryx_arg_transform_array (const jerry_value_t array_val, /**< points to the JS array */ + const jerryx_arg_t *c_arg_p, /**< points to the array of validation/transformation steps */ + jerry_length_t c_arg_cnt) /**< the count of the `c_arg_p` array */ +{ + if (!jerry_value_is_array (array_val)) + { + return jerry_create_error (JERRY_ERROR_TYPE, (jerry_char_t *) "Not an array."); + } + + jerry_value_t arr[c_arg_cnt]; + + for (jerry_length_t i = 0; i < c_arg_cnt; i++) + { + arr[i] = jerry_get_property_by_index (array_val, i); + + if (jerry_value_has_error_flag (arr[i])) + { + for (jerry_length_t j = 0; j < i; j++) + { + jerry_release_value (arr[j]); + } + + return arr[i]; + } + } + + const jerry_value_t ret = jerryx_arg_transform_args (arr, c_arg_cnt, c_arg_p, c_arg_cnt); + + for (jerry_length_t i = 0; i < c_arg_cnt; i++) + { + jerry_release_value (arr[i]); + } + + return ret; +} /* jerryx_arg_transform_array */ diff --git a/jerry-ext/include/jerryscript-ext/arg.h b/jerry-ext/include/jerryscript-ext/arg.h index 03424c378..af81c8c2d 100644 --- a/jerry-ext/include/jerryscript-ext/arg.h +++ b/jerry-ext/include/jerryscript-ext/arg.h @@ -53,6 +53,15 @@ typedef struct jerry_length_t c_arg_cnt; /**< the count of the `c_arg_p` array */ } jerryx_arg_object_props_t; +/** + * The structure used in jerryx_arg_array + */ +typedef struct +{ + const jerryx_arg_t *c_arg_p; /**< points to the array of transformation steps */ + jerry_length_t c_arg_cnt; /**< the count of the `c_arg_p` array */ +} jerryx_arg_array_items_t; + /** * The structure defining a single validation & transformation step. */ @@ -79,6 +88,9 @@ jerry_value_t jerryx_arg_transform_object_properties (const jerry_value_t obj_va const jerry_length_t name_cnt, const jerryx_arg_t *c_arg_p, jerry_length_t c_arg_cnt); +jerry_value_t jerryx_arg_transform_array (const jerry_value_t array_val, + const jerryx_arg_t *c_arg_p, + jerry_length_t c_arg_cnt); /** * Indicates whether an argument is allowed to be coerced into the expected JS type. @@ -162,6 +174,8 @@ static inline jerryx_arg_t jerryx_arg_custom (void *dest, uintptr_t extra_info, jerryx_arg_transform_func_t func); static inline jerryx_arg_t jerryx_arg_object_properties (const jerryx_arg_object_props_t *object_props_p, jerryx_arg_optional_t opt_flag); +static inline jerryx_arg_t +jerryx_arg_array (const jerryx_arg_array_items_t *array_items_p, jerryx_arg_optional_t opt_flag); jerry_value_t jerryx_arg_transform_optional (jerryx_arg_js_iterator_t *js_arg_iter_p, diff --git a/jerry-ext/include/jerryscript-ext/arg.impl.h b/jerry-ext/include/jerryscript-ext/arg.impl.h index 874ed0fd1..371b31088 100644 --- a/jerry-ext/include/jerryscript-ext/arg.impl.h +++ b/jerry-ext/include/jerryscript-ext/arg.impl.h @@ -41,6 +41,7 @@ JERRYX_ARG_TRANSFORM_FUNC_WITH_OPTIONAL_AND_STRICT (boolean) JERRYX_ARG_TRANSFORM_FUNC_WITH_OPTIONAL (function) JERRYX_ARG_TRANSFORM_FUNC_WITH_OPTIONAL (native_pointer) JERRYX_ARG_TRANSFORM_FUNC_WITH_OPTIONAL (object_props) +JERRYX_ARG_TRANSFORM_FUNC_WITH_OPTIONAL (array_items) jerry_value_t jerryx_arg_transform_ignore (jerryx_arg_js_iterator_t *js_arg_iter_p, const jerryx_arg_t *c_arg_p); @@ -361,4 +362,32 @@ jerryx_arg_object_properties (const jerryx_arg_object_props_t *object_props, /** }; } /* jerryx_arg_object_properties */ +/** + * Create a jerryx_arg_t instance for array. + * + * @return a jerryx_arg_t instance. + */ +static inline jerryx_arg_t +jerryx_arg_array (const jerryx_arg_array_items_t *array_items_p, /**< pointer to array items mapping */ + jerryx_arg_optional_t opt_flag) /**< whether the argument is optional */ +{ + jerryx_arg_transform_func_t func; + + if (opt_flag == JERRYX_ARG_OPTIONAL) + { + func = jerryx_arg_transform_array_items_optional; + } + else + { + func = jerryx_arg_transform_array_items; + } + + return (jerryx_arg_t) + { + .func = func, + .dest = NULL, + .extra_info = (uintptr_t) array_items_p + }; +} /* jerryx_arg_array */ + #endif /* !JERRYX_ARG_IMPL_H */ diff --git a/tests/unit-ext/test-common.h b/tests/unit-ext/test-common.h index cf62d281c..330a1d30f 100644 --- a/tests/unit-ext/test-common.h +++ b/tests/unit-ext/test-common.h @@ -18,6 +18,8 @@ #include "jerryscript-port.h" +#define ARRAY_SIZE(array) ((unsigned long) (sizeof (array) / sizeof ((array)[0]))) + #define TEST_ASSERT(x) \ do \ { \ diff --git a/tests/unit-ext/test-ext-arg.c b/tests/unit-ext/test-ext-arg.c index 944e724d3..55f00f481 100644 --- a/tests/unit-ext/test-ext-arg.c +++ b/tests/unit-ext/test-ext-arg.c @@ -55,6 +55,10 @@ const char *test_source = ( " -1000, 4294967297, 65536, -2200000000, 4294967297, -2147483647);" "test_validator_int2(-1.5, -1.5, -1.5, 1.5, 1.5, 1.5, Infinity, -Infinity, 300.5, 300.5);" "test_validator_int3(NaN);" + "var arr = [1, 2];" + "test_validator_array1(arr);" + "test_validator_array1();" + "test_validator_array2(arr);" ); static const jerry_object_native_info_t thing_a_info = @@ -83,8 +87,8 @@ static my_type_b_t my_thing_b; static int validator1_count = 0; static int validator2_count = 0; static int validator_int_count = 0; - static int validator_prop_count = 0; +static int validator_array_count = 0; /** * The handler should have following arguments: @@ -125,7 +129,7 @@ test_validator1_handler (const jerry_value_t func_obj_val __attribute__((unused) args_p, args_cnt, mapping, - 5); + ARRAY_SIZE (mapping)); if (validator1_count == 0) { @@ -218,7 +222,7 @@ test_validator2_handler (const jerry_value_t func_obj_val __attribute__((unused) args_p, args_cnt, mapping, - 2); + ARRAY_SIZE (mapping)); if (validator2_count == 0) { @@ -261,9 +265,9 @@ test_validator_prop1_handler (const jerry_value_t func_obj_val __attribute__((un jerry_value_t is_ok = jerryx_arg_transform_object_properties (args_p[0], (const jerry_char_t **) name_p, - 3, + ARRAY_SIZE (name_p), mapping, - 3); + ARRAY_SIZE (mapping)); TEST_ASSERT (!jerry_value_has_error_flag (is_ok)); TEST_ASSERT (native1); @@ -283,7 +287,7 @@ static jerry_value_t test_validator_prop2_handler (const jerry_value_t func_obj_val __attribute__((unused)), /**< function object */ const jerry_value_t this_val __attribute__((unused)), /**< this value */ const jerry_value_t args_p[], /**< arguments list */ - const jerry_length_t args_cnt __attribute__((unused))) /**< arguments length */ + const jerry_length_t args_cnt) /**< arguments length */ { bool native1 = false; double native2 = 0; @@ -310,7 +314,7 @@ test_validator_prop2_handler (const jerry_value_t func_obj_val __attribute__((un jerryx_arg_object_properties (&prop_info, JERRYX_ARG_OPTIONAL), }; - jerry_value_t is_ok = jerryx_arg_transform_args (args_p, 1, mapping, 1); + jerry_value_t is_ok = jerryx_arg_transform_args (args_p, args_cnt, mapping, ARRAY_SIZE (mapping)); TEST_ASSERT (!jerry_value_has_error_flag (is_ok)); @@ -346,9 +350,9 @@ test_validator_prop3_handler (const jerry_value_t func_obj_val __attribute__((un jerry_value_t is_ok = jerryx_arg_transform_object_properties (args_p[0], (const jerry_char_t **) name_p, - 2, + ARRAY_SIZE (name_p), mapping, - 2); + ARRAY_SIZE (mapping)); TEST_ASSERT (jerry_value_has_error_flag (is_ok)); TEST_ASSERT (!native1); @@ -393,7 +397,7 @@ test_validator_int1_handler (const jerry_value_t func_obj_val __attribute__((unu jerry_value_t is_ok = jerryx_arg_transform_args (args_p, args_cnt, mapping, - 12); + ARRAY_SIZE (mapping)); TEST_ASSERT (!jerry_value_has_error_flag (is_ok)); TEST_ASSERT (num0 == 0); @@ -442,7 +446,7 @@ test_validator_int2_handler (const jerry_value_t func_obj_val __attribute__((unu jerry_value_t is_ok = jerryx_arg_transform_args (args_p, args_cnt, mapping, - 10); + ARRAY_SIZE (mapping)); TEST_ASSERT (jerry_value_has_error_flag (is_ok)); TEST_ASSERT (num0 == -2); @@ -478,7 +482,7 @@ test_validator_int3_handler (const jerry_value_t func_obj_val __attribute__((unu jerry_value_t is_ok = jerryx_arg_transform_args (args_p, args_cnt, mapping, - 1); + ARRAY_SIZE (mapping)); TEST_ASSERT (jerry_value_has_error_flag (is_ok)); @@ -488,6 +492,77 @@ test_validator_int3_handler (const jerry_value_t func_obj_val __attribute__((unu return jerry_create_undefined (); } /* test_validator_int3_handler */ +static jerry_value_t +test_validator_array1_handler (const jerry_value_t func_obj_val __attribute__((unused)), /**< function object */ + const jerry_value_t this_val __attribute__((unused)), /**< this value */ + const jerry_value_t args_p[], /**< arguments list */ + const jerry_length_t args_cnt) /**< arguments length */ +{ + double native1 = 0; + double native2 = 0; + double native3 = 0; + + jerryx_arg_array_items_t arr_info; + + jerryx_arg_t item_mapping[] = + { + jerryx_arg_number (&native1, JERRYX_ARG_COERCE, JERRYX_ARG_REQUIRED), + jerryx_arg_number (&native2, JERRYX_ARG_COERCE, JERRYX_ARG_REQUIRED), + jerryx_arg_number (&native3, JERRYX_ARG_COERCE, JERRYX_ARG_OPTIONAL) + }; + + arr_info.c_arg_p = item_mapping; + arr_info.c_arg_cnt = 3; + + jerryx_arg_t mapping[] = + { + jerryx_arg_array (&arr_info, JERRYX_ARG_OPTIONAL), + }; + + jerry_value_t is_ok = jerryx_arg_transform_args (args_p, args_cnt, mapping, ARRAY_SIZE (mapping)); + + + TEST_ASSERT (!jerry_value_has_error_flag (is_ok)); + + if (validator_array_count == 0) + { + TEST_ASSERT (native1 == 1); + TEST_ASSERT (native2 == 2); + TEST_ASSERT (native3 == 0); + } + + validator_array_count++; + + return jerry_create_undefined (); +} /* test_validator_array1_handler */ + +static jerry_value_t +test_validator_array2_handler (const jerry_value_t func_obj_val __attribute__((unused)), /**< function object */ + const jerry_value_t this_val __attribute__((unused)), /**< this value */ + const jerry_value_t args_p[], /**< arguments list */ + const jerry_length_t args_cnt __attribute__((unused))) /**< arguments length */ +{ + double native1 = 0; + bool native2 = false; + + jerryx_arg_t item_mapping[] = + { + jerryx_arg_number (&native1, JERRYX_ARG_COERCE, JERRYX_ARG_REQUIRED), + jerryx_arg_boolean (&native2, JERRYX_ARG_NO_COERCE, JERRYX_ARG_REQUIRED) + }; + + jerry_value_t is_ok = jerryx_arg_transform_array (args_p[0], item_mapping, ARRAY_SIZE (item_mapping)); + + TEST_ASSERT (jerry_value_has_error_flag (is_ok)); + TEST_ASSERT (native1 == 1); + TEST_ASSERT (!native2); + + validator_array_count++; + jerry_release_value (is_ok); + + return jerry_create_undefined (); +} /* test_validator_array2_handler */ + static jerry_value_t create_object_a_handler (const jerry_value_t func_obj_val __attribute__((unused)), /**< function object */ const jerry_value_t this_val, /**< this value */ @@ -555,6 +630,8 @@ main (void) register_js_function ("test_validator_prop1", test_validator_prop1_handler); register_js_function ("test_validator_prop2", test_validator_prop2_handler); register_js_function ("test_validator_prop3", test_validator_prop3_handler); + register_js_function ("test_validator_array1", test_validator_array1_handler); + register_js_function ("test_validator_array2", test_validator_array2_handler); jerry_value_t parsed_code_val = jerry_parse ((jerry_char_t *) test_source, strlen (test_source), false); TEST_ASSERT (!jerry_value_has_error_flag (parsed_code_val)); @@ -565,6 +642,7 @@ main (void) TEST_ASSERT (validator2_count == 3); TEST_ASSERT (validator_prop_count == 4); TEST_ASSERT (validator_int_count == 3); + TEST_ASSERT (validator_array_count == 3); jerry_release_value (res); jerry_release_value (parsed_code_val);