diff options
Diffstat (limited to 'src/vim9class.c')
-rw-r--r-- | src/vim9class.c | 417 |
1 files changed, 350 insertions, 67 deletions
diff --git a/src/vim9class.c b/src/vim9class.c index e5d9aeb..7227c3d 100644 --- a/src/vim9class.c +++ b/src/vim9class.c @@ -208,7 +208,7 @@ add_member( * "parent_count" is the number of members in the parent class * "members" will be set to the newly allocated array of members and * "member_count" set to the number of members. - * Returns OK or FAIL. + * Returns OK on success and FAIL on memory allocation failure. */ static int add_members_to_class( @@ -301,6 +301,7 @@ object_index_from_itf_index(class_T *itf, int is_method, int idx, class_T *cl) */ static int validate_extends_class( + class_T *cl, char_u *extends_name, class_T **extends_clp, int is_class) @@ -308,6 +309,12 @@ validate_extends_class( typval_T tv; int success = FALSE; + if (STRCMP(cl->class_name, extends_name) == 0) + { + semsg(_(e_cannot_extend_str), extends_name); + return success; + } + tv.v_type = VAR_UNKNOWN; if (eval_variable_import(extends_name, &tv) == FAIL) { @@ -974,6 +981,100 @@ is_valid_constructor(ufunc_T *uf, int is_abstract, int has_static) } /* + * Returns TRUE if 'uf' is a supported builtin method and has the correct + * method signature. + */ + static int +object_check_builtin_method_sig(ufunc_T *uf) +{ + char_u *name = uf->uf_name; + int valid = FALSE; + type_T method_sig; + type_T method_rt; + where_T where = WHERE_INIT; + + // validate the method signature + CLEAR_FIELD(method_sig); + CLEAR_FIELD(method_rt); + method_sig.tt_type = VAR_FUNC; + + if (STRCMP(name, "len") == 0) + { + // def __len(): number + method_rt.tt_type = VAR_NUMBER; + method_sig.tt_member = &method_rt; + valid = TRUE; + } + else if (STRCMP(name, "empty") == 0) + { + // def __empty(): bool + method_rt.tt_type = VAR_BOOL; + method_sig.tt_member = &method_rt; + valid = TRUE; + } + else if (STRCMP(name, "string") == 0) + { + // def __string(): string + method_rt.tt_type = VAR_STRING; + method_sig.tt_member = &method_rt; + valid = TRUE; + } + else + semsg(_(e_builtin_object_method_str_not_supported), uf->uf_name); + + where.wt_func_name = (char *)uf->uf_name; + where.wt_kind = WT_METHOD; + if (valid && !check_type(&method_sig, uf->uf_func_type, TRUE, where)) + valid = FALSE; + + return valid; +} + +/* + * Returns TRUE if "funcname" is a supported builtin object method name + */ + int +is_valid_builtin_obj_methodname(char_u *funcname) +{ + switch (funcname[0]) + { + case 'e': + return STRNCMP(funcname, "empty", 5) == 0; + + case 'l': + return STRNCMP(funcname, "len", 3) == 0; + + case 'n': + return STRNCMP(funcname, "new", 3) == 0; + + case 's': + return STRNCMP(funcname, "string", 6) == 0; + } + + return FALSE; +} + + +/* + * Returns the builtin method "name" in object "obj". Returns NULL if the + * method is not found. + */ + ufunc_T * +class_get_builtin_method( + class_T *cl, + class_builtin_T builtin_method, + int *method_idx) +{ + *method_idx = -1; + + if (cl == NULL) + return NULL; + + *method_idx = cl->class_builtin_methods[builtin_method]; + return *method_idx != -1 ? cl->class_obj_methods[*method_idx] : NULL; +} + +/* * Update the interface class lookup table for the member index on the * interface to the member index in the class implementing the interface. * And a lookup table for the object method index on the interface @@ -1327,6 +1428,33 @@ add_classfuncs_objmethods( } /* + * Update the index of object methods called by builtin functions. + */ + static void +update_builtin_method_index(class_T *cl) +{ + int i; + + for (i = 0; i < CLASS_BUILTIN_MAX; i++) + cl->class_builtin_methods[i] = -1; + + for (i = 0; i < cl->class_obj_method_count; i++) + { + ufunc_T *uf = cl->class_obj_methods[i]; + + if (cl->class_builtin_methods[CLASS_BUILTIN_STRING] == -1 + && STRCMP(uf->uf_name, "string") == 0) + cl->class_builtin_methods[CLASS_BUILTIN_STRING] = i; + else if (cl->class_builtin_methods[CLASS_BUILTIN_EMPTY] == -1 && + STRCMP(uf->uf_name, "empty") == 0) + cl->class_builtin_methods[CLASS_BUILTIN_EMPTY] = i; + else if (cl->class_builtin_methods[CLASS_BUILTIN_LEN] == -1 && + STRCMP(uf->uf_name, "len") == 0) + cl->class_builtin_methods[CLASS_BUILTIN_LEN] = i; + } +} + +/* * Return the end of the class name starting at "arg". Valid characters in a * class name are alphanumeric characters and "_". Also handles imported class * names. @@ -1521,6 +1649,36 @@ early_ret: garray_T objmethods; ga_init2(&objmethods, sizeof(ufunc_T *), 10); + class_T *cl = NULL; + class_T *extends_cl = NULL; // class from "extends" argument + class_T **intf_classes = NULL; + + cl = ALLOC_CLEAR_ONE(class_T); + if (cl == NULL) + goto cleanup; + if (!is_class) + cl->class_flags = CLASS_INTERFACE; + else if (is_abstract) + cl->class_flags = CLASS_ABSTRACT; + + cl->class_refcount = 1; + cl->class_name = vim_strnsave(name_start, name_end - name_start); + if (cl->class_name == NULL) + goto cleanup; + + // Add the class to the script-local variables. + // TODO: handle other context, e.g. in a function + // TODO: does uf_hash need to be cleared? + typval_T tv; + tv.v_type = VAR_CLASS; + tv.vval.v_class = cl; + is_export = class_export; + SOURCING_LNUM = start_lnum; + int rc = set_var_const(cl->class_name, current_sctx.sc_sid, + NULL, &tv, FALSE, 0, 0); + if (rc == FAIL) + goto cleanup; + /* * Go over the body of the class/interface until "endclass" or * "endinterface" is found. @@ -1721,13 +1879,10 @@ early_ret: &varname_end, &has_type, &type_list, &type, is_class ? &init_expr: NULL) == FAIL) break; - if (is_reserved_varname(varname, varname_end)) - { - vim_free(init_expr); - break; - } - if (is_duplicate_variable(&classmembers, &objmembers, varname, - varname_end)) + + if (is_reserved_varname(varname, varname_end) + || is_duplicate_variable(&classmembers, &objmembers, + varname, varname_end)) { vim_free(init_expr); break; @@ -1758,6 +1913,7 @@ early_ret: { exarg_T ea; garray_T lines_to_free; + int is_new = STRNCMP(p, "new", 3) == 0; if (has_public) { @@ -1774,12 +1930,17 @@ early_ret: break; } - if (*p == '_' && *(p + 1) == '_') + if (!is_class && *p == '_') { - // double underscore prefix for a method name is currently - // reserved. This could be used in the future to support - // object methods called by Vim builtin functions. - semsg(_(e_cannot_use_reserved_name_str), p); + // private methods are not supported in an interface + semsg(_(e_protected_method_not_supported_in_interface), p); + break; + } + + if (has_static && !is_new && SAFE_islower(*p) && + is_valid_builtin_obj_methodname(p)) + { + semsg(_(e_builtin_class_method_not_supported), p); break; } @@ -1803,9 +1964,9 @@ early_ret: if (uf != NULL) { char_u *name = uf->uf_name; - int is_new = STRNCMP(name, "new", 3) == 0; - if (!is_class && *name == '_') + if (is_new && !is_valid_constructor(uf, is_abstract, + has_static)) { // private variables are not supported in an interface semsg(_(e_protected_method_not_supported_in_interface), @@ -1813,8 +1974,10 @@ early_ret: func_clear_free(uf, FALSE); break; } - if (is_new && !is_valid_constructor(uf, is_abstract, - has_static)) + + // check for builtin method + if (!is_new && SAFE_islower(*name) && + !object_check_builtin_method_sig(uf)) { func_clear_free(uf, FALSE); break; @@ -1855,15 +2018,13 @@ early_ret: } vim_free(theline); - class_T *extends_cl = NULL; // class from "extends" argument - /* * Check a few things before defining the class. */ // Check the "extends" class is valid. if (success && extends != NULL) - success = validate_extends_class(extends, &extends_cl, is_class); + success = validate_extends_class(cl, extends, &extends_cl, is_class); VIM_CLEAR(extends); // Check the new object methods to make sure their access (public or @@ -1890,8 +2051,6 @@ early_ret: success = validate_abstract_class_methods(&classfunctions, &objmethods, extends_cl); - class_T **intf_classes = NULL; - // Check all "implements" entries are valid. if (success && ga_impl.ga_len > 0) { @@ -1906,24 +2065,10 @@ early_ret: success = check_func_arg_names(&classfunctions, &objmethods, &classmembers); - class_T *cl = NULL; if (success) { // "endclass" encountered without failures: Create the class. - cl = ALLOC_CLEAR_ONE(class_T); - if (cl == NULL) - goto cleanup; - if (!is_class) - cl->class_flags = CLASS_INTERFACE; - else if (is_abstract) - cl->class_flags = CLASS_ABSTRACT; - - cl->class_refcount = 1; - cl->class_name = vim_strnsave(name_start, name_end - name_start); - if (cl->class_name == NULL) - goto cleanup; - if (extends_cl != NULL) { cl->class_extends = extends_cl; @@ -1997,6 +2142,8 @@ early_ret: &objmethods) == FAIL) goto cleanup; + update_builtin_method_index(cl); + cl->class_type.tt_type = VAR_CLASS; cl->class_type.tt_class = cl; cl->class_object_type.tt_type = VAR_OBJECT; @@ -2008,41 +2155,10 @@ early_ret: // TODO: // - Fill hashtab with object members and methods ? - // Add the class to the script-local variables. - // TODO: handle other context, e.g. in a function - // TODO: does uf_hash need to be cleared? - typval_T tv; - tv.v_type = VAR_CLASS; - tv.vval.v_class = cl; - is_export = class_export; - SOURCING_LNUM = start_lnum; - set_var_const(cl->class_name, current_sctx.sc_sid, - NULL, &tv, FALSE, 0, 0); return; } cleanup: - if (cl != NULL) - { - vim_free(cl->class_name); - vim_free(cl->class_class_functions); - if (cl->class_interfaces != NULL) - { - for (int i = 0; i < cl->class_interface_count; ++i) - vim_free(cl->class_interfaces[i]); - vim_free(cl->class_interfaces); - } - if (cl->class_interfaces_cl != NULL) - { - for (int i = 0; i < cl->class_interface_count; ++i) - class_unref(cl->class_interfaces_cl[i]); - vim_free(cl->class_interfaces_cl); - } - vim_free(cl->class_obj_members); - vim_free(cl->class_obj_methods); - vim_free(cl); - } - vim_free(extends); class_unref(extends_cl); @@ -3225,6 +3341,173 @@ member_not_found_msg(class_T *cl, vartype_T v_type, char_u *name, size_t len) } /* + * Compile all the class and object methods in "cl". + */ + void +defcompile_class(class_T *cl) +{ + for (int loop = 1; loop <= 2; ++loop) + { + int func_count = loop == 1 ? cl->class_class_function_count + : cl->class_obj_method_count; + for (int i = 0; i < func_count; i++) + { + ufunc_T *ufunc = loop == 1 ? cl->class_class_functions[i] + : cl->class_obj_methods[i]; + defcompile_function(ufunc, cl); + } + } +} + +/* + * Compile all the classes defined in the current script + */ + void +defcompile_classes_in_script(void) +{ + for (class_T *cl = first_class; cl != NULL; cl = cl->class_next_used) + { + if (eval_variable(cl->class_name, 0, 0, NULL, NULL, + EVAL_VAR_NOAUTOLOAD | EVAL_VAR_NO_FUNC) != FAIL) + defcompile_class(cl); + } +} + +/* + * Returns TRUE if "name" is the name of a class. The typval for the class is + * returned in "rettv". + */ + int +is_class_name(char_u *name, typval_T *rettv) +{ + rettv->v_type = VAR_UNKNOWN; + + if (eval_variable(name, 0, 0, rettv, NULL, EVAL_VAR_NOAUTOLOAD | + EVAL_VAR_NO_FUNC) != FAIL) + return rettv->v_type == VAR_CLASS; + return FALSE; +} + +/* + * Calls the object builtin method "name" with arguments "argv". The value + * returned by the builtin method is in "rettv". Returns OK or FAIL. + */ + static int +object_call_builtin_method( + object_T *obj, + class_builtin_T builtin_method, + int argc, + typval_T *argv, + typval_T *rettv) +{ + ufunc_T *uf; + int midx; + + if (obj == NULL) + return FAIL; + + uf = class_get_builtin_method(obj->obj_class, builtin_method, &midx); + if (uf == NULL) + return FAIL; + + funccall_T *fc = create_funccal(uf, rettv); + int r; + + if (fc == NULL) + return FAIL; + + ++obj->obj_refcount; + + r = call_def_function(uf, argc, argv, 0, NULL, obj, fc, rettv); + + remove_funccal(); + + return r; +} + +/* + * Calls the object "empty()" method and returns the method retun value. In + * case of an error, returns TRUE. + */ + int +object_empty(object_T *obj) +{ + typval_T rettv; + + if (object_call_builtin_method(obj, CLASS_BUILTIN_EMPTY, 0, NULL, &rettv) + == FAIL) + return TRUE; + + return tv_get_bool(&rettv); +} + +/* + * Use the object "len()" method to get an object length. Returns 0 if the + * method is not found or there is an error. + */ + int +object_len(object_T *obj) +{ + typval_T rettv; + + if (object_call_builtin_method(obj, CLASS_BUILTIN_LEN, 0, NULL, &rettv) + == FAIL) + return 0; + + return tv_to_number(&rettv); +} + +/* + * Return a textual representation of object "obj" + */ + char_u * +object_string( + object_T *obj, + char_u *numbuf, + int copyID, + int echo_style, + int restore_copyID, + int composite_val) +{ + typval_T rettv; + + if (object_call_builtin_method(obj, CLASS_BUILTIN_STRING, 0, NULL, &rettv) + == OK + && rettv.vval.v_string != NULL) + return rettv.vval.v_string; + else + { + garray_T ga; + ga_init2(&ga, 1, 50); + + ga_concat(&ga, (char_u *)"object of "); + class_T *cl = obj == NULL ? NULL : obj->obj_class; + ga_concat(&ga, cl == NULL ? (char_u *)"[unknown]" + : cl->class_name); + if (cl != NULL) + { + ga_concat(&ga, (char_u *)" {"); + for (int i = 0; i < cl->class_obj_member_count; ++i) + { + if (i > 0) + ga_concat(&ga, (char_u *)", "); + ocmember_T *m = &cl->class_obj_members[i]; + ga_concat(&ga, m->ocm_name); + ga_concat(&ga, (char_u *)": "); + char_u *tf = NULL; + ga_concat(&ga, echo_string_core( + (typval_T *)(obj + 1) + i, + &tf, numbuf, copyID, echo_style, + restore_copyID, composite_val)); + vim_free(tf); + } + ga_concat(&ga, (char_u *)"}"); + } + return ga.ga_data; + } +} + +/* * Return TRUE when the class "cl", its base class or one of the implemented * interfaces matches the class "other_cl". */ |