text: /* 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 "charmony.h"
  
  #include <string.h>
  #include <stdio.h>
  #include <stdlib.h>
  
  #define CFC_NEED_BASE_STRUCT_DEF
  #include "CFCBase.h"
  #include "CFCPython.h"
  #include "CFCPyClass.h"
  #include "CFCPyMethod.h"
  #include "CFCParcel.h"
  #include "CFCClass.h"
  #include "CFCMethod.h"
  #include "CFCHierarchy.h"
  #include "CFCUtil.h"
  #include "CFCBindCore.h"
  
  struct CFCPython {
      CFCBase base;
      CFCHierarchy *hierarchy;
      char *header;
      char *footer;
  };
  
  static void
  S_destroy(CFCPython *self);
  
  static const CFCMeta CFCPYTHON_META = {
      "Clownfish::CFC::Binding::Python",
      sizeof(CFCPython),
      (CFCBase_destroy_t)S_destroy
  };
  
  CFCPython*
  CFCPython_new(CFCHierarchy *hierarchy) {
      CFCUTIL_NULL_CHECK(hierarchy);
      CFCPython *self = (CFCPython*)CFCBase_allocate(&CFCPYTHON_META);
      self->hierarchy  = (CFCHierarchy*)CFCBase_incref((CFCBase*)hierarchy);
      self->header     = CFCUtil_strdup("");
      self->footer     = CFCUtil_strdup("");
      return self;
  }
  
  static void
  S_destroy(CFCPython *self) {
      CFCBase_decref((CFCBase*)self->hierarchy);
      FREEMEM(self->header);
      FREEMEM(self->footer);
      CFCBase_destroy((CFCBase*)self);
  }
  
  void
  CFCPython_set_header(CFCPython *self, const char *header) {
      CFCUTIL_NULL_CHECK(header);
      free(self->header);
      self->header = CFCUtil_make_c_comment(header);
  }
  
  void
  CFCPython_set_footer(CFCPython *self, const char *footer) {
      CFCUTIL_NULL_CHECK(footer);
      free(self->footer);
      self->footer = CFCUtil_make_c_comment(footer);
  }
  
  static void
  S_write_hostdefs(CFCPython *self) {
      const char pattern[] =
          "%s\n"
          "\n"
          "#ifndef H_CFISH_HOSTDEFS\n"
          "#define H_CFISH_HOSTDEFS 1\n"
          "\n"
          "#include \"Python.h\"\n"
          "\n"
          "#define CFISH_OBJ_HEAD \\\n"
          "    PyObject_HEAD\n"
          "\n"
          "#endif /* H_CFISH_HOSTDEFS */\n"
          "\n"
          "%s\n";
      char *content
          = CFCUtil_sprintf(pattern, self->header, self->footer);
  
      // Write if the content has changed.
      const char *inc_dest = CFCHierarchy_get_include_dest(self->hierarchy);
      char *filepath = CFCUtil_sprintf("%s" CHY_DIR_SEP "cfish_hostdefs.h",
                                       inc_dest);
      CFCUtil_write_if_changed(filepath, content, strlen(content));
  
      FREEMEM(filepath);
      FREEMEM(content);
  }
  
  static char*
  S_gen_callbacks(CFCPython *self, CFCParcel *parcel, CFCClass **ordered) {
      CHY_UNUSED_VAR(self);
      CHY_UNUSED_VAR(parcel);
      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_inert(klass)) {
              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 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"
          "#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"
          "              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[] =
          "%s\n"
          "\n"
          "%s"
          ;
      char *content = CFCUtil_sprintf(pattern, helpers, callbacks);
  
      FREEMEM(callbacks);
      return content;
  }
  
  static char*
  S_gen_type_linkups(CFCPython *self, CFCParcel *parcel, CFCClass **ordered) {
      CHY_UNUSED_VAR(self);
      CHY_UNUSED_VAR(parcel);
      char *handles  = CFCUtil_strdup("");
      char *py_types = CFCUtil_strdup("");
      int num_items = 0;
  
      for (size_t i = 0; ordered[i] != NULL; i++) {
          CFCClass *klass = ordered[i];
          if (CFCClass_inert(klass)) {
              continue;
          }
          const char *class_var = CFCClass_full_class_var(klass);
          const char *struct_sym = CFCClass_get_struct_sym(klass);
          char *handles_temp
              = CFCUtil_sprintf("%s    handles[%d]  = &%s;\n",
                                handles, num_items, class_var);
          char *py_types_temp
              = CFCUtil_sprintf("%s    py_types[%d] = &%s_pytype_struct;\n",
                                py_types, num_items, struct_sym);
          FREEMEM(handles);
          FREEMEM(py_types);
          handles  = handles_temp;
          py_types = py_types_temp;
          num_items++;
      }
  
      char pattern[] =
          "static void\n"
          "S_link_py_types(void) {\n"
          "    const int num_items = %d;\n"
          "    size_t handles_size  = num_items * sizeof(cfish_Class**);\n"
          "    size_t py_types_size = num_items * sizeof(PyTypeObject*);\n"
          "    cfish_Class ***handles  = (cfish_Class***)CFISH_MALLOCATE(handles_size);\n"
          "    PyTypeObject **py_types = (PyTypeObject**)CFISH_MALLOCATE(py_types_size);\n"
          "%s\n"
          "%s\n"
          "    CFBind_assoc_py_types(handles, py_types, num_items);\n"
          "    CFISH_FREEMEM(handles);\n"
          "    CFISH_FREEMEM(py_types);\n"
          "}\n"
          ;
      char *content = CFCUtil_sprintf(pattern, num_items, handles, py_types);
  
      FREEMEM(handles);
      FREEMEM(py_types);
      return content;
  }
  
  static char*
  S_gen_class_bindings(CFCPython *self, CFCParcel *parcel,
                       const char *pymod_name, CFCClass **ordered) {
      CHY_UNUSED_VAR(self);
      CHY_UNUSED_VAR(parcel);
      CHY_UNUSED_VAR(pymod_name);
      char *bindings = CFCUtil_strdup("");
      for (size_t i = 0; ordered[i] != NULL; i++) {
          CFCClass *klass = ordered[i];
          const char *class_name = CFCClass_get_name(klass);
          CFCPyClass *class_binding = CFCPyClass_singleton(class_name);
          if (!class_binding) {
              // No binding spec'd out, so create one using defaults.
              class_binding = CFCPyClass_new(klass);
              CFCPyClass_add_to_registry(class_binding);
          }
  
          char *code = CFCPyClass_gen_binding_code(class_binding);
          bindings = CFCUtil_cat(bindings, code, NULL);
          FREEMEM(code);
      }
      return bindings;
  }
  
  static void
  S_write_module_file(CFCPython *self, CFCParcel *parcel, const char *dest) {
      const char *parcel_name = CFCParcel_get_name(parcel);
      char *pymod_name = CFCUtil_strdup(parcel_name);
      // TODO: Stop lowercasing when parcels are restricted to lowercase.
      for (int i = 0; pymod_name[i] != '\0'; i++) {
          pymod_name[i] = CFCUtil_tolower(pymod_name[i]);
      }
      const char *last_dot = strrchr(pymod_name, '.');
      const char *last_component = last_dot != NULL
                                   ? last_dot + 1
                                   : pymod_name;
      char *helper_mod_name = CFCUtil_sprintf("%s._%s", pymod_name, last_component);
      for (int i = 0; helper_mod_name[i] != '\0'; i++) {
          helper_mod_name[i] = CFCUtil_tolower(helper_mod_name[i]);
      }
  
      CFCClass  **ordered = CFCParcel_get_classes(parcel);
      char *privacy_syms       = CFCUtil_strdup("");
      char *callbacks          = S_gen_callbacks(self, parcel, ordered);
      char *type_linkups       = S_gen_type_linkups(self, parcel, ordered);
      char *pound_includes     = CFCUtil_strdup("");
      char *class_bindings     = S_gen_class_bindings(self, parcel, pymod_name, ordered);
      char *parcel_boots       = CFCUtil_strdup("");
      char *pytype_ready_calls = CFCUtil_strdup("");
      char *module_adds        = CFCUtil_strdup("");
  
      // Add privacy defines and parcel bootstrapping calls.
      const char *privacy_sym = CFCParcel_get_privacy_sym(parcel);
      privacy_syms = CFCUtil_cat(privacy_syms, "#define ", privacy_sym,
                                 "\n", NULL);
      const char *prefix = CFCParcel_get_prefix(parcel);
      parcel_boots = CFCUtil_cat(parcel_boots, "    ", prefix,
                                 "bootstrap_parcel();\n", NULL);
  
      for (size_t i = 0; ordered[i] != NULL; i++) {
          CFCClass *klass = ordered[i];
          const char *struct_sym = CFCClass_get_struct_sym(klass);
  
          const char *include_h  = CFCClass_include_h(klass);
          pound_includes = CFCUtil_cat(pound_includes, "#include \"",
                                       include_h, "\"\n", NULL);
  
          // The PyType_Ready invocations for instantiable classes are handled
          // via bootstrapping of Clownfish Class objects.  Since inert classes
          // do not at present have Class objects, we need to handle their
          // PyType_Ready calls independently.
          if (CFCClass_inert(klass)) {
              pytype_ready_calls = CFCUtil_cat(pytype_ready_calls,
                  "    if (PyType_Ready(&", struct_sym,
                  "_pytype_struct) < 0) { return NULL; }\n", NULL);
          }
  
          module_adds = CFCUtil_cat(module_adds, "    PyModule_AddObject(module, \"",
                                    struct_sym, "\", (PyObject*)&", struct_sym,
                                    "_pytype_struct);\n", NULL);
      }
  
      const char pattern[] =
          "%s\n"
          "\n"
          "%s"
          "\n"
          "#include \"Python.h\"\n"
          "#include \"cfish_parcel.h\"\n"
          "#include \"CFBind.h\"\n"
          "%s\n"
          "\n"
          "%s\n" // callbacks
          "\n"
          "static PyModuleDef module_def = {\n"
          "    PyModuleDef_HEAD_INIT,\n"
          "    \"%s\",\n" // module name
          "    NULL,\n" // docstring
          "    -1,\n"
          "    NULL, NULL, NULL, NULL, NULL\n"
          "};\n"
          "\n"
          "%s" // class bindings
          "\n"
          "%s" // S_link_py_types function
          "\n"
          "PyMODINIT_FUNC\n"
          "PyInit__%s(void) {\n"
          "    cfish_Class_bootstrap_hook1 = CFBind_class_bootstrap_hook1;\n"
          "\n"
          "%s\n" // PyType_Ready calls
          "\n"
          "    S_link_py_types();\n"
          "\n"
          "%s\n" // parcel boots
          "\n"
          "    PyObject *module = PyModule_Create(&module_def);\n"
          "%s\n" // Add types to module
          "\n"
          "    return module;\n"
          "}\n"
          "\n"
          "%s\n"
          "\n";
  
      char *content
          = CFCUtil_sprintf(pattern, self->header, privacy_syms, pound_includes,
                            callbacks, helper_mod_name, class_bindings,
                            type_linkups, last_component, pytype_ready_calls,
                            parcel_boots, module_adds, self->footer);
  
      char *filepath = CFCUtil_sprintf("%s" CHY_DIR_SEP "_%s.c", dest,
                                       last_component);
      CFCUtil_write_if_changed(filepath, content, strlen(content));
      FREEMEM(filepath);
  
      FREEMEM(content);
      FREEMEM(module_adds);
      FREEMEM(pytype_ready_calls);
      FREEMEM(parcel_boots);
      FREEMEM(class_bindings);
      FREEMEM(helper_mod_name);
      FREEMEM(pymod_name);
      FREEMEM(pound_includes);
      FREEMEM(type_linkups);
      FREEMEM(callbacks);
      FREEMEM(privacy_syms);
  }
  
  void
  CFCPython_write_bindings(CFCPython *self, const char *parcel_name, const char *dest) {
      CFCParcel *parcel = CFCParcel_fetch(parcel_name);
      if (parcel == NULL) {
          CFCUtil_die("Unknown parcel: %s", parcel_name);
      }
      S_write_hostdefs(self);
      S_write_module_file(self, parcel, dest);
  }
  
  