summaryrefslogtreecommitdiff
path: root/src/vim9class.c
diff options
context:
space:
mode:
authorBram Moolenaar <Bram@vim.org>2022-12-08 15:32:33 +0000
committerBram Moolenaar <Bram@vim.org>2022-12-08 15:32:33 +0000
commit00b28d6c23d8e662cab27e461825777c0a2e387a (patch)
treeba11ed85b20cf03491e0f24b6d5bf348d2c19388 /src/vim9class.c
parent038e6d20e680ce8c850d07f6b035c4e1904c1201 (diff)
downloadvim-git-00b28d6c23d8e662cab27e461825777c0a2e387a.tar.gz
patch 9.0.1031: Vim9 class is not implemented yetv9.0.1031
Problem: Vim9 class is not implemented yet. Solution: Add very basic class support.
Diffstat (limited to 'src/vim9class.c')
-rw-r--r--src/vim9class.c493
1 files changed, 467 insertions, 26 deletions
diff --git a/src/vim9class.c b/src/vim9class.c
index b2307cb1b..5804b129b 100644
--- 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;
- // 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
+ 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);
+
+ // 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