From 7c067bb408e306d6b57bc8654bd51c3f2470c3bb Mon Sep 17 00:00:00 2001 From: Alexander Pyshchev Date: Mon, 17 Dec 2018 21:13:02 +0200 Subject: [PATCH] Added the rest parameters support. This closes #21 issue on Github. --- njs/njs_function.c | 60 +++++++++++++++++++++++++++++++++++++--- njs/njs_function.h | 3 ++ njs/njs_lexer.c | 9 ++++++ njs/njs_parser.c | 13 +++++++++ njs/njs_parser.h | 1 + njs/test/njs_unit_test.c | 40 +++++++++++++++++++++++++++ 6 files changed, 122 insertions(+), 4 deletions(-) diff --git a/njs/njs_function.c b/njs/njs_function.c index 11cc42c5..83d3db1d 100644 --- a/njs/njs_function.c +++ b/njs/njs_function.c @@ -167,6 +167,42 @@ njs_function_arguments_object_init(njs_vm_t *vm, njs_native_frame_t *frame) } +njs_ret_t +njs_function_rest_parameters_init(njs_vm_t *vm, njs_native_frame_t *frame) +{ + uint32_t length; + nxt_uint_t nargs, n, i; + njs_array_t *array; + njs_value_t *rest_arguments; + + nargs = frame->nargs; + n = frame->function->u.lambda->nargs; + length = (nargs >= n) ? (nargs - n + 1) : 0; + + array = njs_array_alloc(vm, length, 0); + if (nxt_slow_path(array == NULL)) { + return NXT_ERROR; + } + + if (n <= nargs) { + i = 0; + do { + /* GC: retain. */ + array->start[i++] = frame->arguments[n++]; + } while (n <= nargs); + } + + rest_arguments = &frame->arguments[frame->function->u.lambda->nargs]; + + /* GC: retain. */ + rest_arguments->type = NJS_ARRAY; + rest_arguments->data.u.array = array; + rest_arguments->data.truth = 1; + + return NXT_OK; +} + + njs_ret_t njs_function_arguments_thrower(njs_vm_t *vm, njs_value_t *value, njs_value_t *setval, njs_value_t *retval) @@ -482,6 +518,13 @@ njs_function_call(njs_vm_t *vm, njs_index_t retval, size_t advance) } } + if (lambda->rest_parameters) { + ret = njs_function_rest_parameters_init(vm, &frame->native); + if (nxt_slow_path(ret != NXT_OK)) { + return NXT_ERROR; + } + } + vm->active_frame = frame; return NJS_APPLIED; @@ -600,8 +643,9 @@ static njs_ret_t njs_function_prototype_length(njs_vm_t *vm, njs_value_t *value, njs_value_t *setval, njs_value_t *retval) { - nxt_uint_t n; - njs_function_t *function; + nxt_uint_t n; + njs_function_t *function; + njs_function_lambda_t *lambda; function = value->data.u.function; @@ -613,10 +657,18 @@ njs_function_prototype_length(njs_vm_t *vm, njs_value_t *value, } } else { - n = function->u.lambda->nargs + 1; + lambda = function->u.lambda; + n = lambda->nargs + 1 - lambda->rest_parameters; + } + + if (n >= function->args_offset) { + n -= function->args_offset; + + } else { + n = 0; } - njs_value_number_set(retval, n - function->args_offset); + njs_value_number_set(retval, n); return NXT_OK; } diff --git a/njs/njs_function.h b/njs/njs_function.h index 6e80453d..c1396a0f 100644 --- a/njs/njs_function.h +++ b/njs/njs_function.h @@ -31,6 +31,7 @@ struct njs_function_lambda_s { uint8_t block_closures; /* 4 bits */ uint8_t arguments_object; /* 1 bit */ + uint8_t rest_parameters; /* 1 bit */ /* Initial values of local scope. */ njs_value_t *local_scope; @@ -151,6 +152,8 @@ njs_function_t *njs_function_value_copy(njs_vm_t *vm, njs_value_t *value); njs_native_frame_t *njs_function_frame_alloc(njs_vm_t *vm, size_t size); njs_ret_t njs_function_arguments_object_init(njs_vm_t *vm, njs_native_frame_t *frame); +njs_ret_t njs_function_rest_parameters_init(njs_vm_t *vm, + njs_native_frame_t *frame); njs_ret_t njs_function_arguments_thrower(njs_vm_t *vm, njs_value_t *value, njs_value_t *setval, njs_value_t *retval); njs_ret_t njs_function_prototype_create(njs_vm_t *vm, njs_value_t *value, diff --git a/njs/njs_lexer.c b/njs/njs_lexer.c index b2de9045..3ea7495d 100644 --- a/njs/njs_lexer.c +++ b/njs/njs_lexer.c @@ -326,6 +326,15 @@ njs_lexer_next_token(njs_lexer_t *lexer) case NJS_TOKEN_DOT: p = lexer->start; + if (p + 1 < lexer->end + && njs_tokens[p[0]] == NJS_TOKEN_DOT + && njs_tokens[p[1]] == NJS_TOKEN_DOT) + { + lexer->text.length = (p - lexer->text.start) + 2; + lexer->start += 2; + return NJS_TOKEN_ELLIPSIS; + } + if (p == lexer->end || njs_tokens[*p] != NJS_TOKEN_DIGIT) { lexer->text.length = p - lexer->text.start; return NJS_TOKEN_DOT; diff --git a/njs/njs_parser.c b/njs/njs_parser.c index cc0b59b8..68ee8b10 100644 --- a/njs/njs_parser.c +++ b/njs/njs_parser.c @@ -629,6 +629,19 @@ njs_parser_function_lambda(njs_vm_t *vm, njs_parser_t *parser, while (token != NJS_TOKEN_CLOSE_PARENTHESIS) { + if (nxt_slow_path(lambda->rest_parameters)) { + return NJS_TOKEN_ILLEGAL; + } + + if (nxt_slow_path(token == NJS_TOKEN_ELLIPSIS)) { + lambda->rest_parameters = 1; + + token = njs_parser_token(parser); + if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) { + return NJS_TOKEN_ILLEGAL; + } + } + if (nxt_slow_path(token != NJS_TOKEN_NAME)) { return NJS_TOKEN_ILLEGAL; } diff --git a/njs/njs_parser.h b/njs/njs_parser.h index 3aaf9bf1..7cda9b21 100644 --- a/njs/njs_parser.h +++ b/njs/njs_parser.h @@ -29,6 +29,7 @@ typedef enum { NJS_TOKEN_COMMA, NJS_TOKEN_DOT, + NJS_TOKEN_ELLIPSIS, NJS_TOKEN_SEMICOLON, NJS_TOKEN_COLON, diff --git a/njs/test/njs_unit_test.c b/njs/test/njs_unit_test.c index 12d47581..e2445b00 100644 --- a/njs/test/njs_unit_test.c +++ b/njs/test/njs_unit_test.c @@ -5310,9 +5310,19 @@ static njs_unit_test_t njs_test[] = { nxt_string("function f() { }; f.length = 1"), nxt_string("TypeError: Cannot assign to read-only property 'length' of function") }, + { nxt_string("function f(...rest) { }; f.length"), + nxt_string("0") }, + + { nxt_string("function f(...rest) { }; var binded = f.bind(this, [1,2]);" + "binded.length"), + nxt_string("0") }, + { nxt_string("function f(a,b) { }; f.length"), nxt_string("2") }, + { nxt_string("function f(a,...rest) { }; f.length"), + nxt_string("1") }, + { nxt_string("function f(a,b) { }; var ff = f.bind(f, 1); ff.length"), nxt_string("1") }, @@ -6063,6 +6073,36 @@ static njs_unit_test_t njs_test[] = "[concat('.',1,2,3), concat('+',1,2,3,4)]"), nxt_string("1.2.3,1+2+3+4") }, + /* rest parameters. */ + + { nxt_string("function myFoo(a,b,...other) { return other };" + "myFoo(1,2,3,4,5);" ), + nxt_string("3,4,5") }, + + { nxt_string("function myFoo(a,b,...other, c) { return other };"), + nxt_string("SyntaxError: Unexpected token \"c\" in 1") }, + + { nxt_string("function sum(a, b, c, ...other) { return a+b+c+other[2] };" + "sum(\"one \",2,\" three \",\"four \",\"five \",\"the long enough sixth argument \");"), + nxt_string("one 2 three the long enough sixth argument ") }, + + { nxt_string("function myFoo1(a,...other) { return other };" + "function myFoo2(a,b,...other) { return other };" + "myFoo1(1,2,3,4,5,myFoo2(1,2,3,4));"), + nxt_string("2,3,4,5,3,4") }, + + { nxt_string("function myFoo(...other) { return (other instanceof Array) };" + "myFoo(1);" ), + nxt_string("true") }, + + { nxt_string("function myFoo(a,...other) { return other.length };" + "myFoo(1,2,3,4,5);" ), + nxt_string("4") }, + + { nxt_string("function myFoo(a,b,...other) { return other };" + "myFoo(1,2);" ), + nxt_string("") }, + /* Scopes. */ { nxt_string("function f(x) { a = x } var a; f(5); a"), -- 2.47.3