Merged revisions 60080-60089,60091-60093 via svnmerge from

svn+ssh://pythondev@svn.python.org/python/trunk

........
  r60080 | andrew.kuchling | 2008-01-19 17:26:13 +0100 (Sat, 19 Jan 2008) | 2 lines

  Patch #742598 from Michael Pomraning: add .timeout attribute to SocketServer that will call
  .handle_timeout() method when no requests are received within the timeout period.
........
  r60081 | andrew.kuchling | 2008-01-19 17:34:09 +0100 (Sat, 19 Jan 2008) | 1 line

  Add item
........
  r60082 | christian.heimes | 2008-01-19 17:39:27 +0100 (Sat, 19 Jan 2008) | 2 lines

  Disabled test_xmlrpc:test_404. It's causing lots of false alarms.
  I also disabled a test in test_ssl which requires network access to svn.python.org. This fixes a bug Skip has reported a while ago.
........
  r60083 | georg.brandl | 2008-01-19 18:38:53 +0100 (Sat, 19 Jan 2008) | 2 lines

  Clarify thread.join() docs. #1873.
........
  r60084 | georg.brandl | 2008-01-19 19:02:46 +0100 (Sat, 19 Jan 2008) | 2 lines

  #1782: don't leak in error case in PyModule_AddXxxConstant. Patch by Hrvoje Nik?\197?\161i?\196?\135.
........
  r60085 | andrew.kuchling | 2008-01-19 19:08:52 +0100 (Sat, 19 Jan 2008) | 1 line

  Sort two names into position
........
  r60086 | andrew.kuchling | 2008-01-19 19:18:41 +0100 (Sat, 19 Jan 2008) | 2 lines

  Patch #976880: add mmap .rfind() method, and 'end' paramter to .find().
  Contributed by John Lenton.
........
  r60087 | facundo.batista | 2008-01-19 19:38:19 +0100 (Sat, 19 Jan 2008) | 5 lines


  Fix #1693149.  Now you can pass several modules separated by
  coma to trace.py in the same --ignore-module option.
  Thanks Raghuram Devarakonda.
........
  r60088 | facundo.batista | 2008-01-19 19:45:46 +0100 (Sat, 19 Jan 2008) | 3 lines


  Comment in NEWS regarding the change in trace.py.
........
  r60089 | skip.montanaro | 2008-01-19 19:47:24 +0100 (Sat, 19 Jan 2008) | 2 lines

  missing from r60088 checkin.
........
  r60091 | andrew.kuchling | 2008-01-19 20:14:05 +0100 (Sat, 19 Jan 2008) | 1 line

  Add item
........
  r60092 | georg.brandl | 2008-01-19 20:27:05 +0100 (Sat, 19 Jan 2008) | 4 lines

  Fix #1679: "0x" was taken as a valid integer literal.
  Fixes the tokenizer, tokenize.py and int() to reject this.
  Patches by Malte Helmert.
........
  r60093 | georg.brandl | 2008-01-19 20:48:19 +0100 (Sat, 19 Jan 2008) | 3 lines

  Fix #1146: TextWrap vs words 1-character shorter than the width.
  Patch by Quentin Gallet-Gilles.
........
This commit is contained in:
Georg Brandl 2008-01-19 20:08:23 +00:00
parent 2336bddd5d
commit fceab5a385
21 changed files with 301 additions and 40 deletions

View File

@ -137,11 +137,12 @@ Memory-mapped file objects support the following methods:
an exception being raised.
.. method:: mmap.find(string[, start])
.. method:: mmap.find(string[, start[, end]])
Returns the lowest index in the object where the substring *string* is found.
Returns ``-1`` on failure. *start* is the index at which the search begins, and
defaults to zero.
Returns the lowest index in the object where the substring *string* is found,
such that *string* is contained in the range [*start*, *end*]. Optional
arguments *start* and *end* are interpreted as in slice notation.
Returns ``-1`` on failure.
.. method:: mmap.flush([offset, size])
@ -186,6 +187,14 @@ Memory-mapped file objects support the following methods:
:exc:`TypeError` exception.
.. method:: mmap.rfind(string[, start[, end]])
Returns the highest index in the object where the substring *string* is
found, such that *string* is contained in the range [*start*,
*end*]. Optional arguments *start* and *end* are interpreted as in slice
notation. Returns ``-1`` on failure.
.. method:: mmap.seek(pos[, whence])
Set the file's current position. *whence* argument is optional and defaults to

