diff --git a/src/njs_disassembler.c b/src/njs_disassembler.c index 72ea63b9a..b1e7aa50b 100644 --- a/src/njs_disassembler.c +++ b/src/njs_disassembler.c @@ -323,6 +323,21 @@ njs_disassemble(u_char *start, u_char *end, njs_int_t count, njs_arr_t *lines) continue; } + if (operation == NJS_VMCODE_OPTIONAL_CHAIN) { + test_jump = (njs_vmcode_test_jump_t *) p; + + njs_printf("%5uD | %05uz OPTIONAL CHAIN " + "%04Xz %04Xz %z\n", + line, p - start, + (size_t) test_jump->retval, + (size_t) test_jump->value, + (size_t) test_jump->offset); + + p += sizeof(njs_vmcode_test_jump_t); + + continue; + } + if (operation == NJS_VMCODE_FUNCTION_FRAME) { function = (njs_vmcode_function_frame_t *) p; diff --git a/src/njs_generator.c b/src/njs_generator.c index bb443017d..9de1a1450 100644 --- a/src/njs_generator.c +++ b/src/njs_generator.c @@ -103,6 +103,12 @@ typedef struct { } njs_generator_try_ctx_t; +typedef struct { + njs_jump_off_t jump_offset; + njs_index_t prop_index; +} njs_generator_log_assign_ctx_t; + + static u_char *njs_generate_reserve(njs_vm_t *vm, njs_generator_t *generator, size_t size); static njs_int_t njs_generate_code_map(njs_vm_t *vm, njs_generator_t *generator, @@ -238,6 +244,26 @@ static njs_int_t njs_generate_comma_expression_end(njs_vm_t *vm, static njs_int_t njs_generate_global_property_set(njs_vm_t *vm, njs_generator_t *generator, njs_parser_node_t *node_dst, njs_parser_node_t *node_src); +static njs_int_t njs_generate_property_lvalue(njs_vm_t *vm, + njs_generator_t *generator, njs_parser_node_t *node, + njs_generator_state_func_t after, void *ctx, size_t ctx_size); +static njs_int_t njs_generate_preserve_property_lvalue(njs_vm_t *vm, + njs_generator_t *generator, njs_parser_node_t *lvalue, + njs_parser_node_t *expr); +static njs_int_t njs_generate_read_property_assignment(njs_vm_t *vm, + njs_generator_t *generator, njs_parser_node_t *node, + njs_parser_node_t *expr, njs_index_t *prop_index); +static njs_index_t njs_generate_property_index(njs_vm_t *vm, + njs_generator_t *generator, njs_parser_node_t *node, + njs_parser_node_t *object, njs_parser_node_t *property); +static njs_vmcode_t njs_generate_property_get_opcode(njs_parser_node_t *node); +static njs_vmcode_t njs_generate_property_set_opcode(njs_parser_node_t *node); +static njs_int_t njs_generate_property_get(njs_vm_t *vm, + njs_generator_t *generator, njs_parser_node_t *node, njs_index_t value, + njs_index_t object, njs_index_t property); +static njs_int_t njs_generate_property_set(njs_vm_t *vm, + njs_generator_t *generator, njs_parser_node_t *node, njs_index_t value, + njs_index_t object, njs_index_t property); static njs_int_t njs_generate_assignment(njs_vm_t *vm, njs_generator_t *generator, njs_parser_node_t *node); static njs_int_t njs_generate_assignment_name(njs_vm_t *vm, @@ -254,6 +280,12 @@ static njs_int_t njs_generate_operation_assignment_prop(njs_vm_t *vm, njs_generator_t *generator, njs_parser_node_t *node); static njs_int_t njs_generate_operation_assignment_end(njs_vm_t *vm, njs_generator_t *generator, njs_parser_node_t *node); +static njs_int_t njs_generate_logical_assignment(njs_vm_t *vm, + njs_generator_t *generator, njs_parser_node_t *node); +static njs_int_t njs_generate_logical_assignment_prop(njs_vm_t *vm, + njs_generator_t *generator, njs_parser_node_t *node); +static njs_int_t njs_generate_logical_assignment_end(njs_vm_t *vm, + njs_generator_t *generator, njs_parser_node_t *node); static njs_int_t njs_generate_object(njs_vm_t *vm, njs_generator_t *generator, njs_parser_node_t *node); static njs_int_t njs_generate_property_accessor(njs_vm_t *vm, @@ -272,12 +304,21 @@ static njs_int_t njs_generate_template_literal(njs_vm_t *vm, njs_generator_t *generator, njs_parser_node_t *node); static njs_int_t njs_generate_template_literal_end(njs_vm_t *vm, njs_generator_t *generator, njs_parser_node_t *node); +static njs_int_t njs_generate_test_jump(njs_vm_t *vm, + njs_generator_t *generator, njs_parser_node_t *node, njs_vmcode_t opcode, + njs_index_t value, njs_index_t retval, njs_jump_off_t *jump_offset); static njs_int_t njs_generate_test_jump_expression(njs_vm_t *vm, njs_generator_t *generator, njs_parser_node_t *node); static njs_int_t njs_generate_test_jump_expression_after(njs_vm_t *vm, njs_generator_t *generator, njs_parser_node_t *node); static njs_int_t njs_generate_test_jump_expression_end(njs_vm_t *vm, njs_generator_t *generator, njs_parser_node_t *node); +static njs_int_t njs_generate_optional_chain(njs_vm_t *vm, + njs_generator_t *generator, njs_parser_node_t *node); +static njs_int_t njs_generate_optional_chain_after(njs_vm_t *vm, + njs_generator_t *generator, njs_parser_node_t *node); +static njs_int_t njs_generate_optional_chain_end(njs_vm_t *vm, + njs_generator_t *generator, njs_parser_node_t *node); static njs_int_t njs_generate_3addr_operation(njs_vm_t *vm, njs_generator_t *generator, njs_parser_node_t *node, njs_bool_t swap); static njs_int_t njs_generate_3addr_operation_name(njs_vm_t *vm, @@ -622,6 +663,11 @@ njs_generate(njs_vm_t *vm, njs_generator_t *generator, njs_parser_node_t *node) case NJS_TOKEN_REMAINDER_ASSIGNMENT: return njs_generate_operation_assignment(vm, generator, node); + case NJS_TOKEN_LOGICAL_OR_ASSIGNMENT: + case NJS_TOKEN_LOGICAL_AND_ASSIGNMENT: + case NJS_TOKEN_COALESCE_ASSIGNMENT: + return njs_generate_logical_assignment(vm, generator, node); + case NJS_TOKEN_BITWISE_OR: case NJS_TOKEN_BITWISE_XOR: case NJS_TOKEN_BITWISE_AND: @@ -661,6 +707,9 @@ njs_generate(njs_vm_t *vm, njs_generator_t *generator, njs_parser_node_t *node) case NJS_TOKEN_COALESCE: return njs_generate_test_jump_expression(vm, generator, node); + case NJS_TOKEN_OPTIONAL_CHAIN: + return njs_generate_optional_chain(vm, generator, node); + case NJS_TOKEN_DELETE: case NJS_TOKEN_VOID: case NJS_TOKEN_UNARY_PLUS: @@ -3039,6 +3088,214 @@ njs_generate_global_property_set(njs_vm_t *vm, njs_generator_t *generator, } +static njs_int_t +njs_generate_property_lvalue(njs_vm_t *vm, njs_generator_t *generator, + njs_parser_node_t *node, njs_generator_state_func_t after, void *ctx, + size_t ctx_size) +{ + njs_int_t ret; + njs_parser_node_t *lvalue; + + lvalue = node->left; + + /* Object. */ + + njs_generator_next(generator, njs_generate, lvalue->left); + + ret = njs_generator_after(vm, generator, + njs_queue_first(&generator->stack), node, + after, ctx, ctx_size); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } + + /* Property. */ + + return njs_generator_after(vm, generator, + njs_queue_first(&generator->stack), + lvalue->right, njs_generate, NULL, 0); +} + + +static njs_int_t +njs_generate_preserve_property_lvalue(njs_vm_t *vm, + njs_generator_t *generator, njs_parser_node_t *lvalue, + njs_parser_node_t *expr) +{ + njs_index_t index, src; + njs_parser_node_t *object, *property; + njs_vmcode_move_t *move; + + if (!njs_slow_path(njs_parser_has_side_effect(expr))) { + return NJS_OK; + } + + object = lvalue->left; + property = lvalue->right; + + /* + * Preserve object and property values stored in variables in a case + * if the variables can be changed by side effects in expression. + */ + if (object->token_type == NJS_TOKEN_NAME) { + src = object->index; + + index = njs_generate_node_temp_index_get(vm, generator, object); + if (njs_slow_path(index == NJS_INDEX_ERROR)) { + return NJS_ERROR; + } + + njs_generate_code_move(generator, move, index, src, object); + } + + if (property->token_type == NJS_TOKEN_NAME) { + src = property->index; + + index = njs_generate_node_temp_index_get(vm, generator, property); + if (njs_slow_path(index == NJS_INDEX_ERROR)) { + return NJS_ERROR; + } + + njs_generate_code_move(generator, move, index, src, property); + } + + return NJS_OK; +} + + +static njs_int_t +njs_generate_read_property_assignment(njs_vm_t *vm, + njs_generator_t *generator, njs_parser_node_t *node, + njs_parser_node_t *expr, njs_index_t *prop_index) +{ + njs_int_t ret; + njs_index_t index; + njs_parser_node_t *lvalue, *object, *property; + + lvalue = node->left; + object = lvalue->left; + property = lvalue->right; + + ret = njs_generate_preserve_property_lvalue(vm, generator, lvalue, expr); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } + + *prop_index = njs_generate_property_index(vm, generator, node, object, + property); + if (njs_slow_path(*prop_index == NJS_INDEX_ERROR)) { + return NJS_ERROR; + } + + index = njs_generate_node_temp_index_get(vm, generator, node); + if (njs_slow_path(index == NJS_INDEX_ERROR)) { + return NJS_ERROR; + } + + ret = njs_generate_property_get(vm, generator, property, index, + object->index, *prop_index); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } + + node->index = index; + + return NJS_OK; +} + + +static njs_index_t +njs_generate_property_index(njs_vm_t *vm, njs_generator_t *generator, + njs_parser_node_t *node, njs_parser_node_t *object, + njs_parser_node_t *property) +{ + njs_index_t prop_index; + njs_vmcode_3addr_t *to_property_key; + + prop_index = property->index; + + if (!njs_parser_is_primitive(property)) { + prop_index = njs_generate_node_temp_index_get(vm, generator, node); + if (njs_slow_path(prop_index == NJS_INDEX_ERROR)) { + return NJS_INDEX_ERROR; + } + + njs_generate_code(generator, njs_vmcode_3addr_t, to_property_key, + NJS_VMCODE_TO_PROPERTY_KEY_CHK, property); + + to_property_key->src2 = object->index; + to_property_key->src1 = property->index; + to_property_key->dst = prop_index; + } + + return prop_index; +} + + +static njs_vmcode_t +njs_generate_property_get_opcode(njs_parser_node_t *node) +{ + if (node->token_type == NJS_TOKEN_STRING + || (node->token_type == NJS_TOKEN_NUMBER + && node->u.value.atom_id != NJS_ATOM_STRING_unknown)) + { + return NJS_VMCODE_PROPERTY_ATOM_GET; + } + + return NJS_VMCODE_PROPERTY_GET; +} + + +static njs_vmcode_t +njs_generate_property_set_opcode(njs_parser_node_t *node) +{ + if (node->token_type == NJS_TOKEN_STRING + || (node->token_type == NJS_TOKEN_NUMBER + && node->u.value.atom_id != NJS_ATOM_STRING_unknown)) + { + return NJS_VMCODE_PROPERTY_ATOM_SET; + } + + return NJS_VMCODE_PROPERTY_SET; +} + + +static njs_int_t +njs_generate_property_get(njs_vm_t *vm, njs_generator_t *generator, + njs_parser_node_t *node, njs_index_t value, njs_index_t object, + njs_index_t property) +{ + njs_vmcode_prop_get_t *prop_get; + + njs_generate_code(generator, njs_vmcode_prop_get_t, prop_get, + njs_generate_property_get_opcode(node), node); + + prop_get->value = value; + prop_get->object = object; + prop_get->property = property; + + return NJS_OK; +} + + +static njs_int_t +njs_generate_property_set(njs_vm_t *vm, njs_generator_t *generator, + njs_parser_node_t *node, njs_index_t value, njs_index_t object, + njs_index_t property) +{ + njs_vmcode_prop_set_t *prop_set; + + njs_generate_code(generator, njs_vmcode_prop_set_t, prop_set, + njs_generate_property_set_opcode(node), node); + + prop_set->value = value; + prop_set->object = object; + prop_set->property = property; + + return NJS_OK; +} + + static njs_int_t njs_generate_assignment(njs_vm_t *vm, njs_generator_t *generator, njs_parser_node_t *node) @@ -3079,22 +3336,9 @@ njs_generate_assignment(njs_vm_t *vm, njs_generator_t *generator, /* lvalue->token == NJS_TOKEN_PROPERTY(_INIT) */ - /* Object. */ - - njs_generator_next(generator, njs_generate, lvalue->left); - - ret = njs_generator_after(vm, generator, - njs_queue_first(&generator->stack), node, - njs_generate_assignment_prop, NULL, 0); - if (njs_slow_path(ret != NJS_OK)) { - return ret; - } - - /* Property. */ - - return njs_generator_after(vm, generator, - njs_queue_first(&generator->stack), - lvalue->right, njs_generate, NULL, 0); + return njs_generate_property_lvalue(vm, generator, node, + njs_generate_assignment_prop, + NULL, 0); } @@ -3134,42 +3378,14 @@ static njs_int_t njs_generate_assignment_prop(njs_vm_t *vm, njs_generator_t *generator, njs_parser_node_t *node) { - njs_index_t index, src; - njs_parser_node_t *lvalue, *expr, *object, *property; - njs_vmcode_move_t *move; + njs_int_t ret; + njs_parser_node_t *expr; - lvalue = node->left; expr = node->right; - - object = lvalue->left; - property = lvalue->right; - - if (njs_slow_path(njs_parser_has_side_effect(expr))) { - /* - * Preserve object and property values stored in variables in a case - * if the variables can be changed by side effects in expression. - */ - if (object->token_type == NJS_TOKEN_NAME) { - src = object->index; - - index = njs_generate_node_temp_index_get(vm, generator, object); - if (njs_slow_path(index == NJS_INDEX_ERROR)) { - return NJS_ERROR; - } - - njs_generate_code_move(generator, move, index, src, object); - } - - if (property->token_type == NJS_TOKEN_NAME) { - src = property->index; - - index = njs_generate_node_temp_index_get(vm, generator, property); - if (njs_slow_path(index == NJS_INDEX_ERROR)) { - return NJS_ERROR; - } - - njs_generate_code_move(generator, move, index, src, property); - } + ret = njs_generate_preserve_property_lvalue(vm, generator, node->left, + expr); + if (njs_slow_path(ret != NJS_OK)) { + return ret; } njs_generator_next(generator, njs_generate, expr); @@ -3243,18 +3459,10 @@ njs_generate_assignment_end(njs_vm_t *vm, njs_generator_t *generator, break; default: - if (property->token_type == NJS_TOKEN_STRING - || (property->token_type == NJS_TOKEN_NUMBER - && property->u.value.atom_id != NJS_ATOM_STRING_unknown)) - { - opcode = NJS_VMCODE_PROPERTY_ATOM_SET; + opcode = njs_generate_property_set_opcode(property); - } else { - opcode = NJS_VMCODE_PROPERTY_SET; - } - - njs_generate_code(generator, njs_vmcode_prop_set_t, prop_set, - opcode, expr); + njs_generate_code(generator, njs_vmcode_prop_set_t, prop_set, opcode, + expr); } prop_set->value = expr->index; @@ -3337,22 +3545,9 @@ njs_generate_operation_assignment(njs_vm_t *vm, njs_generator_t *generator, /* lvalue->token == NJS_TOKEN_PROPERTY */ - /* Object. */ - - njs_generator_next(generator, njs_generate, lvalue->left); - - ret = njs_generator_after(vm, generator, - njs_queue_first(&generator->stack), node, - njs_generate_operation_assignment_prop, NULL, 0); - if (njs_slow_path(ret != NJS_OK)) { - return ret; - } - - /* Property. */ - - return njs_generator_after(vm, generator, - njs_queue_first(&generator->stack), - lvalue->right, njs_generate, NULL, 0); + return njs_generate_property_lvalue(vm, generator, node, + njs_generate_operation_assignment_prop, + NULL, 0); } @@ -3400,132 +3595,208 @@ static njs_int_t njs_generate_operation_assignment_prop(njs_vm_t *vm, njs_generator_t *generator, njs_parser_node_t *node) { - njs_index_t index, src, prop_index; - njs_vmcode_t opcode; - njs_parser_node_t *lvalue, *object, *property; - njs_vmcode_move_t *move; - njs_vmcode_3addr_t *to_property_key; - njs_vmcode_prop_get_t *prop_get; - - lvalue = node->left; - object = lvalue->left; - property = lvalue->right; + njs_int_t ret; + njs_index_t prop_index; - if (njs_slow_path(njs_parser_has_side_effect(node->right))) { - /* - * Preserve object and property values stored in variables in a case - * if the variables can be changed by side effects in expression. - */ - if (object->token_type == NJS_TOKEN_NAME) { - src = object->index; + ret = njs_generate_read_property_assignment(vm, generator, node, + node->right, &prop_index); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } - index = njs_generate_node_temp_index_get(vm, generator, object); - if (njs_slow_path(index == NJS_INDEX_ERROR)) { - return NJS_ERROR; - } + njs_generator_next(generator, njs_generate, node->right); - njs_generate_code_move(generator, move, index, src, object); - } + return njs_generator_after(vm, generator, + njs_queue_first(&generator->stack), node, + njs_generate_operation_assignment_end, + &prop_index, sizeof(njs_index_t)); +} - if (property->token_type == NJS_TOKEN_NAME) { - src = property->index; - index = njs_generate_node_temp_index_get(vm, generator, property); - if (njs_slow_path(index == NJS_INDEX_ERROR)) { - return NJS_ERROR; - } +static njs_int_t +njs_generate_operation_assignment_end(njs_vm_t *vm, njs_generator_t *generator, + njs_parser_node_t *node) +{ + njs_int_t ret; + njs_index_t prop_index; + njs_parser_node_t *lvalue, *expr; + njs_vmcode_3addr_t *code; - njs_generate_code_move(generator, move, index, src, property); - } - } + lvalue = node->left; + expr = node->right; - prop_index = property->index; + prop_index = *((njs_index_t *) generator->context); - if (!njs_parser_is_primitive(property)) { - prop_index = njs_generate_node_temp_index_get(vm, generator, node); - if (njs_slow_path(prop_index == NJS_INDEX_ERROR)) { - return NJS_ERROR; - } + njs_generate_code(generator, njs_vmcode_3addr_t, code, + node->u.operation, expr); + code->dst = node->index; + code->src1 = node->index; + code->src2 = expr->index; - njs_generate_code(generator, njs_vmcode_3addr_t, to_property_key, - NJS_VMCODE_TO_PROPERTY_KEY_CHK, property); + ret = njs_generate_property_set(vm, generator, lvalue->right, node->index, + lvalue->left->index, prop_index); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } - to_property_key->src2 = object->index; - to_property_key->src1 = property->index; - to_property_key->dst = prop_index; + ret = njs_generate_children_indexes_release(vm, generator, lvalue); + if (njs_slow_path(ret != NJS_OK)) { + return ret; } - index = njs_generate_node_temp_index_get(vm, generator, node); - if (njs_slow_path(index == NJS_INDEX_ERROR)) { - return NJS_ERROR; + return njs_generate_node_index_release_pop(vm, generator, expr); +} + + +static njs_int_t +njs_generate_logical_assignment(njs_vm_t *vm, njs_generator_t *generator, + njs_parser_node_t *node) +{ + njs_int_t ret; + njs_variable_t *var; + njs_parser_node_t *lvalue; + njs_vmcode_variable_t *var_code; + njs_generator_log_assign_ctx_t ctx; + + lvalue = node->left; + + if (lvalue->token_type == NJS_TOKEN_NAME) { + + ret = njs_generate_variable(vm, generator, lvalue, NJS_REFERENCE, + &var); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } + + ret = njs_generate_test_jump(vm, generator, node, node->u.operation, + lvalue->index, lvalue->index, + &ctx.jump_offset); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } + + if (var != NULL && var->type == NJS_VARIABLE_CONST) { + njs_generate_code(generator, njs_vmcode_variable_t, var_code, + NJS_VMCODE_ASSIGNMENT_ERROR, node); + var_code->dst = var->index; + + njs_code_set_jump_offset(generator, njs_vmcode_test_jump_t, + ctx.jump_offset); + + node->index = lvalue->index; + + return njs_generator_stack_pop(vm, generator, NULL); + } + + ctx.prop_index = NJS_INDEX_NONE; + + njs_generator_next(generator, njs_generate, node->right); + + return njs_generator_after(vm, generator, + njs_queue_first(&generator->stack), node, + njs_generate_logical_assignment_end, + &ctx, + sizeof(njs_generator_log_assign_ctx_t)); } - if (property->token_type == NJS_TOKEN_STRING - || (property->token_type == NJS_TOKEN_NUMBER - && property->u.value.atom_id != NJS_ATOM_STRING_unknown)) - { - opcode = NJS_VMCODE_PROPERTY_ATOM_GET; + /* lvalue->token == NJS_TOKEN_PROPERTY */ - } else { - opcode = NJS_VMCODE_PROPERTY_GET; + /* Object. */ + + njs_generator_next(generator, njs_generate, lvalue->left); + + ret = njs_generator_after(vm, generator, + njs_queue_first(&generator->stack), node, + njs_generate_logical_assignment_prop, NULL, 0); + if (njs_slow_path(ret != NJS_OK)) { + return ret; } - njs_generate_code(generator, njs_vmcode_prop_get_t, prop_get, - opcode, property); + /* Property. */ - prop_get->value = index; - prop_get->object = object->index; - prop_get->property = prop_index; + return njs_generator_after(vm, generator, + njs_queue_first(&generator->stack), + lvalue->right, njs_generate, NULL, 0); +} + + +static njs_int_t +njs_generate_logical_assignment_prop(njs_vm_t *vm, njs_generator_t *generator, + njs_parser_node_t *node) +{ + njs_int_t ret; + njs_generator_log_assign_ctx_t ctx; + + ret = njs_generate_read_property_assignment(vm, generator, node, + node->right, &ctx.prop_index); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } + + ret = njs_generate_test_jump(vm, generator, node, node->u.operation, + node->index, node->index, + &ctx.jump_offset); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } njs_generator_next(generator, njs_generate, node->right); return njs_generator_after(vm, generator, njs_queue_first(&generator->stack), node, - njs_generate_operation_assignment_end, - &prop_index, sizeof(njs_index_t)); + njs_generate_logical_assignment_end, + &ctx, + sizeof(njs_generator_log_assign_ctx_t)); } static njs_int_t -njs_generate_operation_assignment_end(njs_vm_t *vm, njs_generator_t *generator, - njs_parser_node_t *node) +njs_generate_logical_assignment_end(njs_vm_t *vm, + njs_generator_t *generator, njs_parser_node_t *node) { - njs_int_t ret; - njs_index_t prop_index; - njs_vmcode_t opcode; - njs_parser_node_t *lvalue, *expr, *prop; - njs_vmcode_3addr_t *code; - njs_vmcode_prop_set_t *prop_set; + njs_int_t ret; + njs_index_t index; + njs_parser_node_t *lvalue, *expr; + njs_vmcode_move_t *move; + njs_generator_log_assign_ctx_t *ctx; lvalue = node->left; expr = node->right; - prop_index = *((njs_index_t *) generator->context); + ctx = generator->context; - njs_generate_code(generator, njs_vmcode_3addr_t, code, - node->u.operation, expr); - code->dst = node->index; - code->src1 = node->index; - code->src2 = expr->index; + index = (lvalue->token_type == NJS_TOKEN_NAME) ? lvalue->index + : node->index; - prop = lvalue->right; + if (index != expr->index) { + njs_generate_code_move(generator, move, index, + expr->index, node); + } - if (prop->token_type == NJS_TOKEN_STRING - || (prop->token_type == NJS_TOKEN_NUMBER - && prop->u.value.atom_id != NJS_ATOM_STRING_unknown)) - { - opcode = NJS_VMCODE_PROPERTY_ATOM_SET; + njs_code_set_jump_offset(generator, njs_vmcode_test_jump_t, + ctx->jump_offset); - } else { - opcode = NJS_VMCODE_PROPERTY_SET; - } + if (ctx->prop_index == NJS_INDEX_NONE) { + ret = njs_generate_global_property_set(vm, generator, lvalue, expr); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } - njs_generate_code(generator, njs_vmcode_prop_set_t, prop_set, - opcode, expr); + node->index = lvalue->index; - prop_set->value = node->index; - prop_set->object = lvalue->left->index; - prop_set->property = prop_index; + ret = njs_generate_node_index_release(vm, generator, expr); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } + + return njs_generator_stack_pop(vm, generator, generator->context); + } + + ret = njs_generate_property_set(vm, generator, lvalue->right, node->index, + lvalue->left->index, ctx->prop_index); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } ret = njs_generate_children_indexes_release(vm, generator, lvalue); if (njs_slow_path(ret != NJS_OK)) { @@ -3765,6 +4036,24 @@ njs_generate_template_literal_end(njs_vm_t *vm, njs_generator_t *generator, } +static njs_int_t +njs_generate_test_jump(njs_vm_t *vm, njs_generator_t *generator, + njs_parser_node_t *node, njs_vmcode_t opcode, njs_index_t value, + njs_index_t retval, njs_jump_off_t *jump_offset) +{ + njs_vmcode_test_jump_t *test_jump; + + njs_generate_code(generator, njs_vmcode_test_jump_t, test_jump, opcode, + node); + + *jump_offset = njs_code_offset(generator, test_jump); + test_jump->value = value; + test_jump->retval = retval; + + return NJS_OK; +} + + static njs_int_t njs_generate_test_jump_expression(njs_vm_t *vm, njs_generator_t *generator, njs_parser_node_t *node) @@ -3782,20 +4071,20 @@ static njs_int_t njs_generate_test_jump_expression_after(njs_vm_t *vm, njs_generator_t *generator, njs_parser_node_t *node) { - njs_jump_off_t jump_offset; - njs_vmcode_test_jump_t *test_jump; - - njs_generate_code(generator, njs_vmcode_test_jump_t, test_jump, - node->u.operation, node); - jump_offset = njs_code_offset(generator, test_jump); - test_jump->value = node->left->index; + njs_int_t ret; + njs_jump_off_t jump_offset; node->index = njs_generate_node_temp_index_get(vm, generator, node); if (njs_slow_path(node->index == NJS_INDEX_ERROR)) { return node->index; } - test_jump->retval = node->index; + ret = njs_generate_test_jump(vm, generator, node, node->u.operation, + node->left->index, node->index, + &jump_offset); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } njs_generator_next(generator, njs_generate, node->right); @@ -3836,6 +4125,135 @@ njs_generate_test_jump_expression_end(njs_vm_t *vm, njs_generator_t *generator, } +static njs_parser_node_t * +njs_generate_optional_method_call(njs_parser_node_t *node) +{ + if (node != NULL + && node->token_type == NJS_TOKEN_METHOD_CALL + && node->u.object != NULL) + { + return node; + } + + return NULL; +} + + +static njs_int_t +njs_generate_optional_chain(njs_vm_t *vm, njs_generator_t *generator, + njs_parser_node_t *node) +{ + njs_jump_off_t jump_offset; + njs_parser_node_t *call, *preserve; + + jump_offset = 0; + + call = njs_generate_optional_method_call(node->right); + if (call != NULL) { + preserve = call->u.object->left; + + if (preserve->token_type == NJS_TOKEN_PROPERTY) { + preserve->hoist = 1; + } + } + + njs_generator_next(generator, njs_generate, node->left); + + return njs_generator_after(vm, generator, + njs_queue_first(&generator->stack), + node, njs_generate_optional_chain_after, + &jump_offset, sizeof(njs_jump_off_t)); +} + + +static njs_int_t +njs_generate_optional_chain_after(njs_vm_t *vm, njs_generator_t *generator, + njs_parser_node_t *node) +{ + njs_jump_off_t *jump_offset; + njs_parser_node_t *call, *prop; + njs_vmcode_test_jump_t *test_jump; + + jump_offset = generator->context; + + njs_generate_code(generator, njs_vmcode_test_jump_t, test_jump, + NJS_VMCODE_OPTIONAL_CHAIN, node); + *jump_offset = njs_code_offset(generator, test_jump); + test_jump->value = node->left->index; + + node->index = njs_generate_node_temp_index_get(vm, generator, node); + if (njs_slow_path(node->index == NJS_INDEX_ERROR)) { + return node->index; + } + + test_jump->retval = node->index; + + call = njs_generate_optional_method_call(node->right); + if (call != NULL) { + prop = call->left; + prop->left->index = call->u.object->left->index; + prop->right->index = call->u.object->right->index; + + } else if (node->u.object != NULL) { + node->u.object->index = node->left->index; + } + + njs_generator_next(generator, njs_generate, node->right); + + return njs_generator_after(vm, generator, + njs_queue_first(&generator->stack), + node, njs_generate_optional_chain_end, + jump_offset, sizeof(njs_jump_off_t)); +} + + +static njs_int_t +njs_generate_optional_chain_end(njs_vm_t *vm, njs_generator_t *generator, + njs_parser_node_t *node) +{ + njs_int_t ret; + njs_jump_off_t *jump_offset; + njs_vmcode_move_t *move; + njs_parser_node_t *call, *preserve; + + jump_offset = generator->context; + + if (node->index != node->right->index) { + njs_generate_code_move(generator, move, node->index, + node->right->index, node); + } + + njs_code_set_jump_offset(generator, njs_vmcode_test_jump_t, + *jump_offset); + + call = njs_generate_optional_method_call(node->right); + if (call != NULL) { + preserve = call->u.object->left; + + if (preserve->token_type != NJS_TOKEN_PROPERTY) { + preserve = NULL; + } + } else { + preserve = NULL; + } + + if (preserve != NULL) { + ret = njs_generate_index_release(vm, generator, + preserve->index); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } + } + + ret = njs_generate_children_indexes_release(vm, generator, node); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } + + return njs_generator_stack_pop(vm, generator, generator->context); +} + + static njs_int_t njs_generate_3addr_operation(njs_vm_t *vm, njs_generator_t *generator, njs_parser_node_t *node, njs_bool_t swap) @@ -4091,23 +4509,9 @@ njs_generate_inc_dec_operation(njs_vm_t *vm, njs_generator_t *generator, /* lvalue->token == NJS_TOKEN_PROPERTY */ - /* Object. */ - - njs_generator_next(generator, njs_generate, lvalue->left); - - ret = njs_generator_after(vm, generator, - njs_queue_first(&generator->stack), node, - njs_generate_inc_dec_operation_prop, - &post, sizeof(njs_bool_t)); - if (njs_slow_path(ret != NJS_OK)) { - return ret; - } - - /* Property. */ - - return njs_generator_after(vm, generator, - njs_queue_first(&generator->stack), - lvalue->right, njs_generate, NULL, 0); + return njs_generate_property_lvalue(vm, generator, node, + njs_generate_inc_dec_operation_prop, + &post, sizeof(njs_bool_t)); } @@ -4679,6 +5083,10 @@ njs_generate_method_call(njs_vm_t *vm, njs_generator_t *generator, njs_int_t ret; njs_parser_node_t *prop; + if (njs_generate_optional_method_call(node) != NULL) { + return njs_generate_method_call_arguments(vm, generator, node); + } + prop = node->left; /* Object. */ @@ -5629,6 +6037,11 @@ njs_generate_node_index_release(njs_vm_t *vm, njs_generator_t *generator, njs_parser_node_t *node) { if (node != NULL && node->temporary) { + if (node->hoist) { + node->hoist = 0; + return NJS_OK; + } + return njs_generate_index_release(vm, generator, node->index); } diff --git a/src/njs_lexer.c b/src/njs_lexer.c index 7827aa0ca..b5c2ce121 100644 --- a/src/njs_lexer.c +++ b/src/njs_lexer.c @@ -205,8 +205,13 @@ static const njs_lexer_multi_t njs_remainder_token[] = { }; +static const njs_lexer_multi_t njs_logical_and_assignment_token[] = { + { '=', NJS_TOKEN_LOGICAL_AND_ASSIGNMENT, 0, NULL }, +}; + + static const njs_lexer_multi_t njs_bitwise_and_token[] = { - { '&', NJS_TOKEN_LOGICAL_AND, 0, NULL }, + { '&', NJS_TOKEN_LOGICAL_AND, 1, njs_logical_and_assignment_token }, { '=', NJS_TOKEN_BITWISE_AND_ASSIGNMENT, 0, NULL }, }; @@ -216,8 +221,13 @@ static const njs_lexer_multi_t njs_bitwise_xor_token[] = { }; +static const njs_lexer_multi_t njs_logical_or_assignment_token[] = { + { '=', NJS_TOKEN_LOGICAL_OR_ASSIGNMENT, 0, NULL }, +}; + + static const njs_lexer_multi_t njs_bitwise_or_token[] = { - { '|', NJS_TOKEN_LOGICAL_OR, 0, NULL }, + { '|', NJS_TOKEN_LOGICAL_OR, 1, njs_logical_or_assignment_token }, { '=', NJS_TOKEN_BITWISE_OR_ASSIGNMENT, 0, NULL }, }; @@ -266,8 +276,13 @@ static const njs_lexer_multi_t njs_greater_token[] = { }; +static const njs_lexer_multi_t njs_coalesce_assignment_token[] = { + { '=', NJS_TOKEN_COALESCE_ASSIGNMENT, 0, NULL }, +}; + + static const njs_lexer_multi_t njs_conditional_token[] = { - { '?', NJS_TOKEN_COALESCE, 0, NULL }, + { '?', NJS_TOKEN_COALESCE, 1, njs_coalesce_assignment_token }, }; diff --git a/src/njs_lexer.h b/src/njs_lexer.h index 3728e2536..9dd210635 100644 --- a/src/njs_lexer.h +++ b/src/njs_lexer.h @@ -50,6 +50,9 @@ typedef enum { NJS_TOKEN_BITWISE_OR_ASSIGNMENT, NJS_TOKEN_BITWISE_XOR_ASSIGNMENT, NJS_TOKEN_BITWISE_AND_ASSIGNMENT, + NJS_TOKEN_LOGICAL_OR_ASSIGNMENT, + NJS_TOKEN_LOGICAL_AND_ASSIGNMENT, + NJS_TOKEN_COALESCE_ASSIGNMENT, NJS_TOKEN_INCREMENT, NJS_TOKEN_DECREMENT, @@ -135,6 +138,8 @@ typedef enum { NJS_TOKEN_PROPERTY_SETTER, NJS_TOKEN_PROTO_INIT, + NJS_TOKEN_OPTIONAL_CHAIN, + NJS_TOKEN_ARRAY, NJS_TOKEN_GRAVE, diff --git a/src/njs_parser.c b/src/njs_parser.c index e2d6f388d..669f619bc 100644 --- a/src/njs_parser.c +++ b/src/njs_parser.c @@ -91,10 +91,20 @@ static njs_int_t njs_parser_member_expression_new_args(njs_parser_t *parser, static njs_parser_node_t *njs_parser_create_call(njs_parser_t *parser, njs_parser_node_t *node, uint8_t ctor); +static njs_int_t njs_parser_call_arguments(njs_parser_t *parser, + njs_lexer_token_t *token, njs_queue_link_t *current, + njs_parser_node_t *func, njs_parser_state_func_t after); + +static njs_int_t njs_parser_right_link_pop(njs_parser_t *parser); +static njs_parser_node_t *njs_parser_optional_chain_method_call( + njs_parser_t *parser, njs_parser_node_t *node, uint32_t token_line); static njs_int_t njs_parser_call_expression(njs_parser_t *parser, njs_lexer_token_t *token, njs_queue_link_t *current); static njs_int_t njs_parser_call_expression_args(njs_parser_t *parser, njs_lexer_token_t *token, njs_queue_link_t *current); +static njs_int_t njs_parser_call_or_property_after(njs_parser_t *parser, + njs_lexer_token_t *token, njs_queue_link_t *current, + njs_parser_state_func_t after); static njs_int_t njs_parser_call_expression_after(njs_parser_t *parser, njs_lexer_token_t *token, njs_queue_link_t *current); static njs_int_t njs_parser_arguments(njs_parser_t *parser, @@ -2444,10 +2454,7 @@ njs_parser_member_expression_bracket(njs_parser_t *parser, njs_lexer_consume_token(parser->lexer, 1); - parser->target->right = parser->node; - parser->node = parser->target; - - return njs_parser_stack_pop(parser); + return njs_parser_right_link_pop(parser); } @@ -2615,14 +2622,8 @@ njs_parser_member_expression_new_after(njs_parser_t *parser, return NJS_ERROR; } - func->token_line = token->line; - parser->node = func; - - njs_lexer_consume_token(parser->lexer, 1); - njs_parser_next(parser, njs_parser_arguments); - - return njs_parser_after(parser, current, func, 1, - njs_parser_member_expression_new_args); + return njs_parser_call_arguments(parser, token, current, func, + njs_parser_member_expression_new_args); } @@ -2681,6 +2682,86 @@ njs_parser_create_call(njs_parser_t *parser, njs_parser_node_t *node, } +static njs_int_t +njs_parser_call_arguments(njs_parser_t *parser, njs_lexer_token_t *token, + njs_queue_link_t *current, njs_parser_node_t *func, + njs_parser_state_func_t after) +{ + func->token_line = token->line; + parser->node = func; + + njs_lexer_consume_token(parser->lexer, 1); + njs_parser_next(parser, njs_parser_arguments); + + return njs_parser_after(parser, current, func, 1, after); +} + + +static njs_int_t +njs_parser_right_link_pop(njs_parser_t *parser) +{ + parser->target->right = parser->node; + parser->node = parser->target; + + return njs_parser_stack_pop(parser); +} + + +static njs_parser_node_t * +njs_parser_optional_chain_property(njs_parser_node_t *node) +{ + if (node == NULL) { + return NULL; + } + + if (node->token_type == NJS_TOKEN_PROPERTY) { + return node; + } + + if (node->token_type == NJS_TOKEN_OPTIONAL_CHAIN + && node->right != NULL + && node->right->token_type == NJS_TOKEN_PROPERTY) + { + return node->right; + } + + return NULL; +} + + +static njs_parser_node_t * +njs_parser_optional_chain_method_call(njs_parser_t *parser, + njs_parser_node_t *node, uint32_t token_line) +{ + njs_parser_node_t *func, *prop, *src; + + src = njs_parser_optional_chain_property(node); + if (src == NULL) { + return NULL; + } + + prop = njs_parser_node_new(parser, NJS_TOKEN_PROPERTY); + if (prop == NULL) { + return NULL; + } + + prop->u.operation = src->u.operation; + prop->token_line = token_line; + prop->left = src->left; + prop->right = src->right; + + func = njs_parser_node_new(parser, NJS_TOKEN_METHOD_CALL); + if (func == NULL) { + return NULL; + } + + func->left = prop; + func->u.object = src; + + return func; +} + + static njs_int_t njs_parser_call_expression(njs_parser_t *parser, njs_lexer_token_t *token, njs_queue_link_t *current) @@ -2737,31 +2818,19 @@ njs_parser_call_expression_args(njs_parser_t *parser, njs_lexer_token_t *token, return NJS_ERROR; } - func->token_line = token->line; - parser->node = func; - - njs_lexer_consume_token(parser->lexer, 1); - njs_parser_next(parser, njs_parser_arguments); - - return njs_parser_after(parser, current, func, 1, - njs_parser_left_hand_side_expression_node); + return njs_parser_call_arguments(parser, token, current, func, + njs_parser_left_hand_side_expression_node); } static njs_int_t -njs_parser_call_expression_after(njs_parser_t *parser, - njs_lexer_token_t *token, njs_queue_link_t *current) +njs_parser_call_or_property_after(njs_parser_t *parser, + njs_lexer_token_t *token, njs_queue_link_t *current, + njs_parser_state_func_t after) { njs_int_t ret; njs_parser_node_t *func; - /* - * Arguments - * [ Expression ] - * . IdentifierName - * TemplateLiteral - */ - switch (token->type) { case NJS_TOKEN_OPEN_PARENTHESIS: func = njs_parser_create_call(parser, parser->node, 0); @@ -2769,14 +2838,8 @@ njs_parser_call_expression_after(njs_parser_t *parser, return NJS_ERROR; } - func->token_line = token->line; - parser->node = func; - - njs_lexer_consume_token(parser->lexer, 1); - njs_parser_next(parser, njs_parser_arguments); - - ret = njs_parser_after(parser, current, func, 1, - njs_parser_left_hand_side_expression_node); + ret = njs_parser_call_arguments(parser, token, current, func, + njs_parser_left_hand_side_expression_node); if (ret != NJS_OK) { return NJS_ERROR; } @@ -2803,8 +2866,23 @@ njs_parser_call_expression_after(njs_parser_t *parser, break; } - return njs_parser_after(parser, current, NULL, 1, - njs_parser_call_expression_after); + return njs_parser_after(parser, current, NULL, 1, after); +} + + +static njs_int_t +njs_parser_call_expression_after(njs_parser_t *parser, + njs_lexer_token_t *token, njs_queue_link_t *current) +{ + /* + * Arguments + * [ Expression ] + * . IdentifierName + * TemplateLiteral + */ + + return njs_parser_call_or_property_after(parser, token, current, + njs_parser_call_expression_after); } @@ -2930,10 +3008,21 @@ njs_parser_argument_list_after(njs_parser_t *parser, njs_lexer_token_t *token, } +static njs_int_t +njs_parser_optional_chain_wrap(njs_parser_t *parser, + njs_lexer_token_t *token, njs_queue_link_t *current) +{ + return njs_parser_right_link_pop(parser); +} + + static njs_int_t njs_parser_optional_expression_after(njs_parser_t *parser, njs_lexer_token_t *token, njs_queue_link_t *current) { + njs_int_t ret; + njs_parser_node_t *opt, *ref; + if (token->type != NJS_TOKEN_CONDITIONAL) { return njs_parser_stack_pop(parser); } @@ -2947,8 +3036,33 @@ njs_parser_optional_expression_after(njs_parser_t *parser, return njs_parser_stack_pop(parser); } + opt = njs_parser_node_new(parser, NJS_TOKEN_OPTIONAL_CHAIN); + if (opt == NULL) { + return NJS_ERROR; + } + + opt->token_line = token->line; + opt->left = parser->node; + opt->left->dest = opt; + + ref = njs_parser_node_new(parser, NJS_TOKEN_OBJECT_VALUE); + if (ref == NULL) { + return NJS_ERROR; + } + + ref->token_line = token->line; + ref->u.object = parser->node; + opt->u.object = ref; + parser->node = ref; + njs_parser_next(parser, njs_parser_optional_chain); + ret = njs_parser_after(parser, current, opt, 1, + njs_parser_optional_chain_wrap); + if (ret != NJS_OK) { + return NJS_ERROR; + } + return njs_parser_after(parser, current, NULL, 1, njs_parser_optional_expression_after); } @@ -2959,17 +3073,12 @@ njs_parser_optional_chain(njs_parser_t *parser, njs_lexer_token_t *token, njs_queue_link_t *current) { njs_int_t ret; - njs_parser_node_t *func; + njs_parser_node_t *node, *func, *prop_node; /* - * ? . Arguments - * ? . [ Expression ] - * ? . IdentifierName - * ? . TemplateLiteral - * OptionalChain Arguments - * OptionalChain [ Expression ] - * OptionalChain . IdentifierName - * OptionalChain TemplateLiteral + * ?. Arguments + * ?. [ Expression ] + * ?. IdentifierName */ if (token->type != NJS_TOKEN_CONDITIONAL) { @@ -2985,7 +3094,8 @@ njs_parser_optional_chain(njs_parser_t *parser, njs_lexer_token_t *token, return njs_parser_failed(parser); } - njs_lexer_consume_token(parser->lexer, 1); + /* Consume both '?' and '.' */ + njs_lexer_consume_token(parser->lexer, 2); token = njs_lexer_token(parser->lexer, 0); if (token == NULL) { @@ -2994,42 +3104,82 @@ njs_parser_optional_chain(njs_parser_t *parser, njs_lexer_token_t *token, switch (token->type) { case NJS_TOKEN_OPEN_PARENTHESIS: - func = njs_parser_create_call(parser, parser->node, 0); + func = njs_parser_optional_chain_method_call(parser, + parser->node->u.object, + token->line); if (func == NULL) { + func = njs_parser_create_call(parser, parser->node, 0); + if (func == NULL) { + return NJS_ERROR; + } + } + + ret = njs_parser_call_arguments(parser, token, current, func, + njs_parser_left_hand_side_expression_node); + if (ret != NJS_OK) { return NJS_ERROR; } - func->token_line = token->line; - parser->node = func; + return njs_parser_after(parser, current, NULL, 1, + njs_parser_optional_chain_after); - njs_lexer_consume_token(parser->lexer, 2); - njs_parser_next(parser, njs_parser_arguments); + case NJS_TOKEN_OPEN_BRACKET: + node = njs_parser_node_new(parser, NJS_TOKEN_PROPERTY); + if (node == NULL) { + return NJS_ERROR; + } - ret = njs_parser_after(parser, current, func, 1, - njs_parser_left_hand_side_expression_node); + node->u.operation = NJS_VMCODE_PROPERTY_GET; + node->left = parser->node; + node->token_line = token->line; + + parser->node = NULL; + + njs_lexer_consume_token(parser->lexer, 1); + + njs_parser_next(parser, njs_parser_expression); + + ret = njs_parser_after(parser, current, node, 1, + njs_parser_member_expression_bracket); if (ret != NJS_OK) { return NJS_ERROR; } - break; + return njs_parser_after(parser, current, NULL, 1, + njs_parser_optional_chain_after); default: - ret = njs_parser_property(parser, token, current); - - switch (ret) { - case NJS_DONE: - case NJS_DECLINED: + if (!njs_lexer_token_is_identifier_name(token)) { return njs_parser_failed(parser); + } - default: - break; + node = njs_parser_node_new(parser, NJS_TOKEN_PROPERTY); + if (node == NULL) { + return NJS_ERROR; } - break; - } + node->u.operation = NJS_VMCODE_PROPERTY_ATOM_GET; + node->token_line = token->line; - return njs_parser_after(parser, current, NULL, 1, - njs_parser_optional_chain_after); + prop_node = njs_parser_node_string(parser->vm, token, + parser); + if (prop_node == NULL) { + return NJS_ERROR; + } + + prop_node->token_line = token->line; + + node->left = parser->node; + node->right = prop_node; + + parser->node = node; + + njs_lexer_consume_token(parser->lexer, 1); + + njs_parser_next(parser, njs_parser_optional_chain_after); + + return NJS_OK; + } } @@ -3037,9 +3187,6 @@ static njs_int_t njs_parser_optional_chain_after(njs_parser_t *parser, njs_lexer_token_t *token, njs_queue_link_t *current) { - njs_int_t ret; - njs_parser_node_t *func; - /* * OptionalChain Arguments * OptionalChain [ Expression ] @@ -3047,49 +3194,8 @@ njs_parser_optional_chain_after(njs_parser_t *parser, njs_lexer_token_t *token, * OptionalChain TemplateLiteral */ - switch (token->type) { - case NJS_TOKEN_OPEN_PARENTHESIS: - func = njs_parser_create_call(parser, parser->node, 0); - if (func == NULL) { - return NJS_ERROR; - } - - func->token_line = token->line; - parser->node = func; - - njs_lexer_consume_token(parser->lexer, 1); - njs_parser_next(parser, njs_parser_arguments); - - ret = njs_parser_after(parser, current, func, 1, - njs_parser_left_hand_side_expression_node); - if (ret != NJS_OK) { - return NJS_ERROR; - } - - break; - - default: - ret = njs_parser_property(parser, token, current); - - switch (ret) { - case NJS_AGAIN: - return NJS_OK; - - case NJS_DONE: - return njs_parser_stack_pop(parser); - - case NJS_DECLINED: - return njs_parser_failed(parser); - - default: - break; - } - - break; - } - - return njs_parser_after(parser, current, NULL, 1, - njs_parser_optional_chain_after); + return njs_parser_call_or_property_after(parser, token, current, + njs_parser_optional_chain_after); } @@ -3222,6 +3328,21 @@ njs_parser_left_hand_side_expression_after(njs_parser_t *parser, /* OptionalExpression */ case NJS_TOKEN_CONDITIONAL: + token = njs_lexer_peek_token(parser->lexer, token, 0); + if (token == NULL) { + return NJS_ERROR; + } + + if (token->type == NJS_TOKEN_DOT + && parser->node != NULL + && parser->node->ctor) + { + njs_parser_syntax_error(parser, + "Optional chaining cannot be used " + "with new"); + return NJS_DONE; + } + njs_parser_next(parser, njs_parser_optional_expression_after); break; @@ -3537,6 +3658,16 @@ njs_parser_unary_expression_next(njs_parser_t *parser, return njs_parser_stack_pop(parser); + case NJS_TOKEN_OPTIONAL_CHAIN: + if (node->right != NULL + && node->right->token_type == NJS_TOKEN_PROPERTY) + { + node->right->token_type = NJS_TOKEN_PROPERTY_DELETE; + node->right->u.operation = NJS_VMCODE_PROPERTY_DELETE; + } + + break; + case NJS_TOKEN_NAME: njs_parser_syntax_error(parser, "Delete of an unqualified identifier"); @@ -3608,10 +3739,7 @@ njs_parser_await_after(njs_parser_t *parser, njs_lexer_token_t *token, return njs_parser_failed(parser); } - parser->target->right = parser->node; - parser->node = parser->target; - - return njs_parser_stack_pop(parser); + return njs_parser_right_link_pop(parser); } @@ -4480,6 +4608,21 @@ njs_parser_assignment_operator(njs_parser_t *parser, njs_lexer_token_t *token, operation = NJS_VMCODE_EXPONENTIATION; break; + case NJS_TOKEN_LOGICAL_OR_ASSIGNMENT: + njs_thread_log_debug("JS: ||="); + operation = NJS_VMCODE_TEST_IF_TRUE; + break; + + case NJS_TOKEN_LOGICAL_AND_ASSIGNMENT: + njs_thread_log_debug("JS: &&="); + operation = NJS_VMCODE_TEST_IF_FALSE; + break; + + case NJS_TOKEN_COALESCE_ASSIGNMENT: + njs_thread_log_debug("JS: ?\?="); + operation = NJS_VMCODE_COALESCE; + break; + default: return njs_parser_stack_pop(parser); } @@ -4522,10 +4665,7 @@ static njs_int_t njs_parser_assignment_operator_after(njs_parser_t *parser, njs_lexer_token_t *token, njs_queue_link_t *current) { - parser->target->right = parser->node; - parser->node = parser->target; - - return njs_parser_stack_pop(parser); + return njs_parser_right_link_pop(parser); } @@ -5407,10 +5547,7 @@ static njs_int_t njs_parser_do_while_semicolon(njs_parser_t *parser, njs_lexer_token_t *token, njs_queue_link_t *current) { - parser->target->right = parser->node; - parser->node = parser->target; - - return njs_parser_stack_pop(parser); + return njs_parser_right_link_pop(parser); } @@ -5581,10 +5718,7 @@ static njs_int_t njs_parser_after_expr(njs_parser_t *parser, njs_lexer_token_t *token, njs_queue_link_t *current) { - parser->target->right = parser->node; - parser->node = parser->target; - - return njs_parser_stack_pop(parser); + return njs_parser_right_link_pop(parser); } @@ -6086,10 +6220,7 @@ static njs_int_t njs_parser_for_in_statement_after(njs_parser_t *parser, njs_lexer_token_t *token, njs_queue_link_t *current) { - parser->target->right = parser->node; - parser->node = parser->target; - - return njs_parser_stack_pop(parser); + return njs_parser_right_link_pop(parser); } @@ -6342,10 +6473,7 @@ njs_parser_return_statement_after(njs_parser_t *parser, return njs_parser_failed(parser); } - parser->target->right = parser->node; - parser->node = parser->target; - - return njs_parser_stack_pop(parser); + return njs_parser_right_link_pop(parser); } @@ -6769,10 +6897,7 @@ njs_parser_throw_statement_after(njs_parser_t *parser, return njs_parser_failed(parser); } - parser->target->right = parser->node; - parser->node = parser->target; - - return njs_parser_stack_pop(parser); + return njs_parser_right_link_pop(parser); } @@ -8084,10 +8209,7 @@ njs_parser_export_after(njs_parser_t *parser, njs_lexer_token_t *token, return njs_parser_failed(parser); } - parser->target->right = parser->node; - parser->node = parser->target; - - return njs_parser_stack_pop(parser); + return njs_parser_right_link_pop(parser); } @@ -9449,6 +9571,9 @@ njs_parser_serialize_node(njs_chb_t *chain, njs_parser_node_t *node) njs_token_serialize(NJS_TOKEN_BITWISE_OR_ASSIGNMENT); njs_token_serialize(NJS_TOKEN_BITWISE_XOR_ASSIGNMENT); njs_token_serialize(NJS_TOKEN_BITWISE_AND_ASSIGNMENT); + njs_token_serialize(NJS_TOKEN_LOGICAL_OR_ASSIGNMENT); + njs_token_serialize(NJS_TOKEN_LOGICAL_AND_ASSIGNMENT); + njs_token_serialize(NJS_TOKEN_COALESCE_ASSIGNMENT); njs_token_serialize(NJS_TOKEN_EQUAL); njs_token_serialize(NJS_TOKEN_NOT_EQUAL); njs_token_serialize(NJS_TOKEN_STRICT_EQUAL); @@ -9480,6 +9605,7 @@ njs_parser_serialize_node(njs_chb_t *chain, njs_parser_node_t *node) njs_token_serialize(NJS_TOKEN_BITWISE_NOT); njs_token_serialize(NJS_TOKEN_LOGICAL_NOT); njs_token_serialize(NJS_TOKEN_COALESCE); + njs_token_serialize(NJS_TOKEN_OPTIONAL_CHAIN); njs_token_serialize(NJS_TOKEN_IN); njs_token_serialize(NJS_TOKEN_OF); njs_token_serialize(NJS_TOKEN_INSTANCEOF); diff --git a/src/njs_vmcode.c b/src/njs_vmcode.c index c89e7f12f..32a1890d9 100644 --- a/src/njs_vmcode.c +++ b/src/njs_vmcode.c @@ -212,6 +212,7 @@ njs_vmcode_interpreter(njs_vm_t *vm, u_char *pc, njs_value_t *rval, NJS_GOTO_ROW(NJS_VMCODE_TEST_IF_TRUE), NJS_GOTO_ROW(NJS_VMCODE_TEST_IF_FALSE), NJS_GOTO_ROW(NJS_VMCODE_COALESCE), + NJS_GOTO_ROW(NJS_VMCODE_OPTIONAL_CHAIN), NJS_GOTO_ROW(NJS_VMCODE_UNARY_PLUS), NJS_GOTO_ROW(NJS_VMCODE_UNARY_NEGATION), NJS_GOTO_ROW(NJS_VMCODE_BITWISE_NOT), @@ -1110,6 +1111,23 @@ NEXT_LBL; BREAK; + CASE (NJS_VMCODE_OPTIONAL_CHAIN): + njs_vmcode_debug_opcode(); + + njs_vmcode_operand(vm, vmcode->operand2, value1); + + if (njs_is_null_or_undefined(value1)) { + njs_vmcode_operand(vm, vmcode->operand1, retval); + njs_set_undefined(retval); + test_jump = (njs_vmcode_test_jump_t *) pc; + ret = test_jump->offset; + + } else { + ret = sizeof(njs_vmcode_3addr_t); + } + + BREAK; + #define NJS_PRE_UNARY \ if (njs_slow_path(!njs_is_numeric(value1))) { \ ret = njs_value_to_numeric(vm, value1, &numeric1); \ diff --git a/src/njs_vmcode.h b/src/njs_vmcode.h index 5170f29fc..2cdfd23b8 100644 --- a/src/njs_vmcode.h +++ b/src/njs_vmcode.h @@ -98,6 +98,7 @@ enum { NJS_VMCODE_TEST_IF_TRUE, NJS_VMCODE_TEST_IF_FALSE, NJS_VMCODE_COALESCE, + NJS_VMCODE_OPTIONAL_CHAIN, NJS_VMCODE_UNARY_PLUS, NJS_VMCODE_UNARY_NEGATION, NJS_VMCODE_BITWISE_NOT, diff --git a/src/test/njs_unit_test.c b/src/test/njs_unit_test.c index 59f95e34b..bbe70ebbb 100644 --- a/src/test/njs_unit_test.c +++ b/src/test/njs_unit_test.c @@ -1503,6 +1503,409 @@ static njs_unit_test_t njs_test[] = { njs_str("null ?? 0 || 1"), njs_str("SyntaxError: Unexpected token \"||\"") }, + /* Logical assignment: ||= */ + + { njs_str("var a = 0; a ||= 5; a"), + njs_str("5") }, + { njs_str("var a = 1; a ||= 5; a"), + njs_str("1") }, + { njs_str("var a = ''; a ||= 'x'; a"), + njs_str("x") }, + { njs_str("var a = 'y'; a ||= 'x'; a"), + njs_str("y") }, + { njs_str("var a = null; a ||= 42; a"), + njs_str("42") }, + { njs_str("var a = undefined; a ||= 1"), + njs_str("1") }, + + /* ||= short-circuit: RHS not evaluated */ + + { njs_str("var a = 1; var b = 0; a ||= (b = 2); b"), + njs_str("0") }, + { njs_str("var a = 0; var b = 0; a ||= (b = 2); b"), + njs_str("2") }, + + /* Logical assignment: &&= */ + + { njs_str("var a = 1; a &&= 5; a"), + njs_str("5") }, + { njs_str("var a = 0; a &&= 5; a"), + njs_str("0") }, + { njs_str("var a = 'y'; a &&= 'x'; a"), + njs_str("x") }, + { njs_str("var a = ''; a &&= 'x'; a"), + njs_str("") }, + + /* &&= short-circuit: RHS not evaluated */ + + { njs_str("var a = 0; var b = 0; a &&= (b = 2); b"), + njs_str("0") }, + { njs_str("var a = 1; var b = 0; a &&= (b = 2); b"), + njs_str("2") }, + + /* Logical assignment: property targets */ + + { njs_str("var o = {a: 0}; o.a ||= 5; o.a"), + njs_str("5") }, + { njs_str("var o = {a: 1}; o.a ||= 5; o.a"), + njs_str("1") }, + { njs_str("var o = {a: 1}; o.a &&= 5; o.a"), + njs_str("5") }, + { njs_str("var o = {a: 0}; o.a &&= 5; o.a"), + njs_str("0") }, + + /* Logical assignment: bracket property targets */ + + { njs_str("var o = {a: 0}; o['a'] ||= 5; o.a"), + njs_str("5") }, + { njs_str("var o = {a: 1}; o['a'] &&= 5; o.a"), + njs_str("5") }, + + /* Logical assignment: expression result value */ + + { njs_str("var a = 1; (a ||= 5)"), + njs_str("1") }, + { njs_str("var a = 0; (a ||= 5)"), + njs_str("5") }, + { njs_str("var a = 1; (a &&= 5)"), + njs_str("5") }, + { njs_str("var a = 0; (a &&= 5)"), + njs_str("0") }, + + /* Logical assignment: const error */ + + { njs_str("const a = 1; a ||= 2"), + njs_str("1") }, + { njs_str("const a = 0; a ||= 2"), + njs_str("TypeError: assignment to constant variable") }, + { njs_str("const a = 1; a &&= 2"), + njs_str("TypeError: assignment to constant variable") }, + { njs_str("const a = 0; a &&= 2"), + njs_str("0") }, + + /* Logical assignment: getter/setter short-circuit. */ + + { njs_str("var log = '';" + "var o = {" + " get x() {log += 'g'; return 1}," + " set x(v) {log += 's'}" + "};" + "o.x ||= 2;" + "log"), + njs_str("g") }, + { njs_str("var log = '';" + "var o = {" + " get x() {log += 'g'; return 0}," + " set x(v) {log += 's'}" + "};" + "o.x ||= 2;" + "log"), + njs_str("gs") }, + { njs_str("var log = '';" + "var o = {" + " get x() {log += 'g'; return 0}," + " set x(v) {log += 's'}" + "};" + "o.x &&= 2;" + "log"), + njs_str("g") }, + { njs_str("var log = '';" + "var o = {" + " get x() {log += 'g'; return 1}," + " set x(v) {log += 's'}" + "};" + "o.x &&= 2;" + "log"), + njs_str("gs") }, + + /* Logical assignment: non-lvalue error */ + + { njs_str("1 ||= 2"), + njs_str("ReferenceError: Invalid left-hand side in assignment") }, + { njs_str("1 &&= 2"), + njs_str("ReferenceError: Invalid left-hand side in assignment") }, + + /* Logical assignment: ??= */ + + { njs_str("var a = null; a ?\?= 5; a"), + njs_str("5") }, + { njs_str("var a = undefined; a ?\?= 5; a"), + njs_str("5") }, + { njs_str("var a = 0; a ?\?= 5; a"), + njs_str("0") }, + { njs_str("var a = ''; a ?\?= 'x'; a"), + njs_str("") }, + { njs_str("var a = false; a ?\?= true; a"), + njs_str("false") }, + { njs_str("var a = 1; a ?\?= 5; a"), + njs_str("1") }, + + /* ??= short-circuit: RHS not evaluated */ + + { njs_str("var a = 1; var b = 0; a ?\?= (b = 2); b"), + njs_str("0") }, + { njs_str("var a = null; var b = 0; a ?\?= (b = 2); b"), + njs_str("2") }, + + /* ??= property targets */ + + { njs_str("var o = {a: null}; o.a ?\?= 5; o.a"), + njs_str("5") }, + { njs_str("var o = {a: 0}; o.a ?\?= 5; o.a"), + njs_str("0") }, + { njs_str("var o = {a: null}; o['a'] ?\?= 5; o.a"), + njs_str("5") }, + + /* ??= expression result value */ + + { njs_str("var a = 1; (a ?\?= 5)"), + njs_str("1") }, + { njs_str("var a = null; (a ?\?= 5)"), + njs_str("5") }, + + /* ??= const error */ + + { njs_str("const a = 1; a ?\?= 2"), + njs_str("1") }, + { njs_str("const a = null; a ?\?= 2"), + njs_str("TypeError: assignment to constant variable") }, + + /* ??= getter/setter short-circuit */ + + { njs_str("var log = '';" + "var o = {" + " get x() {log += 'g'; return 1}," + " set x(v) {log += 's'}" + "};" + "o.x ?\?= 2;" + "log"), + njs_str("g") }, + { njs_str("var log = '';" + "var o = {" + " get x() {log += 'g'; return null}," + " set x(v) {log += 's'}" + "};" + "o.x ?\?= 2;" + "log"), + njs_str("gs") }, + + /* ??= non-lvalue error */ + + { njs_str("1 ?\?= 2"), + njs_str("ReferenceError: Invalid left-hand side in assignment") }, + + /* Optional chaining: property access. */ + + { njs_str("var o = {a: 1}; o?.a"), + njs_str("1") }, + + { njs_str("var o = null; o?.a"), + njs_str("undefined") }, + + { njs_str("undefined?.a"), + njs_str("undefined") }, + + { njs_str("var o = {a: {b: 2}}; o?.a.b"), + njs_str("2") }, + + { njs_str("var o = null; o?.a.b"), + njs_str("undefined") }, + + /* Optional chaining: bracket access. */ + + { njs_str("var o = {a: 1}; o?.['a']"), + njs_str("1") }, + + { njs_str("var o = null; o?.['a']"), + njs_str("undefined") }, + + /* Optional chaining: method call. */ + + { njs_str("var o = {f: function() {return 42}}; o?.f()"), + njs_str("42") }, + + { njs_str("var o = null; o?.f()"), + njs_str("undefined") }, + + { njs_str("var o = { b() { return this._b; }, _b: { c: 42 }};" + "o?.b().c"), + njs_str("42") }, + + { njs_str("var o = null;" + "o?.b().c"), + njs_str("undefined") }, + + /* Optional chaining: optional call. */ + + { njs_str("var f = function() {return 42}; f?.()"), + njs_str("42") }, + + { njs_str("var f = null; f?.()"), + njs_str("undefined") }, + + /* Optional chaining: nested. */ + + { njs_str("var o = {a: {b: 3}}; o?.a?.b"), + njs_str("3") }, + + { njs_str("var o = {a: null}; o?.a?.b"), + njs_str("undefined") }, + + { njs_str("var o = null; o?.a?.b"), + njs_str("undefined") }, + + /* Optional chaining: short-circuit side effects. */ + + { njs_str("var c = 0; var o = null; o?.a; c"), + njs_str("0") }, + + /* Optional chaining: delete semantics. */ + + { njs_str("var o = null; delete o?.a"), + njs_str("true") }, + + { njs_str("var o = null; delete o?.['a']"), + njs_str("true") }, + + { njs_str("var o = {a: 1}; delete o?.a; o.a"), + njs_str("undefined") }, + + { njs_str("var o = {a: 1}; delete o?.['a']; o.a"), + njs_str("undefined") }, + + /* Optional chaining with ??. */ + + { njs_str("var o = null; o?.a ?? 'default'"), + njs_str("default") }, + + { njs_str("var o = {a: 0}; o?.a ?? 'default'"), + njs_str("0") }, + + /* Optional chaining: advanced and corner cases. */ + + { njs_str("var i = 0; var o = null; o?.[i++]; i"), + njs_str("0") }, + + { njs_str("var i = 0; var o = null; o?.f(i++); i"), + njs_str("0") }, + + { njs_str("var o = {x: 7, m: function() {return this.x}}; o.m?.()"), + njs_str("7") }, + + { njs_str("var o = {x: 9, m: function() {return this.x}}; o?.m?.()"), + njs_str("9") }, + + { njs_str("var i = 0; var o = {m: null}; o.m?.(i++); i"), + njs_str("0") }, + + { njs_str("var o = null; (o?.a).b"), + njs_str("TypeError: cannot get property \"b\" of undefined") }, + + { njs_str("var o = {a: null}; o?.a.b"), + njs_str("TypeError: cannot get property \"b\" of null") }, + + { njs_str("var o = null; o?.().a"), + njs_str("undefined") }, + + { njs_str("var o = function() {return {a: 1}}; o?.().a"), + njs_str("1") }, + + { njs_str("var o = {}; o?.()"), + njs_str("TypeError: object is not a function") }, + + { njs_str("var o = {x: 2, m: function() {return this.x}}; (o.m)?.()"), + njs_str("2") }, + + { njs_str("var o = {m: function() {return 42}}; (o?.m)()"), + njs_str("42") }, + + { njs_str("var o = null; (o?.m)()"), + njs_str("TypeError: undefined is not a function") }, + + { njs_str("var o = {a: {b: 1}}; delete o?.a?.b; o.a.b"), + njs_str("undefined") }, + + { njs_str("var o = null; delete o?.a?.b"), + njs_str("true") }, + + /* Optional chaining: nested property + optional call. */ + + { njs_str("var o = {a: {m: function() {return 42}}};" + "o.a.m?.()"), + njs_str("42") }, + + { njs_str("var o = {a: {x: 7, m: function() {return this.x}}};" + "o.a.m?.()"), + njs_str("7") }, + + { njs_str("var o = {a: {m: null}}; o.a.m?.()"), + njs_str("undefined") }, + + { njs_str("var o = {a: {m: function() {return 42}}};" + "var k = 'a'; o[k].m?.()"), + njs_str("42") }, + + { njs_str("var o = {a: {m: function() {return 42}}};" + "var e = 'a'; o?.[e].m?.()"), + njs_str("42") }, + + { njs_str("var o = null; o?.[0].m?.()"), + njs_str("undefined") }, + + { njs_str("var o = {a: {b: {m: function() {return 99}}}};" + "o.a.b.m?.()"), + njs_str("99") }, + + /* Optional chaining: chained call with continuation. */ + + { njs_str("var o = {a: function() { return {b: 42}; }};" + "o?.a().b"), + njs_str("42") }, + + { njs_str("var o = null; o?.a().b"), + njs_str("undefined") }, + + { njs_str("var o = {a: function() { return {b: 42}; }};" + "o?.a?.().b"), + njs_str("42") }, + + { njs_str("var o = null; o?.a?.().b"), + njs_str("undefined") }, + + /* Optional chaining: ternary disambiguation. */ + + { njs_str("var r = 1 ? .5 : 2; r"), + njs_str("0.5") }, + + { njs_str("var r = 0 ? .5 : 2; r"), + njs_str("2") }, + + { njs_str("var a = {b: 1}; a?.b : 2"), + njs_str("SyntaxError: Unexpected token \":\"") }, + + { njs_str("var o = {c: 3}; var r = o ? o?.c : 0; r"), + njs_str("3") }, + + /* Optional chaining: not a valid assignment target. */ + + { njs_str("var o = {a: 1}; o?.a = 2"), + njs_str("ReferenceError: Invalid left-hand side in assignment") }, + + { njs_str("var o = {a: 1}; o?.a++"), + njs_str("ReferenceError: Invalid left-hand side in postfix operation") }, + + { njs_str("var o = {a: 1}; ++o?.a"), + njs_str("ReferenceError: Invalid left-hand side in prefix operation") }, + + { njs_str("function F(){}; new F?.()"), + njs_str("SyntaxError: Optional chaining cannot be used with new") }, + + { njs_str("var o = {m: function() {}}; new o?.m()"), + njs_str("SyntaxError: Optional chaining cannot be used with new") }, + + { njs_str("var o = {m: function() {}}; new o.m?.()"), + njs_str("SyntaxError: Optional chaining cannot be used with new") }, + { njs_str("var a = true; a = -~!a"), njs_str("1") },