| /* 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 <ctype.h> |
| #include <stdio.h> |
| |
| #define CFC_NEED_FUNCTION_STRUCT_DEF |
| #include "CFCFunction.h" |
| #include "CFCMethod.h" |
| #include "CFCType.h" |
| #include "CFCUtil.h" |
| #include "CFCParamList.h" |
| #include "CFCParcel.h" |
| #include "CFCDocuComment.h" |
| #include "CFCVariable.h" |
| |
| #ifndef true |
| #define true 1 |
| #define false 0 |
| #endif |
| |
| struct CFCMethod { |
| CFCFunction function; |
| char *macro_sym; |
| char *short_typedef; |
| char *full_typedef; |
| char *full_callback_sym; |
| char *full_override_sym; |
| int is_final; |
| int is_abstract; |
| int is_novel; |
| }; |
| |
| static void |
| S_update_typedefs(CFCMethod *self, const char *short_sym); |
| |
| const static CFCMeta CFCMETHOD_META = { |
| "Clownfish::CFC::Method", |
| sizeof(CFCMethod), |
| (CFCBase_destroy_t)CFCMethod_destroy |
| }; |
| |
| CFCMethod* |
| CFCMethod_new(CFCParcel *parcel, const char *exposure, const char *class_name, |
| const char *class_cnick, const char *macro_sym, |
| CFCType *return_type, CFCParamList *param_list, |
| CFCDocuComment *docucomment, int is_final, int is_abstract) { |
| CFCMethod *self = (CFCMethod*)CFCBase_allocate(&CFCMETHOD_META); |
| return CFCMethod_init(self, parcel, exposure, class_name, class_cnick, |
| macro_sym, return_type, param_list, docucomment, |
| is_final, is_abstract); |
| } |
| |
| static int |
| S_validate_macro_sym(const char *macro_sym) { |
| if (!macro_sym || !strlen(macro_sym)) { return false; } |
| |
| int need_upper = true; |
| int need_letter = true; |
| for (;; macro_sym++) { |
| if (need_upper && !isupper(*macro_sym)) { return false; } |
| if (need_letter && !isalpha(*macro_sym)) { return false; } |
| need_upper = false; |
| need_letter = false; |
| |
| // We've reached NULL-termination without problems, so succeed. |
| if (!*macro_sym) { return true; } |
| |
| if (!isalnum(*macro_sym)) { |
| if (*macro_sym != '_') { return false; } |
| need_upper = true; |
| } |
| } |
| } |
| |
| CFCMethod* |
| CFCMethod_init(CFCMethod *self, CFCParcel *parcel, const char *exposure, |
| const char *class_name, const char *class_cnick, |
| const char *macro_sym, CFCType *return_type, |
| CFCParamList *param_list, CFCDocuComment *docucomment, |
| int is_final, int is_abstract) { |
| // Validate macro_sym, derive micro_sym. |
| if (!S_validate_macro_sym(macro_sym)) { |
| CFCBase_decref((CFCBase*)self); |
| CFCUtil_die("Invalid macro_sym: '%s'", |
| macro_sym ? macro_sym : "[NULL]"); |
| } |
| char *micro_sym = CFCUtil_strdup(macro_sym); |
| size_t i; |
| for (i = 0; micro_sym[i] != '\0'; i++) { |
| micro_sym[i] = tolower(micro_sym[i]); |
| } |
| |
| // Super-init and clean up derived micro_sym. |
| CFCFunction_init((CFCFunction*)self, parcel, exposure, class_name, |
| class_cnick, micro_sym, return_type, param_list, |
| docucomment, false); |
| FREEMEM(micro_sym); |
| |
| // 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 *prefix = CFCMethod_get_prefix(self); |
| const char *last_colon = strrchr(class_name, ':'); |
| const char *struct_sym = last_colon ? last_colon + 1 : class_name; |
| char *wanted = (char*)MALLOCATE(strlen(prefix) + strlen(struct_sym) + 1); |
| sprintf(wanted, "%s%s", prefix, struct_sym); |
| int mismatch = strcmp(wanted, specifier); |
| FREEMEM(wanted); |
| if (mismatch) { |
| CFCUtil_die("First arg type doesn't match class: '%s' '%s", |
| class_name, specifier); |
| } |
| |
| self->macro_sym = CFCUtil_strdup(macro_sym); |
| self->short_typedef = NULL; |
| self->full_typedef = NULL; |
| self->is_final = is_final; |
| self->is_abstract = is_abstract; |
| |
| // Derive more symbols. |
| const char *full_func_sym = CFCMethod_implementing_func_sym(self); |
| size_t amount = strlen(full_func_sym) + sizeof("_OVERRIDE") + 1; |
| self->full_callback_sym = (char*)MALLOCATE(amount); |
| self->full_override_sym = (char*)MALLOCATE(amount); |
| sprintf(self->full_callback_sym, "%s_CALLBACK", full_func_sym); |
| sprintf(self->full_override_sym, "%s_OVERRIDE", full_func_sym); |
| |
| // Assume that this method is novel until we discover when applying |
| // inheritance that it was overridden. |
| self->is_novel = 1; |
| |
| // Cache typedefs. |
| S_update_typedefs(self, CFCSymbol_short_sym((CFCSymbol*)self)); |
| |
| return self; |
| } |
| |
| void |
| CFCMethod_destroy(CFCMethod *self) { |
| FREEMEM(self->macro_sym); |
| FREEMEM(self->short_typedef); |
| FREEMEM(self->full_typedef); |
| FREEMEM(self->full_callback_sym); |
| FREEMEM(self->full_override_sym); |
| CFCFunction_destroy((CFCFunction*)self); |
| } |
| |
| int |
| CFCMethod_compatible(CFCMethod *self, CFCMethod *other) { |
| if (!other) { return false; } |
| if (strcmp(self->macro_sym, other->macro_sym)) { 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->function.param_list; |
| CFCParamList *other_param_list = other->function.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); |
| size_t i; |
| for (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]) { |
| if (!CFCVariable_equals(my_args[i], other_args[i])) { |
| 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_class = CFCMethod_get_class_name(orig); |
| const char *my_class = CFCMethod_get_class_name(self); |
| CFCUtil_die("Attempt to override final method '%s' from '%s' by '%s'", |
| orig->macro_sym, orig_class, my_class); |
| } |
| if (!CFCMethod_compatible(self, orig)) { |
| const char *func = CFCMethod_implementing_func_sym(self); |
| const char *orig_func = CFCMethod_implementing_func_sym(orig); |
| CFCUtil_die("Non-matching signatures for %s and %s", func, orig_func); |
| } |
| |
| // Mark the Method as no longer novel. |
| self->is_novel = false; |
| } |
| |
| CFCMethod* |
| CFCMethod_finalize(CFCMethod *self) { |
| CFCParcel *parcel = CFCMethod_get_parcel(self); |
| const char *exposure = CFCMethod_get_exposure(self); |
| const char *class_name = CFCMethod_get_class_name(self); |
| const char *class_cnick = CFCMethod_get_class_cnick(self); |
| CFCMethod *finalized |
| = CFCMethod_new(parcel, exposure, class_name, class_cnick, |
| self->macro_sym, self->function.return_type, |
| self->function.param_list, |
| self->function.docucomment, true, |
| self->is_abstract); |
| finalized->is_novel = self->is_final; // Is this right? |
| S_update_typedefs(finalized, CFCSymbol_short_sym((CFCSymbol*)self)); |
| return finalized; |
| } |
| |
| size_t |
| CFCMethod_short_method_sym(CFCMethod *self, const char *invoker, char *buf, |
| size_t buf_size) { |
| CFCUTIL_NULL_CHECK(invoker); |
| size_t needed = strlen(invoker) + 1 + strlen(self->macro_sym) + 1; |
| if (buf_size >= needed) { |
| sprintf(buf, "%s_%s", invoker, self->macro_sym); |
| } |
| return needed; |
| } |
| |
| size_t |
| CFCMethod_full_method_sym(CFCMethod *self, const char *invoker, char *buf, |
| size_t buf_size) { |
| CFCUTIL_NULL_CHECK(invoker); |
| const char *Prefix = CFCMethod_get_Prefix(self); |
| size_t needed = strlen(Prefix) |
| + strlen(invoker) |
| + 1 |
| + strlen(self->macro_sym) |
| + 1; |
| if (buf_size >= needed) { |
| sprintf(buf, "%s%s_%s", Prefix, invoker, self->macro_sym); |
| } |
| return needed; |
| } |
| |
| size_t |
| CFCMethod_full_offset_sym(CFCMethod *self, const char *invoker, char *buf, |
| size_t buf_size) { |
| CFCUTIL_NULL_CHECK(invoker); |
| size_t needed = CFCMethod_full_method_sym(self, invoker, NULL, 0) |
| + strlen("_OFFSET"); |
| if (buf_size >= needed) { |
| CFCMethod_full_method_sym(self, invoker, buf, buf_size); |
| strcat(buf, "_OFFSET"); |
| } |
| return needed; |
| } |
| |
| const char* |
| CFCMethod_get_macro_sym(CFCMethod *self) { |
| return self->macro_sym; |
| } |
| |
| const char* |
| CFCMethod_micro_sym(CFCMethod *self) { |
| return CFCSymbol_micro_sym((CFCSymbol*)self); |
| } |
| |
| static void |
| S_update_typedefs(CFCMethod *self, const char *short_sym) { |
| FREEMEM(self->short_typedef); |
| FREEMEM(self->full_typedef); |
| if (short_sym) { |
| const char *prefix = CFCMethod_get_prefix(self); |
| size_t amount = strlen(short_sym) + 3; |
| self->short_typedef = (char*)MALLOCATE(amount); |
| sprintf(self->short_typedef, "%s_t", short_sym); |
| amount += strlen(prefix); |
| self->full_typedef = (char*)MALLOCATE(amount); |
| sprintf(self->full_typedef, "%s%s_t", prefix, short_sym); |
| } |
| else { |
| self->short_typedef = NULL; |
| self->full_typedef = NULL; |
| } |
| } |
| |
| const char* |
| CFCMethod_short_typedef(CFCMethod *self) { |
| return self->short_typedef; |
| } |
| |
| const char* |
| CFCMethod_full_typedef(CFCMethod *self) { |
| return self->full_typedef; |
| } |
| |
| const char* |
| CFCMethod_full_callback_sym(CFCMethod *self) { |
| return self->full_callback_sym; |
| } |
| |
| const char* |
| CFCMethod_full_override_sym(CFCMethod *self) { |
| return self->full_override_sym; |
| } |
| |
| 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->function.param_list); |
| return CFCVariable_get_type(vars[0]); |
| } |
| |
| CFCParcel* |
| CFCMethod_get_parcel(CFCMethod *self) { |
| return CFCSymbol_get_parcel((CFCSymbol*)self); |
| } |
| |
| const char* |
| CFCMethod_get_prefix(CFCMethod *self) { |
| return CFCSymbol_get_prefix((CFCSymbol*)self); |
| } |
| |
| const char* |
| CFCMethod_get_Prefix(CFCMethod *self) { |
| return CFCSymbol_get_Prefix((CFCSymbol*)self); |
| } |
| |
| const char* |
| CFCMethod_get_exposure(CFCMethod *self) { |
| return CFCSymbol_get_exposure((CFCSymbol*)self); |
| } |
| |
| const char* |
| CFCMethod_get_class_name(CFCMethod *self) { |
| return CFCSymbol_get_class_name((CFCSymbol*)self); |
| } |
| |
| const char* |
| CFCMethod_get_class_cnick(CFCMethod *self) { |
| return CFCSymbol_get_class_cnick((CFCSymbol*)self); |
| } |
| |
| int |
| CFCMethod_public(CFCMethod *self) { |
| return CFCSymbol_public((CFCSymbol*)self); |
| } |
| |
| CFCType* |
| CFCMethod_get_return_type(CFCMethod *self) { |
| return CFCFunction_get_return_type((CFCFunction*)self); |
| } |
| |
| CFCParamList* |
| CFCMethod_get_param_list(CFCMethod *self) { |
| return CFCFunction_get_param_list((CFCFunction*)self); |
| } |
| |
| const char* |
| CFCMethod_implementing_func_sym(CFCMethod *self) { |
| return CFCFunction_full_func_sym((CFCFunction*)self); |
| } |
| |
| const char* |
| CFCMethod_short_implementing_func_sym(CFCMethod *self) { |
| return CFCFunction_short_func_sym((CFCFunction*)self); |
| } |
| |