Berry multiplication between string and int (#23850)

This commit is contained in:
s-hadinger 2025-08-29 11:41:08 +02:00 committed by GitHub
parent 9426df28f9
commit 5a08bc11e3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 182 additions and 7 deletions

View File

@ -181,6 +181,19 @@ print(i) # Only i is accessible here
- `/`: Division - `/`: Division
- `%`: Modulo (remainder) - `%`: 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 #### Relational Operators
- `<`: Less than - `<`: Less than
- `<=`: Less than or equal to - `<=`: Less than or equal to

View File

@ -7,6 +7,7 @@ All notable changes to this project will be documented in this file.
### Added ### Added
- ESP32 ROM SHA Hardware Acceleration to BearSSL (#23819) - ESP32 ROM SHA Hardware Acceleration to BearSSL (#23819)
- Extend state JSON message with functional hostname and ipaddress which could be WiFi or Ethernet - Extend state JSON message with functional hostname and ipaddress which could be WiFi or Ethernet
- Berry multiplication between string and int
### Breaking Changed ### Breaking Changed

View File

@ -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) int be_strcmp(bstring *s1, bstring *s2)
{ {
if (be_eqstr(s1, s2)) { if (be_eqstr(s1, s2)) {

View File

@ -16,6 +16,7 @@ extern "C" {
#endif #endif
bstring* be_strcat(bvm *vm, bstring *s1, bstring *s2); 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); int be_strcmp(bstring *s1, bstring *s2);
bstring* be_num2str(bvm *vm, bvalue *v); bstring* be_num2str(bvm *vm, bvalue *v);
void be_val2str(bvm *vm, int index); void be_val2str(bvm *vm, int index);

View File

@ -463,6 +463,25 @@ static void make_range(bvm *vm, bvalue lower, bvalue upper)
vm->top -= 3; 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) static void connect_str(bvm *vm, bstring *a, bvalue *b)
{ {
bstring *s; bstring *s;
@ -705,6 +724,10 @@ newframe: /* a new call frame */
breal x = var2real(a), y = var2real(b); breal x = var2real(a), y = var2real(b);
var_setreal(dst, x * y); var_setreal(dst, x * y);
#endif // CONFIG_IDF_TARGET_ESP32 #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)) { } else if (var_isinstance(a)) {
ins_binop(vm, "*", ins); ins_binop(vm, "*", ins);
} else { } else {

View File

@ -175,13 +175,6 @@ assert(f"S = {a}" == 'S = foobar{0}')
assert(f"S = {a:i}" == 'S = 0') assert(f"S = {a:i}" == 'S = 0')
assert(f"{a=}" == 'a=foobar{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 # startswith case sensitive
assert(string.startswith("", "") == true) assert(string.startswith("", "") == true)
assert(string.startswith("qwerty", "qw") == 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", "TY", true) == true)
assert(string.endswith("qwerty", "tr", true) == false) assert(string.endswith("qwerty", "tr", true) == false)
assert(string.endswith("qwerty", "qwertyw", 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)