From 2f2a4e066cb3001b883685c6d88dc6aa047d9942 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?T=C3=B3th=20B=C3=A9la?= Date: Fri, 24 Jul 2020 11:24:18 +0200 Subject: [PATCH] Implement String.padStart and String.padEnd (#3999) Based on: https://tc39.es/ecma262/#sec-string.prototype.padstart JerryScript-DCO-1.0-Signed-off-by: Bela Toth tbela@inf.u-szeged.hu --- jerry-core/ecma/base/ecma-helpers-string.c | 91 +++++++++++++++++++ jerry-core/ecma/base/ecma-helpers.h | 6 ++ .../ecma-builtin-string-prototype.c | 8 ++ .../ecma-builtin-string-prototype.inc.h | 2 + jerry-core/lit/lit-magic-strings.inc.h | 6 ++ jerry-core/lit/lit-magic-strings.ini | 2 + .../jerry/es.next/string-prototype-padding.js | 81 +++++++++++++++++ 7 files changed, 196 insertions(+) create mode 100644 tests/jerry/es.next/string-prototype-padding.js diff --git a/jerry-core/ecma/base/ecma-helpers-string.c b/jerry-core/ecma/base/ecma-helpers-string.c index 67a8cfba5..1d1d19877 100644 --- a/jerry-core/ecma/base/ecma-helpers-string.c +++ b/jerry-core/ecma/base/ecma-helpers-string.c @@ -2482,6 +2482,97 @@ ecma_string_trim (const ecma_string_t *string_p) /**< pointer to an ecma string return ret_string_p; } /* ecma_string_trim */ +#if ENABLED (JERRY_ESNEXT) + +/** + * Pad the beginning or the end of string with parameter given in fill_string to the length of max_length. + * + * @return new string from original, padded with given parameters + */ +ecma_value_t +ecma_string_pad (ecma_value_t original_string_p, /**< Input ecma string */ + ecma_value_t max_length, /**< Length to pad to, including original length */ + ecma_value_t fill_string, /**< The string to pad with */ + bool pad_on_start) /**< true - if we are padding to the start, calling with padStart + false - if we are padding to the end, calling with padEnd */ +{ + + /* 3 */ + uint32_t int_max_length; + if (ECMA_IS_VALUE_ERROR (ecma_op_to_length (max_length, &int_max_length))) + { + return ECMA_VALUE_ERROR; + } + /* 4 */ + ecma_string_t *original_str_val_p = ecma_get_string_from_value (original_string_p); + const uint32_t string_length = ecma_string_get_length (original_str_val_p); + /* 5 */ + if (int_max_length <= string_length) + { + ecma_ref_ecma_string (original_str_val_p); + return original_string_p; + } + + ecma_string_t *filler_p = ecma_get_magic_string (LIT_MAGIC_STRING_SPACE_CHAR); + /* 6 - 7 */ + if (!ecma_is_value_undefined (fill_string)) + { + filler_p = ecma_op_to_string (fill_string); + if (filler_p == NULL) + { + return ECMA_VALUE_ERROR; + } + if (ecma_string_is_empty (filler_p)) + { + ecma_ref_ecma_string (original_str_val_p); + return original_string_p; + } + } + + /* 9 */ + uint32_t fill_len = int_max_length - string_length; + + /* 10 */ + uint32_t filler_length = ecma_string_get_length (filler_p); + uint32_t prepend_count = fill_len / filler_length; + ecma_stringbuilder_t builder = ecma_stringbuilder_create (); + + if (!pad_on_start) + { + ecma_stringbuilder_append (&builder, original_str_val_p); + } + + for (uint32_t i = 0; i < prepend_count; i++) + { + ecma_stringbuilder_append (&builder, filler_p); + } + + lit_utf8_size_t read_size; + ecma_char_t ch; + + uint32_t remaining = fill_len - (prepend_count * filler_length); + + ECMA_STRING_TO_UTF8_STRING (filler_p, start_p, utf8_str_size); + while (remaining > 0) + { + read_size = lit_read_code_unit_from_utf8 (start_p, &ch); + ecma_stringbuilder_append_char (&builder, ch); + start_p += read_size; + remaining--; + } + ECMA_FINALIZE_UTF8_STRING (start_p, utf8_str_size); + ecma_deref_ecma_string (filler_p); + + /* 11 - 12 */ + if (pad_on_start) + { + ecma_stringbuilder_append (&builder, original_str_val_p); + } + + return ecma_make_string_value (ecma_stringbuilder_finalize (&builder)); +} /* ecma_string_pad */ +#endif /* ENABLED (JERRY_ESNEXT) */ + /** * Create an empty string builder * diff --git a/jerry-core/ecma/base/ecma-helpers.h b/jerry-core/ecma/base/ecma-helpers.h index 922e2a8ec..6b3e28a94 100644 --- a/jerry-core/ecma/base/ecma-helpers.h +++ b/jerry-core/ecma/base/ecma-helpers.h @@ -382,6 +382,12 @@ ecma_string_t *ecma_string_substr (const ecma_string_t *string_p, lit_utf8_size_ void ecma_string_trim_helper (const lit_utf8_byte_t **utf8_str_p, lit_utf8_size_t *utf8_str_size); ecma_string_t *ecma_string_trim (const ecma_string_t *string_p); +#if ENABLED (JERRY_ESNEXT) +ecma_value_t ecma_string_pad (ecma_value_t original_string_p, + ecma_value_t max_length, + ecma_value_t fill_string, + bool pad_on_start); +#endif /* ENABLED (JERRY_ESNEXT) */ ecma_stringbuilder_t ecma_stringbuilder_create (void); ecma_stringbuilder_t ecma_stringbuilder_create_from (ecma_string_t *string_p); diff --git a/jerry-core/ecma/builtin-objects/ecma-builtin-string-prototype.c b/jerry-core/ecma/builtin-objects/ecma-builtin-string-prototype.c index ed1b4444f..9bb1cf57b 100644 --- a/jerry-core/ecma/builtin-objects/ecma-builtin-string-prototype.c +++ b/jerry-core/ecma/builtin-objects/ecma-builtin-string-prototype.c @@ -80,6 +80,8 @@ enum ECMA_STRING_PROTOTYPE_REPEAT, ECMA_STRING_PROTOTYPE_CODE_POINT_AT, + ECMA_STRING_PROTOTYPE_PAD_START, + ECMA_STRING_PROTOTYPE_PAD_END, /* Note: These 5 routines MUST be in this order */ ECMA_STRING_PROTOTYPE_LAST_INDEX_OF, ECMA_STRING_PROTOTYPE_INDEX_OF, @@ -1388,6 +1390,12 @@ ecma_builtin_string_prototype_dispatch_routine (uint16_t builtin_routine_id, /** ret_value = ecma_builtin_string_prototype_object_iterator (to_string_val); break; } + case ECMA_STRING_PROTOTYPE_PAD_END: + case ECMA_STRING_PROTOTYPE_PAD_START: + { + ret_value = ecma_string_pad (to_string_val, arg1, arg2, builtin_routine_id == ECMA_STRING_PROTOTYPE_PAD_START); + break; + } #endif /* ENABLED (JERRY_ESNEXT) */ default: { diff --git a/jerry-core/ecma/builtin-objects/ecma-builtin-string-prototype.inc.h b/jerry-core/ecma/builtin-objects/ecma-builtin-string-prototype.inc.h index ad0f1fca8..f3e685b4d 100644 --- a/jerry-core/ecma/builtin-objects/ecma-builtin-string-prototype.inc.h +++ b/jerry-core/ecma/builtin-objects/ecma-builtin-string-prototype.inc.h @@ -73,6 +73,8 @@ ROUTINE (LIT_MAGIC_STRING_STARTS_WITH, ECMA_STRING_PROTOTYPE_STARTS_WITH, 2, 1) ROUTINE (LIT_MAGIC_STRING_INCLUDES, ECMA_STRING_PROTOTYPE_INCLUDES, 2, 1) ROUTINE (LIT_MAGIC_STRING_ENDS_WITH, ECMA_STRING_PROTOTYPE_ENDS_WITH, 2, 1) ROUTINE (LIT_MAGIC_STRING_CODE_POINT_AT, ECMA_STRING_PROTOTYPE_CODE_POINT_AT, 1, 1) +ROUTINE (LIT_MAGIC_STRING_PAD_START, ECMA_STRING_PROTOTYPE_PAD_START, 2, 1) +ROUTINE (LIT_MAGIC_STRING_PAD_END, ECMA_STRING_PROTOTYPE_PAD_END, 2, 1) ROUTINE (LIT_GLOBAL_SYMBOL_ITERATOR, ECMA_STRING_PROTOTYPE_ITERATOR, 0, 0) #endif /* ENABLED (JERRY_ESNEXT) */ diff --git a/jerry-core/lit/lit-magic-strings.inc.h b/jerry-core/lit/lit-magic-strings.inc.h index 0befa04cb..246108a53 100644 --- a/jerry-core/lit/lit-magic-strings.inc.h +++ b/jerry-core/lit/lit-magic-strings.inc.h @@ -366,6 +366,9 @@ LIT_MAGIC_STRING_DEF (LIT_MAGIC_STRING_KEY_FOR, "keyFor") LIT_MAGIC_STRING_DEF (LIT_MAGIC_STRING_LENGTH, "length") LIT_MAGIC_STRING_DEF (LIT_MAGIC_STRING_NUMBER, "number") LIT_MAGIC_STRING_DEF (LIT_MAGIC_STRING_OBJECT, "object") +#if ENABLED (JERRY_BUILTIN_STRING) && ENABLED (JERRY_ESNEXT) +LIT_MAGIC_STRING_DEF (LIT_MAGIC_STRING_PAD_END, "padEnd") +#endif #if ENABLED (JERRY_BUILTIN_MATH) LIT_MAGIC_STRING_DEF (LIT_MAGIC_STRING_RANDOM, "random") #endif @@ -555,6 +558,9 @@ LIT_MAGIC_STRING_DEF (LIT_MAGIC_STRING_IS_SEALED_UL, "isSealed") #if ENABLED (JERRY_ESNEXT) LIT_MAGIC_STRING_DEF (LIT_MAGIC_STRING_ITERATOR, "iterator") #endif +#if ENABLED (JERRY_BUILTIN_STRING) && ENABLED (JERRY_ESNEXT) +LIT_MAGIC_STRING_DEF (LIT_MAGIC_STRING_PAD_START, "padStart") +#endif #if ENABLED (JERRY_ESNEXT) \ || !( ENABLED (JERRY_ESNEXT)) LIT_MAGIC_STRING_DEF (LIT_MAGIC_STRING_PARSE_INT, "parseInt") diff --git a/jerry-core/lit/lit-magic-strings.ini b/jerry-core/lit/lit-magic-strings.ini index ac2019225..2578d09bb 100644 --- a/jerry-core/lit/lit-magic-strings.ini +++ b/jerry-core/lit/lit-magic-strings.ini @@ -157,6 +157,7 @@ LIT_MAGIC_STRING_KEY_FOR = "keyFor" LIT_MAGIC_STRING_LENGTH = "length" LIT_MAGIC_STRING_NUMBER = "number" LIT_MAGIC_STRING_OBJECT = "object" +LIT_MAGIC_STRING_PAD_END = "padEnd" LIT_MAGIC_STRING_RANDOM = "random" LIT_MAGIC_STRING_REDUCE = "reduce" LIT_MAGIC_STRING_REJECT = "reject" @@ -189,6 +190,7 @@ LIT_MAGIC_STRING_INDEX_OF_UL = "indexOf" LIT_MAGIC_STRING_IS_ARRAY_UL = "isArray" LIT_MAGIC_STRING_MESSAGE = "message" LIT_MAGIC_STRING_OWN_KEYS_UL = "ownKeys" +LIT_MAGIC_STRING_PAD_START = "padStart" LIT_MAGIC_STRING_REPLACE = "replace" LIT_MAGIC_STRING_RESOLVE = "resolve" LIT_MAGIC_STRING_REVERSE = "reverse" diff --git a/tests/jerry/es.next/string-prototype-padding.js b/tests/jerry/es.next/string-prototype-padding.js new file mode 100644 index 000000000..bb84196fc --- /dev/null +++ b/tests/jerry/es.next/string-prototype-padding.js @@ -0,0 +1,81 @@ +/* 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. + */ + +var test = "bar" + +assert(test.padStart("5", "foo") === "fobar") +assert(test.padStart(6, "foo") === "foobar") +assert(test.padEnd(5, "baz") === "barba") +assert(test.padEnd(6, "baz") === "barbaz") + +// Check for negative value +assert(test.padStart(-5, "foo") === "bar") +assert(test.padEnd(-5, "foo") === "bar") + +// 21.1.3.15.1.6 +assert(test.padStart(10) === " bar") +assert(test.padEnd(10) === "bar ") + +// Empty FilString +assert(test.padEnd(10, "") === "bar") +assert(test.padStart(10, "") === "bar") + +// Check with unicode surrogate characters +// 𐋀 = [55296, 57024] +var unicode_padded = "a".padStart(4 ,"𐋀") +assert(unicode_padded.charCodeAt(0) == 55296) +assert(unicode_padded.charCodeAt(1) == 57024) +assert(unicode_padded.charCodeAt(2) == 55296) +assert(unicode_padded.charCodeAt(3) == 97) + +unicode_padded = "a".padEnd(4 ,"𐋀") +assert(unicode_padded.charCodeAt(0) == 97) +assert(unicode_padded.charCodeAt(1) == 55296) +assert(unicode_padded.charCodeAt(2) == 57024) +assert(unicode_padded.charCodeAt(3) == 55296) + +// 𐋂 = [55296, 57026] +unicode_padded = "𐋂".padStart(4 ,"𐋀") +assert(unicode_padded.charCodeAt(0) == 55296) +assert(unicode_padded.charCodeAt(1) == 57024) +assert(unicode_padded.charCodeAt(2) == 55296) +assert(unicode_padded.charCodeAt(3) == 57026) + +// Check for errors in length +try { + test.padStart(Symbol("Will this fail?"), "It should" ) + assert(false); +} catch (e) { + assert(e instanceof TypeError); +} +try { + test.padEnd(Symbol("Will this fail?"), "It should" ) + assert(false); +} catch (e) { + assert(e instanceof TypeError); +} +// Check for errors in fillString +try { + test.padStart(10, Symbol("Fail, this should. " )) + assert(false); +} catch (e) { + assert(e instanceof TypeError); +} +try { + test.padEnd(10, Symbol("Fail, this should. " )) + assert(false); +} catch (e) { + assert(e instanceof TypeError); +}