blob: fe40119ba56c93fdf8e45d28dc719a7794a10255 [file] [log] [blame]
/* 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 <string.h>
#include <stdlib.h>
#include <stdio.h>
#define CFC_NEED_BASE_STRUCT_DEF
#include "CFCBase.h"
#include "CFCPerlClass.h"
#include "CFCUtil.h"
#include "CFCClass.h"
#include "CFCFunction.h"
#include "CFCMethod.h"
#include "CFCParcel.h"
#include "CFCParamList.h"
#include "CFCFunction.h"
#include "CFCDocuComment.h"
#include "CFCSymbol.h"
#include "CFCVariable.h"
#include "CFCType.h"
#include "CFCPerlPod.h"
#include "CFCPerlSub.h"
#include "CFCPerlMethod.h"
#include "CFCPerlConstructor.h"
#include "CFCPerlTypeMap.h"
struct CFCPerlClass {
CFCBase base;
CFCParcel *parcel;
char *class_name;
CFCClass *client;
char *xs_code;
CFCPerlPod *pod_spec;
char **cons_aliases;
char **cons_inits;
size_t num_cons;
int exclude_cons;
char **class_aliases;
size_t num_class_aliases;
};
static CFCPerlClass **registry = NULL;
static size_t registry_size = 0;
static size_t registry_cap = 0;
static const CFCMeta CFCPERLCLASS_META = {
"Clownfish::CFC::Binding::Perl::Class",
sizeof(CFCPerlClass),
(CFCBase_destroy_t)CFCPerlClass_destroy
};
CFCPerlClass*
CFCPerlClass_new(CFCParcel *parcel, const char *class_name) {
CFCPerlClass *self = (CFCPerlClass*)CFCBase_allocate(&CFCPERLCLASS_META);
return CFCPerlClass_init(self, parcel, class_name);
}
CFCPerlClass*
CFCPerlClass_init(CFCPerlClass *self, CFCParcel *parcel,
const char *class_name) {
CFCUTIL_NULL_CHECK(class_name);
// Client may be NULL, since fetch_singleton() does not always succeed.
CFCClass *client = CFCClass_fetch_singleton(class_name);
if (client == NULL) {
if (parcel == NULL) {
CFCUtil_die("Missing parcel for class %s", class_name);
}
}
else {
CFCParcel *client_parcel = CFCClass_get_parcel(client);
if (parcel == NULL) {
parcel = client_parcel;
}
else if (client_parcel != parcel) {
CFCUtil_die("Wrong parcel %s for class %s",
CFCParcel_get_name(parcel), class_name);
}
}
self->parcel = (CFCParcel*)CFCBase_incref((CFCBase*)parcel);
self->class_name = CFCUtil_strdup(class_name);
self->client = (CFCClass*)CFCBase_incref((CFCBase*)client);
self->pod_spec = NULL;
self->xs_code = NULL;
self->cons_aliases = NULL;
self->cons_inits = NULL;
self->num_cons = 0;
self->exclude_cons = 0;
self->class_aliases = (char**)CALLOCATE(1, sizeof(char*));
self->num_class_aliases = 0;
return self;
}
void
CFCPerlClass_destroy(CFCPerlClass *self) {
CFCBase_decref((CFCBase*)self->parcel);
CFCBase_decref((CFCBase*)self->client);
CFCBase_decref((CFCBase*)self->pod_spec);
FREEMEM(self->class_name);
FREEMEM(self->xs_code);
for (size_t i = 0; i < self->num_cons; i++) {
FREEMEM(self->cons_aliases[i]);
FREEMEM(self->cons_inits[i]);
}
FREEMEM(self->cons_aliases);
FREEMEM(self->cons_inits);
CFCUtil_free_string_array(self->class_aliases);
CFCBase_destroy((CFCBase*)self);
}
static int
S_compare_cfcperlclass(const void *va, const void *vb) {
CFCPerlClass *a = *(CFCPerlClass**)va;
CFCPerlClass *b = *(CFCPerlClass**)vb;
return strcmp(a->class_name, b->class_name);
}
void
CFCPerlClass_add_to_registry(CFCPerlClass *self) {
if (registry_size == registry_cap) {
size_t new_cap = registry_cap + 10;
registry = (CFCPerlClass**)REALLOCATE(registry,
(new_cap + 1) * sizeof(CFCPerlClass*));
for (size_t i = registry_cap; i <= new_cap; i++) {
registry[i] = NULL;
}
registry_cap = new_cap;
}
CFCPerlClass *existing = CFCPerlClass_singleton(self->class_name);
if (existing) {
CFCUtil_die("Class '%s' already registered", self->class_name);
}
registry[registry_size] = (CFCPerlClass*)CFCBase_incref((CFCBase*)self);
registry_size++;
qsort(registry, registry_size, sizeof(CFCPerlClass*),
S_compare_cfcperlclass);
}
CFCPerlClass*
CFCPerlClass_singleton(const char *class_name) {
CFCUTIL_NULL_CHECK(class_name);
for (size_t i = 0; i < registry_size; i++) {
CFCPerlClass *existing = registry[i];
if (strcmp(class_name, existing->class_name) == 0) {
return existing;
}
}
return NULL;
}
CFCPerlClass**
CFCPerlClass_registry() {
if (!registry) {
registry = (CFCPerlClass**)CALLOCATE(1, sizeof(CFCPerlClass*));
}
return registry;
}
void
CFCPerlClass_clear_registry(void) {
for (size_t i = 0; i < registry_size; i++) {
CFCBase_decref((CFCBase*)registry[i]);
}
FREEMEM(registry);
registry_size = 0;
registry_cap = 0;
registry = NULL;
}
void
CFCPerlClass_bind_method(CFCPerlClass *self, const char *alias,
const char *meth_name) {
if (!self->client) {
CFCUtil_die("Can't bind_method %s -- can't find client for %s",
alias, self->class_name);
}
CFCMethod *method = CFCClass_method(self->client, meth_name);
if (!method) {
CFCUtil_die("Can't bind_method %s -- can't find method %s in %s",
alias, meth_name, self->class_name);
}
if (!CFCMethod_is_fresh(method, self->client)) {
CFCUtil_die("Can't bind_method %s -- method %s not fresh in %s",
alias, meth_name, self->class_name);
}
CFCMethod_set_host_alias(method, alias);
}
void
CFCPerlClass_exclude_method(CFCPerlClass *self, const char *meth_name) {
if (!self->client) {
CFCUtil_die("Can't exclude_method %s -- can't find client for %s",
meth_name, self->class_name);
}
CFCMethod *method = CFCClass_method(self->client, meth_name);
if (!method) {
CFCUtil_die("Can't exclude_method %s -- method not found in %s",
meth_name, self->class_name);
}
if (!CFCMethod_is_fresh(method, self->client)) {
CFCUtil_die("Can't exclude_method %s -- method not fresh in %s",
meth_name, self->class_name);
}
CFCMethod_exclude_from_host(method);
}
void
CFCPerlClass_bind_constructor(CFCPerlClass *self, const char *alias,
const char *initializer) {
alias = alias ? alias : "new";
initializer = initializer ? initializer : "init";
size_t size = (self->num_cons + 1) * sizeof(char*);
self->cons_aliases = (char**)REALLOCATE(self->cons_aliases, size);
self->cons_inits = (char**)REALLOCATE(self->cons_inits, size);
self->cons_aliases[self->num_cons] = (char*)CFCUtil_strdup(alias);
self->cons_inits[self->num_cons] = (char*)CFCUtil_strdup(initializer);
self->num_cons++;
if (!self->client) {
CFCUtil_die("Can't bind_constructor %s -- can't find client for %s",
alias, self->class_name);
}
}
void
CFCPerlClass_exclude_constructor(CFCPerlClass *self) {
self->exclude_cons = 1;
}
CFCPerlMethod**
CFCPerlClass_method_bindings(CFCClass *klass) {
size_t num_bound = 0;
CFCMethod **fresh_methods = CFCClass_fresh_methods(klass);
CFCPerlMethod **bound
= (CFCPerlMethod**)CALLOCATE(1, sizeof(CFCPerlMethod*));
// Iterate over the class's fresh methods.
for (size_t i = 0; fresh_methods[i] != NULL; i++) {
CFCMethod *method = fresh_methods[i];
// Skip methods which have been explicitly excluded.
if (CFCMethod_excluded_from_host(method)) {
continue;
}
// Skip methods that shouldn't be bound.
if (!CFCMethod_can_be_bound(method)) {
continue;
}
/* Create the binding, add it to the array.
*
* Also create an XSub binding for each override. Each of these
* directly calls the implementing function, rather than invokes the
* method on the object using vtable method dispatch. Doing things
* this way allows SUPER:: invocations from Perl-space to work
* properly. (The callback to the Perl method is stored in the
* vtable as well. Using dynamic dispatch for SUPER:: invocations
* would result in calling the Perl method over and over.)
*/
CFCPerlMethod *meth_binding = CFCPerlMethod_new(klass, method);
size_t size = (num_bound + 2) * sizeof(CFCPerlMethod*);
bound = (CFCPerlMethod**)REALLOCATE(bound, size);
bound[num_bound] = meth_binding;
num_bound++;
bound[num_bound] = NULL;
}
return bound;
}
static const char NEW[] = "new";
CFCPerlConstructor**
CFCPerlClass_constructor_bindings(CFCClass *klass) {
const char *class_name = CFCClass_get_name(klass);
CFCPerlClass *perl_class = CFCPerlClass_singleton(class_name);
CFCFunction **functions = CFCClass_functions(klass);
size_t num_bound = 0;
CFCPerlConstructor **bound
= (CFCPerlConstructor**)CALLOCATE(1, sizeof(CFCPerlConstructor*));
// Iterate over the list of possible initialization functions.
for (size_t i = 0; functions[i] != NULL; i++) {
CFCFunction *function = functions[i];
const char *func_name = CFCFunction_get_name(function);
const char *alias = NULL;
// Find user-specified alias.
if (perl_class == NULL) {
// Bind init() to new() when possible.
if (strcmp(func_name, "init") == 0
&& CFCFunction_can_be_bound(function)
) {
alias = NEW;
}
}
else {
for (size_t j = 0; j < perl_class->num_cons; j++) {
if (strcmp(func_name, perl_class->cons_inits[j]) == 0) {
alias = perl_class->cons_aliases[j];
if (!CFCFunction_can_be_bound(function)) {
CFCUtil_die("Can't bind %s as %s"
" -- types can't be mapped",
func_name, alias);
}
break;
}
}
// Automatically bind init() to new() when possible.
if (!alias
&& !perl_class->exclude_cons
&& strcmp(func_name, "init") == 0
&& CFCFunction_can_be_bound(function)
) {
int saw_new = 0;
for (size_t j = 0; j < perl_class->num_cons; j++) {
if (strcmp(perl_class->cons_aliases[j], "new") == 0) {
saw_new = 1;
}
}
if (!saw_new) {
alias = NEW;
}
}
}
if (!alias) {
continue;
}
// Create the binding, add it to the array.
CFCPerlConstructor *cons_binding
= CFCPerlConstructor_new(klass, alias, func_name);
size_t size = (num_bound + 2) * sizeof(CFCPerlConstructor*);
bound = (CFCPerlConstructor**)REALLOCATE(bound, size);
bound[num_bound] = cons_binding;
num_bound++;
bound[num_bound] = NULL;
}
return bound;
}
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(raw_brief, client, 2);
// 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(raw_description, client, 2);
}
// 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_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[] =
"=encoding utf8\n"
"\n"
"=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;
}
CFCClass*
CFCPerlClass_get_client(CFCPerlClass *self) {
return self->client;
}
const char*
CFCPerlClass_get_class_name(CFCPerlClass *self) {
return self->class_name;
}
void
CFCPerlClass_append_xs(CFCPerlClass *self, const char *xs) {
if (!self->xs_code) {
self->xs_code = CFCUtil_strdup("");
}
self->xs_code = CFCUtil_cat(self->xs_code, xs, NULL);
}
const char*
CFCPerlClass_get_xs_code(CFCPerlClass *self) {
return self->xs_code;
}
void
CFCPerlClass_set_pod_spec(CFCPerlClass *self, CFCPerlPod *pod_spec) {
CFCPerlPod *old_pod_spec = self->pod_spec;
self->pod_spec = (CFCPerlPod*)CFCBase_incref((CFCBase*)pod_spec);
CFCBase_decref((CFCBase*)old_pod_spec);
}
CFCPerlPod*
CFCPerlClass_get_pod_spec(CFCPerlClass *self) {
return self->pod_spec;
}
void
CFCPerlClass_add_class_alias(CFCPerlClass *self, const char *alias) {
for (size_t i = 0; i < self->num_class_aliases; i++) {
if (strcmp(alias, self->class_aliases[i]) == 0) {
CFCUtil_die("Alias '%s' already added for class '%s'", alias,
self->class_name);
}
}
size_t size = (self->num_class_aliases + 2) * sizeof(char*);
self->class_aliases = (char**)REALLOCATE(self->class_aliases, size);
self->class_aliases[self->num_class_aliases] = CFCUtil_strdup(alias);
self->num_class_aliases++;
self->class_aliases[self->num_class_aliases] = NULL;
}
const char**
CFCPerlClass_get_class_aliases(CFCPerlClass *self) {
return (const char **)self->class_aliases;
}
// Generate C code which initializes method metadata.
char*
CFCPerlClass_method_metadata_code(CFCPerlClass *self) {
const char *class_var = CFCClass_full_class_var(self->client);
CFCMethod **fresh_methods = CFCClass_fresh_methods(self->client);
char *code = CFCUtil_strdup("");
for (int i = 0; fresh_methods[i] != NULL; i++) {
CFCMethod *method = fresh_methods[i];
if (!CFCMethod_novel(method)) { continue; }
const char *meth_name = CFCMethod_get_name(method);
const char *alias = CFCMethod_get_host_alias(method);
if (alias) {
code = CFCUtil_cat(code, " CFISH_Class_Add_Host_Method_Alias(",
class_var, ", \"", alias, "\", \"", meth_name,
"\");\n", NULL);
}
if (CFCMethod_excluded_from_host(method)) {
code = CFCUtil_cat(code, " CFISH_Class_Exclude_Host_Method(",
class_var, ", \"", meth_name, "\");\n", NULL);
}
}
return code;
}
CFCParcel*
CFCPerlClass_get_parcel(CFCPerlClass *self) {
return self->parcel;
}