From 1f68fc7fa5048e0576ff26436012765f4a8fa3d4 Mon Sep 17 00:00:00 2001 From: Neal Norwitz Date: Fri, 14 Jun 2002 00:50:42 +0000 Subject: [PATCH] SF bug # 493951 string.{starts,ends}with vs slices Handle negative indices similar to slices. --- Doc/whatsnew/whatsnew23.tex | 4 ++ Lib/test/string_tests.py | 27 +++++++++++++ Misc/NEWS | 3 ++ Objects/stringobject.c | 81 +++++++++++++++++-------------------- 4 files changed, 70 insertions(+), 45 deletions(-) diff --git a/Doc/whatsnew/whatsnew23.tex b/Doc/whatsnew/whatsnew23.tex index 0759c9df253..6d3651a31e0 100644 --- a/Doc/whatsnew/whatsnew23.tex +++ b/Doc/whatsnew/whatsnew23.tex @@ -497,6 +497,10 @@ u'\u4001abc' >>> \end{verbatim} +\item The \method{startswith()} and \method{endswith()} +string methods now have accept negative numbers for +start and end parameters. + \item Another new string method is \method{zfill()}, originally a function in the \module{string} module. \method{zfill()} pads a numeric string with zeros on the left until it's the specified width. diff --git a/Lib/test/string_tests.py b/Lib/test/string_tests.py index 075e1c9105f..b645354a0c0 100644 --- a/Lib/test/string_tests.py +++ b/Lib/test/string_tests.py @@ -223,6 +223,18 @@ def run_method_tests(test): test('startswith', 'helloworld', 1, 'lowo', 3, 7) test('startswith', 'helloworld', 0, 'lowo', 3, 6) + # test negative indices in startswith + test('startswith', 'hello', 1, 'he', 0, -1) + test('startswith', 'hello', 1, 'he', -53, -1) + test('startswith', 'hello', 0, 'hello', 0, -1) + test('startswith', 'hello', 0, 'hello world', -1, -10) + test('startswith', 'hello', 0, 'ello', -5) + test('startswith', 'hello', 1, 'ello', -4) + test('startswith', 'hello', 0, 'o', -2) + test('startswith', 'hello', 1, 'o', -1) + test('startswith', 'hello', 1, '', -3, -3) + test('startswith', 'hello', 0, 'lo', -9) + test('endswith', 'hello', 1, 'lo') test('endswith', 'hello', 0, 'he') test('endswith', 'hello', 1, '') @@ -238,6 +250,21 @@ def run_method_tests(test): test('endswith', 'ab', 0, 'ab', 0, 1) test('endswith', 'ab', 0, 'ab', 0, 0) + # test negative indices in endswith + test('endswith', 'hello', 1, 'lo', -2) + test('endswith', 'hello', 0, 'he', -2) + test('endswith', 'hello', 1, '', -3, -3) + test('endswith', 'hello', 0, 'hello world', -10, -2) + test('endswith', 'helloworld', 0, 'worl', -6) + test('endswith', 'helloworld', 1, 'worl', -5, -1) + test('endswith', 'helloworld', 1, 'worl', -5, 9) + test('endswith', 'helloworld', 1, 'world', -7, 12) + test('endswith', 'helloworld', 1, 'lowo', -99, -3) + test('endswith', 'helloworld', 1, 'lowo', -8, -3) + test('endswith', 'helloworld', 1, 'lowo', -7, -3) + test('endswith', 'helloworld', 0, 'lowo', 3, -4) + test('endswith', 'helloworld', 0, 'lowo', -8, -2) + test('zfill', '123', '123', 2) test('zfill', '123', '123', 3) test('zfill', '123', '0123', 4) diff --git a/Misc/NEWS b/Misc/NEWS index 383614f0593..4a949f4b377 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -6,6 +6,9 @@ Type/class unification and new-style classes Core and builtins +- Fixed string.startswith and string.endswith builtin methods + so they accept negative indices. [SF bug 493951] + - Fixed a bug with a continue inside a try block and a yield in the finally clause. [SF bug 567538] diff --git a/Objects/stringobject.c b/Objects/stringobject.c index 243fcc695e2..5d6478b783e 100644 --- a/Objects/stringobject.c +++ b/Objects/stringobject.c @@ -1310,6 +1310,21 @@ _PyString_Join(PyObject *sep, PyObject *x) return string_join((PyStringObject *)sep, x); } +static void +string_adjust_indices(int *start, int *end, int len) +{ + if (*end > len) + *end = len; + else if (*end < 0) + *end += len; + if (*end < 0) + *end = 0; + if (*start < 0) + *start += len; + if (*start < 0) + *start = 0; +} + static long string_find_internal(PyStringObject *self, PyObject *args, int dir) { @@ -1332,16 +1347,7 @@ string_find_internal(PyStringObject *self, PyObject *args, int dir) else if (PyObject_AsCharBuffer(subobj, &sub, &n)) return -2; - if (last > len) - last = len; - if (last < 0) - last += len; - if (last < 0) - last = 0; - if (i < 0) - i += len; - if (i < 0) - i = 0; + string_adjust_indices(&i, &last, len); if (dir > 0) { if (n == 0 && i <= last) @@ -1763,16 +1769,8 @@ string_count(PyStringObject *self, PyObject *args) else if (PyObject_AsCharBuffer(subobj, &sub, &n)) return NULL; - if (last > len) - last = len; - if (last < 0) - last += len; - if (last < 0) - last = 0; - if (i < 0) - i += len; - if (i < 0) - i = 0; + string_adjust_indices(&i, &last, len); + m = last + 1 - n; if (n == 0) return PyInt_FromLong((long) (m-i)); @@ -2169,7 +2167,7 @@ string_startswith(PyStringObject *self, PyObject *args) const char* prefix; int plen; int start = 0; - int end = -1; + int end = INT_MAX; PyObject *subobj; if (!PyArg_ParseTuple(args, "O|O&O&:startswith", &subobj, @@ -2193,23 +2191,15 @@ string_startswith(PyStringObject *self, PyObject *args) else if (PyObject_AsCharBuffer(subobj, &prefix, &plen)) return NULL; - /* adopt Java semantics for index out of range. it is legal for - * offset to be == plen, but this only returns true if prefix is - * the empty string. - */ - if (start < 0 || start+plen > len) + string_adjust_indices(&start, &end, len); + + if (start+plen > len) return PyBool_FromLong(0); - if (!memcmp(str+start, prefix, plen)) { - /* did the match end after the specified end? */ - if (end < 0) - return PyBool_FromLong(1); - else if (end - start < plen) - return PyBool_FromLong(0); - else - return PyBool_FromLong(1); - } - else return PyBool_FromLong(0); + if (end-start >= plen) + return PyBool_FromLong(!memcmp(str+start, prefix, plen)); + else + return PyBool_FromLong(0); } @@ -2228,8 +2218,7 @@ string_endswith(PyStringObject *self, PyObject *args) const char* suffix; int slen; int start = 0; - int end = -1; - int lower, upper; + int end = INT_MAX; PyObject *subobj; if (!PyArg_ParseTuple(args, "O|O&O&:endswith", &subobj, @@ -2253,15 +2242,17 @@ string_endswith(PyStringObject *self, PyObject *args) else if (PyObject_AsCharBuffer(subobj, &suffix, &slen)) return NULL; - if (start < 0 || start > len || slen > len) + string_adjust_indices(&start, &end, len); + + if (end-start < slen || start > len) return PyBool_FromLong(0); - upper = (end >= 0 && end <= len) ? end : len; - lower = (upper - slen) > start ? (upper - slen) : start; - - if (upper-lower >= slen && !memcmp(str+lower, suffix, slen)) - return PyBool_FromLong(1); - else return PyBool_FromLong(0); + if (end-slen > start) + start = end - slen; + if (end-start >= slen) + return PyBool_FromLong(!memcmp(str+start, suffix, slen)); + else + return PyBool_FromLong(0); }