]> git.ipfire.org Git - thirdparty/vim.git/commitdiff
patch 9.0.1031: Vim9 class is not implemented yet v9.0.1031
authorBram Moolenaar <Bram@vim.org>
Thu, 8 Dec 2022 15:32:33 +0000 (15:32 +0000)
committerBram Moolenaar <Bram@vim.org>
Thu, 8 Dec 2022 15:32:33 +0000 (15:32 +0000)
Problem:    Vim9 class is not implemented yet.
Solution:   Add very basic class support.

29 files changed:
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/proto/userfunc.pro
src/proto/vim9class.pro
src/proto/vim9instr.pro
src/proto/vim9script.pro
src/proto/vim9type.pro
src/structs.h
src/testdir/Make_all.mak
src/testdir/test_vim9_class.vim [new file with mode: 0644]
src/testing.c
src/typval.c
src/userfunc.c
src/version.c
src/vim.h
src/vim9.h
src/vim9class.c
src/vim9compile.c
src/vim9execute.c
src/vim9expr.c
src/vim9instr.c
src/vim9script.c
src/vim9type.c
src/viminfo.c

index cb86b4d1364ecc907c2925f885601792b8ddd002..b018a105c31f852fbd8458dae4ebd5061c7b9a25 100644 (file)
@@ -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 makes you wonder if you actually want
 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.
index 8d1feb39b6327c8a98ae65567b6ef99038cacf60..6230527a701b35fb72ed09d56341320c8fad1e00 100644 (file)
@@ -3346,4 +3346,28 @@ EXTERN char e_not_allowed_to_add_or_remove_entries_str[]
 #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
index 20883d430960c0f75471d112a7ccb59f5d26a9fc..917e0a852c1dec13ed73afe8e69b5e664788d8a3 100644 (file)
@@ -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_u *op)
            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, typval_T *rettv)
                    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 evaluate, int verbose)
        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:
index 84df6a62aab365b49107294d9c230c8befbe959e..3db4bb7b24b8c0fb7fb1a6104f7bc187caee89f1 100644 (file)
@@ -3770,6 +3770,12 @@ f_empty(typval_T *argvars, typval_T *rettv)
        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 *rettv)
 
     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 *rettv)
        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:
index 31cc760bf09703e619698439a0970dec8621d151..d9466691f40b9df9258b90b7d3d786c206b0efe0 100644 (file)
@@ -2264,6 +2264,8 @@ item_lock(typval_T *tv, int deep, int lock, int check_refcount)
        case VAR_JOB:
        case VAR_CHANNEL:
        case VAR_INSTR:
+       case VAR_CLASS:
+       case VAR_OBJECT:
            break;
 
        case VAR_BLOB:
index 5589af678e1c317cc701a3bccad17939d3dd2c2e..8dd7f09b55ef153d0659dc140b1e371ff33ba5e4 100644 (file)
@@ -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:
index 80120acb303235c223095d7ec2eaea5256e19668..9c084b460524de5662dd902152ed588560c61748 100644 (file)
@@ -308,6 +308,8 @@ json_encode_item(garray_T *gap, typval_T *val, int copyID, int options)
        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;
 
index ee17049ce6dad1053b39fb1303ee1c6e2d6d1289..5c865ce8ded2820ebff663247f78307a93c8b3c9 100644 (file)
@@ -7,6 +7,7 @@ char_u *register_cfunc(cfunc_T cb, cfunc_free_T cb_free, void *state);
 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 *funcname);
 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);
index 4e55178bda8d51ae1dea5f248ea926dfe097f519..4c6e12dab73991aae15b388fba8197d9ad943e6e 100644 (file)
@@ -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 : */
index 87914f36d260dfb7feba505b211510bfb43b0a7f..04d76822fe18f299ff9201dad456bbbe10a3aa09 100644 (file)
@@ -3,6 +3,7 @@ isn_T *generate_instr(cctx_T *cctx, isntype_T isn_type);
 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);
