From: hongzhidao Date: Sun, 7 Apr 2019 06:26:13 +0000 (+0800) Subject: Added arrow function support. X-Git-Tag: 0.3.1~7 X-Git-Url: http://git.kaiwu.me/postgresql/log/contrib/postgres_fdw/static/gitweb.js?a=commitdiff_plain;h=21eab797ea5eae3d289b2af2a457a20f7e9573ad;p=njs.git Added arrow function support. This closes #106 issue on Github. In collaboration with Artem S. Povalyukhin. --- diff --git a/njs/njs_builtin.c b/njs/njs_builtin.c index 56edb726..224a229c 100644 --- a/njs/njs_builtin.c +++ b/njs/njs_builtin.c @@ -257,6 +257,12 @@ njs_builtin_objects_create(njs_vm_t *vm) return NXT_ERROR; } + ret = njs_object_hash_init(vm, &shared->arrow_instance_hash, + &njs_arrow_instance_init); + if (nxt_slow_path(ret != NXT_OK)) { + return NXT_ERROR; + } + ret = njs_object_hash_init(vm, &shared->arguments_object_instance_hash, &njs_arguments_object_instance_init); if (nxt_slow_path(ret != NXT_OK)) { diff --git a/njs/njs_extern.c b/njs/njs_extern.c index 66cf27d3..b92a3754 100644 --- a/njs/njs_extern.c +++ b/njs/njs_extern.c @@ -105,11 +105,12 @@ njs_vm_external_add(njs_vm_t *vm, nxt_lvlhsh_t *hash, njs_external_t *external, * nxt_mp_zalloc() does also: * nxt_lvlhsh_init(&function->object.hash); * function->object.__proto__ = NULL; + * function->ctor = 0; */ function->object.__proto__ = &vm->prototypes[NJS_CONSTRUCTOR_FUNCTION].object; - function->object.shared_hash = vm->shared->function_instance_hash; + function->object.shared_hash = vm->shared->arrow_instance_hash; function->object.type = NJS_FUNCTION; function->object.shared = 1; function->object.extensible = 1; diff --git a/njs/njs_function.c b/njs/njs_function.c index 03e65a06..0ed7c4aa 100644 --- a/njs/njs_function.c +++ b/njs/njs_function.c @@ -38,11 +38,17 @@ njs_function_alloc(njs_vm_t *vm, njs_function_lambda_t *lambda, * function->object.__proto__ = NULL; */ - function->ctor = 1; + function->ctor = !lambda->arrow; function->args_offset = 1; function->u.lambda = lambda; - function->object.shared_hash = vm->shared->function_instance_hash; + if (lambda->arrow || !function->ctor) { + function->object.shared_hash = vm->shared->arrow_instance_hash; + + } else { + function->object.shared_hash = vm->shared->function_instance_hash; + } + function->object.__proto__ = &vm->prototypes[NJS_PROTOTYPE_FUNCTION].object; function->object.type = NJS_FUNCTION; function->object.shared = shared; @@ -1195,6 +1201,23 @@ const njs_object_init_t njs_function_instance_init = { }; +const njs_object_prop_t njs_arrow_instance_properties[] = +{ + { + .type = NJS_PROPERTY_HANDLER, + .name = njs_string("length"), + .value = njs_prop_handler(njs_function_instance_length), + }, +}; + + +const njs_object_init_t njs_arrow_instance_init = { + nxt_string("Arrow instance"), + njs_arrow_instance_properties, + nxt_nitems(njs_arrow_instance_properties), +}; + + njs_ret_t njs_eval_function(njs_vm_t *vm, njs_value_t *args, nxt_uint_t nargs, njs_index_t unused) diff --git a/njs/njs_function.h b/njs/njs_function.h index 05b54531..e07578a9 100644 --- a/njs/njs_function.h +++ b/njs/njs_function.h @@ -30,6 +30,7 @@ struct njs_function_lambda_s { /* Function internal block closures levels. */ uint8_t block_closures; /* 4 bits */ + uint8_t arrow; /* 1 bit */ uint8_t rest_parameters; /* 1 bit */ /* Initial values of local scope. */ @@ -218,6 +219,7 @@ njs_function_previous_frame(njs_native_frame_t *frame) extern const njs_object_init_t njs_function_constructor_init; extern const njs_object_init_t njs_function_prototype_init; extern const njs_object_init_t njs_function_instance_init; +extern const njs_object_init_t njs_arrow_instance_init; extern const njs_object_init_t njs_arguments_object_instance_init; njs_ret_t njs_eval_function(njs_vm_t *vm, njs_value_t *args, nxt_uint_t nargs, diff --git a/njs/njs_lexer.c b/njs/njs_lexer.c index c28cddea..2ac69c41 100644 --- a/njs/njs_lexer.c +++ b/njs/njs_lexer.c @@ -278,6 +278,7 @@ static const njs_lexer_multi_t njs_greater_token[] = { static const njs_lexer_multi_t njs_assignment_token[] = { { '=', NJS_TOKEN_EQUAL, 1, njs_strict_equal_token }, + { '>', NJS_TOKEN_ARROW, 0, NULL }, }; diff --git a/njs/njs_lexer.h b/njs/njs_lexer.h index 95758747..e8f48a02 100644 --- a/njs/njs_lexer.h +++ b/njs/njs_lexer.h @@ -36,6 +36,7 @@ typedef enum { NJS_TOKEN_CONDITIONAL, NJS_TOKEN_ASSIGNMENT, + NJS_TOKEN_ARROW, NJS_TOKEN_ADDITION_ASSIGNMENT, NJS_TOKEN_SUBSTRACTION_ASSIGNMENT, NJS_TOKEN_MULTIPLICATION_ASSIGNMENT, diff --git a/njs/njs_object.c b/njs/njs_object.c index 8c2126f9..0b48996c 100644 --- a/njs/njs_object.c +++ b/njs/njs_object.c @@ -764,7 +764,12 @@ njs_method_private_copy(njs_vm_t *vm, njs_property_query_t *pq) return NXT_ERROR; } - function->object.shared_hash = vm->shared->function_instance_hash; + if (function->ctor) { + function->object.shared_hash = vm->shared->function_instance_hash; + + } else { + function->object.shared_hash = vm->shared->arrow_instance_hash; + } pq->lhq.replace = 0; pq->lhq.value = prop; diff --git a/njs/njs_parser.c b/njs/njs_parser.c index d53cfab7..8313e730 100644 --- a/njs/njs_parser.c +++ b/njs/njs_parser.c @@ -2103,6 +2103,172 @@ njs_parser_property_token(njs_vm_t *vm, njs_parser_t *parser) } +nxt_int_t +njs_parser_match_arrow_expression(njs_vm_t *vm, njs_parser_t *parser, + njs_token_t token) +{ + size_t offset; + nxt_bool_t rest_parameters; + + if (token != NJS_TOKEN_OPEN_PARENTHESIS && token != NJS_TOKEN_NAME) { + return NXT_DECLINED; + } + + offset = 0; + + if (token == NJS_TOKEN_NAME) { + goto arrow; + } + + token = njs_parser_peek_token(vm, parser, &offset); + if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) { + return NXT_DECLINED; + } + + rest_parameters = 0; + + while (token != NJS_TOKEN_CLOSE_PARENTHESIS) { + + if (rest_parameters) { + return NXT_DECLINED; + } + + if (nxt_slow_path(token == NJS_TOKEN_ELLIPSIS)) { + rest_parameters = 1; + + token = njs_parser_peek_token(vm, parser, &offset); + if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) { + return NXT_DECLINED; + } + } + + if (nxt_slow_path(token != NJS_TOKEN_NAME)) { + return NXT_DECLINED; + } + + token = njs_parser_peek_token(vm, parser, &offset); + if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) { + return token; + } + + if (token == NJS_TOKEN_COMMA) { + token = njs_parser_peek_token(vm, parser, &offset); + if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) { + return NXT_DECLINED; + } + } + } + +arrow: + + token = njs_parser_peek_token(vm, parser, &offset); + if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) { + return NXT_DECLINED; + } + + if (parser->lexer->prev_token == NJS_TOKEN_LINE_END) { + return NXT_DECLINED; + } + + if (nxt_slow_path(token != NJS_TOKEN_ARROW)) { + return NXT_DECLINED; + } + + return NXT_OK; +} + + +njs_token_t +njs_parser_arrow_expression(njs_vm_t *vm, njs_parser_t *parser, + njs_token_t token) +{ + njs_ret_t ret; + njs_index_t index; + njs_parser_node_t *node, *body, *parent; + njs_function_lambda_t *lambda; + + node = njs_parser_node_new(vm, parser, NJS_TOKEN_FUNCTION_EXPRESSION); + if (nxt_slow_path(node == NULL)) { + return NJS_TOKEN_ERROR; + } + + node->token_line = njs_parser_token_line(parser); + parser->node = node; + + lambda = nxt_mp_zalloc(vm->mem_pool, sizeof(njs_function_lambda_t)); + if (nxt_slow_path(lambda == NULL)) { + return NJS_TOKEN_ERROR; + } + + lambda->arrow = 1; + + node->u.value.data.u.lambda = lambda; + + ret = njs_parser_scope_begin(vm, parser, NJS_SCOPE_FUNCTION); + if (nxt_slow_path(ret != NXT_OK)) { + return NJS_TOKEN_ERROR; + } + + parser->scope->arrow_function = 1; + + index = NJS_SCOPE_ARGUMENTS; + + /* A "this" reservation. */ + index += sizeof(njs_value_t); + + if (token == NJS_TOKEN_OPEN_PARENTHESIS) { + token = njs_parser_lambda_arguments(vm, parser, lambda, index, token); + if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) { + return token; + } + + } else { + token = njs_parser_lambda_argument(vm, parser, index); + if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) { + return token; + } + + lambda->nargs = 1; + } + + if (parser->lexer->prev_token == NJS_TOKEN_LINE_END) { + return NJS_TOKEN_ILLEGAL; + } + + token = njs_parser_match(vm, parser, token, NJS_TOKEN_ARROW); + if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) { + return token; + } + + if (token == NJS_TOKEN_OPEN_BRACE) { + token = njs_parser_lambda_body(vm, parser, token); + if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) { + return token; + } + + } else { + parent = parser->node; + + token = njs_parser_assignment_expression(vm, parser, token); + if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) { + return token; + } + + body = njs_parser_return_set(vm, parser, parser->node); + if (nxt_slow_path(body == NULL)) { + return NJS_TOKEN_ERROR; + } + + parent->right = body; + parser->node = parent; + } + + njs_parser_scope_end(vm, parser); + + return token; +} + + nxt_bool_t njs_parser_has_side_effect(njs_parser_node_t *node) { diff --git a/njs/njs_parser.h b/njs/njs_parser.h index d2cfff5f..c45da406 100644 --- a/njs/njs_parser.h +++ b/njs/njs_parser.h @@ -32,6 +32,7 @@ struct njs_parser_scope_s { uint8_t nesting; /* 4 bits */ uint8_t argument_closures; uint8_t module; + uint8_t arrow_function; }; @@ -81,6 +82,10 @@ njs_token_t njs_parser_expression(njs_vm_t *vm, njs_parser_t *parser, njs_token_t njs_parser_assignment_expression(njs_vm_t *vm, njs_parser_t *parser, njs_token_t token); njs_token_t njs_parser_function_expression(njs_vm_t *vm, njs_parser_t *parser); +nxt_int_t njs_parser_match_arrow_expression(njs_vm_t *vm, njs_parser_t *parser, + njs_token_t token); +njs_token_t njs_parser_arrow_expression(njs_vm_t *vm, njs_parser_t *parser, + njs_token_t token); njs_token_t njs_parser_module_lambda(njs_vm_t *vm, njs_parser_t *parser); njs_token_t njs_parser_terminal(njs_vm_t *vm, njs_parser_t *parser, njs_token_t token); @@ -226,10 +231,12 @@ njs_parser_global_scope(njs_vm_t *vm) nxt_inline njs_parser_scope_t * -njs_function_scope(njs_parser_scope_t *scope) +njs_function_scope(njs_parser_scope_t *scope, nxt_bool_t any) { while (scope->type != NJS_SCOPE_GLOBAL) { - if (scope->type == NJS_SCOPE_FUNCTION) { + if (scope->type == NJS_SCOPE_FUNCTION + && (any || !scope->arrow_function)) + { return scope; } diff --git a/njs/njs_parser_terminal.c b/njs/njs_parser_terminal.c index 1e552f90..c20502cc 100644 --- a/njs/njs_parser_terminal.c +++ b/njs/njs_parser_terminal.c @@ -35,6 +35,11 @@ njs_parser_terminal(njs_vm_t *vm, njs_parser_t *parser, njs_token_t token) njs_ret_t ret; njs_parser_node_t *node; + ret = njs_parser_match_arrow_expression(vm, parser, token); + if (ret == NXT_OK) { + return njs_parser_arrow_expression(vm, parser, token); + } + if (token == NJS_TOKEN_OPEN_PARENTHESIS) { token = njs_parser_token(vm, parser); @@ -211,20 +216,10 @@ njs_parser_reference(njs_vm_t *vm, njs_parser_t *parser, njs_token_t token, case NJS_TOKEN_THIS: nxt_thread_log_debug("JS: this"); - scope = parser->scope; - - while (scope->type != NJS_SCOPE_GLOBAL) { - if (scope->type == NJS_SCOPE_FUNCTION) { - break; - } - - scope = scope->parent; - } + scope = njs_function_scope(parser->scope, 0); - if (scope->type != NJS_SCOPE_GLOBAL) { - if (njs_function_scope(scope) - == njs_function_scope(parser->scope)) - { + if (scope != NULL) { + if (scope == njs_function_scope(parser->scope, 1)) { node->index = NJS_INDEX_THIS; } else { @@ -232,14 +227,13 @@ njs_parser_reference(njs_vm_t *vm, njs_parser_t *parser, njs_token_t token, node->token_line = token_line; - ret = njs_variable_reference(vm, parser->scope, node, name, - hash, NJS_REFERENCE); + ret = njs_variable_reference(vm, scope, node, name, hash, + NJS_REFERENCE); if (nxt_slow_path(ret != NXT_OK)) { return NULL; } - var = njs_variable_add(vm, parser->scope, name, hash, - NJS_VARIABLE_VAR); + var = njs_variable_add(vm, scope, name, hash, NJS_VARIABLE_VAR); if (nxt_slow_path(var == NULL)) { return NULL; } @@ -361,7 +355,9 @@ njs_parser_reference(njs_vm_t *vm, njs_parser_t *parser, njs_token_t token, case NJS_TOKEN_ARGUMENTS: nxt_thread_log_debug("JS: arguments"); - if (parser->scope->type <= NJS_SCOPE_GLOBAL) { + scope = njs_function_scope(parser->scope, 0); + + if (scope == NULL) { njs_parser_syntax_error(vm, parser, "\"%V\" object " "in global scope", name); @@ -370,13 +366,13 @@ njs_parser_reference(njs_vm_t *vm, njs_parser_t *parser, njs_token_t token, node->token_line = token_line; - ret = njs_variable_reference(vm, parser->scope, node, name, hash, + ret = njs_variable_reference(vm, scope, node, name, hash, NJS_REFERENCE); if (nxt_slow_path(ret != NXT_OK)) { return NULL; } - var = njs_variable_add(vm, parser->scope, name, hash, NJS_VARIABLE_VAR); + var = njs_variable_add(vm, scope, name, hash, NJS_VARIABLE_VAR); if (nxt_slow_path(var == NULL)) { return NULL; } diff --git a/njs/njs_vm.h b/njs/njs_vm.h index 2fdc5a4d..52916ace 100644 --- a/njs/njs_vm.h +++ b/njs/njs_vm.h @@ -1119,6 +1119,7 @@ struct njs_vm_shared_s { nxt_lvlhsh_t array_instance_hash; nxt_lvlhsh_t string_instance_hash; nxt_lvlhsh_t function_instance_hash; + nxt_lvlhsh_t arrow_instance_hash; nxt_lvlhsh_t arguments_object_instance_hash; njs_object_t string_object; diff --git a/njs/test/njs_expect_test.exp b/njs/test/njs_expect_test.exp index 4f030de9..029e65e9 100644 --- a/njs/test/njs_expect_test.exp +++ b/njs/test/njs_expect_test.exp @@ -663,6 +663,8 @@ njs_test { njs_test { {"this\r\n" "this\r\nundefined"} + {"(() => this)()\r\n" + "(() => this)()\r\nundefined"} } "-t module" njs_test { diff --git a/njs/test/njs_unit_test.c b/njs/test/njs_unit_test.c index 6e1d9d86..a7aeb48a 100644 --- a/njs/test/njs_unit_test.c +++ b/njs/test/njs_unit_test.c @@ -6526,6 +6526,12 @@ static njs_unit_test_t njs_test[] = /* arguments object. */ + { nxt_string("arguments"), + nxt_string("SyntaxError: \"arguments\" object in global scope in 1") }, + + { nxt_string("{arguments}"), + nxt_string("SyntaxError: \"arguments\" object in global scope in 1") }, + { nxt_string("var arguments"), nxt_string("SyntaxError: Identifier \"arguments\" is forbidden in var declaration in 1") }, @@ -6625,6 +6631,138 @@ static njs_unit_test_t njs_test[] = "myFoo(1,2);" ), nxt_string("") }, + /* arrow functions. */ + + { nxt_string("()"), + nxt_string("SyntaxError: Unexpected token \")\" in 1") }, + + { nxt_string("() => "), + nxt_string("SyntaxError: Unexpected end of input in 1") }, + + { nxt_string("() => {"), + nxt_string("SyntaxError: Unexpected end of input in 1") }, + + { nxt_string("a\n => 1"), + nxt_string("SyntaxError: Unexpected token \"=>\" in 2") }, + + { nxt_string("new (()=>1)"), + nxt_string("TypeError: function is not a constructor")}, + + { nxt_string("(\n) => {}"), + nxt_string("[object Function]") }, + + { nxt_string("a => 1"), + nxt_string("[object Function]") }, + + { nxt_string("({f:()=>1, g:()=>2}).f()"), + nxt_string("1") }, + + { nxt_string("var f = f => {return 1;}; f()"), + nxt_string("1") }, + + { nxt_string("var f = (f) => {return 1;}; f()"), + nxt_string("1") }, + + { nxt_string("var f = (f, a, b) => {return 1;}; f()"), + nxt_string("1") }, + + { nxt_string("var f = () => {return 1;}; f()"), + nxt_string("1") }, + + { nxt_string("(f => {return 1;})()"), + nxt_string("1") }, + + { nxt_string("((f) => {return 1;})()"), + nxt_string("1") }, + + { nxt_string("(((f) => {return 1;}))()"), + nxt_string("1") }, + + { nxt_string("var f = f => 1; f()"), + nxt_string("1") }, + + { nxt_string("() => 1"), + nxt_string("[object Function]") }, + + { nxt_string("var f = ()=>{}; f()"), + nxt_string("undefined") }, + + { nxt_string("var f = ()=>({}); f()"), + nxt_string("[object Object]") }, + + { nxt_string("var materials = ['Hydrogen', 'Helium', 'Lithium', 'Beryllium'];" + "materials.map(material => { return material.length; });"), + nxt_string("8,6,7,9") }, + + { nxt_string("var materials = ['Hydrogen', 'Helium', 'Lithium', 'Beryllium'];" + "materials.map(material => material.length);"), + nxt_string("8,6,7,9") }, + + { nxt_string("var materials = ['Hydrogen', 'Helium', 'Lithium', 'Beryllium'];" + "materials.map(material => { material.length });"), + nxt_string(",,,") }, + + { nxt_string("function f(a, b, c) {a = 1; return () => { return arguments[1]; };};" + "f(1, 2, 3)('a', 'b');"), + nxt_string("2") }, + + { nxt_string("var f = (...c) => { return (function() { return arguments.length; }).bind(null, c); };" + "var x = f(1,'a',false, {}); x()"), + nxt_string("1") }, + + { nxt_string("var f = (...c) => { return (function() { return arguments.length; }).bind(null, c); };" + "var x = f(1,'a',false, {}); x(1,2,3)"), + nxt_string("4") }, + + { nxt_string("function Car(){ this.age = 0; (() => { this.age++;})();}" + "(new Car()).age"), + nxt_string("1") }, + + { nxt_string("function Car(){ this.age = 0; (function(){ this.age++;})();}" + "(new Car()).age"), + nxt_string("TypeError: cannot get property \"age\" of undefined") }, + + /* arrow functions + global this. */ + + { nxt_string("(() => this)()"), + nxt_string("[object Object]") }, + + { nxt_string("(() => this).call('abc')"), + nxt_string("[object Object]") }, + + { nxt_string("(() => this).apply('abc')"), + nxt_string("[object Object]") }, + + { nxt_string("(() => this).bind('abc')()"), + nxt_string("[object Object]") }, + + { nxt_string("(function() { return (() => this); })()()"), + nxt_string("undefined") }, + + { nxt_string("(function() { return (() => this); }).call('abc')()"), + nxt_string("abc") }, + + { nxt_string("(function() { return (() => this); }).bind('abc')()()"), + nxt_string("abc") }, + + { nxt_string("(function() { return (() => this); })" + ".call('abc').call('bca')"), + nxt_string("abc") }, + + { nxt_string("(function() { return (() => this); })" + ".call('abc').bind('bca')()"), + nxt_string("abc") }, + + { nxt_string("(function() { return function() { return () => this; }; })" + ".call('bca').call('abc')()"), + nxt_string("abc") }, + + { nxt_string("var f = () => 1; f.prototype"), + nxt_string("undefined") }, + + { nxt_string("var f = (a,b) => 0; f.length"), + nxt_string("2") }, + /* Scopes. */ { nxt_string("function f(x) { a = x } var a; f(5); a"), @@ -7568,6 +7706,9 @@ static njs_unit_test_t njs_test[] = { nxt_string("this.NaN + 1"), nxt_string("NaN") }, + { nxt_string("{this}"), + nxt_string("undefined") }, + { nxt_string("if (1) {new this}"), nxt_string("TypeError: object is not a function") }, @@ -7640,6 +7781,9 @@ static njs_unit_test_t njs_test[] = { nxt_string("Object.prototype"), nxt_string("[object Object]") }, + { nxt_string("Object.prototype.valueOf.prototype"), + nxt_string("undefined") }, + { nxt_string("Object.constructor === Function"), nxt_string("true") },