From 66f05215769ac69f9cbab51cda7a5ee0dd24590b Mon Sep 17 00:00:00 2001 From: tianfenghan Date: Mon, 25 Dec 2023 15:31:25 +0800 Subject: [PATCH] Import array/string object --- CMakeLists.txt | 2 +- include/phpy.h | 2 + src/bridge/core.cc | 19 ++++++++ src/python/array.cc | 82 +++++++++++++++++++++----------- src/python/string.cc | 111 ++++++++++++++++++++++++++++++++----------- tests/test_array.py | 19 +++++++- tests/test_stream.py | 6 +-- tests/test_string.py | 18 +++++-- 8 files changed, 196 insertions(+), 63 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c109254..7d851fb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,7 +4,7 @@ project(phpy) #set(CMAKE_BUILD_TYPE Released) set(CMAKE_CXX_STANDARD 14) -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -Wall -g -z now") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -Wall -g") if (NOT DEFINED PHP_CONFIG) set(PHP_CONFIG "php-config") diff --git a/include/phpy.h b/include/phpy.h index 500f082..69b4506 100644 --- a/include/phpy.h +++ b/include/phpy.h @@ -304,12 +304,14 @@ namespace python { PyObject *new_array(zval *zv); PyObject *new_array(PyObject *pv); PyObject *new_string(zval *zv); +PyObject *new_string(size_t len); PyObject *new_string(PyObject *pv); PyObject *new_object(zval *zv); PyObject *new_resource(zval *zv); PyObject *new_reference(zval *zv); PyObject *new_callable(zval *zv); const char *string2utf8(PyObject *pv, ssize_t *len); +const char *string2char_ptr(PyObject *pv, ssize_t *len); void tuple2argv(zval *argv, PyObject *args, ssize_t size, int begin = 1); void release_argv(uint32_t argc, zval *argv); } // namespace python diff --git a/src/bridge/core.cc b/src/bridge/core.cc index 3673b89..2279d92 100644 --- a/src/bridge/core.cc +++ b/src/bridge/core.cc @@ -433,6 +433,25 @@ namespace python { const char *string2utf8(PyObject *pv, ssize_t *len) { return PyUnicode_AsUTF8AndSize(pv, len); }; +const char *string2char_ptr(PyObject *pv, ssize_t *len) { + const char *c_str; + if (ZendString_Check(pv)) { + zval *z2 = zend_string_cast(pv); + *len = Z_STRLEN_P(z2); + c_str = Z_STRVAL_P(z2); + } else if (PyByteArray_Check(pv)) { + c_str = PyByteArray_AS_STRING(pv); + *len = PyByteArray_GET_SIZE(pv); + } else if (PyBytes_Check(pv)) { + c_str = PyBytes_AS_STRING(pv); + *len = PyBytes_GET_SIZE(pv); + } else if (PyUnicode_Check(pv)) { + c_str = PyUnicode_AsUTF8AndSize(pv, len); + } else { + return NULL; + } + return c_str; +} void tuple2argv(zval *argv, PyObject *args, ssize_t size, int begin) { Py_ssize_t i; for (i = begin; i < size; i++) { diff --git a/src/python/array.cc b/src/python/array.cc index f8b69c4..4d3a35d 100644 --- a/src/python/array.cc +++ b/src/python/array.cc @@ -31,6 +31,8 @@ struct ZendArray { zval array; }; +static PyMappingMethods Array_mp_methods = {}; + static PyMethodDef Array_methods[] = { {"get", (PyCFunction) Array_get, METH_VARARGS, "Get array item value" }, {"set", (PyCFunction) Array_set, METH_VARARGS, "Set array item value" }, @@ -63,12 +65,8 @@ static int Array_init(ZendArray *self, PyObject *args, PyObject *kwds) { return 0; } -static PyObject *Array_get(ZendArray *self, PyObject *args) { +static PyObject *Array_getitem(ZendArray *self, PyObject *key) { zval *result; - PyObject *key; - if (!PyArg_ParseTuple(args, "O", &key)) { - return NULL; - } if (PyLong_Check(key)) { result = phpy::php::array_get(&self->array, PyLong_AsLong(key)); } else { @@ -77,50 +75,71 @@ static PyObject *Array_get(ZendArray *self, PyObject *args) { result = phpy::php::array_get(&self->array, skey, l_key); } if (!result) { - Py_INCREF(Py_None); - return Py_None; + Py_RETURN_NONE; } return php2py_object(result); } -static PyObject *Array_set(ZendArray *self, PyObject *args) { - PyObject *value; +static PyObject *Array_get(ZendArray *self, PyObject *args) { PyObject *key; - if (!PyArg_ParseTuple(args, "OO", &key, &value)) { + if (!PyArg_ParseTuple(args, "O", &key)) { return NULL; } + return Array_getitem(self, key); +} + +static bool Array_delitem(ZendArray *self, PyObject *key) { + zend_result result; + if (PyLong_Check(key)) { + result = zend_hash_index_del(Z_ARR(self->array), PyLong_AsLong(key)); + } else { + ssize_t l_key; + auto skey = phpy::python::string2utf8(key, &l_key); + result = zend_hash_str_del(Z_ARR(self->array), skey, l_key); + } + return result == SUCCESS; +} + +static int Array_setitem(ZendArray *self, PyObject *key, PyObject *value) { + // value be set to NULL to delete an item + if (value == NULL) { + return Array_delitem(self, key) ? 0 : -1; + } zval rv; py2php(value, &rv); + zval *result; if (PyLong_Check(key)) { - zend_hash_index_update(Z_ARR(self->array), PyLong_AsLong(key), &rv); + result = zend_hash_index_update(Z_ARR(self->array), PyLong_AsLong(key), &rv); } else { ssize_t l_key; auto skey = phpy::python::string2utf8(key, &l_key); - zend_hash_str_update(Z_ARR(self->array), skey, l_key, &rv); + result = zend_hash_str_update(Z_ARR(self->array), skey, l_key, &rv); } - Py_INCREF(Py_None); - return Py_None; + return result == NULL ? -1 : 0; } -static PyObject *Array_unset(ZendArray *self, PyObject *args) { - zend_result result; +static PyObject *Array_set(ZendArray *self, PyObject *args) { + PyObject *value; PyObject *key; - if (!PyArg_ParseTuple(args, "O", &key)) { + if (!PyArg_ParseTuple(args, "OO", &key, &value)) { return NULL; } - if (PyLong_Check(key)) { - result = zend_hash_index_del(Z_ARR(self->array), PyLong_AsLong(key)); + if (Array_setitem(self, key, value) == 0) { + Py_RETURN_TRUE; } else { - ssize_t l_key; - auto skey = phpy::python::string2utf8(key, &l_key); - result = zend_hash_str_del(Z_ARR(self->array), skey, l_key); + Py_RETURN_FALSE; } - if (result == SUCCESS) { - Py_INCREF(Py_True); - return Py_True; +} + +static PyObject *Array_unset(ZendArray *self, PyObject *args) { + PyObject *key; + if (!PyArg_ParseTuple(args, "O", &key)) { + return NULL; + } + if (Array_delitem(self, key)) { + Py_RETURN_TRUE; } else { - Py_INCREF(Py_False); - return Py_False; + Py_RETURN_FALSE; } } @@ -128,6 +147,10 @@ static PyObject *Array_count(ZendArray *self, PyObject *args) { return PyLong_FromLong(phpy::php::array_count(&self->array)); } +static Py_ssize_t Array_len(ZendArray *self) { + return phpy::php::array_count(&self->array); +} + static void Array_destroy(ZendArray *self) { zval_ptr_dtor(&self->array); Py_TYPE(self)->tp_free((PyObject *) self); @@ -135,10 +158,15 @@ static void Array_destroy(ZendArray *self) { } bool py_module_array_init(PyObject *m) { + Array_mp_methods.mp_length = (lenfunc) Array_len; + Array_mp_methods.mp_subscript = (binaryfunc) Array_getitem; + Array_mp_methods.mp_ass_subscript = (objobjargproc) Array_setitem; + ZendArrayType.tp_name = "zend_array"; ZendArrayType.tp_basicsize = sizeof(ZendArray); ZendArrayType.tp_itemsize = 0; ZendArrayType.tp_dealloc = (destructor) Array_destroy; + ZendArrayType.tp_as_mapping = &Array_mp_methods; ZendArrayType.tp_flags = Py_TPFLAGS_DEFAULT; ZendArrayType.tp_doc = PyDoc_STR("zend_array"); ZendArrayType.tp_methods = Array_methods; diff --git a/src/python/string.cc b/src/python/string.cc index e92a7bd..d03ccd9 100644 --- a/src/python/string.cc +++ b/src/python/string.cc @@ -19,10 +19,7 @@ struct ZendString; static int String_init(ZendString *self, PyObject *args, PyObject *kwds); -static PyObject *String_len(ZendString *self, PyObject *args); static PyObject *String_bytes(ZendString *self, PyObject *args); -static PyObject *String_compare(PyObject *o1, PyObject *o2, int op); -static PyObject *String_str(ZendString *self); static void String_destroy(ZendString *self); // clang-format off @@ -31,8 +28,9 @@ struct ZendString { zval string; }; +static PySequenceMethods String_sq_methods = {}; + static PyMethodDef String_methods[] = { - {"len", (PyCFunction) String_len, METH_NOARGS, "Get string length" }, {"__bytes__", (PyCFunction) String_bytes, METH_NOARGS, "Convert to bytes" }, {NULL} /* Sentinel */ }; @@ -48,13 +46,17 @@ static void String_dtor(PyObject *pv) { } static int String_init(ZendString *self, PyObject *args, PyObject *kwds) { - const char *str; - size_t len; - if (!PyArg_ParseTuple(args, "s#", &str, &len)) { + const char *str = NULL; + size_t len = 0; + if (!PyArg_ParseTuple(args, "|s#", &str, &len)) { PyErr_SetString(PyExc_TypeError, "must supply at least 1 parameter."); return -1; } - ZVAL_STRINGL(&self->string, str, len); + if (str == NULL) { + ZVAL_EMPTY_STRING(&self->string); + } else { + ZVAL_STRINGL(&self->string, str, len); + } phpy::php::add_object((PyObject *) self, String_dtor); return 0; } @@ -67,39 +69,80 @@ static PyObject *String_bytes(ZendString *self, PyObject *args) { return PyBytes_FromStringAndSize(Z_STRVAL_P(&self->string), Z_STRLEN_P(&self->string)); } +static PyObject *String_iadd(ZendString *self, PyObject *o2) { + size_t s1_len = Z_STRLEN(self->string); + ssize_t s2_len; + const char *s2 = phpy::python::string2char_ptr(o2, &s2_len); + if (s2 == NULL) { + PyErr_Format(PyExc_TypeError, "can not concat '%s' to zend_string", Py_TYPE(o2)->tp_name); + return NULL; + } + zend_string *new_zstr = zend_string_extend(Z_STR(self->string), s1_len + s2_len, 0); + if (!new_zstr) { + PyErr_SetString(PyExc_MemoryError, "memory alloc fail"); + return NULL; + } + Z_STR(self->string) = new_zstr; + memcpy(Z_STRVAL(self->string) + s1_len, s2, s2_len); + Py_INCREF(self); + return (PyObject *) self; +} + +static PyObject *String_add(ZendString *self, PyObject *o2) { + size_t s1_len = Z_STRLEN(self->string); + const char *s1 = Z_STRVAL(self->string); + ssize_t s2_len; + const char *s2 = phpy::python::string2char_ptr(o2, &s2_len); + if (s2 == NULL) { + PyErr_Format(PyExc_TypeError, "can not concat '%s' to zend_string", Py_TYPE(o2)->tp_name); + return NULL; + } + ZendString *new_str = (ZendString *) phpy::python::new_string(s1_len + (size_t) s2_len); + memcpy(Z_STRVAL(new_str->string), s1, s1_len); + memcpy(Z_STRVAL(new_str->string) + s1_len, s2, s2_len); + return (PyObject *) new_str; +} + static PyObject *String_compare(PyObject *o1, PyObject *o2, int op) { if (op != Py_EQ) { Py_RETURN_NOTIMPLEMENTED; } - bool equals; zval *z1 = zend_string_cast(o1); - if (ZendString_Check(o2)) { - zval *z2 = zend_string_cast(o2); - equals = zend_string_equal_content(Z_STR_P(z1), Z_STR_P(z2)); - } else if (PyByteArray_Check(o2)) { - const char *val = PyByteArray_AS_STRING(o2); - size_t len = PyByteArray_GET_SIZE(o2); - equals = (len == Z_STRLEN_P(z1) && memcmp(Z_STRVAL_P(z1), val, len) == 0); - } else if (PyBytes_Check(o2)) { - const char *val = PyBytes_AS_STRING(o2); - size_t len = PyBytes_GET_SIZE(o2); - equals = (len == Z_STRLEN_P(z1) && memcmp(Z_STRVAL_P(z1), val, len) == 0); - } else if (PyUnicode_Check(o2)) { - Py_ssize_t len; - const char *val = PyUnicode_AsUTF8AndSize(o2, &len); - equals = (len == (Py_ssize_t) Z_STRLEN_P(z1) && memcmp(Z_STRVAL_P(z1), val, len) == 0); - } else { + ssize_t len; + const char *val = phpy::python::string2char_ptr(o2, &len); + if (val == NULL) { Py_RETURN_NOTIMPLEMENTED; } - if (equals) { + if (len == (Py_ssize_t) Z_STRLEN_P(z1) && memcmp(Z_STRVAL_P(z1), val, len) == 0) { Py_RETURN_TRUE; } else { Py_RETURN_FALSE; } } -static PyObject *String_len(ZendString *self, PyObject *args) { - return PyLong_FromLong(Z_STRLEN_P(&self->string)); +static Py_ssize_t String_len(ZendString *self) { + return Z_STRLEN_P(&self->string); +} + +static PyObject *String_at(ZendString *self, Py_ssize_t offset) { + if (offset >= (Py_ssize_t) Z_STRLEN_P(&self->string)) { + PyErr_SetString(PyExc_IndexError, "zend_string index out of range"); + return NULL; + } + return PyLong_FromUnsignedLong((unsigned long) Z_STRVAL_P(&self->string)[offset]); +} + +static PyObject *String_contains(ZendString *self, PyObject *o2) { + ssize_t len; + const char *val = phpy::python::string2char_ptr(o2, &len); + if (val == NULL) { + Py_RETURN_NOTIMPLEMENTED; + } + if (php_memnstr(Z_STRVAL(self->string), val, len, Z_STRVAL(self->string) + Z_STRLEN(self->string))) { + Py_RETURN_TRUE; + } else { + Py_RETURN_FALSE; + } } static void String_destroy(ZendString *self) { @@ -109,10 +152,17 @@ static void String_destroy(ZendString *self) { } bool py_module_string_init(PyObject *m) { + String_sq_methods.sq_length = (lenfunc) String_len; + String_sq_methods.sq_item = (ssizeargfunc) String_at; + String_sq_methods.sq_concat = (binaryfunc) String_add; + String_sq_methods.sq_contains = (objobjproc) String_contains; + String_sq_methods.sq_inplace_concat = (binaryfunc) String_iadd; + ZendStringType.tp_name = "zend_string"; ZendStringType.tp_basicsize = sizeof(ZendString); ZendStringType.tp_itemsize = 0; ZendStringType.tp_dealloc = (destructor) String_destroy; + ZendStringType.tp_as_sequence = &String_sq_methods; ZendStringType.tp_str = (reprfunc) String_str; ZendStringType.tp_flags = Py_TPFLAGS_DEFAULT; ZendStringType.tp_doc = PyDoc_STR("zend_string"); @@ -173,5 +223,10 @@ PyObject *new_string(zval *zv) { zval_add_ref(&self->string); return (PyObject *) self; } +PyObject *new_string(size_t len) { + zval nv; + Z_STR(nv) = zend_string_alloc(len, 0); + return new_string(&nv); +} } // namespace python } // namespace phpy diff --git a/tests/test_array.py b/tests/test_array.py index 04519d3..feb534d 100644 --- a/tests/test_array.py +++ b/tests/test_array.py @@ -21,5 +21,22 @@ def test_assoc_array(): assert d.get("uuid") == str(uuid) assert d.get("php") == "swoole" assert d.unset("php") - assert d.unset("php") == False + assert d.unset("php") is False assert d.get("php") is None + + +def test_mp_protocol(): + uuid = phpy.call("uniqid") + d = phpy.Array({"hello": "world", "php": "swoole", "uuid": uuid}) + + uuid2 = phpy.call("uniqid") + + assert d['test'] is None + + assert len(d) == 3 + assert d['uuid'] == uuid + d['test'] = uuid2 + assert d['test'] == uuid2 + + del d['test'] + assert d['test'] is None diff --git a/tests/test_stream.py b/tests/test_stream.py index 239c71e..1f36b9a 100644 --- a/tests/test_stream.py +++ b/tests/test_stream.py @@ -21,8 +21,8 @@ def test_stream_client_baidu(): phpy.call('fwrite', rs, "GET / HTTP/1.0\r\nHost: www.baidu.com\r\nAccept: */*\r\n\r\n") - content = '' + content = phpy.String() while not phpy.call('feof', rs): - content += str(phpy.call('fread', rs, 8192)) + content += phpy.call('fread', rs, 8192) - assert content.find('百度搜索') != -1 + assert str(content).find('百度搜索') != -1 diff --git a/tests/test_string.py b/tests/test_string.py index 20fbfbc..7aa6063 100644 --- a/tests/test_string.py +++ b/tests/test_string.py @@ -6,12 +6,12 @@ def test_string(): s = phpy.String("hello world") - assert s.len() == 11 + assert len(s) == 11 def test_random_bytes(): rdata = phpy.call('random_bytes', 128) - assert rdata.len() == 128 + assert len(rdata) == 128 def test_base64_random_data(): @@ -23,4 +23,16 @@ def test_base64_random_data(): def test_string_to_str(): rdata = phpy.call('uniqid') - assert rdata.len() == 13 + assert len(rdata) == 13 + + +def test_string_concat(): + s = phpy.String() + s += "hello" + s += b" world" + s += phpy.String(", swoole is best") + assert s.__contains__('hello') + assert s.__contains__('world') + assert s.__contains__('swoole') + assert 'best' in s + assert s[0] == ord('h')