index 0e98039e8b41d54959f8e34b50171eeaef93ccf1..9a0bcdf37ca985fa24d7023f490041ec9fa4ef7a 100644 (file)
@@ -19,5 +19,5 @@ void update_vim9_script_var(int create, dictitem_T *di, char_u *name, int flags,
 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 : */
index 3c860928b12b20589c2d7590bec5e5275ea7608f..98c6091e98d0b74ad62ea475bcebe22a0f560e9a 100644 (file)
@@ -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);
index 497a35306e79be4663cbc18adb5eeb6b35ff7539..f7ae16b758df94843691092785088ec93235dcb6 100644 (file)
@@ -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 <SNR>123_ (<SNR> 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
index 1ac29b1b6a6b337fc1db0226a6efe0c89ec750c5..886bb08ad864b3ade78aa21c1c55122026c66df2 100644 (file)
@@ -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 (file)
index 0000000..6742ea7
--- /dev/null
@@ -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
index d76d098ee01f79ebab3b1f2232b8cb6bd4f02a87..74b6b0e81b29ca0917ade2ee5636988c727a3a2c 100644 (file)
@@ -1101,6 +1101,8 @@ f_test_refcount(typval_T *argvars, typval_T *rettv)
        case VAR_SPECIAL:
        case VAR_STRING:
        case VAR_INSTR:
+       case VAR_CLASS:
+       case VAR_OBJECT:
            break;
        case VAR_JOB:
 #ifdef FEAT_JOB_CHANNEL
index b1a0060467f2cf937f8a919e9c3fddf78a104f61..6faebe40154ee7e861294ce13ac812ac24adf1c8 100644 (file)
@@ -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, int *denote, int want_bool)
        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 *error)
        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 *varp, char_u *buf, int strict)
        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;
 
index 492c6721bcbb50d55faf48be48054d4ca424117e..5db0b709c2a67493c2a9b1bc839695d56060865e 100644 (file)
@@ -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 *name_arg, garray_T *lines_to_free)
            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 *name_arg, garray_T *lines_to_free)
     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);
 }
 
index f4399e5984b07e6b6d7397e21ade205a11c753a6..18187cd6d10f0a74348b7da232ef911595e95699 100644 (file)
@@ -695,6 +695,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    1031,
 /**/
     1030,
 /**/
index 67751ffbe65b1f563c81901bdde90ce3be027218..e241da0affb624097ed0b7199c47e60fc513f30f 100644 (file)
--- 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
 
