From: Mark Wooding Date: Tue, 22 Oct 2019 18:12:28 +0000 (+0100) Subject: Port to Python 3. X-Git-Url: https://git.distorted.org.uk/~mdw/catacomb-python/commitdiff_plain/f76230157bd427829e49628de37d53f9c8ae7842 Port to Python 3. Most of this is `#ifdef ...' ... `#endif' noise, with a few tweaks thrown in. Some notes on specific parts of the port. * buffer.c: The Python 3 buffer protocol is completely different. Read buffers work differently, but aren't problematic; write buffers can now be held open for an extended period, so we need the locking machinery that was added recently. * catacomb.c: Module initialization has changed, but isn't a great deal more difficult. * catacomb/__init__.py, catacomb/pwsafe.py, pock, pwsafe: There are a number of places which need language-version switches, but none of them is especially interesting. This diff is noisier than it should be because I couldn't adjust the indentation in advance. * mp.c: With the abolition of a separate fixnum type, `mp_frompylong' needed to express the fast path from a fixnum in a different way. * pwsafe: The hacking to alter the error-handling behaviour associated with the `stdout' stream is deeply unpleasant. Sorry. * pyke/pyke.h: Most of the porting work happens here, with alternative definitions for the various macros introduced earlier. * .gitignore: Ignore Python 3 `__pycache__/' turds. Python 3 leaves its pre-tokenized files in `__pycache__/' directories, which somehow manage to be much more objectionable than the loose Python 2 `*.pyc' files. Ignore these. * debian/: Add the necessary things to build a Python 3 extension package. --- diff --git a/.gitignore b/.gitignore index f81c80d..d245190 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ *.pyc +__pycache__/ /COPYING /MANIFEST diff --git a/MANIFEST.in b/MANIFEST.in index 176ec8a..84b81d1 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -26,3 +26,4 @@ recursive-include catacomb *.py ## Debian. include debian/rules debian/control debian/changelog include debian/copyright debian/compat debian/source/format +include debian/*.install diff --git a/buffer.c b/buffer.c index fe7633d..0d3d165 100644 --- a/buffer.c +++ b/buffer.c @@ -58,10 +58,18 @@ static void buf_pydealloc(PyObject *me) FREEOBJ(me); } +#ifdef PY3 + static int rbuf_pygetbuf(PyObject *me, Py_buffer *vw, int f) + { + buf *b = BUF_B(me); + return (PyBuffer_FillInfo(vw, me, BCUR(b), BLEFT(b), 1, f)); + } +#else static Py_ssize_t rbuf_pysegcount(PyObject *me, Py_ssize_t *nn) { if (nn) *nn = BSZ(BUF_B(me)); return (1); } static Py_ssize_t rbuf_pyreadbuf(PyObject *me, Py_ssize_t seg, void **q) { assert(seg == 0); *q = BCUR(BUF_B(me)); return (BLEFT(BUF_B(me))); } +#endif static PyObject *rbmeth_skip(PyObject *me, PyObject *arg) { @@ -256,10 +264,15 @@ static const PyMethodDef rbuf_pymethods[] = { }; static const PyBufferProcs rbuf_pybuffer = { +#ifdef PY3 + rbuf_pygetbuf, /* @bf_getbuffer@ */ + 0, /* @bf_releasebuffer@ */ +#else rbuf_pyreadbuf, /* @bf_getreadbuffer@ */ 0, /* @bf_getwritebuffer@ */ rbuf_pysegcount, /* @bf_getsegcount@ */ 0 /* @bf_getcharbuffer@ */ +#endif }; static const PyTypeObject rbuf_pytype_skel = { @@ -355,10 +368,21 @@ end: return ((PyObject *)me); } +#ifdef PY3 + static int wbuf_pygetbuf(PyObject *me, Py_buffer *vw, int f) + { + buf *b = BUF_B(me); + if (PyBuffer_FillInfo(vw, me, BBASE(b), BLEN(b), 0, f)) return (-1); + BUF_LK(me)++; return (0); + } + static void wbuf_pyrlsbuf(PyObject *me, Py_buffer *vw) + { BUF_LK(me)--; } +#else static Py_ssize_t wbuf_pysegcount(PyObject *me, Py_ssize_t *nn) { if (nn) *nn = BLEN(BUF_B(me)); return (1); } static Py_ssize_t wbuf_pyreadbuf(PyObject *me, Py_ssize_t seg, void **q) { assert(seg == 0); *q = BBASE(BUF_B(me)); return (BLEN(BUF_B(me))); } +#endif static PyObject *wbmeth_zero(PyObject *me, PyObject *arg) { @@ -504,10 +528,15 @@ static const PyMethodDef wbuf_pymethods[] = { }; static const PyBufferProcs wbuf_pybuffer = { +#ifdef PY3 + wbuf_pygetbuf, /* @bf_getbuffer@ */ + wbuf_pyrlsbuf /* @bf_releasebuffer@ */ +#else wbuf_pyreadbuf, /* @bf_getreadbuffer@ */ 0, /* @bf_getwritebuffer@ */ wbuf_pysegcount, /* @bf_getsegcount@ */ 0 /* @bf_getcharbuffer@ */ +#endif }; static const PyTypeObject wbuf_pytype_skel = { diff --git a/bytestring.c b/bytestring.c index ced62b4..89f3e4a 100644 --- a/bytestring.c +++ b/bytestring.c @@ -42,7 +42,9 @@ static PyObject *allocate(PyTypeObject *ty, size_t n) #if defined(CACHE_HASH) || PY_VERSION_HEX >= 0x02030000 x->ob_shash = -1; #endif +#ifdef PY2 x->ob_sstate = SSTATE_NOT_INTERNED; +#endif return ((PyObject *)x); } @@ -172,7 +174,11 @@ static PyObject *bytestring_pyitem(PyObject *me, Py_ssize_t i) PyObject *rc = 0; if (i < 0 || i >= BIN_LEN(me)) IXERR("out of range"); +#ifdef PY3 + rc = getulong(BIN_PTR(me)[i]&0xff); +#else rc = bytestring_pywrap(BIN_PTR(me) + i, 1); +#endif end: return (rc); } @@ -206,8 +212,8 @@ static PyObject *bytestring_pysubscript(PyObject *me, PyObject *ix) if (i < 0) i += BIN_LEN(me); rc = bytestring_pyitem(me, i); } else if (PySlice_Check(ix)) { - if (PySlice_GetIndicesEx((PySliceObject *)ix, BIN_LEN(me), - &i, &j, &k, &n)) + if (PySlice_GetIndicesEx(PY23((PySliceObject *), NOTHING)ix, + BIN_LEN(me), &i, &j, &k, &n)) return (0); if (k == 1) return bytestring_pyslice(me, i, j); rc = bytestring_pywrap(0, n); @@ -266,7 +272,9 @@ static const PyNumberMethods bytestring_pynumber = { 0, /* @nb_add@ */ 0, /* @nb_subtract@ */ 0, /* @nb_multiply@ */ +#ifdef PY2 0, /* @nb_divide@ */ +#endif 0, /* @nb_remainder@ */ 0, /* @nb_divmod@ */ 0, /* @nb_power@ */ diff --git a/catacomb.c b/catacomb.c index c1e91ff..b3d7e52 100644 --- a/catacomb.c +++ b/catacomb.c @@ -321,7 +321,22 @@ static void init_random(void) #endif } -EXPORT void init_base(void) +#ifdef PY3 +static PyModuleDef moddef = { + PyModuleDef_HEAD_INIT, + "catacomb._base", /* @m_name@ */ + "Low-level module for Catacomb bindings. Use `catacomb' instead.", + /* @m_doc@ */ + 0, /* @m_size@ */ + 0, /* @m_methods@ */ + 0, /* @m_slots@ */ + 0, /* @m_traverse@ */ + 0, /* @m_clear@ */ + 0 /* @m_free@ */ +}; +#endif + +EXPORT PyMODINIT_FUNC PY23(init_base, PyInit__base)(void) { PyObject *mod; @@ -330,10 +345,18 @@ EXPORT void init_base(void) INIT_MODULES; INITTYPE(thingtab, root); init_random(); +#ifdef PY3 + moddef.m_methods = donemethods(); + mod = PyModule_Create(&moddef); +#else mod = Py_InitModule("catacomb._base", donemethods()); +#endif INSERT_MODULES; INSERT("_MiscTable", thingtab_pytype); INSERT("smallprimes", smallprimes()); +#ifdef PY3 + return (mod); +#endif } /*----- That's all, folks -------------------------------------------------*/ diff --git a/catacomb/__init__.py b/catacomb/__init__.py index 5483cca..0e5c31c 100644 --- a/catacomb/__init__.py +++ b/catacomb/__init__.py @@ -55,7 +55,8 @@ if _dlflags >= 0: else: pass # can't do this. _sys.setdlopenflags(_dlflags) -import _base +if _sys.version_info >= (3,): from . import _base +else: import _base if _odlflags >= 0: _sys.setdlopenflags(_odlflags) @@ -78,14 +79,23 @@ def default_lostexchook(why, ty, val, tb): lostexchook = default_lostexchook ## Text/binary conversions. -def _bin(s): return s +if _sys.version_info >= (3,): + def _bin(s): return s.encode('iso8859-1') +else: + def _bin(s): return s ## Iterating over dictionaries. -def _iteritems(dict): return dict.iteritems() -def _itervalues(dict): return dict.itervalues() +if _sys.version_info >= (3,): + def _iteritems(dict): return dict.items() + def _itervalues(dict): return dict.values() +else: + def _iteritems(dict): return dict.iteritems() + def _itervalues(dict): return dict.itervalues() ## The built-in bignum type. -_long = long +try: long +except NameError: _long = int +else: _long = long ## How to fix a name back into the right identifier. Alas, the rules are not ## consistent. @@ -177,14 +187,32 @@ def _pp_dict(pp, items): _pp_commas(pp, p, items) ###-------------------------------------------------------------------------- +### Mappings. + +if _sys.version_info >= (3,): + class _tmp: + def __str__(me): return '%s(%r)' % (type(me).__name__, list(me)) + __repr__ = __str__ + def _repr_pretty_(me, pp, cyclep): + ind = _pp_bgroup_tyname(pp, me, '([') + _pp_commas(pp, pp.pretty, me) + pp.end_group(ind, '])') + _augment(_base._KeyView, _tmp) + _augment(_base._ValueView, _tmp) + _augment(_base._ItemView, _tmp) + +###-------------------------------------------------------------------------- ### Bytestrings. class _tmp: def fromhex(x): return ByteString(_unhexify(x)) fromhex = staticmethod(fromhex) - def hex(me): return _hexify(me) - __hex__ = hex + if _sys.version_info >= (3,): + def hex(me): return _hexify(me).decode() + else: + def hex(me): return _hexify(me) + __hex__ = hex def __repr__(me): return 'bytes(%r)' % me.hex() _augment(ByteString, _tmp) @@ -388,8 +416,9 @@ class BaseRat (object): def __rtruediv__(me, you): n, d = _split_rat(you) return type(me)(me._d*n, me._n*d) - __div__ = __truediv__ - __rdiv__ = __rtruediv__ + if _sys.version_info < (3,): + __div__ = __truediv__ + __rdiv__ = __rtruediv__ def _order(me, you, op): n, d = _split_rat(you) return op(me._n*d, n*me._d) @@ -425,8 +454,9 @@ class _tmp: def __rtruediv__(me, you): if isinstance(you, float): return you/_long(me) else: return IntRat(you, me) - __div__ = __truediv__ - __rdiv__ = __rtruediv__ + if _sys.version_info < (3,): + __div__ = __truediv__ + __rdiv__ = __rtruediv__ _repr_pretty_ = _pp_str _augment(MP, _tmp) @@ -439,8 +469,9 @@ class _tmp: def quadsolve(x, y): return x.reduce().quadsolve(y) def __truediv__(me, you): return GFRat(me, you) def __rtruediv__(me, you): return GFRat(you, me) - __div__ = __truediv__ - __rdiv__ = __rtruediv__ + if _sys.version_info < (3,): + __div__ = __truediv__ + __rdiv__ = __rtruediv__ _repr_pretty_ = _pp_str _augment(GF, _tmp) diff --git a/catacomb/pwsafe.py b/catacomb/pwsafe.py index 8fa5ee6..3133d1e 100644 --- a/catacomb/pwsafe.py +++ b/catacomb/pwsafe.py @@ -31,19 +31,28 @@ from __future__ import with_statement import binascii as _B import errno as _E import os as _OS -from cStringIO import StringIO as _StringIO +import sys as _SYS + +if _SYS.version_info >= (3,): from io import StringIO as _StringIO +else: from cStringIO import StringIO as _StringIO import catacomb as _C ###-------------------------------------------------------------------------- ### Python version portability. -def _iterkeys(dict): return dict.iterkeys() -def _itervalues(dict): return dict.itervalues() -def _iteritems(dict): return dict.iteritems() - -def _bin(text): return text -def _text(bin): return bin +if _SYS.version_info >= (3,): + def _iterkeys(dict): return dict.keys() + def _itervalues(dict): return dict.values() + def _iteritems(dict): return dict.items() + def _bin(text): return text.encode(errors = "surrogateescape") + def _text(bin): return bin.decode(errors = "surrogateescape") +else: + def _iterkeys(dict): return dict.iterkeys() + def _itervalues(dict): return dict.itervalues() + def _iteritems(dict): return dict.iteritems() + def _bin(text): return text + def _text(bin): return bin _NUL = _bin('\0') _CIPHER = _bin('cipher:') diff --git a/debian/control b/debian/control index 7b7400d..2957d2e 100644 --- a/debian/control +++ b/debian/control @@ -2,9 +2,10 @@ Source: catacomb-python Section: python Priority: extra XS-Python-Version: >= 2.6, << 2.8 +XS-Python3-Version: >= 3.0 Maintainer: Mark Wooding Build-Depends: debhelper (>= 10), dh-python, pkg-config, - python (>= 2.6.6-3~), python-all-dev, + python (>= 2.6.6-3~), python-all-dev, python3-all-dev, mlib-dev (>= 2.4.99~), catacomb-dev (>= 2.5.0) Standards-Version: 3.8.0 @@ -26,3 +27,21 @@ Description: Python bindings for the Catacomb cryptographic library. clarity of source code and portability more than performance. . This package allows the Catacomb library to be used in Python programs. + +Package: python3-catacomb +Architecture: any +XB-Python-Version: ${python3:Versions} +Depends: ${shlibs:Depends}, ${python3:Depends} +Provides: ${python3:Provides} +Description: Python 3 bindings for the Catacomb cryptographic library. + Catacomb is a cryptographic library. It implements a large number of + encryption algorithms, hash functions, message authentication codes + and random number generators. It has a multi-precision maths library, + for implementing public key schemes such as RSA, DSA and Diffie-Hellman. + It contains rudimentary key-management tools. + . + The objective of Catacomb is to make a crypto library which is + relatively straightforward to audit for security. Its focus is on + clarity of source code and portability more than performance. + . + This package allows the Catacomb library to be used in Python 3 programs. diff --git a/debian/python-catacomb.install b/debian/python-catacomb.install new file mode 100644 index 0000000..4d1b425 --- /dev/null +++ b/debian/python-catacomb.install @@ -0,0 +1,3 @@ +debian/tmp/usr/bin +debian/tmp/usr/share/man +debian/tmp/usr/lib/python2.* diff --git a/debian/python3-catacomb.install b/debian/python3-catacomb.install new file mode 100644 index 0000000..3cff559 --- /dev/null +++ b/debian/python3-catacomb.install @@ -0,0 +1 @@ +debian/tmp/usr/lib/python3.* diff --git a/debian/rules b/debian/rules index 04d6f4e..fea60f6 100755 --- a/debian/rules +++ b/debian/rules @@ -1,10 +1,13 @@ #! /usr/bin/make -f -%:; dh $@ --parallel --with python2 +%:; dh $@ --parallel --with python2,python3 -export PYTHONS := $(shell pyversions -r) +export PYTHON := $(shell pyversions -d) +export PYTHONS := $(shell pyversions -r && py3versions -r) override_dh_auto_test: dh_auto_test -- OPTS-check=-V override_dh_auto_install: - dh_auto_install -- prefix=/usr + dh_auto_install -- prefix=/usr \ + $(foreach p,$(filter-out $(PYTHON),$(PYTHONS)), \ + OPTS-install/$p="--install-scripts=/usr/bin-$p") diff --git a/ec.c b/ec.c index d631a91..459c272 100644 --- a/ec.c +++ b/ec.c @@ -589,6 +589,7 @@ end: return (rc); } +#ifdef PY2 static PyObject *ecpt_pylong(PyObject *me) { ec p = EC_INIT; @@ -600,6 +601,7 @@ end: EC_DESTROY(&p); return (rc); } +#endif static PyObject *ecpt_pynew(PyTypeObject *ty, PyObject *arg, PyObject *kw) { @@ -639,7 +641,9 @@ static const PyNumberMethods ecpt_pynumber = { 0, /* @nb_add@ */ 0, /* @nb_subtract@ */ 0, /* @nb_multiply@ */ +#ifdef PY2 0, /* @nb_divide@ */ +#endif 0, /* @nb_remainder@ */ 0, /* @nb_divmod@ */ 0, /* @nb_power@ */ @@ -653,17 +657,23 @@ static const PyNumberMethods ecpt_pynumber = { 0, /* @nb_and@ */ 0, /* @nb_xor@ */ 0, /* @nb_or@ */ +#ifdef PY2 0, /* @nb_coerce@ */ +#endif ecpt_pyint, /* @nb_int@ */ - ecpt_pylong, /* @nb_long@ */ + PY23(ecpt_pylong, 0), /* @nb_long@ */ 0, /* @nb_float@ */ +#ifdef PY2 0, /* @nb_oct@ */ 0, /* @nb_hex@ */ +#endif 0, /* @nb_inplace_add@ */ 0, /* @nb_inplace_subtract@ */ 0, /* @nb_inplace_multiply@ */ +#ifdef PY2 0, /* @nb_inplace_divide@ */ +#endif 0, /* @nb_inplace_remainder@ */ 0, /* @nb_inplace_power@ */ 0, /* @nb_inplace_lshift@ */ @@ -731,7 +741,7 @@ static const PyTypeObject ecpt_pytype_skel = { static const PyMemberDef ecpt_pymembers[] = { #define MEMBERSTRUCT ecpt_pyobj - MEMRNM(curve, T_OBJECT, ob_type, READONLY, + MEMRNM(curve, T_OBJECT, PY23(ob_type, ob_base.ob_type), READONLY, "P.curve -> elliptic curve containing P") #undef MEMBERSTRUCT { 0 } @@ -765,7 +775,9 @@ static const PyNumberMethods ecptcurve_pynumber = { ecpt_pyadd, /* @nb_add@ */ ecpt_pysub, /* @nb_subtract@ */ ecpt_pymul, /* @nb_multiply@ */ +#ifdef PY2 0, /* @nb_divide@ */ +#endif 0, /* @nb_remainder@ */ 0, /* @nb_divmod@ */ 0, /* @nb_power@ */ @@ -779,12 +791,16 @@ static const PyNumberMethods ecptcurve_pynumber = { 0, /* @nb_and@ */ 0, /* @nb_xor@ */ 0, /* @nb_or@ */ +#ifdef PY2 0, /* @nb_coerce@ */ +#endif 0, /* @nb_int@ */ 0, /* @nb_long@ */ 0, /* @nb_float@ */ +#ifdef PY2 0, /* @nb_oct@ */ 0, /* @nb_hex@ */ +#endif 0, /* @nb_inplace_add@ */ 0, /* @nb_inplace_subtract@ */ diff --git a/field.c b/field.c index 88793b0..b29d8a9 100644 --- a/field.c +++ b/field.c @@ -221,6 +221,7 @@ end: static Py_hash_t fe_pyhash(PyObject *me) { return (mphash(FE_X(me))); } +#ifdef PY2 static int fe_pycoerce(PyObject **x, PyObject **y) { mp *z; @@ -241,6 +242,7 @@ static int fe_pycoerce(PyObject **x, PyObject **y) end: return (-1); } +#endif static PyObject *fe_pyint(PyObject *x) { @@ -253,6 +255,7 @@ static PyObject *fe_pyint(PyObject *x) return (rc); } +#ifdef PY2 static PyObject *fe_pylong(PyObject *x) { mp *xx = F_OUT(FE_F(x), MP_NEW, FE_X(x)); @@ -260,6 +263,7 @@ static PyObject *fe_pylong(PyObject *x) MP_DROP(xx); return (rc); } +#endif #define BASEOP(name, radix, pre) \ static PyObject *fe_py##name(PyObject *x) { \ @@ -268,7 +272,9 @@ static PyObject *fe_pylong(PyObject *x) MP_DROP(xx); \ return (rc); \ } +#ifdef PY2 BASEOP(oct, 8, "0"); +#endif BASEOP(hex, 16, "0x"); #undef BASEOP @@ -322,7 +328,7 @@ static PyObject *feget__value(PyObject *me, void *hunoz) static const PyMemberDef fe_pymembers[] = { #define MEMBERSTRUCT fe_pyobj - MEMRNM(field, T_OBJECT, ob_type, READONLY, + MEMRNM(field, T_OBJECT, PY23(ob_type, ob_base.ob_type), READONLY, "X.field -> field containing X") #undef MEMBERSTRUCT { 0 } @@ -354,7 +360,9 @@ static const PyNumberMethods fe_pynumber = { fe_pyadd, /* @nb_add@ */ fe_pysub, /* @nb_subtract@ */ fe_pymul, /* @nb_multiply@ */ +#ifdef PY2 fe_pydiv, /* @nb_divide@ */ +#endif 0, /* @nb_remainder@ */ 0, /* @nb_divmod@ */ fe_pyexp, /* @nb_power@ */ @@ -368,17 +376,23 @@ static const PyNumberMethods fe_pynumber = { 0, /* @nb_and@ */ 0, /* @nb_xor@ */ 0, /* @nb_or@ */ +#ifdef PY2 fe_pycoerce, /* @nb_coerce@ */ +#endif fe_pyint, /* @nb_int@ */ - fe_pylong, /* @nb_long@ */ + PY23(fe_pylong, 0), /* @nb_long@ */ 0 /* meaningless */, /* @nb_float@ */ +#ifdef PY2 fe_pyoct, /* @nb_oct@ */ fe_pyhex, /* @nb_hex@ */ +#endif 0, /* @nb_inplace_add@ */ 0, /* @nb_inplace_subtract@ */ 0, /* @nb_inplace_multiply@ */ +#ifdef PY2 0, /* @nb_inplace_divide@ */ +#endif 0, /* @nb_inplace_remainder@ */ 0, /* @nb_inplace_power@ */ 0, /* @nb_inplace_lshift@ */ diff --git a/group.c b/group.c index b51cb6a..6b26d9d 100644 --- a/group.c +++ b/group.c @@ -666,6 +666,7 @@ static PyObject *ge_pystr(PyObject *me) return (rc); } +#ifdef PY2 static PyObject *ge_pylong(PyObject *me) { mp *x = 0; @@ -678,6 +679,7 @@ end: mp_drop(x); return (rc); } +#endif static PyObject *ge_pyint(PyObject *me) { @@ -957,7 +959,9 @@ static const PyNumberMethods ge_pynumber = { 0, /* @nb_add@ */ 0, /* @nb_subtract@ */ ge_pymul, /* @nb_multiply@ */ +#ifdef PY2 ge_pydiv, /* @nb_divide@ */ +#endif 0, /* @nb_remainder@ */ 0, /* @nb_divmod@ */ ge_pyexp, /* @nb_power@ */ @@ -971,17 +975,23 @@ static const PyNumberMethods ge_pynumber = { 0, /* @nb_and@ */ 0, /* @nb_xor@ */ 0, /* @nb_or@ */ +#ifdef PY2 0, /* @nb_coerce@ */ +#endif ge_pyint, /* @nb_int@ */ - ge_pylong, /* @nb_long@ */ + PY23(ge_pylong, 0), /* @nb_long@ */ 0 /* meaningless */, /* @nb_float@ */ +#ifdef PY2 0, /* @nb_oct@ */ 0, /* @nb_hex@ */ +#endif 0, /* @nb_inplace_add@ */ 0, /* @nb_inplace_subtract@ */ 0, /* @nb_inplace_multiply@ */ +#ifdef PY2 0, /* @nb_inplace_divide@ */ +#endif 0, /* @nb_inplace_remainder@ */ 0, /* @nb_inplace_power@ */ 0, /* @nb_inplace_lshift@ */ diff --git a/mp.c b/mp.c index 3f96a4e..e299a7d 100644 --- a/mp.c +++ b/mp.c @@ -56,6 +56,12 @@ mp *mp_frompylong(PyObject *obj) mp *x; mpw *p; +#ifdef PY3 + int ov; + long j = PyLong_AsLongAndOverflow(obj, &ov); + if (!ov) return mp_fromlong(MP_NEW, j); +#endif + sz = Py_SIZE(l); if (sz < 0) sz = -sz; bits = (unsigned long)sz * PyLong_SHIFT; @@ -229,8 +235,10 @@ mp *tomp(PyObject *o) return (0); return (x); } +#ifdef PY2 else if (PyInt_Check(o)) return (mp_fromlong(MP_NEW, PyInt_AS_LONG(o))); +#endif else if ((l = PyNumber_Long(o)) != 0) { x = mp_frompylong(l); Py_DECREF(l); @@ -503,7 +511,9 @@ static mp *gf_modinv_checked(mp *d, mp *x, mp *p) #define BASEOP(name, radix, pre) \ static PyObject *mp_py##name(PyObject *x) \ { return mp_topystring(MP_X(x), radix, 0, pre, 0); } +#ifdef PY2 BASEOP(oct, 8, "0"); +#endif BASEOP(hex, 16, "0x"); #undef BASEOP @@ -515,8 +525,10 @@ static PyObject *mp_pyint(PyObject *x) if (!mp_tolong_checked(MP_X(x), &l, 0)) return (PyInt_FromLong(l)); else return mp_topylong(MP_X(x)); } +#ifdef PY2 static PyObject *mp_pylong(PyObject *x) { return (mp_topylong(MP_X(x))); } +#endif static PyObject *mp_pyfloat(PyObject *x) { PyObject *l = mp_topylong(MP_X(x)); @@ -525,6 +537,7 @@ static PyObject *mp_pyfloat(PyObject *x) return (PyFloat_FromDouble(f)); } +#ifdef PY2 #define COERCE(pre, PRE) \ static int pre##_pycoerce(PyObject **x, PyObject **y) \ { \ @@ -544,6 +557,7 @@ static PyObject *mp_pyfloat(PyObject *x) COERCE(mp, MP) COERCE(gf, GF) #undef COERCE +#endif static PyObject *mp_pyrichcompare(PyObject *x, PyObject *y, int op) { @@ -559,8 +573,10 @@ static PyObject *mp_pyrichcompare(PyObject *x, PyObject *y, int op) return (rc); } +#ifdef PY2 static int mp_pycompare(PyObject *x, PyObject *y) { return mp_cmp(MP_X(x), MP_X(y)); } +#endif static PyObject *mp_pynew(PyTypeObject *ty, PyObject *arg, PyObject *kw) { @@ -930,7 +946,9 @@ static const PyNumberMethods mp_pynumber = { mp_pyadd, /* @nb_add@ */ mp_pysub, /* @nb_subtract@ */ mp_pymul, /* @nb_multiply@ */ +#ifdef PY2 0, /* @nb_divide@ */ +#endif mp_pymod, /* @nb_remainder@ */ mp_pydivmod, /* @nb_divmod@ */ mp_pyexp, /* @nb_power@ */ @@ -944,17 +962,23 @@ static const PyNumberMethods mp_pynumber = { mp_pyand2c, /* @nb_and@ */ mp_pyxor2c, /* @nb_xor@ */ mp_pyor2c, /* @nb_or@ */ +#ifdef PY2 mp_pycoerce, /* @nb_coerce@ */ +#endif mp_pyint, /* @nb_int@ */ - mp_pylong, /* @nb_long@ */ + PY23(mp_pylong, 0), /* @nb_long@ */ mp_pyfloat, /* @nb_float@ */ +#ifdef PY2 mp_pyoct, /* @nb_oct@ */ mp_pyhex, /* @nb_hex@ */ +#endif 0, /* @nb_inplace_add@ */ 0, /* @nb_inplace_subtract@ */ 0, /* @nb_inplace_multiply@ */ +#ifdef PY2 0, /* @nb_inplace_divide@ */ +#endif 0, /* @nb_inplace_remainder@ */ 0, /* @nb_inplace_power@ */ 0, /* @nb_inplace_lshift@ */ @@ -981,7 +1005,7 @@ static const PyTypeObject mp_pytype_skel = { 0, /* @tp_print@ */ 0, /* @tp_getattr@ */ 0, /* @tp_setattr@ */ - mp_pycompare, /* @tp_compare@ */ + PY23(mp_pycompare, 0), /* @tp_compare@/@tp_as_async@ */ mp_pyrepr, /* @tp_repr@ */ PYNUMBER(mp), /* @tp_as_number@ */ 0, /* @tp_as_sequence@ */ @@ -1000,13 +1024,16 @@ static const PyTypeObject mp_pytype_skel = { "Multiprecision integers, similar to `long' but more efficient and\n" "versatile. Support all the standard arithmetic operations, with\n" "implicit conversions from `PrimeFilter', and other objects which\n" - "convert to `long'.\n" + "convert to `" PY23("long", "int") "'.\n" "\n" "Constructor MP(X, [radix = R]) attempts to convert X to an `MP'. If\n" "X is a string, it's read in radix-R form, or we look for a prefix\n" "if R = 0. Other acceptable things are field elements, elliptic curve\n" + PY23( "points, group elements, Python `int' and `long' objects, and anything\n" - "with an integer conversion.\n" + "with an integer conversion.\n", + "points, group elements, Python `int' objects, and anything with an\n" + "integer conversion.\n") "\n" "Notes:\n" "\n" @@ -2115,7 +2142,9 @@ static const PyNumberMethods gf_pynumber = { gf_pyadd, /* @nb_add@ */ gf_pysub, /* @nb_subtract@ */ gf_pymul, /* @nb_multiply@ */ +#ifdef PY2 0, /* @nb_divide@ */ +#endif gf_pymod, /* @nb_remainder@ */ gf_pydivmod, /* @nb_divmod@ */ gf_pyexp, /* @nb_power@ */ @@ -2129,17 +2158,23 @@ static const PyNumberMethods gf_pynumber = { gf_pyand, /* @nb_and@ */ gf_pyxor, /* @nb_xor@ */ gf_pyor, /* @nb_or@ */ +#ifdef PY2 gf_pycoerce, /* @nb_coerce@ */ +#endif mp_pyint, /* @nb_int@ */ - mp_pylong, /* @nb_long@ */ + PY23(mp_pylong, 0), /* @nb_long@ */ 0 /* doesn't make any sense */, /* @nb_float@ */ +#ifdef PY2 mp_pyoct, /* @nb_oct@ */ mp_pyhex, /* @nb_hex@ */ +#endif 0, /* @nb_inplace_add@ */ 0, /* @nb_inplace_subtract@ */ 0, /* @nb_inplace_multiply@ */ +#ifdef PY2 0, /* @nb_inplace_divide@ */ +#endif 0, /* @nb_inplace_remainder@ */ 0, /* @nb_inplace_power@ */ 0, /* @nb_inplace_lshift@ */ @@ -2188,8 +2223,11 @@ static const PyTypeObject gf_pytype_skel = { "Constructor GF(X, [radix = R]) attempts to convert X to a `GF'. If\n" "X is a string, it's read in radix-R form, or we look for a prefix\n" "if R = 0. Other acceptable things are field elements, elliptic curve\n" + PY23( "points, group elements, Python `int' and `long' objects, and anything\n" - "with an integer conversion.\n" + "with an integer conversion.\n", + "points, group elements, Python `int' objects, and anything with an\n" + "integer conversion.\n") "\n" "The name is hopelessly wrong from a technical point of view, but\n" "but it's much easier to type than `p2' or `c2' or whatever.\n" diff --git a/pgen.c b/pgen.c index 777ec32..3c2a792 100644 --- a/pgen.c +++ b/pgen.c @@ -125,8 +125,10 @@ static PyObject *pfilt_pyint(PyObject *me) return (rc); } +#ifdef PY2 static PyObject *pfilt_pylong(PyObject *me) { return (mp_topylong(PFILT_F(me)->m)); } +#endif static PyObject *pfget_x(PyObject *me, void *hunoz) { return (mp_pywrap(MP_COPY(PFILT_F(me)->m))); } @@ -156,7 +158,9 @@ static const PyNumberMethods pfilt_pynumber = { 0, /* @nb_add@ */ 0, /* @nb_subtract@ */ 0, /* @nb_multiply@ */ +#ifdef PY2 0, /* @nb_divide@ */ +#endif 0, /* @nb_remainder@ */ 0, /* @nb_divmod@ */ 0, /* @nb_power@ */ @@ -170,17 +174,23 @@ static const PyNumberMethods pfilt_pynumber = { 0, /* @nb_and@ */ 0, /* @nb_xor@ */ 0, /* @nb_or@ */ +#ifdef PY2 0, /* @nb_coerce@ */ +#endif pfilt_pyint, /* @nb_int@ */ - pfilt_pylong, /* @nb_long@ */ + PY23(pfilt_pylong, 0), /* @nb_long@ */ 0, /* @nb_float@ */ +#ifdef PY2 0, /* @nb_oct@ */ 0, /* @nb_hex@ */ +#endif 0, /* @nb_inplace_add@ */ 0, /* @nb_inplace_subtract@ */ 0, /* @nb_inplace_multiply@ */ +#ifdef PY2 0, /* @nb_inplace_divide@ */ +#endif 0, /* @nb_inplace_remainder@ */ 0, /* @nb_inplace_power@ */ 0, /* @nb_inplace_lshift@ */ diff --git a/pock b/pock index f4aeaa9..5d7cc50 100644 --- a/pock +++ b/pock @@ -27,7 +27,7 @@ ###-------------------------------------------------------------------------- ### Imported modules. -from sys import argv, stdin, stdout, stderr +import sys as SYS; from sys import argv, stdin, stdout, stderr import os as OS import itertools as I import math as M @@ -35,6 +35,10 @@ import optparse as OP import catacomb as C +if SYS.version_info >= (3,): + xrange = range + range = lambda *args: list(xrange(*args)) + ###-------------------------------------------------------------------------- ### Utilities. diff --git a/pwsafe b/pwsafe index dd46261..40a64ce 100644 --- a/pwsafe +++ b/pwsafe @@ -30,7 +30,7 @@ from __future__ import with_statement from os import environ -from sys import argv, exit, stdin, stdout, stderr +import sys as SYS; from sys import argv, exit, stdin, stdout, stderr from getopt import getopt, GetoptError from fnmatch import fnmatch import re @@ -41,8 +41,23 @@ from catacomb.pwsafe import * ###-------------------------------------------------------------------------- ### Python version portability. -def _text(bin): return bin -def _bin(text): return text +if SYS.version_info >= (3,): + import io as IO + def hack_stream(stream): + _enc = stream.encoding + _lbuf = stream.line_buffering + _nl = stream.newlines + return IO.TextIOWrapper(stream.detach(), + encoding = _enc, + line_buffering = _lbuf, + newline = _nl, + errors = "surrogateescape") + SYS.stdout = stdout = hack_stream(stdout) + def _text(bin): return bin.decode(errors = "surrogateescape") + def _bin(text): return text.encode(errors = "surrogateescape") +else: + def _text(bin): return bin + def _bin(text): return text def _excval(): return SYS.exc_info()[1] diff --git a/pyke/mapping.c b/pyke/mapping.c index 3ad1ac7..ad02d25 100644 --- a/pyke/mapping.c +++ b/pyke/mapping.c @@ -257,6 +257,354 @@ static const PyTypeObject itemiter_pytype_skel = { 0 /* @tp_is_gc@ */ }; +/*----- Mapping views -----------------------------------------------------*/ + +#ifdef PY3 + +static PyTypeObject *keyview_pytype, *itemview_pytype, *valview_pytype; + +typedef struct view_pyobj { + PyObject_HEAD + PyObject *map; +} view_pyobj; +#define VIEW_MAP(o) (((view_pyobj *)(o))->map) + +static PyObject *gmap_mkview(PyObject *me, PyTypeObject *ty) +{ + view_pyobj *v = PyObject_NEW(view_pyobj, ty); + v->map = me; Py_INCREF(me); + return ((PyObject *)v); +} + +static void view_pydealloc(PyObject *me) + { Py_DECREF(VIEW_MAP(me)); FREEOBJ(me); } + +#define BINOP(op, update) \ + static PyObject *view_py##op(PyObject *me, PyObject *you) \ + { \ + PyObject *set = 0; \ + PyObject *rc = 0; \ + \ + set = PySet_New(me); if (!set) goto end; \ + if (!PyObject_CallMethod(set, #update, "(O)", you)) goto end; \ + rc = set; set = 0; \ + end: \ + Py_XDECREF(set); \ + return (rc); \ + } +BINOP(and, intersection_update) +BINOP(or, update) +BINOP(xor, symmetric_difference_update) +#undef BINOP + +static int all_contained_p(PyObject *x, PyObject *y) +{ + PyObject *i = 0, *e; + int b, rc = -1; + + i = PyObject_GetIter(x); if (!i) goto end; + for (;;) { + e = PyIter_Next(i); if (!e) break; + b = PySequence_Contains(y, e); Py_DECREF(e); if (b < 0) goto end; + if (!b) { rc = 0; goto end; } + } + if (PyErr_Occurred()) goto end; + rc = 1; +end: + Py_XDECREF(i); + return (rc); +} + +static Py_ssize_t view_pysize(PyObject *me) + { return (PyMapping_Size(VIEW_MAP(me))); } + +static PyObject *view_pyrichcompare(PyObject *me, PyObject *you, int op) +{ + PyObject *map = ITER_MAP(me); + Py_ssize_t mysz, yoursz; + int b; + + mysz = PyMapping_Size(map); if (mysz < 0) return (0); + yoursz = PyObject_Size(you); + if (yoursz < 0) { PyErr_Clear(); RETURN_NOTIMPL; } + + switch (op) { + case Py_EQ: + if (mysz != yoursz) RETURN_FALSE; + b = all_contained_p(you, me); + break; + case Py_NE: + if (mysz != yoursz) RETURN_TRUE; + b = all_contained_p(you, me); + break; + case Py_LT: + if (mysz >= yoursz) RETURN_FALSE; + b = all_contained_p(me, you); + break; + case Py_LE: + if (mysz > yoursz) RETURN_FALSE; + b = all_contained_p(me, you); + break; + case Py_GE: + if (mysz < yoursz) RETURN_FALSE; + b = all_contained_p(you, me); + break; + case Py_GT: + if (mysz <= yoursz) RETURN_FALSE; + b = all_contained_p(you, me); + break; + default: + abort(); + } + if (b < 0) return (0); + return (getbool(b)); +} + +static PyObject *keyview_pyiter(PyObject *me) + { return (gmap_mkiter(VIEW_MAP(me), keyiter_pytype)); } + +static int keyview_pyhaskey(PyObject *me, PyObject *k) +{ + PyObject *map = VIEW_MAP(me); + const struct gmap_ops *gmops = GMAP_OPS(map); + return (gmops->lookup(map, k, 0) ? 1 : PyErr_Occurred() ? -1 : 0); +} + +static int itemview_pyhaskey(PyObject *me, PyObject *it) +{ + PyObject *map = VIEW_MAP(me); + const struct gmap_ops *gmops = GMAP_OPS(map); + void *e; + int b; + PyObject *v; + + if (!PyTuple_Check(it) || PyTuple_GET_SIZE(it) != 2) return (0); + e = gmops->lookup(map, PyTuple_GET_ITEM(it, 0), 0); + if (!e) return (PyErr_Occurred() ? -1 : 0); + v = gmops->entry_value(map, e); if (!v) return (-1); + b = PyObject_RichCompareBool(v, PyTuple_GET_ITEM(it, 1), Py_EQ); + Py_DECREF(v); return (b); +} + +static PyObject *valview_pyiter(PyObject *me) + { return (gmap_mkiter(VIEW_MAP(me), valiter_pytype)); } + +static PyObject *itemview_pyiter(PyObject *me) + { return (gmap_mkiter(VIEW_MAP(me), itemiter_pytype)); } + +static const PyNumberMethods view_pynumber = { + 0, /* @nb_add@ */ + 0, /* @nb_subtract@ */ + 0, /* @nb_multiply@ */ +#ifdef PY2 + 0, /* @nb_divide@ */ +#endif + 0, /* @nb_remainder@ */ + 0, /* @nb_divmod@ */ + 0, /* @nb_power@ */ + 0, /* @nb_negative@ */ + 0, /* @nb_positive@ */ + 0, /* @nb_absolute@ */ + 0, /* @nb_nonzero@ */ + 0, /* @nb_invert@ */ + 0, /* @nb_lshift@ */ + 0, /* @nb_rshift@ */ + view_pyand, /* @nb_and@ */ + view_pyxor, /* @nb_xor@ */ + view_pyor, /* @nb_or@ */ + 0, /* @nb_coerce@ */ + 0, /* @nb_int@ */ + 0, /* @nb_long@ */ + 0, /* @nb_float@ */ + 0, /* @nb_oct@ */ + 0, /* @nb_hex@ */ +}; + +static const PySequenceMethods keyview_pysequence = { + view_pysize, /* @sq_length@ */ + 0, /* @sq_concat@ */ + 0, /* @sq_repeat@ */ + 0, /* @sq_item@ */ + 0, /* @sq_slice@ */ + 0, /* @sq_ass_item@ */ + 0, /* @sq_ass_slice@ */ + keyview_pyhaskey, /* @sq_contains@ */ + 0, /* @sq_inplace_concat@ */ + 0, /* @sq_inplace_repeat@ */ +}; + +static const PyTypeObject keyview_pytype_skel = { + PyVarObject_HEAD_INIT(0, 0) /* Header */ + "_KeyView", /* @tp_name@ */ + sizeof(view_pyobj), /* @tp_basicsize@ */ + 0, /* @tp_itemsize@ */ + + view_pydealloc, /* @tp_dealloc@ */ + 0, /* @tp_print@ */ + 0, /* @tp_getattr@ */ + 0, /* @tp_setattr@ */ + 0, /* @tp_compare@ */ + 0, /* @tp_repr@ */ + PYNUMBER(view), /* @tp_as_number@ */ + PYSEQUENCE(keyview), /* @tp_as_sequence@ */ + 0, /* @tp_as_mapping@ */ + 0, /* @tp_hash@ */ + 0, /* @tp_call@ */ + 0, /* @tp_str@ */ + 0, /* @tp_getattro@ */ + 0, /* @tp_setattro@ */ + 0, /* @tp_as_buffer@ */ + Py_TPFLAGS_DEFAULT | /* @tp_flags@ */ + Py_TPFLAGS_BASETYPE, + + /* @tp_doc@ */ + "View of a mapping's keys.", + + 0, /* @tp_traverse@ */ + 0, /* @tp_clear@ */ + view_pyrichcompare, /* @tp_richcompare@ */ + 0, /* @tp_weaklistoffset@ */ + keyview_pyiter, /* @tp_iter@ */ + 0, /* @tp_iternext@ */ + 0, /* @tp_methods@ */ + 0, /* @tp_members@ */ + 0, /* @tp_getset@ */ + 0, /* @tp_base@ */ + 0, /* @tp_dict@ */ + 0, /* @tp_descr_get@ */ + 0, /* @tp_descr_set@ */ + 0, /* @tp_dictoffset@ */ + 0, /* @tp_init@ */ + PyType_GenericAlloc, /* @tp_alloc@ */ + abstract_pynew, /* @tp_new@ */ + 0, /* @tp_free@ */ + 0 /* @tp_is_gc@ */ +}; + +static const PySequenceMethods valview_pysequence = { + view_pysize, /* @sq_length@ */ + 0, /* @sq_concat@ */ + 0, /* @sq_repeat@ */ + 0, /* @sq_item@ */ + 0, /* @sq_slice@ */ + 0, /* @sq_ass_item@ */ + 0, /* @sq_ass_slice@ */ + 0, /* @sq_contains@ */ + 0, /* @sq_inplace_concat@ */ + 0, /* @sq_inplace_repeat@ */ +}; + +static const PyTypeObject valview_pytype_skel = { + PyVarObject_HEAD_INIT(0, 0) /* Header */ + "_ValueView", /* @tp_name@ */ + sizeof(view_pyobj), /* @tp_basicsize@ */ + 0, /* @tp_itemsize@ */ + + view_pydealloc, /* @tp_dealloc@ */ + 0, /* @tp_print@ */ + 0, /* @tp_getattr@ */ + 0, /* @tp_setattr@ */ + 0, /* @tp_compare@ */ + 0, /* @tp_repr@ */ + PYNUMBER(view), /* @tp_as_number@ */ + PYSEQUENCE(valview), /* @tp_as_sequence@ */ + 0, /* @tp_as_mapping@ */ + 0, /* @tp_hash@ */ + 0, /* @tp_call@ */ + 0, /* @tp_str@ */ + 0, /* @tp_getattro@ */ + 0, /* @tp_setattro@ */ + 0, /* @tp_as_buffer@ */ + Py_TPFLAGS_DEFAULT | /* @tp_flags@ */ + Py_TPFLAGS_BASETYPE, + + /* @tp_doc@ */ + "View of a mapping's values.", + + 0, /* @tp_traverse@ */ + 0, /* @tp_clear@ */ + 0, /* @tp_richcompare@ */ + 0, /* @tp_weaklistoffset@ */ + valview_pyiter, /* @tp_iter@ */ + 0, /* @tp_iternext@ */ + 0, /* @tp_methods@ */ + 0, /* @tp_members@ */ + 0, /* @tp_getset@ */ + 0, /* @tp_base@ */ + 0, /* @tp_dict@ */ + 0, /* @tp_descr_get@ */ + 0, /* @tp_descr_set@ */ + 0, /* @tp_dictoffset@ */ + 0, /* @tp_init@ */ + PyType_GenericAlloc, /* @tp_alloc@ */ + abstract_pynew, /* @tp_new@ */ + 0, /* @tp_free@ */ + 0 /* @tp_is_gc@ */ +}; + +static const PySequenceMethods itemview_pysequence = { + view_pysize, /* @sq_length@ */ + 0, /* @sq_concat@ */ + 0, /* @sq_repeat@ */ + 0, /* @sq_item@ */ + 0, /* @sq_slice@ */ + 0, /* @sq_ass_item@ */ + 0, /* @sq_ass_slice@ */ + itemview_pyhaskey, /* @sq_contains@ */ + 0, /* @sq_inplace_concat@ */ + 0, /* @sq_inplace_repeat@ */ +}; + +static const PyTypeObject itemview_pytype_skel = { + PyVarObject_HEAD_INIT(0, 0) /* Header */ + "_ItemView", /* @tp_name@ */ + sizeof(view_pyobj), /* @tp_basicsize@ */ + 0, /* @tp_itemsize@ */ + + view_pydealloc, /* @tp_dealloc@ */ + 0, /* @tp_print@ */ + 0, /* @tp_getattr@ */ + 0, /* @tp_setattr@ */ + 0, /* @tp_compare@ */ + 0, /* @tp_repr@ */ + PYNUMBER(view), /* @tp_as_number@ */ + PYSEQUENCE(itemview), /* @tp_as_sequence@ */ + 0, /* @tp_as_mapping@ */ + 0, /* @tp_hash@ */ + 0, /* @tp_call@ */ + 0, /* @tp_str@ */ + 0, /* @tp_getattro@ */ + 0, /* @tp_setattro@ */ + 0, /* @tp_as_buffer@ */ + Py_TPFLAGS_DEFAULT | /* @tp_flags@ */ + Py_TPFLAGS_BASETYPE, + + /* @tp_doc@ */ + "View of a mapping's key/value items.", + + 0, /* @tp_traverse@ */ + 0, /* @tp_clear@ */ + view_pyrichcompare, /* @tp_richcompare@ */ + 0, /* @tp_weaklistoffset@ */ + itemview_pyiter, /* @tp_iter@ */ + 0, /* @tp_iternext@ */ + 0, /* @tp_methods@ */ + 0, /* @tp_members@ */ + 0, /* @tp_getset@ */ + 0, /* @tp_base@ */ + 0, /* @tp_dict@ */ + 0, /* @tp_descr_get@ */ + 0, /* @tp_descr_set@ */ + 0, /* @tp_dictoffset@ */ + 0, /* @tp_init@ */ + PyType_GenericAlloc, /* @tp_alloc@ */ + abstract_pynew, /* @tp_new@ */ + 0, /* @tp_free@ */ + 0 /* @tp_is_gc@ */ +}; + +#endif + /*----- Other mapping protocol support ------------------------------------*/ Py_ssize_t gmap_pysize(PyObject *me) @@ -319,6 +667,19 @@ const PySequenceMethods gmap_pysequence = { 0 /* @sq_inplace_repeat@ */ }; +#ifdef PY3 + +PyObject *gmapmeth_keys(PyObject *me) + { return (gmap_mkview(me, keyview_pytype)); } + +PyObject *gmapmeth_values(PyObject *me) + { return (gmap_mkview(me, valview_pytype)); } + +PyObject *gmapmeth_items(PyObject *me) + { return (gmap_mkview(me, itemview_pytype)); } + +#else + PyObject *gmapmeth_has_key(PyObject *me, PyObject *arg) { PyObject *k; @@ -408,6 +769,8 @@ PyObject *gmapmeth_itervalues(PyObject *me) PyObject *gmapmeth_iteritems(PyObject *me) { return (gmap_mkiter(me, itemiter_pytype)); } +#endif + PyObject *gmap_pyiter(PyObject *me) { return gmap_mkiter(me, keyiter_pytype); } @@ -493,8 +856,12 @@ static int update_core(PyObject *me, PyObject *map) unsigned foundp; int rc = -1; - v = PyObject_CallMethod(map, "iteritems", 0); + v = PyObject_CallMethod(map, PY23("iteritems", "items"), 0); +#ifdef PY3 + if (v) { i = PyObject_GetIter(v); Py_DECREF(v); v = 0; } +#else i = v; v = 0; +#endif if (i) { for (;;) { @@ -576,6 +943,11 @@ void pyke_gmap_pyinit(void) INITTYPE(keyiter, root); INITTYPE(itemiter, root); INITTYPE(valiter, root); +#ifdef PY3 + INITTYPE(keyview, root); + INITTYPE(valview, root); + INITTYPE(itemview, root); +#endif } void pyke_gmap_pyinsert(PyObject *mod) @@ -583,6 +955,11 @@ void pyke_gmap_pyinsert(PyObject *mod) INSERT("_KeyIter", keyiter_pytype); INSERT("_ValueIter", valiter_pytype); INSERT("_ItemIter", itemiter_pytype); +#ifdef PY3 + INSERT("_KeyView", keyview_pytype); + INSERT("_ValueView", valview_pytype); + INSERT("_ItemView", itemview_pytype); +#endif } /*----- That's all, folks -------------------------------------------------*/ diff --git a/pyke/pyke-mLib.c b/pyke/pyke-mLib.c index fd6f895..30f6735 100644 --- a/pyke/pyke-mLib.c +++ b/pyke/pyke-mLib.c @@ -52,8 +52,10 @@ PyObject *getk64(kludge64 u) Py_DECREF(i); i = t; if ((j = PyLong_FromUnsignedLong(LO64(u))) == 0) goto end; if ((t = PyNumber_InPlaceOr(i, j)) == 0) goto end; +# ifdef PY2 Py_DECREF(i); i = t; if ((t = PyNumber_Int(i)) == 0) goto end; +# endif rc = t; end: Py_XDECREF(i); diff --git a/pyke/pyke.c b/pyke/pyke.c index 03f1c1f..330f15c 100644 --- a/pyke/pyke.c +++ b/pyke/pyke.c @@ -50,11 +50,13 @@ int convulong(PyObject *o, void *pp) PyObject *t; if (!o) VALERR("can't delete"); +#ifdef PY2 if (PyInt_Check(o)) { long i = PyInt_AS_LONG(o); if (i < 0) VALERR("must be nonnegative"); *p = i; } else +#endif { if ((t = PyNumber_Long(o)) == 0) goto end; *p = PyLong_AsUnsignedLong(t); @@ -110,6 +112,7 @@ int convbin(PyObject *o, void *pp) r->sz = BIN_LEN(o); return (1); } +#ifdef PY2 if (PyUnicode_Check(o)) { o = _PyUnicode_AsDefaultEncodedString(o, 0); if (!o) return (0); @@ -117,6 +120,7 @@ int convbin(PyObject *o, void *pp) r->sz = PyString_GET_SIZE(o); return (1); } +#endif return (PyObject_AsReadBuffer(o, &r->p, &r->sz) ? 0 : 1); } @@ -274,6 +278,9 @@ void *newtype(PyTypeObject *metaty, if (ty->ht_name) ty->ht_type.tp_name = TEXT_STR(ty->ht_name); ty->ht_slots = 0; +#ifdef PY3 + ty->ht_qualname = 0; +#endif (void)PyObject_INIT(&ty->ht_type, metaty); Py_INCREF(metaty); return (ty); @@ -281,6 +288,10 @@ void *newtype(PyTypeObject *metaty, void typeready(PyTypeObject *ty) { +#ifdef PY3 + PyHeapTypeObject *hty = (PyHeapTypeObject *)ty; + hty->ht_qualname = hty->ht_name; +#endif PyType_Ready(ty); PyDict_SetItemString(ty->tp_dict, "__module__", modname); } @@ -312,7 +323,8 @@ PyObject *mkexc(PyObject *mod, PyObject *base, while (mm->ml_name) { if ((func = PyCFunction_NewEx((/*unconst*/ PyMethodDef *)mm, 0, mod)) == 0 || - (meth = PyMethod_New(func, 0, exc)) == 0 || + (meth = PY23(PyMethod_New(func, 0, exc), + PyInstanceMethod_New(func))) == 0 || PyDict_SetItemString(dict, mm->ml_name, meth)) goto fail; Py_DECREF(func); func = 0; diff --git a/pyke/pyke.h b/pyke/pyke.h index 06c30c2..a60fd76 100644 --- a/pyke/pyke.h +++ b/pyke/pyke.h @@ -64,6 +64,15 @@ PRIVATE_SYMBOLS; /*----- Python version compatibility hacks --------------------------------*/ +/* Explicit version switching. */ +#if PY_VERSION_HEX >= 0x03000000 +# define PY3 1 +# define PY23(two, three) three +#else +# define PY2 1 +# define PY23(two, three) two +#endif + /* The handy `Py_TYPE' and `Py_SIZE' macros turned up in 2.6. Define them if * they're not already here. */ @@ -82,6 +91,18 @@ PRIVATE_SYMBOLS; # define PyVarObject_HEAD_INIT(super, sz) PyObject_HEAD_INIT(super) sz, #endif +/* Python 3 doesn't have `int', only `long', even though it's called `int' at + * the Python level. Provide some obvious macros to fill in the gaps. + */ +#ifdef PY3 +# define PyInt_Check PyLong_Check +# define PyInt_FromLong PyLong_FromLong +# define PyInt_AS_LONG PyLong_AS_LONG +# define PyInt_AsLong PyLong_AsLong +# define PyInt_AsUnsignedLongMask PyLong_AsUnsignedLongMask +# define PyNumber_Int PyNumber_Long +#endif + /* Python 3.2 changed the type of hash values, so paper over this annoying * difference. */ @@ -89,6 +110,13 @@ PRIVATE_SYMBOLS; typedef long Py_hash_t; #endif +/* Python 3 always has the `CHECKTYPES' behaviour, and doesn't define the + * flag. + */ +#ifdef PY3 +# define Py_TPFLAGS_CHECKTYPES 0 +#endif + /* Plain octet strings. Python 2 calls these `str', while Python 3 calls * them `bytes'. We call them `bin' here, and define the following. * @@ -115,6 +143,25 @@ PRIVATE_SYMBOLS; # * `YN' is a format character for `PyArg_ParseTuple...' for retrieving an * octet string and length from any sort-of vaguely binary-ish object. */ +#ifdef PY3 +# define BINOBJ PyBytesObject +# define BIN_TYPE PyBytes_Type +# define BIN_CHECK(obj) PyBytes_Check(obj) +# define BIN_PTR(obj) PyBytes_AS_STRING(obj) +# define BIN_LEN(obj) PyBytes_GET_SIZE(obj) +# define BIN_FROMSTR(str) PyBytes_FromString(str) +# define BIN_FROMSTRLEN(str, len) PyBytes_FromStringAndSize(str, len) +# define BIN_FORMAT PyBytes_FromFormat +# define BIN_VFORMAT PyBytes_FromFormatV +# define BIN_PREPAREWRITE(obj, ptr, sz) do { \ + (obj) = PyBytes_FromStringAndSize(0, (sz)); \ + (ptr) = PyBytes_AS_STRING(obj); \ + } while (0) +# define BIN_DONEWRITE(obj, sz) do Py_SIZE(obj) = (sz); while (0) +# define BIN_SETLEN(obj, len) do Py_SIZE(obj) = (len); while (0) +# define Y "y" +# define YN "y#" +#else # define BINOBJ PyStringObject # define BIN_TYPE PyString_Type # define BIN_CHECK(obj) PyString_Check(obj) @@ -132,6 +179,7 @@ PRIVATE_SYMBOLS; # define BIN_SETLEN(obj, len) do Py_SIZE(obj) = (len); while (0) # define Y "s" # define YN "s#" +#endif /* Text strings. Both Python 2 and Python 3 call these `str', but they're * very different because a Python 3 `str' is Unicode inside. When dealing @@ -161,6 +209,52 @@ PRIVATE_SYMBOLS; * * (Use `s' and `s#' in `PyArg_ParseTuple...'.) */ +#ifdef PY3 +# define TEXTOBJ PyUnicodeObject +# define TEXT_TYPE PyUnicode_Type +# define TEXT_CHECK(obj) PyUnicode_Check(obj) +# if PY_VERSION_HEX >= 0x03030000 +# define TEXT_PTR(obj) PyUnicode_AsUTF8(obj) +# define TEXT_STR(obj) PyUnicode_AsUTF8(obj) +# define TEXT_PTRLEN(obj, ptr, len) do { \ + Py_ssize_t len_; \ + (ptr) = PyUnicode_AsUTF8AndSize((obj), &len_); \ + (len) = len_; \ + } while (0) +# define TEXT_PREPAREWRITE(obj, ptr, sz) do { \ + (obj) = PyUnicode_New((sz), 127); \ + (ptr) = PyUnicode_DATA(obj); \ + } while (0) +# define TEXT_DONEWRITE(obj, len) do { \ + size_t len_ = (len); \ + assert(PyUnicode_IS_COMPACT_ASCII(obj)); \ + ((char *)PyUnicode_DATA(obj))[len_] = 0; \ + ((PyASCIIObject *)(obj))->length = len_; \ + } while (0) +# else +# define TEXT_PTR(obj) _PyUnicode_AsString(obj) +# define TEXT_STR(obj) _PyUnicode_AsString(obj) +# define TEXT_PTRLEN(obj, ptr, len) do { \ + Py_ssize_t len_; \ + (ptr) = _PyUnicode_AsStringAndSize((obj), &len_); \ + (len) = len_; \ + } while (0) +# define TEXT_PREPAREWRITE(obj, ptr, sz) do { \ + (obj) = PyBytes_FromStringAndSize(0, (sz)); \ + (ptr) = PyBytes_AS_STRING(obj); \ + } while (0) +# define TEXT_DONEWRITE(obj, len) do { \ + PyObject *new_; \ + Py_SIZE(obj) = (len); \ + new_ = PyUnicode_FromEncodedObject(obj, 0, 0); \ + assert(new_); Py_DECREF(obj); (obj) = new_; \ + } while (0) +# endif +# define TEXT_FORMAT PyUnicode_FromFormat +# define TEXT_VFORMAT PyUnicode_FromFormatV +# define TEXT_FROMSTR(str) PyUnicode_FromString(str) +# define TEXT_FROMSTRLEN(str, len) PyUnicode_FromStringAndSize(str, len) +#else # define TEXTOBJ PyStringObject # define TEXT_TYPE PyString_Type # define TEXT_CHECK(obj) PyString_Check(obj) @@ -179,6 +273,7 @@ PRIVATE_SYMBOLS; # define TEXT_DONEWRITE(obj, sz) do { Py_SIZE(obj) = (sz); } while (0) # define TEXT_FROMSTR(str) PyString_FromString(str) # define TEXT_FROMSTRLEN(str, len) PyString_FromStringAndSize(str, len) +#endif /*----- Utilities for returning values and exceptions ---------------------*/ @@ -556,6 +651,13 @@ typedef struct gmap_pyobj { #define GMAP_NAMETHDECL(func, doc) \ extern PyObject *gmapmeth_##func(PyObject *); +#ifdef PY3 +# define GMAP_DOROMETHODS(METH, KWMETH, NAMETH) \ + NAMETH(keys, "D.keys() -> LIST") \ + NAMETH(values, "D.values() -> LIST") \ + NAMETH(items, "D.items() -> LIST") \ + KWMETH(get, "D.get(KEY, [default = None]) -> VALUE") +#else # define GMAP_DOROMETHODS(METH, KWMETH, NAMETH) \ METH (has_key, "D.has_key(KEY) -> BOOL") \ NAMETH(keys, "D.keys() -> LIST") \ @@ -565,6 +667,7 @@ typedef struct gmap_pyobj { NAMETH(itervalues, "D.itervalues() -> ITER") \ NAMETH(iteritems, "D.iteritems() -> ITER") \ KWMETH(get, "D.get(KEY, [default = None]) -> VALUE") +#endif #define GMAP_DOMETHODS(METH, KWMETH, NAMETH) \ GMAP_DOROMETHODS(METH, KWMETH, NAMETH) \ diff --git a/t/t-bytes.py b/t/t-bytes.py index 36a3f9f..27b7e49 100644 --- a/t/t-bytes.py +++ b/t/t-bytes.py @@ -47,10 +47,16 @@ class TestByteString (U.TestCase): x = C.ByteString(T.bin("once upon a time there was a string")) - ## Check that simple indexing works. - me.assertEqual(type(x[3]), C.ByteString) - me.assertEqual(x[3], 'e') - me.assertEqual(x[-5], 't') + ## Check that simple indexing works. Alas the behaviour differs between + ## Python major versions. + if T.PY3: + me.assertEqual(type(x[3]), int) + me.assertEqual(x[3], 101) + me.assertEqual(x[-5], 116) + else: + me.assertEqual(type(x[3]), C.ByteString) + me.assertEqual(x[3], 'e') + me.assertEqual(x[-5], 't') ## Check out-of-range detection. x[34]; me.assertRaises(IndexError, lambda: x[35]) diff --git a/t/t-convert.py b/t/t-convert.py index 08c660f..cadf7b5 100644 --- a/t/t-convert.py +++ b/t/t-convert.py @@ -54,7 +54,7 @@ class TestConvert (U.TestCase): for bad in [lambda x: [x]]: me.assertRaises(TypeError, pow, C.MP(5), bad(2)) me.assertRaises(TypeError, pow, C.MP(5), bad(2), 7) - if not T.DEBUGP: + if not (T.PY2 and T.DEBUGP): ## Debug builds of Python 2 crash here, and it's not our fault. Run ## ## $ python2.7-dbg -c 'pow(long(5), 2, [7])' @@ -84,7 +84,7 @@ class TestConvert (U.TestCase): me.assertRaises(TypeError, pow, bad(5), C.GF(2), bad(7)) me.assertRaises(TypeError, pow, bad(5), bad(2), C.GF(7)) me.assertRaises(TypeError, pow, C.GF(5), bad(2), bad(7)) - if not T.DEBUGP: + if not (T.PY2 and T.DEBUGP): ## Python bug: see above. me.assertRaises(TypeError, pow, C.GF(5), 2, bad(7)) diff --git a/t/t-mp.py b/t/t-mp.py index 93a1e2f..a1dd657 100644 --- a/t/t-mp.py +++ b/t/t-mp.py @@ -71,7 +71,8 @@ class TestMP (U.TestCase): me.assertEqual(str(y), '6556380541834372447694561492436749633') me.assertEqual(repr(y), 'MP(6556380541834372447694561492436749633)') me.assertEqual(hex(y), '0x4eeb684a0954ec4ceb255e3e9778d41') - me.assertEqual(oct(y), '047353320450112516611472622536175135706501') + me.assertEqual(oct(y), T.py23('0', '0o') + + '47353320450112516611472622536175135706501') try: bin except NameError: pass else: me.assertEqual(bin(C.MP(661438603)), @@ -417,7 +418,8 @@ class TestGF (U.TestCase): me.assertEqual(str(y), '0x4eeb684a0954ec4ceb255e3e9778d41') me.assertEqual(repr(y), 'GF(0x4eeb684a0954ec4ceb255e3e9778d41)') me.assertEqual(hex(y), '0x4eeb684a0954ec4ceb255e3e9778d41') - me.assertEqual(oct(y), '047353320450112516611472622536175135706501') + me.assertEqual(oct(y), T.py23('0', '0o') + + '47353320450112516611472622536175135706501') try: bin except NameError: pass else: me.assertEqual(bin(C.GF(661438603)), diff --git a/t/testutils.py b/t/testutils.py index b63a902..0ba49c6 100644 --- a/t/testutils.py +++ b/t/testutils.py @@ -37,17 +37,33 @@ import unittest as U ### Main code. ## Some compatibility hacks. -import itertools as I -def bin(x): return x -range = xrange -long = long -imap = I.imap -def byteseq(seq): return "".join(map(chr, seq)) -def iterkeys(m): return m.iterkeys() -def itervalues(m): return m.itervalues() -def iteritems(m): return m.iteritems() -from cStringIO import StringIO -MAXFIXNUM = SYS.maxint +if SYS.version_info >= (3,): + PY2, PY3 = False, True + def bin(x): return x.encode('iso8859-1') + def py23(x, y): return y + range = range + byteseq = bytes + long = int + imap = map + def iterkeys(m): return m.keys() + def itervalues(m): return m.values() + def iteritems(m): return m.items() + from io import StringIO + MAXFIXNUM = SYS.maxsize +else: + import itertools as I + PY2, PY3 = True, False + def bin(x): return x + def py23(x, y): return x + range = xrange + long = long + imap = I.imap + def byteseq(seq): return "".join(map(chr, seq)) + def iterkeys(m): return m.iterkeys() + def itervalues(m): return m.itervalues() + def iteritems(m): return m.iteritems() + from cStringIO import StringIO + MAXFIXNUM = SYS.maxint DEBUGP = hasattr(SYS, "gettotalrefcount") @@ -127,24 +143,86 @@ class ImmutableMappingTextMixin (U.TestCase): any = True if k >= limk: limk = k + 1 me.assertTrue(me._mkkey(k) in map) - me.assertTrue(map.has_key(me._mkkey(k))) + if PY2: me.assertTrue(map.has_key(me._mkkey(k))) me.assertEqual(me._getvalue(map[me._mkkey(k)]), v) me.assertEqual(me._getvalue(map.get(me._mkkey(k))), v) if any: me.assertTrue(me._mkkey(k) in map) - me.assertFalse(map.has_key(me._mkkey(limk))) + if PY2: me.assertFalse(map.has_key(me._mkkey(limk))) me.assertRaises(KeyError, lambda: map[me._mkkey(limk)]) me.assertEqual(map.get(me._mkkey(limk)), None) - for listfn, getfn in [(lambda x: x.keys(), me._getkey), - (lambda x: x.values(), me._getvalue), - (lambda x: x.items(), me._getitem)]: - rlist, mlist = listfn(map), listfn(model) - me.assertEqual(type(rlist), list) - rlist = B.map(getfn, rlist) - rlist.sort(); mlist.sort(); me.assertEqual(rlist, mlist) - for iterfn, getfn in [(lambda x: x.iterkeys(), me._getkey), - (lambda x: x.itervalues(), me._getvalue), - (lambda x: x.iteritems(), me._getitem)]: - me.assertEqual(set(imap(getfn, iterfn(map))), set(iterfn(model))) + + if PY3: + empty = set() + + for k, v in iteritems(map): + me.assertTrue(k in map.keys()) + me.assertTrue((k, v) in map.items()) + me.assertFalse(me._mkkey(limk) in map.keys()) + + for viewfn, getfn in [(lambda x: x.keys(), me._getkey), + (lambda x: x.items(), me._getitem)]: + rview, rview2, mview = viewfn(map), viewfn(map), viewfn(model) + me.assertEqual(set(imap(getfn, rview)), set(mview)) + me.assertEqual(rview, rview2) + me.assertEqual(rview, set(rview2)) + me.assertEqual(rview | empty, set(rview)) + me.assertEqual(rview | rview2, set(rview)) + me.assertEqual(rview ^ empty, set(rview)) + me.assertEqual(rview ^ rview, empty) + me.assertEqual(rview & empty, empty) + me.assertEqual(len(rview), len(model)) + + if any: subset = set(rview2); subset.pop() + superset = set(rview2); superset.add(object()) + + me.assertFalse(rview < rview2) + me.assertTrue(rview < superset) + me.assertFalse(superset < rview) + me.assertFalse(rview < empty) + if any: + me.assertTrue(empty < rview) + me.assertTrue(subset < rview) + me.assertFalse(rview < subset) + + me.assertTrue(rview <= rview2) + me.assertTrue(rview <= superset) + me.assertFalse(superset <= rview) + if any: + me.assertTrue(empty <= rview) + me.assertFalse(rview <= empty) + me.assertTrue(subset <= rview) + me.assertFalse(rview <= subset) + + me.assertTrue(rview >= rview2) + me.assertTrue(superset >= rview) + me.assertFalse(rview >= superset) + if any: + me.assertTrue(rview >= empty) + me.assertFalse(empty >= rview) + me.assertTrue(rview >= subset) + me.assertFalse(subset >= rview) + + me.assertFalse(rview > rview2) + me.assertTrue(superset > rview) + me.assertFalse(rview > superset) + me.assertFalse(empty > rview) + if any: + me.assertTrue(rview > empty) + me.assertTrue(rview > subset) + me.assertFalse(subset > rview) + + else: + for listfn, getfn in [(lambda x: x.keys(), me._getkey), + (lambda x: x.values(), me._getvalue), + (lambda x: x.items(), me._getitem)]: + rlist, mlist = listfn(map), listfn(model) + me.assertEqual(type(rlist), list) + rlist = B.map(getfn, rlist) + rlist.sort(); mlist.sort(); me.assertEqual(rlist, mlist) + for iterfn, getfn in [(lambda x: x.iterkeys(), me._getkey), + (lambda x: x.itervalues(), me._getvalue), + (lambda x: x.iteritems(), me._getitem)]: + me.assertEqual(set(imap(getfn, iterfn(map))), set(iterfn(model))) class MutableMappingTestMixin (ImmutableMappingTextMixin): @@ -156,8 +234,16 @@ class MutableMappingTestMixin (ImmutableMappingTextMixin): map = emptymapfn() me.assertEqual(len(map), 0) - def check_views(): - me.check_immutable_mapping(map, model) + if not PY3: + def check_views(): + me.check_immutable_mapping(map, model) + else: + kview, iview, vview = map.keys(), map.items(), map.values() + def check_views(): + me.check_immutable_mapping(map, model) + me.assertEqual(set(imap(me._getkey, kview)), model.keys()) + me.assertEqual(set(imap(me._getitem, iview)), model.items()) + me.assertEqual(set(imap(me._getvalue, vview)), set(model.values())) model = { 1: 101, 2: 202, 4: 404 } for k, v in iteritems(model): map[me._mkkey(k)] = me._mkvalue(v)