View File

@ -44,7 +44,7 @@ to behave autonomously; the default is :const:`False`, meaning that Python will
not exit until all threads created by :class:`ThreadingMixIn` have exited.
Server classes have the same external methods and attributes, no matter what
network protocol they use:
network protocol they use.
Server Creation Notes
@ -193,6 +193,13 @@ The server classes support the following class variables:
The type of socket used by the server; :const:`socket.SOCK_STREAM` and
:const:`socket.SOCK_DGRAM` are two possible values.
.. data:: timeout
Timeout duration, measured in seconds, or :const:`None` if no timeout is desired.
If no incoming requests are received within the timeout period,
the :meth:`handle_timeout` method is called and then the server resumes waiting for
requests.
There are various server methods that can be overridden by subclasses of base
server classes like :class:`TCPServer`; these methods aren't useful to external
users of the server object.
@ -220,6 +227,13 @@ users of the server object.
method raises an exception. The default action is to print the traceback to
standard output and continue handling further requests.
.. function:: handle_timeout()
This function is called when the :attr:`timeout` attribute has been set to a
value other than :const:`None` and the timeout period has passed with no
requests being received. The default action for forking servers is
to collect the status of any child processes that have exited, while
in threading servers this method does nothing.
.. function:: process_request(request, client_address)

View File

@ -615,18 +615,19 @@ impossible to detect the termination of alien threads.
When the *timeout* argument is present and not ``None``, it should be a floating
point number specifying a timeout for the operation in seconds (or fractions
thereof). As :meth:`join` always returns ``None``, you must call
:meth:`isAlive` to decide whether a timeout happened.
thereof). As :meth:`join` always returns ``None``, you must call :meth:`isAlive`
after :meth:`join` to decide whether a timeout happened -- if the thread is
still alive, the :meth:`join` call timed out.
When the *timeout* argument is not present or ``None``, the operation will block
until the thread terminates.
A thread can be :meth:`join`\ ed many times.
:meth:`join` may throw a :exc:`RuntimeError`, if an attempt is made to join the
current thread as that would cause a deadlock. It is also an error to
:meth:`join` a thread before it has been started and attempts to do so raises
same exception.
:meth:`join` raises a :exc:`RuntimeError` if an attempt is made to join
the current thread as that would cause a deadlock. It is also an error to
:meth:`join` a thread before it has been started and attempts to do so
raises the same exception.
.. method:: Thread.getName()

View File

@ -64,12 +64,14 @@ The following command-line arguments are supported:
stdout for each file processed.
:option:`--ignore-module`
Ignore the named module and its submodules (if it is a package). May be given
Accepts comma separated list of module names. Ignore each of the named
module and its submodules (if it is a package). May be given
multiple times.
:option:`--ignore-dir`
Ignore all modules and packages in the named directory and subdirectories. May
be given multiple times.
Ignore all modules and packages in the named directory and subdirectories
(multiple directories can be joined by os.pathsep). May be given multiple
times.
.. _trace-api:

View File

@ -960,6 +960,13 @@ complete list of changes, or look through the CVS logs for all the details.
.. Patch #1490190
* :class:`mmap` objects now have a :meth:`rfind` method that finds
a substring, beginning at the end of the string and searching
backwards. The :meth:`find` method
also gained a *end* parameter containing the index at which to stop
the forward search.
(Contributed by John Lenton.)
* The :mod:`new` module has been removed from Python 3.0.
Importing it therefore
triggers a warning message when Python is running in 3.0-warning
@ -1102,6 +1109,13 @@ complete list of changes, or look through the CVS logs for all the details.
(Contributed by Alberto Bertogli.)
.. Patch #1646
* The base classes in the :mod:`SocketServer` module now support
calling a :meth:`handle_timeout` method after a span of inactivity
specified by the server's :attr:`timeout` attribute. (Contributed
by Michael Pomraning.)
.. Patch #742598
* A new variable in the :mod:`sys` module,
:attr:`float_info`, is an object

View File

