Beispiel #1
0
void
CFCClass_add_child(CFCClass *self, CFCClass *child) {
    CFCUTIL_NULL_CHECK(child);
    if (self->tree_grown) {
        CFCUtil_die("Can't call add_child after grow_tree");
    }
    if (self->is_inert) {
        CFCUtil_die("Can't inherit from inert class %s",
                    CFCClass_get_class_name(self));
    }
    if (child->is_inert) {
        CFCUtil_die("Inert class %s can't inherit",
                    CFCClass_get_class_name(child));
    }
    self->num_kids++;
    size_t size = (self->num_kids + 1) * sizeof(CFCClass*);
    self->children = (CFCClass**)REALLOCATE(self->children, size);
    self->children[self->num_kids - 1]
        = (CFCClass*)CFCBase_incref((CFCBase*)child);
    self->children[self->num_kids] = NULL;

    // Add parcel dependency.
    CFCParcel *parcel       = CFCClass_get_parcel(self);
    CFCParcel *child_parcel = CFCClass_get_parcel(child);
    CFCParcel_add_inherited_parcel(child_parcel, parcel);
}
Beispiel #2
0
CFCPerlConstructor*
CFCPerlConstructor_init(CFCPerlConstructor *self, CFCClass *klass,
                        const char *alias, const char *initializer) {
    CFCUTIL_NULL_CHECK(alias);
    CFCUTIL_NULL_CHECK(klass);
    const char *class_name = CFCClass_get_class_name(klass);
    initializer = initializer ? initializer : "init";

    // Find the implementing function.
    self->init_func = NULL;
    CFCFunction **funcs = CFCClass_functions(klass);
    for (size_t i = 0; funcs[i] != NULL; i++) {
        CFCFunction *func = funcs[i];
        const char *func_name = CFCFunction_micro_sym(func);
        if (strcmp(initializer, func_name) == 0) {
            self->init_func = (CFCFunction*)CFCBase_incref((CFCBase*)func);
            break;
        }
    }
    if (!self->init_func) {
        CFCUtil_die("Missing or invalid '%s' function for '%s'",
                    initializer, class_name);
    }
    CFCParamList *param_list = CFCFunction_get_param_list(self->init_func);
    CFCPerlSub_init((CFCPerlSub*)self, param_list, class_name, alias,
                    true);
    return self;
}
Beispiel #3
0
char*
CFCPerlPod_methods_pod(CFCPerlPod *self, CFCClass *klass) {
    const char *class_name = CFCClass_get_class_name(klass);
    char *abstract_pod = CFCUtil_strdup("");
    char *methods_pod  = CFCUtil_strdup("");
    for (size_t i = 0; i < self->num_methods; i++) {
        NamePod meth_spec = self->methods[i];
        CFCMethod *method = CFCClass_method(klass, meth_spec.func);
        if (!method) {
            method = CFCClass_method(klass, meth_spec.alias);
        }
        if (!method) {
            CFCUtil_die("Can't find method '%s' in class '%s'",
                        meth_spec.alias, CFCClass_get_class_name(klass));
        }
        char *meth_pod;
        if (meth_spec.pod) {
            meth_pod = CFCUtil_sprintf("%s\n", meth_spec.pod);
        }
        else {
            meth_pod
                = CFCPerlPod_gen_subroutine_pod(self, (CFCFunction*)method,
                                                meth_spec.alias, klass,
                                                meth_spec.sample, class_name,
                                                false);
        }
        if (CFCMethod_abstract(method)) {
            abstract_pod = CFCUtil_cat(abstract_pod, meth_pod, NULL);
        }
        else {
            methods_pod = CFCUtil_cat(methods_pod, meth_pod, NULL);
        }
        FREEMEM(meth_pod);
    }

    char *pod = CFCUtil_strdup("");
    if (strlen(abstract_pod)) {
        pod = CFCUtil_cat(pod, "=head1 ABSTRACT METHODS\n\n", abstract_pod, NULL);
    }
    FREEMEM(abstract_pod);
    if (strlen(methods_pod)) {
        pod = CFCUtil_cat(pod, "=head1 METHODS\n\n", methods_pod, NULL);
    }
    FREEMEM(methods_pod);

    return pod;
}
Beispiel #4
0
static void
S_register(CFCClass *self) {
    if (registry_size == registry_cap) {
        size_t new_cap = registry_cap + 10;
        registry = (CFCClassRegEntry*)REALLOCATE(
                       registry,
                       (new_cap + 1) * sizeof(CFCClassRegEntry));
        for (size_t i = registry_cap; i <= new_cap; i++) {
            registry[i].key = NULL;
            registry[i].klass = NULL;
        }
        registry_cap = new_cap;
    }

    CFCParcel  *parcel     = CFCClass_get_parcel(self);
    const char *prefix     = CFCParcel_get_prefix(parcel);
    const char *class_name = CFCClass_get_class_name(self);
    const char *cnick      = CFCClass_get_cnick(self);
    const char *key        = self->full_struct_sym;

    for (size_t i = 0; i < registry_size; i++) {
        CFCClass   *other            = registry[i].klass;
        CFCParcel  *other_parcel     = CFCClass_get_parcel(other);
        const char *other_prefix     = CFCParcel_get_prefix(other_parcel);
        const char *other_class_name = CFCClass_get_class_name(other);
        const char *other_cnick      = CFCClass_get_cnick(other);

        if (strcmp(class_name, other_class_name) == 0) {
            CFCUtil_die("Two classes with name %s", class_name);
        }
        if (strcmp(registry[i].key, key) == 0) {
            CFCUtil_die("Class name conflict between %s and %s",
                        class_name, other_class_name);
        }
        if (strcmp(prefix, other_prefix) == 0
            && strcmp(cnick, other_cnick) == 0
           ) {
            CFCUtil_die("Class nickname conflict between %s and %s",
                        class_name, other_class_name);
        }
    }

    registry[registry_size].key   = CFCUtil_strdup(key);
    registry[registry_size].klass = (CFCClass*)CFCBase_incref((CFCBase*)self);
    registry_size++;
}
Beispiel #5
0
CFCMethod*
CFCClass_fresh_method(CFCClass *self, const char *sym) {
    CFCMethod *method = CFCClass_method(self, sym);
    if (method) {
        const char *class_name = CFCClass_get_class_name(self);
        const char *meth_class_name = CFCMethod_get_class_name(method);
        if (strcmp(class_name, meth_class_name) == 0) {
            return method;
        }
    }
    return NULL;
}
Beispiel #6
0
static CFCMethod*
S_make_method_obj(CFCClass *klass, const char *method_name) {
    const char *klass_full_struct_sym = CFCClass_full_struct_sym(klass);
    const char *klass_name   = CFCClass_get_class_name(klass);
    const char *klass_cnick  = CFCClass_get_cnick(klass);
    CFCParcel  *klass_parcel = CFCClass_get_parcel(klass);

    CFCType *return_type = CFCType_new_object(CFCTYPE_INCREMENTED,
                                              klass_parcel, "cfish_Obj", 1);
    CFCType *self_type = CFCType_new_object(0, klass_parcel,
                                            klass_full_struct_sym, 1);
    CFCVariable *self_var = CFCVariable_new(NULL, NULL, NULL, NULL, "self",
                                            self_type, false);
    CFCParamList *param_list = NULL;

    if (strcmp(method_name, "Dump") == 0) {
        param_list = CFCParamList_new(false);
        CFCParamList_add_param(param_list, self_var, NULL);
    }
    else if (strcmp(method_name, "Load") == 0) {
        CFCType *dump_type = CFCType_new_object(0, klass_parcel, "cfish_Obj",
                                                1);
        CFCVariable *dump_var = CFCVariable_new(NULL, NULL, NULL, NULL, "dump",
                                                dump_type, false);
        param_list = CFCParamList_new(false);
        CFCParamList_add_param(param_list, self_var, NULL);
        CFCParamList_add_param(param_list, dump_var, NULL);
        CFCBase_decref((CFCBase*)dump_var);
        CFCBase_decref((CFCBase*)dump_type);
    }
    else {
        CFCUtil_die("Unexpected method_name: '%s'", method_name);
    }

    CFCMethod *method = CFCMethod_new(klass_parcel, "public", klass_name,
                                      klass_cnick, method_name, return_type,
                                      param_list, NULL, false, false);

    CFCBase_decref((CFCBase*)param_list);
    CFCBase_decref((CFCBase*)self_type);
    CFCBase_decref((CFCBase*)self_var);
    CFCBase_decref((CFCBase*)return_type);

    return method;
}
Beispiel #7
0
char*
CFCPerlPod_constructors_pod(CFCPerlPod *self, CFCClass *klass) {
    if (!self->num_constructors) {
        return CFCUtil_strdup("");
    }
    const char *class_name = CFCClass_get_class_name(klass);
    char *pod = CFCUtil_strdup("=head1 CONSTRUCTORS\n\n");
    for (size_t i = 0; i < self->num_constructors; i++) {
        NamePod slot = self->constructors[i];
        if (slot.pod) {
            pod = CFCUtil_cat(pod, slot.pod, "\n", NULL);
        }
        else {
            CFCFunction *init_func = CFCClass_function(klass, slot.func);
            char *sub_pod
                = CFCPerlPod_gen_subroutine_pod(self, init_func, slot.alias, klass,
                                                slot.sample, class_name, true);
            pod = CFCUtil_cat(pod, sub_pod, NULL);
            FREEMEM(sub_pod);
        }
    }
    return pod;
}
Beispiel #8
0
void
CFCDumpable_add_dumpables(CFCDumpable *self, CFCClass *klass) {
    (void)self;

    if (!CFCClass_has_attribute(klass, "dumpable")) {
        CFCUtil_die("Class %s isn't dumpable", CFCClass_get_class_name(klass));
    }

    // Inherit Dump/Load from parent if no fresh member vars.
    CFCClass *parent = CFCClass_get_parent(klass);
    if (parent && CFCClass_has_attribute(parent, "dumpable")) {
        CFCVariable **fresh = CFCClass_fresh_member_vars(klass);
        int needs_autogenerated_dumpables = fresh[0] != NULL ? true : false;
        FREEMEM(fresh);
        if (!needs_autogenerated_dumpables) { return; }
    }

    if (!CFCClass_fresh_method(klass, "Dump")) {
        S_add_dump_method(klass);
    }
    if (!CFCClass_fresh_method(klass, "Load")) {
        S_add_load_method(klass);
    }
}
char*
CFCPerlClass_create_pod(CFCPerlClass *self) {
    CFCPerlPod *pod_spec   = self->pod_spec;
    const char *class_name = self->class_name;
    CFCClass   *client     = self->client;
    if (!pod_spec) {
        return NULL;
    }
    if (!client) {
        CFCUtil_die("No client for %s", class_name);
    }
    CFCDocuComment *docucom = CFCClass_get_docucomment(client);
    if (!docucom) {
        CFCUtil_die("No DocuComment for %s", class_name);
    }

    // Get the class's brief description.
    const char *raw_brief = CFCDocuComment_get_brief(docucom);
    char *brief = CFCPerlPod_md_to_pod(pod_spec, client, raw_brief);

    // Get the class's long description.
    char *description;
    const char *pod_description = CFCPerlPod_get_description(pod_spec);
    if (pod_description && strlen(pod_description)) {
        description = CFCUtil_sprintf("%s\n", pod_description);
    }
    else {
        const char *raw_description = CFCDocuComment_get_long(docucom);
        description = CFCPerlPod_md_to_pod(pod_spec, client, raw_description);
    }

    // Create SYNOPSIS.
    const char *raw_synopsis = CFCPerlPod_get_synopsis(pod_spec);
    char *synopsis = CFCUtil_strdup("");
    if (raw_synopsis && strlen(raw_synopsis)) {
        synopsis = CFCUtil_cat(synopsis, "=head1 SYNOPSIS\n\n", raw_synopsis,
                               "\n", NULL);
    }

    // Create CONSTRUCTORS.
    char *constructor_pod = CFCPerlPod_constructors_pod(pod_spec, client);

    // Create METHODS, possibly including an ABSTRACT METHODS section.
    char *methods_pod = CFCPerlPod_methods_pod(pod_spec, client);

    // Build an INHERITANCE section describing class ancestry.
    char *inheritance = CFCUtil_strdup("");
    if (CFCClass_get_parent(client)) {
        inheritance = CFCUtil_cat(inheritance, "=head1 INHERITANCE\n\n",
                                  class_name, NULL);
        CFCClass *ancestor = client;
        while (NULL != (ancestor = CFCClass_get_parent(ancestor))) {
            const char *ancestor_klass = CFCClass_get_class_name(ancestor);
            if (CFCPerlClass_singleton(ancestor_klass)) {
                inheritance = CFCUtil_cat(inheritance, " isa L<",
                                          ancestor_klass, ">", NULL);
            }
            else {
                inheritance = CFCUtil_cat(inheritance, " isa ",
                                          ancestor_klass, NULL);
            }
        }
        inheritance = CFCUtil_cat(inheritance, ".\n\n", NULL);
    }

    // Put it all together.
    const char pattern[] =
        "=head1 NAME\n"
        "\n"
        "%s - %s"
        "%s"
        "=head1 DESCRIPTION\n"
        "\n"
        "%s"
        "%s"
        "%s"
        "%s"
        "=cut\n"
        "\n";
    char *pod
        = CFCUtil_sprintf(pattern, class_name, brief, synopsis, description,
                          constructor_pod, methods_pod, inheritance);

    FREEMEM(brief);
    FREEMEM(synopsis);
    FREEMEM(description);
    FREEMEM(constructor_pod);
    FREEMEM(methods_pod);
    FREEMEM(inheritance);

    return pod;
}
Beispiel #10
0
static void
S_write_boot_c(CFCPerl *self) {
    CFCClass  **ordered   = CFCHierarchy_ordered_classes(self->hierarchy);
    CFCParcel **parcels   = CFCParcel_all_parcels();
    char *pound_includes  = CFCUtil_strdup("");
    char *bootstrap_code  = CFCUtil_strdup("");
    char *alias_adds      = CFCUtil_strdup("");
    char *isa_pushes      = CFCUtil_strdup("");

    for (size_t i = 0; parcels[i]; ++i) {
        if (!CFCParcel_included(parcels[i])) {
            const char *prefix = CFCParcel_get_prefix(parcels[i]);
            bootstrap_code = CFCUtil_cat(bootstrap_code, "    ", prefix,
                                         "bootstrap_parcel();\n", NULL);
        }
    }

    for (size_t i = 0; ordered[i] != NULL; i++) {
        CFCClass *klass = ordered[i];
        if (CFCClass_included(klass)) { continue; }

        const char *class_name = CFCClass_get_class_name(klass);
        const char *include_h  = CFCClass_include_h(klass);
        pound_includes = CFCUtil_cat(pound_includes, "#include \"",
                                     include_h, "\"\n", NULL);

        if (CFCClass_inert(klass)) { continue; }

        // Add aliases for selected KinoSearch classes which allow old indexes
        // to be read.
        CFCPerlClass *class_binding = CFCPerlClass_singleton(class_name);
        if (class_binding) {
            const char *vtable_var = CFCClass_full_vtable_var(klass);
            const char **aliases
                = CFCPerlClass_get_class_aliases(class_binding);
            for (size_t j = 0; aliases[j] != NULL; j++) {
                const char *alias = aliases[j];
                size_t alias_len  = strlen(alias);
                const char pattern[] =
                    "    cfish_VTable_add_alias_to_registry("
                    "%s, \"%s\", %u);\n";
                char *alias_add
                    = CFCUtil_sprintf(pattern, vtable_var, alias,
                                      (unsigned)alias_len);
                alias_adds = CFCUtil_cat(alias_adds, alias_add, NULL);
                FREEMEM(alias_add);
            }

            char *metadata_code
                = CFCPerlClass_method_metadata_code(class_binding);
            alias_adds = CFCUtil_cat(alias_adds, metadata_code, NULL);
            FREEMEM(metadata_code);
        }

        CFCClass *parent = CFCClass_get_parent(klass);
        if (parent) {
            const char *parent_class_name = CFCClass_get_class_name(parent);
            isa_pushes
                = CFCUtil_cat(isa_pushes, "    isa = get_av(\"",
                              class_name, "::ISA\", 1);\n", NULL);
            isa_pushes
                = CFCUtil_cat(isa_pushes, "    av_push(isa, newSVpv(\"", 
                              parent_class_name, "\", 0));\n", NULL);
        }
    }

    const char pattern[] =
        "%s\n"
        "\n"
        "#include \"cfish_parcel.h\"\n"
        "#include \"EXTERN.h\"\n"
        "#include \"perl.h\"\n"
        "#include \"XSUB.h\"\n"
        "#include \"boot.h\"\n"
        "#include \"Clownfish/String.h\"\n"
        "#include \"Clownfish/VTable.h\"\n"
        "%s\n"
        "\n"
        "void\n"
        "%s() {\n"
        "%s"
        "\n"
        "%s"
        "\n"
        "    AV *isa;\n"
        "%s"
        "}\n"
        "\n"
        "%s\n"
        "\n";
    char *content
        = CFCUtil_sprintf(pattern, self->header, pound_includes,
                          self->boot_func, bootstrap_code, alias_adds,
                          isa_pushes, self->footer);

    const char *src_dest = CFCHierarchy_get_source_dest(self->hierarchy);
    char *boot_c_path = CFCUtil_sprintf("%s" CHY_DIR_SEP "boot.c", src_dest);
    CFCUtil_write_file(boot_c_path, content, strlen(content));
    FREEMEM(boot_c_path);

    FREEMEM(content);
    FREEMEM(isa_pushes);
    FREEMEM(alias_adds);
    FREEMEM(bootstrap_code);
    FREEMEM(pound_includes);
    FREEMEM(parcels);
    FREEMEM(ordered);
}
Beispiel #11
0
static char*
S_convert_link(CFCClass *klass, cmark_node *link) {
    cmark_node *child = cmark_node_first_child(link);
    const char *uri   = cmark_node_get_url(link);
    char       *text  = S_nodes_to_pod(klass, child);
    char       *retval;

    if (!CFCUri_is_clownfish_uri(uri)) {
        retval = S_pod_link(text, uri);
        FREEMEM(text);
        return retval;
    }

    char   *new_uri  = NULL;
    char   *new_text = NULL;
    CFCUri *uri_obj  = CFCUri_new(uri, klass);
    int     type     = CFCUri_get_type(uri_obj);

    switch (type) {
        case CFC_URI_NULL:
            // Change all instances of NULL to 'undef'
            new_text = CFCUtil_strdup("undef");
            break;

        case CFC_URI_CLASS: {
            const char *full_struct_sym = CFCUri_full_struct_sym(uri_obj);
            CFCClass *uri_class
                = CFCClass_fetch_by_struct_sym(full_struct_sym);

            if (!uri_class) {
                CFCUtil_warn("URI class not found: %s", full_struct_sym);
            }
            else if (uri_class != klass) {
                const char *class_name = CFCClass_get_class_name(uri_class);
                new_uri = CFCUtil_strdup(class_name);
            }

            if (text[0] != '\0') {
                // Keep text.
                break;
            }

            if (strcmp(CFCUri_get_prefix(uri_obj),
                       CFCClass_get_prefix(klass)) == 0
            ) {
                // Same parcel.
                const char *struct_sym = CFCUri_get_struct_sym(uri_obj);
                new_text = CFCUtil_strdup(struct_sym);
            }
            else {
                // Other parcel.
                if (!uri_class) {
                    new_text = CFCUtil_strdup(full_struct_sym);
                }
                else {
                    const char *class_name
                        = CFCClass_get_class_name(uri_class);
                    new_text = CFCUtil_strdup(class_name);
                }
            }

            break;
        }

        case CFC_URI_FUNCTION:
        case CFC_URI_METHOD: {
            const char *full_struct_sym = CFCUri_full_struct_sym(uri_obj);
            const char *func_sym        = CFCUri_get_func_sym(uri_obj);

            // Convert "Err_get_error" to "Clownfish->error".
            if (strcmp(full_struct_sym, "cfish_Err") == 0
                && strcmp(func_sym, "get_error") == 0
            ) {
                new_text = CFCUtil_strdup("Clownfish->error");
                break;
            }

            CFCClass *uri_class
                = CFCClass_fetch_by_struct_sym(full_struct_sym);

            // TODO: Link to relevant POD section. This isn't easy because
            // the section headers for functions also contain a description
            // of the parameters.

            if (!uri_class) {
                CFCUtil_warn("URI class not found: %s", full_struct_sym);
            }
            else if (uri_class != klass) {
                const char *class_name = CFCClass_get_class_name(uri_class);
                new_uri = CFCUtil_strdup(class_name);
            }

            new_text = CFCUtil_sprintf("%s()", func_sym);
            for (size_t i = 0; new_text[i] != '\0'; ++i) {
                new_text[i] = tolower(new_text[i]);
            }

            break;
        }
    }

    if (new_text) {
        FREEMEM(text);
        text = new_text;
    }

    if (new_uri) {
        retval = S_pod_link(text, new_uri);
        FREEMEM(new_uri);
        FREEMEM(text);
    }
    else {
        retval = text;
    }

    CFCBase_decref((CFCBase*)uri_obj);

    return retval;
}
Beispiel #12
0
char*
CFCC_link_text(CFCUri *uri_obj, CFCClass *klass) {
    char *link_text = NULL;
    int   type      = CFCUri_get_type(uri_obj);

    switch (type) {
        case CFC_URI_CLASS: {
            if (strcmp(CFCUri_get_prefix(uri_obj),
                       CFCClass_get_prefix(klass)) == 0
            ) {
                // Same parcel.
                const char *struct_sym = CFCUri_get_struct_sym(uri_obj);
                link_text = CFCUtil_strdup(struct_sym);
            }
            else {
                // Other parcel.
                const char *full_struct_sym = CFCUri_full_struct_sym(uri_obj);
                CFCClass *uri_class
                    = CFCClass_fetch_by_struct_sym(full_struct_sym);
                if (!uri_class) {
                    CFCUtil_warn("URI class not found: %s", full_struct_sym);
                }
                else {
                    const char *class_name
                        = CFCClass_get_class_name(uri_class);
                    link_text = CFCUtil_strdup(class_name);
                }
            }

            break;
        }

        case CFC_URI_FUNCTION:
        case CFC_URI_METHOD: {
#if 1
            const char *func_sym = CFCUri_get_func_sym(uri_obj);
            link_text = CFCUtil_sprintf("%s()", func_sym);
#else
            // Full function sym.
            const char *full_struct_sym = CFCUri_full_struct_sym(uri_obj);
            const char *func_sym        = CFCUri_get_func_sym(uri_obj);

            if (strcmp(full_struct_sym,
                       CFCClass_full_struct_sym(klass)) == 0
            ) {
                // Same class.
                link_text = CFCUtil_sprintf("%s()", func_sym);
            }
            else {
                CFCClass *uri_class
                    = CFCClass_fetch_by_struct_sym(full_struct_sym);

                if (!uri_class) {
                    CFCUtil_warn("URI class not found: %s", full_struct_sym);
                    link_text = CFCUtil_sprintf("%s()", func_sym);
                }
                else {
                    const char *prefix   = CFCUri_get_prefix(uri_obj);
                    const char *nickname = CFCClass_get_nickname(uri_class);

                    if (strcmp(prefix, CFCClass_get_prefix(klass)) == 0) {
                        // Same parcel.
                        link_text = CFCUtil_sprintf("%s_%s()", nickname,
                                                    func_sym);
                    }
                    else {
                        // Other parcel.
                        link_text = CFCUtil_sprintf("%s%s_%s()", prefix,
                                                    nickname, func_sym);
                    }
                }
            }
#endif
            break;
        }
    }

    return link_text;
}
Beispiel #13
0
static void
S_run_tests(CFCTest *test) {
    CFCParser *parser = CFCParser_new();
    OK(test, parser != NULL, "new");

    {
        CFCParcel *fish = CFCTest_parse_parcel(test, parser, "parcel Fish;");

        CFCParcel *registered
            = CFCParcel_new("Crustacean", "Crust", NULL, false);
        CFCParcel_register(registered);
        CFCParcel *parcel
            = CFCTest_parse_parcel(test, parser, "parcel Crustacean;");
        OK(test, parcel == registered, "Fetch registered parcel");
        OK(test, CFCParser_get_parcel(parser) == parcel,
           "parcel_definition sets internal var");

        CFCBase_decref((CFCBase*)fish);
        CFCBase_decref((CFCBase*)registered);
        CFCBase_decref((CFCBase*)parcel);
    }

    {
        static const char *const specifiers[8] = {
            "foo", "_foo", "foo_yoo", "FOO", "Foo", "fOO", "f00", "foo_foo_foo"
        };
        for (int i = 0; i < 8; ++i) {
            const char *specifier = specifiers[i];
            char *src = CFCUtil_sprintf("int32_t %s;", specifier);
            CFCVariable *var = CFCTest_parse_variable(test, parser, src);
            STR_EQ(test, CFCVariable_micro_sym(var), specifier,
                   "identifier/declarator: %s", specifier);
            FREEMEM(src);
            CFCBase_decref((CFCBase*)var);
        }
    }

    {
        static const char *const specifiers[6] = {
            "void", "float", "uint32_t", "int64_t", "uint8_t", "bool"
        };
        for (int i = 0; i < 6; ++i) {
            const char *specifier = specifiers[i];
            char *src = CFCUtil_sprintf("int32_t %s;", specifier);
            CFCBase *result = CFCParser_parse(parser, src);
            OK(test, result == NULL,
               "reserved word not parsed as identifier: %s", specifier);
            FREEMEM(src);
            CFCBase_decref(result);
        }
    }

    {
        static const char *const type_strings[7] = {
            "bool", "const char *", "Obj*", "i32_t", "char[]", "long[1]",
            "i64_t[30]"
        };
        for (int i = 0; i < 7; ++i) {
            const char *type_string = type_strings[i];
            CFCType *type = CFCTest_parse_type(test, parser, type_string);
            CFCBase_decref((CFCBase*)type);
        }
    }

    {
        static const char *const class_names[7] = {
            "ByteBuf", "Obj", "ANDMatcher", "Foo", "FooJr", "FooIII", "Foo4th"
        };
        CFCClass *class_list[8];
        for (int i = 0; i < 7; ++i) {
            char *class_code = CFCUtil_sprintf("class %s {}", class_names[i]);
            CFCClass *klass = CFCTest_parse_class(test, parser, class_code);
            class_list[i] = klass;
            FREEMEM(class_code);
        }
        class_list[7] = NULL;
        for (int i = 0; i < 7; ++i) {
            const char *class_name = class_names[i];
            char *src      = CFCUtil_sprintf("%s*", class_name);
            char *expected = CFCUtil_sprintf("crust_%s", class_name);
            CFCType *type = CFCTest_parse_type(test, parser, src);
            CFCType_resolve(type, class_list);
            STR_EQ(test, CFCType_get_specifier(type), expected,
                   "object_type_specifier: %s", class_name);
            FREEMEM(src);
            FREEMEM(expected);
            CFCBase_decref((CFCBase*)type);
        }
        for (int i = 0; i < 7; ++i) {
            CFCBase_decref((CFCBase*)class_list[i]);
        }
        CFCClass_clear_registry();
    }

    {
        CFCType *type = CFCTest_parse_type(test, parser, "const char");
        OK(test, CFCType_const(type), "type_qualifier const");
        CFCBase_decref((CFCBase*)type);
    }

    {
        static const char *const exposures[2] = {
            "public", ""
        };
        static int (*const accessors[2])(CFCSymbol *sym) = {
            CFCSymbol_public, CFCSymbol_parcel
        };
        for (int i = 0; i < 2; ++i) {
            const char *exposure = exposures[i];
            char *src = CFCUtil_sprintf("%s inert int32_t foo;", exposure);
            CFCVariable *var = CFCTest_parse_variable(test, parser, src);
            OK(test, accessors[i]((CFCSymbol*)var), "exposure_specifier %s",
               exposure);
            FREEMEM(src);
            CFCBase_decref((CFCBase*)var);
        }
    }

    {
        static const char *const hex_constants[] = {
            "0x1", "0x0a", "0xFFFFFFFF", "-0xFC", NULL
        };
        S_test_initial_value(test, parser, hex_constants, "int32_t",
                             "hex_constant:");
    }

    {
        static const char *const integer_constants[] = {
            "1", "-9999", "0", "10000", NULL
        };
        S_test_initial_value(test, parser, integer_constants, "int32_t",
                             "integer_constant:");
    }

    {
        static const char *const float_constants[] = {
            "1.0", "-9999.999", "0.1", "0.0", NULL
        };
        S_test_initial_value(test, parser, float_constants, "double",
                             "float_constant:");
    }

    {
        static const char *const string_literals[] = {
            "\"blah\"", "\"blah blah\"", "\"\\\"blah\\\" \\\"blah\\\"\"", NULL
        };
        S_test_initial_value(test, parser, string_literals, "String*",
                             "string_literal:");
    }

    {
        static const char *const composites[5] = {
            "int[]", "i32_t **", "Foo **", "Foo ***", "const void *"
        };
        for (int i = 0; i < 5; ++i) {
            const char *composite = composites[i];
            CFCType *type = CFCTest_parse_type(test, parser, composite);
            OK(test, CFCType_is_composite(type), "composite_type: %s",
               composite);
            CFCBase_decref((CFCBase*)type);
        }
    }

    {
        static const char *const object_types[5] = {
            "Obj *", "incremented Foo*", "decremented String *"
        };
        for (int i = 0; i < 3; ++i) {
            const char *object_type = object_types[i];
            CFCType *type = CFCTest_parse_type(test, parser, object_type);
            OK(test, CFCType_is_object(type), "object_type: %s",
               object_type);
            CFCBase_decref((CFCBase*)type);
        }
    }

    {
        static const char *const param_list_strings[3] = {
            "()",
            "(int foo)",
            "(Obj *foo, Foo **foo_ptr)"
        };
        for (int i = 0; i < 3; ++i) {
            const char *param_list_string = param_list_strings[i];
            CFCParamList *param_list
                = CFCTest_parse_param_list(test, parser, param_list_string);
            INT_EQ(test, CFCParamList_num_vars(param_list), i,
                   "param list num_vars: %d", i);
            CFCBase_decref((CFCBase*)param_list);
        }
    }

    {
        CFCParamList *param_list
            = CFCTest_parse_param_list(test, parser, "(int foo, ...)");
        OK(test, CFCParamList_variadic(param_list), "variadic param list");
        CFCBase_decref((CFCBase*)param_list);
    }

    {
        const char *param_list_string =
            "(int foo = 0xFF, char *bar =\"blah\")";
        CFCParamList *param_list
            = CFCTest_parse_param_list(test, parser, param_list_string);
        const char **initial_values
            = CFCParamList_get_initial_values(param_list);
        STR_EQ(test, initial_values[0], "0xFF",
               "param list initial_values[0]");
        STR_EQ(test, initial_values[1], "\"blah\"",
               "param list initial_values[1]");
        OK(test, initial_values[2] == NULL, "param list initial_values[2]");
        CFCBase_decref((CFCBase*)param_list);
    }

    {
        CFCParser_set_class_name(parser, "Stuff::Obj");
        CFCParser_set_class_cnick(parser, "Obj");

        const char *method_string =
            "public Foo* Spew_Foo(Obj *self, uint32_t *how_many);";
        CFCMethod *method = CFCTest_parse_method(test, parser, method_string);
        CFCBase_decref((CFCBase*)method);

        const char *var_string =
            "public inert Hash *hash;";
        CFCVariable *var = CFCTest_parse_variable(test, parser, var_string);
        CFCBase_decref((CFCBase*)var);
    }

    {
        static const char *const class_names[4] = {
            "Foo", "Foo::FooJr", "Foo::FooJr::FooIII",
            "Foo::FooJr::FooIII::Foo4th"
        };
        for (int i = 0; i < 4; ++i) {
            const char *class_name = class_names[i];
            char *class_string = CFCUtil_sprintf("class %s { }", class_name);
            CFCClass *klass
                = CFCTest_parse_class(test, parser, class_string);
            STR_EQ(test, CFCClass_get_class_name(klass), class_name,
                   "class_name: %s", class_name);
            FREEMEM(class_string);
            CFCBase_decref((CFCBase*)klass);
        }
    }

    {
        static const char *const cnicks[2] =  { "Food", "FF" };
        for (int i = 0; i < 2; ++i) {
            const char *cnick = cnicks[i];
            char *class_string
                = CFCUtil_sprintf("class Foodie%s cnick %s { }", cnick, cnick);
            CFCClass *klass
                = CFCTest_parse_class(test, parser, class_string);
            STR_EQ(test, CFCClass_get_cnick(klass), cnick, "cnick: %s", cnick);
            FREEMEM(class_string);
            CFCBase_decref((CFCBase*)klass);
        }
    }

    CFCBase_decref((CFCBase*)parser);

    CFCClass_clear_registry();
    CFCParcel_reap_singletons();
}