diff --git a/jerry-core/api/jerry-snapshot.c b/jerry-core/api/jerry-snapshot.c index cff61a822..0f07b3937 100644 --- a/jerry-core/api/jerry-snapshot.c +++ b/jerry-core/api/jerry-snapshot.c @@ -161,6 +161,13 @@ snapshot_add_compiled_code (ecma_compiled_code_t *compiled_code_p, /**< compiled ecma_compiled_code_t *copied_code_p = (ecma_compiled_code_t *) copied_code_start_p; #if ENABLED (JERRY_ES2015) + if (compiled_code_p->status_flags & CBC_CODE_FLAG_HAS_TAGGED_LITERALS) + { + const char * const error_message_p = "Unsupported feature: tagged template literals."; + globals_p->snapshot_error = jerry_create_error (JERRY_ERROR_RANGE, (const jerry_char_t *) error_message_p); + return 0; + } + if (compiled_code_p->status_flags & CBC_CODE_FLAGS_CONSTRUCTOR) { globals_p->class_found = true; diff --git a/jerry-core/ecma/base/ecma-gc.c b/jerry-core/ecma/base/ecma-gc.c index e7273ea4f..8ee3e68c6 100644 --- a/jerry-core/ecma/base/ecma-gc.c +++ b/jerry-core/ecma/base/ecma-gc.c @@ -20,6 +20,7 @@ #include "ecma-alloc.h" #include "ecma-array-object.h" #include "ecma-container-object.h" +#include "ecma-function-object.h" #include "ecma-globals.h" #include "ecma-gc.h" #include "ecma-helpers.h" @@ -362,6 +363,22 @@ ecma_gc_mark_container_object (ecma_object_t *object_p) /**< object */ #if ENABLED (JERRY_ES2015) +/** + * Mark tagged template literals of the compiled code. + */ +static void +ecma_gc_mark_tagged_template_literals (const ecma_compiled_code_t *byte_code_p) +{ + JERRY_ASSERT (byte_code_p->status_flags & CBC_CODE_FLAG_HAS_TAGGED_LITERALS); + + ecma_collection_t *collection_p = ecma_compiled_code_get_tagged_template_collection (byte_code_p); + + for (uint32_t i = 0; i < collection_p->item_count; i++) + { + ecma_gc_set_object_visited (ecma_get_object_from_value (collection_p->buffer_p[i])); + } +} /* ecma_gc_mark_tagged_template_literals */ + /** * Mark objects referenced by inactive generator functions, async functions, etc. */ @@ -602,9 +619,17 @@ ecma_gc_mark (ecma_object_t *object_p) /**< object to mark from */ if (!ecma_get_object_is_builtin (object_p)) { ecma_extended_object_t *ext_func_p = (ecma_extended_object_t *) object_p; - ecma_gc_set_object_visited (ECMA_GET_INTERNAL_VALUE_POINTER (ecma_object_t, ext_func_p->u.function.scope_cp)); + +#if ENABLED (JERRY_ES2015) + const ecma_compiled_code_t *byte_code_p = ecma_op_function_get_compiled_code (ext_func_p); + + if (byte_code_p->status_flags & CBC_CODE_FLAG_HAS_TAGGED_LITERALS) + { + ecma_gc_mark_tagged_template_literals (byte_code_p); + } +#endif /* ENABLED (JERRY_ES2015) */ } break; } @@ -620,6 +645,13 @@ ecma_gc_mark (ecma_object_t *object_p) /**< object to mark from */ { ecma_gc_set_object_visited (ecma_get_object_from_value (arrow_func_p->this_binding)); } + + const ecma_compiled_code_t *byte_code_p = ecma_op_arrow_function_get_compiled_code (arrow_func_p); + + if (byte_code_p->status_flags & CBC_CODE_FLAG_HAS_TAGGED_LITERALS) + { + ecma_gc_mark_tagged_template_literals (byte_code_p); + } break; } #endif /* ENABLED (JERRY_ES2015) */ @@ -1133,16 +1165,15 @@ ecma_gc_free_object (ecma_object_t *object_p) /**< object to free */ #if ENABLED (JERRY_SNAPSHOT_EXEC) if (ext_func_p->u.function.bytecode_cp != ECMA_NULL_POINTER) { +#endif /* ENABLED (JERRY_SNAPSHOT_EXEC) */ ecma_bytecode_deref (ECMA_GET_INTERNAL_VALUE_POINTER (ecma_compiled_code_t, ext_func_p->u.function.bytecode_cp)); +#if ENABLED (JERRY_SNAPSHOT_EXEC) } else { ext_object_size = sizeof (ecma_static_function_t); } -#else /* !ENABLED (JERRY_SNAPSHOT_EXEC) */ - ecma_bytecode_deref (ECMA_GET_INTERNAL_VALUE_POINTER (ecma_compiled_code_t, - ext_func_p->u.function.bytecode_cp)); #endif /* ENABLED (JERRY_SNAPSHOT_EXEC) */ break; } @@ -1156,18 +1187,15 @@ ecma_gc_free_object (ecma_object_t *object_p) /**< object to free */ #if ENABLED (JERRY_SNAPSHOT_EXEC) if (arrow_func_p->bytecode_cp != ECMA_NULL_POINTER) { - ecma_bytecode_deref (ECMA_GET_NON_NULL_POINTER (ecma_compiled_code_t, - arrow_func_p->bytecode_cp)); +#endif /* ENABLED (JERRY_SNAPSHOT_EXEC) */ + ecma_bytecode_deref (ECMA_GET_NON_NULL_POINTER (ecma_compiled_code_t, arrow_func_p->bytecode_cp)); ext_object_size = sizeof (ecma_arrow_function_t); +#if ENABLED (JERRY_SNAPSHOT_EXEC) } else { ext_object_size = sizeof (ecma_static_arrow_function_t); } -#else /* !ENABLED (JERRY_SNAPSHOT_EXEC) */ - ecma_bytecode_deref (ECMA_GET_NON_NULL_POINTER (ecma_compiled_code_t, - arrow_func_p->bytecode_cp)); - ext_object_size = sizeof (ecma_arrow_function_t); #endif /* ENABLED (JERRY_SNAPSHOT_EXEC) */ break; } diff --git a/jerry-core/ecma/base/ecma-helpers.c b/jerry-core/ecma/base/ecma-helpers.c index 5212484f8..50efdccb9 100644 --- a/jerry-core/ecma/base/ecma-helpers.c +++ b/jerry-core/ecma/base/ecma-helpers.c @@ -1461,6 +1461,23 @@ ecma_bytecode_deref (ecma_compiled_code_t *bytecode_p) /**< byte code pointer */ } #endif /* ENABLED (JERRY_DEBUGGER) */ +#if ENABLED (JERRY_ES2015) + if (bytecode_p->status_flags & CBC_CODE_FLAG_HAS_TAGGED_LITERALS) + { + ecma_length_t formal_params_number = ecma_compiled_code_get_formal_params (bytecode_p); + + uint8_t *byte_p = (uint8_t *) bytecode_p; + byte_p += ((size_t) bytecode_p->size) << JMEM_ALIGNMENT_LOG; + + ecma_value_t *tagged_base_p = (ecma_value_t *) byte_p; + tagged_base_p -= formal_params_number; + + ecma_collection_t *coll_p = ECMA_GET_INTERNAL_VALUE_POINTER (ecma_collection_t, tagged_base_p[-1]); + + ecma_collection_destroy (coll_p); + } +#endif /* ENABLED (JERRY_ES2015) */ + #if ENABLED (JERRY_MEM_STATS) jmem_stats_free_byte_code_bytes (((size_t) bytecode_p->size) << JMEM_ALIGNMENT_LOG); #endif /* ENABLED (JERRY_MEM_STATS) */ @@ -1478,6 +1495,51 @@ ecma_bytecode_deref (ecma_compiled_code_t *bytecode_p) /**< byte code pointer */ ((size_t) bytecode_p->size) << JMEM_ALIGNMENT_LOG); } /* ecma_bytecode_deref */ +#if ENABLED (JERRY_ES2015) +/** + * Get the tagged template collection of the compiled code + * + * @return pointer to the tagged template collection + */ +ecma_collection_t * +ecma_compiled_code_get_tagged_template_collection (const ecma_compiled_code_t *bytecode_header_p) /**< compiled code */ +{ + JERRY_ASSERT (bytecode_header_p != NULL); + JERRY_ASSERT (bytecode_header_p->status_flags & CBC_CODE_FLAG_HAS_TAGGED_LITERALS); + + uint8_t *byte_p = (uint8_t *) bytecode_header_p; + byte_p += ((size_t) bytecode_header_p->size) << JMEM_ALIGNMENT_LOG; + + ecma_value_t *tagged_base_p = (ecma_value_t *) byte_p; + tagged_base_p -= ecma_compiled_code_get_formal_params (bytecode_header_p); + + return ECMA_GET_INTERNAL_VALUE_POINTER (ecma_collection_t, tagged_base_p[-1]); +} /* ecma_compiled_code_get_tagged_template_collection */ +#endif /* ENABLED (JERRY_ES2015) */ + +#if ENABLED (JERRY_LINE_INFO) || ENABLED (JERRY_ES2015_MODULE_SYSTEM) || ENABLED (JERRY_ES2015) +/** + * Get the number of formal parameters of the compiled code + * + * @return number of formal parameters + */ +ecma_length_t +ecma_compiled_code_get_formal_params (const ecma_compiled_code_t *bytecode_header_p) /**< compiled code */ +{ + if (!(bytecode_header_p->status_flags & CBC_CODE_FLAGS_MAPPED_ARGUMENTS_NEEDED)) + { + return 0; + } + + if (bytecode_header_p->status_flags & CBC_CODE_FLAGS_UINT16_ARGUMENTS) + { + return ((cbc_uint16_arguments_t *) bytecode_header_p)->argument_end; + } + + return ((cbc_uint8_arguments_t *) bytecode_header_p)->argument_end; +} /* ecma_compiled_code_get_formal_params */ +#endif /* ENABLED (JERRY_LINE_INFO) || ENABLED (JERRY_ES2015_MODULE_SYSTEM) || ENABLED (JERRY_ES2015) */ + #if (JERRY_STACK_LIMIT != 0) /** * Check the current stack usage by calculating the difference from the initial stack base. diff --git a/jerry-core/ecma/base/ecma-helpers.h b/jerry-core/ecma/base/ecma-helpers.h index f5801eedf..4306b1f98 100644 --- a/jerry-core/ecma/base/ecma-helpers.h +++ b/jerry-core/ecma/base/ecma-helpers.h @@ -425,6 +425,12 @@ void ecma_raise_error_from_error_reference (ecma_value_t value); void ecma_bytecode_ref (ecma_compiled_code_t *bytecode_p); void ecma_bytecode_deref (ecma_compiled_code_t *bytecode_p); +#if ENABLED (JERRY_ES2015) +ecma_collection_t *ecma_compiled_code_get_tagged_template_collection (const ecma_compiled_code_t *bytecode_header_p); +#endif /* ENABLED (JERRY_ES2015) */ +#if ENABLED (JERRY_LINE_INFO) || ENABLED (JERRY_ES2015_MODULE_SYSTEM) || ENABLED (JERRY_ES2015) +ecma_length_t ecma_compiled_code_get_formal_params (const ecma_compiled_code_t *bytecode_p); +#endif /* ENABLED (JERRY_LINE_INFO) || ENABLED (JERRY_ES2015_MODULE_SYSTEM) || ENABLED (JERRY_ES2015) */ #if (JERRY_STACK_LIMIT != 0) uintptr_t ecma_get_current_stack_usage (void); #endif /* (JERRY_STACK_LIMIT != 0) */ diff --git a/jerry-core/ecma/builtin-objects/ecma-builtin-string.c b/jerry-core/ecma/builtin-objects/ecma-builtin-string.c index 9e967bf6d..5067b86ce 100644 --- a/jerry-core/ecma/builtin-objects/ecma-builtin-string.c +++ b/jerry-core/ecma/builtin-objects/ecma-builtin-string.c @@ -112,6 +112,159 @@ ecma_builtin_string_object_from_char_code (ecma_value_t this_arg, /**< 'this' ar #if ENABLED (JERRY_ES2015) +/** + * The String object's 'raw' routine + * + * See also: + * ECMA-262 v6, 21.1.2.4 + * + * @return ecma value + * Returned value must be freed with ecma_free_value. + */ +static ecma_value_t +ecma_builtin_string_object_raw (ecma_value_t this_arg, /**< 'this' argument */ + const ecma_value_t args[], /**< arguments list */ + ecma_length_t args_number) /**< number of arguments */ +{ + JERRY_UNUSED (this_arg); + + /* 1 - 2. */ + const ecma_value_t *substitutions; + ecma_length_t number_of_substitutions; + + if (args_number > 1) + { + substitutions = args + 1; + number_of_substitutions = args_number - 1; + } + else + { + substitutions = NULL; + number_of_substitutions = 0; + } + + /* 3. */ + ecma_value_t template = args_number > 0 ? args[0] : ECMA_VALUE_UNDEFINED; + + ecma_value_t cooked = ecma_op_to_object (template); + + /* 4. */ + if (ECMA_IS_VALUE_ERROR (cooked)) + { + return cooked; + } + + ecma_object_t *cooked_obj_p = ecma_get_object_from_value (cooked); + + /* 5. */ + ecma_value_t raw = ecma_op_object_get_by_magic_id (cooked_obj_p, LIT_MAGIC_STRING_RAW); + + ecma_deref_object (cooked_obj_p); + + if (ECMA_IS_VALUE_ERROR (raw)) + { + return raw; + } + + ecma_value_t raw_obj = ecma_op_to_object (raw); + + /* 6. */ + if (ECMA_IS_VALUE_ERROR (raw_obj)) + { + ecma_free_value (raw); + return raw_obj; + } + + ecma_object_t *raw_obj_p = ecma_get_object_from_value (raw_obj); + + ecma_value_t ret_value = ECMA_VALUE_ERROR; + + /* 7 - 8. */ + uint32_t literal_segments; + if (ECMA_IS_VALUE_ERROR (ecma_op_object_get_length (raw_obj_p, &literal_segments))) + { + goto cleanup; + } + + /* 9. */ + if (literal_segments == 0) + { + ret_value = ecma_make_magic_string_value (LIT_MAGIC_STRING__EMPTY); + goto cleanup; + } + + /* 10. */ + ecma_stringbuilder_t builder = ecma_stringbuilder_create (); + + /* 11. */ + uint32_t next_index = 0; + + /* 12. */ + while (true) + { + /* 12.a,b */ + ecma_value_t next_seg = ecma_op_object_get_by_uint32_index (raw_obj_p, next_index); + + if (ECMA_IS_VALUE_ERROR (next_seg)) + { + goto builder_cleanup; + } + + ecma_string_t *next_seg_srt_p = ecma_op_to_string (next_seg); + + /* 12.c */ + if (JERRY_UNLIKELY (next_seg_srt_p == NULL)) + { + ecma_free_value (next_seg); + goto builder_cleanup; + } + + /* 12.d */ + ecma_stringbuilder_append (&builder, next_seg_srt_p); + + ecma_deref_ecma_string (next_seg_srt_p); + ecma_free_value (next_seg); + + /* 12.e */ + if (next_index + 1 == literal_segments) + { + ret_value = ecma_make_string_value (ecma_stringbuilder_finalize (&builder)); + goto cleanup; + } + + /* 12.f-g */ + if (next_index >= number_of_substitutions) + { + continue; + } + + /* 12.h */ + ecma_string_t *next_sub_p = ecma_op_to_string (substitutions[next_index]); + + /* 12.i */ + if (JERRY_UNLIKELY (next_sub_p == NULL)) + { + goto builder_cleanup; + } + + /* 12.j */ + ecma_stringbuilder_append (&builder, next_sub_p); + ecma_deref_ecma_string (next_sub_p); + + /* 12.k */ + next_index++; + } + +builder_cleanup: + ecma_stringbuilder_destroy (&builder); + +cleanup: + ecma_deref_object (raw_obj_p); + ecma_free_value (raw); + + return ret_value; +} /* ecma_builtin_string_object_raw */ + /** * The String object's 'fromCodePoint' routine * diff --git a/jerry-core/ecma/builtin-objects/ecma-builtin-string.inc.h b/jerry-core/ecma/builtin-objects/ecma-builtin-string.inc.h index ceab0b059..3f5d5469d 100644 --- a/jerry-core/ecma/builtin-objects/ecma-builtin-string.inc.h +++ b/jerry-core/ecma/builtin-objects/ecma-builtin-string.inc.h @@ -43,6 +43,7 @@ ROUTINE (LIT_MAGIC_STRING_FROM_CHAR_CODE_UL, ecma_builtin_string_object_from_cha #if ENABLED (JERRY_ES2015) ROUTINE (LIT_MAGIC_STRING_FROM_CODE_POINT_UL, ecma_builtin_string_object_from_code_point, NON_FIXED, 1) +ROUTINE (LIT_MAGIC_STRING_RAW, ecma_builtin_string_object_raw, NON_FIXED, 1) #endif /* ENABLED (JERRY_ES2015) */ #endif /* ENABLED (JERRY_BUILTIN_STRING) */ diff --git a/jerry-core/ecma/operations/ecma-function-object.c b/jerry-core/ecma/operations/ecma-function-object.c index d4a1dad29..95c68243c 100644 --- a/jerry-core/ecma/operations/ecma-function-object.c +++ b/jerry-core/ecma/operations/ecma-function-object.c @@ -45,29 +45,18 @@ ecma_op_resource_name (const ecma_compiled_code_t *bytecode_header_p) { JERRY_ASSERT (bytecode_header_p != NULL); - ecma_length_t formal_params_number = 0; - - if (bytecode_header_p->status_flags & CBC_CODE_FLAGS_MAPPED_ARGUMENTS_NEEDED) - { - if (bytecode_header_p->status_flags & CBC_CODE_FLAGS_UINT16_ARGUMENTS) - { - cbc_uint16_arguments_t *args_p = (cbc_uint16_arguments_t *) bytecode_header_p; - - formal_params_number = args_p->argument_end; - } - else - { - cbc_uint8_arguments_t *args_p = (cbc_uint8_arguments_t *) bytecode_header_p; - - formal_params_number = args_p->argument_end; - } - } - uint8_t *byte_p = (uint8_t *) bytecode_header_p; byte_p += ((size_t) bytecode_header_p->size) << JMEM_ALIGNMENT_LOG; ecma_value_t *resource_name_p = (ecma_value_t *) byte_p; - resource_name_p -= formal_params_number; + resource_name_p -= ecma_compiled_code_get_formal_params (bytecode_header_p); + +#if ENABLED (JERRY_ES2015) + if (bytecode_header_p->status_flags & CBC_CODE_FLAG_HAS_TAGGED_LITERALS) + { + resource_name_p--; + } +#endif /* ENABLED (JERRY_ES2015) */ return resource_name_p[-1]; } /* ecma_op_resource_name */ diff --git a/jerry-core/include/jerryscript-snapshot.h b/jerry-core/include/jerryscript-snapshot.h index c0f3c1093..f79ddadbe 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 (34u) +#define JERRY_SNAPSHOT_VERSION (35u) /** * Flags for jerry_generate_snapshot and jerry_generate_function_snapshot. diff --git a/jerry-core/lit/lit-magic-strings.inc.h b/jerry-core/lit/lit-magic-strings.inc.h index d06939f24..a0accbc75 100644 --- a/jerry-core/lit/lit-magic-strings.inc.h +++ b/jerry-core/lit/lit-magic-strings.inc.h @@ -102,6 +102,9 @@ LIT_MAGIC_STRING_DEF (LIT_MAGIC_STRING_POP, "pop") #if ENABLED (JERRY_BUILTIN_MATH) LIT_MAGIC_STRING_DEF (LIT_MAGIC_STRING_POW, "pow") #endif +#if ENABLED (JERRY_ES2015) +LIT_MAGIC_STRING_DEF (LIT_MAGIC_STRING_RAW, "raw") +#endif LIT_MAGIC_STRING_DEF (LIT_MAGIC_STRING_SET, "set") #if ENABLED (JERRY_BUILTIN_MATH) LIT_MAGIC_STRING_DEF (LIT_MAGIC_STRING_SIN, "sin") diff --git a/jerry-core/lit/lit-magic-strings.ini b/jerry-core/lit/lit-magic-strings.ini index bb39c3b5c..a2042c314 100644 --- a/jerry-core/lit/lit-magic-strings.ini +++ b/jerry-core/lit/lit-magic-strings.ini @@ -54,6 +54,7 @@ LIT_MAGIC_STRING_MIN = "min" LIT_MAGIC_STRING_NOW = "now" LIT_MAGIC_STRING_POP = "pop" LIT_MAGIC_STRING_POW = "pow" +LIT_MAGIC_STRING_RAW = "raw" LIT_MAGIC_STRING_SET = "set" LIT_MAGIC_STRING_SIN = "sin" LIT_MAGIC_STRING_TAN = "tan" diff --git a/jerry-core/parser/js/byte-code.h b/jerry-core/parser/js/byte-code.h index 21addae4d..a2919b23c 100644 --- a/jerry-core/parser/js/byte-code.h +++ b/jerry-core/parser/js/byte-code.h @@ -662,6 +662,14 @@ VM_OC_YIELD | VM_OC_GET_STACK) \ CBC_OPCODE (CBC_EXT_RETURN, CBC_NO_FLAG, -1, \ VM_OC_EXT_RETURN | VM_OC_GET_STACK) \ + CBC_OPCODE (CBC_EXT_STRING_CONCAT, CBC_NO_FLAG, -1, \ + VM_OC_STRING_CONCAT | VM_OC_GET_STACK_STACK | VM_OC_PUT_STACK) \ + CBC_OPCODE (CBC_EXT_STRING_CONCAT_RIGHT_LITERAL, CBC_HAS_LITERAL_ARG, 0, \ + VM_OC_STRING_CONCAT | VM_OC_GET_STACK_LITERAL | VM_OC_PUT_STACK) \ + CBC_OPCODE (CBC_EXT_STRING_CONCAT_TWO_LITERALS, CBC_HAS_LITERAL_ARG | CBC_HAS_LITERAL_ARG2, 1, \ + VM_OC_STRING_CONCAT | VM_OC_GET_LITERAL_LITERAL | VM_OC_PUT_STACK) \ + CBC_OPCODE (CBC_EXT_GET_TAGGED_TEMPLATE_LITERAL, CBC_HAS_BYTE_ARG, 1, \ + VM_OC_GET_TEMPLATE_OBJECT | VM_OC_PUT_STACK) \ \ /* Last opcode (not a real opcode). */ \ CBC_OPCODE (CBC_EXT_END, CBC_NO_FLAG, 0, \ @@ -753,6 +761,7 @@ typedef enum CBC_CODE_FLAGS_CONSTRUCTOR = (1u << 10), /**< this function is a constructor */ CBC_CODE_FLAGS_GENERATOR = (1u << 11), /**< this function is a generator */ CBC_CODE_FLAGS_REST_PARAMETER = (1u << 12), /**< this function has rest parameter */ + CBC_CODE_FLAG_HAS_TAGGED_LITERALS = (1u << 13), /**< this function has tagged template literal list */ } cbc_code_flags; /** diff --git a/jerry-core/parser/js/js-lexer.c b/jerry-core/parser/js/js-lexer.c index d6c2e60d1..e67435bc6 100644 --- a/jerry-core/parser/js/js-lexer.c +++ b/jerry-core/parser/js/js-lexer.c @@ -874,8 +874,15 @@ lexer_parse_identifier (parser_context_t *context_p, /**< context */ * Parse string. */ void -lexer_parse_string (parser_context_t *context_p) /**< context */ +lexer_parse_string (parser_context_t *context_p, /**< context */ + lexer_string_options_t opts) /**< options */ { +#if ENABLED (JERRY_ES2015) + const size_t raw_length_inc = (opts & LEXER_STRING_RAW) ? 1 : 0; +#else /* ENABLED (JERRY_ES2015) */ + JERRY_UNUSED (opts); +#endif /* ENABLED (JERRY_ES2015) */ + uint8_t str_end_character = context_p->source_p[0]; const uint8_t *source_p = context_p->source_p + 1; const uint8_t *string_start_p = source_p; @@ -918,6 +925,10 @@ lexer_parse_string (parser_context_t *context_p) /**< context */ continue; } +#if ENABLED (JERRY_ES2015) + length += raw_length_inc; +#endif /* ENABLED (JERRY_ES2015) */ + has_escape = true; /* Newline is ignored. */ @@ -931,6 +942,9 @@ lexer_parse_string (parser_context_t *context_p) /**< context */ } line++; +#if ENABLED (JERRY_ES2015) + length += raw_length_inc; +#endif /* ENABLED (JERRY_ES2015) */ column = 1; continue; } @@ -938,6 +952,9 @@ lexer_parse_string (parser_context_t *context_p) /**< context */ { source_p++; line++; +#if ENABLED (JERRY_ES2015) + length += raw_length_inc; +#endif /* ENABLED (JERRY_ES2015) */ column = 1; continue; } @@ -949,6 +966,13 @@ lexer_parse_string (parser_context_t *context_p) /**< context */ continue; } +#if ENABLED (JERRY_ES2015) + if (opts & LEXER_STRING_RAW) + { + continue; + } +#endif /* ENABLED (JERRY_ES2015) */ + if (*source_p == LIT_CHAR_0 && source_p + 1 < source_end_p && (*(source_p + 1) < LIT_CHAR_0 || *(source_p + 1) > LIT_CHAR_9)) @@ -1086,16 +1110,17 @@ lexer_parse_string (parser_context_t *context_p) /**< context */ #if ENABLED (JERRY_ES2015) else if (str_end_character == LIT_CHAR_GRAVE_ACCENT) { - /* Newline (without backslash) is part of the string. */ + /* Newline (without backslash) is part of the string. + Note: ECMAScript v6, 11.8.6.1 or are both normalized to */ if (*source_p == LIT_CHAR_CR) { + has_escape = true; source_p++; length++; if (source_p < source_end_p && *source_p == LIT_CHAR_LF) { source_p++; - length++; } line++; column = 1; @@ -1658,7 +1683,7 @@ lexer_next_token (parser_context_t *context_p) /**< context */ case LIT_CHAR_GRAVE_ACCENT: #endif /* ENABLED (JERRY_ES2015) */ { - lexer_parse_string (context_p); + lexer_parse_string (context_p, LEXER_STRING_NO_OPTS); return; } @@ -1931,11 +1956,6 @@ lexer_process_char_literal (parser_context_t *context_p, /**< context */ context_p->literal_count++; } /* lexer_process_char_literal */ -/** - * Maximum local buffer size for identifiers which contains escape sequences. - */ -#define LEXER_MAX_LITERAL_LOCAL_BUFFER_SIZE 48 - /** * Convert an ident with escapes to a utf8 string. */ @@ -1974,6 +1994,238 @@ lexer_convert_ident_to_cesu8 (uint8_t *destination_p, /**< destination string */ while (destination_p < destination_end_p); } /* lexer_convert_ident_to_cesu8 */ +/** + * Convert literal to character sequence + */ +const uint8_t * +lexer_convert_literal_to_chars (parser_context_t *context_p, /**< context */ + const lexer_lit_location_t *literal_p, /**< literal location */ + uint8_t *local_byte_array_p, /**< local byte array to store chars */ + lexer_string_options_t opts) /**< options */ +{ + JERRY_ASSERT (context_p->u.allocated_buffer_p == NULL); + + if (!literal_p->has_escape) + { + return literal_p->char_p; + } + + uint8_t *destination_start_p; + if (literal_p->length > LEXER_MAX_LITERAL_LOCAL_BUFFER_SIZE) + { + context_p->u.allocated_buffer_p = (uint8_t *) parser_malloc_local (context_p, literal_p->length); + context_p->allocated_buffer_size = literal_p->length; + destination_start_p = context_p->u.allocated_buffer_p; + } + else + { + destination_start_p = local_byte_array_p; + } + + if (literal_p->type == LEXER_IDENT_LITERAL) + { + lexer_convert_ident_to_cesu8 (destination_start_p, literal_p->char_p, literal_p->length); + return destination_start_p; + } + + const uint8_t *source_p = literal_p->char_p; + uint8_t *destination_p = destination_start_p; + + uint8_t str_end_character = source_p[-1]; + +#if ENABLED (JERRY_ES2015) + if (str_end_character == LIT_CHAR_RIGHT_BRACE) + { + str_end_character = LIT_CHAR_GRAVE_ACCENT; + } + + bool is_raw = (opts & LEXER_STRING_RAW) != 0; +#else /* !ENABLED (JERRY_ES2015) */ + JERRY_UNUSED (opts); + bool is_raw = false; +#endif /* ENABLED (JERRY_ES2015) */ + + while (true) + { + if (*source_p == str_end_character) + { + break; + } + + if (*source_p == LIT_CHAR_BACKSLASH && !is_raw) + { + uint8_t conv_character; + + source_p++; + JERRY_ASSERT (source_p < context_p->source_end_p); + + /* Newline is ignored. */ + if (*source_p == LIT_CHAR_CR) + { + source_p++; + JERRY_ASSERT (source_p < context_p->source_end_p); + + if (*source_p == LIT_CHAR_LF) + { + source_p++; + } + continue; + } + else if (*source_p == LIT_CHAR_LF) + { + source_p++; + continue; + } + else if (*source_p == LEXER_NEWLINE_LS_PS_BYTE_1 && LEXER_NEWLINE_LS_PS_BYTE_23 (source_p)) + { + source_p += 3; + continue; + } + + if (*source_p >= LIT_CHAR_0 && *source_p <= LIT_CHAR_3) + { + lit_code_point_t octal_number = (uint32_t) (*source_p - LIT_CHAR_0); + + source_p++; + JERRY_ASSERT (source_p < context_p->source_end_p); + + if (*source_p >= LIT_CHAR_0 && *source_p <= LIT_CHAR_7) + { + octal_number = octal_number * 8 + (uint32_t) (*source_p - LIT_CHAR_0); + source_p++; + JERRY_ASSERT (source_p < context_p->source_end_p); + + if (*source_p >= LIT_CHAR_0 && *source_p <= LIT_CHAR_7) + { + octal_number = octal_number * 8 + (uint32_t) (*source_p - LIT_CHAR_0); + source_p++; + JERRY_ASSERT (source_p < context_p->source_end_p); + } + } + + destination_p += lit_code_point_to_cesu8_bytes (destination_p, octal_number); + continue; + } + + if (*source_p >= LIT_CHAR_4 && *source_p <= LIT_CHAR_7) + { + uint32_t octal_number = (uint32_t) (*source_p - LIT_CHAR_0); + + source_p++; + JERRY_ASSERT (source_p < context_p->source_end_p); + + if (*source_p >= LIT_CHAR_0 && *source_p <= LIT_CHAR_7) + { + octal_number = octal_number * 8 + (uint32_t) (*source_p - LIT_CHAR_0); + source_p++; + JERRY_ASSERT (source_p < context_p->source_end_p); + } + + *destination_p++ = (uint8_t) octal_number; + continue; + } + + if (*source_p == LIT_CHAR_LOWERCASE_X || *source_p == LIT_CHAR_LOWERCASE_U) + { + source_p++; + destination_p += lit_code_point_to_cesu8_bytes (destination_p, + lexer_unchecked_hex_to_character (&source_p)); + continue; + } + + conv_character = *source_p; + switch (*source_p) + { + case LIT_CHAR_LOWERCASE_B: + { + conv_character = 0x08; + break; + } + case LIT_CHAR_LOWERCASE_T: + { + conv_character = 0x09; + break; + } + case LIT_CHAR_LOWERCASE_N: + { + conv_character = 0x0a; + break; + } + case LIT_CHAR_LOWERCASE_V: + { + conv_character = 0x0b; + break; + } + case LIT_CHAR_LOWERCASE_F: + { + conv_character = 0x0c; + break; + } + case LIT_CHAR_LOWERCASE_R: + { + conv_character = 0x0d; + break; + } + } + + if (conv_character != *source_p) + { + *destination_p++ = conv_character; + source_p++; + continue; + } + } +#if ENABLED (JERRY_ES2015) + else if (str_end_character == LIT_CHAR_GRAVE_ACCENT) + { + if (source_p[0] == LIT_CHAR_DOLLAR_SIGN + && source_p[1] == LIT_CHAR_LEFT_BRACE) + { + source_p++; + JERRY_ASSERT (source_p < context_p->source_end_p); + break; + } + if (*source_p == LIT_CHAR_CR) + { + *destination_p++ = LIT_CHAR_LF; + source_p++; + if (*source_p != str_end_character + && *source_p == LIT_CHAR_LF) + { + source_p++; + } + continue; + } + } +#endif /* ENABLED (JERRY_ES2015) */ + + if (*source_p >= LEXER_UTF8_4BYTE_START) + { + /* Processing 4 byte unicode sequence (even if it is + * after a backslash). Always converted to two 3 byte + * long sequence. */ + lit_four_byte_utf8_char_to_cesu8 (destination_p, source_p); + + destination_p += 6; + source_p += 4; + continue; + } + + *destination_p++ = *source_p++; + + /* There is no need to check the source_end_p + * since the string is terminated by a quotation mark. */ + while (IS_UTF8_INTERMEDIATE_OCTET (*source_p)) + { + *destination_p++ = *source_p++; + } + } + + JERRY_ASSERT (destination_p == destination_start_p + literal_p->length); + + return destination_start_p; +} /* lexer_convert_literal_to_chars */ + /** * Construct a literal object from an identifier. */ @@ -1982,213 +2234,11 @@ lexer_construct_literal_object (parser_context_t *context_p, /**< context */ const lexer_lit_location_t *literal_p, /**< literal location */ uint8_t literal_type) /**< final literal type */ { - uint8_t *destination_start_p; - const uint8_t *source_p; uint8_t local_byte_array[LEXER_MAX_LITERAL_LOCAL_BUFFER_SIZE]; - - JERRY_ASSERT (literal_p->type == LEXER_IDENT_LITERAL - || literal_p->type == LEXER_STRING_LITERAL); - JERRY_ASSERT (context_p->u.allocated_buffer_p == NULL); - - destination_start_p = local_byte_array; - source_p = literal_p->char_p; - - if (literal_p->has_escape) - { - uint8_t *destination_p; - - if (literal_p->length > LEXER_MAX_LITERAL_LOCAL_BUFFER_SIZE) - { - destination_start_p = (uint8_t *) parser_malloc_local (context_p, literal_p->length); - context_p->u.allocated_buffer_p = destination_start_p; - context_p->allocated_buffer_size = literal_p->length; - } - - destination_p = destination_start_p; - - if (literal_p->type == LEXER_IDENT_LITERAL) - { - lexer_convert_ident_to_cesu8 (destination_start_p, source_p, literal_p->length); - } - else - { - uint8_t str_end_character = source_p[-1]; - -#if ENABLED (JERRY_ES2015) - if (str_end_character == LIT_CHAR_RIGHT_BRACE) - { - str_end_character = LIT_CHAR_GRAVE_ACCENT; - } -#endif /* ENABLED (JERRY_ES2015) */ - - while (true) - { - if (*source_p == str_end_character) - { - break; - } - - if (*source_p == LIT_CHAR_BACKSLASH) - { - uint8_t conv_character; - - source_p++; - JERRY_ASSERT (source_p < context_p->source_end_p); - - /* Newline is ignored. */ - if (*source_p == LIT_CHAR_CR) - { - source_p++; - JERRY_ASSERT (source_p < context_p->source_end_p); - - if (*source_p == LIT_CHAR_LF) - { - source_p++; - } - continue; - } - else if (*source_p == LIT_CHAR_LF) - { - source_p++; - continue; - } - else if (*source_p == LEXER_NEWLINE_LS_PS_BYTE_1 && LEXER_NEWLINE_LS_PS_BYTE_23 (source_p)) - { - source_p += 3; - continue; - } - - if (*source_p >= LIT_CHAR_0 && *source_p <= LIT_CHAR_3) - { - lit_code_point_t octal_number = (uint32_t) (*source_p - LIT_CHAR_0); - - source_p++; - JERRY_ASSERT (source_p < context_p->source_end_p); - - if (*source_p >= LIT_CHAR_0 && *source_p <= LIT_CHAR_7) - { - octal_number = octal_number * 8 + (uint32_t) (*source_p - LIT_CHAR_0); - source_p++; - JERRY_ASSERT (source_p < context_p->source_end_p); - - if (*source_p >= LIT_CHAR_0 && *source_p <= LIT_CHAR_7) - { - octal_number = octal_number * 8 + (uint32_t) (*source_p - LIT_CHAR_0); - source_p++; - JERRY_ASSERT (source_p < context_p->source_end_p); - } - } - - destination_p += lit_code_point_to_cesu8_bytes (destination_p, octal_number); - continue; - } - - if (*source_p >= LIT_CHAR_4 && *source_p <= LIT_CHAR_7) - { - uint32_t octal_number = (uint32_t) (*source_p - LIT_CHAR_0); - - source_p++; - JERRY_ASSERT (source_p < context_p->source_end_p); - - if (*source_p >= LIT_CHAR_0 && *source_p <= LIT_CHAR_7) - { - octal_number = octal_number * 8 + (uint32_t) (*source_p - LIT_CHAR_0); - source_p++; - JERRY_ASSERT (source_p < context_p->source_end_p); - } - - *destination_p++ = (uint8_t) octal_number; - continue; - } - - if (*source_p == LIT_CHAR_LOWERCASE_X || *source_p == LIT_CHAR_LOWERCASE_U) - { - source_p++; - destination_p += lit_code_point_to_cesu8_bytes (destination_p, - lexer_unchecked_hex_to_character (&source_p)); - continue; - } - - conv_character = *source_p; - switch (*source_p) - { - case LIT_CHAR_LOWERCASE_B: - { - conv_character = 0x08; - break; - } - case LIT_CHAR_LOWERCASE_T: - { - conv_character = 0x09; - break; - } - case LIT_CHAR_LOWERCASE_N: - { - conv_character = 0x0a; - break; - } - case LIT_CHAR_LOWERCASE_V: - { - conv_character = 0x0b; - break; - } - case LIT_CHAR_LOWERCASE_F: - { - conv_character = 0x0c; - break; - } - case LIT_CHAR_LOWERCASE_R: - { - conv_character = 0x0d; - break; - } - } - - if (conv_character != *source_p) - { - *destination_p++ = conv_character; - source_p++; - continue; - } - } -#if ENABLED (JERRY_ES2015) - else if (str_end_character == LIT_CHAR_GRAVE_ACCENT - && source_p[0] == LIT_CHAR_DOLLAR_SIGN - && source_p[1] == LIT_CHAR_LEFT_BRACE) - { - source_p++; - JERRY_ASSERT (source_p < context_p->source_end_p); - break; - } -#endif /* ENABLED (JERRY_ES2015) */ - - if (*source_p >= LEXER_UTF8_4BYTE_START) - { - /* Processing 4 byte unicode sequence (even if it is - * after a backslash). Always converted to two 3 byte - * long sequence. */ - lit_four_byte_utf8_char_to_cesu8 (destination_p, source_p); - - destination_p += 6; - source_p += 4; - continue; - } - - *destination_p++ = *source_p++; - - /* There is no need to check the source_end_p - * since the string is terminated by a quotation mark. */ - while (IS_UTF8_INTERMEDIATE_OCTET (*source_p)) - { - *destination_p++ = *source_p++; - } - } - - JERRY_ASSERT (destination_p == destination_start_p + literal_p->length); - } - - source_p = destination_start_p; - } + const uint8_t *source_p = lexer_convert_literal_to_chars (context_p, + literal_p, + local_byte_array, + LEXER_STRING_NO_OPTS); lexer_process_char_literal (context_p, source_p, @@ -2196,20 +2246,10 @@ lexer_construct_literal_object (parser_context_t *context_p, /**< context */ literal_type, literal_p->has_escape); - if (destination_start_p != local_byte_array) - { - JERRY_ASSERT (context_p->u.allocated_buffer_p == destination_start_p); - - context_p->u.allocated_buffer_p = NULL; - parser_free_local (destination_start_p, - context_p->allocated_buffer_size); - } - + parser_free_allocated_buffer (context_p); JERRY_ASSERT (context_p->u.allocated_buffer_p == NULL); } /* lexer_construct_literal_object */ -#undef LEXER_MAX_LITERAL_LOCAL_BUFFER_SIZE - /** * Construct a number object. * @@ -2784,7 +2824,7 @@ lexer_expect_object_literal_id (parser_context_t *context_p, /**< context */ case LIT_CHAR_DOUBLE_QUOTE: case LIT_CHAR_SINGLE_QUOTE: { - lexer_parse_string (context_p); + lexer_parse_string (context_p, LEXER_STRING_NO_OPTS); create_literal_object = true; break; } diff --git a/jerry-core/parser/js/js-lexer.h b/jerry-core/parser/js/js-lexer.h index e4f2f97a6..216df07e9 100644 --- a/jerry-core/parser/js/js-lexer.h +++ b/jerry-core/parser/js/js-lexer.h @@ -214,6 +214,11 @@ typedef enum #define LEXER_BINARY_LVALUE_OP_TOKEN_TO_OPCODE(token_type) \ ((cbc_opcode_t) ((((token_type) - LEXER_ASSIGN_ADD) * 2) + CBC_ASSIGN_ADD)) +/** + * Maximum local buffer size for identifiers which contains escape sequences. + */ +#define LEXER_MAX_LITERAL_LOCAL_BUFFER_SIZE 48 + /** * Lexer newline flags. */ @@ -234,6 +239,15 @@ typedef enum LEXER_OBJ_IDENT_OBJECT_PATTERN = (1u << 3), /**< parse "get"/"set" as string literal in object pattern */ } lexer_obj_ident_opts_t; +/** + * Lexer string options. + */ +typedef enum +{ + LEXER_STRING_NO_OPTS = (1u << 0), /**< no options */ + LEXER_STRING_RAW = (1u << 1), /**< raw string ECMAScript v6, 11.8.6.1: TVR */ +} lexer_string_options_t; + /** * Lexer scan identifier parse options. */ diff --git a/jerry-core/parser/js/js-parser-expr.c b/jerry-core/parser/js/js-parser-expr.c index 57503a672..4b4ff5b03 100644 --- a/jerry-core/parser/js/js-parser-expr.c +++ b/jerry-core/parser/js/js-parser-expr.c @@ -20,6 +20,7 @@ #include "ecma-helpers.h" #include "lit-char-helpers.h" +#include "js-parser-tagged-template-literal.h" /** \addtogroup parser Parser * @{ @@ -1142,21 +1143,21 @@ parser_parse_template_literal (parser_context_t *context_p) /**< context */ { if (context_p->last_cbc_opcode == CBC_PUSH_TWO_LITERALS) { - context_p->last_cbc_opcode = CBC_ADD_TWO_LITERALS; + context_p->last_cbc_opcode = PARSER_TO_EXT_OPCODE (CBC_EXT_STRING_CONCAT_TWO_LITERALS); } else if (context_p->last_cbc_opcode == CBC_PUSH_LITERAL) { - context_p->last_cbc_opcode = CBC_ADD_RIGHT_LITERAL; + context_p->last_cbc_opcode = PARSER_TO_EXT_OPCODE (CBC_EXT_STRING_CONCAT_RIGHT_LITERAL); } else { - parser_emit_cbc (context_p, CBC_ADD); + parser_emit_cbc_ext (context_p, CBC_EXT_STRING_CONCAT); } } context_p->source_p--; context_p->column--; - lexer_parse_string (context_p); + lexer_parse_string (context_p, LEXER_STRING_NO_OPTS); if (is_empty_head || context_p->token.lit_location.length > 0) { @@ -1166,20 +1167,21 @@ parser_parse_template_literal (parser_context_t *context_p) /**< context */ if (context_p->last_cbc_opcode == CBC_PUSH_LITERAL) { - context_p->last_cbc_opcode = CBC_ADD_TWO_LITERALS; + context_p->last_cbc_opcode = PARSER_TO_EXT_OPCODE (CBC_EXT_STRING_CONCAT_TWO_LITERALS); context_p->last_cbc.value = context_p->lit_object.index; context_p->last_cbc.literal_type = context_p->token.lit_location.type; context_p->last_cbc.literal_keyword_type = context_p->token.keyword_type; } else { - parser_emit_cbc_literal_from_token (context_p, CBC_ADD_RIGHT_LITERAL); + parser_emit_cbc_ext_literal_from_token (context_p, CBC_EXT_STRING_CONCAT_RIGHT_LITERAL); } } while (context_p->source_p[-1] != LIT_CHAR_GRAVE_ACCENT) { lexer_next_token (context_p); + parser_parse_expression (context_p, PARSE_EXPR); if (context_p->token.type != LEXER_RIGHT_BRACE) @@ -1189,16 +1191,16 @@ parser_parse_template_literal (parser_context_t *context_p) /**< context */ if (context_p->last_cbc_opcode == CBC_PUSH_LITERAL) { - context_p->last_cbc_opcode = CBC_ADD_RIGHT_LITERAL; + context_p->last_cbc_opcode = PARSER_TO_EXT_OPCODE (CBC_EXT_STRING_CONCAT_RIGHT_LITERAL); } else { - parser_emit_cbc (context_p, CBC_ADD); + parser_emit_cbc_ext (context_p, CBC_EXT_STRING_CONCAT); } context_p->source_p--; context_p->column--; - lexer_parse_string (context_p); + lexer_parse_string (context_p, LEXER_STRING_NO_OPTS); if (context_p->token.lit_location.length > 0) { @@ -1206,12 +1208,75 @@ parser_parse_template_literal (parser_context_t *context_p) /**< context */ &context_p->token.lit_location, context_p->token.lit_location.type); - parser_emit_cbc_literal_from_token (context_p, CBC_ADD_RIGHT_LITERAL); + parser_emit_cbc_ext_literal_from_token (context_p, CBC_EXT_STRING_CONCAT_RIGHT_LITERAL); + } + } +} /* parser_parse_template_literal */ + +/** + * Parse tagged template literal. + */ +static size_t +parser_parse_tagged_template_literal (parser_context_t *context_p) /**< context */ +{ + JERRY_ASSERT (context_p->token.type == LEXER_TEMPLATE_LITERAL); + + uint32_t call_arguments = 0; + ecma_collection_t *collection_p; + + if (context_p->tagged_template_literal_cp == JMEM_CP_NULL) + { + collection_p = ecma_new_collection (); + ECMA_SET_INTERNAL_VALUE_POINTER (context_p->tagged_template_literal_cp, collection_p); + } + else + { + collection_p = ECMA_GET_INTERNAL_VALUE_POINTER (ecma_collection_t, context_p->tagged_template_literal_cp); + if (collection_p->item_count > CBC_MAXIMUM_BYTE_VALUE) + { + parser_raise_error (context_p, PARSER_ERR_ARGUMENT_LIMIT_REACHED); } } - return; -} /* parser_parse_template_literal */ + const uint32_t tagged_id = collection_p->item_count; + uint32_t prop_idx = 0; + ecma_object_t *raw_strings_p; + ecma_object_t *template_obj_p = parser_new_tagged_template_literal (&raw_strings_p); + ecma_collection_push_back (collection_p, ecma_make_object_value (template_obj_p)); + + parser_tagged_template_literal_append_strings (context_p, template_obj_p, raw_strings_p, prop_idx++); + + call_arguments++; + parser_emit_cbc_ext_call (context_p, CBC_EXT_GET_TAGGED_TEMPLATE_LITERAL, tagged_id); + + while (context_p->source_p[-1] != LIT_CHAR_GRAVE_ACCENT) + { + JERRY_ASSERT (context_p->source_p[-1] == LIT_CHAR_LEFT_BRACE); + lexer_next_token (context_p); + + if (++call_arguments > CBC_MAXIMUM_BYTE_VALUE) + { + parser_raise_error (context_p, PARSER_ERR_ARGUMENT_LIMIT_REACHED); + } + + parser_parse_expression (context_p, PARSE_EXPR); + + if (context_p->token.type != LEXER_RIGHT_BRACE) + { + parser_raise_error (context_p, PARSER_ERR_RIGHT_BRACE_EXPECTED); + } + + context_p->source_p--; + context_p->column--; + lexer_parse_string (context_p, LEXER_STRING_NO_OPTS); + + parser_tagged_template_literal_append_strings (context_p, template_obj_p, raw_strings_p, prop_idx++); + } + + parser_tagged_template_literal_finalize (template_obj_p, raw_strings_p); + + return call_arguments; +} /* parser_parse_tagged_template_literal */ /** * Throws an error if the current expression is not an assignment expression. @@ -1681,6 +1746,9 @@ parser_process_unary_expression (parser_context_t *context_p, /**< context */ continue; } +#if ENABLED (JERRY_ES2015) + case LEXER_TEMPLATE_LITERAL: +#endif /* ENABLED (JERRY_ES2015) */ case LEXER_LEFT_PAREN: { size_t call_arguments = 0; @@ -1724,45 +1792,54 @@ parser_process_unary_expression (parser_context_t *context_p, /**< context */ } } - lexer_next_token (context_p); - #if ENABLED (JERRY_ES2015) bool has_spread_element = false; -#endif /* ENABLED (JERRY_ES2015) */ - if (context_p->token.type != LEXER_RIGHT_PAREN) + if (context_p->token.type == LEXER_TEMPLATE_LITERAL) + { + call_arguments = parser_parse_tagged_template_literal (context_p); + } + else { - while (true) - { - if (++call_arguments > CBC_MAXIMUM_BYTE_VALUE) - { - parser_raise_error (context_p, PARSER_ERR_ARGUMENT_LIMIT_REACHED); - } - -#if ENABLED (JERRY_ES2015) - if (context_p->token.type == LEXER_THREE_DOTS) - { - has_spread_element = true; - call_arguments++; - parser_emit_cbc_ext (context_p, CBC_EXT_PUSH_SPREAD_ELEMENT); - lexer_next_token (context_p); - } #endif /* ENABLED (JERRY_ES2015) */ - - parser_parse_expression (context_p, PARSE_EXPR_NO_COMMA); - - if (context_p->token.type != LEXER_COMMA) - { - break; - } - lexer_next_token (context_p); - } + lexer_next_token (context_p); if (context_p->token.type != LEXER_RIGHT_PAREN) { - parser_raise_error (context_p, PARSER_ERR_RIGHT_PAREN_EXPECTED); + while (true) + { + if (++call_arguments > CBC_MAXIMUM_BYTE_VALUE) + { + parser_raise_error (context_p, PARSER_ERR_ARGUMENT_LIMIT_REACHED); + } + +#if ENABLED (JERRY_ES2015) + if (context_p->token.type == LEXER_THREE_DOTS) + { + has_spread_element = true; + call_arguments++; + parser_emit_cbc_ext (context_p, CBC_EXT_PUSH_SPREAD_ELEMENT); + lexer_next_token (context_p); + } +#endif /* ENABLED (JERRY_ES2015) */ + + parser_parse_expression (context_p, PARSE_EXPR_NO_COMMA); + + if (context_p->token.type != LEXER_COMMA) + { + break; + } + lexer_next_token (context_p); + } + + if (context_p->token.type != LEXER_RIGHT_PAREN) + { + parser_raise_error (context_p, PARSER_ERR_RIGHT_PAREN_EXPECTED); + } } +#if ENABLED (JERRY_ES2015) } +#endif /* ENABLED (JERRY_ES2015) */ lexer_next_token (context_p); diff --git a/jerry-core/parser/js/js-parser-internal.h b/jerry-core/parser/js/js-parser-internal.h index d08224535..476bf2434 100644 --- a/jerry-core/parser/js/js-parser-internal.h +++ b/jerry-core/parser/js/js-parser-internal.h @@ -421,6 +421,7 @@ typedef struct parser_saved_context_t uint16_t scope_stack_reg_top; /**< preserved top register of scope stack */ #if ENABLED (JERRY_ES2015) uint16_t scope_stack_global_end; /**< end of global declarations of a function */ + ecma_value_t tagged_template_literal_cp; /**< compessed pointer to the tagged template literal collection */ #endif /* ENABLED (JERRY_ES2015) */ #ifndef JERRY_NDEBUG @@ -491,6 +492,7 @@ typedef struct uint16_t scope_stack_reg_top; /**< current top register of scope stack */ #if ENABLED (JERRY_ES2015) uint16_t scope_stack_global_end; /**< end of global declarations of a function */ + ecma_value_t tagged_template_literal_cp; /**< compessed pointer to the tagged template literal collection */ #endif /* ENABLED (JERRY_ES2015) */ uint8_t stack_top_uint8; /**< top byte stored on the stack */ @@ -533,6 +535,7 @@ void *parser_malloc (parser_context_t *context_p, size_t size); void parser_free (void *ptr, size_t size); void *parser_malloc_local (parser_context_t *context_p, size_t size); void parser_free_local (void *ptr, size_t size); +void parser_free_allocated_buffer (parser_context_t *context_p); /* Parser byte stream. */ @@ -602,6 +605,8 @@ void parser_set_continues_to_current_position (parser_context_t *context_p, pars parser_emit_cbc ((context_p), PARSER_TO_EXT_OPCODE (opcode)) #define parser_emit_cbc_ext_literal(context_p, opcode, literal_index) \ parser_emit_cbc_literal ((context_p), PARSER_TO_EXT_OPCODE (opcode), (literal_index)) +#define parser_emit_cbc_ext_literal_from_token(context_p, opcode) \ + parser_emit_cbc_literal_from_token ((context_p), PARSER_TO_EXT_OPCODE (opcode)) #define parser_emit_cbc_ext_call(context_p, opcode, call_arguments) \ parser_emit_cbc_call ((context_p), PARSER_TO_EXT_OPCODE (opcode), (call_arguments)) #define parser_emit_cbc_ext_call(context_p, opcode, call_arguments) \ @@ -631,10 +636,13 @@ bool lexer_check_arrow (parser_context_t *context_p); bool lexer_check_arrow_param (parser_context_t *context_p); bool lexer_check_yield_no_arg (parser_context_t *context_p); #endif /* ENABLED (JERRY_ES2015) */ -void lexer_parse_string (parser_context_t *context_p); +void lexer_parse_string (parser_context_t *context_p, lexer_string_options_t opts); void lexer_expect_identifier (parser_context_t *context_p, uint8_t literal_type); void lexer_scan_identifier (parser_context_t *context_p, uint32_t ident_opts); void lexer_convert_ident_to_cesu8 (uint8_t *destination_p, const uint8_t *source_p, prop_length_t length); + +const uint8_t *lexer_convert_literal_to_chars (parser_context_t *context_p, const lexer_lit_location_t *literal_p, + uint8_t *local_byte_array_p, lexer_string_options_t opts); void lexer_expect_object_literal_id (parser_context_t *context_p, uint32_t ident_opts); void lexer_construct_literal_object (parser_context_t *context_p, const lexer_lit_location_t *literal_p, uint8_t literal_type); diff --git a/jerry-core/parser/js/js-parser-mem.c b/jerry-core/parser/js/js-parser-mem.c index d3fafb6a1..4e9c932f1 100644 --- a/jerry-core/parser/js/js-parser-mem.c +++ b/jerry-core/parser/js/js-parser-mem.c @@ -82,12 +82,27 @@ parser_malloc_local (parser_context_t *context_p, /**< context */ /** * Free memory allocated by parser_malloc_local. */ -void parser_free_local (void *ptr, /**< pointer to free */ - size_t size) /**< size of the memory */ +void +parser_free_local (void *ptr, /**< pointer to free */ + size_t size) /**< size of the memory */ { jmem_heap_free_block (ptr, size); } /* parser_free_local */ +/** + * Free the dynamically allocated buffer stored in the context + */ +void +parser_free_allocated_buffer (parser_context_t *context_p) /**< context */ +{ + if (context_p->u.allocated_buffer_p != NULL) + { + parser_free_local (context_p->u.allocated_buffer_p, + context_p->allocated_buffer_size); + context_p->u.allocated_buffer_p = NULL; + } +} /* parser_free_allocated_buffer */ + /**********************************************************************/ /* Parser data management functions */ /**********************************************************************/ diff --git a/jerry-core/parser/js/js-parser-tagged-template-literal.c b/jerry-core/parser/js/js-parser-tagged-template-literal.c new file mode 100644 index 000000000..3cb62253e --- /dev/null +++ b/jerry-core/parser/js/js-parser-tagged-template-literal.c @@ -0,0 +1,153 @@ +/* Copyright JS Foundation and other contributors, http://js.foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "js-parser-tagged-template-literal.h" +#include "js-lexer.h" +#include "ecma-array-object.h" +#include "ecma-builtin-helpers.h" +#include "ecma-gc.h" +#include "ecma-helpers.h" + +/* \addtogroup parser Parser + * @{ + * + * \addtogroup jsparser JavaScript + * @{ + * + * \addtogroup jsparser_tagged_template_literal Tagged template literal + * @{ + */ + +#if ENABLED (JERRY_ES2015) +/** + * Append the cooked and raw string to the corresponding array + */ +void +parser_tagged_template_literal_append_strings (parser_context_t *context_p, /**< parser context */ + ecma_object_t *template_obj_p, /**< template object */ + ecma_object_t *raw_strings_p, /**< raw strings object */ + uint32_t prop_idx) /**< property index to set the values */ +{ + lexer_lit_location_t *lit_loc_p = &context_p->token.lit_location; + + if (lit_loc_p->length == 0) + { + ecma_builtin_helper_def_prop_by_index (template_obj_p, + prop_idx, + ecma_make_magic_string_value (LIT_MAGIC_STRING__EMPTY), + ECMA_PROPERTY_FIXED); + + ecma_builtin_helper_def_prop_by_index (raw_strings_p, + prop_idx, + ecma_make_magic_string_value (LIT_MAGIC_STRING__EMPTY), + ECMA_PROPERTY_FIXED); + return; + } + + uint8_t local_byte_array[LEXER_MAX_LITERAL_LOCAL_BUFFER_SIZE]; + const uint8_t *source_p = lexer_convert_literal_to_chars (context_p, + &context_p->token.lit_location, + local_byte_array, + LEXER_STRING_NO_OPTS); + + ecma_string_t *raw_str_p; + ecma_string_t *cooked_str_p = ecma_new_ecma_string_from_utf8 (source_p, lit_loc_p->length); + parser_free_allocated_buffer (context_p); + + if (lit_loc_p->has_escape) + { + context_p->source_p = context_p->token.lit_location.char_p - 1; + lexer_parse_string (context_p, LEXER_STRING_RAW); + source_p = lexer_convert_literal_to_chars (context_p, + &context_p->token.lit_location, + local_byte_array, + LEXER_STRING_RAW); + + raw_str_p = ecma_new_ecma_string_from_utf8 (source_p, lit_loc_p->length); + parser_free_allocated_buffer (context_p); + } + else + { + ecma_ref_ecma_string (cooked_str_p); + raw_str_p = cooked_str_p; + } + + ecma_builtin_helper_def_prop_by_index (template_obj_p, + prop_idx, + ecma_make_string_value (cooked_str_p), + ECMA_PROPERTY_FIXED); + + ecma_builtin_helper_def_prop_by_index (raw_strings_p, + prop_idx, + ecma_make_string_value (raw_str_p), + ECMA_PROPERTY_FIXED); + + ecma_deref_ecma_string (cooked_str_p); + ecma_deref_ecma_string (raw_str_p); +} /* parser_tagged_template_literal_append_strings */ + +/** + * Create new tagged template literal object + * + * @return pointer to the allocated object + */ +ecma_object_t * +parser_new_tagged_template_literal (ecma_object_t **raw_strings_p) /**< [out] raw strings object */ +{ + ecma_object_t *template_obj_p = ecma_op_new_array_object (0); + *raw_strings_p = ecma_op_new_array_object (0); + + ecma_builtin_helper_def_prop (template_obj_p, + ecma_get_magic_string (LIT_MAGIC_STRING_RAW), + ecma_make_object_value (*raw_strings_p), + ECMA_PROPERTY_FIXED); + ecma_deref_object (*raw_strings_p); + + return template_obj_p; +} /* parser_new_tagged_template_literal */ + +/** + * Set integrity level of the given template array object to "frozen" + */ +static void +parser_tagged_template_literal_freeze_array (ecma_object_t *obj_p) +{ + JERRY_ASSERT (ecma_get_object_type (obj_p) == ECMA_OBJECT_TYPE_ARRAY); + + ecma_set_object_extensible (obj_p, false); + ecma_extended_object_t *ext_obj_p = (ecma_extended_object_t *) obj_p; + uint8_t new_prop_value = (uint8_t) (ext_obj_p->u.array.u.length_prop & ~ECMA_PROPERTY_FLAG_WRITABLE); + ext_obj_p->u.array.u.length_prop = new_prop_value; +} /* parser_tagged_template_literal_freeze_array */ + +/** + * Finalize the tagged template object + */ +void +parser_tagged_template_literal_finalize (ecma_object_t *template_obj_p, /**< template object */ + ecma_object_t *raw_strings_p) /**< raw strings object */ +{ + parser_tagged_template_literal_freeze_array (template_obj_p); + parser_tagged_template_literal_freeze_array (raw_strings_p); + + ecma_deref_object (template_obj_p); +} /* parser_tagged_template_literal_finalize */ +#endif /* ENABLED (JERRY_ES2015) */ + +/** + * @} + * @} + * @} + */ diff --git a/jerry-core/parser/js/js-parser-tagged-template-literal.h b/jerry-core/parser/js/js-parser-tagged-template-literal.h new file mode 100644 index 000000000..b4fe9a59d --- /dev/null +++ b/jerry-core/parser/js/js-parser-tagged-template-literal.h @@ -0,0 +1,51 @@ +/* Copyright JS Foundation and other contributors, http://js.foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ECMA_TAGGED_TEMPLATE_LITERAL_H +#define ECMA_TAGGED_TEMPLATE_LITERAL_H + +/* \addtogroup parser Parser + * @{ + * + * \addtogroup jsparser JavaScript + * @{ + * + * \addtogroup jsparser_tagged_template_literal Tagged template literal + * @{ + */ + +#include "common.h" +#include "ecma-globals.h" +#include "js-parser-internal.h" + +#if ENABLED (JERRY_ES2015) +ecma_object_t * +parser_new_tagged_template_literal (ecma_object_t **raw_strings_p); + +void +parser_tagged_template_literal_append_strings (parser_context_t *context_p, ecma_object_t *template_obj_p, + ecma_object_t *raw_strings_p, uint32_t prop_index); + +void +parser_tagged_template_literal_finalize (ecma_object_t *template_obj_p, ecma_object_t *raw_strings_p); +#endif /* ENABLED (JERRY_ES2015) */ + +#endif /* ECMA_TAGGED_TEMPLATE_LITERAL_H */ + +/** + * @} + * @} + * @} + */ diff --git a/jerry-core/parser/js/js-parser.c b/jerry-core/parser/js/js-parser.c index 683c89203..481d2cea4 100644 --- a/jerry-core/parser/js/js-parser.c +++ b/jerry-core/parser/js/js-parser.c @@ -1163,6 +1163,13 @@ parser_post_processing (parser_context_t *context_p) /**< context */ total_size += context_p->argument_count * sizeof (ecma_value_t); } +#if ENABLED (JERRY_ES2015) + if (context_p->tagged_template_literal_cp != JMEM_CP_NULL) + { + total_size += sizeof (ecma_value_t); + } +#endif /* ENABLED (JERRY_ES2015) */ + #if ENABLED (JERRY_LINE_INFO) || ENABLED (JERRY_ES2015_MODULE_SYSTEM) if (JERRY_CONTEXT (resource_name) != ECMA_VALUE_UNDEFINED) { @@ -1290,6 +1297,11 @@ parser_post_processing (parser_context_t *context_p) /**< context */ { compiled_code_p->status_flags |= CBC_CODE_FLAGS_REST_PARAMETER; } + + if (context_p->tagged_template_literal_cp != JMEM_CP_NULL) + { + compiled_code_p->status_flags |= CBC_CODE_FLAG_HAS_TAGGED_LITERALS; + } #endif /* ENABLED (JERRY_ES2015) */ literal_pool_p = ((ecma_value_t *) byte_code_p) - context_p->register_count; @@ -1572,6 +1584,20 @@ parser_post_processing (parser_context_t *context_p) /**< context */ } } +#if ENABLED (JERRY_ES2015) + if (context_p->tagged_template_literal_cp != JMEM_CP_NULL) + { + ecma_value_t *tagged_base_p = (ecma_value_t *) (((uint8_t *) compiled_code_p) + total_size); + + if (PARSER_NEEDS_MAPPED_ARGUMENTS (context_p->status_flags)) + { + tagged_base_p -= context_p->argument_count; + } + + tagged_base_p[-1] = (ecma_value_t) context_p->tagged_template_literal_cp; + } +#endif /* ENABLED (JERRY_ES2015) */ + #if ENABLED (JERRY_LINE_INFO) || ENABLED (JERRY_ES2015_MODULE_SYSTEM) if (JERRY_CONTEXT (resource_name) != ECMA_VALUE_UNDEFINED) { @@ -1582,6 +1608,13 @@ parser_post_processing (parser_context_t *context_p) /**< context */ resource_name_p -= context_p->argument_count; } +#if ENABLED (JERRY_ES2015) + if (context_p->tagged_template_literal_cp != JMEM_CP_NULL) + { + resource_name_p--; + } +#endif /* ENABLED (JERRY_ES2015) */ + resource_name_p[-1] = JERRY_CONTEXT (resource_name); } #endif /* ENABLED (JERRY_LINE_INFO) || ENABLED (JERRY_ES2015_MODULE_SYSTEM) */ @@ -1852,6 +1885,7 @@ parser_parse_source (const uint8_t *arg_list_p, /**< function argument list */ #if ENABLED (JERRY_ES2015) context.status_flags |= PARSER_GET_CLASS_PARSER_OPTS (parse_opts); + context.tagged_template_literal_cp = JMEM_CP_NULL; #endif /* ENABLED (JERRY_ES2015) */ context.stack_depth = 0; @@ -1890,6 +1924,7 @@ parser_parse_source (const uint8_t *arg_list_p, /**< function argument list */ context.scope_stack_reg_top = 0; #if ENABLED (JERRY_ES2015) context.scope_stack_global_end = 0; + context.tagged_template_literal_cp = JMEM_CP_NULL; #endif /* ENABLED (JERRY_ES2015) */ #ifndef JERRY_NDEBUG @@ -2082,11 +2117,7 @@ parser_parse_source (const uint8_t *arg_list_p, /**< function argument list */ parser_free_jumps (context.last_statement); } - if (context.u.allocated_buffer_p != NULL) - { - parser_free_local (context.u.allocated_buffer_p, - context.allocated_buffer_size); - } + parser_free_allocated_buffer (&context); scanner_cleanup (&context); @@ -2168,6 +2199,7 @@ parser_save_context (parser_context_t *context_p, /**< context */ saved_context_p->scope_stack_reg_top = context_p->scope_stack_reg_top; #if ENABLED (JERRY_ES2015) saved_context_p->scope_stack_global_end = context_p->scope_stack_global_end; + saved_context_p->tagged_template_literal_cp = context_p->tagged_template_literal_cp; #endif /* ENABLED (JERRY_ES2015) */ #ifndef JERRY_NDEBUG @@ -2195,6 +2227,7 @@ parser_save_context (parser_context_t *context_p, /**< context */ context_p->scope_stack_reg_top = 0; #if ENABLED (JERRY_ES2015) context_p->scope_stack_global_end = 0; + context_p->tagged_template_literal_cp = JMEM_CP_NULL; #endif /* ENABLED (JERRY_ES2015) */ #ifndef JERRY_NDEBUG @@ -2239,6 +2272,7 @@ parser_restore_context (parser_context_t *context_p, /**< context */ context_p->scope_stack_reg_top = saved_context_p->scope_stack_reg_top; #if ENABLED (JERRY_ES2015) context_p->scope_stack_global_end = saved_context_p->scope_stack_global_end; + context_p->tagged_template_literal_cp = saved_context_p->tagged_template_literal_cp; #endif /* ENABLED (JERRY_ES2015) */ #ifndef JERRY_NDEBUG @@ -2482,9 +2516,25 @@ parser_raise_error (parser_context_t *context_p, /**< context */ parser_free_jumps (saved_context_p->last_statement); } +#if ENABLED (JERRY_ES2015) + if (saved_context_p->tagged_template_literal_cp != JMEM_CP_NULL) + { + ecma_collection_free (ECMA_GET_INTERNAL_VALUE_POINTER (ecma_collection_t, + saved_context_p->tagged_template_literal_cp)); + } +#endif /* ENABLED (JERRY_ES2015) */ + saved_context_p = saved_context_p->prev_context_p; } +#if ENABLED (JERRY_ES2015) + if (context_p->tagged_template_literal_cp != JMEM_CP_NULL) + { + ecma_collection_free (ECMA_GET_INTERNAL_VALUE_POINTER (ecma_collection_t, + context_p->tagged_template_literal_cp)); + } +#endif /* ENABLED (JERRY_ES2015) */ + context_p->error = error; PARSER_THROW (context_p->try_buffer); /* Should never been reached. */ diff --git a/jerry-core/parser/js/js-scanner-internal.h b/jerry-core/parser/js/js-scanner-internal.h index 6a05279f7..c2ff55213 100644 --- a/jerry-core/parser/js/js-scanner-internal.h +++ b/jerry-core/parser/js/js-scanner-internal.h @@ -100,6 +100,7 @@ typedef enum SCAN_STACK_COMPUTED_PROPERTY, /**< computed property name */ SCAN_STACK_COMPUTED_GENERATOR_FUNCTION, /**< computed property name */ SCAN_STACK_TEMPLATE_STRING, /**< template string */ + SCAN_STACK_TAGGED_TEMPLATE_LITERAL, /**< tagged template literal */ SCAN_STACK_FOR_BLOCK_END, /**< end of "for" statement with let/const declaration */ SCAN_STACK_ARROW_ARGUMENTS, /**< might be arguments of an arrow function */ SCAN_STACK_ARROW_EXPRESSION, /**< expression body of an arrow function */ diff --git a/jerry-core/parser/js/js-scanner.c b/jerry-core/parser/js/js-scanner.c index f3d15d0f5..18eec398c 100644 --- a/jerry-core/parser/js/js-scanner.c +++ b/jerry-core/parser/js/js-scanner.c @@ -613,6 +613,17 @@ scanner_scan_post_primary_expression (parser_context_t *context_p, /**< context scanner_context_p->mode = SCAN_MODE_PRIMARY_EXPRESSION; return true; } +#if ENABLED (JERRY_ES2015) + case LEXER_TEMPLATE_LITERAL: + { + if (JERRY_UNLIKELY (context_p->source_p[-1] != LIT_CHAR_GRAVE_ACCENT)) + { + scanner_context_p->mode = SCAN_MODE_PRIMARY_EXPRESSION; + parser_stack_push_uint8 (context_p, SCAN_STACK_TAGGED_TEMPLATE_LITERAL); + } + return true; + } +#endif /* ENABLED (JERRY_ES2015) */ case LEXER_LEFT_SQUARE: { parser_stack_push_uint8 (context_p, SCAN_STACK_PROPERTY_ACCESSOR); @@ -1268,6 +1279,7 @@ scanner_scan_primary_expression_end (parser_context_t *context_p, /**< context * return SCAN_KEEP_TOKEN; } case SCAN_STACK_TEMPLATE_STRING: + case SCAN_STACK_TAGGED_TEMPLATE_LITERAL: { if (type != LEXER_RIGHT_BRACE) { @@ -1276,7 +1288,7 @@ scanner_scan_primary_expression_end (parser_context_t *context_p, /**< context * context_p->source_p--; context_p->column--; - lexer_parse_string (context_p); + lexer_parse_string (context_p, LEXER_STRING_NO_OPTS); if (context_p->source_p[-1] != LIT_CHAR_GRAVE_ACCENT) { diff --git a/jerry-core/vm/vm.c b/jerry-core/vm/vm.c index a6f1a8510..0dbbf1f46 100644 --- a/jerry-core/vm/vm.c +++ b/jerry-core/vm/vm.c @@ -2098,7 +2098,40 @@ vm_loop (vm_frame_ctx_t *frame_ctx_p) /**< frame context */ goto error; } -#endif /* ENABLED (JERRY_ES2015) */ + case VM_OC_STRING_CONCAT: + { + ecma_string_t *left_str_p = ecma_op_to_string (left_value); + + if (JERRY_UNLIKELY (left_str_p == NULL)) + { + result = ECMA_VALUE_ERROR; + goto error; + } + ecma_string_t *right_str_p = ecma_op_to_string (right_value); + + if (JERRY_UNLIKELY (right_str_p == NULL)) + { + ecma_deref_ecma_string (left_str_p); + result = ECMA_VALUE_ERROR; + goto error; + } + + ecma_string_t *result_str_p = ecma_concat_ecma_strings (left_str_p, right_str_p); + ecma_deref_ecma_string (right_str_p); + + *stack_top_p++ = ecma_make_string_value (result_str_p); + goto free_both_values; + } + case VM_OC_GET_TEMPLATE_OBJECT: + { + uint8_t tagged_idx = *byte_code_p++; + ecma_collection_t *collection_p = ecma_compiled_code_get_tagged_template_collection (bytecode_header_p); + JERRY_ASSERT (tagged_idx < collection_p->item_count); + + *stack_top_p++ = ecma_copy_value (collection_p->buffer_p[tagged_idx]); + continue; + } + #endif /* ENABLED (JERRY_ES2015) */ case VM_OC_PUSH_ELISON: { *stack_top_p++ = ECMA_VALUE_ARRAY_HOLE; diff --git a/jerry-core/vm/vm.h b/jerry-core/vm/vm.h index 47b4ed156..29d98d688 100644 --- a/jerry-core/vm/vm.h +++ b/jerry-core/vm/vm.h @@ -258,6 +258,8 @@ typedef enum VM_OC_CREATE_GENERATOR, /**< create a generator object */ VM_OC_YIELD, /**< yield operation */ VM_OC_EXT_RETURN, /**< return which also clears the stack */ + VM_OC_STRING_CONCAT, /**< string concatenation */ + VM_OC_GET_TEMPLATE_OBJECT, /**< GetTemplateObject operation */ #endif /* ENABLED (JERRY_ES2015) */ VM_OC_NONE, /**< a special opcode for unsupported byte codes */ } vm_oc_types; @@ -311,6 +313,8 @@ typedef enum VM_OC_CREATE_GENERATOR = VM_OC_NONE, /**< create a generator object */ VM_OC_YIELD = VM_OC_NONE, /**< yield operation */ VM_OC_EXT_RETURN = VM_OC_NONE, /**< return which also clears the stack */ + VM_OC_STRING_CONCAT = VM_OC_NONE, /**< string concatenation */ + VM_OC_GET_TEMPLATE_OBJECT = VM_OC_NONE, /**< GetTemplateObject operation */ #endif /* !ENABLED (JERRY_ES2015) */ VM_OC_UNUSED = VM_OC_NONE /**< placeholder if the list is empty */ diff --git a/tests/jerry/es2015/string-raw.js b/tests/jerry/es2015/string-raw.js new file mode 100644 index 000000000..9f8093ad4 --- /dev/null +++ b/tests/jerry/es2015/string-raw.js @@ -0,0 +1,58 @@ +/* Copyright JS Foundation and other contributors, http://js.foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +function assertThrows(str, error) { + try { + eval(str); + assert(false); + } catch (e) { + if (typeof error === "function") { + assert(e instanceof error); + } else { + assert(e === error) + } + } +} + +// https://www.ecma-international.org/ecma-262/6.0/#sec-string.raw 21.1.2.4 +// Note: the string error messages represents the nth step of the routine +var abruptTests = [ + ["undefined", TypeError], // 3. + ["{ get raw() { throw '5'; } }", '5'], + ["{ raw : undefined }", TypeError], // 5.toObject + ["{ raw : { get length() { throw '7.1'; } } }", '7.1'], + ["{ raw : { length : { toString() { throw '7.2'; } } } }", '7.2'], + ["{ raw : { length: 2, get '0'() { throw '12.b.1'} } }", '12.b.1'], + ["{ raw : { length: 2, '0' : { toString() { throw '12.b.2';} } } }", '12.b.2'], + ["{ raw : { length: 2, '0' : 1 } }, { toString() { throw '12.h';} }", '12.h'], +]; + +abruptTests.forEach(e => { + assertThrows("String.raw(" + e[0] + ")", e[1]); +}); + +// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/raw +assert(String.raw`Hi\n${2+3}!` === 'Hi\\n5!'); +assert(String.raw`Hi\u000A!` === 'Hi\\u000A!'); + +let name = 'Bob'; +assert(String.raw`Hi\n${name}!` === "Hi\\nBob!"); + +let str = String.raw({ + raw: ['foo', 'bar', 'baz'] +}, 2 + 3, 'Java' + 'Script'); +assert(str === "foo5barJavaScriptbaz"); + +assert(String.raw({ raw: 'test' }, 0, 1, 2) === "t0e1s2t"); diff --git a/tests/jerry/es2015/symbol-prototype.toprimitive.js b/tests/jerry/es2015/symbol-prototype.toprimitive.js index 31fbe525d..f174e844e 100644 --- a/tests/jerry/es2015/symbol-prototype.toprimitive.js +++ b/tests/jerry/es2015/symbol-prototype.toprimitive.js @@ -24,7 +24,7 @@ var obj2 = { return true; } }; - + assert(+obj2 === 10); - //assert(`${obj2}` === "hello"); //FIXME: template literals requires String hint during concatenation + assert(`${obj2}` === "hello"); assert(obj2 + '' === "true"); diff --git a/tests/jerry/es2015/tagged-template-literal.js b/tests/jerry/es2015/tagged-template-literal.js new file mode 100644 index 000000000..07d8b5867 --- /dev/null +++ b/tests/jerry/es2015/tagged-template-literal.js @@ -0,0 +1,128 @@ +/* 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. + */ + +// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals +// Tagged templates +var person = 'Mike'; +var age = 28; + +function myTag(strings, personExp, ageExp) { + assert(strings[0] == "That "); + assert(strings[1] == " is a "); + var str0 = strings[0]; + var str1 = strings[1]; + + var ageStr; + if (ageExp > 99){ + ageStr = 'centenarian'; + } else { + ageStr = 'youngster'; + } + + return `${str0}${personExp}${str1}${ageStr}`; +} + +var output = myTag`That ${ person } is a ${ age }`; +assert(output === "That Mike is a youngster"); + +function template(strings, ...keys) { + return (function(...values) { + var dict = values[values.length - 1] || {}; + var result = [strings[0]]; + keys.forEach(function(key, i) { + var value = Number.isInteger(key) ? values[key] : dict[key]; + result.push(value, strings[i + 1]); + }); + return result.join(''); + }); +} + +var t1Closure = template`${0}${1}${0}!`; +assert(t1Closure('Y', 'A') === "YAY!"); +var t2Closure = template`${0} ${'foo'}!`; +assert(t2Closure('Hello', {foo: 'World'}) === "Hello World!"); + +// Raw strings +(function () { + function tag(strings) { + assert(strings.raw[0].length === 40); + } + + tag`string text line 1 \n string text line 2`; +})(); + +assert (String.raw`Hi\n${2+3}!` === "Hi\\n5!"); + +(function () { + function empty(strings, ...params) { + assert(strings.length === 4); + assert(strings.raw.length === 4); + assert(params.length === 3); + strings.forEach ((e) => assert (e === "")); + strings.raw.forEach ((e) => assert (e === "")); + params.forEach ((e) => assert (e === 1)); + } + + empty`${1}${1}${1}`; +})(); + +(function () { + function f (str) { + return str.raw[0].length; + } + assert (eval("f`a\u2029b`") === 3); +})(); + +(function () { + function testRaw(parts, a, b) { + assert(parts instanceof Array); + assert(parts.raw instanceof Array); + assert(parts.length === 3); + assert(parts[0] === "str"); + assert(parts[1] === "escaped\n"); + assert(parts[2] === ""); + assert(parts.raw.length === 3); + assert(parts.raw[0] === "str"); + assert(parts.raw[1] === "escaped\\n"); + assert(parts.raw[2] === ""); + assert(a === 123); + assert(b === 456); + return true; + } + + assert(testRaw `str${123}escaped\n${456}` === true); +})(); + +// TemplateStrings call site caching +(function () { + let str = (arr) => arr; + function getStr() { + return str`foo`; + } + var chainedCall = getStr(); + var localCall = str`foo`; + assert(chainedCall === getStr() && chainedCall !== localCall); +})(); + +// TemplateStrings permanent caching +(function () { + let str = (arr) => arr; + function getStr() { + return str`foo`; + } + var chainedCall = getStr(); + var localNew = new getStr(); + assert(chainedCall === getStr() && chainedCall === localNew); +})(); diff --git a/tests/jerry/es2015/template_string.js b/tests/jerry/es2015/template_string.js index fc5c28610..6f6a3c3b0 100644 --- a/tests/jerry/es2015/template_string.js +++ b/tests/jerry/es2015/template_string.js @@ -41,9 +41,9 @@ switch (1) { default: - `` - `abc` - `ab${a+b}${ `x` }c` + ``; + `abc`; + `ab${a+b}${ `x` }c`; assert (`` === ''); assert (`abc` === 'abc'); @@ -90,3 +90,15 @@ must_throw ("`${}`"); must_throw ("`${1}"); must_throw ("`${1}.${"); must_throw ("`${1}.${2}"); + +// line break normalization +var cr = eval("`a" + String.fromCharCode(13) + "b`"); +var lf = eval("`a" + String.fromCharCode(10) + "b`"); +var crlf = eval("`a" + String.fromCharCode(13,10) + "b`"); + +assert(cr.length === 3); +assert(lf.length === 3); +assert(crlf.length === 3); +assert(cr[1] === lf[1]); +assert(lf[1] === crlf[1]); +assert(crlf[1] === '\n'); diff --git a/tests/unit-core/test-snapshot.c b/tests/unit-core/test-snapshot.c index 750870551..b599266bd 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, 0x22, 0x00, 0x00, 0x00, + 0x4A, 0x52, 0x52, 0x59, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x03, 0x00, 0x01, 0x00, 0x41, 0x00, 0x01, 0x00,