Write host aliases and exclusions to parcel_perl.json

Write host-specific data like method aliases and exclusions to a JSON
file for each parcel. This file is installed in the Clownfish include
directory.
diff --git a/compiler/perl/lib/Clownfish/CFC.xs b/compiler/perl/lib/Clownfish/CFC.xs
index 74bce9d..3fda1c7 100644
--- a/compiler/perl/lib/Clownfish/CFC.xs
+++ b/compiler/perl/lib/Clownfish/CFC.xs
@@ -1884,6 +1884,13 @@
 PPCODE:
     CFCBindCore_copy_headers(self, dest_dir);
 
+void
+write_host_data_json(self, dest_dir)
+    CFCBindCore *self;
+    const char *dest_dir;
+PPCODE:
+    CFCBindCore_write_host_data_json(self, dest_dir, "perl");
+
 
 MODULE = Clownfish::CFC  PACKAGE = Clownfish::CFC::Binding::Core::Function
 
diff --git a/compiler/perl/lib/Clownfish/CFC/Perl/Build.pm b/compiler/perl/lib/Clownfish/CFC/Perl/Build.pm
index f1c351f..ef0e0f3 100644
--- a/compiler/perl/lib/Clownfish/CFC/Perl/Build.pm
+++ b/compiler/perl/lib/Clownfish/CFC/Perl/Build.pm
@@ -288,14 +288,13 @@
         footer    => '',
     );
     print "Writing Clownfish autogenerated files...\n";
+    my $inc_dir = catdir( $self->blib, 'arch', 'Clownfish', '_include' );
     my $cfh_modified = $core_binding->write_all_modified;
     if ($cfh_modified) {
         unlink('typemap');
         print "Writing typemap...\n";
         $self->add_to_cleanup('typemap');
         $perl_binding->write_xs_typemap;
-
-        my $inc_dir = catdir( $self->blib, 'arch', 'Clownfish', '_include' );
         $core_binding->copy_headers($inc_dir);
     }
 
@@ -316,6 +315,8 @@
             );
         }
 
+        $core_binding->write_host_data_json($inc_dir);
+
         print "Writing POD...\n";
         my $pod_files = $perl_binding->write_pod;
         $self->add_to_cleanup($_) for @$pod_files;
diff --git a/compiler/src/CFCBindClass.c b/compiler/src/CFCBindClass.c
index ac8b8fd..75b77e9 100644
--- a/compiler/src/CFCBindClass.c
+++ b/compiler/src/CFCBindClass.c
@@ -642,3 +642,41 @@
     return short_names;
 }
 
+char*
+CFCBindClass_host_data_json(CFCBindClass *self) {
+    if (CFCClass_final(self->client)) { return CFCUtil_strdup(""); }
+
+    CFCMethod **fresh_methods = CFCClass_fresh_methods(self->client);
+    char *methods_json = CFCUtil_strdup("");
+
+    for (int i = 0; fresh_methods[i] != NULL; i++) {
+        CFCMethod *method = fresh_methods[i];
+        char *method_json = CFCBindMeth_host_data_json(method);
+        if (method_json[0] != '\0') {
+            const char *sep = methods_json[0] == '\0' ? "" : ",\n";
+            methods_json = CFCUtil_cat(methods_json, sep, method_json, NULL);
+        }
+        FREEMEM(method_json);
+    }
+
+    char *json;
+
+    if (methods_json[0] == '\0') {
+        json = CFCUtil_strdup("");
+    }
+    else {
+        const char *class_name = CFCClass_get_name(self->client);
+
+        const char *pattern =
+            "        \"%s\": {\n"
+            "            \"methods\": {\n"
+            "%s\n"
+            "            }\n"
+            "        }";
+        json = CFCUtil_sprintf(pattern, class_name, methods_json);
+    }
+
+    FREEMEM(methods_json);
+    return json;
+}
+
diff --git a/compiler/src/CFCBindClass.h b/compiler/src/CFCBindClass.h
index 4780bdc..450e0aa 100644
--- a/compiler/src/CFCBindClass.h
+++ b/compiler/src/CFCBindClass.h
@@ -54,6 +54,11 @@
 char*
 CFCBindClass_to_c_data(CFCBindClass *self);
 
+/** Return host-specific data for the class as JSON fragment.
+ */
+char*
+CFCBindClass_host_data_json(CFCBindClass *self);
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/compiler/src/CFCBindCore.c b/compiler/src/CFCBindCore.c
index c835cc9..ecdc091 100644
--- a/compiler/src/CFCBindCore.c
+++ b/compiler/src/CFCBindCore.c
@@ -74,6 +74,10 @@
 static char*
 S_charmony_alloca_defines();
 
+static void
+S_write_host_data_json(CFCBindCore *self, CFCParcel *parcel,
+                       const char *dest_dir, const char *host_lang);
+
 static const CFCMeta CFCBINDCORE_META = {
     "Clownfish::CFC::Binding::Core",
     sizeof(CFCBindCore),
@@ -759,4 +763,67 @@
     FREEMEM(default_dest);
 }
 
