diff --git a/CMakeLists.txt b/CMakeLists.txt index 1d47a9e724..f06347e277 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -26,6 +26,7 @@ set(CMAKE_SHARED_LIBRARY_LINK_C_FLAGS ) set(JERRY_CMDLINE ON CACHE BOOL "Build jerry command line tool?") set(JERRY_CMDLINE_MINIMAL OFF CACHE BOOL "Build jerry minimal command line tool?") set(JERRY_PORT_DEFAULT ON CACHE BOOL "Build default jerry port implementation?") +set(JERRY_EXT ON CACHE BOOL "Build jerry-ext?") set(JERRY_LIBC ON CACHE BOOL "Build and use jerry-libc?") set(JERRY_LIBM ON CACHE BOOL "Build and use jerry-libm?") set(UNITTESTS OFF CACHE BOOL "Build unit tests?") @@ -94,6 +95,7 @@ message(STATUS "ENABLE_STRIP " ${ENABLE_STRIP} ${ENABLE_STRIP_MESSA message(STATUS "JERRY_CMDLINE " ${JERRY_CMDLINE}) message(STATUS "JERRY_CMDLINE_MINIMAL " ${JERRY_CMDLINE_MINIMAL}) message(STATUS "JERRY_PORT_DEFAULT " ${JERRY_PORT_DEFAULT} ${JERRY_PORT_DEFAULT_MESSAGE}) +message(STATUS "JERRY_EXT " ${JERRY_EXT} ${JERRY_EXT_MESSAGE}) message(STATUS "JERRY_LIBC " ${JERRY_LIBC} ${JERRY_LIBC_MESSAGE}) message(STATUS "JERRY_LIBM " ${JERRY_LIBM} ${JERRY_LIBM_MESSAGE}) message(STATUS "UNITTESTS " ${UNITTESTS}) @@ -238,6 +240,11 @@ if(JERRY_PORT_DEFAULT) add_subdirectory(jerry-port/default) endif() +# Jerry's extension tools +if(JERRY_EXT) + add_subdirectory(jerry-ext) +endif() + # Jerry command line tool if(JERRY_CMDLINE OR JERRY_CMDLINE_MINIMAL) add_subdirectory(jerry-main) @@ -249,4 +256,7 @@ if(UNITTESTS) if(JERRY_LIBM) add_subdirectory(tests/unit-libm) endif() + if(JERRY_EXT) + add_subdirectory(tests/unit-ext) + endif() endif() diff --git a/docs/09.EXT-REFERENCE-ARG.md b/docs/09.EXT-REFERENCE-ARG.md new file mode 100644 index 0000000000..76c5414d32 --- /dev/null +++ b/docs/09.EXT-REFERENCE-ARG.md @@ -0,0 +1,413 @@ +# jerryx_arg types + +## jerryx_arg_t + +**Summary** + +The structure defining a single validation/transformation step. + +*Note*: For commonly used validators, `arg.h` provides helpers to create the `jerryx_arg_t`s. +For example, `jerryx_arg_number ()`, `jerryx_arg_boolean ()`, etc. + +**Prototype** + +```c +typedef struct +{ + /** the transform function */ + jerryx_arg_transform_func_t func; + /** pointer to destination where func should store the result */ + void *dest; + /** extra information, specific to func */ + uintptr_t extra_info; +} jerryx_arg_t; +``` + +**See also** + +- [jerryx_arg_number](#jerryx_arg_number) +- [jerryx_arg_boolean](#jerryx_arg_boolean) +- [jerryx_arg_string](#jerryx_arg_string) +- [jerryx_arg_function](#jerryx_arg_function) +- [jerryx_arg_native_pointer](#jerryx_arg_native_pointer) +- [jerryx_arg_ignore](#jerryx_arg_ignore) + +## jerryx_arg_transform_func_t + +**Summary** + +Signature of the transform function. + +Users can create custom transformations by implementing a transform function +and using `jerryx_arg_custom ()`. + +The function is expected to return `undefined` if it ran successfully or +return an `Error` in case it failed. The function can use the iterator and the +helpers `jerryx_arg_js_iterator_pop ()` and `jerryx_arg_js_iterator_peek ()` to +get the next input value. + +*Note*: A transform function is allowed to consume any number of input values! +This enables complex validation like handling different JS function signatures, +mapping multiple input arguments to a C struct, etc. + +The function is expected to store the result of +a successful transformation into `c_arg_p->dest`. In case the validation did +not pass, the transform should not modify `c_arg_p->dest`. + +Additional parameters can be provided to the function through `c_arg_p->extra_info`. + +**Prototype** + +```c +typedef jerry_value_t (*jerryx_arg_transform_func_t) (jerryx_arg_js_iterator_t *js_arg_iter_p, + const jerryx_arg_t *c_arg_p); +``` + +**See also** + +- [jerryx_arg_custom](#jerryx_arg_custom) +- [jerryx_arg_js_iterator_pop](#jerryx_arg_js_iterator_pop) +- [jerryx_arg_js_iterator_peek](#jerryx_arg_js_iterator_peek) + + +## jerryx_arg_coerce_t + +Enum that indicates whether an argument is allowed to be coerced into the expected JS type. + + - JERRYX_ARG_COERCE - the transform will invoke toNumber, toBoolean, toString, etc. + - JERRYX_ARG_NO_COERCE - the type coercion is not allowed. The transform will fail if the type does not match the expectation. + +**See also** + +- [jerryx_arg_number](#jerryx_arg_number) +- [jerryx_arg_boolean](#jerryx_arg_boolean) +- [jerryx_arg_string](#jerryx_arg_string) + +## jerryx_arg_optional_t + +Enum that indicates whether an argument is optional or required. + + - JERRYX_ARG_OPTIONAL - The argument is optional. If the argument is `undefined` the transform is successful and `c_arg_p->dest` remains untouched. + - JERRYX_ARG_REQUIRED - The argument is required. If the argument is `undefined` the transform will fail and `c_arg_p->dest` remains untouched. + +**See also** + +- [jerryx_arg_number](#jerryx_arg_number) +- [jerryx_arg_boolean](#jerryx_arg_boolean) +- [jerryx_arg_string](#jerryx_arg_string) +- [jerryx_arg_function](#jerryx_arg_function) +- [jerryx_arg_native_pointer](#jerryx_arg_native_pointer) + +# Main functions + +## jerryx_arg_transform_this_and_args + +**Summary** + +Validate the this value and the JS arguments, and assign them to the native arguments. +This function is useful to perform input validation inside external function handlers (see `jerry_external_handler_t`). + +**Prototype** + +```c +jerry_value_t +jerryx_arg_transform_this_and_args (const jerry_value_t this_val, + const jerry_value_t *js_arg_p, + const jerry_length_t js_arg_cnt, + const jerryx_arg_t *c_arg_p, + jerry_length_t c_arg_cnt) +``` + + - `this_val` - `this` value. Note this is processed as the first value, before the array of arguments. + - `js_arg_p` - points to the array with JS arguments. + - `js_arg_cnt` - the count of the `js_arg_p` 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. + +**Example** + +```c +// JS signature: function (requiredBool, requiredString, optionalNumber) +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; + char required_str[16]; + double optional_num = 1234.567; // default value + + // "mapping" defines the steps to transform input arguments to C variables: + const jerryx_arg_t mapping[] = + { + // `this` is the first value. No checking needed on `this` for this function. + jerryx_arg_ignore (), + + jerryx_arg_boolean (&required_bool, JERRYX_ARG_NO_COERCE, JERRYX_ARG_REQUIRED), + jerryx_arg_string (&required_str, sizeof (required_str), JERRYX_ARG_NO_COERCE, JERRYX_ARG_REQUIRED), + jerryx_arg_number (&optional_num, JERRYX_ARG_NO_COERCE, JERRYX_ARG_OPTIONAL), + }; + + // Validate and transform: + const jerry_value_t rv = jerryx_arg_transform_this_and_args (this_val, + args_p, + args_count, + mapping, + ARRAY_LENGTH (mapping)); + + if (jerry_value_has_error_flag (rv)) + { + // Handle error + return rv; + } + + // Validated and tranformed successfully! + // required_bool, required_str and optional_num can now be used. + // ... +} +``` + +**See also** + +- [jerryx_arg_ignore](#jerryx_arg_ignore) +- [jerryx_arg_number](#jerryx_arg_number) +- [jerryx_arg_boolean](#jerryx_arg_boolean) +- [jerryx_arg_string](#jerryx_arg_string) +- [jerryx_arg_function](#jerryx_arg_function) +- [jerryx_arg_native_pointer](#jerryx_arg_native_pointer) +- [jerryx_arg_custom](#jerryx_arg_custom) + + +## jerryx_arg_transform_args + +**Summary** + +Validate an array of `jerry_value_t` and assign them to the native arguments. + +**Prototype** + +```c +jerry_value_t +jerryx_arg_transform_args (const jerry_value_t *js_arg_p, + const jerry_length_t js_arg_cnt, + const jerryx_arg_t *c_arg_p, + jerry_length_t c_arg_cnt) +``` + + - `js_arg_p` - points to the array with JS arguments. + - `js_arg_cnt` - the count of the `js_arg_p` 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_transform_this_and_args](#jerryx_arg_transform_this_and_args) + + +# Helpers for commonly used validations + +## jerryx_arg_number + +**Summary** + +Create a validation/transformation step (`jerryx_arg_t`) that expects to consume +one `number` JS argument and stores it into a C `double`. + +**Prototype** + +```c +static inline jerryx_arg_t +jerryx_arg_number (double *dest, + jerryx_arg_coerce_t coerce_flag, + jerryx_arg_optional_t opt_flag) +``` + + - return value - the created `jerryx_arg_t` instance. + - `dest` - pointer to the `double` where the result should be stored. + - `coerce_flag` - whether type coercion is allowed. + - `opt_flag` - whether the argument is optional. + +**See also** + +- [jerryx_arg_transform_this_and_args](#jerryx_arg_transform_this_and_args) + +## jerryx_arg_boolean + +**Summary** + +Create a validation/transformation step (`jerryx_arg_t`) that expects to +consume one `boolean` JS argument and stores it into a C `bool`. + +**Prototype** + +```c +static inline jerryx_arg_t +jerryx_arg_boolean (bool *dest, + jerryx_arg_coerce_t coerce_flag, + jerryx_arg_optional_t opt_flag) +``` + - return value - the created `jerryx_arg_t` instance. + - `dest` - pointer to the `bool` where the result should be stored. + - `coerce_flag` - whether type coercion is allowed. + - `opt_flag` - whether the argument is optional. + +**See also** + +- [jerryx_arg_transform_this_and_args](#jerryx_arg_transform_this_and_args) + + +## jerryx_arg_string + +**Summary** + +Create a validation/transformation step (`jerryx_arg_t`) that expects to +consume one `string` JS argument and stores it into a C `char` array. + +**Prototype** + +```c +static inline jerryx_arg_t +jerryx_arg_string (char *dest, + uint32_t size, + jerryx_arg_coerce_t coerce_flag, + jerryx_arg_optional_t opt_flag) +``` + + - return value - the created `jerryx_arg_t` instance. + - `dest` - pointer to the native char array where the result should be stored. + - `size` - the size of native char array. + - `coerce_flag` - whether type coercion is allowed. + - `opt_flag` - whether the argument is optional. + +**See also** + +- [jerryx_arg_transform_this_and_args](#jerryx_arg_transform_this_and_args) + + +## jerryx_arg_function + +**Summary** + +Create a validation/transformation step (`jerryx_arg_t`) that expects to +consume one `function` JS argument and stores it into a C `jerry_value_t`. + +**Prototype** + +```c +static inline jerryx_arg_t +jerryx_arg_function (jerry_value_t *dest, + jerryx_arg_optional_t opt_flag) + +``` + - return value - the created `jerryx_arg_t` instance. + - `dest` - pointer to the `jerry_value_t` where the result should be stored. + - `opt_flag` - whether the argument is optional. + +**See also** + +- [jerryx_arg_transform_this_and_args](#jerryx_arg_transform_this_and_args) + +## jerryx_arg_native_pointer + +**Summary** + +Create a validation/transformation step (`jerryx_arg_t`) that expects to +consume one `Object` JS argument that is 'backed' with a native pointer with +a given type info. In case the native pointer info matches, the transform +will succeed and the object's native pointer will be assigned to `*dest`. + +**Prototype** + +```c +static inline jerryx_arg_t +jerryx_arg_native_pointer (void **dest, + const jerry_object_native_info_t *info_p, + jerryx_arg_optional_t opt_flag) +``` + - return value - the created `jerryx_arg_t` instance. + - `dest` - pointer to where the resulting native pointer should be stored. + - `info_p` - expected the type info. + - `opt_flag` - whether the argument is optional. + +**See also** + +- [jerryx_arg_transform_this_and_args](#jerryx_arg_transform_this_and_args) + + +# Functions to create custom validations + +## jerryx_arg_custom + +**Summary** + +Create a jerryx_arg_t instance with custom transform. + +**Prototype** + +```c +static inline jerryx_arg_t +jerryx_arg_custom (void *dest, + uintptr_t extra_info, + jerryx_arg_transform_func_t func) + +``` + - return value - the created `jerryx_arg_t` instance. + - `dest` - pointer to the native argument where the result should be stored. + - `extra_info` - the extra parameter data, specific to the transform function. + - `func` - the custom transform function. + +**See also** + +- [jerryx_arg_transform_this_and_args](#jerryx_arg_transform_this_and_args) + + + +## jerryx_arg_js_iterator_pop + +**Summary** + +Pop the current `jerry_value_t` argument from the iterator. +It will change the `js_arg_idx` and `js_arg_p` value in the iterator. + +**Prototype** + +```c +jerry_value_t +jerryx_arg_js_iterator_pop (jerryx_arg_js_iterator_t *js_arg_iter_p) +``` + - return value - the `jerry_value_t` argument that was popped. + - `js_arg_iter_p` - the JS arg iterator from which to pop. + +## jerryx_arg_js_iterator_peek + +**Summary** + +Get the current JS argument from the iterator, without moving the iterator forward. +*Note:* Unlike `jerryx_arg_js_iterator_pop ()`, it will not change `js_arg_idx` and +`js_arg_p` value in the iterator. + +**Prototype** + +```c +jerry_value_t +jerryx_arg_js_iterator_peek (jerryx_arg_js_iterator_t *js_arg_iter_p) +``` + - return value - the current `jerry_value_t` argument. + - `js_arg_iter_p` - the JS arg iterator from which to peek. + +## jerryx_arg_js_iterator_index + +**Summary** + +Get the index of the current JS argument from the iterator. + +**Prototype** + +```c +jerry_length_t +jerryx_arg_js_iterator_index (jerryx_arg_js_iterator_t *js_arg_iter_p) +``` + - return value - the index of current JS argument. + - `js_arg_iter_p` - the JS arg iterator from which to peek. diff --git a/jerry-ext/CMakeLists.txt b/jerry-ext/CMakeLists.txt new file mode 100644 index 0000000000..07f92adae8 --- /dev/null +++ b/jerry-ext/CMakeLists.txt @@ -0,0 +1,32 @@ +# Copyright JS Foundation and other contributors, http://js.foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +cmake_minimum_required (VERSION 2.8.12) +set(JERRY_EXT_NAME jerry-ext) +project (${JERRY_EXT_NAME} C) + +# Include directories +set(INCLUDE_EXT "${CMAKE_CURRENT_SOURCE_DIR}/include") + +# Source directories +file(GLOB SOURCE_EXT arg/*.c) + +add_library(${JERRY_EXT_NAME} STATIC ${SOURCE_EXT}) + +target_include_directories(${JERRY_EXT_NAME} PUBLIC ${INCLUDE_EXT}) + +target_link_libraries(${JERRY_EXT_NAME} jerry-core) + +install(TARGETS ${JERRY_EXT_NAME} DESTINATION lib) +install(DIRECTORY ${INCLUDE_EXT}/ DESTINATION include) diff --git a/jerry-ext/arg/arg-internal.h b/jerry-ext/arg/arg-internal.h new file mode 100644 index 0000000000..3f9c4fe074 --- /dev/null +++ b/jerry-ext/arg/arg-internal.h @@ -0,0 +1,31 @@ +/* Copyright JS Foundation and other contributors, http://js.foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef JERRYX_ARG_INTERNAL_H +#define JERRYX_ARG_INTERNAL_H + +#include "jerryscript.h" + +/** + * The iterator structor for JS arguments. + */ +struct jerryx_arg_js_iterator_t +{ + const jerry_value_t *js_arg_p; /**< the JS arguments */ + const jerry_length_t js_arg_cnt; /**< the total num of JS arguments */ + jerry_length_t js_arg_idx; /**< current index of JS argument */ +}; + +#endif /* !JERRYX_ARG_INTERNAL_H */ diff --git a/jerry-ext/arg/arg-js-iterator-helper.c b/jerry-ext/arg/arg-js-iterator-helper.c new file mode 100644 index 0000000000..7267a681a9 --- /dev/null +++ b/jerry-ext/arg/arg-js-iterator-helper.c @@ -0,0 +1,58 @@ +/* Copyright JS Foundation and other contributors, http://js.foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "arg-internal.h" +#include "jerryscript-ext/arg.h" +#include "jerryscript.h" + +/** + * Pop the current JS argument from the iterator. + * It will change the index and js_arg_p value in the iterator. + * + * @return the current JS argument. + */ +jerry_value_t +jerryx_arg_js_iterator_pop (jerryx_arg_js_iterator_t *js_arg_iter_p) /**< the JS arg iterator */ +{ + return (js_arg_iter_p->js_arg_idx++ < js_arg_iter_p->js_arg_cnt ? *js_arg_iter_p->js_arg_p++ + : jerry_create_undefined ()); +} /* jerryx_arg_js_iterator_pop */ + +/** + * Get the current JS argument from the iterator. + * + * Note: + * Unlike jerryx_arg_js_iterator_pop, it will not change index and + * js_arg_p value in the iterator. + * + * @return the current JS argument. + */ +jerry_value_t +jerryx_arg_js_iterator_peek (jerryx_arg_js_iterator_t *js_arg_iter_p) /**< the JS arg iterator */ +{ + return (js_arg_iter_p->js_arg_idx < js_arg_iter_p->js_arg_cnt ? *js_arg_iter_p->js_arg_p + : jerry_create_undefined ()); +} /* jerryx_arg_js_iterator_peek */ + +/** + * Get the index of the current JS argument + * + * @return the index + */ +jerry_length_t +jerryx_arg_js_iterator_index (jerryx_arg_js_iterator_t *js_arg_iter_p) /**< the JS arg iterator */ +{ + return js_arg_iter_p->js_arg_idx; +} /* jerryx_arg_js_iterator_index */ diff --git a/jerry-ext/arg/arg-transform-functions.c b/jerry-ext/arg/arg-transform-functions.c new file mode 100644 index 0000000000..4b0b1762d6 --- /dev/null +++ b/jerry-ext/arg/arg-transform-functions.c @@ -0,0 +1,301 @@ +/* Copyright JS Foundation and other contributors, http://js.foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "jerryscript-ext/arg.h" +#include "jerryscript.h" + +/** + * The common function to deal with optional arguments. + * The core transform function is provided by argument `func`. + * + * @return jerry undefined: the transformer passes, + * jerry error: the transformer fails. + */ +jerry_value_t +jerryx_arg_transform_optional (jerryx_arg_js_iterator_t *js_arg_iter_p, /**< available JS args */ + const jerryx_arg_t *c_arg_p, /**< native arg */ + jerryx_arg_transform_func_t func) /**< the core transform function */ +{ + jerry_value_t js_arg = jerryx_arg_js_iterator_peek (js_arg_iter_p); + + if (jerry_value_is_undefined (js_arg)) + { + return js_arg; + } + + return func (js_arg_iter_p, c_arg_p); +} /* jerryx_arg_transform_optional */ + +/** + * Tranform a JS argument to a double. Type coercion is not allowed. + * + * @return jerry undefined: the transformer passes, + * jerry error: the transformer fails. + */ +jerry_value_t +jerryx_arg_transform_number_strict (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); + + if (!jerry_value_is_number (js_arg)) + { + return jerry_create_error (JERRY_ERROR_TYPE, + (jerry_char_t *) "It is not a number."); + } + + double *dest = c_arg_p->dest; + *dest = jerry_get_number_value (js_arg); + + return jerry_create_undefined (); +} /* jerryx_arg_transform_number_strict */ + +/** + * Tranform a JS argument to a double. Type coercion is allowed. + * + * @return jerry undefined: the transformer passes, + * jerry error: the transformer fails. + */ +jerry_value_t +jerryx_arg_transform_number (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); + + jerry_value_t to_number = jerry_value_to_number (js_arg); + + if (jerry_value_has_error_flag (to_number)) + { + jerry_release_value (to_number); + + return jerry_create_error (JERRY_ERROR_TYPE, + (jerry_char_t *) "It can not be converted to a number."); + } + + double *dest = c_arg_p->dest; + *dest = jerry_get_number_value (to_number); + jerry_release_value (to_number); + + return jerry_create_undefined (); +} /* jerryx_arg_transform_number */ + +/** + * Tranform a JS argument to a boolean. Type coercion is not allowed. + * + * @return jerry undefined: the transformer passes, + * jerry error: the transformer fails. + */ +jerry_value_t +jerryx_arg_transform_boolean_strict (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); + + if (!jerry_value_is_boolean (js_arg)) + { + return jerry_create_error (JERRY_ERROR_TYPE, + (jerry_char_t *) "It is not a boolean."); + } + + bool *dest = c_arg_p->dest; + *dest = jerry_get_boolean_value (js_arg); + + return jerry_create_undefined (); +} /* jerryx_arg_transform_boolean_strict */ + +/** + * Tranform a JS argument to a boolean. Type coercion is allowed. + * + * @return jerry undefined: the transformer passes, + * jerry error: the transformer fails. + */ +jerry_value_t +jerryx_arg_transform_boolean (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); + + bool to_boolean = jerry_value_to_boolean (js_arg); + + bool *dest = c_arg_p->dest; + *dest = to_boolean; + + return jerry_create_undefined (); +} /* jerryx_arg_transform_boolean */ + +/** + * The common routine for string transformer. + * + * @return jerry undefined: the transformer passes, + * jerry error: the transformer fails. + */ +static jerry_value_t +jerryx_arg_string_common_routine (jerry_value_t js_arg, /**< JS arg */ + const jerryx_arg_t *c_arg_p) /**< native arg */ +{ + jerry_size_t size = jerry_string_to_char_buffer (js_arg, + (jerry_char_t *) c_arg_p->dest, + (jerry_size_t) c_arg_p->extra_info); + + if (size == 0) + { + return jerry_create_error (JERRY_ERROR_TYPE, + (jerry_char_t *) "The size of the buffer is not large enough."); + } + + return jerry_create_undefined (); +} /* jerryx_arg_string_common_routine */ + +/** + * Tranform a JS argument to a char array. Type coercion is not allowed. + * + * @return jerry undefined: the transformer passes, + * jerry error: the transformer fails. + */ +jerry_value_t +jerryx_arg_transform_string_strict (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); + + if (!jerry_value_is_string (js_arg)) + { + return jerry_create_error (JERRY_ERROR_TYPE, + (jerry_char_t *) "It is not a string."); + } + + return jerryx_arg_string_common_routine (js_arg, c_arg_p); +} /* jerryx_arg_transform_string_strict */ + +/** + * Tranform a JS argument to a char array. Type coercion is allowed. + * + * @return jerry undefined: the transformer passes, + * jerry error: the transformer fails. + */ +jerry_value_t +jerryx_arg_transform_string (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); + + jerry_value_t to_string = jerry_value_to_string (js_arg); + + if (jerry_value_has_error_flag (to_string)) + { + jerry_release_value (to_string); + + return jerry_create_error (JERRY_ERROR_TYPE, + (jerry_char_t *) "It can not be converted to a string."); + } + + jerry_value_t ret = jerryx_arg_string_common_routine (to_string, c_arg_p); + jerry_release_value (to_string); + + return ret; +} /* jerryx_arg_transform_string */ + +/** + * Check whether the JS argument is jerry function, if so, assign to the native argument. + * + * @return jerry undefined: the transformer passes, + * jerry error: the transformer fails. + */ +jerry_value_t +jerryx_arg_transform_function (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); + + if (!jerry_value_is_function (js_arg)) + { + return jerry_create_error (JERRY_ERROR_TYPE, + (jerry_char_t *) "It is not a function."); + } + + jerry_value_t *func_p = c_arg_p->dest; + *func_p = jerry_acquire_value (js_arg); + + return jerry_create_undefined (); +} /* jerryx_arg_transform_function */ + +/** + * Check whether the native pointer has the expected type info. + * If so, assign it to the native argument. + * + * @return jerry undefined: the transformer passes, + * jerry error: the transformer fails. + */ +jerry_value_t +jerryx_arg_transform_native_pointer (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); + + if (!jerry_value_is_object (js_arg)) + { + return jerry_create_error (JERRY_ERROR_TYPE, + (jerry_char_t *) "It is not a object."); + } + + const jerry_object_native_info_t *expected_info_p; + const jerry_object_native_info_t *out_info_p; + expected_info_p = (const jerry_object_native_info_t *) c_arg_p->extra_info; + void **ptr_p = (void **) c_arg_p->dest; + bool is_ok = jerry_get_object_native_pointer (js_arg, ptr_p, &out_info_p); + + if (!is_ok || out_info_p != expected_info_p) + { + return jerry_create_error (JERRY_ERROR_TYPE, + (jerry_char_t *) "The object has no native pointer or type does not match."); + } + + return jerry_create_undefined (); +} /* jerryx_arg_transform_native_pointer */ + +/** + * Define transformer for optional argument. + */ +#define JERRYX_ARG_TRANSFORM_OPTIONAL(type) \ +jerry_value_t \ +jerryx_arg_transform_ ## type ## _optional (jerryx_arg_js_iterator_t *js_arg_iter_p, \ + const jerryx_arg_t *c_arg_p) \ +{ \ + return jerryx_arg_transform_optional (js_arg_iter_p, c_arg_p, jerryx_arg_transform_ ## type); \ +} + +JERRYX_ARG_TRANSFORM_OPTIONAL (number) +JERRYX_ARG_TRANSFORM_OPTIONAL (number_strict) +JERRYX_ARG_TRANSFORM_OPTIONAL (boolean) +JERRYX_ARG_TRANSFORM_OPTIONAL (boolean_strict) +JERRYX_ARG_TRANSFORM_OPTIONAL (string) +JERRYX_ARG_TRANSFORM_OPTIONAL (string_strict) +JERRYX_ARG_TRANSFORM_OPTIONAL (function) +JERRYX_ARG_TRANSFORM_OPTIONAL (native_pointer) + +/** + * Ignore the JS argument. + * + * @return jerry undefined + */ +jerry_value_t +jerryx_arg_transform_ignore (jerryx_arg_js_iterator_t *js_arg_iter_p, /**< available JS args */ + const jerryx_arg_t *c_arg_p) /**< the native arg */ +{ + (void) js_arg_iter_p; /* unused */ + (void) c_arg_p; /* unused */ + + return jerry_create_undefined (); +} /* jerryx_arg_transform_ignore */ diff --git a/jerry-ext/arg/arg.c b/jerry-ext/arg/arg.c new file mode 100644 index 0000000000..33620988a6 --- /dev/null +++ b/jerry-ext/arg/arg.c @@ -0,0 +1,88 @@ +/* Copyright JS Foundation and other contributors, http://js.foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "arg-internal.h" +#include "jerryscript-ext/arg.h" +#include "jerryscript.h" + +/** + * Validate the JS arguments and assign them to the native arguments. + * + * @return jerry undefined: all validators passed, + * jerry error: a validator failed. + */ +jerry_value_t +jerryx_arg_transform_args (const jerry_value_t *js_arg_p, /**< points to the array with JS arguments */ + const jerry_length_t js_arg_cnt, /**< the count of the `js_arg_p` 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 */ +{ + jerry_value_t ret = jerry_create_undefined (); + + jerryx_arg_js_iterator_t iterator = + { + .js_arg_p = js_arg_p, + .js_arg_cnt = js_arg_cnt, + .js_arg_idx = 0 + }; + + for (; c_arg_cnt != 0 && !jerry_value_has_error_flag (ret); c_arg_cnt--, c_arg_p++) + { + ret = c_arg_p->func (&iterator, c_arg_p); + } + + return ret; +} /* jerryx_arg_transform_args */ + +/** + * Validate the this value and the JS arguments, + * and assign them to the native arguments. + * This function is useful to perform input validation inside external + * function handlers (see jerry_external_handler_t). + * @note this_val is processed as the first value, before the array of arguments. + * + * @return jerry undefined: all validators passed, + * jerry error: a validator failed. + */ +jerry_value_t +jerryx_arg_transform_this_and_args (const jerry_value_t this_val, /**< the this_val for the external function */ + const jerry_value_t *js_arg_p, /**< points to the array with JS arguments */ + const jerry_length_t js_arg_cnt, /**< the count of the `js_arg_p` array */ + 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 */ +{ + if (c_arg_cnt == 0) + { + return jerry_create_undefined (); + } + + jerryx_arg_js_iterator_t iterator = + { + .js_arg_p = &this_val, + .js_arg_cnt = 1, + .js_arg_idx = 0 + }; + + jerry_value_t ret = c_arg_p->func (&iterator, c_arg_p); + + if (jerry_value_has_error_flag (ret)) + { + jerry_release_value (ret); + + return jerry_create_error (JERRY_ERROR_TYPE, (jerry_char_t *) "'this' validation failed"); + } + + return jerryx_arg_transform_args (js_arg_p, js_arg_cnt, c_arg_p + 1, c_arg_cnt - 1); +} /* jerryx_arg_transform_this_and_args */ diff --git a/jerry-ext/include/jerryscript-ext/arg.h b/jerry-ext/include/jerryscript-ext/arg.h new file mode 100644 index 0000000000..99a30d28ca --- /dev/null +++ b/jerry-ext/include/jerryscript-ext/arg.h @@ -0,0 +1,124 @@ +/* Copyright JS Foundation and other contributors, http://js.foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef JERRYX_ARG_H +#define JERRYX_ARG_H + +#include +#include +#include +#include +#include "jerryscript.h" + +#ifdef __cplusplus +extern "C" +{ +#endif /* __cplusplus */ + +/** + * The forward declaration of jerryx_arg_t. + */ +typedef struct jerryx_arg_t jerryx_arg_t; + +/** + * The forward declaration of jerryx_arg_js_iterator_t + */ +typedef struct jerryx_arg_js_iterator_t jerryx_arg_js_iterator_t; + +/** + * Signature of the transform function. + */ +typedef jerry_value_t (*jerryx_arg_transform_func_t) (jerryx_arg_js_iterator_t *js_arg_iter_p, /**< available JS args */ + const jerryx_arg_t *c_arg_p); /**< native arg */ + +/** + * The structure defining a single validation & transformation step. + */ +struct jerryx_arg_t +{ + jerryx_arg_transform_func_t func; /**< the transform function */ + void *dest; /**< pointer to destination where func should store the result */ + uintptr_t extra_info; /**< extra information, specific to func */ +}; + +jerry_value_t jerryx_arg_transform_this_and_args (const jerry_value_t this_val, + const jerry_value_t *js_arg_p, + const jerry_length_t js_arg_cnt, + const jerryx_arg_t *c_arg_p, + jerry_length_t c_arg_cnt); + +jerry_value_t jerryx_arg_transform_args (const jerry_value_t *js_arg_p, + const jerry_length_t js_arg_cnt, + 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. + */ +typedef enum +{ + JERRYX_ARG_COERCE, /**< the transform inside will invoke toNumber, toBoolean or toString */ + JERRYX_ARG_NO_COERCE /**< the type coercion is not allowed. */ +} jerryx_arg_coerce_t; + +/** + * Indicates whether an argument is optional or required. + */ +typedef enum +{ + /** + * The argument is optional. If the argument is `undefined` the transform is + * successful and `c_arg_p->dest` remains untouched. + */ + JERRYX_ARG_OPTIONAL, + /** + * The argument is required. If the argument is `undefined` the transform + * will fail and `c_arg_p->dest` remains untouched. + */ + JERRYX_ARG_REQUIRED +} jerryx_arg_optional_t; + +/* Inline functions for initializing jerryx_arg_t */ +static inline jerryx_arg_t +jerryx_arg_number (double *dest, jerryx_arg_coerce_t conv_flag, jerryx_arg_optional_t opt_flag); +static inline jerryx_arg_t +jerryx_arg_boolean (bool *dest, jerryx_arg_coerce_t conv_flag, jerryx_arg_optional_t opt_flag); +static inline jerryx_arg_t +jerryx_arg_string (char *dest, uint32_t size, jerryx_arg_coerce_t conv_flag, jerryx_arg_optional_t opt_flag); +static inline jerryx_arg_t +jerryx_arg_function (jerry_value_t *dest, jerryx_arg_optional_t opt_flag); +static inline jerryx_arg_t +jerryx_arg_native_pointer (void **dest, const jerry_object_native_info_t *info_p, jerryx_arg_optional_t opt_flag); +static inline jerryx_arg_t +jerryx_arg_ignore (void); +static inline jerryx_arg_t +jerryx_arg_custom (void *dest, uintptr_t extra_info, jerryx_arg_transform_func_t func); + +jerry_value_t +jerryx_arg_transform_optional (jerryx_arg_js_iterator_t *js_arg_iter_p, + const jerryx_arg_t *c_arg_p, + jerryx_arg_transform_func_t func); + +/* Helper functions for transform functions. */ +jerry_value_t jerryx_arg_js_iterator_pop (jerryx_arg_js_iterator_t *js_arg_iter_p); +jerry_value_t jerryx_arg_js_iterator_peek (jerryx_arg_js_iterator_t *js_arg_iter_p); +jerry_length_t jerryx_arg_js_iterator_index (jerryx_arg_js_iterator_t *js_arg_iter_p); + +#include "arg.impl.h" + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* !JERRYX_ARG_H */ diff --git a/jerry-ext/include/jerryscript-ext/arg.impl.h b/jerry-ext/include/jerryscript-ext/arg.impl.h new file mode 100644 index 0000000000..dbd2117eea --- /dev/null +++ b/jerry-ext/include/jerryscript-ext/arg.impl.h @@ -0,0 +1,278 @@ +/* Copyright JS Foundation and other contributors, http://js.foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef JERRYX_ARG_IMPL_H +#define JERRYX_ARG_IMPL_H + +/* transform functions for each type. */ +jerry_value_t jerryx_arg_transform_number_strict (jerryx_arg_js_iterator_t *js_arg_iter_p, + const jerryx_arg_t *c_arg_p); +jerry_value_t jerryx_arg_transform_number_strict_optional (jerryx_arg_js_iterator_t *js_arg_iter_p, + const jerryx_arg_t *c_arg_p); +jerry_value_t jerryx_arg_transform_number_optional (jerryx_arg_js_iterator_t *js_arg_iter_p, + const jerryx_arg_t *c_arg_p); +jerry_value_t jerryx_arg_transform_number (jerryx_arg_js_iterator_t *js_arg_iter_p, + const jerryx_arg_t *c_arg_p); +jerry_value_t jerryx_arg_transform_boolean_strict (jerryx_arg_js_iterator_t *js_arg_iter_p, + const jerryx_arg_t *c_arg_p); +jerry_value_t jerryx_arg_transform_boolean_strict_optional (jerryx_arg_js_iterator_t *js_arg_iter_p, + const jerryx_arg_t *c_arg_p); +jerry_value_t jerryx_arg_transform_boolean_optional (jerryx_arg_js_iterator_t *js_arg_iter_p, + const jerryx_arg_t *c_arg_p); +jerry_value_t jerryx_arg_transform_boolean (jerryx_arg_js_iterator_t *js_arg_iter_p, + const jerryx_arg_t *c_arg_p); +jerry_value_t jerryx_arg_transform_string_strict (jerryx_arg_js_iterator_t *js_arg_iter_p, + const jerryx_arg_t *c_arg_p); +jerry_value_t jerryx_arg_transform_string_strict_optional (jerryx_arg_js_iterator_t *js_arg_iter_p, + const jerryx_arg_t *c_arg_p); +jerry_value_t jerryx_arg_transform_string_optional (jerryx_arg_js_iterator_t *js_arg_iter_p, + const jerryx_arg_t *c_arg_p); +jerry_value_t jerryx_arg_transform_string (jerryx_arg_js_iterator_t *js_arg_iter_p, + const jerryx_arg_t *c_arg_p); +jerry_value_t jerryx_arg_transform_function (jerryx_arg_js_iterator_t *js_arg_iter_p, + const jerryx_arg_t *c_arg_p); +jerry_value_t jerryx_arg_transform_function_optional (jerryx_arg_js_iterator_t *js_arg_iter_p, + const jerryx_arg_t *c_arg_p); +jerry_value_t jerryx_arg_transform_native_pointer (jerryx_arg_js_iterator_t *js_arg_iter_p, + const jerryx_arg_t *c_arg_p); +jerry_value_t jerryx_arg_transform_native_pointer_optional (jerryx_arg_js_iterator_t *js_arg_iter_p, + const jerryx_arg_t *c_arg_p); +jerry_value_t jerryx_arg_transform_ignore (jerryx_arg_js_iterator_t *js_arg_iter_p, + const jerryx_arg_t *c_arg_p); + +/** + * Create a validation/transformation step (`jerryx_arg_t`) that expects to + * consume one `number` JS argument and stores it into a C `double`. + * + * @return a jerryx_arg_t instance. + */ +static inline jerryx_arg_t +jerryx_arg_number (double *dest, /**< pointer to the double where the result should be stored */ + jerryx_arg_coerce_t coerce_flag, /**< whether type coercion is allowed */ + jerryx_arg_optional_t opt_flag) /**< whether the argument is optional */ +{ + jerryx_arg_transform_func_t func; + + if (coerce_flag == JERRYX_ARG_NO_COERCE) + { + if (opt_flag == JERRYX_ARG_OPTIONAL) + { + func = jerryx_arg_transform_number_strict_optional; + } + else + { + func = jerryx_arg_transform_number_strict; + } + } + else + { + if (opt_flag == JERRYX_ARG_OPTIONAL) + { + func = jerryx_arg_transform_number_optional; + } + else + { + func = jerryx_arg_transform_number; + } + } + + return (jerryx_arg_t) + { + .func = func, + .dest = (void *) dest + }; +} /* jerryx_arg_number */ + +/** + * Create a validation/transformation step (`jerryx_arg_t`) that expects to + * consume one `boolean` JS argument and stores it into a C `bool`. + * + * @return a jerryx_arg_t instance. + */ +static inline jerryx_arg_t +jerryx_arg_boolean (bool *dest, /**< points to the native bool */ + jerryx_arg_coerce_t coerce_flag, /**< whether type coercion is allowed */ + jerryx_arg_optional_t opt_flag) /**< whether the argument is optional */ +{ + jerryx_arg_transform_func_t func; + + if (coerce_flag == JERRYX_ARG_NO_COERCE) + { + if (opt_flag == JERRYX_ARG_OPTIONAL) + { + func = jerryx_arg_transform_boolean_strict_optional; + } + else + { + func = jerryx_arg_transform_boolean_strict; + } + } + else + { + if (opt_flag == JERRYX_ARG_OPTIONAL) + { + func = jerryx_arg_transform_boolean_optional; + } + else + { + func = jerryx_arg_transform_boolean; + } + } + + return (jerryx_arg_t) + { + .func = func, + .dest = (void *) dest + }; +} /* jerryx_arg_boolean */ + +/** + * Create a validation/transformation step (`jerryx_arg_t`) that expects to + * consume one `string` JS argument and stores it into a C `char` array. + * + * @return a jerryx_arg_t instance. + */ +static inline jerryx_arg_t +jerryx_arg_string (char *dest, /**< pointer to the native char array where the result should be stored */ + uint32_t size, /**< the size of native char array */ + jerryx_arg_coerce_t coerce_flag, /**< whether type coercion is allowed */ + jerryx_arg_optional_t opt_flag) /**< whether the argument is optional */ +{ + jerryx_arg_transform_func_t func; + + if (coerce_flag == JERRYX_ARG_NO_COERCE) + { + if (opt_flag == JERRYX_ARG_OPTIONAL) + { + func = jerryx_arg_transform_string_strict_optional; + } + else + { + func = jerryx_arg_transform_string_strict; + } + } + else + { + if (opt_flag == JERRYX_ARG_OPTIONAL) + { + func = jerryx_arg_transform_string_optional; + } + else + { + func = jerryx_arg_transform_string; + } + } + + return (jerryx_arg_t) + { + .func = func, + .dest = (void *) dest, + .extra_info = (uintptr_t) size + }; +} /* jerryx_arg_string */ + +/** + * Create a validation/transformation step (`jerryx_arg_t`) that expects to + * consume one `function` JS argument and stores it into a C `jerry_value_t`. + * + * @return a jerryx_arg_t instance. + */ +static inline jerryx_arg_t +jerryx_arg_function (jerry_value_t *dest, /**< pointer to the jerry_value_t where the result should be stored */ + 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_function_optional; + } + else + { + func = jerryx_arg_transform_function; + } + + return (jerryx_arg_t) + { + .func = func, + .dest = (void *) dest + }; +} /* jerryx_arg_function */ + +/** + * Create a validation/transformation step (`jerryx_arg_t`) that expects to + * consume one `Object` JS argument that is 'backed' with a native pointer with + * a given type info. In case the native pointer info matches, the transform + * will succeed and the object's native pointer will be assigned to *dest. + * + * @return a jerryx_arg_t instance. + */ +static inline jerryx_arg_t +jerryx_arg_native_pointer (void **dest, /**< pointer to where the resulting native pointer should be stored */ + const jerry_object_native_info_t *info_p, /**< expected the type info */ + 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_native_pointer_optional; + } + else + { + func = jerryx_arg_transform_native_pointer; + } + + return (jerryx_arg_t) + { + .func = func, + .dest = (void *) dest, + .extra_info = (uintptr_t) info_p + }; +} /* jerryx_arg_native_pointer */ + +/** + * Create a jerryx_arg_t instance for ignored argument. + * + * @return a jerryx_arg_t instance. + */ +static inline jerryx_arg_t +jerryx_arg_ignore (void) +{ + return (jerryx_arg_t) + { + .func = jerryx_arg_transform_ignore + }; +} /* jerryx_arg_ignore */ + +/** + * Create a jerryx_arg_t instance with custom transform. + * + * @return a jerryx_arg_t instance. + */ +static inline jerryx_arg_t +jerryx_arg_custom (void *dest, /**< pointer to the native argument where the result should be stored */ + uintptr_t extra_info, /**< the extra parameter, specific to the transform function */ + jerryx_arg_transform_func_t func) /**< the custom transform function */ +{ + return (jerryx_arg_t) + { + .func = func, + .dest = dest, + .extra_info = extra_info + }; +} /* jerryx_arg_custom */ + +#endif /* !JERRYX_ARG_IMPL_H */ diff --git a/tests/unit-ext/CMakeLists.txt b/tests/unit-ext/CMakeLists.txt new file mode 100644 index 0000000000..53896df62a --- /dev/null +++ b/tests/unit-ext/CMakeLists.txt @@ -0,0 +1,37 @@ +# Copyright JS Foundation and other contributors, http://js.foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +cmake_minimum_required (VERSION 2.8.12) +project (unit-ext C) + +# Unit tests main modules +file(GLOB SOURCE_UNIT_TEST_EXT_MODULES *.c) + +# Unit tests declaration +add_custom_target(unittests-ext) + +foreach(SOURCE_UNIT_TEST_EXT ${SOURCE_UNIT_TEST_EXT_MODULES}) + get_filename_component(TARGET_NAME ${SOURCE_UNIT_TEST_EXT} NAME_WE) + set(TARGET_NAME unit-${TARGET_NAME}) + + add_executable(${TARGET_NAME} ${SOURCE_UNIT_TEST_EXT}) + set_property(TARGET ${TARGET_NAME} + PROPERTY LINK_FLAGS "${LINKER_FLAGS_COMMON}") + + link_directories(${CMAKE_BINARY_DIR}) + + target_link_libraries(${TARGET_NAME} jerry-ext jerry-core jerry-port-default-minimal) + + add_dependencies(unittests-ext ${TARGET_NAME}) +endforeach() diff --git a/tests/unit-ext/test-common.h b/tests/unit-ext/test-common.h new file mode 100644 index 0000000000..0254a00969 --- /dev/null +++ b/tests/unit-ext/test-common.h @@ -0,0 +1,36 @@ +/* Copyright JS Foundation and other contributors, http://js.foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TEST_COMMON_H +#define TEST_COMMON_H + +#include "jerryscript-port.h" + +#define TEST_ASSERT(x) \ + do \ + { \ + if (!(x)) \ + { \ + jerry_port_log (JERRY_LOG_LEVEL_ERROR, \ + "TEST: Assertion '%s' failed at %s(%s):%lu.\n", \ + #x, \ + __FILE__, \ + __func__, \ + (unsigned long) __LINE__); \ + jerry_port_fatal (ERR_FAILED_INTERNAL_ASSERTION); \ + } \ + } while (0) + +#endif /* TEST_COMMON_H */ diff --git a/tests/unit-ext/test-ext-arg.c b/tests/unit-ext/test-ext-arg.c new file mode 100644 index 0000000000..b0d20015f0 --- /dev/null +++ b/tests/unit-ext/test-ext-arg.c @@ -0,0 +1,290 @@ +/* Copyright JS Foundation and other contributors, http://js.foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Unit test for jerry-ext/args. + */ + +#include "jerryscript.h" +#include "jerryscript-ext/arg.h" +#include "test-common.h" + +#include + +const char *test_source = ( + "var arg1 = true;" + "var arg2 = 10.5;" + "var arg3 = 'abc';" + "var arg4 = function foo() {};" + "test_validator1(arg1, arg2, arg3, arg4);" + "arg1 = new Boolean(true);" + "arg3 = new String('abc');" + "test_validator1(arg1, arg2, arg3);" + "arg2 = new Number(10.5);" + "test_validator1(arg1, arg2, arg3);" + "test_validator1(arg1, 10.5, 'abcdef');" + "var obj_a = new MyObjectA();" + "var obj_b = new MyObjectB();" + "test_validator2.call(obj_a, 5);" + "test_validator2.call(obj_b, 5);" + "test_validator2.call(obj_a, 1);" + ); + +static const jerry_object_native_info_t thing_a_info = +{ + .free_cb = NULL +}; + +static const jerry_object_native_info_t thing_b_info = +{ + .free_cb = NULL +}; + +typedef struct +{ + int x; +} my_type_a_t; + +typedef struct +{ + bool x; +} my_type_b_t; + +static my_type_a_t my_thing_a; +static my_type_b_t my_thing_b; + +static int validator1_count = 0; +static int validator2_count = 0; + +/** + * The handler should have following arguments: + * this: Ignore. + * arg1: Bool. + * arg2: Number. It must be strict primitive number. + * arg3: String. + * arg4: function. It is an optional argument. + * + */ +static jerry_value_t +test_validator1_handler (const jerry_value_t func_obj_val __attribute__((unused)), /**< function object */ + const jerry_value_t this_val, /**< this value */ + const jerry_value_t args_p[], /**< arguments list */ + const jerry_length_t args_cnt) /**< arguments length */ +{ + + bool arg1; + double arg2 = 0.0; + char arg3[5]; + jerry_value_t arg4 = jerry_create_undefined (); + + jerryx_arg_t mapping[] = + { + /* ignore this */ + jerryx_arg_ignore (), + /* 1st argument should be boolean */ + jerryx_arg_boolean (&arg1, JERRYX_ARG_COERCE, JERRYX_ARG_REQUIRED), + /* 2nd argument should be strict number */ + jerryx_arg_number (&arg2, JERRYX_ARG_NO_COERCE, JERRYX_ARG_REQUIRED), + /* 3th argument should be string */ + jerryx_arg_string (arg3, 5, JERRYX_ARG_COERCE, JERRYX_ARG_REQUIRED), + /* 4th argument should be function, and it is optional */ + jerryx_arg_function (&arg4, JERRYX_ARG_OPTIONAL) + }; + + jerry_value_t is_ok = jerryx_arg_transform_this_and_args (this_val, + args_p, + args_cnt, + mapping, + 5); + + if (validator1_count == 0) + { + TEST_ASSERT (!jerry_value_has_error_flag (is_ok)); + TEST_ASSERT (arg1); + TEST_ASSERT (arg2 == 10.5); + TEST_ASSERT (arg3[0] == 'a' && arg3[1] == 'b' + && arg3[2] == 'c' && arg3[4] == '\0'); + TEST_ASSERT (jerry_value_is_function (arg4)); + } + else if (validator1_count == 1) + { + TEST_ASSERT (!jerry_value_has_error_flag (is_ok)); + TEST_ASSERT (arg1); + TEST_ASSERT (arg2 == 10.5); + TEST_ASSERT (arg3[0] == 'a' && arg3[1] == 'b' + && arg3[2] == 'c' && arg3[4] == '\0'); + TEST_ASSERT (jerry_value_is_undefined (arg4)); + } + else + { + TEST_ASSERT (jerry_value_has_error_flag (is_ok)); + } + + jerry_release_value (is_ok); + jerry_release_value (arg4); + validator1_count ++; + + return jerry_create_undefined (); +} /* test_validator1_handler */ + +/** + * The JS argument should be number, whose value is equal with the extra_info . + */ +static jerry_value_t +my_custom_transform (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); + jerry_value_t to_number = jerry_value_to_number (js_arg); + + if (jerry_value_has_error_flag (to_number)) + { + jerry_release_value (to_number); + + return jerry_create_error (JERRY_ERROR_TYPE, + (jerry_char_t *) "It can not be converted to a number."); + } + + int expected_num = (int) c_arg_p->extra_info; + int get_num = (int) jerry_get_number_value (to_number); + + if (get_num != expected_num) + { + return jerry_create_error (JERRY_ERROR_TYPE, + (jerry_char_t *) "Number value is not expected."); + } + + return jerry_create_undefined (); +} /* my_custom_transform */ + +/** + * The handler should have following arguments: + * this: with native pointer whose type is bind_a_info. + * arg1: should pass the custom tranform function. + */ +static jerry_value_t +test_validator2_handler (const jerry_value_t func_obj_val __attribute__((unused)), /**< function object */ + const jerry_value_t this_val, /**< this value */ + const jerry_value_t args_p[], /**< arguments list */ + const jerry_length_t args_cnt) /**< arguments length */ +{ + my_type_a_t *thing_p; + + jerryx_arg_t mapping[] = + { + /* this should has native pointer, whose type is thing_a_info */ + jerryx_arg_native_pointer ((void **) &thing_p, &thing_a_info, JERRYX_ARG_REQUIRED), + /* custom tranform function */ + jerryx_arg_custom (NULL, 5, my_custom_transform) + }; + + jerry_value_t is_ok = jerryx_arg_transform_this_and_args (this_val, + args_p, + args_cnt, + mapping, + 2); + + if (validator2_count == 0) + { + TEST_ASSERT (!jerry_value_has_error_flag (is_ok)); + TEST_ASSERT (thing_p == &my_thing_a); + TEST_ASSERT (thing_p->x == 1); + } + else + { + TEST_ASSERT (jerry_value_has_error_flag (is_ok)); + } + + jerry_release_value (is_ok); + validator2_count ++; + + return jerry_create_undefined (); +} /* test_validator2_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 */ + const jerry_value_t args_p[] __attribute__((unused)), /**< arguments list */ + const jerry_length_t args_cnt __attribute__((unused))) /**< arguments length */ +{ + TEST_ASSERT (jerry_value_is_object (this_val)); + + my_thing_a.x = 1; + jerry_set_object_native_pointer (this_val, + &my_thing_a, + &thing_a_info); + + return jerry_create_boolean (true); +} /* create_object_a_handler */ + +static jerry_value_t +create_object_b_handler (const jerry_value_t func_obj_val __attribute__((unused)), /**< function object */ + const jerry_value_t this_val, /**< this value */ + const jerry_value_t args_p[] __attribute__((unused)), /**< arguments list */ + const jerry_length_t args_cnt __attribute__((unused))) /**< arguments length */ +{ + TEST_ASSERT (jerry_value_is_object (this_val)); + + my_thing_b.x = false; + jerry_set_object_native_pointer (this_val, + &my_thing_b, + &thing_b_info); + + return jerry_create_boolean (true); +} /* create_object_b_handler */ + +/** + * Register a JavaScript function in the global object. + */ +static void +register_js_function (const char *name_p, /**< name of the function */ + jerry_external_handler_t handler_p) /**< function callback */ +{ + jerry_value_t global_obj_val = jerry_get_global_object (); + + jerry_value_t function_val = jerry_create_external_function (handler_p); + jerry_value_t function_name_val = jerry_create_string ((const jerry_char_t *) name_p); + jerry_value_t result_val = jerry_set_property (global_obj_val, function_name_val, function_val); + + jerry_release_value (function_name_val); + jerry_release_value (function_val); + jerry_release_value (global_obj_val); + + jerry_release_value (result_val); +} /* register_js_function */ + +int +main (void) +{ + jerry_init (JERRY_INIT_EMPTY); + + register_js_function ("test_validator1", test_validator1_handler); + register_js_function ("test_validator2", test_validator2_handler); + register_js_function ("MyObjectA", create_object_a_handler); + register_js_function ("MyObjectB", create_object_b_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)); + + jerry_value_t res = jerry_run (parsed_code_val); + TEST_ASSERT (!jerry_value_has_error_flag (res)); + TEST_ASSERT (validator1_count == 4); + TEST_ASSERT (validator2_count == 3); + + jerry_release_value (res); + jerry_release_value (parsed_code_val); + + jerry_cleanup (); +} /* main */ diff --git a/tools/build.py b/tools/build.py index d6c1af63ce..7ab91d7997 100755 --- a/tools/build.py +++ b/tools/build.py @@ -74,6 +74,8 @@ def devhelp(helpstring): help='enable the jerry debugger (%(choices)s; default: %(default)s)') parser.add_argument('--jerry-debugger-port', metavar='N', action='store', type=int, default=5001, help='add custom port number (default: %(default)s)') + parser.add_argument('--jerry-ext', metavar='X', choices=['ON', 'OFF'], default='ON', type=str.upper, + help='build jerry-ext (default: %(default)s)') parser.add_argument('--jerry-libc', metavar='X', choices=['ON', 'OFF'], default='ON', type=str.upper, help='build and use jerry-libc (%(choices)s; default: %(default)s)') parser.add_argument('--jerry-libm', metavar='X', choices=['ON', 'OFF'], default='ON', type=str.upper, @@ -145,6 +147,7 @@ def generate_build_options(arguments): build_options.append('-DJERRY_CMDLINE=%s' % arguments.jerry_cmdline) build_options.append('-DJERRY_CMDLINE_MINIMAL=%s' % arguments.jerry_cmdline_minimal) build_options.append('-DJERRY_PORT_DEFAULT=%s' % arguments.jerry_port_default) + build_options.append('-DJERRY_EXT=%s' % arguments.jerry_ext) build_options.append('-DJERRY_LIBC=%s' % arguments.jerry_libc) build_options.append('-DJERRY_LIBM=%s' % arguments.jerry_libm) build_options.append('-DFEATURE_JS_PARSER=%s' % arguments.js_parser) diff --git a/tools/check-cppcheck.sh b/tools/check-cppcheck.sh index c6dd1dbc52..6f7cb54269 100755 --- a/tools/check-cppcheck.sh +++ b/tools/check-cppcheck.sh @@ -23,12 +23,14 @@ else fi JERRY_CORE_DIRS=`find jerry-core -type d` +JERRY_EXT_DIRS=`find jerry-ext -type d` JERRY_PORT_DIRS=`find jerry-port -type d` JERRY_LIBC_DIRS=`find jerry-libc -type d` JERRY_LIBM_DIRS=`find jerry-libm -type d` + INCLUDE_DIRS=() -for DIR in $JERRY_CORE_DIRS $JERRY_PORT_DIRS $JERRY_LIBC_DIRS $JERRY_LIBM_DIRS +for DIR in $JERRY_CORE_DIRS $JERRY_EXT_DIRS $JERRY_PORT_DIRS $JERRY_LIBC_DIRS $JERRY_LIBM_DIRS do INCLUDE_DIRS=("${INCLUDE_DIRS[@]}" "-I$DIR") done @@ -41,4 +43,4 @@ cppcheck -j$CPPCHECK_JOBS --force \ --exitcode-suppressions=tools/cppcheck/suppressions-list \ --suppressions-list=tools/cppcheck/suppressions-list \ "${INCLUDE_DIRS[@]}" \ - jerry-core jerry-port jerry-libc jerry-libm jerry-main tests/unit-* + jerry-core jerry-ext jerry-port jerry-libc jerry-libm jerry-main tests/unit-* diff --git a/tools/check-license.py b/tools/check-license.py index 76573d6f72..56d4b3c232 100755 --- a/tools/check-license.py +++ b/tools/check-license.py @@ -40,6 +40,7 @@ INCLUDE_DIRS = [ 'cmake', 'jerry-core', + 'jerry-ext', 'jerry-libc', 'jerry-libm', 'jerry-main', diff --git a/tools/check-vera.sh b/tools/check-vera.sh index 617bc2eb9a..e837545890 100755 --- a/tools/check-vera.sh +++ b/tools/check-vera.sh @@ -15,6 +15,7 @@ # limitations under the License. JERRY_CORE_FILES=`find ./jerry-core -name "*.c" -or -name "*.h"` +JERRY_EXT_FILES=`find ./jerry-ext -name "*.c" -or -name "*.h"` JERRY_PORT_FILES=`find ./jerry-port -name "*.c" -or -name "*.h"` JERRY_LIBC_FILES=`find ./jerry-libc -name "*.c" -or -name "*.h"` JERRY_LIBM_FILES=`find ./jerry-libm -name "*.c" -or -name "*.h"` @@ -28,4 +29,4 @@ fi vera++ -r tools/vera++ -p jerry \ -e --no-duplicate \ - $MANUAL_CHECK_FILES $JERRY_CORE_FILES $JERRY_PORT_FILES $JERRY_LIBC_FILES $JERRY_LIBM_FILES $JERRY_MAIN_FILES $UNIT_TEST_FILES + $MANUAL_CHECK_FILES $JERRY_CORE_FILES $JERRY_EXT_FILES $JERRY_PORT_FILES $JERRY_LIBC_FILES $JERRY_LIBM_FILES $JERRY_MAIN_FILES $UNIT_TEST_FILES diff --git a/tools/cppcheck/suppressions-list b/tools/cppcheck/suppressions-list index 8f55d6e0c2..29006b5bc5 100644 --- a/tools/cppcheck/suppressions-list +++ b/tools/cppcheck/suppressions-list @@ -2,3 +2,4 @@ wrongmathcall:tests/unit-libm/test-libm.inc.h variableScope:jerry-libm/*.c invalidPointerCast:jerry-libm/*.c arithOperationsOnVoidPointer:jerry-libc/*.c +commaSeparatedReturn:jerry-ext/include/jerryscript-ext/arg.impl.h