blob: 1d449e43d65fc5337f87425cca722acd45946810 [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 <stdio.h>
#define CFC_NEED_CALLABLE_STRUCT_DEF
#include "CFCCallable.h"
#include "CFCMethod.h"
#include "CFCType.h"
#include "CFCClass.h"
#include "CFCUtil.h"
#include "CFCParamList.h"
#include "CFCDocuComment.h"
#include "CFCVariable.h"
#include "CFCJson.h"
#ifndef true
#define true 1
#define false 0
#endif
struct CFCMethod {
CFCCallable callable;
CFCMethod *novel_method;
char *fresh_class_name;
char *host_alias;
int is_final;
int is_abstract;
int is_novel;
int is_excluded;
};
static const CFCMeta CFCMETHOD_META = {
"Clownfish::CFC::Model::Method",
sizeof(CFCMethod),
(CFCBase_destroy_t)CFCMethod_destroy
};
CFCMethod*
CFCMethod_new(const char *exposure, const char *name, CFCType *return_type,
CFCParamList *param_list, CFCDocuComment *docucomment,
const char *class_name, int is_final, int is_abstract) {
CFCMethod *self = (CFCMethod*)CFCBase_allocate(&CFCMETHOD_META);
return CFCMethod_init(self, exposure, name, return_type, param_list,
docucomment, class_name, is_final, is_abstract);
}
static int
S_validate_meth_name(const char *meth_name) {
if (!meth_name || !strlen(meth_name)) { return false; }
int need_upper = true;
int need_letter = true;
for (;; meth_name++) {
if (need_upper && !CFCUtil_isupper(*meth_name)) { return false; }
if (need_letter && !CFCUtil_isalpha(*meth_name)) { return false; }
need_upper = false;
need_letter = false;
// We've reached NULL-termination without problems, so succeed.
if (!*meth_name) { return true; }
if (!CFCUtil_isalnum(*meth_name)) {
if (*meth_name != '_') { return false; }
need_upper = true;
}
}
}
CFCMethod*
CFCMethod_init(CFCMethod *self, const char *exposure, const char *name,
CFCType *return_type, CFCParamList *param_list,
CFCDocuComment *docucomment, const char *class_name,
int is_final, int is_abstract) {
// Validate class_name.
CFCUTIL_NULL_CHECK(class_name);
if (!CFCClass_validate_class_name(class_name)) {
CFCBase_decref((CFCBase*)self);
CFCUtil_die("Invalid class_name: '%s'", class_name);
}
// Validate name.
if (!S_validate_meth_name(name)) {
CFCBase_decref((CFCBase*)self);
CFCUtil_die("Invalid name: '%s'",
name ? name : "[NULL]");
}
// Super-init.
CFCCallable_init((CFCCallable*)self, exposure, name, return_type,
param_list, docucomment);
// Verify that the first element in the arg list is a self.
CFCVariable **args = CFCParamList_get_variables(param_list);
if (!args[0]) { CFCUtil_die("Missing 'self' argument"); }
CFCType *type = CFCVariable_get_type(args[0]);
const char *specifier = CFCType_get_specifier(type);
const char *last_colon = strrchr(class_name, ':');
const char *struct_sym = last_colon ? last_colon + 1 : class_name;
if (strcmp(specifier, struct_sym) != 0) {
const char *first_underscore = strchr(specifier, '_');
int mismatch = !first_underscore
|| strcmp(first_underscore + 1, struct_sym) != 0;
if (mismatch) {
CFCUtil_die("First arg type doesn't match class: '%s' '%s'",
class_name, specifier);
}
}
self->novel_method = NULL;
self->fresh_class_name = CFCUtil_strdup(class_name);
self->host_alias = NULL;
self->is_final = is_final;
self->is_abstract = is_abstract;
self->is_excluded = false;
// Assume that this method is novel until we discover when applying
// inheritance that it overrides another.
self->is_novel = true;
return self;
}
void
CFCMethod_resolve_types(CFCMethod *self) {
CFCCallable_resolve_types((CFCCallable*)self);
}
void
CFCMethod_destroy(CFCMethod *self) {
CFCBase_decref((CFCBase*)self->novel_method);
FREEMEM(self->fresh_class_name);
FREEMEM(self->host_alias);
CFCCallable_destroy((CFCCallable*)self);
}
int
CFCMethod_compatible(CFCMethod *self, CFCMethod *other) {
if (!other) { return false; }
const char *name = CFCMethod_get_name(self);
const char *other_name = CFCMethod_get_name(other);
if (strcmp(name, other_name)) { return false; }
int my_public = CFCMethod_public(self);
int other_public = CFCMethod_public(other);
if (!!my_public != !!other_public) { return false; }
// Check arguments and initial values.
CFCParamList *my_param_list = self->callable.param_list;
CFCParamList *other_param_list = other->callable.param_list;
CFCVariable **my_args = CFCParamList_get_variables(my_param_list);
CFCVariable **other_args = CFCParamList_get_variables(other_param_list);
const char **my_vals = CFCParamList_get_initial_values(my_param_list);
const char **other_vals = CFCParamList_get_initial_values(other_param_list);
for (size_t i = 1; ; i++) { // start at 1, skipping self
if (!!my_args[i] != !!other_args[i]) { return false; }
if (!!my_vals[i] != !!other_vals[i]) { return false; }
if (my_vals[i]) {
if (strcmp(my_vals[i], other_vals[i])) { return false; }
}
if (my_args[i]) {
CFCType *my_type = CFCVariable_get_type(my_args[i]);
CFCType *other_type = CFCVariable_get_type(other_args[i]);
if (!CFCType_equals(my_type, other_type)) {
return false;
}
const char *my_sym = CFCVariable_get_name(my_args[i]);
const char *other_sym = CFCVariable_get_name(other_args[i]);
if (strcmp(my_sym, other_sym) != 0) {
return false;
}
}
else {
break;
}
}
// Check return types.
CFCType *type = CFCMethod_get_return_type(self);
CFCType *other_type = CFCMethod_get_return_type(other);
if (CFCType_is_object(type)) {
// Weak validation to allow covariant object return types.
if (!CFCType_is_object(other_type)) { return false; }
if (!CFCType_similar(type, other_type)) { return false; }
}
else {
if (!CFCType_equals(type, other_type)) { return false; }
}
return true;
}
void
CFCMethod_override(CFCMethod *self, CFCMethod *orig) {
// Check that the override attempt is legal.
if (CFCMethod_final(orig)) {
const char *orig_name = CFCMethod_get_name(orig);
CFCUtil_die("Attempt to override final method '%s' from '%s' by '%s'",
orig_name, orig->fresh_class_name, self->fresh_class_name);
}
if (!CFCMethod_compatible(self, orig)) {
const char *orig_name = CFCMethod_get_name(orig);
CFCUtil_die("Non-matching signatures for method '%s' in '%s' and '%s'",
orig_name, orig->fresh_class_name, self->fresh_class_name);
}
// Mark the Method as no longer novel.
self->is_novel = false;
// Cache novel method.
CFCMethod *novel_method = orig->is_novel ? orig : orig->novel_method;
self->novel_method = (CFCMethod*)CFCBase_incref((CFCBase*)novel_method);
}
CFCMethod*
CFCMethod_finalize(CFCMethod *self) {
const char *exposure = CFCMethod_get_exposure(self);
const char *name = CFCMethod_get_name(self);
CFCMethod *finalized
= CFCMethod_new(exposure, name,
self->callable.return_type,
self->callable.param_list,
self->callable.docucomment,
self->fresh_class_name, true, self->is_abstract);
finalized->novel_method
= (CFCMethod*)CFCBase_incref((CFCBase*)self->novel_method);
finalized->is_novel = self->is_novel;
return finalized;
}
int
CFCMethod_can_be_bound(CFCMethod *method) {
/*
* Check for
* - private methods
* - methods with types which cannot be mapped automatically
*/
return !CFCSymbol_private((CFCSymbol*)method)
&& CFCCallable_can_be_bound((CFCCallable*)method);
}
void
CFCMethod_read_host_data_json(CFCMethod *self, CFCJson *hash,
const char *path) {
int excluded = false;
const char *alias = NULL;
CFCJson **children = CFCJson_get_children(hash);
for (int i = 0; children[i]; i += 2) {
const char *key = CFCJson_get_string(children[i]);
if (strcmp(key, "excluded") == 0) {
excluded = CFCJson_get_bool(children[i+1]);
}
else if (strcmp(key, "alias") == 0) {
alias = CFCJson_get_string(children[i+1]);
}
else {
CFCUtil_die("Unexpected key '%s' in '%s'", key, path);
}
}
if (excluded) {
CFCMethod_exclude_from_host(self);
}
else if (alias) {
CFCMethod_set_host_alias(self, alias);
}
}
void
CFCMethod_set_host_alias(CFCMethod *self, const char *alias) {
if (!alias || !alias[0]) {
CFCUtil_die("Missing required param 'alias'");
}
if (!self->is_novel) {
const char *name = CFCMethod_get_name(self);
CFCUtil_die("Can't set_host_alias %s -- method %s not novel in %s",
alias, name, self->fresh_class_name);
}
if (self->host_alias) {
const char *name = CFCMethod_get_name(self);
if (strcmp(self->host_alias, alias) == 0) { return; }
CFCUtil_die("Can't set_host_alias %s -- already set to %s for method"
" %s in %s", alias, self->host_alias, name,
self->fresh_class_name);
}
self->host_alias = CFCUtil_strdup(alias);
}
const char*
CFCMethod_get_host_alias(CFCMethod *self) {
CFCMethod *novel_method = CFCMethod_find_novel_method(self);
return novel_method->host_alias;
}
void
CFCMethod_exclude_from_host(CFCMethod *self) {
if (!self->is_novel) {
const char *name = CFCMethod_get_name(self);
CFCUtil_die("Can't exclude_from_host -- method %s not novel in %s",
name, self->fresh_class_name);
}
self->is_excluded = true;
}
int
CFCMethod_excluded_from_host(CFCMethod *self) {
CFCMethod *novel_method = CFCMethod_find_novel_method(self);
return novel_method->is_excluded;
}
CFCMethod*
CFCMethod_find_novel_method(CFCMethod *self) {
if (self->is_novel) {
return self;
}
else {
return self->novel_method;
}
}
static char*
S_short_method_sym(CFCMethod *self, CFCClass *invoker, const char *postfix) {
const char *nickname = CFCClass_get_nickname(invoker);
const char *name = CFCMethod_get_name(self);
return CFCUtil_sprintf("%s_%s%s", nickname, name, postfix);
}
static char*
S_full_method_sym(CFCMethod *self, CFCClass *invoker, const char *postfix) {
const char *PREFIX = CFCClass_get_PREFIX(invoker);
const char *nickname = CFCClass_get_nickname(invoker);
const char *name = CFCMethod_get_name(self);
return CFCUtil_sprintf("%s%s_%s%s", PREFIX, nickname, name, postfix);
}
char*
CFCMethod_short_method_sym(CFCMethod *self, CFCClass *invoker) {
return S_short_method_sym(self, invoker, "");
}
char*
CFCMethod_full_method_sym(CFCMethod *self, CFCClass *invoker) {
return S_full_method_sym(self, invoker, "");
}
char*
CFCMethod_full_offset_sym(CFCMethod *self, CFCClass *invoker) {
return S_full_method_sym(self, invoker, "_OFFSET");
}
const char*
CFCMethod_get_name(CFCMethod *self) {
return CFCSymbol_get_name((CFCSymbol*)self);
}
char*
CFCMethod_short_typedef(CFCMethod *self, CFCClass *invoker) {
return S_short_method_sym(self, invoker, "_t");
}
char*
CFCMethod_full_typedef(CFCMethod *self, CFCClass *invoker) {
return S_full_method_sym(self, invoker, "_t");
}
char*
CFCMethod_full_override_sym(CFCMethod *self, CFCClass *klass) {
const char *Prefix = CFCClass_get_Prefix(klass);
const char *nickname = CFCClass_get_nickname(klass);
const char *name = CFCMethod_get_name(self);
return CFCUtil_sprintf("%s%s_%s_OVERRIDE", Prefix, nickname, name);
}
int
CFCMethod_final(CFCMethod *self) {
return self->is_final;
}
int
CFCMethod_abstract(CFCMethod *self) {
return self->is_abstract;
}
int
CFCMethod_novel(CFCMethod *self) {
return self->is_novel;
}
CFCType*
CFCMethod_self_type(CFCMethod *self) {
CFCVariable **vars = CFCParamList_get_variables(self->callable.param_list);
return CFCVariable_get_type(vars[0]);
}
const char*
CFCMethod_get_exposure(CFCMethod *self) {
return CFCSymbol_get_exposure((CFCSymbol*)self);
}
int
CFCMethod_is_fresh(CFCMethod *self, CFCClass *klass) {
const char *class_name = CFCClass_get_name(klass);
return strcmp(self->fresh_class_name, class_name) == 0;
}
int
CFCMethod_public(CFCMethod *self) {
return CFCSymbol_public((CFCSymbol*)self);
}
CFCType*
CFCMethod_get_return_type(CFCMethod *self) {
return self->callable.return_type;
}
CFCParamList*
CFCMethod_get_param_list(CFCMethod *self) {
return self->callable.param_list;
}
char*
CFCMethod_imp_func(CFCMethod *self, CFCClass *klass) {
CFCClass *ancestor = klass;
while (ancestor) {
if (CFCMethod_is_fresh(self, ancestor)) { break; }
ancestor = CFCClass_get_parent(ancestor);
}
if (!ancestor) {
CFCUtil_die("No fresh method implementation found for '%s' in '%s'",
CFCMethod_get_name(self), CFCClass_get_name(klass));
}
return S_full_method_sym(self, ancestor, "_IMP");
}
char*
CFCMethod_short_imp_func(CFCMethod *self, CFCClass *klass) {
return S_short_method_sym(self, klass, "_IMP");
}