To: vim_dev@googlegroups.com Subject: Patch 9.0.1031 Fcc: outbox From: Bram Moolenaar Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ------------ Patch 9.0.1031 Problem: Vim9 class is not implemented yet. Solution: Add very basic class support. Files: runtime/doc/vim9class.txt, src/errors.h, src/eval.c, src/evalfunc.c, src/evalvars.c, src/if_py_both.h, src/json.c, src/structs.h, src/testing.c, src/typval.c, src/userfunc.c, src/proto/userfunc.pro, src/vim.h, src/vim9.h, src/vim9class.c, src/proto/vim9class.pro, src/vim9compile.c, src/vim9execute.c, src/vim9expr.c, src/vim9instr.c, src/proto/vim9instr.pro, src/vim9script.c, src/proto/vim9script.pro, src/vim9type.c, src/proto/vim9type.pro, src/viminfo.c, src/testdir/Make_all.mak, src/testdir/test_vim9_class.vim *** ../vim-9.0.1030/runtime/doc/vim9class.txt 2022-12-04 20:11:12.791828025 +0000 --- runtime/doc/vim9class.txt 2022-12-08 09:07:00.157532559 +0000 *************** *** 336,341 **** --- 336,344 ---- 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,377 **** *implements* A class can implement one or more interfaces. *specifies* ! A class can declare it's 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. Defining an interface ~ --- 372,380 ---- *implements* A class can implement one or more interfaces. *specifies* ! 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 in many languages, especially Java. Defining an interface ~ *************** *** 634,640 **** to allow that. This helps writing code with fewer mistakes. ! Making object membes 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. --- 637,643 ---- to allow that. This helps writing code with fewer mistakes. ! 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. *** ../vim-9.0.1030/src/errors.h 2022-12-04 21:40:48.467175257 +0000 --- src/errors.h 2022-12-08 12:21:36.131379099 +0000 *************** *** 3346,3349 **** --- 3346,3373 ---- #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")); + 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 *** ../vim-9.0.1030/src/eval.c 2022-11-28 20:34:47.704140309 +0000 --- src/eval.c 2022-12-08 14:06:38.808126638 +0000 *************** *** 1548,1554 **** { cc = *endp; *endp = NUL; ! if (in_vim9script() && check_reserved_name(lp->ll_name) == FAIL) return; if (lp->ll_blob != NULL) --- 1548,1554 ---- { cc = *endp; *endp = NUL; ! if (in_vim9script() && check_reserved_name(lp->ll_name, NULL) == FAIL) return; if (lp->ll_blob != NULL) *************** *** 1724,1729 **** --- 1724,1731 ---- case VAR_JOB: case VAR_CHANNEL: case VAR_INSTR: + case VAR_CLASS: + case VAR_OBJECT: break; case VAR_BLOB: *************** *** 3850,3861 **** --- 3852,3876 ---- 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,4690 **** --- 4700,4707 ---- 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,4793 **** --- 4805,4812 ---- 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,5786 **** --- 5800,5815 ---- 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,6593 **** --- 6617,6636 ---- 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,6649 **** --- 6687,6694 ---- case VAR_JOB: case VAR_CHANNEL: case VAR_INSTR: + case VAR_CLASS: + case VAR_OBJECT: copy_tv(from, to); break; case VAR_LIST: *** ../vim-9.0.1030/src/evalfunc.c 2022-12-06 16:16:57.971656206 +0000 --- src/evalfunc.c 2022-12-07 09:49:43.662984070 +0000 *************** *** 3770,3775 **** --- 3770,3781 ---- 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,7272 **** --- 7273,7280 ---- case VAR_JOB: case VAR_CHANNEL: case VAR_INSTR: + case VAR_CLASS: + case VAR_OBJECT: emsg(_(e_invalid_type_for_len)); break; } *************** *** 10183,10189 **** if (argvars[2].v_type == VAR_FUNC || argvars[2].v_type == VAR_PARTIAL ! || argvars[2].v_type == VAR_INSTR) expr = &argvars[2]; else sub = tv_get_string_buf_chk(&argvars[2], subbuf); --- 10191,10199 ---- if (argvars[2].v_type == VAR_FUNC || argvars[2].v_type == VAR_PARTIAL ! || 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,10622 **** --- 10627,10634 ---- 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: *** ../vim-9.0.1030/src/evalvars.c 2022-12-02 15:58:34.602705473 +0000 --- src/evalvars.c 2022-12-07 09:51:22.714902172 +0000 *************** *** 2264,2269 **** --- 2264,2271 ---- case VAR_JOB: case VAR_CHANNEL: case VAR_INSTR: + case VAR_CLASS: + case VAR_OBJECT: break; case VAR_BLOB: *** ../vim-9.0.1030/src/if_py_both.h 2022-11-29 13:46:43.122213356 +0000 --- src/if_py_both.h 2022-12-07 10:11:40.887756522 +0000 *************** *** 6422,6427 **** --- 6422,6429 ---- 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: *** ../vim-9.0.1030/src/json.c 2022-09-17 21:07:52.099993159 +0100 --- src/json.c 2022-12-07 09:52:16.206857930 +0000 *************** *** 308,313 **** --- 308,315 ---- 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; *** ../vim-9.0.1030/src/structs.h 2022-12-07 22:30:14.991089265 +0000 --- src/structs.h 2022-12-08 13:26:32.799900700 +0000 *************** *** 1406,1411 **** --- 1406,1414 ---- 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,1420 **** --- 1418,1425 ---- 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,1449 **** VAR_JOB, // "v_job" is used VAR_CHANNEL, // "v_channel" is used VAR_INSTR, // "v_instr" 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 type_T **tt_args; // func argument types, allocated }; --- 1439,1456 ---- 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. 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,1457 **** --- 1459,1496 ---- 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,1483 **** 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!) 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!) #endif ! blob_T *v_blob; // blob value (can be NULL!) instr_T *v_instr; // instructions to execute } vval; } typval_T; --- 1506,1524 ---- union { varnumber_T v_number; // number value ! 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) #endif ! 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,1669 **** * Structure to hold info for a user function. * When adding a field check copy_lambda_to_global_func(). */ ! typedef struct { int uf_varargs; // variable nr of arguments (old style) int uf_flags; // FC_ flags --- 1704,1710 ---- * Structure to hold info for a user function. * When adding a field check copy_lambda_to_global_func(). */ ! struct ufunc_S { int uf_varargs; // variable nr of arguments (old style) int uf_flags; // FC_ flags *************** *** 1671,1676 **** --- 1712,1720 ---- 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,1737 **** 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 --- 1775,1781 ---- char_u uf_name[4]; // name of function (actual size equals name); // can start with 123_ ( is K_SPECIAL // KS_EXTRA KE_SNR) ! }; // flags used in uf_flags #define FC_ABORT 0x01 // abort function on error *************** *** 1750,1755 **** --- 1794,1802 ---- // 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 *** ../vim-9.0.1030/src/testing.c 2022-11-09 00:44:25.315439173 +0000 --- src/testing.c 2022-12-07 09:52:56.990824217 +0000 *************** *** 1101,1106 **** --- 1101,1108 ---- case VAR_SPECIAL: case VAR_STRING: case VAR_INSTR: + case VAR_CLASS: + case VAR_OBJECT: break; case VAR_JOB: #ifdef FEAT_JOB_CHANNEL *** ../vim-9.0.1030/src/typval.c 2022-11-30 20:20:52.755228276 +0000 --- src/typval.c 2022-12-08 13:21:44.371816108 +0000 *************** *** 84,89 **** --- 84,96 ---- 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,158 **** --- 160,171 ---- 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,239 **** --- 247,258 ---- 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,338 **** --- 352,363 ---- 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,1034 **** --- 1054,1065 ---- 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,1163 **** --- 1189,1202 ---- 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,1883 **** --- 1917,1929 ---- 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; *** ../vim-9.0.1030/src/userfunc.c 2022-11-25 16:31:46.972606656 +0000 --- src/userfunc.c 2022-12-07 16:01:48.183339139 +0000 *************** *** 214,219 **** --- 214,221 ---- 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,297 **** --- 294,344 ---- } } } + 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,1395 **** s = *arg + 1; ret = get_function_args(&s, equal_arrow ? ')' : '-', NULL, types_optional ? &argtypes : NULL, types_optional, evalarg, ! NULL, &default_args, TRUE, NULL, NULL); if (ret == FAIL || skip_arrow(s, equal_arrow, &ret_type, NULL) == NULL) { if (types_optional) --- 1436,1442 ---- 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, NULL); if (ret == FAIL || skip_arrow(s, equal_arrow, &ret_type, NULL) == NULL) { if (types_optional) *************** *** 1406,1412 **** ret = get_function_args(arg, equal_arrow ? ')' : '-', pnewargs, types_optional ? &argtypes : NULL, types_optional, evalarg, &varargs, &default_args, ! FALSE, NULL, NULL); if (ret == FAIL || (s = skip_arrow(*arg, equal_arrow, &ret_type, equal_arrow || vim9script ? &white_error : NULL)) == NULL) --- 1453,1459 ---- ret = get_function_args(arg, equal_arrow ? ')' : '-', pnewargs, types_optional ? &argtypes : NULL, types_optional, evalarg, &varargs, &default_args, ! FALSE, NULL, NULL, NULL, NULL); if (ret == FAIL || (s = skip_arrow(*arg, equal_arrow, &ret_type, equal_arrow || vim9script ? &white_error : NULL)) == NULL) *************** *** 1733,1739 **** * 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 get_func_arguments( char_u **arg, evalarg_T *evalarg, --- 1780,1786 ---- * 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. */ ! int get_func_arguments( char_u **arg, evalarg_T *evalarg, *************** *** 1809,1815 **** funcexe_T *funcexe) // various values { char_u *argp; ! int ret = OK; typval_T argvars[MAX_FUNC_ARGS + 1]; // vars for arguments int argcount = 0; // number of arguments found int vim9script = in_vim9script(); --- 1856,1862 ---- funcexe_T *funcexe) // various values { char_u *argp; ! int ret; typval_T argvars[MAX_FUNC_ARGS + 1]; // vars for arguments int argcount = 0; // number of arguments found int vim9script = in_vim9script(); *************** *** 4370,4379 **** * 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. * 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) { int j; int c; --- 4417,4431 ---- * 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, ! class_T *class_arg) { int j; int c; *************** *** 4488,4495 **** p = eap->arg; } ! name = save_function_name(&p, &is_global, eap->skip, ! TFN_NO_AUTOLOAD | TFN_NEW_FUNC, &fudi); paren = (vim_strchr(p, '(') != NULL); if (name == NULL && (fudi.fd_dict == NULL || !paren) && !eap->skip) { --- 4540,4548 ---- p = eap->arg; } ! 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,4696 **** if (get_function_args(&p, ')', &newargs, eap->cmdidx == CMD_def ? &argtypes : NULL, FALSE, NULL, &varargs, &default_args, eap->skip, ! eap, lines_to_free) == FAIL) goto errret_2; whitep = p; --- 4743,4749 ---- if (get_function_args(&p, ')', &newargs, eap->cmdidx == CMD_def ? &argtypes : NULL, FALSE, NULL, &varargs, &default_args, eap->skip, ! eap, class_arg, &newlines, lines_to_free) == FAIL) goto errret_2; whitep = p; *************** *** 5145,5151 **** garray_T lines_to_free; ga_init2(&lines_to_free, sizeof(char_u *), 50); ! (void)define_function(eap, NULL, &lines_to_free); ga_clear_strings(&lines_to_free); } --- 5198,5204 ---- garray_T lines_to_free; ga_init2(&lines_to_free, sizeof(char_u *), 50); ! (void)define_function(eap, NULL, &lines_to_free, NULL); ga_clear_strings(&lines_to_free); } *** ../vim-9.0.1030/src/proto/userfunc.pro 2022-10-01 15:32:42.539442245 +0100 --- src/proto/userfunc.pro 2022-12-07 15:29:14.772312232 +0000 *************** *** 7,12 **** --- 7,13 ---- 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,51 **** 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); 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); --- 46,52 ---- 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, 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); *** ../vim-9.0.1030/src/vim.h 2022-11-23 20:19:17.129682462 +0000 --- src/vim.h 2022-12-07 09:50:23.922950781 +0000 *************** *** 2120,2125 **** --- 2120,2127 ---- #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 *** ../vim-9.0.1030/src/vim9.h 2022-10-07 14:31:04.320852668 +0100 --- src/vim9.h 2022-12-08 12:40:20.798899365 +0000 *************** *** 32,37 **** --- 32,38 ---- 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,115 **** --- 111,117 ---- 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,468 **** --- 465,476 ---- 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,519 **** --- 522,528 ---- debug_T debug; deferins_T defer; echowin_T echowin; + construct_T construct; } isn_arg; }; *************** *** 757,763 **** int lhs_has_type; // type was specified type_T *lhs_type; ! type_T *lhs_member_type; int lhs_append; // used by ISN_REDIREND } lhs_T; --- 766,773 ---- int lhs_has_type; // type was specified type_T *lhs_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; *** ../vim-9.0.1030/src/vim9class.c 2022-12-04 20:11:12.791828025 +0000 --- src/vim9class.c 2022-12-08 14:07:59.048131829 +0000 *************** *** 27,35 **** void ex_class(exarg_T *eap) { ! int is_abstract = eap->cmdidx == CMD_abstract; char_u *arg = eap->arg; if (is_abstract) { if (STRNCMP(arg, "class", 5) != 0 || !VIM_ISWHITE(arg[5])) --- 27,42 ---- void ex_class(exarg_T *eap) { ! 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,82 **** semsg(_(e_class_name_must_start_with_uppercase_letter_str), arg); return; } // TODO: ! // generics: // extends SomeClass // implements SomeInterface // specifies SomeInterface ! // 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 } /* --- 52,337 ---- 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: // 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); + + // 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,110 **** --- 361,551 ---- // 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 *** ../vim-9.0.1030/src/proto/vim9class.pro 2022-12-04 20:11:12.791828025 +0000 --- src/proto/vim9class.pro 2022-12-08 12:55:49.451427123 +0000 *************** *** 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 : */ *** ../vim-9.0.1030/src/vim9compile.c 2022-11-22 18:12:40.833924528 +0000 --- src/vim9compile.c 2022-12-08 12:40:53.882939900 +0000 *************** *** 43,48 **** --- 43,62 ---- 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,302 **** { return (cctx != NULL && (lookup_local(name, len, NULL, cctx) == OK ! || arg_exists(name, len, NULL, NULL, NULL, cctx) == OK)) || script_var_exists(name, len, cctx, NULL) == OK || find_imported(name, len, FALSE) != NULL; } --- 310,320 ---- { return (cctx != NULL && (lookup_local(name, len, 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,963 **** goto theend; } ! ufunc = define_function(eap, lambda_name, lines_to_free); if (ufunc == NULL) { r = eap->skip ? OK : FAIL; --- 975,981 ---- goto theend; } ! ufunc = define_function(eap, lambda_name, lines_to_free, NULL); if (ufunc == NULL) { r = eap->skip ? OK : FAIL; *************** *** 1450,1455 **** --- 1468,1474 ---- 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,1515 **** else { // No specific kind of variable recognized, just a name. ! if (check_reserved_name(lhs->lhs_name) == FAIL) return FAIL; if (lookup_local(var_start, lhs->lhs_varlen, --- 1528,1534 ---- else { // No specific kind of variable recognized, just a name. ! if (check_reserved_name(lhs->lhs_name, cctx) == FAIL) return FAIL; if (lookup_local(var_start, lhs->lhs_varlen, *************** *** 1757,1764 **** lhs->lhs_type = &t_any; } ! if (lhs->lhs_type->tt_member == NULL) lhs->lhs_member_type = &t_any; else lhs->lhs_member_type = lhs->lhs_type->tt_member; } --- 1776,1791 ---- lhs->lhs_type = &t_any; } ! 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,1885 **** --- 1907,1917 ---- 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,2002 **** return FAIL; } ! if (lhs->lhs_type == &t_any) { // Index on variable of unknown type: check at runtime. dest_type = VAR_ANY; --- 2028,2034 ---- return FAIL; } ! if (lhs->lhs_type == NULL || lhs->lhs_type == &t_any) { // Index on variable of unknown type: check at runtime. dest_type = VAR_ANY; *************** *** 2042,2049 **** 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 (is_assign) { --- 2074,2085 ---- 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_CLASS ! || dest_type == VAR_OBJECT ! || dest_type == VAR_ANY) { if (is_assign) { *************** *** 2466,2471 **** --- 2502,2509 ---- 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,2902 **** --- 2935,2956 ---- 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,3513 **** { 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) { emsg(_(e_missing_return_statement)); goto erret; } // Return void if there is no return at the end. ! generate_instr(&cctx, ISN_RETURN_VOID); } // When compiled with ":silent!" and there was an error don't consider the --- 3554,3572 ---- { 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 ! && (ufunc->uf_flags & FC_NEW) != FC_NEW) { emsg(_(e_missing_return_statement)); goto erret; } // Return void if there is no return at the end. ! // 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 *** ../vim-9.0.1030/src/vim9execute.c 2022-11-25 16:31:46.968606662 +0000 --- src/vim9execute.c 2022-12-08 13:01:19.175468604 +0000 *************** *** 2029,2034 **** --- 2029,2035 ---- 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,2088 **** // Stack contains: // -3 value to be stored // -2 index ! // -1 dict or list tv = STACK_TV_BOT(-3); SOURCING_LNUM = iptr->isn_lnum; if (dest_type == VAR_ANY) --- 2083,2089 ---- // Stack contains: // -3 value to be stored // -2 index ! // -1 dict, list, blob or object tv = STACK_TV_BOT(-3); SOURCING_LNUM = iptr->isn_lnum; if (dest_type == VAR_ANY) *************** *** 2203,2208 **** --- 2204,2216 ---- 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,3006 **** --- 3009,3026 ---- 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,4106 **** goto on_error; break; ! // return from a :def function call without a value case ISN_RETURN_VOID: 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; // FALLTHROUGH // return from a :def function call with what is on the stack --- 4112,4136 ---- goto on_error; break; ! // 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; ! 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,4199 **** 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); ga_clear_strings(&lines_to_free); } break; --- 4223,4229 ---- 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, NULL); ga_clear_strings(&lines_to_free); } break; *************** *** 6018,6023 **** --- 6048,6058 ---- 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,6452 **** --- 6482,6490 ---- 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,6984 **** --- 7017,7024 ---- case VAR_ANY: case VAR_VOID: case VAR_INSTR: + case VAR_CLASS: + case VAR_OBJECT: break; } return FALSE; *** ../vim-9.0.1030/src/vim9expr.c 2022-11-06 18:27:09.363922860 +0000 --- src/vim9expr.c 2022-12-07 10:09:42.463753806 +0000 *************** *** 235,240 **** --- 235,242 ---- case VAR_JOB: case VAR_CHANNEL: case VAR_INSTR: + case VAR_CLASS: + case VAR_OBJECT: case VAR_UNKNOWN: case VAR_ANY: case VAR_VOID: *** ../vim-9.0.1030/src/vim9instr.c 2022-10-08 14:39:31.966903597 +0100 --- src/vim9instr.c 2022-12-08 12:38:37.182753606 +0000 *************** *** 114,119 **** --- 114,137 ---- } /* + * 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,168 **** --- 181,188 ---- case VAR_JOB: case VAR_CHANNEL: case VAR_INSTR: + case VAR_CLASS: + case VAR_OBJECT: to_string_error(type->tt_type); return FAIL; } *************** *** 2403,2408 **** --- 2423,2429 ---- case ISN_COMPARESPECIAL: case ISN_COMPARESTRING: case ISN_CONCAT: + case ISN_CONSTRUCT: case ISN_COND2BOOL: case ISN_DEBUG: case ISN_DEFER: *************** *** 2457,2462 **** --- 2478,2484 ---- case ISN_REDIRSTART: case ISN_RETURN: case ISN_RETURN_VOID: + case ISN_RETURN_OBJECT: case ISN_SHUFFLE: case ISN_SLICE: case ISN_SOURCE: *** ../vim-9.0.1030/src/proto/vim9instr.pro 2022-10-07 14:31:04.324852691 +0100 --- src/proto/vim9instr.pro 2022-12-08 12:40:52.634938418 +0000 *************** *** 3,8 **** --- 3,9 ---- 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); *** ../vim-9.0.1030/src/vim9script.c 2022-11-25 16:31:46.968606662 +0000 --- src/vim9script.c 2022-12-07 18:10:31.103199234 +0000 *************** *** 838,844 **** // 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) { vim_free(name); return p; --- 838,844 ---- // 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, NULL) == FAIL) { vim_free(name); return p; *************** *** 1126,1137 **** }; int ! check_reserved_name(char_u *name) { int idx; for (idx = 0; reserved[idx] != NULL; ++idx) ! if (STRCMP(reserved[idx], name) == 0) { semsg(_(e_cannot_use_reserved_name), name); return FAIL; --- 1126,1142 ---- }; int ! check_reserved_name(char_u *name, cctx_T *cctx) { int idx; for (idx = 0; reserved[idx] != NULL; ++idx) ! 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; *** ../vim-9.0.1030/src/proto/vim9script.pro 2022-08-24 16:30:30.690752449 +0100 --- src/proto/vim9script.pro 2022-12-07 17:56:10.911342014 +0000 *************** *** 19,23 **** 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); /* vim: set ft=c : */ --- 19,23 ---- 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, cctx_T *cctx); /* vim: set ft=c : */ *** ../vim-9.0.1030/src/vim9type.c 2022-10-09 12:55:29.590190644 +0100 --- src/vim9type.c 2022-12-08 13:45:12.936060332 +0000 *************** *** 29,35 **** * Allocate memory for a type_T and add the pointer to type_gap, so that it can * be easily freed later. */ ! static type_T * get_type_ptr(garray_T *type_gap) { type_T *type; --- 29,35 ---- * Allocate memory for a type_T and add the pointer to type_gap, so that it can * be easily freed later. */ ! type_T * get_type_ptr(garray_T *type_gap) { type_T *type; *************** *** 94,100 **** *ret = *type; if (ret->tt_member != NULL) ! ret->tt_member = alloc_type(ret->tt_member); if (type->tt_args != NULL) { int i; --- 94,105 ---- *ret = *type; if (ret->tt_member != NULL) ! { ! // 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,130 **** free_type(type->tt_args[i]); vim_free(type->tt_args); } ! free_type(type->tt_member); vim_free(type); } --- 129,139 ---- free_type(type->tt_args[i]); vim_free(type->tt_args); } ! ! // 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,1208 **** --- 1212,1219 ---- 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,1456 **** --- 1462,1469 ---- 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"; *** ../vim-9.0.1030/src/proto/vim9type.pro 2022-10-09 12:55:29.590190644 +0100 --- src/proto/vim9type.pro 2022-12-07 21:22:10.052795209 +0000 *************** *** 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); *** ../vim-9.0.1030/src/viminfo.c 2022-09-17 21:07:52.103993150 +0100 --- src/viminfo.c 2022-12-07 10:11:25.767756863 +0000 *************** *** 1370,1375 **** --- 1370,1377 ---- 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); *** ../vim-9.0.1030/src/testdir/Make_all.mak 2022-11-17 11:34:33.333726348 +0000 --- src/testdir/Make_all.mak 2022-12-06 18:52:19.893770307 +0000 *************** *** 37,42 **** --- 37,43 ---- TEST_VIM9 = \ test_vim9_assign \ test_vim9_builtin \ + test_vim9_class \ test_vim9_cmd \ test_vim9_disassemble \ test_vim9_expr \ *************** *** 48,53 **** --- 49,55 ---- 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 \ *** ../vim-9.0.1030/src/testdir/test_vim9_class.vim 2022-12-08 15:28:29.853992301 +0000 --- src/testdir/test_vim9_class.vim 2022-12-08 15:22:38.214234840 +0000 *************** *** 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 *** ../vim-9.0.1030/src/version.c 2022-12-08 12:00:32.414054432 +0000 --- src/version.c 2022-12-08 15:27:58.250013050 +0000 *************** *** 697,698 **** --- 697,700 ---- { /* Add new patch number below this line */ + /**/ + 1031, /**/ -- hundred-and-one symptoms of being an internet addict: 264. You turn to the teletext page "surfing report" and are surprised that it is about sizes of waves and a weather forecast for seaside resorts. /// Bram Moolenaar -- Bram@Moolenaar.net -- http://www.Moolenaar.net \\\ /// \\\ \\\ sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ /// \\\ help me help AIDS victims -- http://ICCF-Holland.org ///