summaryrefslogtreecommitdiffstats
path: root/src/vim9class.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 07:39:57 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 07:40:16 +0000
commit6af24b2457752c0d36aaf9f29f03d39afd09937f (patch)
tree2671b594908d1f971de6b2a2d473f97dfb7291d2 /src/vim9class.c
parentReleasing progress-linux version 2:9.1.0016-1~progress7.99u1. (diff)
downloadvim-6af24b2457752c0d36aaf9f29f03d39afd09937f.tar.xz
vim-6af24b2457752c0d36aaf9f29f03d39afd09937f.zip
Merging upstream version 2:9.1.0199.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/vim9class.c')
-rw-r--r--src/vim9class.c417
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".
*/