From 5a08bc11e3e2b9c7e9252cf84bb99cf0833a47e9 Mon Sep 17 00:00:00 2001 From: s-hadinger <49731213+s-hadinger@users.noreply.github.com> Date: Fri, 29 Aug 2025 11:41:08 +0200 Subject: [PATCH] Berry multiplication between string and int (#23850) --- .doc_for_ai/BERRY_LANGUAGE_REFERENCE.md | 13 +++ CHANGELOG.md | 1 + lib/libesp32/berry/src/be_strlib.c | 44 ++++++++++ lib/libesp32/berry/src/be_strlib.h | 1 + lib/libesp32/berry/src/be_vm.c | 23 +++++ lib/libesp32/berry/tests/string.be | 107 ++++++++++++++++++++++-- 6 files changed, 182 insertions(+), 7 deletions(-) diff --git a/.doc_for_ai/BERRY_LANGUAGE_REFERENCE.md b/.doc_for_ai/BERRY_LANGUAGE_REFERENCE.md index 9023af055..0ff518bef 100644 --- a/.doc_for_ai/BERRY_LANGUAGE_REFERENCE.md +++ b/.doc_for_ai/BERRY_LANGUAGE_REFERENCE.md @@ -181,6 +181,19 @@ print(i) # Only i is accessible here - `/`: Division - `%`: Modulo (remainder) +**String Multiplication**: The `*` operator supports string multiplication when the left operand is a string and the right operand is an integer or boolean: + +```berry +"aze" * 3 # "azeazeaze" - repeat string 3 times +"aze" * 0 # "" - empty string +"aze" * true # "aze" - string if true +"aze" * false # "" - empty string if false + +# Common use cases: +" " * indent # Create indentation spaces +f"{n} time{'s' * bool(n >= 2)}" # Conditional pluralization +``` + #### Relational Operators - `<`: Less than - `<=`: Less than or equal to diff --git a/CHANGELOG.md b/CHANGELOG.md index 58a43e39c..a57976bd2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ All notable changes to this project will be documented in this file. ### Added - ESP32 ROM SHA Hardware Acceleration to BearSSL (#23819) - Extend state JSON message with functional hostname and ipaddress which could be WiFi or Ethernet +- Berry multiplication between string and int ### Breaking Changed diff --git a/lib/libesp32/berry/src/be_strlib.c b/lib/libesp32/berry/src/be_strlib.c index bc029ee4b..676a1ec04 100644 --- a/lib/libesp32/berry/src/be_strlib.c +++ b/lib/libesp32/berry/src/be_strlib.c @@ -77,6 +77,50 @@ bstring* be_strcat(bvm *vm, bstring *s1, bstring *s2) } } +bstring* be_strmul(bvm *vm, bstring *s1, bint n) +{ + /* Handle edge cases */ + if (n == 1) { + return s1; + } + + size_t string_len = (size_t)str_len(s1); + if ((n <= 0) || (string_len == 0)) { + return be_newstrn(vm, "", 0); + } + + /* Check for potential overflow */ + if (n > (bint)(vm->bytesmaxsize / string_len)) { + be_raise(vm, "runtime_error", "string multiplication result too large"); + } + + size_t total_len = string_len * (size_t)n; + + /* Use the same pattern as be_strcat for short vs long strings */ + if (total_len <= SHORT_STR_MAX_LEN) { + char buf[SHORT_STR_MAX_LEN + 1]; + const char *src = str(s1); + char *dst = buf; + + for (bint i = 0; i < n; i++) { + memcpy(dst, src, string_len); + dst += string_len; + } + buf[total_len] = '\0'; + return be_newstrn(vm, buf, total_len); + } else { + /* Long string */ + bstring *result = be_newstrn(vm, NULL, total_len); + char *dst = (char*)str(result); + const char *src = str(s1); + + for (bint i = 0; i < n; i++) { + memcpy(dst + i * string_len, src, string_len); + } + return result; + } +} + int be_strcmp(bstring *s1, bstring *s2) { if (be_eqstr(s1, s2)) { diff --git a/lib/libesp32/berry/src/be_strlib.h b/lib/libesp32/berry/src/be_strlib.h index 093c37c63..2cd8fca33 100644 --- a/lib/libesp32/berry/src/be_strlib.h +++ b/lib/libesp32/berry/src/be_strlib.h @@ -16,6 +16,7 @@ extern "C" { #endif bstring* be_strcat(bvm *vm, bstring *s1, bstring *s2); +bstring* be_strmul(bvm *vm, bstring *s1, bint n); int be_strcmp(bstring *s1, bstring *s2); bstring* be_num2str(bvm *vm, bvalue *v); void be_val2str(bvm *vm, int index); diff --git a/lib/libesp32/berry/src/be_vm.c b/lib/libesp32/berry/src/be_vm.c index 1e441ffa7..9a2c5a204 100644 --- a/lib/libesp32/berry/src/be_vm.c +++ b/lib/libesp32/berry/src/be_vm.c @@ -463,6 +463,25 @@ static void make_range(bvm *vm, bvalue lower, bvalue upper) vm->top -= 3; } +static void multiply_str(bvm *vm, bvalue *a_value, bvalue *count) +{ + bint n = 0; + bstring *result; + bstring *str = var_tostr(a_value); + + /* Convert count to integer */ + if (var_isint(count)) { + n = var_toint(count); + } else if (var_isbool(count)) { + n = var_tobool(count) ? 1 : 0; + } else { + binop_error(vm, "*", a_value, count); + } + + result = be_strmul(vm, str, n); + var_setstr(vm->top, result); +} + static void connect_str(bvm *vm, bstring *a, bvalue *b) { bstring *s; @@ -705,6 +724,10 @@ newframe: /* a new call frame */ breal x = var2real(a), y = var2real(b); var_setreal(dst, x * y); #endif // CONFIG_IDF_TARGET_ESP32 + } else if (var_isstr(a) && (var_isint(b) || var_isbool(b))) { + multiply_str(vm, a, b); + reg = vm->reg; + *RA() = *vm->top; /* copy result to R(A) */ } else if (var_isinstance(a)) { ins_binop(vm, "*", ins); } else { diff --git a/lib/libesp32/berry/tests/string.be b/lib/libesp32/berry/tests/string.be index 61d323aee..c5f6c0b33 100644 --- a/lib/libesp32/berry/tests/string.be +++ b/lib/libesp32/berry/tests/string.be @@ -175,13 +175,6 @@ assert(f"S = {a}" == 'S = foobar{0}') assert(f"S = {a:i}" == 'S = 0') assert(f"{a=}" == 'a=foobar{0}') -# new option for f-strings, '::' encodes for ':' -assert(f"{true ? 1 :: 2}" == "1") -assert(f"{false ? 1 :: 2}" == "2") -assert(f"{false ? 1 :: 2 : i}" == " 2") -assert(f"{false ? 1 :: 2:i}" == "2") -assert(f"{false ? 1 :: 2:04i}" == "0002") - # startswith case sensitive assert(string.startswith("", "") == true) assert(string.startswith("qwerty", "qw") == true) @@ -220,3 +213,103 @@ 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) + +# unicode literals +assert("\uF014" == "\xEF\x80\x94") + +# string multiplication tests +# Basic integer multiplication +assert("aze" * 3 == "azeazeaze") +assert("ab" * 5 == "ababababab") +assert("x" * 1 == "x") +assert("hello" * 2 == "hellohello") + +# Zero and negative multiplication +assert("aze" * 0 == "") +assert("hello" * -1 == "") +assert("test" * -5 == "") + +# Boolean multiplication +assert("aze" * true == "aze") +assert("aze" * false == "") +assert("hello" * true == "hello") +assert("world" * false == "") + +# Empty string multiplication +assert("" * 0 == "") +assert("" * 1 == "") +assert("" * 5 == "") +assert("" * 100 == "") +assert("" * true == "") +assert("" * false == "") + +# Single character multiplication +assert("a" * 10 == "aaaaaaaaaa") +assert("z" * 0 == "") +assert("!" * 3 == "!!!") + +# Large multiplication (testing performance and memory) +var large_result = "abc" * 20 +assert(size(large_result) == 60) +assert(large_result == "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc") + +# Edge cases with special characters +assert("\n" * 3 == "\n\n\n") +assert("\t" * 2 == "\t\t") +assert("\"" * 4 == "\"\"\"\"") +assert("\\" * 3 == "\\\\\\") + +# Verify that regular multiplication still works +assert(3 * 4 == 12) +assert(2.5 * 4 == 10) +assert(5 * 3 == 15) +assert(0 * 10 == 0) + +# Test that invalid combinations still throw errors +try + var result = "hello" * "world" + assert(false, "Should have thrown an error") +except 'type_error' + # Expected error +end + +try + var result = "hello" * 3.14 + assert(false, "Should have thrown an error") +except 'type_error' + # Expected error +end + +try + var result = "hello" * nil + assert(false, "Should have thrown an error") +except 'type_error' + # Expected error +end + +# Test with variables +var s = "test" +var count = 3 +assert(s * count == "testtesttest") + +var bool_true = true +var bool_false = false +assert(s * bool_true == "test") +assert(s * bool_false == "") + +# Test chaining and expressions +assert(("a" * 2) + ("b" * 3) == "aabbb") +assert("x" * (1 + 2) == "xxx") +assert("y" * (true && true) == "y") +assert("z" * (false || false) == "") + +# Test with longer strings +var long_str = "Hello, World!" +assert(long_str * 0 == "") +assert(long_str * 1 == "Hello, World!") +assert(long_str * 2 == "Hello, World!Hello, World!") + +# Test boundary conditions +assert("a" * 64 == "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") # SHORT_STR_MAX_LEN +var very_long = "a" * 100 # Should use long string path +assert(size(very_long) == 100)