@ -158,6 +158,7 @@ class BaseServer:
- server_bind()
- server_activate()
- get_request() -> request, client_address
- handle_timeout()
- verify_request(request, client_address)
- server_close()
- process_request(request, client_address)
@ -171,6 +172,7 @@ class BaseServer:
Class variables that may be overridden by derived classes or
instances:
- timeout
- address_family
- socket_type
- allow_reuse_address
@ -182,6 +184,8 @@ class BaseServer:
"""
timeout = None
def __init__(self, server_address, RequestHandlerClass):
"""Constructor. May be extended, do not override."""
self.server_address = server_address
@ -204,8 +208,9 @@ class BaseServer:
# finishing a request is fairly arbitrary. Remember:
#
# - handle_request() is the top-level call. It calls
# get_request(), verify_request() and process_request()
# - get_request() is different for stream or datagram sockets
# await_request(), verify_request() and process_request()
# - get_request(), called by await_request(), is different for
# stream or datagram sockets
# - process_request() is the place that may fork a new process
# or create a new thread to finish the request
# - finish_request() instantiates the request handler class;
@ -214,7 +219,7 @@ class BaseServer:
def handle_request(self):
"""Handle one request, possibly blocking."""
try:
request, client_address = self.get_request()
request, client_address = self.await_request()
except socket.error:
return
if self.verify_request(request, client_address):
@ -224,6 +229,28 @@ class BaseServer:
self.handle_error(request, client_address)
self.close_request(request)
def await_request(self):
"""Call get_request or handle_timeout, observing self.timeout.
Returns value from get_request() or raises socket.timeout exception if
timeout was exceeded.
"""
if self.timeout is not None:
# If timeout == 0, you're responsible for your own fd magic.
import select
fd_sets = select.select([self], [], [], self.timeout)
if not fd_sets[0]:
self.handle_timeout()
raise socket.timeout("Listening timed out")
return self.get_request()
def handle_timeout(self):
"""Called if no new request arrives within self.timeout.
Overridden by ForkingMixIn.
"""
pass
def verify_request(self, request, client_address):
"""Verify the request. May be overridden.
@ -289,6 +316,7 @@ class TCPServer(BaseServer):
- server_bind()
- server_activate()
- get_request() -> request, client_address
- handle_timeout()
- verify_request(request, client_address)
- process_request(request, client_address)
- close_request(request)
@ -301,6 +329,7 @@ class TCPServer(BaseServer):
Class variables that may be overridden by derived classes or
instances:
- timeout
- address_family
- socket_type
- request_queue_size (only for stream sockets)
@ -405,11 +434,12 @@ class ForkingMixIn:
"""Mix-in class to handle each request in a new process."""
timeout = 300
active_children = None
max_children = 40
def collect_children(self):
"""Internal routine to wait for died children."""
"""Internal routine to wait for children that have exited."""
while self.active_children:
if len(self.active_children) < self.max_children:
options = os.WNOHANG
@ -424,6 +454,13 @@ class ForkingMixIn:
if not pid: break
self.active_children.remove(pid)
def handle_timeout(self):
"""Wait for zombies after self.timeout seconds of inactivity.
May be extended, do not override.
"""
self.collect_children()
def process_request(self, request, client_address):
"""Fork a new subprocess to process the request."""
self.collect_children()

View File

@ -743,6 +743,11 @@ class BuiltinTest(unittest.TestCase):
self.assertEqual(int('0O123', 8), 83)
self.assertEqual(int('0B100', 2), 4)
# Bug 1679: "0x" is not a valid hex literal
self.assertRaises(ValueError, int, "0x", 16)
self.assertRaises(ValueError, int, "0x", 0)
# SF bug 1334662: int(string, base) wrong answers
# Various representations of 2**32 evaluated to 0
# rather than 2**32 in previous versions

View File

@ -32,6 +32,8 @@ class TokenTests(unittest.TestCase):
self.assertEquals(0o377, 255)
self.assertEquals(2147483647, 0o17777777777)
self.assertEquals(0b1001, 9)
# "0x" is not a valid literal
self.assertRaises(SyntaxError, eval, "0x")
from sys import maxsize
if maxsize == 2147483647:
self.assertEquals(-2147483647-1, -0o20000000000)

View File

@ -252,6 +252,42 @@ class MmapTests(unittest.TestCase):
self.assertEqual(m.find(slice + b'x'), -1)
m.close()
def test_find_end(self):
# test the new 'end' parameter works as expected
f = open(TESTFN, 'w+')
data = 'one two ones'
n = len(data)
f.write(data)
f.flush()
m = mmap.mmap(f.fileno(), n)
f.close()
self.assertEqual(m.find('one'), 0)
self.assertEqual(m.find('ones'), 8)
self.assertEqual(m.find('one', 0, -1), 0)
self.assertEqual(m.find('one', 1), 8)
self.assertEqual(m.find('one', 1, -1), 8)
self.assertEqual(m.find('one', 1, -2), -1)
def test_rfind(self):
# test the new 'end' parameter works as expected
f = open(TESTFN, 'w+')
data = 'one two ones'
n = len(data)
f.write(data)
f.flush()
m = mmap.mmap(f.fileno(), n)
f.close()
self.assertEqual(m.rfind('one'), 8)
self.assertEqual(m.rfind('one '), 0)
self.assertEqual(m.rfind('one', 0, -1), 8)
self.assertEqual(m.rfind('one', 0, -2), 0)
self.assertEqual(m.rfind('one', 1, -1), 8)
self.assertEqual(m.rfind('one', 1, -2), -1)
def test_double_close(self):
# make sure a double close doesn't crash on Solaris (Bug# 665913)
f = open(TESTFN, 'wb+')

View File

@ -288,7 +288,6 @@ class GeneralModuleTests(unittest.TestCase):
def testRefCountGetNameInfo(self):
# Testing reference count for getnameinfo
import sys
if hasattr(sys, "getrefcount"):
try:
# On some versions, this loses a reference

View File

@ -38,6 +38,27 @@ def handle_error(prefix):
class BasicTests(unittest.TestCase):
def testSSLconnect(self):
if not test_support.is_resource_enabled('network'):
return
s = ssl.wrap_socket(socket.socket(socket.AF_INET),
cert_reqs=ssl.CERT_NONE)
s.connect(("svn.python.org", 443))
c = s.getpeercert()
if c:
raise test_support.TestFailed("Peer cert %s shouldn't be here!")
s.close()
# this should fail because we have no verification certs
s = ssl.wrap_socket(socket.socket(socket.AF_INET),
cert_reqs=ssl.CERT_REQUIRED)
try:
s.connect(("svn.python.org", 443))
except ssl.SSLError:
pass
finally:
s.close()
def testCrucialConstants(self):
ssl.PROTOCOL_SSLv2
ssl.PROTOCOL_SSLv23
@ -81,7 +102,6 @@ class BasicTests(unittest.TestCase):
class NetworkedTests(unittest.TestCase):
def testConnect(self):
s = ssl.wrap_socket(socket.socket(socket.AF_INET),
cert_reqs=ssl.CERT_NONE)
s.connect(("svn.python.org", 443))

View File

@ -385,6 +385,19 @@ How *do* you spell that odd word, anyways?
' o'],
subsequent_indent = ' '*15)
# bug 1146. Prevent a long word to be wrongly wrapped when the
# preceding word is exactly one character shorter than the width
self.check_wrap(self.text, 12,
['Did you say ',
'"supercalifr',
'agilisticexp',
'ialidocious?',
'" How *do*',
'you spell',
'that odd',
'word,',
'anyways?'])
def test_nobreak_long(self):
# Test with break_long_words disabled
self.wrapper.break_long_words = 0

View File

@ -347,7 +347,8 @@ class SimpleServerTestCase(unittest.TestCase):
# protocol error; provide additional information in test output
self.fail("%s\n%s" % (e, e.headers))
def test_404(self):
# [ch] The test 404 is causing lots of false alarms.
def XXXtest_404(self):
# send POST with httplib, it should return 404 header and
# 'Not Found' message.
conn = httplib.HTTPConnection('localhost', PORT)

View File

@ -159,7 +159,12 @@ class TextWrapper:
Handle a chunk of text (most likely a word, not whitespace) that
is too long to fit in any line.
"""
space_left = max(width - cur_len, 1)
# Figure out when indent is larger than the specified width, and make
# sure at least one character is stripped off on every pass
if width < 1:
space_left = 1
else:
space_left = width - cur_len
# If we're allowed to break long words, then do so: put as much
# of the next chunk onto the current line as will fit.

View File

@ -49,9 +49,9 @@ Comment = r'#[^\r\n]*'
Ignore = Whitespace + any(r'\\\r?\n' + Whitespace) + maybe(Comment)
Name = r'[a-zA-Z_]\w*'
Hexnumber = r'0[xX][\da-fA-F]*'
Binnumber = r'0[bB][01]*'
Octnumber = r'0[oO][0-7]*'
Hexnumber = r'0[xX][\da-fA-F]+'
Binnumber = r'0[bB][01]+'
Octnumber = r'0[oO][0-7]+'
Decnumber = r'(?:0+|[1-9]\d*)'
Intnumber = group(Hexnumber, Binnumber, Octnumber, Decnumber)
Exponent = r'[eE][-+]?\d+'

View File

@ -96,8 +96,9 @@ Modifiers:
(Can only be used with --count or --report.)
Filters, may be repeated multiple times:
--ignore-module=<mod> Ignore the given module and its submodules
(if it is a package).
--ignore-module=<mod> Ignore the given module(s) and its submodules
(if it is a package). Accepts comma separated
list of module names
--ignore-dir=<dir> Ignore files in the given directory (multiple
directories can be joined by os.pathsep).
""" % sys.argv[0])
@ -725,7 +726,8 @@ def main(argv=None):
continue
if opt == "--ignore-module":
ignore_modules.append(val)
for mod in val.split(","):
ignore_modules.append(mod.strip())
continue
if opt == "--ignore-dir":

View File

@ -109,8 +109,6 @@ Tony Campbell
Brett Cannon
Mike Carlton
Terry Carroll
Brian Leair
Luke Kenneth Casson Leighton
Donn Cave
Per Cederqvist
Octavian Cerna
@ -390,6 +388,7 @@ Piers Lauder
Ben Laurie
Simon Law
Chris Lawrence
Brian Leair
Christopher Lee
Inyeol Lee
John J. Lee
@ -397,7 +396,9 @@ Thomas Lee
Luc Lefebvre
Kip Lehman
Joerg Lehmann
Luke Kenneth Casson Leighton
Marc-Andre Lemburg
John Lenton
Mark Levinson
William Lewis
Robert van Liere
@ -524,6 +525,7 @@ Martijn Pieters
François Pinard
Zach Pincus
Michael Piotrowski
Michael Pomraning
Iustin Pop
John Popplewell
Amrit Prem

View File

@ -254,19 +254,22 @@ mmap_read_method(mmap_object *self,
}
static PyObject *
mmap_find_method(mmap_object *self,
PyObject *args)
mmap_gfind(mmap_object *self,
PyObject *args,
int reverse)
{
Py_ssize_t start = self->pos;
Py_ssize_t end = self->size;
char *needle;
Py_ssize_t len;
CHECK_VALID(NULL);
if (!PyArg_ParseTuple(args, "s#|n:find", &needle, &len, &start)) {
if (!PyArg_ParseTuple(args, reverse ? "s#|nn:rfind" : "s#|nn:find",
&needle, &len, &start, &end)) {
return NULL;
} else {
char *p;
char *e = self->data + self->size;
char sign = reverse ? -1 : 1;
if (start < 0)
start += self->size;
@ -275,7 +278,18 @@ mmap_find_method(mmap_object *self,
else if ((size_t)start > self->size)
start = self->size;
for (p = self->data + start; p + len <= e; ++p) {
if (end < 0)
end += self->size;
if (end < 0)
end = 0;
else if ((size_t)end > self->size)
end = self->size;
start += (Py_ssize_t)self->data;
end += (Py_ssize_t)self->data;
for (p = (char *)(reverse ? end - len : start);
p >= (char *)start && p + len <= (char *)end; p+=sign) {
Py_ssize_t i;
for (i = 0; i < len && needle[i] == p[i]; ++i)
/* nothing */;
@ -287,6 +301,20 @@ mmap_find_method(mmap_object *self,
}
}
static PyObject *
mmap_find_method(mmap_object *self,
PyObject *args)
{
return mmap_gfind(self, args, 0);
}
static PyObject *
mmap_rfind_method(mmap_object *self,
PyObject *args)
{
return mmap_gfind(self, args, 1);
}
static int
is_writable(mmap_object *self)
{
@ -604,6 +632,7 @@ mmap_move_method(mmap_object *self, PyObject *args)
static struct PyMethodDef mmap_object_methods[] = {
{"close", (PyCFunction) mmap_close_method, METH_NOARGS},
{"find", (PyCFunction) mmap_find_method, METH_VARARGS},
{"rfind", (PyCFunction) mmap_rfind_method, METH_VARARGS},
{"flush", (PyCFunction) mmap_flush_method, METH_VARARGS},
{"move", (PyCFunction) mmap_move_method, METH_VARARGS},
{"read", (PyCFunction) mmap_read_method, METH_VARARGS},

View File

@ -1352,19 +1352,38 @@ tok_get(register struct tok_state *tok, char **p_start, char **p_end)
goto imaginary;
#endif
if (c == 'x' || c == 'X') {
/* Hex */
c = tok_nextc(tok);
if (!isxdigit(c)) {
tok->done = E_TOKEN;
tok_backup(tok, c);
return ERRORTOKEN;
}
do {
c = tok_nextc(tok);
} while (isxdigit(c));
}
else if (c == 'o' || c == 'O') {
/* Octal */
c = tok_nextc(tok);
if (c < '0' || c > '8') {
tok->done = E_TOKEN;
tok_backup(tok, c);
return ERRORTOKEN;
}
do {
c = tok_nextc(tok);
} while ('0' <= c && c < '8');
}
else if (c == 'b' || c == 'B') {
/* Binary */
c = tok_nextc(tok);
if (c != '0' && c != '1') {
tok->done = E_TOKEN;
tok_backup(tok, c);
return ERRORTOKEN;
}
do {
c = tok_nextc(tok);
} while (c == '0' || c == '1');

View File

@ -696,11 +696,23 @@ PyModule_AddObject(PyObject *m, const char *name, PyObject *o)
int
PyModule_AddIntConstant(PyObject *m, const char *name, long value)
{
return PyModule_AddObject(m, name, PyLong_FromLong(value));
PyObject *o = PyLong_FromLong(value);
if (!o)
return -1;
if (PyModule_AddObject(m, name, o) == 0)
return 0;
Py_DECREF(o);
return -1;
}
int
PyModule_AddStringConstant(PyObject *m, const char *name, const char *value)
{
return PyModule_AddObject(m, name, PyUnicode_FromString(value));
PyObject *o = PyUnicode_FromString(value);
if (!o)
return -1;
if (PyModule_AddObject(m, name, o) == 0)
return 0;
Py_DECREF(o);
return -1;
}

View File

@ -116,12 +116,30 @@ PyOS_strtoul(register char *str, char **ptr, int base)
if (*str == '0') {
++str;
if (*str == 'x' || *str == 'X') {
/* there must be at least one digit after 0x */
if (_PyLong_DigitValue[Py_CHARMASK(str[1])] >= 16) {
if (ptr)
*ptr = str;
return 0;
}
++str;
base = 16;
} else if (*str == 'o' || *str == 'O') {
/* there must be at least one digit after 0o */
if (_PyLong_DigitValue[Py_CHARMASK(str[1])] >= 8) {
if (ptr)
*ptr = str;
return 0;
}
++str;
base = 8;
} else if (*str == 'b' || *str == 'B') {
/* there must be at least one digit after 0b */
if (_PyLong_DigitValue[Py_CHARMASK(str[1])] >= 2) {
if (ptr)
*ptr = str;
return 0;
}
++str;
base = 2;
} else {
@ -143,22 +161,43 @@ PyOS_strtoul(register char *str, char **ptr, int base)
case 16:
if (*str == '0') {
++str;
if (*str == 'x' || *str == 'X')
if (*str == 'x' || *str == 'X') {
/* there must be at least one digit after 0x */
if (_PyLong_DigitValue[Py_CHARMASK(str[1])] >= 16) {
if (ptr)
*ptr = str;
return 0;
}
++str;
}
}
break;
case 8:
if (*str == '0') {
++str;
if (*str == 'o' || *str == 'O')
if (*str == 'o' || *str == 'O') {
/* there must be at least one digit after 0o */
if (_PyLong_DigitValue[Py_CHARMASK(str[1])] >= 8) {
if (ptr)
*ptr = str;
return 0;
}
++str;
}
}
break;
case 2:
if(*str == '0') {
++str;
if (*str == 'b' || *str == 'B')
if (*str == 'b' || *str == 'B') {
/* there must be at least one digit after 0b */
if (_PyLong_DigitValue[Py_CHARMASK(str[1])] >= 2) {
if (ptr)
*ptr = str;
return 0;
}
++str;
}
}
break;
}