index 095fd275519ed2e5c680d67ed481d636f9478309..96ab40e3b38a22892d41db36d87280e1f3e454a0 100644 (file)
@@ -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;
index b2307cb1b7f8da4eb0e5ae670c3620c8a6aec482..5804b129b797ee093b19aded760037d874f175fa 100644 (file)
     void
 ex_class(exarg_T *eap)
 {
-    int is_abstract = eap->cmdidx == CMD_abstract;
+    if (!current_script_is_vim9()
+               || (cmdmod.cmod_flags & CMOD_LEGACY)
+               || !getline_equal(eap->getline, eap->cookie, getsourceline))
+    {
+       emsg(_(e_class_can_only_be_defined_in_vim9_script));
+       return;
+    }
 
     char_u *arg = eap->arg;
+    int is_abstract = eap->cmdidx == CMD_abstract;
     if (is_abstract)
     {
        if (STRNCMP(arg, "class", 5) != 0 || !VIM_ISWHITE(arg[5]))
@@ -45,38 +52,286 @@ ex_class(exarg_T *eap)
        semsg(_(e_class_name_must_start_with_uppercase_letter_str), arg);
        return;
     }
+    char_u *name_end = find_name_end(arg, NULL, NULL, FNE_CHECK_START);
+    if (!IS_WHITE_OR_NUL(*name_end))
+    {
+       semsg(_(e_white_space_required_after_class_name_str), arg);
+       return;
+    }
 
     // TODO:
-    // generics: <Tkey, Tentry>
+    //    generics: <Tkey, Tentry>
     //    extends SomeClass
     //    implements SomeInterface
     //    specifies SomeInterface
+    //    check nothing follows
+
+    // TODO: handle "is_export" if it is set
+
+    garray_T   type_list;          // list of pointers to allocated types
+    ga_init2(&type_list, sizeof(type_T *), 10);
+
+    // Growarray with object members declared in the class.
+    garray_T objmembers;
+    ga_init2(&objmembers, sizeof(objmember_T), 10);
+
+    // Growarray with object methods declared in the class.
+    garray_T objmethods;
+    ga_init2(&objmethods, sizeof(ufunc_T), 10);
+
+    /*
+     * Go over the body of the class until "endclass" is found.
+     */
+    char_u *theline = NULL;
+    int success = FALSE;
+    for (;;)
+    {
+       vim_free(theline);
+       theline = eap->getline(':', eap->cookie, 0, GETLINE_CONCAT_ALL);
+       if (theline == NULL)
+           break;
+       char_u *line = skipwhite(theline);
+
+       // TODO:
+       // class members (public, read access, private):
+       //        static varname
+       //        public static varname
+       //        static _varname
+       //
+       // constructors:
+       //        def new()
+       //        enddef
+       //        def newOther()
+       //        enddef
+       //
+       // methods (object, class, generics):
+       //        def someMethod()
+       //        enddef
+       //        static def someMethod()
+       //        enddef
+       //        def <Tval> someMethod()
+       //        enddef
+       //        static def <Tval> someMethod()
+       //        enddef
+
+       char_u *p = line;
+       if (checkforcmd(&p, "endclass", 4))
+       {
+           if (STRNCMP(line, "endclass", 8) != 0)
+               semsg(_(e_command_cannot_be_shortened_str), line);
+           else if (*p == '|' || !ends_excmd2(line, p))
+               semsg(_(e_trailing_characters_str), p);
 
+           success = TRUE;
+           break;
+       }
+
+       // "this.varname"
+       // "this._varname"
+       // TODO:
+       //      "public this.varname"
+       if (STRNCMP(line, "this", 4) == 0)
+       {
+           if (line[4] != '.' || !eval_isnamec1(line[5]))
+           {
+               semsg(_(e_invalid_object_member_declaration_str), line);
+               break;
+           }
+           char_u *varname = line + 5;
+           char_u *varname_end = to_name_end(varname, FALSE);
+
+           char_u *colon = skipwhite(varname_end);
+           // TODO: accept initialization and figure out type from it
+           if (*colon != ':')
+           {
+               emsg(_(e_type_or_initialization_required));
+               break;
+           }
+           if (VIM_ISWHITE(*varname_end))
+           {
+               semsg(_(e_no_white_space_allowed_before_colon_str), varname);
+               break;
+           }
+           if (!VIM_ISWHITE(colon[1]))
+           {
+               semsg(_(e_white_space_required_after_str_str), ":", varname);
+               break;
+           }
+
+           char_u *type_arg = skipwhite(colon + 1);
+           type_T *type = parse_type(&type_arg, &type_list, TRUE);
+           if (type == NULL)
+               break;
 
-    // TODO: handle until "endclass" is found:
-    // object and class members (public, read access, private):
-    //   public this.varname
-    //   public static varname
-    //   this.varname
-    //   static varname
-    //   this._varname
-    //   static _varname
-    //
-    // constructors:
-    //   def new()
-    //   enddef
-    //   def newOther()
-    //   enddef
-    //
-    // methods (object, class, generics):
-    //   def someMethod()
-    //   enddef
-    //   static def someMethod()
-    //   enddef
-    //   def <Tval> someMethod()
-    //   enddef
-    //   static def <Tval> someMethod()
-    //   enddef
+           if (ga_grow(&objmembers, 1) == FAIL)
+               break;
+           objmember_T *m = ((objmember_T *)objmembers.ga_data)
+                                                         + objmembers.ga_len;
+           m->om_name = vim_strnsave(varname, varname_end - varname);
+           m->om_type = type;
+           ++objmembers.ga_len;
+       }
+
+       else
+       {
+           semsg(_(e_not_valid_command_in_class_str), line);
+           break;
+       }
+    }
+    vim_free(theline);
+
+    if (success)
+    {
+       class_T *cl = ALLOC_CLEAR_ONE(class_T);
+       if (cl == NULL)
+           goto cleanup;
+       cl->class_refcount = 1;
+       cl->class_name = vim_strnsave(arg, name_end - arg);
+
+       // Members are used by the new() function, add them here.
+       cl->class_obj_member_count = objmembers.ga_len;
+       cl->class_obj_members = ALLOC_MULT(objmember_T, objmembers.ga_len);
+       if (cl->class_name == NULL
+               || cl->class_obj_members == NULL)
+       {
+           vim_free(cl->class_name);
+           vim_free(cl->class_obj_members);
+           vim_free(cl);
+           goto cleanup;
+       }
+       mch_memmove(cl->class_obj_members, objmembers.ga_data,
+                                     sizeof(objmember_T) * objmembers.ga_len);
+       vim_free(objmembers.ga_data);
+
+       int have_new = FALSE;
+       for (int i = 0; i < objmethods.ga_len; ++i)
+           if (STRCMP((((ufunc_T *)objmethods.ga_data) + i)->uf_name,
+                                                                  "new") == 0)
+           {
+               have_new = TRUE;
+               break;
+           }
+       if (!have_new)
+       {
+           // No new() method was defined, add the default constructor.
+           garray_T fga;
+           ga_init2(&fga, 1, 1000);
+           ga_concat(&fga, (char_u *)"new(");
+           for (int i = 0; i < cl->class_obj_member_count; ++i)
+           {
+               if (i > 0)
+                   ga_concat(&fga, (char_u *)", ");
+               ga_concat(&fga, (char_u *)"this.");
+               objmember_T *m = cl->class_obj_members + i;
+               ga_concat(&fga, (char_u *)m->om_name);
+           }
+           ga_concat(&fga, (char_u *)")\nenddef\n");
+           ga_append(&fga, NUL);
+
+           exarg_T fea;
+           CLEAR_FIELD(fea);
+           fea.cmdidx = CMD_def;
+           fea.cmd = fea.arg = fga.ga_data;
+
+           garray_T lines_to_free;
+           ga_init2(&lines_to_free, sizeof(char_u *), 50);
+
+           ufunc_T *nf = define_function(&fea, NULL, &lines_to_free, cl);
+
+           ga_clear_strings(&lines_to_free);
+           vim_free(fga.ga_data);
+
+           if (nf != NULL && ga_grow(&objmethods, 1) == OK)
+           {
+               ((ufunc_T **)objmethods.ga_data)[objmethods.ga_len] = nf;
+               ++objmethods.ga_len;
+
+               nf->uf_flags |= FC_NEW;
+               nf->uf_class = cl;
+               nf->uf_ret_type = get_type_ptr(&type_list);
+               if (nf->uf_ret_type != NULL)
+               {
+                   nf->uf_ret_type->tt_type = VAR_OBJECT;
+                   nf->uf_ret_type->tt_member = (type_T *)cl;
+                   nf->uf_ret_type->tt_argcount = 0;
+                   nf->uf_ret_type->tt_args = NULL;
+               }
+               cl->class_new_func = nf;
+           }
+       }
+
+       cl->class_obj_method_count = objmethods.ga_len;
+       cl->class_obj_methods = ALLOC_MULT(ufunc_T *, objmethods.ga_len);
+       if (cl->class_obj_methods == NULL)
+       {
+           vim_free(cl->class_name);
+           vim_free(cl->class_obj_members);
+           vim_free(cl->class_obj_methods);
+           vim_free(cl);
+           goto cleanup;
+       }
+       mch_memmove(cl->class_obj_methods, objmethods.ga_data,
+                                       sizeof(ufunc_T *) * objmethods.ga_len);
+       vim_free(objmethods.ga_data);
+
+       cl->class_type.tt_type = VAR_CLASS;
+       cl->class_type.tt_member = (type_T *)cl;
+       cl->class_type_list = type_list;
+
+       // TODO:
+       // - Add the methods to the class
+       //      - array with ufunc_T pointers
+       // - Fill hashtab with object members and methods
+       // - Generate the default new() method, if needed.
+       // Later:
+       // - class members
+       // - class methods
+
+       // Add the class to the script-local variables.
+       typval_T tv;
+       tv.v_type = VAR_CLASS;
+       tv.vval.v_class = cl;
+       set_var_const(cl->class_name, current_sctx.sc_sid,
+                                            NULL, &tv, FALSE, ASSIGN_DECL, 0);
+       return;
+    }
+
+cleanup:
+    for (int i = 0; i < objmembers.ga_len; ++i)
+    {
+       objmember_T *m = ((objmember_T *)objmembers.ga_data) + i;
+       vim_free(m->om_name);
+    }
+    ga_clear(&objmembers);
+
+    ga_clear(&objmethods);
+    clear_type_list(&type_list);
+}
+
+/*
+ * Find member "name" in class "cl" and return its type.
+ * When not found t_any is returned.
+ */
+    type_T *
+class_member_type(
+       class_T *cl,
+       char_u  *name,
+       char_u  *name_end,
+       int     *member_idx)
+{
+    *member_idx = -1;  // not found (yet)
+    size_t len = name_end - name;
+
+    for (int i = 0; i < cl->class_obj_member_count; ++i)
+    {
+       objmember_T *m = cl->class_obj_members + i;
+       if (STRNCMP(m->om_name, name, len) == 0 && m->om_name[len] == NUL)
+       {
+           *member_idx = i;
+           return m->om_type;
+       }
+    }
+    return &t_any;
 }
 
 /*
@@ -106,5 +361,191 @@ ex_type(exarg_T *eap UNUSED)
     // TODO
 }
 
+/*
+ * Evaluate what comes after a class:
+ * - class member: SomeClass.varname
+ * - class method: SomeClass.SomeMethod()
+ * - class constructor: SomeClass.new()
+ * - object member: someObject.varname
+ * - object method: someObject.SomeMethod()
+ *
+ * "*arg" points to the '.'.
+ * "*arg" is advanced to after the member name or method call.
+ *
+ * Returns FAIL or OK.
+ */
+    int
+class_object_index(
+    char_u     **arg,
+    typval_T   *rettv,
+    evalarg_T  *evalarg,
+    int                verbose UNUSED) // give error messages
+{
+    // int             evaluate = evalarg != NULL
+    //                               && (evalarg->eval_flags & EVAL_EVALUATE);
+
+    if (VIM_ISWHITE((*arg)[1]))
+    {
+       semsg(_(e_no_white_space_allowed_after_str_str), ".", *arg);
+       return FAIL;
+    }
+
+    ++*arg;
+    char_u *name = *arg;
+    char_u *name_end = find_name_end(name, NULL, NULL, FNE_CHECK_START);
+    if (name_end == name)
+       return FAIL;
+    size_t len = name_end - name;
+
+    class_T *cl = rettv->v_type == VAR_CLASS ? rettv->vval.v_class
+                                            : rettv->vval.v_object->obj_class;
+    if (*name_end == '(')
+    {
+       for (int i = 0; i < cl->class_obj_method_count; ++i)
+       {
+           ufunc_T *fp = cl->class_obj_methods[i];
+           if (STRNCMP(name, fp->uf_name, len) == 0 && fp->uf_name[len] == NUL)
+           {
+               typval_T    argvars[MAX_FUNC_ARGS + 1];
+               int         argcount = 0;
+
+               char_u *argp = name_end;
+               int ret = get_func_arguments(&argp, evalarg, 0,
+                                                          argvars, &argcount);
+               if (ret == FAIL)
+                   return FAIL;
+
+               funcexe_T   funcexe;
+               CLEAR_FIELD(funcexe);
+               funcexe.fe_evaluate = TRUE;
+
+               // Call the user function.  Result goes into rettv;
+               // TODO: pass the object
+               rettv->v_type = VAR_UNKNOWN;
+               int error = call_user_func_check(fp, argcount, argvars,
+                                                       rettv, &funcexe, NULL);
+
+               // Clear the arguments.
+               for (int idx = 0; idx < argcount; ++idx)
+                   clear_tv(&argvars[idx]);
+
+               if (error != FCERR_NONE)
+               {
+                   user_func_error(error, printable_func_name(fp),
+                                                        funcexe.fe_found_var);
+                   return FAIL;
+               }
+               *arg = argp;
+               return OK;
+           }
+       }
+
+       semsg(_(e_method_not_found_on_class_str_str), cl->class_name, name);
+    }
+
+    else if (rettv->v_type == VAR_OBJECT)
+    {
+       for (int i = 0; i < cl->class_obj_member_count; ++i)
+       {
+           objmember_T *m = &cl->class_obj_members[i];
+           if (STRNCMP(name, m->om_name, len) == 0 && m->om_name[len] == NUL)
+           {
+               // The object only contains a pointer to the class, the member
+               // values array follows right after that.
+               object_T *obj = rettv->vval.v_object;
+               typval_T *tv = (typval_T *)(obj + 1) + i;
+               copy_tv(tv, rettv);
+               object_unref(obj);
+
+               *arg = name_end;
+               return OK;
+           }
+       }
+
+       semsg(_(e_member_not_found_on_object_str_str), cl->class_name, name);
+    }
+
+    // TODO: class member
+
+    return FAIL;
+}
+
+/*
+ * Make a copy of an object.
+ */
+    void
+copy_object(typval_T *from, typval_T *to)
+{
+    *to = *from;
+    if (to->vval.v_object != NULL)
+       ++to->vval.v_object->obj_refcount;
+}
+
+/*
+ * Free an object.
+ */
+    static void
+object_clear(object_T *obj)
+{
+    class_T *cl = obj->obj_class;
+
+    // the member values are just after the object structure
+    typval_T *tv = (typval_T *)(obj + 1);
+    for (int i = 0; i < cl->class_obj_member_count; ++i)
+       clear_tv(tv + i);
+
+    vim_free(obj);
+}
+
+/*
+ * Unreference an object.
+ */
+    void
+object_unref(object_T *obj)
+{
+    if (obj != NULL && --obj->obj_refcount <= 0)
+       object_clear(obj);
+}
+
+/*
+ * Make a copy of a class.
+ */
+    void
+copy_class(typval_T *from, typval_T *to)
+{
+    *to = *from;
+    if (to->vval.v_class != NULL)
+       ++to->vval.v_class->class_refcount;
+}
+
+/*
+ * Unreference a class.  Free it when the reference count goes down to zero.
+ */
+    void
+class_unref(typval_T *tv)
+{
+    class_T *cl = tv->vval.v_class;
+    if (cl != NULL && --cl->class_refcount <= 0)
+    {
+       vim_free(cl->class_name);
+
+       for (int i = 0; i < cl->class_obj_member_count; ++i)
+       {
+           objmember_T *m = &cl->class_obj_members[i];
+           vim_free(m->om_name);
+       }
+       vim_free(cl->class_obj_members);
+
+       vim_free(cl->class_obj_methods);
+
+       if (cl->class_new_func != NULL)
+           func_ptr_unref(cl->class_new_func);
+
+       clear_type_list(&cl->class_type_list);
+
+       vim_free(cl);
+    }
+}
+
 
 #endif // FEAT_EVAL
index 17066b0e9cc7806b00a3da094b1ea51af172d591..ab9e965690ec875e290b4911ae9e8d1ccbcb269b 100644 (file)
@@ -43,6 +43,20 @@ lookup_local(char_u *name, size_t len, lvar_T *lvar, cctx_T *cctx)
     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, cctx_T *cctx)
 {
     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, cctx_T *cctx, garray_T *lines_to_free)
        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 *eap, cmdidx_T cmdidx, cctx_T *cctx)
                    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
index 217147a7486de823df9cdc7b55a559c848e3647d..b164502576354223bfe8edb27b6d5d90946e90b4 100644 (file)
@@ -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 *ectx)
     // 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 *ectx)
                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 *instr, int instr_count, ufunc_T *ufunc)
 
        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 *instr, int instr_count, ufunc_T *ufunc)
            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;
index 7a089e973347e60d6efead9b23928aa76464a898..0e2e8eac9423f14bbdfe0038757430057a3c4f3e 100644 (file)
@@ -235,6 +235,8 @@ compile_member(int is_slice, int *keeping_dict, cctx_T *cctx)
            case VAR_JOB:
            case VAR_CHANNEL:
            case VAR_INSTR:
+           case VAR_CLASS:
+           case VAR_OBJECT:
            case VAR_UNKNOWN:
            case VAR_ANY:
            case VAR_VOID:
index 600fab466120b32992b135a85c2ebc17c8dbb422..cd5597db6417061bc29ad9f3b1b60729ff70d05a 100644 (file)
@@ -113,6 +113,24 @@ generate_instr_debug(cctx_T *cctx)
     return isn;
 }
 
+/*
+ * 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.
@@ -163,6 +181,8 @@ may_generate_2STRING(int offset, int tolerant, cctx_T *cctx)
        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:
index 1d7eea0d404aeff2aeb28d96dca2e8ff6f6229f9..feb9b18aa1205aaf1a42e67f834002a91da255b8 100644 (file)
@@ -838,7 +838,7 @@ vim9_declare_scriptvar(exarg_T *eap, char_u *arg)
     // 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;
index c8f571dfaea31473a17697b7a4ae3b6781adff52..b25541122228458fa23723094cd903381a5529ba 100644 (file)
@@ -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, int flags)
        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";
index 58494d4d0a2732c2e1c449eb076f875c8316fdcc..2e89fb5859b82491b683f8181c4b8cc16f6215df 100644 (file)
@@ -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);