diff --git a/jerry-core/config.h b/jerry-core/config.h index e5d39eb92..5c52d4541 100644 --- a/jerry-core/config.h +++ b/jerry-core/config.h @@ -40,6 +40,7 @@ # define CONFIG_DISABLE_ES2015_BUILTIN # define CONFIG_DISABLE_ES2015_CLASS # define CONFIG_DISABLE_ES2015_FUNCTION_PARAMETER_INITIALIZER +# define CONFIG_DISABLE_ES2015_FUNCTION_REST_PARAMETER # define CONFIG_DISABLE_ES2015_MAP_BUILTIN # define CONFIG_DISABLE_ES2015_OBJECT_INITIALIZER # define CONFIG_DISABLE_ES2015_PROMISE_BUILTIN diff --git a/jerry-core/include/jerryscript-snapshot.h b/jerry-core/include/jerryscript-snapshot.h index 29b6b9080..db1282dbb 100644 --- a/jerry-core/include/jerryscript-snapshot.h +++ b/jerry-core/include/jerryscript-snapshot.h @@ -30,7 +30,7 @@ extern "C" /** * Jerry snapshot format version. */ -#define JERRY_SNAPSHOT_VERSION (20u) +#define JERRY_SNAPSHOT_VERSION (21u) /** * Flags for jerry_generate_snapshot and jerry_generate_function_snapshot. diff --git a/jerry-core/parser/js/byte-code.h b/jerry-core/parser/js/byte-code.h index 1f2a44dbc..5aa5c8cff 100644 --- a/jerry-core/parser/js/byte-code.h +++ b/jerry-core/parser/js/byte-code.h @@ -648,6 +648,7 @@ typedef enum CBC_CODE_FLAGS_STATIC_FUNCTION = (1u << 7), /**< this function is a static snapshot function */ CBC_CODE_FLAGS_DEBUGGER_IGNORE = (1u << 8), /**< this function should be ignored by debugger */ CBC_CODE_FLAGS_CONSTRUCTOR = (1u << 9), /**< this function is a constructor */ + CBC_CODE_FLAGS_REST_PARAMETER = (1u << 10), /**< this function has rest parameter */ } cbc_code_flags; /** diff --git a/jerry-core/parser/js/js-lexer.c b/jerry-core/parser/js/js-lexer.c index 6a8657afa..97f0eaa1c 100644 --- a/jerry-core/parser/js/js-lexer.c +++ b/jerry-core/parser/js/js-lexer.c @@ -1152,6 +1152,17 @@ lexer_next_token (parser_context_t *context_p) /**< context */ return; } +#ifndef CONFIG_DISABLE_ES2015_FUNCTION_REST_PARAMETER + if (length >= 3 + && context_p->source_p[1] == LIT_CHAR_DOT + && context_p->source_p[2] == LIT_CHAR_DOT) + { + context_p->token.type = LEXER_THREE_DOTS; + length = 3; + break; + } +#endif /* !CONFIG_DISABLE_ES2015_FUNCTION_REST_PARAMETER */ + context_p->token.type = LEXER_DOT; length = 1; break; diff --git a/jerry-core/parser/js/js-lexer.h b/jerry-core/parser/js/js-lexer.h index dcf20aa79..3f6672757 100644 --- a/jerry-core/parser/js/js-lexer.h +++ b/jerry-core/parser/js/js-lexer.h @@ -42,6 +42,9 @@ typedef enum #ifndef CONFIG_DISABLE_ES2015_TEMPLATE_STRINGS LEXER_TEMPLATE_LITERAL, /**< multi segment template literal */ #endif /* !CONFIG_DISABLE_ES2015_TEMPLATE_STRINGS */ +#ifndef CONFIG_DISABLE_ES2015_FUNCTION_REST_PARAMETER + LEXER_THREE_DOTS, /**< ... (rest or spread operator) */ +#endif /* !CONFIG_DISABLE_ES2015_FUNCTION_REST_PARAMETER */ /* Unary operators * IMPORTANT: update CBC_UNARY_OP_TOKEN_TO_OPCODE and diff --git a/jerry-core/parser/js/js-parser-internal.h b/jerry-core/parser/js/js-parser-internal.h index 9896b9a0c..8681da0ee 100644 --- a/jerry-core/parser/js/js-parser-internal.h +++ b/jerry-core/parser/js/js-parser-internal.h @@ -45,6 +45,9 @@ typedef enum PARSER_IS_FUNC_EXPRESSION = (1u << 3), /**< a function expression is parsed */ PARSER_IS_PROPERTY_GETTER = (1u << 4), /**< a property getter function is parsed */ PARSER_IS_PROPERTY_SETTER = (1u << 5), /**< a property setter function is parsed */ +#ifndef CONFIG_DISABLE_ES2015_FUNCTION_REST_PARAMETER + PARSER_FUNCTION_HAS_REST_PARAM = (1u << 6), /**< function has rest parameter */ +#endif /* !CONFIG_DISABLE_ES2015_FUNCTION_REST_PARAMETER */ PARSER_HAS_NON_STRICT_ARG = (1u << 7), /**< the function has arguments which * are not supported in strict mode */ PARSER_ARGUMENTS_NEEDED = (1u << 8), /**< arguments object must be created */ diff --git a/jerry-core/parser/js/js-parser-scanner.c b/jerry-core/parser/js/js-parser-scanner.c index 17adefc4c..1eb10e6ce 100644 --- a/jerry-core/parser/js/js-parser-scanner.c +++ b/jerry-core/parser/js/js-parser-scanner.c @@ -865,6 +865,13 @@ parser_scan_until (parser_context_t *context_p, /**< context */ { while (true) { +#ifndef CONFIG_DISABLE_ES2015_FUNCTION_REST_PARAMETER + if (context_p->token.type == LEXER_THREE_DOTS) + { + lexer_next_token (context_p); + } +#endif /* !CONFIG_DISABLE_ES2015_FUNCTION_REST_PARAMETER */ + if (context_p->token.type != LEXER_LITERAL || context_p->token.lit_location.type != LEXER_IDENT_LITERAL) { diff --git a/jerry-core/parser/js/js-parser-util.c b/jerry-core/parser/js/js-parser-util.c index ae31aad1a..b4f697ac7 100644 --- a/jerry-core/parser/js/js-parser-util.c +++ b/jerry-core/parser/js/js-parser-util.c @@ -1043,6 +1043,16 @@ parser_error_to_string (parser_error_t error) /**< error code */ { return "Duplicated function argument names are not allowed here."; } +#endif /* !CONFIG_DISABLE_ES2015_FUNCTION_PARAMETER_INITIALIZER */ +#ifndef CONFIG_DISABLE_ES2015_FUNCTION_PARAMETER_INITIALIZER + case PARSER_ERR_FORMAL_PARAM_AFTER_REST_PARAMETER: + { + return "Rest parameter must be last formal parameter."; + } + case PARSER_ERR_REST_PARAMETER_DEFAULT_INITIALIZER: + { + return "Rest parameter may not have a default initializer."; + } #endif /* !CONFIG_DISABLE_ES2015_FUNCTION_PARAMETER_INITIALIZER */ case PARSER_ERR_OBJECT_PROPERTY_REDEFINED: { diff --git a/jerry-core/parser/js/js-parser.c b/jerry-core/parser/js/js-parser.c index 45346b7f4..43862e363 100644 --- a/jerry-core/parser/js/js-parser.c +++ b/jerry-core/parser/js/js-parser.c @@ -700,6 +700,14 @@ parser_generate_initializers (parser_context_t *context_p, /**< context */ } } +#ifndef CONFIG_DISABLE_ES2015_FUNCTION_REST_PARAMETER + if (context_p->status_flags & PARSER_FUNCTION_HAS_REST_PARAM) + { + JERRY_ASSERT ((argument_count - 1) == context_p->argument_count); + return dst_p; + } +#endif /* !CONFIG_DISABLE_ES2015_FUNCTION_REST_PARAMETER */ + JERRY_ASSERT (argument_count == context_p->argument_count); return dst_p; } /* parser_generate_initializers */ @@ -1742,6 +1750,14 @@ parser_post_processing (parser_context_t *context_p) /**< context */ compiled_code_p->refs = 1; compiled_code_p->status_flags = CBC_CODE_FLAGS_FUNCTION; +#ifndef CONFIG_DISABLE_ES2015_FUNCTION_REST_PARAMETER + if (context_p->status_flags & PARSER_FUNCTION_HAS_REST_PARAM) + { + JERRY_ASSERT (context_p->argument_count > 0); + context_p->argument_count--; + } +#endif /* !CONFIG_DISABLE_ES2015_FUNCTION_REST_PARAMETER */ + if (needs_uint16_arguments) { cbc_uint16_arguments_t *args_p = (cbc_uint16_arguments_t *) compiled_code_p; @@ -1807,6 +1823,13 @@ parser_post_processing (parser_context_t *context_p) /**< context */ } #endif /* !CONFIG_DISABLE_ES2015_CLASS */ +#ifndef CONFIG_DISABLE_ES2015_FUNCTION_REST_PARAMETER + if (context_p->status_flags & PARSER_FUNCTION_HAS_REST_PARAM) + { + compiled_code_p->status_flags |= CBC_CODE_FLAGS_REST_PARAMETER; + } +#endif /* !CONFIG_DISABLE_ES2015_FUNCTION_REST_PARAMETER */ + literal_pool_p = (ecma_value_t *) byte_code_p; literal_pool_p -= context_p->register_count; byte_code_p += literal_length; @@ -2160,6 +2183,18 @@ parser_parse_function_arguments (parser_context_t *context_p, /**< context */ { uint16_t literal_count = context_p->literal_count; +#ifndef CONFIG_DISABLE_ES2015_FUNCTION_REST_PARAMETER + if (context_p->status_flags & PARSER_FUNCTION_HAS_REST_PARAM) + { + parser_raise_error (context_p, PARSER_ERR_FORMAL_PARAM_AFTER_REST_PARAMETER); + } + else if (context_p->token.type == LEXER_THREE_DOTS) + { + lexer_expect_identifier (context_p, LEXER_IDENT_LITERAL); + context_p->status_flags |= PARSER_FUNCTION_HAS_REST_PARAM; + } +#endif /* !CONFIG_DISABLE_ES2015_FUNCTION_REST_PARAMETER */ + if (context_p->token.type != LEXER_LITERAL || context_p->token.lit_location.type != LEXER_IDENT_LITERAL) { @@ -2239,6 +2274,13 @@ parser_parse_function_arguments (parser_context_t *context_p, /**< context */ #ifndef CONFIG_DISABLE_ES2015_FUNCTION_PARAMETER_INITIALIZER if (context_p->token.type == LEXER_ASSIGN) { +#ifndef CONFIG_DISABLE_ES2015_FUNCTION_REST_PARAMETER + if (context_p->status_flags & PARSER_FUNCTION_HAS_REST_PARAM) + { + parser_raise_error (context_p, PARSER_ERR_REST_PARAMETER_DEFAULT_INITIALIZER); + } +#endif /* !CONFIG_DISABLE_ES2015_FUNCTION_REST_PARAMETER */ + parser_branch_t skip_init; if (duplicated_argument_names) diff --git a/jerry-core/parser/js/js-parser.h b/jerry-core/parser/js/js-parser.h index 7aa6a2a7e..a8fab2ea2 100644 --- a/jerry-core/parser/js/js-parser.h +++ b/jerry-core/parser/js/js-parser.h @@ -122,6 +122,10 @@ typedef enum #ifndef CONFIG_DISABLE_ES2015_FUNCTION_PARAMETER_INITIALIZER PARSER_ERR_DUPLICATED_ARGUMENT_NAMES, /**< duplicated argument names */ #endif /* !CONFIG_DISABLE_ES2015_FUNCTION_PARAMETER_INITIALIZER */ +#ifndef CONFIG_DISABLE_ES2015_FUNCTION_REST_PARAMETER + PARSER_ERR_FORMAL_PARAM_AFTER_REST_PARAMETER, /**< formal parameter after rest parameter */ + PARSER_ERR_REST_PARAMETER_DEFAULT_INITIALIZER, /**< rest parameter default initializer */ +#endif /* !CONFIG_DISABLE_ES2015_FUNCTION_REST_PARAMETER */ PARSER_ERR_OBJECT_PROPERTY_REDEFINED, /**< property of object literal redefined */ PARSER_ERR_NON_STRICT_ARG_DEFINITION /**< non-strict argument definition */ } parser_error_t; diff --git a/jerry-core/profiles/README.md b/jerry-core/profiles/README.md index 0965432dc..1f007caba 100644 --- a/jerry-core/profiles/README.md +++ b/jerry-core/profiles/README.md @@ -33,6 +33,7 @@ Alternatively, if you want to use a custom profile at CONFIG_DISABLE_ES2015_BUILTIN CONFIG_DISABLE_ES2015_CLASS CONFIG_DISABLE_ES2015_FUNCTION_PARAMETER_INITIALIZER +CONFIG_DISABLE_ES2015_FUNCTION_REST_PARAMETER CONFIG_DISABLE_ES2015_MAP_BUILTIN CONFIG_DISABLE_ES2015_OBJECT_INITIALIZER CONFIG_DISABLE_ES2015_PROMISE_BUILTIN @@ -93,6 +94,8 @@ In JerryScript all of the features are enabled by default, so an empty profile f Disable the [class](https://www.ecma-international.org/ecma-262/6.0/#sec-class-definitions) language element. * `CONFIG_DISABLE_ES2015_FUNCTION_PARAMETER_INITIALIZER`: Disable the [default value](http://www.ecma-international.org/ecma-262/6.0/#sec-function-definitions) for formal parameters. +* `CONFIG_DISABLE_ES2015_FUNCTION_REST_PARAMETER`: + Disable the [rest parameter](http://www.ecma-international.org/ecma-262/6.0/#sec-function-definitions). * `CONFIG_DISABLE_ES2015_MAP_BUILTIN`: Disable the [Map](http://www.ecma-international.org/ecma-262/6.0/#sec-keyed-collection) built-ins. * `CONFIG_DISABLE_ES2015_OBJECT_INITIALIZER`: @@ -107,5 +110,6 @@ In JerryScript all of the features are enabled by default, so an empty profile f Disable the [ArrayBuffer](http://www.ecma-international.org/ecma-262/6.0/#sec-arraybuffer-objects) and [TypedArray](http://www.ecma-international.org/ecma-262/6.0/#sec-typedarray-objects) built-ins. * `CONFIG_DISABLE_ES2015`: Disable all of the implemented [ECMAScript2015 features](http://www.ecma-international.org/ecma-262/6.0/). (equivalent to `CONFIG_DISABLE_ES2015_ARROW_FUNCTION`, `CONFIG_DISABLE_ES2015_BUILTIN`, `CONFIG_DISABLE_ES2015_CLASS`, - `CONFIG_DISABLE_ES2015_FUNCTION_PARAMETER_INITIALIZER`, `CONFIG_DISABLE_ES2015_MAP_BUILTIN`, `CONFIG_DISABLE_ES2015_OBJECT_INITIALIZER`, + `CONFIG_DISABLE_ES2015_FUNCTION_PARAMETER_INITIALIZER`, `CONFIG_DISABLE_ES2015_FUNCTION_REST_PARAMETER`, + `CONFIG_DISABLE_ES2015_MAP_BUILTIN`, `CONFIG_DISABLE_ES2015_OBJECT_INITIALIZER`, `CONFIG_DISABLE_ES2015_SYMBOL_BUILTIN`, `CONFIG_DISABLE_ES2015_PROMISE_BUILTIN`, `CONFIG_DISABLE_ES2015_TEMPLATE_STRINGS`, and `CONFIG_DISABLE_ES2015_TYPEDARRAY_BUILTIN`). diff --git a/jerry-core/vm/vm.c b/jerry-core/vm/vm.c index d2cdd546f..51c14c2b8 100644 --- a/jerry-core/vm/vm.c +++ b/jerry-core/vm/vm.c @@ -3428,6 +3428,10 @@ vm_execute (vm_frame_ctx_t *frame_ctx_p, /**< frame context */ frame_ctx_p->stack_top_p = frame_ctx_p->registers_p + register_end; +#ifndef CONFIG_DISABLE_ES2015_FUNCTION_REST_PARAMETER + uint32_t function_call_argument_count = arg_list_len; +#endif /* !CONFIG_DISABLE_ES2015_FUNCTION_REST_PARAMETER */ + if (arg_list_len > argument_end) { arg_list_len = argument_end; @@ -3450,6 +3454,19 @@ vm_execute (vm_frame_ctx_t *frame_ctx_p, /**< frame context */ } } +#ifndef CONFIG_DISABLE_ES2015_FUNCTION_REST_PARAMETER + if (bytecode_header_p->status_flags & CBC_CODE_FLAGS_REST_PARAMETER) + { + JERRY_ASSERT (function_call_argument_count >= arg_list_len); + ecma_value_t new_array = ecma_op_create_array_object (arg_p + arg_list_len, + function_call_argument_count - arg_list_len, + false); + JERRY_ASSERT (!ECMA_IS_VALUE_ERROR (new_array)); + frame_ctx_p->registers_p[argument_end] = new_array; + arg_list_len++; + } +#endif /* !CONFIG_DISABLE_ES2015_FUNCTION_REST_PARAMETER */ + JERRY_CONTEXT (status_flags) &= (uint32_t) ~ECMA_STATUS_DIRECT_EVAL; JERRY_CONTEXT (vm_top_context_p) = frame_ctx_p; diff --git a/tests/jerry/es2015/function-rest-parameter.js b/tests/jerry/es2015/function-rest-parameter.js new file mode 100644 index 000000000..e2187dcbc --- /dev/null +++ b/tests/jerry/es2015/function-rest-parameter.js @@ -0,0 +1,79 @@ +// 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. + +function CheckSyntaxError (str) +{ + try { + eval (str); + assert (false); + } catch (e) { + assert (e instanceof SyntaxError); + } + + /* force the pre-scanner */ + try { + eval ('switch (1) { default: ' + str + '}'); + assert (false); + } catch (e) { + assert (e instanceof SyntaxError); + } +} + +CheckSyntaxError ('function x (a, b, ...c, d) {}'); +CheckSyntaxError ('function x (... c = 5) {}'); +CheckSyntaxError ('function x (...) {}'); +CheckSyntaxError ('"use strict" function x (...arguments) {}'); + +rest_params = ['hello', true, 7, {}, [], function () {}]; + +function f (x, y, ...a) { + for (var i = 0; i < a.length; i++) { + assert (a[i] == rest_params[i]); + } + return (x + y) * a.length; +} + +assert (f (1, 2, rest_params[0], rest_params[1], rest_params[2]) === 9); +assert (f.length === 2); + +function g (...a) { + return a.reduce (function (accumulator, currentValue) { return accumulator + currentValue }); +} + +assert (g (1, 2, 3, 4) === 10); + +function h (...arguments) { + return arguments.length; +} + +assert (h (1, 2, 3, 4) === 4); + +function f2 (a = 1, b = 1, c = 1, ...d) { + assert (JSON.stringify (d) === '[]'); + return a + b + c; +} + +assert (f2 () === 3); +assert (f2 (2) === 4); +assert (f2 (2, 3) === 6); +assert (f2 (2, 3, 4) === 9); + +function g2 (a = 5, b = a + 1, ...c) { + return a + b + c.length; +} + +assert (g2 () === 11); +assert (g2 (1) === 3); +assert (g2 (1, 2) === 3); +assert (g2 (1, 2, 3) === 4); diff --git a/tests/unit-core/test-snapshot.c b/tests/unit-core/test-snapshot.c index 219d8181f..f17c2126b 100644 --- a/tests/unit-core/test-snapshot.c +++ b/tests/unit-core/test-snapshot.c @@ -223,7 +223,7 @@ main (void) /* Check the snapshot data. Unused bytes should be filled with zeroes */ const uint8_t expected_data[] = { - 0x4A, 0x52, 0x52, 0x59, 0x14, 0x00, 0x00, 0x00, + 0x4A, 0x52, 0x52, 0x59, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x03, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,