diff src/vim9class.c @ 31396:307f68a41b03 v9.0.1031

patch 9.0.1031: Vim9 class is not implemented yet Commit: https://github.com/vim/vim/commit/00b28d6c23d8e662cab27e461825777c0a2e387a Author: Bram Moolenaar <Bram@vim.org> Date: Thu Dec 8 15:32:33 2022 +0000 patch 9.0.1031: Vim9 class is not implemented yet Problem: Vim9 class is not implemented yet. Solution: Add very basic class support.
author Bram Moolenaar <Bram@vim.org>
date Thu, 08 Dec 2022 16:45:03 +0100
parents 5acc0d2cf4f7
children 60b1d266548d
line wrap: on
line diff
--- a/src/vim9class.c
+++ b/src/vim9class.c
@@ -27,9 +27,16 @@
     void
 ex_class(exarg_T *eap)
 {
-    int is_abstract = eap->cmdidx == CMD_abstract;
+    if (!current_script_is_vim9()
+		|| (cmdmod.cmod_flags & CMOD_LEGACY)
+		|| !getline_equal(eap->getline, eap->cookie, getsourceline))
+    {
+	emsg(_(e_class_can_only_be_defined_in_vim9_script));
+	return;
+    }
 
     char_u *arg = eap->arg;
+    int is_abstract = eap->cmdidx == CMD_abstract;
     if (is_abstract)
     {
 	if (STRNCMP(arg, "class", 5) != 0 || !VIM_ISWHITE(arg[5]))
@@ -45,38 +52,286 @@ ex_class(exarg_T *eap)
 	semsg(_(e_class_name_must_start_with_uppercase_letter_str), arg);
 	return;
     }
+    char_u *name_end = find_name_end(arg, NULL, NULL, FNE_CHECK_START);
+    if (!IS_WHITE_OR_NUL(*name_end))
+    {
+	semsg(_(e_white_space_required_after_class_name_str), arg);
+	return;
+    }
 
     // TODO:
-    // generics: <Tkey, Tentry>
+    //    generics: <Tkey, Tentry>
     //    extends SomeClass
     //    implements SomeInterface
     //    specifies SomeInterface
+    //    check nothing follows
 
+    // TODO: handle "is_export" if it is set
+
+    garray_T	type_list;	    // list of pointers to allocated types
+    ga_init2(&type_list, sizeof(type_T *), 10);
+
+    // Growarray with object members declared in the class.
+    garray_T objmembers;
+    ga_init2(&objmembers, sizeof(objmember_T), 10);
+
+    // Growarray with object methods declared in the class.
+    garray_T objmethods;
+    ga_init2(&objmethods, sizeof(ufunc_T), 10);
+
+    /*
+     * Go over the body of the class until "endclass" is found.
+     */
+    char_u *theline = NULL;
+    int success = FALSE;
+    for (;;)
+    {
+	vim_free(theline);
+	theline = eap->getline(':', eap->cookie, 0, GETLINE_CONCAT_ALL);
+	if (theline == NULL)
+	    break;
+	char_u *line = skipwhite(theline);
+
+	// TODO:
+	// class members (public, read access, private):
+	//	  static varname
+	//	  public static varname
+	//	  static _varname
+	//
+	// constructors:
+	//	  def new()
+	//	  enddef
+	//	  def newOther()
+	//	  enddef
+	//
+	// methods (object, class, generics):
+	//	  def someMethod()
+	//	  enddef
+	//	  static def someMethod()
+	//	  enddef
+	//	  def <Tval> someMethod()
+	//	  enddef
+	//	  static def <Tval> someMethod()
+	//	  enddef
+
+	char_u *p = line;
+	if (checkforcmd(&p, "endclass", 4))
+	{
+	    if (STRNCMP(line, "endclass", 8) != 0)
+		semsg(_(e_command_cannot_be_shortened_str), line);
+	    else if (*p == '|' || !ends_excmd2(line, p))
+		semsg(_(e_trailing_characters_str), p);
+
+	    success = TRUE;
+	    break;
+	}
+
+	// "this.varname"
+	// "this._varname"
+	// TODO:
+	//	"public this.varname"
+	if (STRNCMP(line, "this", 4) == 0)
+	{
+	    if (line[4] != '.' || !eval_isnamec1(line[5]))
+	    {
+		semsg(_(e_invalid_object_member_declaration_str), line);
+		break;
+	    }
+	    char_u *varname = line + 5;
+	    char_u *varname_end = to_name_end(varname, FALSE);
+
+	    char_u *colon = skipwhite(varname_end);
+	    // TODO: accept initialization and figure out type from it
+	    if (*colon != ':')
+	    {
+		emsg(_(e_type_or_initialization_required));
+		break;
+	    }
+	    if (VIM_ISWHITE(*varname_end))
+	    {
+		semsg(_(e_no_white_space_allowed_before_colon_str), varname);
+		break;
+	    }
+	    if (!VIM_ISWHITE(colon[1]))
+	    {
+		semsg(_(e_white_space_required_after_str_str), ":", varname);
+		break;
+	    }
+
+	    char_u *type_arg = skipwhite(colon + 1);
+	    type_T *type = parse_type(&type_arg, &type_list, TRUE);
+	    if (type == NULL)
+		break;
+
+	    if (ga_grow(&objmembers, 1) == FAIL)
+		break;
+	    objmember_T *m = ((objmember_T *)objmembers.ga_data)
+							  + objmembers.ga_len;
+	    m->om_name = vim_strnsave(varname, varname_end - varname);
+	    m->om_type = type;
+	    ++objmembers.ga_len;
+	}
+
+	else
+	{
+	    semsg(_(e_not_valid_command_in_class_str), line);
+	    break;
+	}
+    }
+    vim_free(theline);
+
+    if (success)
+    {
+	class_T *cl = ALLOC_CLEAR_ONE(class_T);
+	if (cl == NULL)
+	    goto cleanup;
+	cl->class_refcount = 1;
+	cl->class_name = vim_strnsave(arg, name_end - arg);
 
-    // TODO: handle until "endclass" is found:
-    // object and class members (public, read access, private):
-    //	  public this.varname
-    //	  public static varname
-    //	  this.varname
-    //	  static varname
-    //	  this._varname
-    //	  static _varname
-    //
-    // constructors:
-    //	  def new()
-    //	  enddef
-    //	  def newOther()
-    //	  enddef
-    //
-    // methods (object, class, generics):
-    //	  def someMethod()
-    //	  enddef
-    //	  static def someMethod()
-    //	  enddef
-    //	  def <Tval> someMethod()
-    //	  enddef
-    //	  static def <Tval> someMethod()
-    //	  enddef
+	// Members are used by the new() function, add them here.
+	cl->class_obj_member_count = objmembers.ga_len;
+	cl->class_obj_members = ALLOC_MULT(objmember_T, objmembers.ga_len);
+	if (cl->class_name == NULL
+		|| cl->class_obj_members == NULL)
+	{
+	    vim_free(cl->class_name);
+	    vim_free(cl->class_obj_members);
+	    vim_free(cl);
+	    goto cleanup;
+	}
+	mch_memmove(cl->class_obj_members, objmembers.ga_data,
+				      sizeof(objmember_T) * objmembers.ga_len);
+	vim_free(objmembers.ga_data);
+
+	int have_new = FALSE;
+	for (int i = 0; i < objmethods.ga_len; ++i)
+	    if (STRCMP((((ufunc_T *)objmethods.ga_data) + i)->uf_name,
+								   "new") == 0)
+	    {
+		have_new = TRUE;
+		break;
+	    }
+	if (!have_new)
+	{
+	    // No new() method was defined, add the default constructor.
+	    garray_T fga;
+	    ga_init2(&fga, 1, 1000);
+	    ga_concat(&fga, (char_u *)"new(");
+	    for (int i = 0; i < cl->class_obj_member_count; ++i)
+	    {
+		if (i > 0)
+		    ga_concat(&fga, (char_u *)", ");
+		ga_concat(&fga, (char_u *)"this.");
+		objmember_T *m = cl->class_obj_members + i;
+		ga_concat(&fga, (char_u *)m->om_name);
+	    }
+	    ga_concat(&fga, (char_u *)")\nenddef\n");
+	    ga_append(&fga, NUL);
+
+	    exarg_T fea;
+	    CLEAR_FIELD(fea);
+	    fea.cmdidx = CMD_def;
+	    fea.cmd = fea.arg = fga.ga_data;
+
+	    garray_T lines_to_free;
+	    ga_init2(&lines_to_free, sizeof(char_u *), 50);
+
+	    ufunc_T *nf = define_function(&fea, NULL, &lines_to_free, cl);
+
+	    ga_clear_strings(&lines_to_free);
+	    vim_free(fga.ga_data);
+
+	    if (nf != NULL && ga_grow(&objmethods, 1) == OK)
+	    {
+		((ufunc_T **)objmethods.ga_data)[objmethods.ga_len] = nf;
+		++objmethods.ga_len;
+
+		nf->uf_flags |= FC_NEW;
+		nf->uf_class = cl;
+		nf->uf_ret_type = get_type_ptr(&type_list);
+		if (nf->uf_ret_type != NULL)
+		{
+		    nf->uf_ret_type->tt_type = VAR_OBJECT;
+		    nf->uf_ret_type->tt_member = (type_T *)cl;
+		    nf->uf_ret_type->tt_argcount = 0;
+		    nf->uf_ret_type->tt_args = NULL;
+		}
+		cl->class_new_func = nf;
+	    }
+	}
+
+	cl->class_obj_method_count = objmethods.ga_len;
+	cl->class_obj_methods = ALLOC_MULT(ufunc_T *, objmethods.ga_len);
+	if (cl->class_obj_methods == NULL)
+	{
+	    vim_free(cl->class_name);
+	    vim_free(cl->class_obj_members);
+	    vim_free(cl->class_obj_methods);
+	    vim_free(cl);
+	    goto cleanup;
+	}
+	mch_memmove(cl->class_obj_methods, objmethods.ga_data,
+					sizeof(ufunc_T *) * objmethods.ga_len);
+	vim_free(objmethods.ga_data);
+
+	cl->class_type.tt_type = VAR_CLASS;
+	cl->class_type.tt_member = (type_T *)cl;
+	cl->class_type_list = type_list;
+
+	// TODO:
+	// - Add the methods to the class
+	//	- array with ufunc_T pointers
+	// - Fill hashtab with object members and methods
+	// - Generate the default new() method, if needed.
+	// Later:
+	// - class members
+	// - class methods
+
+	// Add the class to the script-local variables.
+	typval_T tv;
+	tv.v_type = VAR_CLASS;
+	tv.vval.v_class = cl;
+	set_var_const(cl->class_name, current_sctx.sc_sid,
+					     NULL, &tv, FALSE, ASSIGN_DECL, 0);
+	return;
+    }
+
+cleanup:
+    for (int i = 0; i < objmembers.ga_len; ++i)
+    {
+	objmember_T *m = ((objmember_T *)objmembers.ga_data) + i;
+	vim_free(m->om_name);
+    }
+    ga_clear(&objmembers);
+
+    ga_clear(&objmethods);
+    clear_type_list(&type_list);
+}
+
+/*
+ * Find member "name" in class "cl" and return its type.
+ * When not found t_any is returned.
+ */
+    type_T *
+class_member_type(
+	class_T *cl,
+	char_u	*name,
+	char_u	*name_end,
+	int	*member_idx)
+{
+    *member_idx = -1;  // not found (yet)
+    size_t len = name_end - name;
+
+    for (int i = 0; i < cl->class_obj_member_count; ++i)
+    {
+	objmember_T *m = cl->class_obj_members + i;
+	if (STRNCMP(m->om_name, name, len) == 0 && m->om_name[len] == NUL)
+	{
+	    *member_idx = i;
+	    return m->om_type;
+	}
+    }
+    return &t_any;
 }
 
 /*
@@ -106,5 +361,191 @@ ex_type(exarg_T *eap UNUSED)
     // TODO
 }
 
+/*
+ * Evaluate what comes after a class:
+ * - class member: SomeClass.varname
+ * - class method: SomeClass.SomeMethod()
+ * - class constructor: SomeClass.new()
+ * - object member: someObject.varname
+ * - object method: someObject.SomeMethod()
+ *
+ * "*arg" points to the '.'.
+ * "*arg" is advanced to after the member name or method call.
+ *
+ * Returns FAIL or OK.
+ */
+    int
+class_object_index(
+    char_u	**arg,
+    typval_T	*rettv,
+    evalarg_T	*evalarg,
+    int		verbose UNUSED)	// give error messages
+{
+    // int		evaluate = evalarg != NULL
+    //				      && (evalarg->eval_flags & EVAL_EVALUATE);
+
+    if (VIM_ISWHITE((*arg)[1]))
+    {
+	semsg(_(e_no_white_space_allowed_after_str_str), ".", *arg);
+	return FAIL;
+    }
+
+    ++*arg;
+    char_u *name = *arg;
+    char_u *name_end = find_name_end(name, NULL, NULL, FNE_CHECK_START);
+    if (name_end == name)
+	return FAIL;
+    size_t len = name_end - name;
+
+    class_T *cl = rettv->v_type == VAR_CLASS ? rettv->vval.v_class
+					     : rettv->vval.v_object->obj_class;
+    if (*name_end == '(')
+    {
+	for (int i = 0; i < cl->class_obj_method_count; ++i)
+	{
+	    ufunc_T *fp = cl->class_obj_methods[i];
+	    if (STRNCMP(name, fp->uf_name, len) == 0 && fp->uf_name[len] == NUL)
+	    {
+		typval_T    argvars[MAX_FUNC_ARGS + 1];
+		int	    argcount = 0;
+
+		char_u *argp = name_end;
+		int ret = get_func_arguments(&argp, evalarg, 0,
+							   argvars, &argcount);
+		if (ret == FAIL)
+		    return FAIL;
+
+		funcexe_T   funcexe;
+		CLEAR_FIELD(funcexe);
+		funcexe.fe_evaluate = TRUE;
+
+		// Call the user function.  Result goes into rettv;
+		// TODO: pass the object
+		rettv->v_type = VAR_UNKNOWN;
+		int error = call_user_func_check(fp, argcount, argvars,
+							rettv, &funcexe, NULL);
+
+		// Clear the arguments.
+		for (int idx = 0; idx < argcount; ++idx)
+		    clear_tv(&argvars[idx]);
+
+		if (error != FCERR_NONE)
+		{
+		    user_func_error(error, printable_func_name(fp),
+							 funcexe.fe_found_var);
+		    return FAIL;
+		}
+		*arg = argp;
+		return OK;
+	    }
+	}
+
+	semsg(_(e_method_not_found_on_class_str_str), cl->class_name, name);
+    }
+
+    else if (rettv->v_type == VAR_OBJECT)
+    {
+	for (int i = 0; i < cl->class_obj_member_count; ++i)
+	{
+	    objmember_T *m = &cl->class_obj_members[i];
+	    if (STRNCMP(name, m->om_name, len) == 0 && m->om_name[len] == NUL)
+	    {
+		// The object only contains a pointer to the class, the member
+		// values array follows right after that.
+		object_T *obj = rettv->vval.v_object;
+		typval_T *tv = (typval_T *)(obj + 1) + i;
+		copy_tv(tv, rettv);
+		object_unref(obj);
+
+		*arg = name_end;
+		return OK;
+	    }
+	}
+
+	semsg(_(e_member_not_found_on_object_str_str), cl->class_name, name);
+    }
+
+    // TODO: class member
+
+    return FAIL;
+}
+
+/*
+ * Make a copy of an object.
+ */
+    void
+copy_object(typval_T *from, typval_T *to)
+{
+    *to = *from;
+    if (to->vval.v_object != NULL)
+	++to->vval.v_object->obj_refcount;
+}
+
+/*
+ * Free an object.
+ */
+    static void
+object_clear(object_T *obj)
+{
+    class_T *cl = obj->obj_class;
+
+    // the member values are just after the object structure
+    typval_T *tv = (typval_T *)(obj + 1);
+    for (int i = 0; i < cl->class_obj_member_count; ++i)
+	clear_tv(tv + i);
+
+    vim_free(obj);
+}
+
+/*
+ * Unreference an object.
+ */
+    void
+object_unref(object_T *obj)
+{
+    if (obj != NULL && --obj->obj_refcount <= 0)
+	object_clear(obj);
+}
+
+/*
+ * Make a copy of a class.
+ */
+    void
+copy_class(typval_T *from, typval_T *to)
+{
+    *to = *from;
+    if (to->vval.v_class != NULL)
+	++to->vval.v_class->class_refcount;
+}
+
+/*
+ * Unreference a class.  Free it when the reference count goes down to zero.
+ */
+    void
+class_unref(typval_T *tv)
+{
+    class_T *cl = tv->vval.v_class;
+    if (cl != NULL && --cl->class_refcount <= 0)
+    {
+	vim_free(cl->class_name);
+
+	for (int i = 0; i < cl->class_obj_member_count; ++i)
+	{
+	    objmember_T *m = &cl->class_obj_members[i];
+	    vim_free(m->om_name);
+	}
+	vim_free(cl->class_obj_members);
+
+	vim_free(cl->class_obj_methods);
+
+	if (cl->class_new_func != NULL)
+	    func_ptr_unref(cl->class_new_func);
+
+	clear_type_list(&cl->class_type_list);
+
+	vim_free(cl);
+    }
+}
+
 
 #endif // FEAT_EVAL