Skip to content

Commit eb45d97

Browse files
glenn20dpgeorge
authored andcommitted
py/objstr: Support tuples and start/end args in startswith and endswith.
This change allows tuples to be passed as the prefix/suffix argument to the `str.startswith()` and `str.endswith()` methods. The methods will return `True` if the string starts/ends with any of the prefixes/suffixes in the tuple. Also adds full support for the `start` and `end` arguments to both methods for compatibility with CPython. Tests have been updated for the new behaviour. Signed-off-by: Glenn Moloney <glenn.moloney@gmail.com>
1 parent 69ffd2a commit eb45d97

9 files changed

+83
-51
lines changed

py/objstr.c

Lines changed: 42 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,26 @@ static void check_is_str_or_bytes(mp_obj_t self_in) {
6767
mp_check_self(mp_obj_is_str_or_bytes(self_in));
6868
}
6969

70+
static const byte *get_substring_data(const mp_obj_t obj, size_t n_args, const mp_obj_t *args, size_t *len) {
71+
// Get substring data from obj, using args[0,1] to specify start and end indices.
72+
GET_STR_DATA_LEN(obj, str, str_len);
73+
if (n_args > 0) {
74+
const mp_obj_type_t *self_type = mp_obj_get_type(obj);
75+
const byte *end = str + str_len;
76+
if (n_args > 1 && args[1] != mp_const_none) {
77+
end = str_index_to_ptr(self_type, str, str_len, args[1], true);
78+
}
79+
if (args[0] != mp_const_none) {
80+
str = str_index_to_ptr(self_type, str, str_len, args[0], true);
81+
}
82+
str_len = MAX(end - str, 0);
83+
}
84+
if (len) {
85+
*len = str_len;
86+
}
87+
return str;
88+
}
89+
7090
/******************************************************************************/
7191
/* str */
7292

@@ -802,37 +822,34 @@ static mp_obj_t str_rindex(size_t n_args, const mp_obj_t *args) {
802822
}
803823
MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(str_rindex_obj, 2, 4, str_rindex);
804824

