From cb25fd0bd79dfcd8097db6bf8c0319cb44a07531 Mon Sep 17 00:00:00 2001 From: Marvin Humphrey Date: Mon, 1 Feb 2016 19:13:00 -0800 Subject: [PATCH 01/21] Gen stub Py method wrappers. --- compiler/src/CFCPyClass.c | 17 +++++++++++++++++ compiler/src/CFCPyMethod.c | 18 ++++++++++++++++++ compiler/src/CFCPyMethod.h | 3 +++ 3 files changed, 38 insertions(+) diff --git a/compiler/src/CFCPyClass.c b/compiler/src/CFCPyClass.c index 9f46b60c..11a49058 100644 --- a/compiler/src/CFCPyClass.c +++ b/compiler/src/CFCPyClass.c @@ -145,6 +145,23 @@ CFCPyClass_gen_binding_code(CFCPyClass *self) { char *bindings = CFCUtil_strdup(self->pre_code ? self->pre_code : ""); char *meth_defs = CFCUtil_strdup(self->meth_defs); + // Instance methods. + CFCMethod **methods = CFCClass_fresh_methods(klass); + for (size_t j = 0; methods[j] != NULL; j++) { + CFCMethod *meth = methods[j]; + + if (CFCMethod_excluded_from_host(meth) + || !CFCMethod_can_be_bound(meth) + ) { + continue; + } + + // Add the function wrapper. + char *wrapper = CFCPyMethod_wrapper(meth, klass); + bindings = CFCUtil_cat(bindings, wrapper, "\n", NULL); + FREEMEM(wrapper); + } + // Complete the PyMethodDef array. const char *struct_sym = CFCClass_get_struct_sym(klass); char *meth_defs_pattern = diff --git a/compiler/src/CFCPyMethod.c b/compiler/src/CFCPyMethod.c index 983df28b..0a5cd275 100644 --- a/compiler/src/CFCPyMethod.c +++ b/compiler/src/CFCPyMethod.c @@ -215,3 +215,21 @@ S_maybe_unreachable(CFCType *return_type) { return return_statement; } +char* +CFCPyMethod_wrapper(CFCMethod *method, CFCClass *invoker) { + char *meth_sym = CFCMethod_full_method_sym(method, invoker); + + char pattern[] = + "static PyObject*\n" + "S_%s(PyObject *unused1, PyObject *unused2) {\n" + " CFISH_UNUSED_VAR(unused1);\n" + " CFISH_UNUSED_VAR(unused2);\n" + " Py_RETURN_NONE;\n" + "}\n" + ; + char *wrapper = CFCUtil_sprintf(pattern, meth_sym); + FREEMEM(meth_sym); + + return wrapper; +} + diff --git a/compiler/src/CFCPyMethod.h b/compiler/src/CFCPyMethod.h index 15fa1e6f..53768e21 100644 --- a/compiler/src/CFCPyMethod.h +++ b/compiler/src/CFCPyMethod.h @@ -31,6 +31,9 @@ struct CFCClass; char* CFCPyMethod_callback_def(struct CFCMethod *method, struct CFCClass *invoker); +char* +CFCPyMethod_wrapper(struct CFCMethod *method, struct CFCClass *invoker); + #ifdef __cplusplus } #endif From 6a50d2179d0464048f54f021eff3e70029b6162d Mon Sep 17 00:00:00 2001 From: Marvin Humphrey Date: Tue, 2 Feb 2016 10:39:32 -0800 Subject: [PATCH 02/21] Consider num args in Py binding. For generated CPython method glue code, support keyword args if the method takes arguments. --- compiler/src/CFCPyMethod.c | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/compiler/src/CFCPyMethod.c b/compiler/src/CFCPyMethod.c index 0a5cd275..863a0cb7 100644 --- a/compiler/src/CFCPyMethod.c +++ b/compiler/src/CFCPyMethod.c @@ -215,20 +215,40 @@ S_maybe_unreachable(CFCType *return_type) { return return_statement; } +static char* +S_meth_top(CFCMethod *method) { + CFCParamList *param_list = CFCMethod_get_param_list(method); + + if (CFCParamList_num_vars(param_list) == 1) { + char pattern[] = + "(PyObject *self, PyObject *unused) {\n" + " CFISH_UNUSED_VAR(unused);\n" + ; + return CFCUtil_sprintf(pattern); + } + else { + char pattern[] = + "(PyObject *self, PyObject *args, PyObject *kwargs) {\n" + ; + char *result = CFCUtil_sprintf(pattern); + return result; + } +} + char* CFCPyMethod_wrapper(CFCMethod *method, CFCClass *invoker) { char *meth_sym = CFCMethod_full_method_sym(method, invoker); + char *meth_top = S_meth_top(method); char pattern[] = "static PyObject*\n" - "S_%s(PyObject *unused1, PyObject *unused2) {\n" - " CFISH_UNUSED_VAR(unused1);\n" - " CFISH_UNUSED_VAR(unused2);\n" + "S_%s%s" " Py_RETURN_NONE;\n" "}\n" ; - char *wrapper = CFCUtil_sprintf(pattern, meth_sym); + char *wrapper = CFCUtil_sprintf(pattern, meth_sym, meth_top); FREEMEM(meth_sym); + FREEMEM(meth_top); return wrapper; } From 15602e67deb05b28329c9eaf0612a739547ddf8b Mon Sep 17 00:00:00 2001 From: Marvin Humphrey Date: Tue, 2 Feb 2016 11:03:02 -0800 Subject: [PATCH 03/21] Prepare to parse Python meth args. Stub out data structures into which we will read arguments from Python. --- compiler/src/CFCPyMethod.c | 40 +++++++++++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/compiler/src/CFCPyMethod.c b/compiler/src/CFCPyMethod.c index 863a0cb7..c89d12c0 100644 --- a/compiler/src/CFCPyMethod.c +++ b/compiler/src/CFCPyMethod.c @@ -67,6 +67,41 @@ S_build_py_args(CFCParamList *param_list) { return py_args; } +/* Generate the code which parses arguments passed from Python and converts + * them to Clownfish-flavored C values. + */ +static char* +S_gen_arg_parsing(CFCParamList *param_list) { + char *content = NULL; + + CFCVariable **vars = CFCParamList_get_variables(param_list); + const char **vals = CFCParamList_get_initial_values(param_list); + int num_vars = CFCParamList_num_vars(param_list); + + char *declarations = CFCUtil_strdup(""); + char *keywords = CFCUtil_strdup(""); + char *format_str = CFCUtil_strdup(""); + char *targets = CFCUtil_strdup(""); + int optional_started = 0; + + char parse_pattern[] = + "%s" + " char *keywords[] = {%sNULL};\n" + " char *fmt = \"%s\";\n" + " int ok = PyArg_ParseTupleAndKeywords(args, kwargs, fmt,\n" + " keywords%s);\n" + " if (!ok) { return NULL; }\n" + ; + content = CFCUtil_sprintf(parse_pattern, declarations, keywords, + format_str, targets); + + FREEMEM(declarations); + FREEMEM(keywords); + FREEMEM(format_str); + FREEMEM(targets); + return content; +} + static char* S_build_pymeth_invocation(CFCMethod *method) { CFCType *return_type = CFCMethod_get_return_type(method); @@ -227,10 +262,13 @@ S_meth_top(CFCMethod *method) { return CFCUtil_sprintf(pattern); } else { + char *arg_parsing = S_gen_arg_parsing(param_list); char pattern[] = "(PyObject *self, PyObject *args, PyObject *kwargs) {\n" + "%s" ; - char *result = CFCUtil_sprintf(pattern); + char *result = CFCUtil_sprintf(pattern, arg_parsing); + FREEMEM(arg_parsing); return result; } } From 74878d3ce0be405b575552dfb25caa6a00e46ba9 Mon Sep 17 00:00:00 2001 From: Marvin Humphrey Date: Tue, 2 Feb 2016 11:09:57 -0800 Subject: [PATCH 04/21] Detect required arg after optional. For now, Clownfish allows required arguments to appear after optional arguments in a parameter list. Python does not, so we need to detect that error case. Eventually, we'll either fix Clownfish to take on Python's restriction, or else force the user to create manual glue for problematic methods. --- compiler/src/CFCPyMethod.c | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/compiler/src/CFCPyMethod.c b/compiler/src/CFCPyMethod.c index c89d12c0..fcbeee13 100644 --- a/compiler/src/CFCPyMethod.c +++ b/compiler/src/CFCPyMethod.c @@ -71,7 +71,7 @@ S_build_py_args(CFCParamList *param_list) { * them to Clownfish-flavored C values. */ static char* -S_gen_arg_parsing(CFCParamList *param_list) { +S_gen_arg_parsing(CFCParamList *param_list, int first_tick, char **error) { char *content = NULL; CFCVariable **vars = CFCParamList_get_variables(param_list); @@ -84,6 +84,21 @@ S_gen_arg_parsing(CFCParamList *param_list) { char *targets = CFCUtil_strdup(""); int optional_started = 0; + for (int i = first_tick; i < num_vars; i++) { + const char *val = vals[i]; + if (val == NULL) { + if (optional_started) { // problem! + *error = "Required after optional param"; + goto CLEAN_UP_AND_RETURN; + } + } + else { + if (!optional_started) { + optional_started = 1; + } + } + } + char parse_pattern[] = "%s" " char *keywords[] = {%sNULL};\n" @@ -95,6 +110,7 @@ S_gen_arg_parsing(CFCParamList *param_list) { content = CFCUtil_sprintf(parse_pattern, declarations, keywords, format_str, targets); +CLEAN_UP_AND_RETURN: FREEMEM(declarations); FREEMEM(keywords); FREEMEM(format_str); @@ -262,7 +278,14 @@ S_meth_top(CFCMethod *method) { return CFCUtil_sprintf(pattern); } else { - char *arg_parsing = S_gen_arg_parsing(param_list); + char *error = NULL; + char *arg_parsing = S_gen_arg_parsing(param_list, 1, &error); + if (error) { + CFCUtil_die("%s in %s", error, CFCMethod_get_name(method)); + } + if (!arg_parsing) { + return NULL; + } char pattern[] = "(PyObject *self, PyObject *args, PyObject *kwargs) {\n" "%s" From f11b123c496486c51d3a402e16636fc6430fe379 Mon Sep 17 00:00:00 2001 From: Marvin Humphrey Date: Mon, 22 Feb 2016 19:34:59 -0800 Subject: [PATCH 05/21] Declare C arg vars in Py glue. --- compiler/src/CFCPyMethod.c | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/compiler/src/CFCPyMethod.c b/compiler/src/CFCPyMethod.c index fcbeee13..dc98fb45 100644 --- a/compiler/src/CFCPyMethod.c +++ b/compiler/src/CFCPyMethod.c @@ -67,6 +67,20 @@ S_build_py_args(CFCParamList *param_list) { return py_args; } +static char* +S_gen_decs(CFCParamList *param_list, int first_tick) { + char *decs = CFCUtil_strdup(""); + int num_vars = CFCParamList_num_vars(param_list); + CFCVariable **vars = CFCParamList_get_variables(param_list); + for (int i = first_tick; i < num_vars; i++) { + CFCType *type = CFCVariable_get_type(vars[i]); + const char *name = CFCVariable_get_name(vars[i]); + decs = CFCUtil_cat(decs, " ", CFCType_to_c(type), " ", name, + "_ARG = 0;\n", NULL); + } + return decs; +} + /* Generate the code which parses arguments passed from Python and converts * them to Clownfish-flavored C values. */ @@ -286,11 +300,13 @@ S_meth_top(CFCMethod *method) { if (!arg_parsing) { return NULL; } + char *decs = S_gen_decs(param_list, 1); char pattern[] = "(PyObject *self, PyObject *args, PyObject *kwargs) {\n" + "%s" // decs "%s" ; - char *result = CFCUtil_sprintf(pattern, arg_parsing); + char *result = CFCUtil_sprintf(pattern, decs, arg_parsing); FREEMEM(arg_parsing); return result; } From 1853cbfd000430937312142034090de25de8039f Mon Sep 17 00:00:00 2001 From: Marvin Humphrey Date: Tue, 2 Feb 2016 13:38:19 -0800 Subject: [PATCH 06/21] Gen CFBindArg declarations. Generate the declarations for the CFBindArg wrapper structs which are required by some Python argument conversion routines. --- compiler/src/CFCPyMethod.c | 58 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/compiler/src/CFCPyMethod.c b/compiler/src/CFCPyMethod.c index dc98fb45..48ef4437 100644 --- a/compiler/src/CFCPyMethod.c +++ b/compiler/src/CFCPyMethod.c @@ -81,6 +81,58 @@ S_gen_decs(CFCParamList *param_list, int first_tick) { return decs; } +/* Some of the ParseTuple conversion routines provided by the Python-flavored + * CFBind module accept a CFBindArg instead of just a pointer to the value + * itself. This routine generates the declarations for those CFBindArg + * variables, as well as handling some default values. + */ +static char* +S_gen_declaration(CFCVariable *var, const char *val) { + CFCType *type = CFCVariable_get_type(var); + const char *var_name = CFCVariable_get_name(var); + const char *type_str = CFCType_to_c(type); + char *result = NULL; + + if (CFCType_is_object(type)) { + const char *specifier = CFCType_get_specifier(type); + if (strcmp(specifier, "cfish_String") == 0) { + if (val && strcmp(val, "NULL") != 0) { + const char pattern[] = + " const char arg_%s_DEFAULT[] = %s;\n" + " %s_ARG = CFISH_SSTR_WRAP_UTF8(\n" + " arg_%s_DEFAULT, sizeof(arg_%s_DEFAULT) - 1);\n" + ; + result = CFCUtil_sprintf(pattern, var_name, val, var_name, + var_name, var_name); + } + } + else { + if (val && strcmp(val, "NULL") != 0) { + CFCUtil_die("Can't assign a default of '%s' to a %s", + val, type_str); + } + if (strcmp(specifier, "cfish_Hash") != 0 + && strcmp(specifier, "cfish_Vector") != 0 + ) { + const char *class_var = CFCType_get_class_var(type); + char pattern[] = + " CFBindArg wrap_arg_%s = {%s, &%s_ARG};\n" + ; + result = CFCUtil_sprintf(pattern, var_name, class_var, + var_name); + } + } + } + else if (CFCType_is_primitive(type)) { + ; + } + else { + CFCUtil_die("Unexpected type, can't gen declaration: %s", type_str); + } + + return result; +} + /* Generate the code which parses arguments passed from Python and converts * them to Clownfish-flavored C values. */ @@ -99,7 +151,9 @@ S_gen_arg_parsing(CFCParamList *param_list, int first_tick, char **error) { int optional_started = 0; for (int i = first_tick; i < num_vars; i++) { + CFCVariable *var = vars[i]; const char *val = vals[i]; + if (val == NULL) { if (optional_started) { // problem! *error = "Required after optional param"; @@ -111,6 +165,10 @@ S_gen_arg_parsing(CFCParamList *param_list, int first_tick, char **error) { optional_started = 1; } } + + char *declaration = S_gen_declaration(var, val); + declarations = CFCUtil_cat(declarations, declaration, NULL); + FREEMEM(declaration); } char parse_pattern[] = From ba87d53627914d6815122d59e9b0ae5afbcf9e23 Mon Sep 17 00:00:00 2001 From: Marvin Humphrey Date: Tue, 2 Feb 2016 13:42:27 -0800 Subject: [PATCH 07/21] Gen list of converter/arg-pointer pairs. Generate the list of function-pointer/arg-pointer pairs passed which get passed to `PyArg_ParseTupleAndKeywords`. --- compiler/src/CFCPyMethod.c | 51 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/compiler/src/CFCPyMethod.c b/compiler/src/CFCPyMethod.c index 48ef4437..c0363a57 100644 --- a/compiler/src/CFCPyMethod.c +++ b/compiler/src/CFCPyMethod.c @@ -133,6 +133,53 @@ S_gen_declaration(CFCVariable *var, const char *val) { return result; } +static char* +S_gen_target(CFCVariable *var, const char *value) { + CFCType *type = CFCVariable_get_type(var); + const char *specifier = CFCType_get_specifier(type); + const char *micro_sym = CFCVariable_get_name(var); + const char *maybe_maybe = ""; + const char *dest_name; + char *var_name = NULL; + if (CFCType_is_primitive(type)) { + dest_name = CFCType_get_specifier(type); + if (value != NULL) { + maybe_maybe = "maybe_"; + } + var_name = CFCUtil_sprintf("%s_ARG", CFCVariable_get_name(var)); + } + else if (CFCType_is_object(type)) { + if (CFCType_nullable(type) || + (value && strcmp(value, "NULL") == 0) + ) { + maybe_maybe = "maybe_"; + } + if (strcmp(specifier, "cfish_String") == 0) { + dest_name = "string"; + var_name = CFCUtil_sprintf("%s_ARG", CFCVariable_get_name(var)); + } + else if (strcmp(specifier, "cfish_Hash") == 0) { + dest_name = "hash"; + var_name = CFCUtil_sprintf("%s_ARG", CFCVariable_get_name(var)); + } + else if (strcmp(specifier, "cfish_Vector") == 0) { + dest_name = "vec"; + var_name = CFCUtil_sprintf("%s_ARG", CFCVariable_get_name(var)); + } + else { + dest_name = "obj"; + var_name = CFCUtil_sprintf("wrap_arg_%s", micro_sym); + } + } + else { + dest_name = "INVALID"; + } + char *content = CFCUtil_sprintf(", CFBind_%sconvert_%s, &%s", + maybe_maybe, dest_name, var_name); + FREEMEM(var_name); + return content; +} + /* Generate the code which parses arguments passed from Python and converts * them to Clownfish-flavored C values. */ @@ -169,6 +216,10 @@ S_gen_arg_parsing(CFCParamList *param_list, int first_tick, char **error) { char *declaration = S_gen_declaration(var, val); declarations = CFCUtil_cat(declarations, declaration, NULL); FREEMEM(declaration); + + char *target = S_gen_target(var, val); + targets = CFCUtil_cat(targets, target, NULL); + FREEMEM(target); } char parse_pattern[] = From 4cdfb56b6ed149cfef354720e7514d1d23bd8769 Mon Sep 17 00:00:00 2001 From: Marvin Humphrey Date: Tue, 2 Feb 2016 13:45:03 -0800 Subject: [PATCH 08/21] Build up the ParseTuple format string. --- compiler/src/CFCPyMethod.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/compiler/src/CFCPyMethod.c b/compiler/src/CFCPyMethod.c index c0363a57..8d6adfbf 100644 --- a/compiler/src/CFCPyMethod.c +++ b/compiler/src/CFCPyMethod.c @@ -201,6 +201,10 @@ S_gen_arg_parsing(CFCParamList *param_list, int first_tick, char **error) { CFCVariable *var = vars[i]; const char *val = vals[i]; + const char *var_name = CFCVariable_get_name(var); + keywords = CFCUtil_cat(keywords, "\"", var_name, "\", ", NULL); + + // Build up ParseTuple format string. if (val == NULL) { if (optional_started) { // problem! *error = "Required after optional param"; @@ -210,8 +214,10 @@ S_gen_arg_parsing(CFCParamList *param_list, int first_tick, char **error) { else { if (!optional_started) { optional_started = 1; + format_str = CFCUtil_cat(format_str, "|", NULL); } } + format_str = CFCUtil_cat(format_str, "O&", NULL); char *declaration = S_gen_declaration(var, val); declarations = CFCUtil_cat(declarations, declaration, NULL); From a9daf3cf7bb82ee5c9f51a83b5f3d166fb35de6d Mon Sep 17 00:00:00 2001 From: Marvin Humphrey Date: Tue, 2 Feb 2016 15:44:10 -0800 Subject: [PATCH 09/21] Incref `decremented` args in Py glue. For args that are specified as `decremented` in the Clownfish method signature, add an INCREF in the glue code. --- compiler/src/CFCPyMethod.c | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/compiler/src/CFCPyMethod.c b/compiler/src/CFCPyMethod.c index 8d6adfbf..5464dbb2 100644 --- a/compiler/src/CFCPyMethod.c +++ b/compiler/src/CFCPyMethod.c @@ -427,18 +427,43 @@ S_meth_top(CFCMethod *method) { } } +static char* +S_gen_arg_increfs(CFCParamList *param_list, int first_tick) { + CFCVariable **vars = CFCParamList_get_variables(param_list); + int num_vars = CFCParamList_num_vars(param_list); + char *content = CFCUtil_strdup(""); + for (int i = first_tick;i < num_vars; i++) { + CFCType *type = CFCVariable_get_type(vars[i]); + if (CFCType_decremented(type)) { + const char *name = CFCVariable_get_name(vars[i]); + const char *specifier = CFCType_get_specifier(type); + char pattern[] = + " %s_ARG = (%s*)CFISH_INCREF(%s_ARG);\n"; + char *incref = CFCUtil_sprintf(pattern, name, specifier, name); + content = CFCUtil_cat(content, incref, NULL); + FREEMEM(incref); + } + } + return content; +} + char* CFCPyMethod_wrapper(CFCMethod *method, CFCClass *invoker) { + CFCParamList *param_list = CFCMethod_get_param_list(method); char *meth_sym = CFCMethod_full_method_sym(method, invoker); char *meth_top = S_meth_top(method); + char *increfs = S_gen_arg_increfs(param_list, 1); char pattern[] = "static PyObject*\n" "S_%s%s" + "%s" // increfs " Py_RETURN_NONE;\n" "}\n" ; - char *wrapper = CFCUtil_sprintf(pattern, meth_sym, meth_top); + char *wrapper = CFCUtil_sprintf(pattern, meth_sym, meth_top, + increfs); + FREEMEM(increfs); FREEMEM(meth_sym); FREEMEM(meth_top); From d88c0bf5c19a22b655bb5c59a6f2a0d2c366f85c Mon Sep 17 00:00:00 2001 From: Marvin Humphrey Date: Tue, 2 Feb 2016 15:46:23 -0800 Subject: [PATCH 10/21] Prepare decrefs to run in Py glue. --- compiler/src/CFCPyMethod.c | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/compiler/src/CFCPyMethod.c b/compiler/src/CFCPyMethod.c index 5464dbb2..450c26ee 100644 --- a/compiler/src/CFCPyMethod.c +++ b/compiler/src/CFCPyMethod.c @@ -447,22 +447,51 @@ S_gen_arg_increfs(CFCParamList *param_list, int first_tick) { return content; } +// Prep refcount decrement calls to follow the Clownfish subroutine +// invocation. +static char* +S_gen_decrefs(CFCParamList *param_list, int first_tick) { + CFCVariable **vars = CFCParamList_get_variables(param_list); + int num_vars = CFCParamList_num_vars(param_list); + char *decrefs = CFCUtil_strdup(""); + + for (int i = first_tick; i < num_vars; i++) { + CFCVariable *var = vars[i]; + CFCType *type = CFCVariable_get_type(var); + const char *micro_sym = CFCVariable_get_name(var); + const char *specifier = CFCType_get_specifier(type); + + if (strcmp(specifier, "cfish_String") == 0 + || strcmp(specifier, "cfish_Vector") == 0 + || strcmp(specifier, "cfish_Hash") == 0 + ) { + decrefs = CFCUtil_cat(decrefs, " CFISH_DECREF(", micro_sym, + "_ARG);\n", NULL); + } + } + + return decrefs; +} + char* CFCPyMethod_wrapper(CFCMethod *method, CFCClass *invoker) { CFCParamList *param_list = CFCMethod_get_param_list(method); char *meth_sym = CFCMethod_full_method_sym(method, invoker); char *meth_top = S_meth_top(method); char *increfs = S_gen_arg_increfs(param_list, 1); + char *decrefs = S_gen_decrefs(param_list, 1); char pattern[] = "static PyObject*\n" "S_%s%s" "%s" // increfs + "%s" // decrefs " Py_RETURN_NONE;\n" "}\n" ; char *wrapper = CFCUtil_sprintf(pattern, meth_sym, meth_top, - increfs); + increfs, decrefs); + FREEMEM(decrefs); FREEMEM(increfs); FREEMEM(meth_sym); FREEMEM(meth_top); From 71c836a2a2329c3a999ce102a6bd38f1b0f08169 Mon Sep 17 00:00:00 2001 From: Marvin Humphrey Date: Tue, 2 Feb 2016 16:08:54 -0800 Subject: [PATCH 11/21] Add helper for generating arg lists. --- compiler/src/CFCPyMethod.c | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/compiler/src/CFCPyMethod.c b/compiler/src/CFCPyMethod.c index 450c26ee..4220aea1 100644 --- a/compiler/src/CFCPyMethod.c +++ b/compiler/src/CFCPyMethod.c @@ -473,6 +473,26 @@ S_gen_decrefs(CFCParamList *param_list, int first_tick) { return decrefs; } +static char* +S_gen_arg_list(CFCParamList *param_list, const char *first_arg) { + CFCVariable **vars = CFCParamList_get_variables(param_list); + int num_vars = CFCParamList_num_vars(param_list); + char *arg_list = CFCUtil_strdup(""); + for (int i = 0; i < num_vars; i++) { + if (i > 0) { + arg_list = CFCUtil_cat(arg_list, ", ", NULL); + } + if (i == 0 && first_arg != NULL) { + arg_list = CFCUtil_cat(arg_list, first_arg, NULL); + } + else { + arg_list = CFCUtil_cat(arg_list, CFCVariable_get_name(vars[i]), + "_ARG", NULL); + } + } + return arg_list; +} + char* CFCPyMethod_wrapper(CFCMethod *method, CFCClass *invoker) { CFCParamList *param_list = CFCMethod_get_param_list(method); From d8ced59cb135f628c0266e6aff0141152280f603 Mon Sep 17 00:00:00 2001 From: Marvin Humphrey Date: Tue, 2 Feb 2016 17:13:28 -0800 Subject: [PATCH 12/21] Register PyMethodDefs for glued methods. Create PyMethodDef entries for each wrapped Clownfish method. --- compiler/src/CFCPyClass.c | 5 +++++ compiler/src/CFCPyMethod.c | 21 +++++++++++++++++++++ compiler/src/CFCPyMethod.h | 5 +++++ runtime/python/test/test_clownfish.py | 2 ++ 4 files changed, 33 insertions(+) diff --git a/compiler/src/CFCPyClass.c b/compiler/src/CFCPyClass.c index 11a49058..529c6cfb 100644 --- a/compiler/src/CFCPyClass.c +++ b/compiler/src/CFCPyClass.c @@ -160,6 +160,11 @@ CFCPyClass_gen_binding_code(CFCPyClass *self) { char *wrapper = CFCPyMethod_wrapper(meth, klass); bindings = CFCUtil_cat(bindings, wrapper, "\n", NULL); FREEMEM(wrapper); + + // Add PyMethodDef entry. + char *meth_def = CFCPyMethod_pymethoddef(meth, klass); + meth_defs = CFCUtil_cat(meth_defs, " ", meth_def, "\n", NULL); + FREEMEM(meth_def); } // Complete the PyMethodDef array. diff --git a/compiler/src/CFCPyMethod.c b/compiler/src/CFCPyMethod.c index 4220aea1..12915689 100644 --- a/compiler/src/CFCPyMethod.c +++ b/compiler/src/CFCPyMethod.c @@ -519,3 +519,24 @@ CFCPyMethod_wrapper(CFCMethod *method, CFCClass *invoker) { return wrapper; } +char* +CFCPyMethod_pymethoddef(CFCMethod *method, CFCClass *invoker) { + CFCParamList *param_list = CFCMethod_get_param_list(method); + const char *flags = CFCParamList_num_vars(param_list) == 1 + ? "METH_NOARGS" + : "METH_KEYWORDS|METH_VARARGS"; + char *meth_sym = CFCMethod_full_method_sym(method, invoker); + char *micro_sym = CFCUtil_strdup(CFCSymbol_get_name((CFCSymbol*)method)); + for (int i = 0; micro_sym[i] != 0; i++) { + micro_sym[i] = tolower(micro_sym[i]); + } + + char pattern[] = + "{\"%s\", (PyCFunction)S_%s, %s, NULL},"; + char *py_meth_def = CFCUtil_sprintf(pattern, micro_sym, meth_sym, flags); + + FREEMEM(meth_sym); + FREEMEM(micro_sym); + return py_meth_def; +} + diff --git a/compiler/src/CFCPyMethod.h b/compiler/src/CFCPyMethod.h index 53768e21..b3d68089 100644 --- a/compiler/src/CFCPyMethod.h +++ b/compiler/src/CFCPyMethod.h @@ -31,6 +31,11 @@ struct CFCClass; char* CFCPyMethod_callback_def(struct CFCMethod *method, struct CFCClass *invoker); +/** Generate a PyMethodDef entry for an instance method. + */ +char* +CFCPyMethod_pymethoddef(struct CFCMethod *method, struct CFCClass *invoker); + char* CFCPyMethod_wrapper(struct CFCMethod *method, struct CFCClass *invoker); diff --git a/runtime/python/test/test_clownfish.py b/runtime/python/test/test_clownfish.py index d8f2594c..f7908ca6 100644 --- a/runtime/python/test/test_clownfish.py +++ b/runtime/python/test/test_clownfish.py @@ -14,6 +14,7 @@ # limitations under the License. import unittest +import inspect import clownfish class MyTest(unittest.TestCase): @@ -23,6 +24,7 @@ def testTrue(self): def testClassesPresent(self): self.assertIsInstance(clownfish.Hash, type) + self.assertTrue(inspect.ismethoddescriptor(clownfish.Hash.store)) if __name__ == '__main__': unittest.main() From 5443a74386b0bd6217d12c16a5ef9265c99d78c7 Mon Sep 17 00:00:00 2001 From: Marvin Humphrey Date: Tue, 2 Feb 2016 17:50:12 -0800 Subject: [PATCH 13/21] Stub out generated Python constructors. --- compiler/src/CFCPyClass.c | 23 +++++++++++++++++++++-- compiler/src/CFCPyMethod.c | 18 ++++++++++++++++++ compiler/src/CFCPyMethod.h | 7 +++++++ 3 files changed, 46 insertions(+), 2 deletions(-) diff --git a/compiler/src/CFCPyClass.c b/compiler/src/CFCPyClass.c index 529c6cfb..0fe168e6 100644 --- a/compiler/src/CFCPyClass.c +++ b/compiler/src/CFCPyClass.c @@ -145,6 +145,14 @@ CFCPyClass_gen_binding_code(CFCPyClass *self) { char *bindings = CFCUtil_strdup(self->pre_code ? self->pre_code : ""); char *meth_defs = CFCUtil_strdup(self->meth_defs); + // Constructor. + CFCFunction *init_func = CFCClass_function(klass, "init"); + if (init_func && CFCFunction_can_be_bound(init_func)) { + char *wrapper = CFCPyMethod_constructor_wrapper(init_func, klass); + bindings = CFCUtil_cat(bindings, wrapper, "\n", NULL); + FREEMEM(wrapper); + } + // Instance methods. CFCMethod **methods = CFCClass_fresh_methods(klass); for (size_t j = 0; methods[j] != NULL; j++) { @@ -201,6 +209,16 @@ S_pytype_struct_def(CFCPyClass *self) { const char *struct_sym = CFCClass_get_struct_sym(klass); + char *tp_new; + CFCFunction *init_func = CFCClass_function(klass, "init"); + if (init_func && CFCFunction_can_be_bound(init_func)) { + tp_new = CFCUtil_sprintf("S_%s_PY_NEW", + CFCClass_full_struct_sym(klass)); + } + else { + tp_new = CFCUtil_strdup("0"); + } + char pattern[] = "static PyTypeObject %s_pytype_struct = {\n" " PyVarObject_HEAD_INIT(NULL, 0)\n" @@ -240,12 +258,13 @@ S_pytype_struct_def(CFCPyClass *self) { " 0, // tp_dictoffset\n" " 0, // tp_init\n" " 0, // tp_allow\n" - " 0, // tp_new\n" + " %s, // tp_new\n" "};\n" ; char *content = CFCUtil_sprintf(pattern, struct_sym, pymod_name, - struct_sym, struct_sym); + struct_sym, struct_sym, tp_new); + FREEMEM(tp_new); FREEMEM(pymod_name); return content; } diff --git a/compiler/src/CFCPyMethod.c b/compiler/src/CFCPyMethod.c index 12915689..c93252ec 100644 --- a/compiler/src/CFCPyMethod.c +++ b/compiler/src/CFCPyMethod.c @@ -519,6 +519,24 @@ CFCPyMethod_wrapper(CFCMethod *method, CFCClass *invoker) { return wrapper; } +char* +CFCPyMethod_constructor_wrapper(CFCFunction *init_func, CFCClass *invoker) { + const char *struct_sym = CFCClass_full_struct_sym(invoker); + + char pattern[] = + "static PyObject*\n" + "S_%s_PY_NEW(PyTypeObject *type, PyObject *args, PyObject *kwargs) {\n" + " CFISH_UNUSED_VAR(type);\n" + " CFISH_UNUSED_VAR(args);\n" + " CFISH_UNUSED_VAR(kwargs);\n" + " Py_RETURN_NONE;\n" + "}\n" + ; + char *wrapper = CFCUtil_sprintf(pattern, struct_sym); + + return wrapper; +} + char* CFCPyMethod_pymethoddef(CFCMethod *method, CFCClass *invoker) { CFCParamList *param_list = CFCMethod_get_param_list(method); diff --git a/compiler/src/CFCPyMethod.h b/compiler/src/CFCPyMethod.h index b3d68089..f85844f9 100644 --- a/compiler/src/CFCPyMethod.h +++ b/compiler/src/CFCPyMethod.h @@ -21,6 +21,7 @@ extern "C" { #endif +struct CFCFunction; struct CFCMethod; struct CFCClass; @@ -39,6 +40,12 @@ CFCPyMethod_pymethoddef(struct CFCMethod *method, struct CFCClass *invoker); char* CFCPyMethod_wrapper(struct CFCMethod *method, struct CFCClass *invoker); +/** Generate glue code for a constructor. + */ +char* +CFCPyMethod_constructor_wrapper(struct CFCFunction *init_func, + struct CFCClass *invoker); + #ifdef __cplusplus } #endif From a6068b41523b9c216bb63ace4d66f9f5cccf1477 Mon Sep 17 00:00:00 2001 From: Marvin Humphrey Date: Tue, 2 Feb 2016 18:43:41 -0800 Subject: [PATCH 14/21] Gen arg parsing for Py constructor glue. --- compiler/src/CFCPyMethod.c | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/compiler/src/CFCPyMethod.c b/compiler/src/CFCPyMethod.c index c93252ec..2f6168ad 100644 --- a/compiler/src/CFCPyMethod.c +++ b/compiler/src/CFCPyMethod.c @@ -521,19 +521,33 @@ CFCPyMethod_wrapper(CFCMethod *method, CFCClass *invoker) { char* CFCPyMethod_constructor_wrapper(CFCFunction *init_func, CFCClass *invoker) { + CFCParamList *param_list = CFCFunction_get_param_list(init_func); + char *decs = S_gen_decs(param_list, 1); const char *struct_sym = CFCClass_full_struct_sym(invoker); + char *error = NULL; + char *arg_parsing = S_gen_arg_parsing(param_list, 1, &error); + if (error) { + CFCUtil_die("%s in constructor for %s", error, + CFCClass_get_name(invoker)); + } + if (!arg_parsing) { + CFCUtil_die("Unexpected arg parsing error for %s", + CFCClass_get_name(invoker)); + } char pattern[] = "static PyObject*\n" "S_%s_PY_NEW(PyTypeObject *type, PyObject *args, PyObject *kwargs) {\n" - " CFISH_UNUSED_VAR(type);\n" - " CFISH_UNUSED_VAR(args);\n" - " CFISH_UNUSED_VAR(kwargs);\n" + "%s" // decs + "%s" // arg_parsing " Py_RETURN_NONE;\n" "}\n" ; - char *wrapper = CFCUtil_sprintf(pattern, struct_sym); + char *wrapper = CFCUtil_sprintf(pattern, struct_sym, decs, + arg_parsing); + FREEMEM(decs); + FREEMEM(arg_parsing); return wrapper; } From 2e4d3d3e6292539af7aab6419286ebe1be90e5ba Mon Sep 17 00:00:00 2001 From: Marvin Humphrey Date: Tue, 2 Feb 2016 18:48:50 -0800 Subject: [PATCH 15/21] Add increfs and decrefs to gen ctor glue. Incref decremented args. Signal to CFBind_run_trapped when it needs to decref some args. --- compiler/src/CFCPyMethod.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/compiler/src/CFCPyMethod.c b/compiler/src/CFCPyMethod.c index 2f6168ad..ef8b1521 100644 --- a/compiler/src/CFCPyMethod.c +++ b/compiler/src/CFCPyMethod.c @@ -523,6 +523,8 @@ char* CFCPyMethod_constructor_wrapper(CFCFunction *init_func, CFCClass *invoker) { CFCParamList *param_list = CFCFunction_get_param_list(init_func); char *decs = S_gen_decs(param_list, 1); + char *increfs = S_gen_arg_increfs(param_list, 1); + char *decrefs = S_gen_decrefs(param_list, 1); const char *struct_sym = CFCClass_full_struct_sym(invoker); char *error = NULL; char *arg_parsing = S_gen_arg_parsing(param_list, 1, &error); @@ -540,12 +542,16 @@ CFCPyMethod_constructor_wrapper(CFCFunction *init_func, CFCClass *invoker) { "S_%s_PY_NEW(PyTypeObject *type, PyObject *args, PyObject *kwargs) {\n" "%s" // decs "%s" // arg_parsing + "%s" // increfs + "%s" // decrefs " Py_RETURN_NONE;\n" "}\n" ; char *wrapper = CFCUtil_sprintf(pattern, struct_sym, decs, - arg_parsing); + arg_parsing, increfs, decrefs); + FREEMEM(decrefs); + FREEMEM(increfs); FREEMEM(decs); FREEMEM(arg_parsing); return wrapper; From 73635f233dc3a5b1addf17aacdf5773d8fad1113 Mon Sep 17 00:00:00 2001 From: Marvin Humphrey Date: Fri, 5 Feb 2016 18:03:01 -0800 Subject: [PATCH 16/21] Handle default values for primitive types. Set conversion args to default values prior to Python argument parsing. --- compiler/src/CFCPyMethod.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/compiler/src/CFCPyMethod.c b/compiler/src/CFCPyMethod.c index ef8b1521..d8128c29 100644 --- a/compiler/src/CFCPyMethod.c +++ b/compiler/src/CFCPyMethod.c @@ -124,7 +124,10 @@ S_gen_declaration(CFCVariable *var, const char *val) { } } else if (CFCType_is_primitive(type)) { - ; + if (val) { + char pattern[] = " %s_ARG = %s;\n"; + result = CFCUtil_sprintf(pattern, var_name, val); + } } else { CFCUtil_die("Unexpected type, can't gen declaration: %s", type_str); From b05965cafb60362badf530872dc81a304b9db761 Mon Sep 17 00:00:00 2001 From: Marvin Humphrey Date: Sat, 6 Feb 2016 09:41:59 -0800 Subject: [PATCH 17/21] More liberal Py to Cfish Obj conversion. When converting a Python argument to Clownfish `Obj*`, allow conversion from Python dict, list, string, integer, and other types -- instead of just checking to confirm that the argument is already a Clownfish Obj. --- compiler/src/CFCPyMethod.c | 3 ++- runtime/python/cfext/CFBind.c | 7 ++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/compiler/src/CFCPyMethod.c b/compiler/src/CFCPyMethod.c index d8128c29..ea97d6ae 100644 --- a/compiler/src/CFCPyMethod.c +++ b/compiler/src/CFCPyMethod.c @@ -464,7 +464,8 @@ S_gen_decrefs(CFCParamList *param_list, int first_tick) { const char *micro_sym = CFCVariable_get_name(var); const char *specifier = CFCType_get_specifier(type); - if (strcmp(specifier, "cfish_String") == 0 + if (strcmp(specifier, "cfish_Obj") == 0 + || strcmp(specifier, "cfish_String") == 0 || strcmp(specifier, "cfish_Vector") == 0 || strcmp(specifier, "cfish_Hash") == 0 ) { diff --git a/runtime/python/cfext/CFBind.c b/runtime/python/cfext/CFBind.c index a1e47eb0..cd770783 100644 --- a/runtime/python/cfext/CFBind.c +++ b/runtime/python/cfext/CFBind.c @@ -294,12 +294,13 @@ S_convert_obj(PyObject *py_obj, CFBindArg *arg, bool nullable) { return 0; } } - PyTypeObject *py_type = S_get_cached_py_type(arg->klass); - if (!PyObject_TypeCheck(py_obj, py_type)) { + + bool success = S_maybe_py_to_cfish(py_obj, arg->klass, true, nullable, + NULL, arg->ptr); + if (!success) { PyErr_SetString(PyExc_TypeError, "Invalid argument type"); return 0; } - *((PyObject**)arg->ptr) = py_obj; return 1; } From 674721cc3e0f7fac030cf75fcdcfc4ee3d2f539f Mon Sep 17 00:00:00 2001 From: Marvin Humphrey Date: Mon, 22 Feb 2016 18:29:02 -0800 Subject: [PATCH 18/21] Test err handling in Py glue. Check that a Clownfish exception gets transformed into a Python exception. --- runtime/python/test/test_err.py | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 runtime/python/test/test_err.py diff --git a/runtime/python/test/test_err.py b/runtime/python/test/test_err.py new file mode 100644 index 00000000..a553d748 --- /dev/null +++ b/runtime/python/test/test_err.py @@ -0,0 +1,31 @@ +# 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. + +import unittest +import clownfish +import sys + +class TestErr(unittest.TestCase): + + def testErrorHandling(self): + vec = clownfish.Vector() + try: + vec.grow(sys.maxsize) + except RuntimeError as e: + self.assertTrue(str(e).find("overflow") != -1) + +if __name__ == '__main__': + unittest.main() + From f624495ea3f0e80a6d95fb37d51fdb7a2b5b8124 Mon Sep 17 00:00:00 2001 From: Marvin Humphrey Date: Mon, 22 Feb 2016 19:40:17 -0800 Subject: [PATCH 19/21] Add actual method invocation to Py glue. --- compiler/src/CFCPyMethod.c | 64 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 62 insertions(+), 2 deletions(-) diff --git a/compiler/src/CFCPyMethod.c b/compiler/src/CFCPyMethod.c index ea97d6ae..f83fdc0b 100644 --- a/compiler/src/CFCPyMethod.c +++ b/compiler/src/CFCPyMethod.c @@ -497,24 +497,84 @@ S_gen_arg_list(CFCParamList *param_list, const char *first_arg) { return arg_list; } +static char* +S_gen_meth_invocation(CFCMethod *method, CFCClass *invoker) { + CFCParamList *param_list = CFCMethod_get_param_list(method); + char *full_meth = CFCMethod_full_method_sym(method, invoker); + char *meth_type_c = CFCMethod_full_typedef(method, invoker); + const char *class_var = CFCClass_full_class_var(invoker); + char *first_arg + = CFCUtil_sprintf("(%s*)self", CFCClass_full_struct_sym(invoker)); + char *arg_list = S_gen_arg_list(param_list, first_arg); + + CFCType *return_type = CFCMethod_get_return_type(method); + char *maybe_declare; + const char *maybe_assign; + if (CFCType_is_void(return_type)) { + maybe_declare = CFCUtil_strdup(""); + maybe_assign = ""; + } + else { + maybe_declare = CFCUtil_sprintf(" %s retvalCF;\n", + CFCType_to_c(return_type)); + maybe_assign = "retvalCF = "; + } + + const char pattern[] = + "%s" + " %s method = CFISH_METHOD_PTR(%s, %s);\n" + " CFBIND_TRY(%smethod(%s));\n" + ; + char *content + = CFCUtil_sprintf(pattern, maybe_declare, meth_type_c, class_var, + full_meth, maybe_assign, arg_list); + + FREEMEM(arg_list); + FREEMEM(first_arg); + FREEMEM(maybe_declare); + FREEMEM(full_meth); + FREEMEM(meth_type_c); + return content; +} + char* CFCPyMethod_wrapper(CFCMethod *method, CFCClass *invoker) { CFCParamList *param_list = CFCMethod_get_param_list(method); + CFCType *return_type = CFCMethod_get_return_type(method); char *meth_sym = CFCMethod_full_method_sym(method, invoker); char *meth_top = S_meth_top(method); char *increfs = S_gen_arg_increfs(param_list, 1); char *decrefs = S_gen_decrefs(param_list, 1); + char *invocation = S_gen_meth_invocation(method, invoker); + char *ret; + if (CFCType_is_void(return_type)) { + ret = CFCUtil_strdup(" Py_RETURN_NONE;\n"); + } + else if (CFCType_incremented(return_type)) { + ret = CFCUtil_strdup(" return CFBind_cfish_to_py_zeroref((cfish_Obj*)retvalCF);\n"); + } + else { + char *conv = CFCPyTypeMap_c_to_py(return_type, "retvalCF"); + ret = CFCUtil_sprintf(" return %s;\n", conv); + FREEMEM(conv); + } char pattern[] = "static PyObject*\n" "S_%s%s" "%s" // increfs + "%s" // invocation "%s" // decrefs - " Py_RETURN_NONE;\n" + " if (CFBind_migrate_cferr()) {\n" + " return NULL;\n" + " }\n" + "%s" // ret "}\n" ; char *wrapper = CFCUtil_sprintf(pattern, meth_sym, meth_top, - increfs, decrefs); + increfs, invocation, decrefs, ret); + FREEMEM(ret); + FREEMEM(invocation); FREEMEM(decrefs); FREEMEM(increfs); FREEMEM(meth_sym); From 8b41f26264da06c6b43655a1e939ac1123957d9d Mon Sep 17 00:00:00 2001 From: Marvin Humphrey Date: Mon, 22 Feb 2016 19:40:54 -0800 Subject: [PATCH 20/21] Add actual constructor invocation to Py glue. --- compiler/src/CFCPyMethod.c | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/compiler/src/CFCPyMethod.c b/compiler/src/CFCPyMethod.c index f83fdc0b..3d13665c 100644 --- a/compiler/src/CFCPyMethod.c +++ b/compiler/src/CFCPyMethod.c @@ -586,9 +586,13 @@ CFCPyMethod_wrapper(CFCMethod *method, CFCClass *invoker) { char* CFCPyMethod_constructor_wrapper(CFCFunction *init_func, CFCClass *invoker) { CFCParamList *param_list = CFCFunction_get_param_list(init_func); + const char *self_type + = CFCType_to_c(CFCFunction_get_return_type(init_func)); + char *func_sym = CFCFunction_full_func_sym(init_func, invoker); char *decs = S_gen_decs(param_list, 1); char *increfs = S_gen_arg_increfs(param_list, 1); char *decrefs = S_gen_decrefs(param_list, 1); + const char *class_var = CFCClass_full_class_var(invoker); const char *struct_sym = CFCClass_full_struct_sym(invoker); char *error = NULL; char *arg_parsing = S_gen_arg_parsing(param_list, 1, &error); @@ -600,6 +604,9 @@ CFCPyMethod_constructor_wrapper(CFCFunction *init_func, CFCClass *invoker) { CFCUtil_die("Unexpected arg parsing error for %s", CFCClass_get_name(invoker)); } + char *first_arg = CFCUtil_sprintf("(%s)CFISH_Class_Make_Obj(%s)", + self_type, class_var); + char *arg_list = S_gen_arg_list(param_list, first_arg); char pattern[] = "static PyObject*\n" @@ -607,13 +614,22 @@ CFCPyMethod_constructor_wrapper(CFCFunction *init_func, CFCClass *invoker) { "%s" // decs "%s" // arg_parsing "%s" // increfs + " %s self = NULL;\n" + " CFBIND_TRY(self = %s(%s));\n" "%s" // decrefs - " Py_RETURN_NONE;\n" + " if (CFBind_migrate_cferr()) {\n" + " return NULL;\n" + " }\n" + " return (PyObject*)self;\n" "}\n" ; char *wrapper = CFCUtil_sprintf(pattern, struct_sym, decs, - arg_parsing, increfs, decrefs); + arg_parsing, increfs, self_type, + func_sym, arg_list, decrefs); + FREEMEM(arg_list); + FREEMEM(first_arg); + FREEMEM(func_sym); FREEMEM(decrefs); FREEMEM(increfs); FREEMEM(decs); From 9d058f4b324dabf4f2cdd2ea88f40c9aabe2d622 Mon Sep 17 00:00:00 2001 From: Marvin Humphrey Date: Wed, 10 Feb 2016 18:45:50 -0800 Subject: [PATCH 21/21] Add test for the Py binding of Hash. --- runtime/python/test/test_hash.py | 102 +++++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 runtime/python/test/test_hash.py diff --git a/runtime/python/test/test_hash.py b/runtime/python/test/test_hash.py new file mode 100644 index 00000000..5bfd0340 --- /dev/null +++ b/runtime/python/test/test_hash.py @@ -0,0 +1,102 @@ +# 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. + +import unittest +import inspect +import clownfish + +class TestHash(unittest.TestCase): + + def testStoreFetch(self): + h = clownfish.Hash() + h.store("foo", "bar") + h.store("foo", "bar") + self.assertEqual(h.fetch("foo"), "bar") + h.store("nada", None) + self.assertEqual(h.fetch("nada"), None) + + def testDelete(self): + h = clownfish.Hash() + h.store("foo", "bar") + got = h.delete("foo") + self.assertEqual(h.get_size(), 0) + self.assertEqual(got, "bar") + + def testClear(self): + h = clownfish.Hash() + h.store("foo", 1) + h.clear() + self.assertEqual(h.get_size(), 0) + + def testHasKey(self): + h = clownfish.Hash() + h.store("foo", 1) + h.store("nada", None) + self.assertTrue(h.has_key("foo")) + self.assertFalse(h.has_key("bar")) + self.assertTrue(h.has_key("nada")) + + def testKeys(self): + h = clownfish.Hash() + h.store("a", 1) + h.store("b", 1) + keys = sorted(h.keys()) + self.assertEqual(keys, ["a", "b"]) + + def testValues(self): + h = clownfish.Hash() + h.store("foo", "a") + h.store("bar", "b") + got = sorted(h.values()) + self.assertEqual(got, ["a", "b"]) + + def testGetCapacity(self): + h = clownfish.Hash(capacity=1) + self.assertGreater(h.get_capacity(), 0) + + def testGetSize(self): + h = clownfish.Hash() + self.assertEqual(h.get_size(), 0) + h.store("meep", "moop") + self.assertEqual(h.get_size(), 1) + + def testEquals(self): + h = clownfish.Hash() + other = clownfish.Hash() + h.store("a", "foo") + other.store("a", "foo") + self.assertTrue(h.equals(other)) + other.store("b", "bar") + self.assertFalse(h.equals(other)) + self.assertTrue(h.equals({"a":"foo"}), + "equals() true against a Python dict") + vec = clownfish.Vector() + self.assertFalse(h.equals(vec), + "equals() false against conflicting Clownfish type") + self.assertFalse(h.equals(1), + "equals() false against conflicting Python type") + + def testIterator(self): + h = clownfish.Hash() + h.store("a", "foo") + i = clownfish.HashIterator(h) + self.assertTrue(i.next()) + self.assertEqual(i.get_key(), "a") + self.assertEqual(i.get_value(), "foo") + self.assertFalse(i.next()) + +if __name__ == '__main__': + unittest.main() +