blob: 60c9166b9e3f80331703317085328081eb81b507 [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 <stdlib.h>
#include <string.h>
#include <ctype.h>
#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"
#include "ppport.h"
#ifndef true
#define true 1
#define false 0
#endif
#define CFC_NEED_SYMBOL_STRUCT_DEF
#include "CFCSymbol.h"
#include "CFCClass.h"
#include "CFCDumpable.h"
#include "CFCFunction.h"
#include "CFCMethod.h"
#include "CFCParcel.h"
#include "CFCDocuComment.h"
#include "CFCUtil.h"
#include "CFCVariable.h"
typedef struct CFCClassAttribute {
char *name;
char *value;
} CFCClassAttribute;
typedef struct CFCClassRegEntry {
char *key;
struct CFCClass *klass;
} CFCClassRegEntry;
static CFCClassRegEntry *registry = NULL;
static size_t registry_size = 0;
static size_t registry_cap = 0;
struct CFCClass {
CFCSymbol symbol;
int tree_grown;
CFCDocuComment *docucomment;
struct CFCClass *parent;
struct CFCClass **children;
size_t num_kids;
CFCFunction **functions;
size_t num_functions;
CFCMethod **methods;
size_t num_methods;
CFCVariable **member_vars;
size_t num_member_vars;
CFCVariable **inert_vars;
size_t num_inert_vars;
CFCClassAttribute **attributes;
size_t num_attributes;
char *autocode;
char *source_class;
char *parent_class_name;
int is_final;
int is_inert;
char *struct_sym;
char *full_struct_sym;
char *short_vtable_var;
char *full_vtable_var;
char *full_vtable_type;
char *include_h;
};
// Link up parents and kids.
static void
S_establish_ancestry(CFCClass *self);
// Pass down member vars to from parent to children.
static void
S_bequeath_member_vars(CFCClass *self);
// Create auto-generated methods. This must be called after member vars are
// passed down but before methods are passed down.
static void
S_generate_automethods(CFCClass *self);
// Create dumpable functions unless hand coded versions were supplied.
static void
S_create_dumpables(CFCClass *self);
// Pass down methods to from parent to children.
static void
S_bequeath_methods(CFCClass *self);
CFCClass*
CFCClass_create(struct CFCParcel *parcel, const char *exposure,
const char *class_name, const char *cnick,
const char *micro_sym, CFCDocuComment *docucomment,
const char *source_class, const char *parent_class_name,
int is_final, int is_inert) {
CFCClass *self = (CFCClass*)CFCBase_allocate(sizeof(CFCClass),
"Clownfish::Class");
return CFCClass_do_create(self, parcel, exposure, class_name, cnick,
micro_sym, docucomment, source_class,
parent_class_name, is_final, is_inert);
}
CFCClass*
CFCClass_do_create(CFCClass *self, struct CFCParcel *parcel,
const char *exposure, const char *class_name,
const char *cnick, const char *micro_sym,
CFCDocuComment *docucomment, const char *source_class,
const char *parent_class_name, int is_final, int is_inert) {
CFCUTIL_NULL_CHECK(class_name);
exposure = exposure ? exposure : "parcel";
micro_sym = micro_sym ? micro_sym : "class";
CFCSymbol_init((CFCSymbol*)self, parcel, exposure, class_name, cnick,
micro_sym);
self->parent = NULL;
self->tree_grown = false;
self->autocode = (char*)CALLOCATE(1, sizeof(char));
self->children = (CFCClass**)CALLOCATE(1, sizeof(CFCClass*));
self->num_kids = 0;
self->functions = (CFCFunction**)CALLOCATE(1, sizeof(CFCFunction*));
self->num_functions = 0;
self->methods = (CFCMethod**)CALLOCATE(1, sizeof(CFCMethod*));
self->num_methods = 0;
self->member_vars = (CFCVariable**)CALLOCATE(1, sizeof(CFCVariable*));
self->num_member_vars = 0;
self->inert_vars = (CFCVariable**)CALLOCATE(1, sizeof(CFCVariable*));
self->num_inert_vars = 0;
self->attributes = (CFCClassAttribute**)CALLOCATE(1, sizeof(CFCClassAttribute*));
self->num_attributes = 0;
self->parent_class_name = CFCUtil_strdup(parent_class_name);
self->docucomment
= (CFCDocuComment*)CFCBase_incref((CFCBase*)docucomment);
// Assume that Foo::Bar should be found in Foo/Bar.h.
self->source_class = source_class
? CFCUtil_strdup(source_class)
: CFCUtil_strdup(class_name);
// Cache several derived symbols.
const char *last_colon = strrchr(class_name, ':');
self->struct_sym = last_colon
? CFCUtil_strdup(last_colon + 1)
: CFCUtil_strdup(class_name);
const char *prefix = CFCSymbol_get_prefix((CFCSymbol*)self);
size_t prefix_len = strlen(prefix);
size_t struct_sym_len = strlen(self->struct_sym);
self->short_vtable_var = (char*)MALLOCATE(struct_sym_len + 1);
self->full_struct_sym = (char*)MALLOCATE(prefix_len + struct_sym_len + 1);
self->full_vtable_var = (char*)MALLOCATE(prefix_len + struct_sym_len + 1);
self->full_vtable_type = (char*)MALLOCATE(prefix_len + struct_sym_len + 3 + 1);
size_t i;
for (i = 0; i < struct_sym_len; i++) {
self->short_vtable_var[i] = toupper(self->struct_sym[i]);
}
self->short_vtable_var[struct_sym_len] = '\0';
int check = sprintf(self->full_struct_sym, "%s%s", prefix,
self->struct_sym);
if (check < 0) { croak("sprintf failed"); }
for (i = 0; self->full_struct_sym[i] != '\0'; i++) {
self->full_vtable_var[i] = toupper(self->full_struct_sym[i]);
}
self->full_vtable_var[i] = '\0';
check = sprintf(self->full_vtable_type, "%s_VT", self->full_vtable_var);
if (check < 0) { croak("sprintf failed"); }
// Cache the relative path to the autogenerated C header file.
size_t source_class_len = strlen(self->source_class);
self->include_h = (char*)MALLOCATE(source_class_len + 3);
int j;
for (i = 0, j = 0; i < source_class_len; i++) {
if (self->source_class[i] == ':') {
self->include_h[j++] = '/';
i++;
}
else {
self->include_h[j++] = self->source_class[i];
}
}
self->include_h[j] = '\0';
strcat(self->include_h, ".h");
self->is_final = !!is_final;
self->is_inert = !!is_inert;
// Store in registry.
CFCClass_register(self);
return self;
}
void
CFCClass_destroy(CFCClass *self) {
CFCBase_decref((CFCBase*)self->docucomment);
CFCBase_decref((CFCBase*)self->parent);
size_t i;
for (i = 0; self->children[i] != NULL; i++) {
CFCBase_decref((CFCBase*)self->children[i]);
}
for (i = 0; self->functions[i] != NULL; i++) {
CFCBase_decref((CFCBase*)self->functions[i]);
}
for (i = 0; self->methods[i] != NULL; i++) {
CFCBase_decref((CFCBase*)self->methods[i]);
}
for (i = 0; self->member_vars[i] != NULL; i++) {
CFCBase_decref((CFCBase*)self->member_vars[i]);
}
for (i = 0; self->inert_vars[i] != NULL; i++) {
CFCBase_decref((CFCBase*)self->inert_vars[i]);
}
for (i = 0; self->attributes[i] != NULL; i++) {
CFCClassAttribute *attribute = self->attributes[i];
FREEMEM(attribute->name);
FREEMEM(attribute->value);
FREEMEM(attribute);
}
FREEMEM(self->children);
FREEMEM(self->functions);
FREEMEM(self->methods);
FREEMEM(self->member_vars);
FREEMEM(self->inert_vars);
FREEMEM(self->attributes);
FREEMEM(self->autocode);
FREEMEM(self->source_class);
FREEMEM(self->parent_class_name);
FREEMEM(self->struct_sym);
FREEMEM(self->short_vtable_var);
FREEMEM(self->full_struct_sym);
FREEMEM(self->full_vtable_var);
FREEMEM(self->full_vtable_type);
CFCSymbol_destroy((CFCSymbol*)self);
}
void
CFCClass_register(CFCClass *self) {
if (registry_size == registry_cap) {
size_t new_cap = registry_cap + 10;
registry = (CFCClassRegEntry*)REALLOCATE(
registry,
(new_cap + 1) * sizeof(CFCClassRegEntry));
size_t i;
for (i = registry_cap; i <= new_cap; i++) {
registry[i].key = NULL;
registry[i].klass = NULL;
}
registry_cap = new_cap;
}
CFCParcel *parcel = CFCSymbol_get_parcel((CFCSymbol*)self);
const char *class_name = CFCSymbol_get_class_name((CFCSymbol*)self);
CFCClass *existing = CFCClass_fetch_singleton(parcel, class_name);
const char *key = self->full_struct_sym;
if (existing) {
croak("New class %s conflicts with existing class %s",
CFCSymbol_get_class_name((CFCSymbol*)self),
CFCSymbol_get_class_name((CFCSymbol*)existing));
}
registry[registry_size].key = CFCUtil_strdup(key);
registry[registry_size].klass = (CFCClass*)CFCBase_incref((CFCBase*)self);
registry_size++;
}
CFCClass*
CFCClass_fetch_singleton(CFCParcel *parcel, const char *class_name) {
CFCUTIL_NULL_CHECK(class_name);
// Build up the key.
const char *last_colon = strrchr(class_name, ':');
const char *struct_sym = last_colon
? last_colon + 1
: class_name;
const char *prefix = parcel ? CFCParcel_get_prefix(parcel) : "";
size_t prefix_len = strlen(prefix);
size_t struct_sym_len = strlen(struct_sym);
const size_t MAX_LEN = 256;
if (prefix_len + struct_sym_len > MAX_LEN) {
croak("names too long: '%s', '%s'", prefix, struct_sym);
}
char key[MAX_LEN + 1];
int check = sprintf(key, "%s%s", prefix, struct_sym);
if (check < 0) { croak("sprintf failed"); }
size_t i;
for (i = 0; i < registry_size; i++) {
if (strcmp(registry[i].key, key) == 0) {
return registry[i].klass;
}
}
return NULL;
}
void
CFCClass_clear_registry(void) {
size_t i;
for (i = 0; i < registry_size; i++) {
CFCBase_decref((CFCBase*)registry[i].klass);
}
FREEMEM(registry);
registry_size = 0;
registry_cap = 0;
registry = NULL;
}
void
CFCClass_add_child(CFCClass *self, CFCClass *child) {
CFCUTIL_NULL_CHECK(child);
if (self->tree_grown) { croak("Can't call add_child after grow_tree"); }
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;
}
void
CFCClass_add_function(CFCClass *self, CFCFunction *func) {
CFCUTIL_NULL_CHECK(func);
if (self->tree_grown) {
croak("Can't call add_function after grow_tree");
}
self->num_functions++;
size_t size = (self->num_functions + 1) * sizeof(CFCFunction*);
self->functions = (CFCFunction**)REALLOCATE(self->functions, size);
self->functions[self->num_functions - 1]
= (CFCFunction*)CFCBase_incref((CFCBase*)func);
self->functions[self->num_functions] = NULL;
}
void
CFCClass_add_method(CFCClass *self, CFCMethod *method) {
CFCUTIL_NULL_CHECK(method);
if (self->tree_grown) {
croak("Can't call add_method after grow_tree");
}
if (self->is_inert) {
croak("Can't add_method to an inert class");
}
self->num_methods++;
size_t size = (self->num_methods + 1) * sizeof(CFCMethod*);
self->methods = (CFCMethod**)REALLOCATE(self->methods, size);
self->methods[self->num_methods - 1]
= (CFCMethod*)CFCBase_incref((CFCBase*)method);
self->methods[self->num_methods] = NULL;
}
void
CFCClass_add_member_var(CFCClass *self, CFCVariable *var) {
CFCUTIL_NULL_CHECK(var);
if (self->tree_grown) {
croak("Can't call add_member_var after grow_tree");
}
self->num_member_vars++;
size_t size = (self->num_member_vars + 1) * sizeof(CFCVariable*);
self->member_vars = (CFCVariable**)REALLOCATE(self->member_vars, size);
self->member_vars[self->num_member_vars - 1]
= (CFCVariable*)CFCBase_incref((CFCBase*)var);
self->member_vars[self->num_member_vars] = NULL;
}
void
CFCClass_add_inert_var(CFCClass *self, CFCVariable *var) {
CFCUTIL_NULL_CHECK(var);
if (self->tree_grown) {
croak("Can't call add_inert_var after grow_tree");
}
self->num_inert_vars++;
size_t size = (self->num_inert_vars + 1) * sizeof(CFCVariable*);
self->inert_vars = (CFCVariable**)REALLOCATE(self->inert_vars, size);
self->inert_vars[self->num_inert_vars - 1]
= (CFCVariable*)CFCBase_incref((CFCBase*)var);
self->inert_vars[self->num_inert_vars] = NULL;
}
void
CFCClass_add_attribute(CFCClass *self, const char *name, const char *value) {
if (!name || !strlen(name)) { croak("'name' is required"); }
if (CFCClass_has_attribute(self, name)) {
croak("Attribute '%s' already registered");
}
CFCClassAttribute *attribute
= (CFCClassAttribute*)MALLOCATE(sizeof(CFCClassAttribute));
attribute->name = CFCUtil_strdup(name);
attribute->value = CFCUtil_strdup(value);
self->num_attributes++;
size_t size = (self->num_attributes + 1) * sizeof(CFCClassAttribute*);
self->attributes = (CFCClassAttribute**)REALLOCATE(self->attributes, size);
self->attributes[self->num_attributes - 1] = attribute;
self->attributes[self->num_attributes] = NULL;
}
int
CFCClass_has_attribute(CFCClass *self, const char *name) {
CFCUTIL_NULL_CHECK(name);
size_t i;
for (i = 0; i < self->num_attributes; i++) {
if (strcmp(name, self->attributes[i]->name) == 0) {
return true;
}
}
return false;
}
static CFCFunction*
S_find_func(CFCFunction **funcs, const char *sym) {
const size_t MAX_LEN = 128;
char lcsym[MAX_LEN + 1];
size_t sym_len = strlen(sym);
if (sym_len > MAX_LEN) { croak("sym too long: '%s'", sym); }
size_t i;
for (i = 0; i <= sym_len; i++) {
lcsym[i] = tolower(sym[i]);
}
for (i = 0; funcs[i] != NULL; i++) {
CFCFunction *func = funcs[i];
const char *func_micro_sym = CFCSymbol_micro_sym((CFCSymbol*)func);
if (strcmp(lcsym, func_micro_sym) == 0) {
return func;
}
}
return NULL;
}
CFCFunction*
CFCClass_function(CFCClass *self, const char *sym) {
return S_find_func(self->functions, sym);
}
CFCMethod*
CFCClass_method(CFCClass *self, const char *sym) {
return (CFCMethod*)S_find_func((CFCFunction**)self->methods, sym);
}
CFCMethod*
CFCClass_novel_method(CFCClass *self, const char *sym) {
CFCMethod *method = CFCClass_method(self, sym);
if (method) {
const char *cnick = CFCClass_get_cnick(self);
const char *meth_cnick
= CFCSymbol_get_class_cnick((CFCSymbol*)method);
if (strcmp(cnick, meth_cnick) == 0) {
return method;
}
}
return NULL;
}
// Pass down member vars to from parent to children.
static void
S_bequeath_member_vars(CFCClass *self) {
size_t i;
for (i = 0; self->children[i] != NULL; i++) {
CFCClass *child = self->children[i];
size_t num_vars = self->num_member_vars + child->num_member_vars;
size_t size = (num_vars + 1) * sizeof(CFCVariable*);
child->member_vars
= (CFCVariable**)REALLOCATE(child->member_vars, size);
memmove(child->member_vars + self->num_member_vars,
child->member_vars,
child->num_member_vars * sizeof(CFCVariable*));
memcpy(child->member_vars, self->member_vars,
self->num_member_vars * sizeof(CFCVariable*));
size_t j;
for (j = 0; self->member_vars[j] != NULL; j++) {
CFCBase_incref((CFCBase*)child->member_vars[j]);
}
child->num_member_vars = num_vars;
child->member_vars[num_vars] = NULL;
S_bequeath_member_vars(child);
}
}
static void
S_bequeath_methods(CFCClass *self) {
size_t child_num;
for (child_num = 0; self->children[child_num] != NULL; child_num++) {
CFCClass *child = self->children[child_num];
// Create array of methods, preserving exact order so vtables match up.
size_t num_methods = 0;
size_t max_methods = self->num_methods + child->num_methods;
CFCMethod **methods = (CFCMethod**)MALLOCATE(
(max_methods + 1) * sizeof(CFCMethod*));
// Gather methods which child inherits or overrides.
size_t i;
for (i = 0; i < self->num_methods; i++) {
CFCMethod *method = self->methods[i];
const char *micro_sym = CFCSymbol_micro_sym((CFCSymbol*)method);
CFCMethod *child_method = CFCClass_method(child, micro_sym);
if (child_method) {
CFCMethod_override(child_method, method);
methods[num_methods++] = child_method;
}
else {
methods[num_methods++] = method;
}
}
// Append novel child methods to array. Child methods which were just
// marked via CFCMethod_override() a moment ago are skipped.
for (i = 0; i < child->num_methods; i++) {
CFCMethod *method = child->methods[i];
if (CFCMethod_novel(method)) {
methods[num_methods++] = method;
}
}
methods[num_methods] = NULL;
// Manage refcounts and assign new array. Transform to final methods
// if child class is a final class.
if (child->is_final) {
for (i = 0; i < num_methods; i++) {
methods[i] = CFCMethod_finalize(methods[i]);
}
}
else {
for (i = 0; i < num_methods; i++) {
CFCBase_incref((CFCBase*)methods[i]);
}
}
for (i = 0; i < child->num_methods; i++) {
CFCBase_decref((CFCBase*)child->methods[i]);
}
FREEMEM(child->methods);
child->methods = methods;
child->num_methods = num_methods;
// Pass it all down to the next generation.
S_bequeath_methods(child);
child->tree_grown = true;
}
}
// Let the children know who their parent class is.
static void
S_establish_ancestry(CFCClass *self) {
size_t i;
for (i = 0; i < self->num_kids; i++) {
CFCClass *child = self->children[i];
// This is a circular reference and thus a memory leak, but we don't
// care, because we have to have everything in memory at once anyway.
CFCClass_set_parent(child, self);
S_establish_ancestry(child);
}
}
static size_t
S_family_tree_size(CFCClass *self) {
size_t count = 1; // self
size_t i;
for (i = 0; i < self->num_kids; i++) {
count += S_family_tree_size(self->children[i]);
}
return count;
}
static void
S_create_dumpables(CFCClass *self) {
if (CFCClass_has_attribute(self, "dumpable")) {
CFCDumpable *dumpable = CFCDumpable_new();
CFCDumpable_add_dumpables(dumpable, self);
CFCBase_decref((CFCBase*)dumpable);
}
}
void
CFCClass_grow_tree(CFCClass *self) {
if (self->tree_grown) {
croak("Can't call grow_tree more than once");
}
S_establish_ancestry(self);
S_bequeath_member_vars(self);
S_generate_automethods(self);
S_bequeath_methods(self);
self->tree_grown = 1;
}
static void
S_generate_automethods(CFCClass *self) {
S_create_dumpables(self);
size_t i;
for (i = 0; i < self->num_kids; i++) {
S_generate_automethods(self->children[i]);
}
}
// Return value is valid only so long as object persists (elements are not
// refcounted).
CFCClass**
CFCClass_tree_to_ladder(CFCClass *self) {
size_t ladder_len = S_family_tree_size(self);
CFCClass **ladder = (CFCClass**)MALLOCATE((ladder_len + 1) * sizeof(CFCClass*));
ladder[ladder_len] = NULL;
size_t step = 0;
ladder[step++] = self;
size_t i;
for (i = 0; i < self->num_kids; i++) {
CFCClass *child = self->children[i];
CFCClass **child_ladder = CFCClass_tree_to_ladder(child);
size_t j;
for (j = 0; child_ladder[j] != NULL; j++) {
ladder[step++] = child_ladder[j];
}
FREEMEM(child_ladder);
}
return ladder;
}
static CFCSymbol**
S_novel_syms(CFCClass *self, CFCSymbol **syms) {
const char *cnick = CFCSymbol_get_class_cnick((CFCSymbol*)self);
size_t count = 0;
while (syms[count] != NULL) { count++; }
size_t amount = (count + 1) * sizeof(CFCSymbol*);
CFCSymbol **novel = (CFCSymbol**)MALLOCATE(amount);
size_t num_novel = 0;
size_t i;
for (i = 0; i < count; i++) {
CFCSymbol *sym = syms[i];
const char *sym_cnick = CFCSymbol_get_class_cnick((CFCSymbol*)sym);
if (strcmp(sym_cnick, cnick) == 0) {
novel[num_novel++] = sym;
}
}
novel[num_novel] = NULL;
return novel;
}
CFCMethod**
CFCClass_novel_methods(CFCClass *self) {
return (CFCMethod**)S_novel_syms(self, (CFCSymbol**)self->methods);
}
CFCVariable**
CFCClass_novel_member_vars(CFCClass *self) {
return (CFCVariable**)S_novel_syms(self, (CFCSymbol**)self->member_vars);
}
CFCClass**
CFCClass_children(CFCClass *self) {
return self->children;
}
CFCFunction**
CFCClass_functions(CFCClass *self) {
return self->functions;
}
CFCMethod**
CFCClass_methods(CFCClass *self) {
return self->methods;
}
CFCVariable**
CFCClass_member_vars(CFCClass *self) {
return self->member_vars;
}
CFCVariable**
CFCClass_inert_vars(CFCClass *self) {
return self->inert_vars;
}
const char*
CFCClass_get_cnick(CFCClass *self) {
return CFCSymbol_get_class_cnick((CFCSymbol*)self);
}
void
CFCClass_set_parent(CFCClass *self, CFCClass *parent) {
CFCBase_decref((CFCBase*)self->parent);
self->parent = (CFCClass*)CFCBase_incref((CFCBase*)parent);
}
CFCClass*
CFCClass_get_parent(CFCClass *self) {
return self->parent;
}
void
CFCClass_append_autocode(CFCClass *self, const char *autocode) {
size_t size = strlen(self->autocode) + strlen(autocode) + 1;
self->autocode = (char*)REALLOCATE(self->autocode, size);
strcat(self->autocode, autocode);
}
const char*
CFCClass_get_autocode(CFCClass *self) {
return self->autocode;
}
const char*
CFCClass_get_source_class(CFCClass *self) {
return self->source_class;
}
const char*
CFCClass_get_parent_class_name(CFCClass *self) {
return self->parent_class_name;
}
int
CFCClass_final(CFCClass *self) {
return self->is_final;
}
int
CFCClass_inert(CFCClass *self) {
return self->is_inert;
}
const char*
CFCClass_get_struct_sym(CFCClass *self) {
return self->struct_sym;
}
const char*
CFCClass_full_struct_sym(CFCClass *self) {
return self->full_struct_sym;
}
const char*
CFCClass_short_vtable_var(CFCClass *self) {
return self->short_vtable_var;
}
const char*
CFCClass_full_vtable_var(CFCClass *self) {
return self->full_vtable_var;
}
const char*
CFCClass_full_vtable_type(CFCClass *self) {
return self->full_vtable_type;
}
const char*
CFCClass_include_h(CFCClass *self) {
return self->include_h;
}
struct CFCDocuComment*
CFCClass_get_docucomment(CFCClass *self) {
return self->docucomment;
}