805-
// TODO: (Much) more variety in args
806-
static mp_obj_t str_startswith(size_t n_args, const mp_obj_t *args) {
807-
const mp_obj_type_t *self_type = mp_obj_get_type(args[0]);
808-
GET_STR_DATA_LEN(args[0], str, str_len);
809-
size_t prefix_len;
810-
const char *prefix = mp_obj_str_get_data(args[1], &prefix_len);
811-
const byte *start = str;
812-
if (n_args > 2) {
813-
start = str_index_to_ptr(self_type, str, str_len, args[2], true);
825+
static mp_obj_t str_startendswith(size_t n_args, const mp_obj_t *args, bool ends_with) {
826+
size_t str_len;
827+
const byte *str = get_substring_data(args[0], n_args - 2, args + 2, &str_len);
828+
mp_obj_t *prefixes = (mp_obj_t *)&args[1];
829+
size_t n_prefixes = 1;
830+
if (mp_obj_is_type(args[1], &mp_type_tuple)) {
831+
mp_obj_tuple_get(args[1], &n_prefixes, &prefixes);
814832
}
815-
if (prefix_len + (start - str) > str_len) {
816-
return mp_const_false;
833+
size_t prefix_len;
834+
for (size_t i = 0; i < n_prefixes; i++) {
835+
const char *prefix = mp_obj_str_get_data(prefixes[i], &prefix_len);
836+
const byte *s = str + (ends_with ? str_len - prefix_len : 0);
837+
if (prefix_len <= str_len && memcmp(s, prefix, prefix_len) == 0) {
838+
return mp_const_true;
839+
}
817840
}
818-
return mp_obj_new_bool(memcmp(start, prefix, prefix_len) == 0);
841+
return mp_const_false;
819842
}
820-
MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(str_startswith_obj, 2, 3, str_startswith);
821843

822-
static mp_obj_t str_endswith(size_t n_args, const mp_obj_t *args) {
823-
GET_STR_DATA_LEN(args[0], str, str_len);
824-
size_t suffix_len;
825-
const char *suffix = mp_obj_str_get_data(args[1], &suffix_len);
826-
if (n_args > 2) {
827-
mp_raise_NotImplementedError(MP_ERROR_TEXT("start/end indices"));
828-
}
844+
static mp_obj_t str_startswith(size_t n_args, const mp_obj_t *args) {
845+
return str_startendswith(n_args, args, false);
846+
}
847+
MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(str_startswith_obj, 2, 4, str_startswith);
829848

830-
if (suffix_len > str_len) {
831-
return mp_const_false;
832-
}
833-
return mp_obj_new_bool(memcmp(str + (str_len - suffix_len), suffix, suffix_len) == 0);
849+
static mp_obj_t str_endswith(size_t n_args, const mp_obj_t *args) {
850+
return str_startendswith(n_args, args, true);
834851
}
835-
MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(str_endswith_obj, 2, 3, str_endswith);
852+
MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(str_endswith_obj, 2, 4, str_endswith);
836853

837854
enum { LSTRIP, RSTRIP, STRIP };
838855

tests/basics/string_endswith.py

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,28 @@
55
print("foobar".endswith(""))
66
print("foobar".endswith("foobarbaz"))
77

8-
#print("1foobar".startswith("foo", 1))
9-
#print("1foo".startswith("foo", 1))
10-
#print("1foo".startswith("1foo", 1))
11-
#print("1fo".startswith("foo", 1))
12-
#print("1fo".startswith("foo", 10))
8+
print("foobar".endswith("bar", 3))
9+
print("foobar".endswith("bar", 4))
10+
print("foobar".endswith("foo", 0, 3))
11+
print("foobar".endswith("foo", 0, 4))
12+
print("foobar".endswith("foo", 1, 3))
13+
print("foobar".endswith("foo", 1, 3))
14+
print("foobar".endswith("oo", 1, 3))
15+
print("foobar".endswith("o", 2, 3))
16+
print("foobar".endswith("o", 3, 3))
17+
print("foobar".endswith("o", 4, 3))
18+
19+
print("foobar".endswith("bar", None, None))
20+
print("foobar".endswith("bar", None, 3))
21+
print("foobar".endswith("bar", 3, None))
22+
print("foobar".endswith("bar", 2, None))
23+
print("foobar".endswith("foo", None, 3))
24+
25+
print("foobar".endswith(("bar", "foo")))
26+
print("foobar".endswith(("foo", "bar")))
27+
print("foobar".endswith(("foo", "bar1")))
28+
print("foobar".endswith(("bar", )))
29+
print("foobar".endswith(("foo", )))
1330

1431
try:
1532
"foobar".endswith(1)

tests/basics/string_endswith_upy.py

Lines changed: 0 additions & 6 deletions
This file was deleted.

tests/basics/string_endswith_upy.py.exp

Lines changed: 0 additions & 1 deletion
This file was deleted.

tests/basics/string_startswith.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,25 @@
1010
print("1fo".startswith("foo", 1))
1111
print("1fo".startswith("foo", 10))
1212

13+
print("1foobar".startswith("foo", 1, 5))
14+
print("1foobar".startswith("foo", 1, 4))
15+
print("1foobar".startswith("foo", 1, 3))
16+
print("1foobar".startswith("oo", 2, 4))
17+
print("1foobar".startswith("o", 3, 4))
18+
print("1foobar".startswith("o", 4, 4))
19+
print("1foobar".startswith("o", 5, 4))
20+
21+
print("foobar".startswith("foo", None, None))
22+
print("foobar".startswith("foo", None, 3))
23+
print("foobar".startswith("foo", None, 2))
24+
print("foobar".startswith("bar", 3, None))
25+
26+
27+
print("foobar".startswith(("foo", "sth")))
28+
print("foobar".startswith(("sth", "foo")))
29+
print("foobar".startswith(("sth", "foo2")))
30+
print("foobar".startswith(("foo", )))
31+
1332
try:
1433
"foobar".startswith(1)
1534
except TypeError:

tests/basics/string_startswith_upy.py

Lines changed: 0 additions & 6 deletions
This file was deleted.

tests/basics/string_startswith_upy.py.exp

Lines changed: 0 additions & 1 deletion
This file was deleted.

tests/misc/non_compliant.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -63,12 +63,6 @@
6363
except NotImplementedError:
6464
print("NotImplementedError")
6565

66-
# str.endswith(s, start) not implemented
67-
try:
68-
"abc".endswith("c", 1)
69-
except NotImplementedError:
70-
print("NotImplementedError")
71-
7266
# str subscr with step!=1 not implemented
7367
try:
7468
print("abc"[1:2:3])

tests/misc/non_compliant.py.exp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ NotImplementedError
1313
NotImplementedError
1414
NotImplementedError
1515
NotImplementedError
16-
NotImplementedError
1716
b'\x01\x02'
1817
b'\x01\x00'
1918
NotImplementedError

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy