From ea134da9297b14bfa8dbf3a45149bc2df28c1eb9 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Thu, 2 Apr 2015 18:46:50 +0300 Subject: [PATCH] Issue #16840: Tkinter now supports 64-bit integers added in Tcl 8.4 and arbitrary precision integers added in Tcl 8.5. --- Lib/test/test_tcl.py | 46 +++++++-- Misc/NEWS | 3 + Modules/_tkinter.c | 222 +++++++++++++++++++++++++++++++++++++++---- 3 files changed, 248 insertions(+), 23 deletions(-) diff --git a/Lib/test/test_tcl.py b/Lib/test/test_tcl.py index c9410a95c96..9b1a8d7701d 100644 --- a/Lib/test/test_tcl.py +++ b/Lib/test/test_tcl.py @@ -133,9 +133,22 @@ class TclTest(unittest.TestCase): tcl = self.interp self.assertRaises(TclError,tcl.unsetvar,'a') + def get_integers(self): + integers = (0, 1, -1, 2**31-1, -2**31) + if tcl_version >= (8, 4): # wideInt was added in Tcl 8.4 + integers += (2**31, -2**31-1, 2**63-1, -2**63) + if tcl_version >= (8, 5): # bignum was added in Tcl 8.5 + integers += (2**63, -2**63-1, 2**1000, -2**1000) + return integers + def test_getint(self): tcl = self.interp.tk - self.assertEqual(tcl.getint(' 42 '), 42) + for i in self.get_integers(): + self.assertEqual(tcl.getint(' %d ' % i), i) + self.assertEqual(tcl.getint(' %#o ' % i), i) + self.assertEqual(tcl.getint(' %#x ' % i), i) + if tcl_version < (8, 5): # bignum was added in Tcl 8.5 + self.assertRaises(TclError, tcl.getint, str(2**1000)) self.assertEqual(tcl.getint(42), 42) self.assertRaises(TypeError, tcl.getint) self.assertRaises(TypeError, tcl.getint, '42', '10') @@ -270,7 +283,7 @@ class TclTest(unittest.TestCase): check('"a\xbd\u20ac"', 'a\xbd\u20ac') check(r'"a\xbd\u20ac"', 'a\xbd\u20ac') check(r'"a\0b"', 'a\x00b') - if tcl_version >= (8, 5): + if tcl_version >= (8, 5): # bignum was added in Tcl 8.5 check('2**64', str(2**64)) def test_exprdouble(self): @@ -302,7 +315,7 @@ class TclTest(unittest.TestCase): check('[string length "a\xbd\u20ac"]', 3.0) check(r'[string length "a\xbd\u20ac"]', 3.0) self.assertRaises(TclError, tcl.exprdouble, '"abc"') - if tcl_version >= (8, 5): + if tcl_version >= (8, 5): # bignum was added in Tcl 8.5 check('2**64', float(2**64)) def test_exprlong(self): @@ -334,7 +347,7 @@ class TclTest(unittest.TestCase): check('[string length "a\xbd\u20ac"]', 3) check(r'[string length "a\xbd\u20ac"]', 3) self.assertRaises(TclError, tcl.exprlong, '"abc"') - if tcl_version >= (8, 5): + if tcl_version >= (8, 5): # bignum was added in Tcl 8.5 self.assertRaises(TclError, tcl.exprlong, '2**64') def test_exprboolean(self): @@ -375,7 +388,7 @@ class TclTest(unittest.TestCase): check('[string length "a\xbd\u20ac"]', True) check(r'[string length "a\xbd\u20ac"]', True) self.assertRaises(TclError, tcl.exprboolean, '"abc"') - if tcl_version >= (8, 5): + if tcl_version >= (8, 5): # bignum was added in Tcl 8.5 check('2**64', True) def test_booleans(self): @@ -397,6 +410,21 @@ class TclTest(unittest.TestCase): check('1 < 2', True) check('1 > 2', False) + def test_expr_bignum(self): + tcl = self.interp + for i in self.get_integers(): + result = tcl.call('expr', str(i)) + if self.wantobjects: + self.assertEqual(result, i) + self.assertIsInstance(result, int) + else: + self.assertEqual(result, str(i)) + self.assertIsInstance(result, str) + if tcl_version < (8, 5): # bignum was added in Tcl 8.5 + result = tcl.call('expr', str(2**1000)) + self.assertEqual(result, str(2**1000)) + self.assertIsInstance(result, str) + def test_passing_values(self): def passValue(value): return self.interp.call('set', '_', value) @@ -414,8 +442,10 @@ class TclTest(unittest.TestCase): b'str\xc0\x80ing' if self.wantobjects else 'str\xc0\x80ing') self.assertEqual(passValue(b'str\xbding'), b'str\xbding' if self.wantobjects else 'str\xbding') - for i in (0, 1, -1, 2**31-1, -2**31): + for i in self.get_integers(): self.assertEqual(passValue(i), i if self.wantobjects else str(i)) + if tcl_version < (8, 5): # bignum was added in Tcl 8.5 + self.assertEqual(passValue(2**1000), str(2**1000)) for f in (0.0, 1.0, -1.0, 1/3, sys.float_info.min, sys.float_info.max, -sys.float_info.min, -sys.float_info.max): @@ -473,8 +503,10 @@ class TclTest(unittest.TestCase): check(b'str\x00ing', 'str\x00ing') check(b'str\xc0\x80ing', 'str\xc0\x80ing') check(b'str\xc0\x80ing\xe2\x82\xac', 'str\xc0\x80ing\xe2\x82\xac') - for i in (0, 1, -1, 2**31-1, -2**31): + for i in self.get_integers(): check(i, str(i)) + if tcl_version < (8, 5): # bignum was added in Tcl 8.5 + check(2**1000, str(2**1000)) for f in (0.0, 1.0, -1.0): check(f, repr(f)) for f in (1/3.0, sys.float_info.min, sys.float_info.max, diff --git a/Misc/NEWS b/Misc/NEWS index 66ecff14684..6730e9faa74 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -24,6 +24,9 @@ Core and Builtins Library ------- +- Issue #16840: Tkinter now supports 64-bit integers added in Tcl 8.4 and + arbitrary precision integers added in Tcl 8.5. + - Issue #23834: Fix socket.sendto(), use the C Py_ssize_t type to store the result of sendto() instead of the C int type. diff --git a/Modules/_tkinter.c b/Modules/_tkinter.c index 43ee6786ae7..6599564b383 100644 --- a/Modules/_tkinter.c +++ b/Modules/_tkinter.c @@ -69,6 +69,11 @@ Copyright (C) 1994 Steen Lumholt. #error "Tk older than 8.3.1 not supported" #endif +#if TK_VERSION_HEX >= 0x08050000 +#define HAVE_LIBTOMMAMTH +#include +#endif + #if !(defined(MS_WINDOWS) || defined(__CYGWIN__)) #define HAVE_CREATEFILEHANDLER #endif @@ -247,6 +252,8 @@ typedef struct { const Tcl_ObjType *ByteArrayType; const Tcl_ObjType *DoubleType; const Tcl_ObjType *IntType; + const Tcl_ObjType *WideIntType; + const Tcl_ObjType *BignumType; const Tcl_ObjType *ListType; const Tcl_ObjType *ProcBodyType; const Tcl_ObjType *StringType; @@ -583,6 +590,8 @@ Tkapp_New(char *screenName, char *className, v->ByteArrayType = Tcl_GetObjType("bytearray"); v->DoubleType = Tcl_GetObjType("double"); v->IntType = Tcl_GetObjType("int"); + v->WideIntType = Tcl_GetObjType("wideInt"); + v->BignumType = Tcl_GetObjType("bignum"); v->ListType = Tcl_GetObjType("list"); v->ProcBodyType = Tcl_GetObjType("procbody"); v->StringType = Tcl_GetObjType("string"); @@ -875,28 +884,92 @@ static PyType_Spec PyTclObject_Type_spec = { #define CHECK_STRING_LENGTH(s) #endif +#ifdef HAVE_LIBTOMMAMTH +static Tcl_Obj* +asBignumObj(PyObject *value) +{ + Tcl_Obj *result; + int neg; + PyObject *hexstr; + char *hexchars; + mp_int bigValue; + + neg = Py_SIZE(value) < 0; + hexstr = _PyLong_Format(value, 16); + if (hexstr == NULL) + return NULL; + hexchars = PyUnicode_AsUTF8(hexstr); + if (hexchars == NULL) { + Py_DECREF(hexstr); + return NULL; + } + hexchars += neg + 2; /* skip sign and "0x" */ + mp_init(&bigValue); + if (mp_read_radix(&bigValue, hexchars, 16) != MP_OKAY) { + mp_clear(&bigValue); + Py_DECREF(hexstr); + PyErr_NoMemory(); + return NULL; + } + Py_DECREF(hexstr); + bigValue.sign = neg ? MP_NEG : MP_ZPOS; + result = Tcl_NewBignumObj(&bigValue); + mp_clear(&bigValue); + if (result == NULL) { + PyErr_NoMemory(); + return NULL; + } + return result; +} +#endif + static Tcl_Obj* AsObj(PyObject *value) { Tcl_Obj *result; - long longVal; - int overflow; if (PyBytes_Check(value)) return Tcl_NewByteArrayObj((unsigned char *)PyBytes_AS_STRING(value), PyBytes_GET_SIZE(value)); - else if (PyBool_Check(value)) + + if (PyBool_Check(value)) return Tcl_NewBooleanObj(PyObject_IsTrue(value)); - else if (PyLong_CheckExact(value) && - ((longVal = PyLong_AsLongAndOverflow(value, &overflow)), - !overflow)) { + + if (PyLong_CheckExact(value)) { + int overflow; + long longValue; +#ifdef TCL_WIDE_INT_TYPE + Tcl_WideInt wideValue; +#endif + longValue = PyLong_AsLongAndOverflow(value, &overflow); + if (!overflow) { + return Tcl_NewLongObj(longValue); + } /* If there is an overflow in the long conversion, + fall through to wideInt handling. */ +#ifdef TCL_WIDE_INT_TYPE + if (_PyLong_AsByteArray((PyLongObject *)value, + (unsigned char *)(void *)&wideValue, + sizeof(wideValue), + PY_LITTLE_ENDIAN, + /* signed */ 1) == 0) { + return Tcl_NewWideIntObj(wideValue); + } + PyErr_Clear(); +#endif + /* If there is an overflow in the wideInt conversion, + fall through to bignum handling. */ +#ifdef HAVE_LIBTOMMAMTH + return asBignumObj(value); +#endif + /* If there is no wideInt or bignum support, fall through to default object handling. */ - return Tcl_NewLongObj(longVal); } - else if (PyFloat_Check(value)) + + if (PyFloat_Check(value)) return Tcl_NewDoubleObj(PyFloat_AS_DOUBLE(value)); - else if (PyTuple_Check(value)) { + + if (PyTuple_Check(value)) { Tcl_Obj **argv; Py_ssize_t size, i; @@ -916,7 +989,8 @@ AsObj(PyObject *value) ckfree(FREECAST argv); return result; } - else if (PyUnicode_Check(value)) { + + if (PyUnicode_Check(value)) { void *inbuf; Py_ssize_t size; int kind; @@ -966,12 +1040,14 @@ AsObj(PyObject *value) ckfree(FREECAST outbuf); return result; } - else if(PyTclObject_Check(value)) { + + if (PyTclObject_Check(value)) { Tcl_Obj *v = ((PyTclObject*)value)->value; Tcl_IncrRefCount(v); return v; } - else { + + { PyObject *v = PyObject_Str(value); if (!v) return 0; @@ -990,6 +1066,62 @@ fromBoolean(PyObject* tkapp, Tcl_Obj *value) return PyBool_FromLong(boolValue); } +#ifdef TCL_WIDE_INT_TYPE +static PyObject* +fromWideIntObj(PyObject* tkapp, Tcl_Obj *value) +{ + Tcl_WideInt wideValue; + if (Tcl_GetWideIntFromObj(Tkapp_Interp(tkapp), value, &wideValue) == TCL_OK) { +#ifdef HAVE_LONG_LONG + if (sizeof(wideValue) <= SIZEOF_LONG_LONG) + return PyLong_FromLongLong(wideValue); +#endif + return _PyLong_FromByteArray((unsigned char *)(void *)&wideValue, + sizeof(wideValue), + PY_LITTLE_ENDIAN, + /* signed */ 1); + } + return NULL; +} +#endif + +#ifdef HAVE_LIBTOMMAMTH +static PyObject* +fromBignumObj(PyObject* tkapp, Tcl_Obj *value) +{ + mp_int bigValue; + unsigned long numBytes; + unsigned char *bytes; + PyObject *res; + + if (Tcl_GetBignumFromObj(Tkapp_Interp(tkapp), value, &bigValue) != TCL_OK) + return Tkinter_Error(tkapp); + numBytes = mp_unsigned_bin_size(&bigValue); + bytes = PyMem_Malloc(numBytes); + if (bytes == NULL) { + mp_clear(&bigValue); + return PyErr_NoMemory(); + } + if (mp_to_unsigned_bin_n(&bigValue, bytes, + &numBytes) != MP_OKAY) { + mp_clear(&bigValue); + PyMem_Free(bytes); + return PyErr_NoMemory(); + } + res = _PyLong_FromByteArray(bytes, numBytes, + /* big-endian */ 0, + /* unsigned */ 0); + PyMem_Free(bytes); + if (res != NULL && bigValue.sign == MP_NEG) { + PyObject *res2 = PyNumber_Negative(res); + Py_DECREF(res); + res = res2; + } + mp_clear(&bigValue); + return res; +} +#endif + static PyObject* FromObj(PyObject* tkapp, Tcl_Obj *value) { @@ -1017,9 +1149,33 @@ FromObj(PyObject* tkapp, Tcl_Obj *value) } if (value->typePtr == app->IntType) { - return PyLong_FromLong(value->internalRep.longValue); + long longValue; + if (Tcl_GetLongFromObj(interp, value, &longValue) == TCL_OK) + return PyLong_FromLong(longValue); + /* If there is an error in the long conversion, + fall through to wideInt handling. */ } +#ifdef TCL_WIDE_INT_TYPE + if (value->typePtr == app->IntType || + value->typePtr == app->WideIntType) { + result = fromWideIntObj(tkapp, value); + if (result != NULL || PyErr_Occurred()) + return result; + Tcl_ResetResult(interp); + /* If there is an error in the wideInt conversion, + fall through to bignum handling. */ + } +#endif + +#ifdef HAVE_LIBTOMMAMTH + if (value->typePtr == app->IntType || + value->typePtr == app->WideIntType || + value->typePtr == app->BignumType) { + return fromBignumObj(tkapp, value); + } +#endif + if (value->typePtr == app->ListType) { int size; int i, status; @@ -1067,6 +1223,15 @@ FromObj(PyObject* tkapp, Tcl_Obj *value) } #endif +#ifdef HAVE_LIBTOMMAMTH + if (app->BignumType == NULL && + strcmp(value->typePtr->name, "bignum") == 0) { + /* bignum type is not registered in Tcl */ + app->BignumType = value->typePtr; + return fromBignumObj(tkapp, value); + } +#endif + return newPyTclObject(value); } @@ -1700,7 +1865,12 @@ static PyObject * Tkapp_GetInt(PyObject *self, PyObject *args) { char *s; - int v; +#if defined(TCL_WIDE_INT_TYPE) || defined(HAVE_LIBTOMMAMTH) + Tcl_Obj *value; + PyObject *result; +#else + int intValue; +#endif if (PyTuple_Size(args) == 1) { PyObject* o = PyTuple_GetItem(args, 0); @@ -1712,9 +1882,29 @@ Tkapp_GetInt(PyObject *self, PyObject *args) if (!PyArg_ParseTuple(args, "s:getint", &s)) return NULL; CHECK_STRING_LENGTH(s); - if (Tcl_GetInt(Tkapp_Interp(self), s, &v) == TCL_ERROR) +#if defined(TCL_WIDE_INT_TYPE) || defined(HAVE_LIBTOMMAMTH) + value = Tcl_NewStringObj(s, -1); + if (value == NULL) return Tkinter_Error(self); - return Py_BuildValue("i", v); + /* Don't use Tcl_GetInt() because it returns ambiguous result for value + in ranges -2**32..-2**31-1 and 2**31..2**32-1 (on 32-bit platform). + + Prefer bignum because Tcl_GetWideIntFromObj returns ambiguous result for + value in ranges -2**64..-2**63-1 and 2**63..2**64-1 (on 32-bit platform). + */ +#ifdef HAVE_LIBTOMMAMTH + result = fromBignumObj(self, value); +#else + result = fromWideIntObj(self, value); +#endif + Tcl_DecrRefCount(value); + if (result != NULL || PyErr_Occurred()) + return result; +#else + if (Tcl_GetInt(Tkapp_Interp(self), s, &intValue) == TCL_OK) + return PyLong_FromLong(intValue); +#endif + return Tkinter_Error(self); } static PyObject *