1
0
forked from aniani/vim

patch 9.0.1031: Vim9 class is not implemented yet

Problem:    Vim9 class is not implemented yet.
Solution:   Add very basic class support.
This commit is contained in:
Bram Moolenaar 2022-12-08 15:32:33 +00:00
parent 038e6d20e6
commit 00b28d6c23
29 changed files with 1066 additions and 74 deletions

View 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.

View 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

View 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:

View 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:

View 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:

View 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:

View 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;

View 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);

View 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 : */

View 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);

View 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 : */

View 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);

View 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

View 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 \

View File

@ -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

View 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

View 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;

View 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);
}

View File

@ -695,6 +695,8 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
1031,
/**/
1030,
/**/

View File

@ -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

View 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;

View File

@ -27,9 +27,16 @@
void
ex_class(exarg_T *eap)
{
int is_abstract = eap->cmdidx == CMD_abstract;
if (!current_script_is_vim9()
|| (cmdmod.cmod_flags & CMOD_LEGACY)
|| !getline_equal(eap->getline, eap->cookie, getsourceline))
{
emsg(_(e_class_can_only_be_defined_in_vim9_script));
return;
}
char_u *arg = eap->arg;
int is_abstract = eap->cmdidx == CMD_abstract;
if (is_abstract)
{
if (STRNCMP(arg, "class", 5) != 0 || !VIM_ISWHITE(arg[5]))
@ -45,38 +52,286 @@ ex_class(exarg_T *eap)
semsg(_(e_class_name_must_start_with_uppercase_letter_str), arg);
return;
}
char_u *name_end = find_name_end(arg, NULL, NULL, FNE_CHECK_START);
if (!IS_WHITE_OR_NUL(*name_end))
{
semsg(_(e_white_space_required_after_class_name_str), arg);
return;
}
// TODO:
// generics: <Tkey, Tentry>
// generics: <Tkey, Tentry>
// extends SomeClass
// implements SomeInterface
// specifies SomeInterface
// check nothing follows
// TODO: handle "is_export" if it is set
// 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
garray_T type_list; // list of pointers to allocated types
ga_init2(&type_list, sizeof(type_T *), 10);
// Growarray with object members declared in the class.
garray_T objmembers;
ga_init2(&objmembers, sizeof(objmember_T), 10);
// Growarray with object methods declared in the class.
garray_T objmethods;
ga_init2(&objmethods, sizeof(ufunc_T), 10);
/*
* Go over the body of the class until "endclass" is found.
*/
char_u *theline = NULL;
int success = FALSE;
for (;;)
{
vim_free(theline);
theline = eap->getline(':', eap->cookie, 0, GETLINE_CONCAT_ALL);
if (theline == NULL)
break;
char_u *line = skipwhite(theline);
// TODO:
// class members (public, read access, private):
// static varname
// public static varname
// static _varname
//
// constructors:
// def new()
// enddef
// def newOther()
// enddef
//
// methods (object, class, generics):
// def someMethod()
// enddef
// static def someMethod()
// enddef
// def <Tval> someMethod()
// enddef
// static def <Tval> someMethod()
// enddef
char_u *p = line;
if (checkforcmd(&p, "endclass", 4))
{
if (STRNCMP(line, "endclass", 8) != 0)
semsg(_(e_command_cannot_be_shortened_str), line);
else if (*p == '|' || !ends_excmd2(line, p))
semsg(_(e_trailing_characters_str), p);
success = TRUE;
break;
}
// "this.varname"
// "this._varname"
// TODO:
// "public this.varname"
if (STRNCMP(line, "this", 4) == 0)
{
if (line[4] != '.' || !eval_isnamec1(line[5]))
{
semsg(_(e_invalid_object_member_declaration_str), line);
break;
}
char_u *varname = line + 5;
char_u *varname_end = to_name_end(varname, FALSE);
char_u *colon = skipwhite(varname_end);
// TODO: accept initialization and figure out type from it
if (*colon != ':')
{
emsg(_(e_type_or_initialization_required));
break;
}
if (VIM_ISWHITE(*varname_end))
{
semsg(_(e_no_white_space_allowed_before_colon_str), varname);
break;
}
if (!VIM_ISWHITE(colon[1]))
{
semsg(_(e_white_space_required_after_str_str), ":", varname);
break;
}
char_u *type_arg = skipwhite(colon + 1);
type_T *type = parse_type(&type_arg, &type_list, TRUE);
if (type == NULL)
break;
if (ga_grow(&objmembers, 1) == FAIL)
break;
objmember_T *m = ((objmember_T *)objmembers.ga_data)
+ objmembers.ga_len;
m->om_name = vim_strnsave(varname, varname_end - varname);
m->om_type = type;
++objmembers.ga_len;
}
else
{
semsg(_(e_not_valid_command_in_class_str), line);
break;
}
}
vim_free(theline);
if (success)
{
class_T *cl = ALLOC_CLEAR_ONE(class_T);
if (cl == NULL)
goto cleanup;
cl->class_refcount = 1;
cl->class_name = vim_strnsave(arg, name_end - arg);
// 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

View 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

View 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;

View 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:

View 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:

View 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;

View 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";

View 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);