From 7910ca91a9005798ae8d6537f5fb55e01d07a17a Mon Sep 17 00:00:00 2001 From: Marvin Humphrey Date: Wed, 20 Jan 2016 16:25:02 -0800 Subject: [PATCH 01/19] Use Python refcounting. In our Python bindings, Clownfish objects are Python objects, so use Python's refcounting. --- runtime/python/cfext/CFBind.c | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/runtime/python/cfext/CFBind.c b/runtime/python/cfext/CFBind.c index a386e4c7..6b131201 100644 --- a/runtime/python/cfext/CFBind.c +++ b/runtime/python/cfext/CFBind.c @@ -40,23 +40,20 @@ uint32_t cfish_get_refcount(void *vself) { - CFISH_UNUSED_VAR(vself); - CFISH_THROW(CFISH_ERR, "TODO"); - CFISH_UNREACHABLE_RETURN(uint32_t); + return Py_REFCNT(vself); } cfish_Obj* cfish_inc_refcount(void *vself) { - CFISH_UNUSED_VAR(vself); - CFISH_THROW(CFISH_ERR, "TODO"); - CFISH_UNREACHABLE_RETURN(cfish_Obj*); + Py_INCREF(vself); + return (cfish_Obj*)vself; } uint32_t cfish_dec_refcount(void *vself) { - CFISH_UNUSED_VAR(vself); - CFISH_THROW(CFISH_ERR, "TODO"); - CFISH_UNREACHABLE_RETURN(uint32_t); + uint32_t modified_refcount = Py_REFCNT(vself); + Py_DECREF(vself); + return modified_refcount; } /**** Obj ******************************************************************/ From eccf1b645bb9042825d84bbe4db4041ad63e2fc1 Mon Sep 17 00:00:00 2001 From: Marvin Humphrey Date: Fri, 22 Jan 2016 15:40:08 -0800 Subject: [PATCH 02/19] Implement Clownfish exceptions using `longjmp`. Though Python uses exceptions, executing exceptions from the Python C API requires setting thread-local error information and indicating an error condition to the Python interpreter by returning NULL from the glue function. So Clownfish has to have its own independent exception handling. --- runtime/python/cfext/CFBind.c | 66 +++++++++++++++++++++++++---------- 1 file changed, 48 insertions(+), 18 deletions(-) diff --git a/runtime/python/cfext/CFBind.c b/runtime/python/cfext/CFBind.c index 6b131201..3c4bb4b6 100644 --- a/runtime/python/cfext/CFBind.c +++ b/runtime/python/cfext/CFBind.c @@ -19,6 +19,8 @@ #define C_CFISH_METHOD #define C_CFISH_ERR +#include + #include "charmony.h" #include "CFBind.h" @@ -36,6 +38,8 @@ #include "Clownfish/Util/Memory.h" #include "Clownfish/Vector.h" +static bool Err_initialized; + /**** refcounting **********************************************************/ uint32_t @@ -114,48 +118,74 @@ CFISH_Method_Host_Name_IMP(cfish_Method *self) { /**** Err ******************************************************************/ +/* TODO: Thread safety? */ +static cfish_Err *current_error; +static cfish_Err *thrown_error; +static jmp_buf *current_env; + void -cfish_Err_init_class() { - CFISH_THROW(CFISH_ERR, "TODO"); +cfish_Err_init_class(void) { + Err_initialized = true; } cfish_Err* cfish_Err_get_error() { - CFISH_THROW(CFISH_ERR, "TODO"); - CFISH_UNREACHABLE_RETURN(cfish_Err*); + return current_error; } void cfish_Err_set_error(cfish_Err *error) { - CFISH_UNUSED_VAR(error); - CFISH_THROW(CFISH_ERR, "TODO"); + if (current_error) { + CFISH_DECREF(current_error); + } + current_error = error; } void cfish_Err_do_throw(cfish_Err *error) { - CFISH_UNUSED_VAR(error); - CFISH_THROW(CFISH_ERR, "TODO"); + if (current_env) { + thrown_error = error; + longjmp(*current_env, 1); + } + else { + cfish_String *message = CFISH_Err_Get_Mess(error); + char *utf8 = CFISH_Str_To_Utf8(message); + fprintf(stderr, "%s", utf8); + CFISH_FREEMEM(utf8); + exit(EXIT_FAILURE); + } } void cfish_Err_throw_mess(cfish_Class *klass, cfish_String *message) { - CFISH_UNUSED_VAR(klass); - CFISH_UNUSED_VAR(message); - CFISH_THROW(CFISH_ERR, "TODO"); + CFISH_UNUSED_VAR(klass); // TODO use klass + cfish_Err *err = cfish_Err_new(message); + cfish_Err_do_throw(err); } void cfish_Err_warn_mess(cfish_String *message) { - CFISH_UNUSED_VAR(message); - CFISH_THROW(CFISH_ERR, "TODO"); + char *utf8 = CFISH_Str_To_Utf8(message); + fprintf(stderr, "%s", utf8); + CFISH_FREEMEM(utf8); + CFISH_DECREF(message); } cfish_Err* -cfish_Err_trap(CFISH_Err_Attempt_t routine, void *routine_context) { - CFISH_UNUSED_VAR(routine); - CFISH_UNUSED_VAR(routine_context); - CFISH_THROW(CFISH_ERR, "TODO"); - CFISH_UNREACHABLE_RETURN(cfish_Err*); +cfish_Err_trap(CFISH_Err_Attempt_t routine, void *context) { + jmp_buf env; + jmp_buf *prev_env = current_env; + current_env = &env; + + if (!setjmp(env)) { + routine(context); + } + + current_env = prev_env; + + cfish_Err *error = thrown_error; + thrown_error = NULL; + return error; } /**** TestUtils ************************************************************/ From 9162199c26ab869bff1133d517388899468b4170 Mon Sep 17 00:00:00 2001 From: Marvin Humphrey Date: Fri, 22 Jan 2016 16:51:01 -0800 Subject: [PATCH 03/19] Re-raise Python exceptions from Clownfish. Add a function which takes the Python `sys.exc_info`, wraps it in a Clownfish Err, and THROWs it. --- runtime/python/cfext/CFBind.c | 36 +++++++++++++++++++++++++++++++++++ runtime/python/cfext/CFBind.h | 13 +++++++++++++ 2 files changed, 49 insertions(+) diff --git a/runtime/python/cfext/CFBind.c b/runtime/python/cfext/CFBind.c index 3c4bb4b6..95ae70dc 100644 --- a/runtime/python/cfext/CFBind.c +++ b/runtime/python/cfext/CFBind.c @@ -40,6 +40,42 @@ static bool Err_initialized; +/**** Utility **************************************************************/ + +void +CFBind_reraise_pyerr(cfish_Class *err_klass, cfish_String *mess) { + PyObject *type, *value, *traceback; + PyObject *type_pystr = NULL; + PyObject *value_pystr = NULL; + PyObject *traceback_pystr = NULL; + char *type_str = ""; + char *value_str = ""; + char *traceback_str = ""; + PyErr_GetExcInfo(&type, &value, &traceback); + if (type != NULL) { + PyObject *type_pystr = PyObject_Str(type); + type_str = PyUnicode_AsUTF8(type_pystr); + } + if (value != NULL) { + PyObject *value_pystr = PyObject_Str(value); + value_str = PyUnicode_AsUTF8(value_pystr); + } + if (traceback != NULL) { + PyObject *traceback_pystr = PyObject_Str(traceback); + traceback_str = PyUnicode_AsUTF8(traceback_pystr); + } + cfish_String *new_mess = cfish_Str_newf("%o... %s: %s %s", mess, type_str, + value_str, traceback_str); + Py_XDECREF(type); + Py_XDECREF(value); + Py_XDECREF(traceback); + Py_XDECREF(type_pystr); + Py_XDECREF(value_pystr); + Py_XDECREF(traceback_pystr); + CFISH_DECREF(mess); + cfish_Err_throw_mess(err_klass, new_mess); +} + /**** refcounting **********************************************************/ uint32_t diff --git a/runtime/python/cfext/CFBind.h b/runtime/python/cfext/CFBind.h index 810f2012..d6a362ea 100644 --- a/runtime/python/cfext/CFBind.h +++ b/runtime/python/cfext/CFBind.h @@ -24,6 +24,19 @@ extern "C" { #include "cfish_platform.h" #include "Python.h" +struct cfish_Class; +struct cfish_String; + +/** Wrap the current state of Python's sys.exc_info in a Clownfish Err and + * throw it. + * + * One refcount of `mess` will be consumed by this function. + * + * TODO: Leave the original exception intact. + */ +void +CFBind_reraise_pyerr(struct cfish_Class *err_klass, struct cfish_String *mess); + #ifdef __cplusplus } #endif From 991e4a576ab7edfd96e285c8493c3bdbd1794d6d Mon Sep 17 00:00:00 2001 From: Marvin Humphrey Date: Fri, 22 Jan 2016 17:14:30 -0800 Subject: [PATCH 04/19] Add ParseTuple-compatible converters for numerics. Add some conversion functions which conform to the API specified by Python's ParseTuple C functionality. --- runtime/python/cfext/CFBind.c | 305 ++++++++++++++++++++++++++++++++++ runtime/python/cfext/CFBind.h | 73 ++++++++ 2 files changed, 378 insertions(+) diff --git a/runtime/python/cfext/CFBind.c b/runtime/python/cfext/CFBind.c index 95ae70dc..76533255 100644 --- a/runtime/python/cfext/CFBind.c +++ b/runtime/python/cfext/CFBind.c @@ -76,6 +76,311 @@ CFBind_reraise_pyerr(cfish_Class *err_klass, cfish_String *mess) { cfish_Err_throw_mess(err_klass, new_mess); } +static int +S_convert_sint(PyObject *py_obj, void *ptr, bool nullable, unsigned width) { + if (py_obj == Py_None) { + if (nullable) { + return 1; + } + else { + PyErr_SetString(PyExc_TypeError, "Required argument cannot be None"); + return 0; + } + } + int overflow = 0; + int64_t value = PyLong_AsLongLongAndOverflow(py_obj, &overflow); + if (value == -1 && PyErr_Occurred()) { + return 0; + } + switch (width & 0xF) { + case 1: + if (value < INT8_MIN || value > INT8_MAX) { overflow = 1; } + break; + case 2: + if (value < INT16_MIN || value > INT16_MAX) { overflow = 1; } + break; + case 4: + if (value < INT32_MIN || value > INT32_MAX) { overflow = 1; } + break; + case 8: + break; + } + if (overflow) { + PyErr_SetString(PyExc_OverflowError, "Python int out of range"); + return 0; + } + switch (width & 0xF) { + case 1: + *((int8_t*)ptr) = (int8_t)value; + break; + case 2: + *((int16_t*)ptr) = (int16_t)value; + break; + case 4: + *((int32_t*)ptr) = (int32_t)value; + break; + case 8: + *((int64_t*)ptr) = value; + break; + } + return 1; +} + +static int +S_convert_uint(PyObject *py_obj, void *ptr, bool nullable, unsigned width) { + if (py_obj == Py_None) { + if (nullable) { + return 1; + } + else { + PyErr_SetString(PyExc_TypeError, "Required argument cannot be None"); + return 0; + } + } + uint64_t value = PyLong_AsUnsignedLongLong(py_obj); + if (PyErr_Occurred()) { + return 0; + } + int overflow = 0; + switch (width & 0xF) { + case 1: + if (value > UINT8_MAX) { overflow = 1; } + break; + case 2: + if (value > UINT16_MAX) { overflow = 1; } + break; + case 4: + if (value > UINT32_MAX) { overflow = 1; } + break; + case 8: + break; + } + if (overflow) { + PyErr_SetString(PyExc_OverflowError, "Python int out of range"); + return 0; + } + switch (width & 0xF) { + case 1: + *((uint8_t*)ptr) = (uint8_t)value; + break; + case 2: + *((uint16_t*)ptr) = (uint16_t)value; + break; + case 4: + *((uint32_t*)ptr) = (uint32_t)value; + break; + case 8: + *((uint64_t*)ptr) = value; + break; + } + return 1; +} + +int +CFBind_convert_char(PyObject *py_obj, char *ptr) { + return S_convert_sint(py_obj, ptr, false, sizeof(char)); +} + +int +CFBind_convert_short(PyObject *py_obj, short *ptr) { + return S_convert_sint(py_obj, ptr, false, sizeof(short)); +} + +int +CFBind_convert_int(PyObject *py_obj, int *ptr) { + return S_convert_sint(py_obj, ptr, false, sizeof(int)); +} + +int +CFBind_convert_long(PyObject *py_obj, long *ptr) { + return S_convert_sint(py_obj, ptr, false, sizeof(long)); +} + +int +CFBind_convert_int8_t(PyObject *py_obj, int8_t *ptr) { + return S_convert_sint(py_obj, ptr, false, sizeof(int8_t)); +} + +int +CFBind_convert_int16_t(PyObject *py_obj, int16_t *ptr) { + return S_convert_sint(py_obj, ptr, false, sizeof(int16_t)); +} + +int +CFBind_convert_int32_t(PyObject *py_obj, int32_t *ptr) { + return S_convert_sint(py_obj, ptr, false, sizeof(int32_t)); +} + +int +CFBind_convert_int64_t(PyObject *py_obj, int64_t *ptr) { + return S_convert_sint(py_obj, ptr, false, sizeof(int64_t)); +} + +int +CFBind_convert_uint8_t(PyObject *py_obj, uint8_t *ptr) { + return S_convert_uint(py_obj, ptr, false, sizeof(uint8_t)); +} + +int +CFBind_convert_uint16_t(PyObject *py_obj, uint16_t *ptr) { + return S_convert_uint(py_obj, ptr, false, sizeof(uint16_t)); +} + +int +CFBind_convert_uint32_t(PyObject *py_obj, uint32_t *ptr) { + return S_convert_uint(py_obj, ptr, false, sizeof(uint32_t)); +} + +int +CFBind_convert_uint64_t(PyObject *py_obj, uint64_t *ptr) { + return S_convert_uint(py_obj, ptr, false, sizeof(uint64_t)); +} + +int +CFBind_convert_size_t(PyObject *py_obj, size_t *ptr) { + return S_convert_uint(py_obj, ptr, false, sizeof(size_t)); +} + +int +CFBind_maybe_convert_char(PyObject *py_obj, char *ptr) { + return S_convert_sint(py_obj, ptr, true, sizeof(char)); +} + +int +CFBind_maybe_convert_short(PyObject *py_obj, short *ptr) { + return S_convert_sint(py_obj, ptr, true, sizeof(short)); +} + +int +CFBind_maybe_convert_int(PyObject *py_obj, int *ptr) { + return S_convert_sint(py_obj, ptr, true, sizeof(int)); +} + +int +CFBind_maybe_convert_long(PyObject *py_obj, long *ptr) { + return S_convert_sint(py_obj, ptr, true, sizeof(long)); +} + +int +CFBind_maybe_convert_int8_t(PyObject *py_obj, int8_t *ptr) { + return S_convert_sint(py_obj, ptr, true, sizeof(int8_t)); +} + +int +CFBind_maybe_convert_int16_t(PyObject *py_obj, int16_t *ptr) { + return S_convert_sint(py_obj, ptr, true, sizeof(int16_t)); +} + +int +CFBind_maybe_convert_int32_t(PyObject *py_obj, int32_t *ptr) { + return S_convert_sint(py_obj, ptr, true, sizeof(int32_t)); +} + +int +CFBind_maybe_convert_int64_t(PyObject *py_obj, int64_t *ptr) { + return S_convert_sint(py_obj, ptr, true, sizeof(int64_t)); +} + +int +CFBind_maybe_convert_uint8_t(PyObject *py_obj, uint8_t *ptr) { + return S_convert_uint(py_obj, ptr, true, sizeof(uint8_t)); +} + +int +CFBind_maybe_convert_uint16_t(PyObject *py_obj, uint16_t *ptr) { + return S_convert_uint(py_obj, ptr, true, sizeof(uint16_t)); +} + +int +CFBind_maybe_convert_uint32_t(PyObject *py_obj, uint32_t *ptr) { + return S_convert_uint(py_obj, ptr, true, sizeof(uint32_t)); +} + +int +CFBind_maybe_convert_uint64_t(PyObject *py_obj, uint64_t *ptr) { + return S_convert_uint(py_obj, ptr, true, sizeof(uint64_t)); +} + +int +CFBind_maybe_convert_size_t(PyObject *py_obj, size_t *ptr) { + return S_convert_uint(py_obj, ptr, true, sizeof(size_t)); +} + +static int +S_convert_floating(PyObject *py_obj, void *ptr, bool nullable, int width) { + if (py_obj == Py_None) { + if (nullable) { + return 1; + } + else { + PyErr_SetString(PyExc_TypeError, "Required argument cannot be None"); + return 0; + } + } + double value = PyFloat_AsDouble(py_obj); + if (PyErr_Occurred()) { + return 0; + } + switch (width & 0xF) { + case sizeof(float): + *((float*)ptr) = (float)value; + break; + case sizeof(double): + *((double*)ptr) = value; + break; + } + return 1; +} + +int +CFBind_convert_float(PyObject *py_obj, float *ptr) { + return S_convert_floating(py_obj, ptr, false, sizeof(float)); +} + +int +CFBind_convert_double(PyObject *py_obj, double *ptr) { + return S_convert_floating(py_obj, ptr, false, sizeof(double)); +} + +int +CFBind_maybe_convert_float(PyObject *py_obj, float *ptr) { + return S_convert_floating(py_obj, ptr, true, sizeof(float)); +} + +int +CFBind_maybe_convert_double(PyObject *py_obj, double *ptr) { + return S_convert_floating(py_obj, ptr, true, sizeof(double)); +} + +static int +S_convert_bool(PyObject *py_obj, bool *ptr, bool nullable) { + if (py_obj == Py_None) { + if (nullable) { + return 1; + } + else { + PyErr_SetString(PyExc_TypeError, "Required argument cannot be None"); + return 0; + } + } + int truth = PyObject_IsTrue(py_obj); + if (truth == -1) { + return 0; + } + *ptr = !!truth; + return 1; +} + +int +CFBind_convert_bool(PyObject *py_obj, bool *ptr) { + return S_convert_bool(py_obj, ptr, false); +} + +int +CFBind_maybe_convert_bool(PyObject *py_obj, bool *ptr) { + return S_convert_bool(py_obj, ptr, true); +} + /**** refcounting **********************************************************/ uint32_t diff --git a/runtime/python/cfext/CFBind.h b/runtime/python/cfext/CFBind.h index d6a362ea..85be7aff 100644 --- a/runtime/python/cfext/CFBind.h +++ b/runtime/python/cfext/CFBind.h @@ -37,6 +37,79 @@ struct cfish_String; void CFBind_reraise_pyerr(struct cfish_Class *err_klass, struct cfish_String *mess); +/* ParseTuple conversion routines for primitive numeric types. + * + * If the value of `input` is out of range for the an integer C type, an + * OverflowError will be raised. + * + * If `input` is `None`, the "maybe_convert" variants will leave `ptr` + * untouched, while the "convert" routines will raise a TypeError. + */ +int +CFBind_convert_char(PyObject *input, char *ptr); +int +CFBind_convert_short(PyObject *input, short *ptr); +int +CFBind_convert_int(PyObject *input, int *ptr); +int +CFBind_convert_long(PyObject *input, long *ptr); +int +CFBind_convert_int8_t(PyObject *input, int8_t *ptr); +int +CFBind_convert_int16_t(PyObject *input, int16_t *ptr); +int +CFBind_convert_int32_t(PyObject *input, int32_t *ptr); +int +CFBind_convert_int64_t(PyObject *input, int64_t *ptr); +int +CFBind_convert_uint8_t(PyObject *input, uint8_t *ptr); +int +CFBind_convert_uint16_t(PyObject *input, uint16_t *ptr); +int +CFBind_convert_uint32_t(PyObject *input, uint32_t *ptr); +int +CFBind_convert_uint64_t(PyObject *input, uint64_t *ptr); +int +CFBind_convert_bool(PyObject *input, bool *ptr); +int +CFBind_convert_size_t(PyObject *input, size_t *ptr); +int +CFBind_convert_float(PyObject *input, float *ptr); +int +CFBind_convert_double(PyObject *input, double *ptr); +int +CFBind_maybe_convert_char(PyObject *input, char *ptr); +int +CFBind_maybe_convert_short(PyObject *input, short *ptr); +int +CFBind_maybe_convert_int(PyObject *input, int *ptr); +int +CFBind_maybe_convert_long(PyObject *input, long *ptr); +int +CFBind_maybe_convert_int8_t(PyObject *input, int8_t *ptr); +int +CFBind_maybe_convert_int16_t(PyObject *input, int16_t *ptr); +int +CFBind_maybe_convert_int32_t(PyObject *input, int32_t *ptr); +int +CFBind_maybe_convert_int64_t(PyObject *input, int64_t *ptr); +int +CFBind_maybe_convert_uint8_t(PyObject *input, uint8_t *ptr); +int +CFBind_maybe_convert_uint16_t(PyObject *input, uint16_t *ptr); +int +CFBind_maybe_convert_uint32_t(PyObject *input, uint32_t *ptr); +int +CFBind_maybe_convert_uint64_t(PyObject *input, uint64_t *ptr); +int +CFBind_maybe_convert_bool(PyObject *input, bool *ptr); +int +CFBind_maybe_convert_size_t(PyObject *input, size_t *ptr); +int +CFBind_maybe_convert_float(PyObject *input, float *ptr); +int +CFBind_maybe_convert_double(PyObject *input, double *ptr); + #ifdef __cplusplus } #endif From 2aab6efe2533e6e5b526cd6b408e24443c6d0f61 Mon Sep 17 00:00:00 2001 From: Marvin Humphrey Date: Mon, 25 Jan 2016 16:39:02 -0800 Subject: [PATCH 05/19] Add wrappers for `To_Host` in Python bindings. Add NULL-safe wrappers, one of which is refcount neutral. --- runtime/python/cfext/CFBind.h | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/runtime/python/cfext/CFBind.h b/runtime/python/cfext/CFBind.h index 85be7aff..6637fcac 100644 --- a/runtime/python/cfext/CFBind.h +++ b/runtime/python/cfext/CFBind.h @@ -23,6 +23,7 @@ extern "C" { #include "cfish_platform.h" #include "Python.h" +#include "Clownfish/Obj.h" struct cfish_Class; struct cfish_String; @@ -37,6 +38,34 @@ struct cfish_String; void CFBind_reraise_pyerr(struct cfish_Class *err_klass, struct cfish_String *mess); +/** Null-safe invocation of Obj_To_Host. + */ +static CFISH_INLINE PyObject* +CFBind_cfish_to_py(struct cfish_Obj *obj) { + if (obj != NULL) { + return (PyObject*)CFISH_Obj_To_Host(obj); + } + else { + Py_RETURN_NONE; + } +} + +/** Perform the same conversion as `CFBind_cfish_to_py`, but ensure that the + * result is refcount-neutral, decrementing a refcount from `obj` and passing + * it along. + */ +static CFISH_INLINE PyObject* +CFBind_cfish_to_py_zeroref(struct cfish_Obj *obj) { + if (obj != NULL) { + PyObject *result = (PyObject*)CFISH_Obj_To_Host(obj); + CFISH_DECREF(obj); + return result; + } + else { + return Py_None; + } +} + /* ParseTuple conversion routines for primitive numeric types. * * If the value of `input` is out of range for the an integer C type, an From 3e4952ca641308a8704fa41c51786dace4389ce7 Mon Sep 17 00:00:00 2001 From: Marvin Humphrey Date: Mon, 25 Jan 2016 16:41:37 -0800 Subject: [PATCH 06/19] `To_Host` for Python returns incref'd self. The default implementation for `To_Host` for Python just returns the result of calling INCREF on the invocant. --- runtime/python/cfext/CFBind.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/runtime/python/cfext/CFBind.c b/runtime/python/cfext/CFBind.c index 76533255..ef0d094a 100644 --- a/runtime/python/cfext/CFBind.c +++ b/runtime/python/cfext/CFBind.c @@ -405,9 +405,7 @@ cfish_dec_refcount(void *vself) { void* CFISH_Obj_To_Host_IMP(cfish_Obj *self) { - CFISH_UNUSED_VAR(self); - CFISH_THROW(CFISH_ERR, "TODO"); - CFISH_UNREACHABLE_RETURN(void*); + return CFISH_INCREF(self); } /**** Class ****************************************************************/ From bebc75899ec1dbbbd040fbf6ff21b1b6c4b9be97 Mon Sep 17 00:00:00 2001 From: Marvin Humphrey Date: Mon, 25 Jan 2016 17:02:25 -0800 Subject: [PATCH 07/19] To_Host for composite types to Python. Implement To_Host conversion for Hash -> Python dict and Vector -> Python list. --- runtime/python/cfext/CFBind.c | 39 +++++++++++++++++++++++++++++------ 1 file changed, 33 insertions(+), 6 deletions(-) diff --git a/runtime/python/cfext/CFBind.c b/runtime/python/cfext/CFBind.c index ef0d094a..f524b789 100644 --- a/runtime/python/cfext/CFBind.c +++ b/runtime/python/cfext/CFBind.c @@ -31,6 +31,7 @@ #include "Clownfish/ByteBuf.h" #include "Clownfish/Err.h" #include "Clownfish/Hash.h" +#include "Clownfish/HashIterator.h" #include "Clownfish/Method.h" #include "Clownfish/Num.h" #include "Clownfish/String.h" @@ -572,16 +573,42 @@ CFISH_BB_To_Host_IMP(cfish_ByteBuf *self) { void* CFISH_Vec_To_Host_IMP(cfish_Vector *self) { - CFISH_UNUSED_VAR(self); - CFISH_THROW(CFISH_ERR, "TODO"); - CFISH_UNREACHABLE_RETURN(void*); + uint32_t num_elems = CFISH_Vec_Get_Size(self); + PyObject *list = PyList_New(num_elems); + + // Iterate over vector items. + for (uint32_t i = 0; i < num_elems; i++) { + cfish_Obj *val = CFISH_Vec_Fetch(self, i); + PyObject *item = CFBind_cfish_to_py(val); + PyList_SET_ITEM(list, i, item); + } + + return list; } void* CFISH_Hash_To_Host_IMP(cfish_Hash *self) { - CFISH_UNUSED_VAR(self); - CFISH_THROW(CFISH_ERR, "TODO"); - CFISH_UNREACHABLE_RETURN(void*); + PyObject *dict = PyDict_New(); + + // Iterate over key-value pairs. + cfish_HashIterator *iter = cfish_HashIter_new(self); + while (CFISH_HashIter_Next(iter)) { + cfish_String *key = (cfish_String*)CFISH_HashIter_Get_Key(iter); + if (!cfish_Obj_is_a((cfish_Obj*)key, CFISH_STRING)) { + CFISH_THROW(CFISH_ERR, "Non-string key: %o", + cfish_Obj_get_class_name((cfish_Obj*)key)); + } + size_t size = CFISH_Str_Get_Size(key); + const char *ptr = CFISH_Str_Get_Ptr8(key); + PyObject *py_key = PyUnicode_FromStringAndSize(ptr, size); + PyObject *py_val = CFBind_cfish_to_py(CFISH_HashIter_Get_Value(iter)); + PyDict_SetItem(dict, py_key, py_val); + Py_DECREF(py_key); + Py_DECREF(py_val); + } + CFISH_DECREF(iter); + + return dict; } void* From 5d3809ba960455a6695fcfece1063e7d91f23fac Mon Sep 17 00:00:00 2001 From: Marvin Humphrey Date: Mon, 25 Jan 2016 17:26:40 -0800 Subject: [PATCH 08/19] Implement scalar types To_Host for Python. - String -> string - Blob -> bytes - Boolean -> bool - Integer -> int - Float -> float --- runtime/python/cfext/CFBind.c | 38 ++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/runtime/python/cfext/CFBind.c b/runtime/python/cfext/CFBind.c index f524b789..e991bf62 100644 --- a/runtime/python/cfext/CFBind.c +++ b/runtime/python/cfext/CFBind.c @@ -552,23 +552,23 @@ cfish_TestUtils_destroy_host_runtime(void *runtime) { void* CFISH_Str_To_Host_IMP(cfish_String *self) { - CFISH_UNUSED_VAR(self); - CFISH_THROW(CFISH_ERR, "TODO"); - CFISH_UNREACHABLE_RETURN(void*); + const char *ptr = CFISH_Str_Get_Ptr8(self); + size_t size = CFISH_Str_Get_Size(self); + return PyUnicode_FromStringAndSize(ptr, size); } void* CFISH_Blob_To_Host_IMP(cfish_Blob *self) { - CFISH_UNUSED_VAR(self); - CFISH_THROW(CFISH_ERR, "TODO"); - CFISH_UNREACHABLE_RETURN(void*); + const char *buf = CFISH_Blob_Get_Buf(self); + size_t size = CFISH_Blob_Get_Size(self); + return PyBytes_FromStringAndSize(buf, size); } void* CFISH_BB_To_Host_IMP(cfish_ByteBuf *self) { - CFISH_UNUSED_VAR(self); - CFISH_THROW(CFISH_ERR, "TODO"); - CFISH_UNREACHABLE_RETURN(void*); + CFISH_BB_To_Host_t super_to_host + = CFISH_SUPER_METHOD_PTR(CFISH_BYTEBUF, CFISH_BB_To_Host); + return super_to_host(self); } void* @@ -613,22 +613,24 @@ CFISH_Hash_To_Host_IMP(cfish_Hash *self) { void* CFISH_Float_To_Host_IMP(cfish_Float *self) { - CFISH_UNUSED_VAR(self); - CFISH_THROW(CFISH_ERR, "TODO"); - CFISH_UNREACHABLE_RETURN(void*); + return PyFloat_FromDouble(CFISH_Float_Get_Value(self)); } void* CFISH_Int_To_Host_IMP(cfish_Integer *self) { - CFISH_UNUSED_VAR(self); - CFISH_THROW(CFISH_ERR, "TODO"); - CFISH_UNREACHABLE_RETURN(void*); + int64_t num = CFISH_Int_Get_Value(self); + return PyLong_FromLongLong(num); } void* CFISH_Bool_To_Host_IMP(cfish_Boolean *self) { - CFISH_UNUSED_VAR(self); - CFISH_THROW(CFISH_ERR, "TODO"); - CFISH_UNREACHABLE_RETURN(void*); + if (self == CFISH_TRUE) { + Py_INCREF(Py_True); + return Py_True; + } + else { + Py_INCREF(Py_False); + return Py_False; + } } From 82d79df8bc4c57ff42922a60436425c078ae6731 Mon Sep 17 00:00:00 2001 From: Marvin Humphrey Date: Tue, 26 Jan 2016 17:24:22 -0800 Subject: [PATCH 09/19] Add CF pound-includes in autogen Py module file. --- compiler/src/CFCPython.c | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/compiler/src/CFCPython.c b/compiler/src/CFCPython.c index 2c57eb0b..3efaa3aa 100644 --- a/compiler/src/CFCPython.c +++ b/compiler/src/CFCPython.c @@ -125,12 +125,26 @@ S_write_module_file(CFCPython *self, CFCParcel *parcel, const char *dest) { helper_mod_name[i] = tolower(helper_mod_name[i]); } + CFCClass **ordered = CFCHierarchy_ordered_classes(self->hierarchy); + CFCParcel **parcels = CFCParcel_all_parcels(); + char *pound_includes = CFCUtil_strdup(""); + + for (size_t i = 0; ordered[i] != NULL; i++) { + CFCClass *klass = ordered[i]; + if (CFCClass_included(klass)) { continue; } + + const char *include_h = CFCClass_include_h(klass); + pound_includes = CFCUtil_cat(pound_includes, "#include \"", + include_h, "\"\n", NULL); + } + const char pattern[] = "%s\n" "\n" "#include \"Python.h\"\n" "#include \"cfish_parcel.h\"\n" "#include \"CFBind.h\"\n" + "%s\n" "\n" "static PyModuleDef module_def = {\n" " PyModuleDef_HEAD_INIT,\n" @@ -150,7 +164,7 @@ S_write_module_file(CFCPython *self, CFCParcel *parcel, const char *dest) { "\n"; char *content - = CFCUtil_sprintf(pattern, self->header, + = CFCUtil_sprintf(pattern, self->header, pound_includes, helper_mod_name, last_component, self->footer); char *filepath = CFCUtil_sprintf("%s" CHY_DIR_SEP "_%s.c", dest, @@ -161,6 +175,8 @@ S_write_module_file(CFCPython *self, CFCParcel *parcel, const char *dest) { FREEMEM(content); FREEMEM(helper_mod_name); FREEMEM(pymod_name); + FREEMEM(pound_includes); + FREEMEM(ordered); } void From 999b39f44d69744f08f86e5688a84d7ff0153cd8 Mon Sep 17 00:00:00 2001 From: Marvin Humphrey Date: Tue, 26 Jan 2016 17:42:39 -0800 Subject: [PATCH 10/19] Add dummy py_obj_is_a implementation. --- runtime/python/cfext/CFBind.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/runtime/python/cfext/CFBind.c b/runtime/python/cfext/CFBind.c index e991bf62..d3c39c21 100644 --- a/runtime/python/cfext/CFBind.c +++ b/runtime/python/cfext/CFBind.c @@ -43,6 +43,14 @@ static bool Err_initialized; /**** Utility **************************************************************/ +static bool +S_py_obj_is_a(PyObject *py_obj, cfish_Class *klass) { + CFISH_UNUSED_VAR(py_obj); + CFISH_UNUSED_VAR(klass); + CFISH_THROW(CFISH_ERR, "TODO"); + CFISH_UNREACHABLE_RETURN(bool); +} + void CFBind_reraise_pyerr(cfish_Class *err_klass, cfish_String *mess) { PyObject *type, *value, *traceback; From 99d6bc0fe89382c8492d97825fc03ca82eac051f Mon Sep 17 00:00:00 2001 From: Marvin Humphrey Date: Wed, 27 Jan 2016 15:35:08 -0800 Subject: [PATCH 11/19] Conversion routines from Python to Clownfish. --- runtime/python/cfext/CFBind.c | 196 ++++++++++++++++++++++++++++++++++ runtime/python/cfext/CFBind.h | 34 ++++++ 2 files changed, 230 insertions(+) diff --git a/runtime/python/cfext/CFBind.c b/runtime/python/cfext/CFBind.c index d3c39c21..501c3429 100644 --- a/runtime/python/cfext/CFBind.c +++ b/runtime/python/cfext/CFBind.c @@ -37,6 +37,7 @@ #include "Clownfish/String.h" #include "Clownfish/TestHarness/TestUtils.h" #include "Clownfish/Util/Memory.h" +#include "Clownfish/Util/StringHelper.h" #include "Clownfish/Vector.h" static bool Err_initialized; @@ -85,6 +86,201 @@ CFBind_reraise_pyerr(cfish_Class *err_klass, cfish_String *mess) { cfish_Err_throw_mess(err_klass, new_mess); } +static cfish_Vector* +S_py_list_to_vector(PyObject *list) { + Py_ssize_t size = PyList_GET_SIZE(list); + cfish_Vector *vec = cfish_Vec_new(size); + for (Py_ssize_t i = 0; i < size; i++) { + CFISH_Vec_Store(vec, i, CFBind_py_to_cfish(PyList_GET_ITEM(list, i), NULL)); + } + return vec; +} + +static cfish_Hash* +S_py_dict_to_hash(PyObject *dict) { + Py_ssize_t pos = 0; + PyObject *key, *value; + cfish_Hash *hash = cfish_Hash_new(PyDict_Size(dict)); + while (PyDict_Next(dict, &pos, &key, &value)) { + char *ptr; + Py_ssize_t size; + PyObject *stringified = key; + if (!PyUnicode_CheckExact(key)) { + stringified = PyObject_Str(key); + } + ptr = PyUnicode_AsUTF8AndSize(stringified, &size); + if (!ptr) { + cfish_String *mess + = CFISH_MAKE_MESS("Failed to stringify as UTF-8"); + CFBind_reraise_pyerr(CFISH_ERR, mess); + } + CFISH_Hash_Store_Utf8(hash, ptr, size, CFBind_py_to_cfish(value, NULL)); + if (stringified != key) { + Py_DECREF(stringified); + } + } + return hash; +} + +static cfish_Obj* +S_maybe_increment(void *vobj, bool increment) { + if (increment) { + return CFISH_INCREF((cfish_Obj*)vobj); + } + return (cfish_Obj*)vobj; +} + +static bool +S_maybe_py_to_cfish(PyObject *py_obj, cfish_Class *klass, bool increment, + bool nullable, void *allocation, cfish_Obj **obj_ptr) { + CFISH_UNUSED_VAR(allocation); // FIXME implement stack strings + + if (!py_obj || py_obj == Py_None) { + *obj_ptr = NULL; + return nullable; + } + + // Default to accepting any type. + if (klass == NULL) { + klass = CFISH_OBJ; + } + + if (S_py_obj_is_a(py_obj, klass)) { + *obj_ptr = S_maybe_increment(py_obj, increment); + return true; + } + else if (py_obj == Py_True) { + if (klass != CFISH_BOOLEAN && klass != CFISH_OBJ) { + return false; + } + *obj_ptr = S_maybe_increment(CFISH_TRUE, increment); + return true; + } + else if (py_obj == Py_False) { + if (klass != CFISH_BOOLEAN && klass != CFISH_OBJ) { + return false; + } + *obj_ptr = S_maybe_increment(CFISH_FALSE, increment); + return true; + } + else if (klass == CFISH_BOOLEAN) { + int truthiness = PyObject_IsTrue(py_obj); + if (truthiness == 1) { + *obj_ptr = S_maybe_increment(CFISH_TRUE, increment); + } + else if (truthiness == 0) { + *obj_ptr = S_maybe_increment(CFISH_FALSE, increment); + } + else { + return false; + } + return true; + } + + // From here on out, we're going to return a new Clownfish object. The + // caller has to take ownership of a refcount; if they don't want to, then + // fail rather than attempt to return an object with a refcount of 0. + if (!increment) { + return false; + } + + if (PyUnicode_CheckExact(py_obj)) { + if (klass != CFISH_STRING && klass != CFISH_OBJ) { + return false; + } + // TODO: Allow Clownfish String to wrap buffer of Python str? + Py_ssize_t size; + char *ptr = PyUnicode_AsUTF8AndSize(py_obj, &size); + // TODO: Can we guarantee that Python will always supply valid UTF-8? + if (!ptr || !cfish_StrHelp_utf8_valid(ptr, size)) { + return false; + } + *obj_ptr = (cfish_Obj*)cfish_Str_new_from_trusted_utf8(ptr, size); + return true; + } + else if (PyBytes_CheckExact(py_obj)) { + if (klass != CFISH_BLOB && klass != CFISH_OBJ) { + return false; + } + char *ptr = PyBytes_AS_STRING(py_obj); + Py_ssize_t size = PyBytes_GET_SIZE(py_obj); + *obj_ptr = (cfish_Obj*)cfish_BB_new_bytes(ptr, size); + return true; + } + else if (PyList_CheckExact(py_obj)) { + if (klass != CFISH_VECTOR && klass != CFISH_OBJ) { + return false; + } + *obj_ptr = (cfish_Obj*)S_py_list_to_vector(py_obj); + return true; + } + else if (PyDict_CheckExact(py_obj)) { + if (klass != CFISH_HASH && klass != CFISH_OBJ) { + return false; + } + *obj_ptr = (cfish_Obj*)S_py_dict_to_hash(py_obj); + return true; + } + else if (PyLong_CheckExact(py_obj)) { + if (klass != CFISH_INTEGER && klass != CFISH_OBJ) { + return false; + } + // Raises ValueError on overflow. + int64_t value = PyLong_AsLongLong(py_obj); + if (PyErr_Occurred()) { + return false; + } + *obj_ptr = (cfish_Obj*)cfish_Int_new(value); + return true; + } + else if (PyFloat_CheckExact(py_obj)) { + if (klass != CFISH_FLOAT && klass != CFISH_OBJ) { + return false; + } + double value = PyFloat_AsDouble(py_obj); + if (PyErr_Occurred()) { + return false; + } + *obj_ptr = (cfish_Obj*)cfish_Float_new(value); + return true; + } + + // The value did not meet the required spec, so return false to indicate + // failure. + return false; +} + +cfish_Obj* +CFBind_py_to_cfish_nullable(PyObject *py_obj, cfish_Class *klass) { + cfish_Obj *retval; + bool success = S_maybe_py_to_cfish(py_obj, klass, true, true, NULL, &retval); + if (!success) { + CFISH_THROW(CFISH_ERR, "Can't convert to %o", klass); + } + return retval; +} + +cfish_Obj* +CFBind_py_to_cfish(PyObject *py_obj, cfish_Class *klass) { + cfish_Obj *retval; + bool success = S_maybe_py_to_cfish(py_obj, klass, true, false, NULL, &retval); + if (!success) { + CFISH_THROW(CFISH_ERR, "Can't convert to %o", klass); + } + return retval; +} + +cfish_Obj* +CFBind_py_to_cfish_noinc(PyObject *py_obj, cfish_Class *klass, + void *allocation) { + cfish_Obj *retval; + bool success = S_maybe_py_to_cfish(py_obj, klass, false, false, allocation, &retval); + if (!success) { + CFISH_THROW(CFISH_ERR, "Can't convert to %o", klass); + } + return retval; +} + static int S_convert_sint(PyObject *py_obj, void *ptr, bool nullable, unsigned width) { if (py_obj == Py_None) { diff --git a/runtime/python/cfext/CFBind.h b/runtime/python/cfext/CFBind.h index 6637fcac..e4e0f23f 100644 --- a/runtime/python/cfext/CFBind.h +++ b/runtime/python/cfext/CFBind.h @@ -66,6 +66,40 @@ CFBind_cfish_to_py_zeroref(struct cfish_Obj *obj) { } } +/** Perform recursive conversion of Python objects to Clownfish, return an + * incremented Clownfish Obj. + * + * string -> String + * bytes -> Blob + * None -> NULL + * bool -> Boolean + * int -> Integer + * float -> Float + * list -> Vector + * dict -> Hash + * + * Python dict keys will be stringified. Other Clownfish objects will be + * left intact. Anything else will be stringified. + */ +cfish_Obj* +CFBind_py_to_cfish(PyObject *py_obj, cfish_Class *klass); + +/** As CFBind_py_to_cfish above, but returns NULL if the PyObject is None + * or NULL. + */ +cfish_Obj* +CFBind_py_to_cfish_nullable(PyObject *py_obj, cfish_Class *klass); + +/** As CFBind_py_to_cfish above, but returns an object that can be used for a + * while with no need to decref. + * + * If `klass` is STRING or OBJ, `allocation` must point to stack-allocated + * memory that can hold a String. Otherwise, `allocation` should be NULL. + */ +cfish_Obj* +CFBind_py_to_cfish_noinc(PyObject *py_obj, cfish_Class *klass, + void *allocation); + /* ParseTuple conversion routines for primitive numeric types. * * If the value of `input` is out of range for the an integer C type, an From 2b65f36064ae0f733123a82202dc97f18a6dd325 Mon Sep 17 00:00:00 2001 From: Marvin Humphrey Date: Wed, 27 Jan 2016 16:43:55 -0800 Subject: [PATCH 12/19] Add ParseTuple converters for composite types. Provide ParseTuple-compatible C functions which convert Python list to Clownfish Vector and Python dict to Clownfish Hash. --- runtime/python/cfext/CFBind.c | 78 +++++++++++++++++++++++++++++++++++ runtime/python/cfext/CFBind.h | 14 +++++++ 2 files changed, 92 insertions(+) diff --git a/runtime/python/cfext/CFBind.c b/runtime/python/cfext/CFBind.c index 501c3429..e7d15ed9 100644 --- a/runtime/python/cfext/CFBind.c +++ b/runtime/python/cfext/CFBind.c @@ -281,6 +281,84 @@ CFBind_py_to_cfish_noinc(PyObject *py_obj, cfish_Class *klass, return retval; } +static int +S_convert_hash(PyObject *py_obj, cfish_Hash **hash_ptr, bool nullable) { + if (py_obj == NULL) { // Py_CLEANUP_SUPPORTED cleanup + CFISH_DECREF(*hash_ptr); + return 1; + } + + if (py_obj == Py_None) { + if (nullable) { + return Py_CLEANUP_SUPPORTED; + } + else { + PyErr_SetString(PyExc_TypeError, "Required argument cannot be None"); + return 0; + } + } + else if (PyDict_CheckExact(py_obj)) { + *hash_ptr = S_py_dict_to_hash(py_obj); + return Py_CLEANUP_SUPPORTED; + } + else if (S_py_obj_is_a(py_obj, CFISH_HASH)) { + *hash_ptr = (cfish_Hash*)CFISH_INCREF(py_obj); + return Py_CLEANUP_SUPPORTED; + } + else { + return 0; + } +} + +int +CFBind_convert_hash(PyObject *py_obj, cfish_Hash **hash_ptr) { + return S_convert_hash(py_obj, hash_ptr, false); +} + +int +CFBind_maybe_convert_hash(PyObject *py_obj, cfish_Hash **hash_ptr) { + return S_convert_hash(py_obj, hash_ptr, true); +} + +static int +S_convert_vec(PyObject *py_obj, cfish_Vector **vec_ptr, bool nullable) { + if (py_obj == NULL) { // Py_CLEANUP_SUPPORTED cleanup + CFISH_DECREF(*vec_ptr); + return 1; + } + + if (py_obj == Py_None) { + if (nullable) { + return Py_CLEANUP_SUPPORTED; + } + else { + PyErr_SetString(PyExc_TypeError, "Required argument cannot be None"); + return 0; + } + } + else if (PyList_CheckExact(py_obj)) { + *vec_ptr = S_py_list_to_vector(py_obj); + return Py_CLEANUP_SUPPORTED; + } + else if (S_py_obj_is_a(py_obj, CFISH_VECTOR)) { + *vec_ptr = (cfish_Vector*)CFISH_INCREF(py_obj); + return Py_CLEANUP_SUPPORTED; + } + else { + return 0; + } +} + +int +CFBind_convert_vec(PyObject *py_obj, cfish_Vector **vec_ptr) { + return S_convert_vec(py_obj, vec_ptr, false); +} + +int +CFBind_maybe_convert_vec(PyObject *py_obj, cfish_Vector **vec_ptr) { + return S_convert_vec(py_obj, vec_ptr, true); +} + static int S_convert_sint(PyObject *py_obj, void *ptr, bool nullable, unsigned width) { if (py_obj == Py_None) { diff --git a/runtime/python/cfext/CFBind.h b/runtime/python/cfext/CFBind.h index e4e0f23f..d7eb0ba1 100644 --- a/runtime/python/cfext/CFBind.h +++ b/runtime/python/cfext/CFBind.h @@ -100,6 +100,20 @@ cfish_Obj* CFBind_py_to_cfish_noinc(PyObject *py_obj, cfish_Class *klass, void *allocation); +/* ParseTuple conversion routines for reference types. + * + * If `input` is `None`, the "maybe_convert" variants will leave `ptr` + * untouched, while the "convert" routines will raise a TypeError. + */ +int +CFBind_convert_hash(PyObject *input, cfish_Hash **ptr); +int +CFBind_convert_vec(PyObject *input, cfish_Vector **ptr); +int +CFBind_maybe_convert_hash(PyObject *input, cfish_Hash **ptr); +int +CFBind_maybe_convert_vec(PyObject *input, cfish_Vector **ptr); + /* ParseTuple conversion routines for primitive numeric types. * * If the value of `input` is out of range for the an integer C type, an From 9bbb13f48240d2365b02371bf9f9ce23c7e30c34 Mon Sep 17 00:00:00 2001 From: Marvin Humphrey Date: Wed, 27 Jan 2016 17:06:12 -0800 Subject: [PATCH 13/19] Add dummy routine for fetching PyTypeObject. --- runtime/python/cfext/CFBind.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/runtime/python/cfext/CFBind.c b/runtime/python/cfext/CFBind.c index e7d15ed9..9ecd11e2 100644 --- a/runtime/python/cfext/CFBind.c +++ b/runtime/python/cfext/CFBind.c @@ -42,6 +42,9 @@ static bool Err_initialized; +static PyTypeObject* +S_get_cached_py_type(cfish_Class *klass); + /**** Utility **************************************************************/ static bool @@ -693,6 +696,13 @@ CFISH_Obj_To_Host_IMP(cfish_Obj *self) { /**** Class ****************************************************************/ +static PyTypeObject* +S_get_cached_py_type(cfish_Class *self) { + // FIXME: dummy implementation + CFISH_UNUSED_VAR(self); + return NULL; +} + cfish_Obj* CFISH_Class_Make_Obj_IMP(cfish_Class *self) { CFISH_UNUSED_VAR(self); From 514827b7293471645b0d6a5d450ef7ad1177e79d Mon Sep 17 00:00:00 2001 From: Marvin Humphrey Date: Wed, 27 Jan 2016 17:13:51 -0800 Subject: [PATCH 14/19] ParseTuple routines for Obj, String. --- runtime/python/cfext/CFBind.c | 100 ++++++++++++++++++++++++++++++++++ runtime/python/cfext/CFBind.h | 13 +++++ 2 files changed, 113 insertions(+) diff --git a/runtime/python/cfext/CFBind.c b/runtime/python/cfext/CFBind.c index 9ecd11e2..7e7f6177 100644 --- a/runtime/python/cfext/CFBind.c +++ b/runtime/python/cfext/CFBind.c @@ -284,6 +284,106 @@ CFBind_py_to_cfish_noinc(PyObject *py_obj, cfish_Class *klass, return retval; } +static int +S_convert_obj(PyObject *py_obj, CFBindArg *arg, bool nullable) { + if (py_obj == Py_None) { + if (nullable) { + return 1; + } + else { + PyErr_SetString(PyExc_TypeError, "Required argument cannot be None"); + return 0; + } + } + PyTypeObject *py_type = S_get_cached_py_type(arg->klass); + if (!PyObject_TypeCheck(py_obj, py_type)) { + PyErr_SetString(PyExc_TypeError, "Invalid argument type"); + return 0; + } + *((PyObject**)arg->ptr) = py_obj; + return 1; +} + +int +CFBind_convert_obj(PyObject *py_obj, CFBindArg *arg) { + return S_convert_obj(py_obj, arg, false); +} + +int +CFBind_maybe_convert_obj(PyObject *py_obj, CFBindArg *arg) { + return S_convert_obj(py_obj, arg, true); +} + +static int +S_convert_string(PyObject *py_obj, cfish_String **ptr, bool nullable) { + if (py_obj == NULL) { // Py_CLEANUP_SUPPORTED cleanup + if (*ptr != NULL) { + if (!CFISH_Str_Is_Copy_On_IncRef(*ptr)) { + CFISH_DECREF(*ptr); + } + *ptr = NULL; + } + return 1; + } + + if (py_obj == Py_None) { + if (*ptr != NULL) { + // Default value passed as stack string. + *ptr = CFISH_Str_Clone(*ptr); + return Py_CLEANUP_SUPPORTED; + } + else if (nullable) { + return 1; + } + else { + PyErr_SetString(PyExc_TypeError, "Required argument cannot be None"); + return 0; + } + } + else if (PyUnicode_CheckExact(py_obj)) { + Py_ssize_t size; + char *utf8 = PyUnicode_AsUTF8AndSize(py_obj, &size); + if (!utf8) { + return 0; + } + *ptr = cfish_Str_new_from_trusted_utf8(utf8, size); + return Py_CLEANUP_SUPPORTED; + } + else if (S_py_obj_is_a(py_obj, CFISH_STRING)) { + *ptr = (cfish_String*)CFISH_INCREF(py_obj); + return Py_CLEANUP_SUPPORTED; + } + else if (S_py_obj_is_a(py_obj, CFISH_OBJ)) { + *ptr = CFISH_Obj_To_String((cfish_Obj*)py_obj); + return Py_CLEANUP_SUPPORTED; + } + else { + PyObject *stringified = PyObject_Str(py_obj); + if (stringified == NULL) { + return 0; + } + Py_ssize_t size; + char *utf8 = PyUnicode_AsUTF8AndSize(stringified, &size); + if (!utf8) { + Py_DECREF(stringified); + return 0; + } + *ptr = cfish_Str_new_from_trusted_utf8(utf8, size); + Py_DECREF(stringified); + return Py_CLEANUP_SUPPORTED; + } +} + +int +CFBind_convert_string(PyObject *py_obj, cfish_String **ptr) { + return S_convert_string(py_obj, ptr, false); +} + +int +CFBind_maybe_convert_string(PyObject *py_obj, cfish_String **ptr) { + return S_convert_string(py_obj, ptr, true); +} + static int S_convert_hash(PyObject *py_obj, cfish_Hash **hash_ptr, bool nullable) { if (py_obj == NULL) { // Py_CLEANUP_SUPPORTED cleanup diff --git a/runtime/python/cfext/CFBind.h b/runtime/python/cfext/CFBind.h index d7eb0ba1..b6e57fd3 100644 --- a/runtime/python/cfext/CFBind.h +++ b/runtime/python/cfext/CFBind.h @@ -100,16 +100,29 @@ cfish_Obj* CFBind_py_to_cfish_noinc(PyObject *py_obj, cfish_Class *klass, void *allocation); +typedef struct CFBindArg { + cfish_Class *klass; + void *ptr; +} CFBindArg; + /* ParseTuple conversion routines for reference types. * * If `input` is `None`, the "maybe_convert" variants will leave `ptr` * untouched, while the "convert" routines will raise a TypeError. */ int +CFBind_convert_obj(PyObject *input, CFBindArg *arg); +int +CFBind_convert_string(PyObject *input, cfish_String **ptr); +int CFBind_convert_hash(PyObject *input, cfish_Hash **ptr); int CFBind_convert_vec(PyObject *input, cfish_Vector **ptr); int +CFBind_maybe_convert_obj(PyObject *input, CFBindArg *arg); +int +CFBind_maybe_convert_string(PyObject *input, cfish_String **ptr); +int CFBind_maybe_convert_hash(PyObject *input, cfish_Hash **ptr); int CFBind_maybe_convert_vec(PyObject *input, cfish_Vector **ptr); From 8996f0f62d0e07ed3b0d14934041aeb9d71fa698 Mon Sep 17 00:00:00 2001 From: Marvin Humphrey Date: Wed, 27 Jan 2016 16:05:37 -0800 Subject: [PATCH 15/19] Stub out callback-generating code for Python. --- compiler/src/CFCPyMethod.c | 38 +++++++++++++++++++++++++++++ compiler/src/CFCPyMethod.h | 39 ++++++++++++++++++++++++++++++ compiler/src/CFCPython.c | 49 +++++++++++++++++++++++++++++++++++++- 3 files changed, 125 insertions(+), 1 deletion(-) create mode 100644 compiler/src/CFCPyMethod.c create mode 100644 compiler/src/CFCPyMethod.h diff --git a/compiler/src/CFCPyMethod.c b/compiler/src/CFCPyMethod.c new file mode 100644 index 00000000..2892b86b --- /dev/null +++ b/compiler/src/CFCPyMethod.c @@ -0,0 +1,38 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +#include "CFCPyMethod.h" +#include "CFCPyTypeMap.h" +#include "CFCUtil.h" +#include "CFCClass.h" +#include "CFCFunction.h" +#include "CFCMethod.h" +#include "CFCSymbol.h" +#include "CFCType.h" +#include "CFCParcel.h" +#include "CFCParamList.h" +#include "CFCVariable.h" + +#ifndef true +#define true 1 +#define false 0 +#endif + +char* +CFCPyMethod_callback_def(CFCMethod *method, CFCClass *invoker) { + return CFCUtil_strdup(""); +} diff --git a/compiler/src/CFCPyMethod.h b/compiler/src/CFCPyMethod.h new file mode 100644 index 00000000..15fa1e6f --- /dev/null +++ b/compiler/src/CFCPyMethod.h @@ -0,0 +1,39 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef H_CFCPYMETHOD +#define H_CFCPYMETHOD + +#ifdef __cplusplus +extern "C" { +#endif + +struct CFCMethod; +struct CFCClass; + +/** Return C code which knows how to call back into Python for this method. This + * code is run when a Python subclass has overridden a method in a Clownfish base + * class. + */ +char* +CFCPyMethod_callback_def(struct CFCMethod *method, struct CFCClass *invoker); + +#ifdef __cplusplus +} +#endif + +#endif /* H_CFCPYMETHOD */ + diff --git a/compiler/src/CFCPython.c b/compiler/src/CFCPython.c index 3efaa3aa..10c9872d 100644 --- a/compiler/src/CFCPython.c +++ b/compiler/src/CFCPython.c @@ -24,6 +24,7 @@ #define CFC_NEED_BASE_STRUCT_DEF #include "CFCBase.h" #include "CFCPython.h" +#include "CFCPyMethod.h" #include "CFCParcel.h" #include "CFCClass.h" #include "CFCMethod.h" @@ -108,6 +109,48 @@ S_write_hostdefs(CFCPython *self) { FREEMEM(content); } +static char* +S_gen_callbacks(CFCPython *self, CFCParcel *parcel, CFCClass **ordered) { + char *callbacks = CFCUtil_strdup(""); + + // Generate implementation files containing callback definitions. + for (size_t i = 0; ordered[i] != NULL; i++) { + CFCClass *klass = ordered[i]; + if (CFCClass_included(klass) + || CFCClass_inert(klass) + //|| CFCClass_get_parcel(klass) != parcel + ) { + continue; + } + + CFCMethod **fresh_methods = CFCClass_fresh_methods(klass); + for (int meth_num = 0; fresh_methods[meth_num] != NULL; meth_num++) { + CFCMethod *method = fresh_methods[meth_num]; + + // Define callback. + if (CFCMethod_novel(method) && !CFCMethod_final(method)) { + char *cb_def = CFCPyMethod_callback_def(method, klass); + callbacks = CFCUtil_cat(callbacks, cb_def, "\n", NULL); + FREEMEM(cb_def); + } + } + } + + static const char helpers[] = + "" + ; + + static const char pattern[] = + "%s\n" + "\n" + "%s" + ; + char *content = CFCUtil_sprintf(pattern, helpers, callbacks); + + FREEMEM(callbacks); + return content; +} + static void S_write_module_file(CFCPython *self, CFCParcel *parcel, const char *dest) { const char *parcel_name = CFCParcel_get_name(parcel); @@ -127,6 +170,7 @@ S_write_module_file(CFCPython *self, CFCParcel *parcel, const char *dest) { CFCClass **ordered = CFCHierarchy_ordered_classes(self->hierarchy); CFCParcel **parcels = CFCParcel_all_parcels(); + char *callbacks = S_gen_callbacks(self, parcel, ordered); char *pound_includes = CFCUtil_strdup(""); for (size_t i = 0; ordered[i] != NULL; i++) { @@ -146,6 +190,8 @@ S_write_module_file(CFCPython *self, CFCParcel *parcel, const char *dest) { "#include \"CFBind.h\"\n" "%s\n" "\n" + "%s\n" // callbacks + "\n" "static PyModuleDef module_def = {\n" " PyModuleDef_HEAD_INIT,\n" " \"%s\",\n" // module name @@ -164,7 +210,7 @@ S_write_module_file(CFCPython *self, CFCParcel *parcel, const char *dest) { "\n"; char *content - = CFCUtil_sprintf(pattern, self->header, pound_includes, + = CFCUtil_sprintf(pattern, self->header, pound_includes, callbacks, helper_mod_name, last_component, self->footer); char *filepath = CFCUtil_sprintf("%s" CHY_DIR_SEP "_%s.c", dest, @@ -176,6 +222,7 @@ S_write_module_file(CFCPython *self, CFCParcel *parcel, const char *dest) { FREEMEM(helper_mod_name); FREEMEM(pymod_name); FREEMEM(pound_includes); + FREEMEM(callbacks); FREEMEM(ordered); } From bb7e880799d6bb0e4743d24fcacb630e04b2e32f Mon Sep 17 00:00:00 2001 From: Marvin Humphrey Date: Wed, 27 Jan 2016 18:23:36 -0800 Subject: [PATCH 16/19] Add helper routines for Python callbacks. The generated C code for calling back into Python from Clownfish when a method is overridden will require these helper functions for running the Python callable, converting return values, etc. --- compiler/src/CFCPython.c | 203 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 202 insertions(+), 1 deletion(-) diff --git a/compiler/src/CFCPython.c b/compiler/src/CFCPython.c index 10c9872d..b39240d6 100644 --- a/compiler/src/CFCPython.c +++ b/compiler/src/CFCPython.c @@ -137,7 +137,208 @@ S_gen_callbacks(CFCPython *self, CFCParcel *parcel, CFCClass **ordered) { } static const char helpers[] = - "" + "static PyObject*\n" + "S_pack_tuple(int num_args, ...) {\n" + " PyObject *tuple = PyTuple_New(num_args);\n" + " va_list args;\n" + " va_start(args, num_args);\n" + " for (int i = 0; i < num_args; i++) {\n" + " PyObject *arg = va_arg(args, PyObject*);\n" + " PyTuple_SET_ITEM(tuple, i, arg);\n" + " }\n" + " va_end(args);\n" + " return tuple;\n" + "}\n" + "\n" + "static PyObject*\n" + "S_call_pymeth(PyObject *self, const char *meth_name, PyObject *args,\n" + " const char *file, int line, const char *func) {\n" + " PyObject *callable = PyObject_GetAttrString(self, meth_name);\n" + " if (!PyCallable_Check(callable)) {\n" + " cfish_String *mess\n" + " = cfish_Err_make_mess(file, line, func, \"Attr '%s' not callable\",\n" + " meth_name);\n" + " cfish_Err_throw_mess(CFISH_ERR, mess);\n" + " }\n" + " PyObject *result = PyObject_CallObject(callable, args);\n" + " Py_DECREF(args);\n" + " if (result == NULL) {\n" + " cfish_String *mess\n" + " = cfish_Err_make_mess(file, line, func,\n" + " \"Callback to '%s' failed\", meth_name);\n" + " CFBind_reraise_pyerr(CFISH_ERR, mess);\n" + " }\n" + " return result;\n" + "}\n" + "\n" + "#define CALL_PYMETH_VOID(self, meth_name, args) \\\n" + " S_call_pymeth_void(self, meth_name, args, \\\n" + " __FILE__, __LINE__, CFISH_ERR_FUNC_MACRO)\n" + "\n" + "static void\n" + "S_call_pymeth_void(PyObject *self, const char *meth_name, PyObject *args,\n" + " const char *file, int line, const char *func) {\n" + " PyObject *py_result\n" + " = S_call_pymeth(self, meth_name, args, file, line, func);\n" + " if (py_result == NULL) {\n" + " cfish_String *mess\n" + " = cfish_Err_make_mess(file, line, func, \"Call to %s failed\",\n" + " meth_name);\n" + " CFBind_reraise_pyerr(CFISH_ERR, mess);\n" + " }\n" + " Py_DECREF(py_result);\n" + "}\n" + "\n" + "#define CALL_PYMETH_BOOL(self, meth_name, args) \\\n" + " S_call_pymeth_bool(self, meth_name, args, \\\n" + " __FILE__, __LINE__, CFISH_ERR_FUNC_MACRO)\n" + "\n" + "static bool\n" + "S_call_pymeth_bool(PyObject *self, const char *meth_name, PyObject *args,\n" + " const char *file, int line, const char *func) {\n" + " PyObject *py_result\n" + " = S_call_pymeth(self, meth_name, args, file, line, func);\n" + " int truthiness = py_result != NULL\n" + " ? PyObject_IsTrue(py_result)\n" + " : -1;\n" + " if (truthiness == -1) {\n" + " cfish_String *mess\n" + " = cfish_Err_make_mess(file, line, func, \"Call to %s failed\",\n" + " meth_name);\n" + " CFBind_reraise_pyerr(CFISH_ERR, mess);\n" + " }\n" + " Py_DECREF(py_result);\n" + " return !!truthiness;\n" + "}\n" + "\n" + "#define CALL_PYMETH_OBJ(self, meth_name, args, ret_class, nullable) \\\n" + " S_call_pymeth_obj(self, meth_name, args, ret_class, nullable, \\\n" + " __FILE__, __LINE__, CFISH_ERR_FUNC_MACRO)\n" + "\n" + "static cfish_Obj*\n" + "S_call_pymeth_obj(PyObject *self, const char *meth_name,\n" + " PyObject *args, cfish_Class *ret_class, bool nullable,\n" + " const char *file, int line, const char *func) {\n" + " PyObject *py_result\n" + " = S_call_pymeth(self, meth_name, args, file, line, func);\n" + " cfish_Obj *result = CFBind_py_to_cfish(py_result, ret_class);\n" + " Py_DECREF(py_result);\n" + " if (!nullable && result == NULL) {\n" + " CFISH_THROW(CFISH_ERR, \"%s cannot return NULL\", meth_name);\n" + " }\n" + " else if (!cfish_Obj_is_a(result, ret_class)) {\n" + " cfish_Class *result_class = cfish_Obj_get_class(result);\n" + " CFISH_DECREF(result);\n" + " CFISH_THROW(CFISH_ERR, \"%s returned %o instead of %o\", meth_name,\n" + " CFISH_Class_Get_Name(result_class),\n" + " CFISH_Class_Get_Name(ret_class));\n" + " }\n" + " return result;\n" + "}\n" + "\n" + "#define CALL_PYMETH_DOUBLE(self, meth_name, args) \\\n" + " S_call_pymeth_f64(self, meth_name, args, \\\n" + " __FILE__, __LINE__, CFISH_ERR_FUNC_MACRO)\n" + "#define CALL_PYMETH_FLOAT(self, meth_name, args) \\\n" + " ((float)S_call_pymeth_f64(self, meth_name, args, \\\n" + " __FILE__, __LINE__, CFISH_ERR_FUNC_MACRO))\n" + "\n" + "static double\n" + "S_call_pymeth_f64(PyObject *self, const char *meth_name, PyObject *args,\n" + " const char *file, int line, const char *func) {\n" + " PyObject *py_result\n" + " = S_call_pymeth(self, meth_name, args, file, line, func);\n" + " PyErr_Clear();\n" + " double result = PyFloat_AsDouble(py_result);\n" + " if (PyErr_Occurred()) {\n" + " cfish_String *mess\n" + " = cfish_Err_make_mess(file, line, func,\n" + " \"Converting result of '%s' to double failed\",\n" + " meth_name);\n" + " CFBind_reraise_pyerr(CFISH_ERR, mess);\n" + " }\n" + " Py_DECREF(py_result);\n" + " return result;\n" + "}\n" + "\n" + "#define CALL_PYMETH_INT64_T(self, meth_name, args) \\\n" + " S_call_pymeth_i64(self, meth_name, args, INT64_MAX, INT64_MIN, \\\n" + " __FILE__, __LINE__, CFISH_ERR_FUNC_MACRO)\n" + "#define CALL_PYMETH_INT32_T(self, meth_name, args) \\\n" + " ((int32_t)S_call_pymeth_i64(self, meth_name, args, INT32_MAX, INT32_MIN, \\\n" + " __FILE__, __LINE__, CFISH_ERR_FUNC_MACRO))\n" + "#define CALL_PYMETH_INT16_T(self, meth_name, args) \\\n" + " ((int16_t)S_call_pymeth_i64(self, meth_name, args, INT16_MAX, INT16_MIN, \\\n" + " __FILE__, __LINE__, CFISH_ERR_FUNC_MACRO))\n" + "#define CALL_PYMETH_INT8_T(self, meth_name, args) \\\n" + " ((int8_t)S_call_pymeth_i64(self, meth_name, args, INT8_MAX, INT8_MIN, \\\n" + " __FILE__, __LINE__, CFISH_ERR_FUNC_MACRO))\n" + "#define CALL_PYMETH_CHAR(self, meth_name, args) \\\n" + " ((char)S_call_pymeth_i64(self, meth_name, args, CHAR_MAX, CHAR_MIN, \\\n" + " __FILE__, __LINE__, CFISH_ERR_FUNC_MACRO))\n" + "#define CALL_PYMETH_SHORT(self, meth_name, args) \\\n" + " ((short)S_call_pymeth_i64(self, meth_name, args, SHRT_MAX, SHRT_MIN, \\\n" + " __FILE__, __LINE__, CFISH_ERR_FUNC_MACRO))\n" + "#define CALL_PYMETH_INT(self, meth_name, args) \\\n" + " ((int16_t)S_call_pymeth_i64(self, meth_name, args, INT_MAX, INT_MIN, \\\n" + " __FILE__, __LINE__, CFISH_ERR_FUNC_MACRO))\n" + "#define CALL_PYMETH_LONG(self, meth_name, args) \\\n" + " ((int16_t)S_call_pymeth_i64(self, meth_name, args, LONG_MAX, LONG_MIN, \\\n" + " __FILE__, __LINE__, CFISH_ERR_FUNC_MACRO))\n" + "\n" + "static int64_t\n" + "S_call_pymeth_i64(PyObject *self, const char *meth_name, PyObject *args,\n" + " int64_t max, int64_t min,\n" + " const char *file, int line, const char *func) {\n" + " PyObject *py_result\n" + " = S_call_pymeth(self, meth_name, args, file, line, func);\n" + " PyErr_Clear();\n" + " int64_t result = PyLong_AsLongLong(py_result);\n" + " if (PyErr_Occurred() || result > max || result < min) {\n" + " cfish_String *mess\n" + " = cfish_Err_make_mess(file, line, func,\n" + " \"Converting result of '%s' to int64_t failed\",\n" + " meth_name);\n" + " CFBind_reraise_pyerr(CFISH_ERR, mess);\n" + " }\n" + " Py_DECREF(py_result);\n" + " return result;\n" + "}\n" + "\n" + "#define CALL_PYMETH_UINT64_T(self, meth_name, args) \\\n" + " S_call_pymeth_u64(self, meth_name, args, UINT64_MAX, \\\n" + " __FILE__, __LINE__, CFISH_ERR_FUNC_MACRO)\n" + "#define CALL_PYMETH_UINT32_T(self, meth_name, args) \\\n" + " ((uint32_t)S_call_pymeth_u64(self, meth_name, args, UINT32_MAX, \\\n" + " __FILE__, __LINE__, CFISH_ERR_FUNC_MACRO))\n" + "#define CALL_PYMETH_UINT16_T(self, meth_name, args) \\\n" + " ((uint32_t)S_call_pymeth_u64(self, meth_name, args, UINT16_MAX, \\\n" + " __FILE__, __LINE__, CFISH_ERR_FUNC_MACRO))\n" + "#define CALL_PYMETH_UINT8_T(self, meth_name, args) \\\n" + " ((uint32_t)S_call_pymeth_u64(self, meth_name, args, UINT8_MAX, \\\n" + " __FILE__, __LINE__, CFISH_ERR_FUNC_MACRO))\n" + "#define CALL_PYMETH_SIZE_T(self, meth_name, args) \\\n" + " S_call_pymeth_u64(self, meth_name, args, SIZE_MAX, \\\n" + " __FILE__, __LINE__, CFISH_ERR_FUNC_MACRO)\n" + "\n" + "static uint64_t\n" + "S_call_pymeth_u64(PyObject *self, const char *meth_name, PyObject *args,\n" + " uint64_t max,\n" + " const char *file, int line, const char *func) {\n" + " PyObject *py_result\n" + " = S_call_pymeth(self, meth_name, args, file, line, func);\n" + " PyErr_Clear();\n" + " uint64_t result = PyLong_AsUnsignedLongLong(py_result);\n" + " if (PyErr_Occurred()) {\n" + " cfish_String *mess\n" + " = cfish_Err_make_mess(file, line, func,\n" + " \"Converting result of '%s' to uint64_t failed\",\n" + " meth_name);\n" + " CFBind_reraise_pyerr(CFISH_ERR, mess);\n" + " }\n" + " Py_DECREF(py_result);\n" + " return result;\n" + "}\n" ; static const char pattern[] = From 91622dbf97f1afb2a195609fd1bea2b703119211 Mon Sep 17 00:00:00 2001 From: Marvin Humphrey Date: Thu, 18 Feb 2016 19:31:30 -0800 Subject: [PATCH 17/19] Prepare to trap errors in Python glue code. Add private macro `CFBIND_TRY` and supporting routines, in an effort to wrap Clownfish C routines and trap errors. This will be used inside glue code for Python, setting `sys.exc_info` when something goes wrong. --- compiler/src/CFCPython.c | 9 +++++++++ runtime/python/cfext/CFBind.c | 22 ++++++++++++++++++++++ runtime/python/cfext/CFBind.h | 12 ++++++++++++ 3 files changed, 43 insertions(+) diff --git a/compiler/src/CFCPython.c b/compiler/src/CFCPython.c index b39240d6..a2b13a43 100644 --- a/compiler/src/CFCPython.c +++ b/compiler/src/CFCPython.c @@ -149,6 +149,15 @@ S_gen_callbacks(CFCPython *self, CFCParcel *parcel, CFCClass **ordered) { " va_end(args);\n" " return tuple;\n" "}\n" + "#define CFBIND_TRY(routine) \\\n" + " do { \\\n" + " jmp_buf env; \\\n" + " jmp_buf *prev_env = CFBind_swap_env(&env); \\\n" + " if (!setjmp(env)) { \\\n" + " routine; \\\n" + " } \\\n" + " CFBind_swap_env(prev_env); \\\n" + " } while (0)\n" "\n" "static PyObject*\n" "S_call_pymeth(PyObject *self, const char *meth_name, PyObject *args,\n" diff --git a/runtime/python/cfext/CFBind.c b/runtime/python/cfext/CFBind.c index 7e7f6177..48743a73 100644 --- a/runtime/python/cfext/CFBind.c +++ b/runtime/python/cfext/CFBind.c @@ -855,6 +855,28 @@ static cfish_Err *current_error; static cfish_Err *thrown_error; static jmp_buf *current_env; +jmp_buf* +CFBind_swap_env(jmp_buf *env) { + jmp_buf *prev_env = current_env; + current_env = env; + return prev_env; +} + +int +CFBind_migrate_cferr() { + if (thrown_error != NULL) { + cfish_Err *err = thrown_error; + thrown_error = NULL; + cfish_String *mess = CFISH_Err_Get_Mess(err); + char *utf8 = CFISH_Str_To_Utf8(mess); + PyErr_SetString(PyExc_RuntimeError, utf8); + CFISH_FREEMEM(utf8); + CFISH_DECREF(err); + return true; + } + return false; +} + void cfish_Err_init_class(void) { Err_initialized = true; diff --git a/runtime/python/cfext/CFBind.h b/runtime/python/cfext/CFBind.h index b6e57fd3..9279510c 100644 --- a/runtime/python/cfext/CFBind.h +++ b/runtime/python/cfext/CFBind.h @@ -21,6 +21,7 @@ extern "C" { #endif +#include #include "cfish_platform.h" #include "Python.h" #include "Clownfish/Obj.h" @@ -38,6 +39,17 @@ struct cfish_String; void CFBind_reraise_pyerr(struct cfish_Class *err_klass, struct cfish_String *mess); +/** Set the current_env, return the old value (internal use only). + */ +jmp_buf* +CFBind_swap_env(jmp_buf *env); + +/** If a Clownfish error was thrown, propagate it to Python and return true + * (internal use only). + */ +int +CFBind_migrate_cferr(void); + /** Null-safe invocation of Obj_To_Host. */ static CFISH_INLINE PyObject* From 02617ac057b70a2358233d9f65fb205123b308e1 Mon Sep 17 00:00:00 2001 From: Marvin Humphrey Date: Wed, 27 Jan 2016 19:14:25 -0800 Subject: [PATCH 18/19] Generate OVERRIDEs for non-overrideable meths. Generate error-throwing OVERRIDE routines for methods which cannot be overridden. --- compiler/src/CFCPyMethod.c | 76 +++++++++++++++++++++++++++++++++++++- 1 file changed, 75 insertions(+), 1 deletion(-) diff --git a/compiler/src/CFCPyMethod.c b/compiler/src/CFCPyMethod.c index 2892b86b..3cc7b96e 100644 --- a/compiler/src/CFCPyMethod.c +++ b/compiler/src/CFCPyMethod.c @@ -14,6 +14,7 @@ * limitations under the License. */ +#include #include "CFCPyMethod.h" #include "CFCPyTypeMap.h" @@ -32,7 +33,80 @@ #define false 0 #endif +/* Take a NULL-terminated list of CFCVariables and build up a string of + * directives like: + * + * UNUSED_VAR(var1); + * UNUSED_VAR(var2); + */ +static char* +S_build_unused_vars(CFCVariable **vars); + +/* Create an unreachable return statement if necessary, in order to thwart + * compiler warnings. */ +static char* +S_maybe_unreachable(CFCType *return_type); + char* CFCPyMethod_callback_def(CFCMethod *method, CFCClass *invoker) { - return CFCUtil_strdup(""); + CFCParamList *param_list = CFCMethod_get_param_list(method); + CFCVariable **vars = CFCParamList_get_variables(param_list); + CFCType *return_type = CFCMethod_get_return_type(method); + const char *ret_type_str = CFCType_to_c(return_type); + const char *params = CFCParamList_to_c(param_list); + char *override_sym = CFCMethod_full_override_sym(method, invoker); + char *content; + + //if (CFCMethod_can_be_bound(method)) { + if (false) { + } + else { + char *unused = S_build_unused_vars(vars); + char *unreachable = S_maybe_unreachable(return_type); + char *meth_sym = CFCMethod_full_method_sym(method, invoker); + const char pattern[] = + "%s\n" + "%s(%s) {%s\n" + " CFISH_THROW(CFISH_ERR, \"Can't override %s via binding\");%s\n" + "}\n"; + content = CFCUtil_sprintf(pattern, ret_type_str, override_sym, + params, unused, meth_sym, unreachable); + FREEMEM(meth_sym); + FREEMEM(unused); + FREEMEM(unreachable); + } + + FREEMEM(override_sym); + return content; } + +static char* +S_build_unused_vars(CFCVariable **vars) { + char *unused = CFCUtil_strdup(""); + + for (int i = 0; vars[i] != NULL; i++) { + const char *var_name = CFCVariable_get_name(vars[i]); + size_t size = strlen(unused) + strlen(var_name) + 80; + unused = (char*)REALLOCATE(unused, size); + strcat(unused, "\n CFISH_UNUSED_VAR("); + strcat(unused, var_name); + strcat(unused, ");"); + } + + return unused; +} + +static char* +S_maybe_unreachable(CFCType *return_type) { + char *return_statement; + if (CFCType_is_void(return_type)) { + return_statement = CFCUtil_strdup(""); + } + else { + const char *ret_type_str = CFCType_to_c(return_type); + char pattern[] = "\n CFISH_UNREACHABLE_RETURN(%s);"; + return_statement = CFCUtil_sprintf(pattern, ret_type_str); + } + return return_statement; +} + From 335b61fc0f35868d1cda0bb1bbd879c616df1ae3 Mon Sep 17 00:00:00 2001 From: Marvin Humphrey Date: Wed, 27 Jan 2016 19:24:32 -0800 Subject: [PATCH 19/19] Gen OVERRIDE callbacks for Python. Generate the C functions which call back into Python from Clownfish-flavored C when a method is overridden. --- compiler/src/CFCPyMethod.c | 109 ++++++++++++++++++++++++++++++++++++- 1 file changed, 107 insertions(+), 2 deletions(-) diff --git a/compiler/src/CFCPyMethod.c b/compiler/src/CFCPyMethod.c index 3cc7b96e..983df28b 100644 --- a/compiler/src/CFCPyMethod.c +++ b/compiler/src/CFCPyMethod.c @@ -15,6 +15,7 @@ */ #include +#include #include "CFCPyMethod.h" #include "CFCPyTypeMap.h" @@ -47,6 +48,93 @@ S_build_unused_vars(CFCVariable **vars); static char* S_maybe_unreachable(CFCType *return_type); +static char* +S_build_py_args(CFCParamList *param_list) { + int num_vars = CFCParamList_num_vars(param_list); + CFCVariable **vars = CFCParamList_get_variables(param_list); + char pattern[] = " PyObject *cfcb_ARGS = S_pack_tuple(%d"; + char *py_args = CFCUtil_sprintf(pattern, num_vars - 1); + + for (int i = 1; vars[i] != NULL; i++) { + const char *var_name = CFCVariable_get_name(vars[i]); + CFCType *type = CFCVariable_get_type(vars[i]); + char *conversion = CFCPyTypeMap_c_to_py(type, var_name); + py_args = CFCUtil_cat(py_args, ",\n ", conversion, NULL); + FREEMEM(conversion); + } + py_args = CFCUtil_cat(py_args, ");", NULL); + + return py_args; +} + +static char* +S_build_pymeth_invocation(CFCMethod *method) { + CFCType *return_type = CFCMethod_get_return_type(method); + const char *micro_sym = CFCSymbol_get_name((CFCSymbol*)method); + char *invocation = NULL; + const char *ret_type_str = CFCType_to_c(return_type); + + if (CFCType_is_void(return_type)) { + const char pattern[] = + " CALL_PYMETH_VOID((PyObject*)self, \"%s\", cfcb_ARGS);"; + invocation = CFCUtil_sprintf(pattern, micro_sym); + } + else if (CFCType_is_object(return_type)) { + const char *nullable + = CFCType_nullable(return_type) ? "true" : "false"; + const char *ret_class = CFCType_get_class_var(return_type); + const char pattern[] = + " %s cfcb_RESULT = (%s)CALL_PYMETH_OBJ((PyObject*)self, \"%s\", cfcb_ARGS, %s, %s);"; + invocation = CFCUtil_sprintf(pattern, ret_type_str, ret_type_str, micro_sym, + ret_class, nullable); + } + else if (CFCType_is_primitive(return_type)) { + char type_upcase[64]; + if (strlen(ret_type_str) > 63) { + CFCUtil_die("Unexpectedly long type name: %s", ret_type_str); + } + for (int i = 0, max = strlen(ret_type_str) + 1; i < max; i++) { + type_upcase[i] = toupper(ret_type_str[i]); + } + const char pattern[] = + " %s cfcb_RESULT = CALL_PYMETH_%s((PyObject*)self, \"%s\", cfcb_ARGS);"; + invocation = CFCUtil_sprintf(pattern, ret_type_str, type_upcase, + micro_sym); + } + else { + CFCUtil_die("Unexpected return type: %s", CFCType_to_c(return_type)); + } + + return invocation; +} + +static char* +S_callback_refcount_mods(CFCParamList *param_list) { + char *refcount_mods = CFCUtil_strdup(""); + CFCVariable **arg_vars = CFCParamList_get_variables(param_list); + + // Adjust refcounts of arguments per method signature, so that Perl code + // does not have to. + for (int i = 0; arg_vars[i] != NULL; i++) { + CFCVariable *var = arg_vars[i]; + CFCType *type = CFCVariable_get_type(var); + const char *name = CFCVariable_get_name(var); + if (!CFCType_is_object(type)) { + continue; + } + else if (CFCType_incremented(type)) { + refcount_mods = CFCUtil_cat(refcount_mods, " CFISH_INCREF(", + name, ");\n", NULL); + } + else if (CFCType_decremented(type)) { + refcount_mods = CFCUtil_cat(refcount_mods, " CFISH_DECREF(", + name, ");\n", NULL); + } + } + + return refcount_mods; +} + char* CFCPyMethod_callback_def(CFCMethod *method, CFCClass *invoker) { CFCParamList *param_list = CFCMethod_get_param_list(method); @@ -57,8 +145,25 @@ CFCPyMethod_callback_def(CFCMethod *method, CFCClass *invoker) { char *override_sym = CFCMethod_full_override_sym(method, invoker); char *content; - //if (CFCMethod_can_be_bound(method)) { - if (false) { + if (CFCMethod_can_be_bound(method)) { + char *py_args = S_build_py_args(param_list); + char *invocation = S_build_pymeth_invocation(method); + char *refcount_mods = S_callback_refcount_mods(param_list); + const char *maybe_return = CFCType_is_void(return_type) + ? "" + : " return cfcb_RESULT;\n"; + + const char pattern[] = + "%s\n" + "%s(%s) {\n" + "%s\n" + "%s\n" + "%s" + "%s" + "}\n"; + content = CFCUtil_sprintf(pattern, ret_type_str, override_sym, params, + py_args, invocation, refcount_mods, + maybe_return); } else { char *unused = S_build_unused_vars(vars);