# HG changeset patch # User Bram Moolenaar # Date 1670514303 -3600 # Node ID 307f68a41b03cdd1c654042c6f8e5b0d8ba3a4cb # Parent 88027ff410752265269e55b55af1198407125b3e patch 9.0.1031: Vim9 class is not implemented yet Commit: https://github.com/vim/vim/commit/00b28d6c23d8e662cab27e461825777c0a2e387a Author: Bram Moolenaar 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. diff --git a/runtime/doc/vim9class.txt b/runtime/doc/vim9class.txt --- a/runtime/doc/vim9class.txt +++ b/runtime/doc/vim9class.txt @@ -336,6 +336,9 @@ Defining a class ~ A class is defined between `:class` and `:endclass`. The whole class is defined in one script file. It is not possible to add to a class later. +A class can only be defined in a |Vim9| script file. *E1315* +A class cannot be defined inside a function. + It is possible to define more than one class in a script file. Although it usually is better to export only one main class. It can be useful to define types, enums and helper classes though. @@ -369,9 +372,9 @@ A class can extend one other class. *implements* A class can implement one or more interfaces. *specifies* -A class can declare it's interface, the object members and methods, with a +A class can declare its interface, the object members and methods, with a named interface. This avoids the need for separately specifying the -interface, which is often done an many languages, especially Java. +interface, which is often done in many languages, especially Java. Defining an interface ~ @@ -634,7 +637,7 @@ directly writing you get an error, which to allow that. This helps writing code with fewer mistakes. -Making object membes private with an underscore ~ +Making object members private with an underscore ~ When an object member is private, it can only be read and changed inside the class (and in sub-classes), then it cannot be used outside of the class. diff --git a/src/errors.h b/src/errors.h --- a/src/errors.h +++ b/src/errors.h @@ -3346,4 +3346,28 @@ EXTERN char e_not_allowed_to_add_or_remo #ifdef FEAT_EVAL EXTERN char e_class_name_must_start_with_uppercase_letter_str[] INIT(= N_("E1314: Class name must start with an uppercase letter: %s")); -#endif +EXTERN char e_white_space_required_after_class_name_str[] + INIT(= N_("E1315: White space required after class name: %s")); +EXTERN char e_class_can_only_be_defined_in_vim9_script[] + INIT(= N_("E1316: Class can only be defined in Vim9 script")); +EXTERN char e_invalid_object_member_declaration_str[] + INIT(= N_("E1317: Invalid object member declaration: %s")); +EXTERN char e_not_valid_command_in_class_str[] + INIT(= N_("E1318: Not a valid command in a class: %s")); +EXTERN char e_using_class_as_number[] + INIT(= N_("E1319: Using a class as a Number")); +EXTERN char e_using_object_as_number[] + INIT(= N_("E1320: Using an object as a Number")); +EXTERN char e_using_class_as_float[] + INIT(= N_("E1321: Using a class as a Float")); +EXTERN char e_using_object_as_float[] + INIT(= N_("E1322: Using an object as a Float")); +EXTERN char e_using_class_as_string[] + INIT(= N_("E1323: Using a class as a String")); +EXTERN char e_using_object_as_string[] + INIT(= N_("E1324: Using an object as a String")); +EXTERN char e_method_not_found_on_class_str_str[] + INIT(= N_("E1325: Method not found on class \"%s\": %s")); +EXTERN char e_member_not_found_on_object_str_str[] + INIT(= N_("E1326: Member not found on object \"%s\": %s")); +#endif diff --git a/src/eval.c b/src/eval.c --- a/src/eval.c +++ b/src/eval.c @@ -1548,7 +1548,7 @@ set_var_lval( { cc = *endp; *endp = NUL; - if (in_vim9script() && check_reserved_name(lp->ll_name) == FAIL) + if (in_vim9script() && check_reserved_name(lp->ll_name, NULL) == FAIL) return; if (lp->ll_blob != NULL) @@ -1724,6 +1724,8 @@ tv_op(typval_T *tv1, typval_T *tv2, char case VAR_JOB: case VAR_CHANNEL: case VAR_INSTR: + case VAR_CLASS: + case VAR_OBJECT: break; case VAR_BLOB: @@ -3850,12 +3852,25 @@ handle_predefined(char_u *s, int len, ty return OK; } break; + case 10: if (STRNCMP(s, "null_class", 10) == 0) + { + rettv->v_type = VAR_CLASS; + rettv->vval.v_class = NULL; + return OK; + } + break; case 11: if (STRNCMP(s, "null_string", 11) == 0) { rettv->v_type = VAR_STRING; rettv->vval.v_string = NULL; return OK; } + if (STRNCMP(s, "null_object", 11) == 0) + { + rettv->v_type = VAR_OBJECT; + rettv->vval.v_object = NULL; + return OK; + } break; case 12: if (STRNCMP(s, "null_channel", 12) == 0) @@ -4685,6 +4700,8 @@ check_can_index(typval_T *rettv, int eva case VAR_JOB: case VAR_CHANNEL: case VAR_INSTR: + case VAR_CLASS: + case VAR_OBJECT: if (verbose) emsg(_(e_cannot_index_special_variable)); return FAIL; @@ -4788,6 +4805,8 @@ eval_index_inner( case VAR_JOB: case VAR_CHANNEL: case VAR_INSTR: + case VAR_CLASS: + case VAR_OBJECT: break; // not evaluating, skipping over subscript case VAR_NUMBER: @@ -5781,6 +5800,16 @@ echo_string_core( r = (char_u *)"instructions"; break; + case VAR_CLASS: + *tofree = NULL; + r = (char_u *)"class"; + break; + + case VAR_OBJECT: + *tofree = NULL; + r = (char_u *)"object"; + break; + case VAR_FLOAT: *tofree = NULL; vim_snprintf((char *)numbuf, NUMBUFLEN, "%g", tv->vval.v_float); @@ -6588,6 +6617,20 @@ handle_subscript( ret = FAIL; } } + else if (**arg == '.' && (rettv->v_type == VAR_CLASS + || rettv->v_type == VAR_OBJECT)) + { + // class member: SomeClass.varname + // class method: SomeClass.SomeMethod() + // class constructor: SomeClass.new() + // object member: someObject.varname + // object method: someObject.SomeMethod() + if (class_object_index(arg, rettv, evalarg, verbose) == FAIL) + { + clear_tv(rettv); + ret = FAIL; + } + } else break; } @@ -6644,6 +6687,8 @@ item_copy( case VAR_JOB: case VAR_CHANNEL: case VAR_INSTR: + case VAR_CLASS: + case VAR_OBJECT: copy_tv(from, to); break; case VAR_LIST: diff --git a/src/evalfunc.c b/src/evalfunc.c --- a/src/evalfunc.c +++ b/src/evalfunc.c @@ -3770,6 +3770,12 @@ f_empty(typval_T *argvars, typval_T *ret case VAR_SPECIAL: n = argvars[0].vval.v_number != VVAL_TRUE; break; + case VAR_CLASS: + n = argvars[0].vval.v_class != NULL; + break; + case VAR_OBJECT: + n = argvars[0].vval.v_object != NULL; + break; case VAR_BLOB: n = argvars[0].vval.v_blob == NULL @@ -7267,6 +7273,8 @@ f_len(typval_T *argvars, typval_T *rettv case VAR_JOB: case VAR_CHANNEL: case VAR_INSTR: + case VAR_CLASS: + case VAR_OBJECT: emsg(_(e_invalid_type_for_len)); break; } @@ -10183,7 +10191,9 @@ f_substitute(typval_T *argvars, typval_T if (argvars[2].v_type == VAR_FUNC || argvars[2].v_type == VAR_PARTIAL - || argvars[2].v_type == VAR_INSTR) + || argvars[2].v_type == VAR_INSTR + || argvars[2].v_type == VAR_CLASS + || argvars[2].v_type == VAR_OBJECT) expr = &argvars[2]; else sub = tv_get_string_buf_chk(&argvars[2], subbuf); @@ -10617,6 +10627,8 @@ f_type(typval_T *argvars, typval_T *rett case VAR_CHANNEL: n = VAR_TYPE_CHANNEL; break; case VAR_BLOB: n = VAR_TYPE_BLOB; break; case VAR_INSTR: n = VAR_TYPE_INSTR; break; + case VAR_CLASS: n = VAR_TYPE_CLASS; break; + case VAR_OBJECT: n = VAR_TYPE_OBJECT; break; case VAR_UNKNOWN: case VAR_ANY: case VAR_VOID: diff --git a/src/evalvars.c b/src/evalvars.c --- a/src/evalvars.c +++ b/src/evalvars.c @@ -2264,6 +2264,8 @@ item_lock(typval_T *tv, int deep, int lo case VAR_JOB: case VAR_CHANNEL: case VAR_INSTR: + case VAR_CLASS: + case VAR_OBJECT: break; case VAR_BLOB: diff --git a/src/if_py_both.h b/src/if_py_both.h --- a/src/if_py_both.h +++ b/src/if_py_both.h @@ -6422,6 +6422,8 @@ ConvertToPyObject(typval_T *tv) case VAR_CHANNEL: case VAR_JOB: case VAR_INSTR: + case VAR_CLASS: + case VAR_OBJECT: Py_INCREF(Py_None); return Py_None; case VAR_BOOL: diff --git a/src/json.c b/src/json.c --- a/src/json.c +++ b/src/json.c @@ -308,6 +308,8 @@ json_encode_item(garray_T *gap, typval_T case VAR_JOB: case VAR_CHANNEL: case VAR_INSTR: + case VAR_CLASS: + case VAR_OBJECT: semsg(_(e_cannot_json_encode_str), vartype_name(val->v_type)); return FAIL; diff --git a/src/proto/userfunc.pro b/src/proto/userfunc.pro --- a/src/proto/userfunc.pro +++ b/src/proto/userfunc.pro @@ -7,6 +7,7 @@ char_u *register_cfunc(cfunc_T cb, cfunc int get_lambda_tv(char_u **arg, typval_T *rettv, int types_optional, evalarg_T *evalarg); char_u *deref_func_name(char_u *name, int *lenp, partial_T **partialp, type_T **type, int no_autoload, int new_function, int *found_var); void emsg_funcname(char *ermsg, char_u *name); +int get_func_arguments(char_u **arg, evalarg_T *evalarg, int partial_argc, typval_T *argvars, int *argcount); int get_func_tv(char_u *name, int len, typval_T *rettv, char_u **arg, evalarg_T *evalarg, funcexe_T *funcexe); char_u *fname_trans_sid(char_u *name, char_u *fname_buf, char_u **tofree, int *error); void func_name_with_sid(char_u *name, int sid, char_u *buffer); @@ -45,7 +46,7 @@ char_u *get_scriptlocal_funcname(char_u char_u *alloc_printable_func_name(char_u *fname); char_u *save_function_name(char_u **name, int *is_global, int skip, int flags, funcdict_T *fudi); void list_functions(regmatch_T *regmatch); -ufunc_T *define_function(exarg_T *eap, char_u *name_arg, garray_T *lines_to_free); +ufunc_T *define_function(exarg_T *eap, char_u *name_arg, garray_T *lines_to_free, class_T *class_arg); void ex_function(exarg_T *eap); ufunc_T *find_func_by_name(char_u *name, compiletype_T *compile_type); void ex_defcompile(exarg_T *eap); diff --git a/src/proto/vim9class.pro b/src/proto/vim9class.pro --- a/src/proto/vim9class.pro +++ b/src/proto/vim9class.pro @@ -1,6 +1,12 @@ /* vim9class.c */ void ex_class(exarg_T *eap); +type_T *class_member_type(class_T *cl, char_u *name, char_u *name_end, int *member_idx); void ex_interface(exarg_T *eap); void ex_enum(exarg_T *eap); void ex_type(exarg_T *eap); +int class_object_index(char_u **arg, typval_T *rettv, evalarg_T *evalarg, int verbose); +void copy_object(typval_T *from, typval_T *to); +void object_unref(object_T *obj); +void copy_class(typval_T *from, typval_T *to); +void class_unref(typval_T *tv); /* vim: set ft=c : */ diff --git a/src/proto/vim9instr.pro b/src/proto/vim9instr.pro --- a/src/proto/vim9instr.pro +++ b/src/proto/vim9instr.pro @@ -3,6 +3,7 @@ isn_T *generate_instr(cctx_T *cctx, isnt isn_T *generate_instr_drop(cctx_T *cctx, isntype_T isn_type, int drop); isn_T *generate_instr_type(cctx_T *cctx, isntype_T isn_type, type_T *type); isn_T *generate_instr_debug(cctx_T *cctx); +int generate_CONSTRUCT(cctx_T *cctx, class_T *cl); int may_generate_2STRING(int offset, int tolerant, cctx_T *cctx); int generate_add_instr(cctx_T *cctx, vartype_T vartype, type_T *type1, type_T *type2, exprtype_T expr_type); vartype_T operator_type(type_T *type1, type_T *type2); diff --git a/src/proto/vim9script.pro b/src/proto/vim9script.pro --- a/src/proto/vim9script.pro +++ b/src/proto/vim9script.pro @@ -19,5 +19,5 @@ void update_vim9_script_var(int create, void hide_script_var(scriptitem_T *si, int idx, int func_defined); svar_T *find_typval_in_script(typval_T *dest, scid_T sid, int must_find); int check_script_var_type(svar_T *sv, typval_T *value, char_u *name, where_T where); -int check_reserved_name(char_u *name); +int check_reserved_name(char_u *name, cctx_T *cctx); /* vim: set ft=c : */ diff --git a/src/proto/vim9type.pro b/src/proto/vim9type.pro --- a/src/proto/vim9type.pro +++ b/src/proto/vim9type.pro @@ -1,4 +1,5 @@ /* vim9type.c */ +type_T *get_type_ptr(garray_T *type_gap); type_T *copy_type(type_T *type, garray_T *type_gap); void clear_type_list(garray_T *gap); type_T *alloc_type(type_T *type); diff --git a/src/structs.h b/src/structs.h --- a/src/structs.h +++ b/src/structs.h @@ -1406,6 +1406,9 @@ typedef struct { typedef struct isn_S isn_T; // instruction typedef struct dfunc_S dfunc_T; // :def function +typedef struct type_S type_T; +typedef struct ufunc_S ufunc_T; + typedef struct jobvar_S job_T; typedef struct readq_S readq_T; typedef struct writeq_S writeq_T; @@ -1415,6 +1418,8 @@ typedef struct channel_S channel_T; typedef struct cctx_S cctx_T; typedef struct ectx_S ectx_T; typedef struct instr_S instr_T; +typedef struct class_S class_T; +typedef struct object_S object_T; typedef enum { @@ -1434,16 +1439,18 @@ typedef enum VAR_JOB, // "v_job" is used VAR_CHANNEL, // "v_channel" is used VAR_INSTR, // "v_instr" is used + VAR_CLASS, // "v_class" is used + VAR_OBJECT, // "v_object" is used } vartype_T; // A type specification. -typedef struct type_S type_T; struct type_S { vartype_T tt_type; int8_T tt_argcount; // for func, incl. vararg, -1 for unknown int8_T tt_min_argcount; // number of non-optional arguments char_u tt_flags; // TTFLAG_ values type_T *tt_member; // for list, dict, func return type + // for class: class_T type_T **tt_args; // func argument types, allocated }; @@ -1452,6 +1459,38 @@ typedef struct { type_T *type_decl; // declared type or equal to type_current } type2_T; +/* + * Entry for an object member variable. + */ +typedef struct { + char_u *om_name; // allocated + type_T *om_type; +} objmember_T; + +// "class_T": used for v_class of typval of VAR_CLASS +struct class_S +{ + char_u *class_name; // allocated + int class_refcount; + + int class_obj_member_count; + objmember_T *class_obj_members; // allocated + + int class_obj_method_count; + ufunc_T **class_obj_methods; // allocated + ufunc_T *class_new_func; // new() function that was created + + garray_T class_type_list; // used for type pointers + type_T class_type; +}; + +// Used for v_object of typval of VAR_OBJECT. +// The member variables follow in an array of typval_T. +struct object_S { + class_T *obj_class; // class this object is created for + int obj_refcount; +}; + #define TTFLAG_VARARGS 0x01 // func args ends with "..." #define TTFLAG_BOOL_OK 0x02 // can be converted to bool #define TTFLAG_STATIC 0x04 // one of the static types, e.g. t_any @@ -1467,17 +1506,19 @@ typedef struct union { varnumber_T v_number; // number value - float_T v_float; // floating number value - char_u *v_string; // string value (can be NULL!) - list_T *v_list; // list value (can be NULL!) - dict_T *v_dict; // dict value (can be NULL!) + float_T v_float; // floating point number value + char_u *v_string; // string value (can be NULL) + list_T *v_list; // list value (can be NULL) + dict_T *v_dict; // dict value (can be NULL) partial_T *v_partial; // closure: function with args #ifdef FEAT_JOB_CHANNEL - job_T *v_job; // job value (can be NULL!) - channel_T *v_channel; // channel value (can be NULL!) + job_T *v_job; // job value (can be NULL) + channel_T *v_channel; // channel value (can be NULL) #endif - blob_T *v_blob; // blob value (can be NULL!) + blob_T *v_blob; // blob value (can be NULL) instr_T *v_instr; // instructions to execute + class_T *v_class; // class value (can be NULL) + object_T *v_object; // object value (can be NULL) } vval; } typval_T; @@ -1663,7 +1704,7 @@ typedef enum { * Structure to hold info for a user function. * When adding a field check copy_lambda_to_global_func(). */ -typedef struct +struct ufunc_S { int uf_varargs; // variable nr of arguments (old style) int uf_flags; // FC_ flags @@ -1671,6 +1712,9 @@ typedef struct int uf_cleared; // func_clear() was already called def_status_T uf_def_status; // UF_NOT_COMPILED, UF_TO_BE_COMPILED, etc. int uf_dfunc_idx; // only valid if uf_def_status is UF_COMPILED + + class_T *uf_class; // for object method and constructor + garray_T uf_args; // arguments, including optional arguments garray_T uf_def_args; // default argument expressions int uf_args_visible; // normally uf_args.ga_len, less when @@ -1731,7 +1775,7 @@ typedef struct char_u uf_name[4]; // name of function (actual size equals name); // can start with 123_ ( is K_SPECIAL // KS_EXTRA KE_SNR) -} ufunc_T; +}; // flags used in uf_flags #define FC_ABORT 0x01 // abort function on error @@ -1750,6 +1794,9 @@ typedef struct // copy_lambda_to_global_func() #define FC_LAMBDA 0x2000 // one line "return {expr}" +#define FC_OBJECT 010000 // object method +#define FC_NEW 030000 // constructor (also an object method) + #define MAX_FUNC_ARGS 20 // maximum number of function arguments #define VAR_SHORT_LEN 20 // short variable name length #define FIXVAR_CNT 12 // number of fixed variables diff --git a/src/testdir/Make_all.mak b/src/testdir/Make_all.mak --- a/src/testdir/Make_all.mak +++ b/src/testdir/Make_all.mak @@ -37,6 +37,7 @@ SCRIPTS_TINY_OUT = \ TEST_VIM9 = \ test_vim9_assign \ test_vim9_builtin \ + test_vim9_class \ test_vim9_cmd \ test_vim9_disassemble \ test_vim9_expr \ @@ -48,6 +49,7 @@ TEST_VIM9 = \ TEST_VIM9_RES = \ test_vim9_assign.res \ test_vim9_builtin.res \ + test_vim9_class.res \ test_vim9_cmd.res \ test_vim9_disassemble.res \ test_vim9_expr.res \ diff --git a/src/testdir/test_vim9_class.vim b/src/testdir/test_vim9_class.vim new file mode 100644 --- /dev/null +++ b/src/testdir/test_vim9_class.vim @@ -0,0 +1,145 @@ +" Test Vim9 classes + +source check.vim +import './vim9.vim' as v9 + +def Test_class_basic() + var lines =<< trim END + class NotWorking + endclass + END + v9.CheckScriptFailure(lines, 'E1316:') + + lines =<< trim END + vim9script + class notWorking + endclass + END + v9.CheckScriptFailure(lines, 'E1314:') + + lines =<< trim END + vim9script + class Not@working + endclass + END + v9.CheckScriptFailure(lines, 'E1315:') + + lines =<< trim END + vim9script + abstract noclass Something + endclass + END + v9.CheckScriptFailure(lines, 'E475:') + + lines =<< trim END + vim9script + abstract classy Something + endclass + END + v9.CheckScriptFailure(lines, 'E475:') + + lines =<< trim END + vim9script + class Something + endcl + END + v9.CheckScriptFailure(lines, 'E1065:') + + lines =<< trim END + vim9script + class Something + endclass school's out + END + v9.CheckScriptFailure(lines, 'E488:') + + lines =<< trim END + vim9script + class Something + endclass | echo 'done' + END + v9.CheckScriptFailure(lines, 'E488:') + + lines =<< trim END + vim9script + class Something + this + endclass + END + v9.CheckScriptFailure(lines, 'E1317:') + + lines =<< trim END + vim9script + class Something + this. + endclass + END + v9.CheckScriptFailure(lines, 'E1317:') + + lines =<< trim END + vim9script + class Something + this .count + endclass + END + v9.CheckScriptFailure(lines, 'E1317:') + + lines =<< trim END + vim9script + class Something + this. count + endclass + END + v9.CheckScriptFailure(lines, 'E1317:') + + lines =<< trim END + vim9script + class Something + this.count: number + that.count + endclass + END + v9.CheckScriptFailure(lines, 'E1318: Not a valid command in a class: that.count') + + lines =<< trim END + vim9script + class Something + this.count + endclass + END + v9.CheckScriptFailure(lines, 'E1022:') + + lines =<< trim END + vim9script + class Something + this.count : number + endclass + END + v9.CheckScriptFailure(lines, 'E1059:') + + lines =<< trim END + vim9script + class Something + this.count:number + endclass + END + v9.CheckScriptFailure(lines, 'E1069:') + + lines =<< trim END + vim9script + + class TextPosition + this.lnum: number + this.col: number + endclass + + # # FIXME: this works but leaks memory + # # use the automatically generated new() method + # var pos = TextPosition.new(2, 12) + # assert_equal(2, pos.lnum) + # assert_equal(12, pos.col) + END + v9.CheckScriptSuccess(lines) +enddef + + +" vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker diff --git a/src/testing.c b/src/testing.c --- a/src/testing.c +++ b/src/testing.c @@ -1101,6 +1101,8 @@ f_test_refcount(typval_T *argvars, typva case VAR_SPECIAL: case VAR_STRING: case VAR_INSTR: + case VAR_CLASS: + case VAR_OBJECT: break; case VAR_JOB: #ifdef FEAT_JOB_CHANNEL diff --git a/src/typval.c b/src/typval.c --- a/src/typval.c +++ b/src/typval.c @@ -84,6 +84,13 @@ free_tv(typval_T *varp) channel_unref(varp->vval.v_channel); break; #endif + case VAR_CLASS: + class_unref(varp); + break; + case VAR_OBJECT: + object_unref(varp->vval.v_object); + break; + case VAR_NUMBER: case VAR_FLOAT: case VAR_ANY: @@ -153,6 +160,12 @@ clear_tv(typval_T *varp) case VAR_INSTR: VIM_CLEAR(varp->vval.v_instr); break; + case VAR_CLASS: + class_unref(varp); + break; + case VAR_OBJECT: + object_unref(varp->vval.v_object); + break; case VAR_UNKNOWN: case VAR_ANY: case VAR_VOID: @@ -234,6 +247,12 @@ tv_get_bool_or_number_chk(typval_T *varp case VAR_BLOB: emsg(_(e_using_blob_as_number)); break; + case VAR_CLASS: + emsg(_(e_using_class_as_number)); + break; + case VAR_OBJECT: + emsg(_(e_using_object_as_number)); + break; case VAR_VOID: emsg(_(e_cannot_use_void_value)); break; @@ -333,6 +352,12 @@ tv_get_float_chk(typval_T *varp, int *er case VAR_BLOB: emsg(_(e_using_blob_as_float)); break; + case VAR_CLASS: + emsg(_(e_using_class_as_float)); + break; + case VAR_OBJECT: + emsg(_(e_using_object_as_float)); + break; case VAR_VOID: emsg(_(e_cannot_use_void_value)); break; @@ -1029,6 +1054,12 @@ tv_get_string_buf_chk_strict(typval_T *v case VAR_BLOB: emsg(_(e_using_blob_as_string)); break; + case VAR_CLASS: + emsg(_(e_using_class_as_string)); + break; + case VAR_OBJECT: + emsg(_(e_using_object_as_string)); + break; case VAR_JOB: #ifdef FEAT_JOB_CHANNEL if (in_vim9script()) @@ -1158,6 +1189,14 @@ copy_tv(typval_T *from, typval_T *to) to->vval.v_instr = from->vval.v_instr; break; + case VAR_CLASS: + copy_class(from, to); + break; + + case VAR_OBJECT: + copy_object(from, to); + break; + case VAR_STRING: case VAR_FUNC: if (from->vval.v_string == NULL) @@ -1878,6 +1917,13 @@ tv_equal( case VAR_INSTR: return tv1->vval.v_instr == tv2->vval.v_instr; + case VAR_CLASS: + return tv1->vval.v_class == tv2->vval.v_class; + + case VAR_OBJECT: + // TODO: compare values + return tv1->vval.v_object == tv2->vval.v_object; + case VAR_PARTIAL: return tv1->vval.v_partial == tv2->vval.v_partial; diff --git a/src/userfunc.c b/src/userfunc.c --- a/src/userfunc.c +++ b/src/userfunc.c @@ -214,6 +214,8 @@ get_function_args( garray_T *default_args, int skip, exarg_T *eap, // can be NULL + class_T *class_arg, + garray_T *newlines, // function body lines garray_T *lines_to_free) { int mustend = FALSE; @@ -292,6 +294,51 @@ get_function_args( } } } + else if (class_arg != NULL && STRNCMP(p, "this.", 5) == 0) + { + // this.memberName + p += 5; + arg = p; + while (ASCII_ISALNUM(*p) || *p == '_') + ++p; + + // TODO: check the argument is indeed a member + if (newargs != NULL && ga_grow(newargs, 1) == FAIL) + return FAIL; + if (newargs != NULL) + { + ((char_u **)(newargs->ga_data))[newargs->ga_len] = + vim_strnsave(arg, p - arg); + newargs->ga_len++; + + if (argtypes != NULL && ga_grow(argtypes, 1) == OK) + { + // TODO: use the actual type + ((char_u **)argtypes->ga_data)[argtypes->ga_len++] = + vim_strsave((char_u *)"any"); + + // Add a line to the function body for the assignment. + if (ga_grow(newlines, 1) == OK) + { + // "this.name = name" + int len = 5 + (p - arg) + 3 + (p - arg) + 1; + char_u *assignment = alloc(len); + if (assignment != NULL) + { + c = *p; + *p = NUL; + vim_snprintf((char *)assignment, len, + "this.%s = %s", arg, arg); + *p = c; + ((char_u **)(newlines->ga_data))[ + newlines->ga_len++] = assignment; + } + } + } + } + if (*p == ',') + ++p; + } else { char_u *np; @@ -1389,7 +1436,7 @@ get_lambda_tv( s = *arg + 1; ret = get_function_args(&s, equal_arrow ? ')' : '-', NULL, types_optional ? &argtypes : NULL, types_optional, evalarg, - NULL, &default_args, TRUE, NULL, NULL); + NULL, &default_args, TRUE, NULL, NULL, NULL, NULL); if (ret == FAIL || skip_arrow(s, equal_arrow, &ret_type, NULL) == NULL) { if (types_optional) @@ -1406,7 +1453,7 @@ get_lambda_tv( ret = get_function_args(arg, equal_arrow ? ')' : '-', pnewargs, types_optional ? &argtypes : NULL, types_optional, evalarg, &varargs, &default_args, - FALSE, NULL, NULL); + FALSE, NULL, NULL, NULL, NULL); if (ret == FAIL || (s = skip_arrow(*arg, equal_arrow, &ret_type, equal_arrow || vim9script ? &white_error : NULL)) == NULL) @@ -1733,7 +1780,7 @@ emsg_funcname(char *ermsg, char_u *name) * Return them in "*argvars[MAX_FUNC_ARGS + 1]" and the count in "argcount". * On failure FAIL is returned but the "argvars[argcount]" are still set. */ - static int + int get_func_arguments( char_u **arg, evalarg_T *evalarg, @@ -1809,7 +1856,7 @@ get_func_tv( funcexe_T *funcexe) // various values { char_u *argp; - int ret = OK; + int ret; typval_T argvars[MAX_FUNC_ARGS + 1]; // vars for arguments int argcount = 0; // number of arguments found int vim9script = in_vim9script(); @@ -4370,10 +4417,15 @@ list_functions(regmatch_T *regmatch) * When "name_arg" is not NULL this is a nested function, using "name_arg" for * the function name. * "lines_to_free" is a list of strings to be freed later. + * If "class_arg" is not NULL then the function is defined in this class. * Returns a pointer to the function or NULL if no function defined. */ ufunc_T * -define_function(exarg_T *eap, char_u *name_arg, garray_T *lines_to_free) +define_function( + exarg_T *eap, + char_u *name_arg, + garray_T *lines_to_free, + class_T *class_arg) { int j; int c; @@ -4488,8 +4540,9 @@ define_function(exarg_T *eap, char_u *na p = eap->arg; } - name = save_function_name(&p, &is_global, eap->skip, - TFN_NO_AUTOLOAD | TFN_NEW_FUNC, &fudi); + int tfn_flags = TFN_NO_AUTOLOAD | TFN_NEW_FUNC + | (class_arg == 0 ? 0 : TFN_INT); + name = save_function_name(&p, &is_global, eap->skip, tfn_flags, &fudi); paren = (vim_strchr(p, '(') != NULL); if (name == NULL && (fudi.fd_dict == NULL || !paren) && !eap->skip) { @@ -4690,7 +4743,7 @@ define_function(exarg_T *eap, char_u *na if (get_function_args(&p, ')', &newargs, eap->cmdidx == CMD_def ? &argtypes : NULL, FALSE, NULL, &varargs, &default_args, eap->skip, - eap, lines_to_free) == FAIL) + eap, class_arg, &newlines, lines_to_free) == FAIL) goto errret_2; whitep = p; @@ -5145,7 +5198,7 @@ ex_function(exarg_T *eap) garray_T lines_to_free; ga_init2(&lines_to_free, sizeof(char_u *), 50); - (void)define_function(eap, NULL, &lines_to_free); + (void)define_function(eap, NULL, &lines_to_free, NULL); ga_clear_strings(&lines_to_free); } diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -696,6 +696,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 1031, +/**/ 1030, /**/ 1029, diff --git a/src/vim.h b/src/vim.h --- a/src/vim.h +++ b/src/vim.h @@ -2120,6 +2120,8 @@ typedef int sock_T; #define VAR_TYPE_CHANNEL 9 #define VAR_TYPE_BLOB 10 #define VAR_TYPE_INSTR 11 +#define VAR_TYPE_CLASS 12 +#define VAR_TYPE_OBJECT 13 #define DICT_MAXNEST 100 // maximum nesting of lists and dicts diff --git a/src/vim9.h b/src/vim9.h --- a/src/vim9.h +++ b/src/vim9.h @@ -32,6 +32,7 @@ typedef enum { ISN_SOURCE, // source autoload script, isn_arg.number is the script ID ISN_INSTR, // instructions compiled from expression + ISN_CONSTRUCT, // construct an object, using contstruct_T // get and set variables ISN_LOAD, // push local variable isn_arg.number @@ -110,6 +111,7 @@ typedef enum { ISN_PCALL_END, // cleanup after ISN_PCALL with cpf_top set ISN_RETURN, // return, result is on top of stack ISN_RETURN_VOID, // Push void, then return + ISN_RETURN_OBJECT, // Push constructed object, then return ISN_FUNCREF, // push a function ref to dfunc isn_arg.funcref ISN_NEWFUNC, // create a global function from a lambda function ISN_DEF, // list functions @@ -463,6 +465,12 @@ typedef struct { long ewin_time; // time argument (msec) } echowin_T; +// arguments to ISN_CONSTRUCT +typedef struct { + int construct_size; // size of object in bytes + class_T *construct_class; // class the object is created from +} construct_T; + /* * Instruction */ @@ -514,6 +522,7 @@ struct isn_S { debug_T debug; deferins_T defer; echowin_T echowin; + construct_T construct; } isn_arg; }; @@ -757,7 +766,8 @@ typedef struct { int lhs_has_type; // type was specified type_T *lhs_type; - type_T *lhs_member_type; + int lhs_member_idx; // object member index + type_T *lhs_member_type; // list/dict/object member type int lhs_append; // used by ISN_REDIREND } lhs_T; diff --git a/src/vim9class.c b/src/vim9class.c --- 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: + // generics: // 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 someMethod() + // enddef + // static def 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 someMethod() - // enddef - // static def 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 diff --git a/src/vim9compile.c b/src/vim9compile.c --- a/src/vim9compile.c +++ b/src/vim9compile.c @@ -43,6 +43,20 @@ lookup_local(char_u *name, size_t len, l if (len == 0) return FAIL; + if (len == 4 && STRNCMP(name, "this", 4) == 0 + && cctx->ctx_ufunc != NULL + && (cctx->ctx_ufunc->uf_flags & FC_OBJECT)) + { + if (lvar != NULL) + { + CLEAR_POINTER(lvar); + lvar->lv_name = (char_u *)"this"; + if (cctx->ctx_ufunc->uf_class != NULL) + lvar->lv_type = &cctx->ctx_ufunc->uf_class->class_type; + } + return OK; + } + // Find local in current function scope. for (idx = 0; idx < cctx->ctx_locals.ga_len; ++idx) { @@ -296,7 +310,11 @@ variable_exists(char_u *name, size_t len { return (cctx != NULL && (lookup_local(name, len, NULL, cctx) == OK - || arg_exists(name, len, NULL, NULL, NULL, cctx) == OK)) + || arg_exists(name, len, NULL, NULL, NULL, cctx) == OK + || (len == 4 + && cctx->ctx_ufunc != NULL + && (cctx->ctx_ufunc->uf_flags & FC_OBJECT) + && STRNCMP(name, "this", 4) == 0))) || script_var_exists(name, len, cctx, NULL) == OK || find_imported(name, len, FALSE) != NULL; } @@ -957,7 +975,7 @@ compile_nested_function(exarg_T *eap, cc goto theend; } - ufunc = define_function(eap, lambda_name, lines_to_free); + ufunc = define_function(eap, lambda_name, lines_to_free, NULL); if (ufunc == NULL) { r = eap->skip ? OK : FAIL; @@ -1450,6 +1468,7 @@ compile_lhs( lhs->lhs_dest = dest_local; lhs->lhs_vimvaridx = -1; lhs->lhs_scriptvar_idx = -1; + lhs->lhs_member_idx = -1; // "dest_end" is the end of the destination, including "[expr]" or // ".name". @@ -1509,7 +1528,7 @@ compile_lhs( else { // No specific kind of variable recognized, just a name. - if (check_reserved_name(lhs->lhs_name) == FAIL) + if (check_reserved_name(lhs->lhs_name, cctx) == FAIL) return FAIL; if (lookup_local(var_start, lhs->lhs_varlen, @@ -1757,8 +1776,16 @@ compile_lhs( lhs->lhs_type = &t_any; } - if (lhs->lhs_type->tt_member == NULL) + if (lhs->lhs_type == NULL || lhs->lhs_type->tt_member == NULL) lhs->lhs_member_type = &t_any; + else if (lhs->lhs_type->tt_type == VAR_CLASS + || lhs->lhs_type->tt_type == VAR_OBJECT) + { + // for an object or class member get the type of the member + class_T *cl = (class_T *)lhs->lhs_type->tt_member; + lhs->lhs_member_type = class_member_type(cl, after + 1, + lhs->lhs_end, &lhs->lhs_member_idx); + } else lhs->lhs_member_type = lhs->lhs_type->tt_member; } @@ -1880,6 +1907,11 @@ compile_assign_index( r = FAIL; } } + else if (lhs->lhs_member_idx >= 0) + { + // object member index + r = generate_PUSHNR(cctx, lhs->lhs_member_idx); + } else // if (*p == '.') { char_u *key_end = to_name_end(p + 1, TRUE); @@ -1996,7 +2028,7 @@ compile_assign_unlet( return FAIL; } - if (lhs->lhs_type == &t_any) + if (lhs->lhs_type == NULL || lhs->lhs_type == &t_any) { // Index on variable of unknown type: check at runtime. dest_type = VAR_ANY; @@ -2042,8 +2074,12 @@ compile_assign_unlet( if (compile_load_lhs(lhs, var_start, rhs_type, cctx) == FAIL) return FAIL; - if (dest_type == VAR_LIST || dest_type == VAR_DICT - || dest_type == VAR_BLOB || dest_type == VAR_ANY) + if (dest_type == VAR_LIST + || dest_type == VAR_DICT + || dest_type == VAR_BLOB + || dest_type == VAR_CLASS + || dest_type == VAR_OBJECT + || dest_type == VAR_ANY) { if (is_assign) { @@ -2466,6 +2502,8 @@ compile_assignment(char_u *arg, exarg_T case VAR_PARTIAL: case VAR_VOID: case VAR_INSTR: + case VAR_CLASS: + case VAR_OBJECT: case VAR_SPECIAL: // cannot happen // This is skipped for local variables, they are always // initialized to zero. But in a "for" or "while" loop @@ -2897,6 +2935,22 @@ compile_def_function( if (check_args_shadowing(ufunc, &cctx) == FAIL) goto erret; + // For an object method and constructor "this" is the first local variable. + if (ufunc->uf_flags & FC_OBJECT) + { + dfunc_T *dfunc = ((dfunc_T *)def_functions.ga_data) + + ufunc->uf_dfunc_idx; + if (GA_GROW_FAILS(&dfunc->df_var_names, 1)) + goto erret; + ((char_u **)dfunc->df_var_names.ga_data)[0] = + vim_strsave((char_u *)"this"); + ++dfunc->df_var_names.ga_len; + + // In the constructor allocate memory for the object. + if ((ufunc->uf_flags & FC_NEW) == FC_NEW) + generate_CONSTRUCT(&cctx, ufunc->uf_class); + } + if (ufunc->uf_def_args.ga_len > 0) { int count = ufunc->uf_def_args.ga_len; @@ -3500,14 +3554,19 @@ nextline: { if (ufunc->uf_ret_type->tt_type == VAR_UNKNOWN) ufunc->uf_ret_type = &t_void; - else if (ufunc->uf_ret_type->tt_type != VAR_VOID) + else if (ufunc->uf_ret_type->tt_type != VAR_VOID + && (ufunc->uf_flags & FC_NEW) != FC_NEW) { emsg(_(e_missing_return_statement)); goto erret; } // Return void if there is no return at the end. - generate_instr(&cctx, ISN_RETURN_VOID); + // For a constructor return the object. + if ((ufunc->uf_flags & FC_NEW) == FC_NEW) + generate_instr(&cctx, ISN_RETURN_OBJECT); + else + generate_instr(&cctx, ISN_RETURN_VOID); } // When compiled with ":silent!" and there was an error don't consider the diff --git a/src/vim9execute.c b/src/vim9execute.c --- a/src/vim9execute.c +++ b/src/vim9execute.c @@ -2029,6 +2029,7 @@ handle_debug(isn_T *iptr, ectx_T *ectx) for (ni = iptr + 1; ni->isn_type != ISN_FINISH; ++ni) if (ni->isn_type == ISN_DEBUG || ni->isn_type == ISN_RETURN + || ni->isn_type == ISN_RETURN_OBJECT || ni->isn_type == ISN_RETURN_VOID) { end_lnum = ni->isn_lnum + (ni->isn_type == ISN_DEBUG ? 0 : 1); @@ -2082,7 +2083,7 @@ execute_storeindex(isn_T *iptr, ectx_T * // Stack contains: // -3 value to be stored // -2 index - // -1 dict or list + // -1 dict, list, blob or object tv = STACK_TV_BOT(-3); SOURCING_LNUM = iptr->isn_lnum; if (dest_type == VAR_ANY) @@ -2203,6 +2204,13 @@ execute_storeindex(isn_T *iptr, ectx_T * return FAIL; blob_set_append(blob, lidx, nr); } + else if (dest_type == VAR_CLASS || dest_type == VAR_OBJECT) + { + long idx = (long)tv_idx->vval.v_number; + object_T *obj = tv_dest->vval.v_object; + typval_T *otv = (typval_T *)(obj + 1); + otv[idx] = *tv; + } else { status = FAIL; @@ -3001,6 +3009,18 @@ exec_instructions(ectx_T *ectx) iptr = &ectx->ec_instr[ectx->ec_iidx++]; switch (iptr->isn_type) { + // Constructor, new() method. + case ISN_CONSTRUCT: + // "this" is always the local variable at index zero + tv = STACK_TV_VAR(0); + tv->v_type = VAR_OBJECT; + tv->vval.v_object = alloc_clear( + iptr->isn_arg.construct.construct_size); + tv->vval.v_object->obj_class = + iptr->isn_arg.construct.construct_class; + tv->vval.v_object->obj_refcount = 1; + break; + // execute Ex command line case ISN_EXEC: if (exec_command(iptr) == FAIL) @@ -4092,15 +4112,25 @@ exec_instructions(ectx_T *ectx) goto on_error; break; - // return from a :def function call without a value + // Return from a :def function call without a value. + // Return from a constructor. case ISN_RETURN_VOID: + case ISN_RETURN_OBJECT: if (GA_GROW_FAILS(&ectx->ec_stack, 1)) goto theend; tv = STACK_TV_BOT(0); ++ectx->ec_stack.ga_len; - tv->v_type = VAR_VOID; - tv->vval.v_number = 0; - tv->v_lock = 0; + if (iptr->isn_type == ISN_RETURN_VOID) + { + tv->v_type = VAR_VOID; + tv->vval.v_number = 0; + tv->v_lock = 0; + } + else + { + *tv = *STACK_TV_VAR(0); + ++tv->vval.v_object->obj_refcount; + } // FALLTHROUGH // return from a :def function call with what is on the stack @@ -4193,7 +4223,7 @@ exec_instructions(ectx_T *ectx) CLEAR_FIELD(ea); ea.cmd = ea.arg = iptr->isn_arg.string; ga_init2(&lines_to_free, sizeof(char_u *), 50); - define_function(&ea, NULL, &lines_to_free); + define_function(&ea, NULL, &lines_to_free, NULL); ga_clear_strings(&lines_to_free); } break; @@ -6018,6 +6048,11 @@ list_instructions(char *pfx, isn_T *inst switch (iptr->isn_type) { + case ISN_CONSTRUCT: + smsg("%s%4d NEW %s size %d", pfx, current, + iptr->isn_arg.construct.construct_class->class_name, + (int)iptr->isn_arg.construct.construct_size); + break; case ISN_EXEC: smsg("%s%4d EXEC %s", pfx, current, iptr->isn_arg.string); break; @@ -6447,6 +6482,9 @@ list_instructions(char *pfx, isn_T *inst case ISN_RETURN_VOID: smsg("%s%4d RETURN void", pfx, current); break; + case ISN_RETURN_OBJECT: + smsg("%s%4d RETURN object", pfx, current); + break; case ISN_FUNCREF: { funcref_T *funcref = &iptr->isn_arg.funcref; @@ -6979,6 +7017,8 @@ tv2bool(typval_T *tv) case VAR_ANY: case VAR_VOID: case VAR_INSTR: + case VAR_CLASS: + case VAR_OBJECT: break; } return FALSE; diff --git a/src/vim9expr.c b/src/vim9expr.c --- a/src/vim9expr.c +++ b/src/vim9expr.c @@ -235,6 +235,8 @@ compile_member(int is_slice, int *keepin case VAR_JOB: case VAR_CHANNEL: case VAR_INSTR: + case VAR_CLASS: + case VAR_OBJECT: case VAR_UNKNOWN: case VAR_ANY: case VAR_VOID: diff --git a/src/vim9instr.c b/src/vim9instr.c --- a/src/vim9instr.c +++ b/src/vim9instr.c @@ -114,6 +114,24 @@ generate_instr_debug(cctx_T *cctx) } /* + * Generate an ISN_CONSTRUCT instruction. + * The object will have "size" members. + */ + int +generate_CONSTRUCT(cctx_T *cctx, class_T *cl) +{ + isn_T *isn; + + RETURN_OK_IF_SKIP(cctx); + if ((isn = generate_instr(cctx, ISN_CONSTRUCT)) == NULL) + return FAIL; + isn->isn_arg.construct.construct_size = sizeof(object_T) + + cl->class_obj_member_count * sizeof(typval_T); + isn->isn_arg.construct.construct_class = cl; + return OK; +} + +/* * If type at "offset" isn't already VAR_STRING then generate ISN_2STRING. * But only for simple types. * When "tolerant" is TRUE convert most types to string, e.g. a List. @@ -163,6 +181,8 @@ may_generate_2STRING(int offset, int tol case VAR_JOB: case VAR_CHANNEL: case VAR_INSTR: + case VAR_CLASS: + case VAR_OBJECT: to_string_error(type->tt_type); return FAIL; } @@ -2403,6 +2423,7 @@ delete_instr(isn_T *isn) case ISN_COMPARESPECIAL: case ISN_COMPARESTRING: case ISN_CONCAT: + case ISN_CONSTRUCT: case ISN_COND2BOOL: case ISN_DEBUG: case ISN_DEFER: @@ -2457,6 +2478,7 @@ delete_instr(isn_T *isn) case ISN_REDIRSTART: case ISN_RETURN: case ISN_RETURN_VOID: + case ISN_RETURN_OBJECT: case ISN_SHUFFLE: case ISN_SLICE: case ISN_SOURCE: diff --git a/src/vim9script.c b/src/vim9script.c --- a/src/vim9script.c +++ b/src/vim9script.c @@ -838,7 +838,7 @@ vim9_declare_scriptvar(exarg_T *eap, cha // parse type, check for reserved name p = skipwhite(p + 1); type = parse_type(&p, &si->sn_type_list, TRUE); - if (type == NULL || check_reserved_name(name) == FAIL) + if (type == NULL || check_reserved_name(name, NULL) == FAIL) { vim_free(name); return p; @@ -1126,12 +1126,17 @@ static char *reserved[] = { }; int -check_reserved_name(char_u *name) +check_reserved_name(char_u *name, cctx_T *cctx) { int idx; for (idx = 0; reserved[idx] != NULL; ++idx) - if (STRCMP(reserved[idx], name) == 0) + if (STRCMP(reserved[idx], name) == 0 + // "this" can be used in an object method + && !(STRCMP("this", name) == 0 + && cctx != NULL + && cctx->ctx_ufunc != NULL + && (cctx->ctx_ufunc->uf_flags & FC_OBJECT))) { semsg(_(e_cannot_use_reserved_name), name); return FAIL; diff --git a/src/vim9type.c b/src/vim9type.c --- a/src/vim9type.c +++ b/src/vim9type.c @@ -29,7 +29,7 @@ * Allocate memory for a type_T and add the pointer to type_gap, so that it can * be easily freed later. */ - static type_T * + type_T * get_type_ptr(garray_T *type_gap) { type_T *type; @@ -94,7 +94,12 @@ alloc_type(type_T *type) *ret = *type; if (ret->tt_member != NULL) - ret->tt_member = alloc_type(ret->tt_member); + { + // tt_member points to the class_T for VAR_CLASS and VAR_OBJECT + if (type->tt_type != VAR_CLASS && type->tt_type != VAR_OBJECT) + ret->tt_member = alloc_type(ret->tt_member); + } + if (type->tt_args != NULL) { int i; @@ -124,7 +129,11 @@ free_type(type_T *type) free_type(type->tt_args[i]); vim_free(type->tt_args); } - free_type(type->tt_member); + + // for an object and class tt_member is a pointer to the class + if (type->tt_type != VAR_OBJECT && type->tt_type != VAR_CLASS) + free_type(type->tt_member); + vim_free(type); } @@ -1203,6 +1212,8 @@ equal_type(type_T *type1, type_T *type2, case VAR_JOB: case VAR_CHANNEL: case VAR_INSTR: + case VAR_CLASS: + case VAR_OBJECT: break; // not composite is always OK case VAR_LIST: case VAR_DICT: @@ -1451,6 +1462,8 @@ vartype_name(vartype_T type) case VAR_LIST: return "list"; case VAR_DICT: return "dict"; case VAR_INSTR: return "instr"; + case VAR_CLASS: return "class"; + case VAR_OBJECT: return "object"; case VAR_FUNC: case VAR_PARTIAL: return "func"; diff --git a/src/viminfo.c b/src/viminfo.c --- a/src/viminfo.c +++ b/src/viminfo.c @@ -1370,6 +1370,8 @@ write_viminfo_varlist(FILE *fp) case VAR_JOB: case VAR_CHANNEL: case VAR_INSTR: + case VAR_CLASS: + case VAR_OBJECT: continue; } fprintf(fp, "!%s\t%s\t", this_var->di_key, s);