diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b904a36d..5d0fc1eda 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ All notable changes to this project will be documented in this file. - HASPmota support for spangroup (styled text) (#20852) - HASPmota support for led (#20857) - HASPmota improve arc and img (#20894) +- Berry add `string.starstwith`, `string.endswith` and `%q` format ### Breaking Changed - Drop support for old (insecure) fingerprint format (#20842) diff --git a/lib/libesp32/berry/src/be_strlib.c b/lib/libesp32/berry/src/be_strlib.c index a52b8b670..308906125 100644 --- a/lib/libesp32/berry/src/be_strlib.c +++ b/lib/libesp32/berry/src/be_strlib.c @@ -669,6 +669,17 @@ int be_str_format(bvm *vm) } break; } + case 'q': { + const char *s = be_toescape(vm, index, 'q'); + int len = be_strlen(vm, index); + if (len > 100 && strlen(mode) == 2) { + be_pushvalue(vm, index); + } else { + snprintf(buf, sizeof(buf), "%s", s); + be_pushstring(vm, buf); + } + break; + } default: /* error */ be_raise(vm, "runtime_error", be_pushfstring(vm, "invalid option '%%%c' to 'format'", *p)); @@ -940,6 +951,60 @@ static int str_escape(bvm *vm) be_return_nil(vm); } +static int str_startswith(bvm *vm) +{ + int top = be_top(vm); + if (top >= 2 && be_isstring(vm, 1) && be_isstring(vm, 2)) { + bbool case_insensitive = bfalse; + if (top >= 3 && be_isbool(vm, 3)) { + case_insensitive = be_tobool(vm, 3); + } + bbool result = bfalse; + const char *s = be_tostring(vm, 1); + const char *p = be_tostring(vm, 2); + size_t len = (size_t)be_strlen(vm, 2); + if (case_insensitive) { + if (strncasecmp(s, p, len) == 0) { + result = btrue; + } + } else { + if (strncmp(s, p, len) == 0) { + result = btrue; + } + } + be_pushbool(vm, result); + be_return(vm); + } + be_return_nil(vm); +} + +static int str_endswith(bvm *vm) +{ + int top = be_top(vm); + if (top >= 2 && be_isstring(vm, 1) && be_isstring(vm, 2)) { + bbool case_insensitive = bfalse; + if (top >= 3 && be_isbool(vm, 3)) { + case_insensitive = be_tobool(vm, 3); + } + bbool result = bfalse; + const char *s = be_tostring(vm, 1); + const char *p = be_tostring(vm, 2); + size_t len = (size_t)be_strlen(vm, 2); + if (case_insensitive) { + if (strncasecmp(s + (int)strlen(s) - (int)len, p, len) == 0) { + result = btrue; + } + } else { + if (strncmp(s + (int)strlen(s) - (int)len, p, len) == 0) { + result = btrue; + } + } + be_pushbool(vm, result); + be_return(vm); + } + be_return_nil(vm); +} + #if !BE_USE_PRECOMPILED_OBJECT be_native_module_attr_table(string) { be_native_module_function("format", be_str_format), @@ -954,6 +1019,8 @@ be_native_module_attr_table(string) { be_native_module_function("tr", str_tr), be_native_module_function("escape", str_escape), be_native_module_function("replace", str_replace), + be_native_module_function("startswith", str_startswith), + be_native_module_function("endswith", str_endswith), }; be_define_native_module(string, NULL); @@ -972,6 +1039,8 @@ module string (scope: global, depend: BE_USE_STRING_MODULE) { tr, func(str_tr) escape, func(str_escape) replace, func(str_replace) + startswith, func(str_startswith) + endswith, func(str_endswith) } @const_object_info_end */ #include "../generate/be_fixed_string.h" diff --git a/lib/libesp32/berry/tests/string.be b/lib/libesp32/berry/tests/string.be index 82f824451..15b403bb5 100644 --- a/lib/libesp32/berry/tests/string.be +++ b/lib/libesp32/berry/tests/string.be @@ -147,6 +147,8 @@ assert(string.format("%s", nil) == 'nil') assert(string.format("%s", true) == 'true') assert(string.format("%s", false) == 'false') +assert(string.format("%q", "\ntest") == '\'\\ntest\'') + # format is now synonym to string.format assert(format == string.format) assert(format("%.1f", 3) == '3.0') @@ -169,3 +171,42 @@ var a = 'foobar{0}' assert(f"S = {a}" == 'S = foobar{0}') assert(f"S = {a:i}" == 'S = 0') assert(f"{a=}" == 'a=foobar{0}') + +# startswith case sensitive +assert(string.startswith("", "") == true) +assert(string.startswith("qwerty", "qw") == true) +assert(string.startswith("qwerty", "qwerty") == true) +assert(string.startswith("qwerty", "") == true) +assert(string.startswith("qwerty", "qW") == false) +assert(string.startswith("qwerty", "QW") == false) +assert(string.startswith("qwerty", "qz") == false) +assert(string.startswith("qwerty", "qwertyw") == false) + +# startswith case insensitive +assert(string.startswith("qwerty", "qw", true) == true) +assert(string.startswith("qwerty", "qwerty", true) == true) +assert(string.startswith("qwerty", "", true) == true) +assert(string.startswith("qwerty", "qW", true) == true) +assert(string.startswith("qwerty", "QW", true) == true) +assert(string.startswith("qwerty", "qz", true) == false) +assert(string.startswith("qwerty", "qwertyw", true) == false) + +# endswith case sensitive +assert(string.endswith("", "") == true) +assert(string.endswith("qwerty", "ty") == true) +assert(string.endswith("qwerty", "qwerty") == true) +assert(string.endswith("qwerty", "") == true) +assert(string.endswith("qwerty", "tY") == false) +assert(string.endswith("qwerty", "TY") == false) +assert(string.endswith("qwerty", "tr") == false) +assert(string.endswith("qwerty", "qwertyw") == false) + +# endswith case insensitive +assert(string.endswith("", "", true) == true) +assert(string.endswith("qwerty", "ty", true) == true) +assert(string.endswith("qwerty", "qwerty", true) == true) +assert(string.endswith("qwerty", "", true) == true) +assert(string.endswith("qwerty", "tY", true) == true) +assert(string.endswith("qwerty", "TY", true) == true) +assert(string.endswith("qwerty", "tr", true) == false) +assert(string.endswith("qwerty", "qwertyw", true) == false)