+void
+CFCBindCore_write_host_data_json(CFCBindCore *self, const char *dest_dir,
+                                 const char *host_lang) {
+    CFCParcel **parcels = CFCParcel_all_parcels();
+
+    for (size_t i = 0; parcels[i] != NULL; i++) {
+        CFCParcel *parcel = parcels[i];
+        if (!CFCParcel_included(parcel) && CFCParcel_is_installed(parcel)) {
+            S_write_host_data_json(self, parcel, dest_dir, host_lang);
+        }
+    }
+}
+
+static void
+S_write_host_data_json(CFCBindCore *self, CFCParcel *parcel,
+                       const char *dest_dir, const char *host_lang) {
+    const char *prefix      = CFCParcel_get_prefix(parcel);
+    const char *parcel_name = CFCParcel_get_name(parcel);
+    CFCVersion *version     = CFCParcel_get_version(parcel);
+    const char *vstring     = CFCVersion_get_vstring(version);
+
+    char *classes_json = CFCUtil_strdup("");
+    CFCClass **ordered = CFCHierarchy_ordered_classes(self->hierarchy);
+
+    for (size_t i = 0; ordered[i] != NULL; i++) {
+        CFCClass *klass = ordered[i];
+        const char *class_prefix = CFCClass_get_prefix(klass);
+        if (strcmp(class_prefix, prefix) != 0) { continue; }
+
+        CFCBindClass *class_binding = CFCBindClass_new(klass);
+
+        char *class_json = CFCBindClass_host_data_json(class_binding);
+        if (class_json[0] != '\0') {
+            const char *sep = classes_json[0] == '\0' ? "" : ",\n";
+            classes_json = CFCUtil_cat(classes_json, sep, class_json, NULL);
+        }
+
+        FREEMEM(class_json);
+        CFCBase_decref((CFCBase*)class_binding);
+    }
+    FREEMEM(ordered);
+
+    char *filepath = CFCUtil_sprintf("%s" CHY_DIR_SEP "%s" CHY_DIR_SEP "%s"
+                                     CHY_DIR_SEP "parcel_%s.json", dest_dir,
+                                     parcel_name, vstring, host_lang);
+    remove(filepath);
+
+    if (classes_json[0] != '\0') {
+        const char *pattern =
+            "{\n"
+            "    \"classes\": {\n"
+            "%s\n"
+            "    }\n"
+            "}\n";
+        char *json = CFCUtil_sprintf(pattern, classes_json);
+        CFCUtil_write_file(filepath, json, strlen(json));
+        FREEMEM(json);
+    }
+
+    FREEMEM(filepath);
+    FREEMEM(classes_json);
+}
+
 
diff --git a/compiler/src/CFCBindCore.h b/compiler/src/CFCBindCore.h
index eff3efe..aa40bb2 100644
--- a/compiler/src/CFCBindCore.h
+++ b/compiler/src/CFCBindCore.h
@@ -66,6 +66,12 @@
 void
 CFCBindCore_copy_headers(CFCBindCore *self, const char *dest_dir);
 
+/* Write host-specific data to a JSON file for each source parcel.
+ */
+void
+CFCBindCore_write_host_data_json(CFCBindCore *self, const char *dest_dir,
+                                 const char *host_lang);
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/compiler/src/CFCBindMethod.c b/compiler/src/CFCBindMethod.c
index 400d554..d57a29e 100644
--- a/compiler/src/CFCBindMethod.c
+++ b/compiler/src/CFCBindMethod.c
@@ -228,3 +228,37 @@
     return buf;
 }
 
+char*
+CFCBindMeth_host_data_json(CFCMethod *method) {
+    if (!CFCMethod_novel(method)) { return CFCUtil_strdup(""); }
+
+    int         excluded = CFCMethod_excluded_from_host(method);
+    const char *alias    = CFCMethod_get_host_alias(method);
+    char       *pair     = NULL;
+    char       *json     = NULL;
+
+    if (excluded) {
+        pair = CFCUtil_strdup("\"excluded\": true");
+    }
+    else if (alias) {
+        pair = CFCUtil_sprintf("\"alias\": \"%s\"", alias);
+    }
+
+    if (pair) {
+        const char *method_name = CFCMethod_get_name(method);
+
+        const char *pattern =
+            "                \"%s\": {\n"
+            "                    %s\n"
+            "                }";
+        json = CFCUtil_sprintf(pattern, method_name, pair);
+
+        FREEMEM(pair);
+    }
+    else {
+        json = CFCUtil_strdup("");
+    }
+
+    return json;
+}
+
diff --git a/compiler/src/CFCBindMethod.h b/compiler/src/CFCBindMethod.h
index 4928c70..f1b0bd4 100644
--- a/compiler/src/CFCBindMethod.h
+++ b/compiler/src/CFCBindMethod.h
@@ -57,6 +57,12 @@
 char*
 CFCBindMeth_imp_declaration(struct CFCMethod *method, struct CFCClass *klass);
 
+/** Return a JSON fragment for method data specified by the host bindings
+ * (alias or excluded).
+ */
+char*
+CFCBindMeth_host_data_json(struct CFCMethod *method);
+
 #ifdef __cplusplus
 }
 #endif