diff options
Diffstat (limited to 'src/nvim/eval')
-rw-r--r-- | src/nvim/eval/buffer.c | 43 | ||||
-rw-r--r-- | src/nvim/eval/buffer.h | 9 | ||||
-rw-r--r-- | src/nvim/eval/decode.c | 34 | ||||
-rw-r--r-- | src/nvim/eval/decode.h | 12 | ||||
-rw-r--r-- | src/nvim/eval/encode.c | 29 | ||||
-rw-r--r-- | src/nvim/eval/encode.h | 11 | ||||
-rw-r--r-- | src/nvim/eval/executor.c | 11 | ||||
-rw-r--r-- | src/nvim/eval/executor.h | 8 | ||||
-rw-r--r-- | src/nvim/eval/funcs.c | 2257 | ||||
-rw-r--r-- | src/nvim/eval/funcs.h | 22 | ||||
-rw-r--r-- | src/nvim/eval/gc.c | 3 | ||||
-rw-r--r-- | src/nvim/eval/gc.h | 7 | ||||
-rw-r--r-- | src/nvim/eval/typval.c | 1382 | ||||
-rw-r--r-- | src/nvim/eval/typval.h | 117 | ||||
-rw-r--r-- | src/nvim/eval/typval_defs.h | 109 | ||||
-rw-r--r-- | src/nvim/eval/typval_encode.c.h | 109 | ||||
-rw-r--r-- | src/nvim/eval/typval_encode.h | 53 | ||||
-rw-r--r-- | src/nvim/eval/userfunc.c | 1170 | ||||
-rw-r--r-- | src/nvim/eval/userfunc.h | 27 | ||||
-rw-r--r-- | src/nvim/eval/vars.c | 1074 | ||||
-rw-r--r-- | src/nvim/eval/vars.h | 12 | ||||
-rw-r--r-- | src/nvim/eval/window.c | 113 | ||||
-rw-r--r-- | src/nvim/eval/window.h | 11 |
23 files changed, 3708 insertions, 2915 deletions
diff --git a/src/nvim/eval/buffer.c b/src/nvim/eval/buffer.c index 2f37d1ba2e..c60a104381 100644 --- a/src/nvim/eval/buffer.c +++ b/src/nvim/eval/buffer.c @@ -1,12 +1,9 @@ -// This is an open source non-commercial project. Dear PVS-Studio, please check -// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com - // eval/buffer.c: Buffer related builtin functions #include <stdbool.h> #include <string.h> -#include "nvim/ascii.h" +#include "nvim/ascii_defs.h" #include "nvim/autocmd.h" #include "nvim/buffer.h" #include "nvim/buffer_defs.h" @@ -17,17 +14,18 @@ #include "nvim/eval/funcs.h" #include "nvim/eval/typval.h" #include "nvim/eval/typval_defs.h" +#include "nvim/func_attr.h" #include "nvim/globals.h" -#include "nvim/macros.h" +#include "nvim/macros_defs.h" #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/move.h" #include "nvim/path.h" -#include "nvim/pos.h" +#include "nvim/pos_defs.h" #include "nvim/sign.h" -#include "nvim/types.h" +#include "nvim/types_defs.h" #include "nvim/undo.h" -#include "nvim/vim.h" +#include "nvim/vim_defs.h" typedef struct { win_T *cob_curwin_save; @@ -126,7 +124,7 @@ static void set_buffer_lines(buf_T *buf, linenr_T lnum_arg, bool append, typval_ FUNC_ATTR_NONNULL_ARG(4, 5) { linenr_T lnum = lnum_arg + (append ? 1 : 0); - long added = 0; + int added = 0; // When using the current buffer ml_mfp will be set if needed. Useful when // setline() is used on startup. For other buffers the buffer must be @@ -160,10 +158,7 @@ static void set_buffer_lines(buf_T *buf, linenr_T lnum_arg, bool append, typval_ if (lines->v_type == VAR_LIST) { l = lines->vval.v_list; if (l == NULL || tv_list_len(l) == 0) { - // set proper return code - if (lnum > curbuf->b_ml.ml_line_count) { - rettv->vval.v_number = 1; // FAIL - } + // not appending anything always succeeds goto cleanup; } li = tv_list_first(l); @@ -172,7 +167,7 @@ static void set_buffer_lines(buf_T *buf, linenr_T lnum_arg, bool append, typval_ } // Default result is zero == OK. - for (;;) { + while (true) { if (lines->v_type == VAR_LIST) { // List argument, get next string. if (li == NULL) { @@ -304,7 +299,10 @@ void f_bufload(typval_T *argvars, typval_T *unused, EvalFuncData fptr) buf_T *buf = get_buf_arg(&argvars[0]); if (buf != NULL) { - buffer_ensure_loaded(buf); + if (swap_exists_action != SEA_READONLY) { + swap_exists_action = SEA_NONE; + } + buf_ensure_loaded(buf); } } @@ -442,7 +440,7 @@ void f_deletebufline(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) if (last > curbuf->b_ml.ml_line_count) { last = curbuf->b_ml.ml_line_count; } - const long count = last - first + 1; + const int count = last - first + 1; // When coming here from Insert mode, sync undo, so that this can be // undone separately from what was previously inserted. @@ -487,8 +485,7 @@ static dict_T *get_buffer_info(buf_T *buf) dict_T *const dict = tv_dict_alloc(); tv_dict_add_nr(dict, S_LEN("bufnr"), buf->b_fnum); - tv_dict_add_str(dict, S_LEN("name"), - buf->b_ffname != NULL ? (const char *)buf->b_ffname : ""); + tv_dict_add_str(dict, S_LEN("name"), buf->b_ffname != NULL ? buf->b_ffname : ""); tv_dict_add_nr(dict, S_LEN("lnum"), buf == curbuf ? curwin->w_cursor.lnum : buflist_findlnum(buf)); tv_dict_add_nr(dict, S_LEN("linecount"), buf->b_ml.ml_line_count); @@ -496,8 +493,7 @@ static dict_T *get_buffer_info(buf_T *buf) tv_dict_add_nr(dict, S_LEN("listed"), buf->b_p_bl); tv_dict_add_nr(dict, S_LEN("changed"), bufIsChanged(buf)); tv_dict_add_nr(dict, S_LEN("changedtick"), buf_get_changedtick(buf)); - tv_dict_add_nr(dict, S_LEN("hidden"), - buf->b_ml.ml_mfp != NULL && buf->b_nwindows == 0); + tv_dict_add_nr(dict, S_LEN("hidden"), buf->b_ml.ml_mfp != NULL && buf->b_nwindows == 0); // Get a reference to buffer variables tv_dict_add_dict(dict, S_LEN("variables"), buf->b_vars); @@ -511,7 +507,7 @@ static dict_T *get_buffer_info(buf_T *buf) } tv_dict_add_list(dict, S_LEN("windows"), windows); - if (buf->b_signlist != NULL) { + if (buf->b_signs) { // List of signs placed in this buffer tv_dict_add_list(dict, S_LEN("signs"), get_buffer_signs(buf)); } @@ -609,13 +605,12 @@ static void get_buffer_lines(buf_T *buf, linenr_T start, linenr_T end, int retli } tv_list_alloc_ret(rettv, end - start + 1); while (start <= end) { - tv_list_append_string(rettv->vval.v_list, - (const char *)ml_get_buf(buf, start++, false), -1); + tv_list_append_string(rettv->vval.v_list, ml_get_buf(buf, start++), -1); } } else { rettv->v_type = VAR_STRING; rettv->vval.v_string = ((start >= 1 && start <= buf->b_ml.ml_line_count) - ? xstrdup(ml_get_buf(buf, start, false)) : NULL); + ? xstrdup(ml_get_buf(buf, start)) : NULL); } } diff --git a/src/nvim/eval/buffer.h b/src/nvim/eval/buffer.h index 4a2f8f9e94..1d346b99a5 100644 --- a/src/nvim/eval/buffer.h +++ b/src/nvim/eval/buffer.h @@ -1,10 +1,9 @@ -#ifndef NVIM_EVAL_BUFFER_H -#define NVIM_EVAL_BUFFER_H +#pragma once -#include "nvim/buffer_defs.h" -#include "nvim/eval/typval_defs.h" +#include "nvim/buffer_defs.h" // IWYU pragma: keep +#include "nvim/eval/typval_defs.h" // IWYU pragma: keep +#include "nvim/types_defs.h" // IWYU pragma: keep #ifdef INCLUDE_GENERATED_DECLARATIONS # include "eval/buffer.h.generated.h" #endif -#endif // NVIM_EVAL_BUFFER_H diff --git a/src/nvim/eval/decode.c b/src/nvim/eval/decode.c index cd1479f150..03f79fca84 100644 --- a/src/nvim/eval/decode.c +++ b/src/nvim/eval/decode.c @@ -1,6 +1,3 @@ -// This is an open source non-commercial project. Dear PVS-Studio, please check -// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com - #include <assert.h> #include <msgpack/object.h> #include <stdbool.h> @@ -10,22 +7,22 @@ #include <string.h> #include "klib/kvec.h" -#include "nvim/ascii.h" +#include "nvim/ascii_defs.h" #include "nvim/charset.h" #include "nvim/eval.h" #include "nvim/eval/decode.h" #include "nvim/eval/encode.h" #include "nvim/eval/typval.h" #include "nvim/eval/typval_defs.h" +#include "nvim/func_attr.h" #include "nvim/garray.h" #include "nvim/gettext.h" -#include "nvim/hashtab.h" -#include "nvim/macros.h" +#include "nvim/macros_defs.h" #include "nvim/mbyte.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/types.h" -#include "nvim/vim.h" +#include "nvim/types_defs.h" +#include "nvim/vim_defs.h" /// Helper structure for container_struct typedef struct { @@ -148,7 +145,7 @@ static inline int json_decoder_pop(ValuesStackItem obj, ValuesStack *const stack assert(!(key.is_special_string || key.val.vval.v_string == NULL || *key.val.vval.v_string == NUL)); - dictitem_T *const obj_di = tv_dict_item_alloc((const char *)key.val.vval.v_string); + dictitem_T *const obj_di = tv_dict_item_alloc(key.val.vval.v_string); tv_clear(&key.val); if (tv_dict_add(last_container.container.vval.v_dict, obj_di) == FAIL) { @@ -179,8 +176,7 @@ static inline int json_decoder_pop(ValuesStackItem obj, ValuesStack *const stack && (obj.is_special_string || obj.val.vval.v_string == NULL || *obj.val.vval.v_string == NUL - || tv_dict_find(last_container.container.vval.v_dict, - (const char *)obj.val.vval.v_string, -1))) { + || tv_dict_find(last_container.container.vval.v_dict, obj.val.vval.v_string, -1))) { tv_clear(&obj.val); // Restart @@ -228,7 +224,7 @@ static inline int json_decoder_pop(ValuesStackItem obj, ValuesStack *const stack /// /// @param[out] ret_tv Address where new special dictionary is saved. /// @param[in] len Expected number of items to be populated before list -/// becomes accessible from VimL. It is still valid to +/// becomes accessible from Vimscript. It is still valid to /// underpopulate a list, value only controls how many elements /// will be allocated in advance. @see ListLenSpecials. /// @@ -439,7 +435,7 @@ static inline int parse_json_string(const char *const buf, const size_t buf_len, t += 4; uvarnumber_T ch; vim_str2nr(ubuf, NULL, NULL, - STR2NR_HEX | STR2NR_FORCE, NULL, &ch, 4, true); + STR2NR_HEX | STR2NR_FORCE, NULL, &ch, 4, true, NULL); if (ch == 0) { hasnul = true; } @@ -608,7 +604,7 @@ parse_json_number_check: // Convert integer varnumber_T nr; int num_len; - vim_str2nr(s, NULL, &num_len, 0, &nr, NULL, (int)(p - s), true); + vim_str2nr(s, NULL, &num_len, 0, &nr, NULL, (int)(p - s), true, NULL); if ((int)exp_num_len != num_len) { semsg(_("E685: internal error: while converting number \"%.*s\" " "to integer vim_str2nr consumed %i bytes in place of %zu"), @@ -646,7 +642,7 @@ parse_json_number_ret: } \ } while (0) -/// Convert JSON string into VimL object +/// Convert JSON string into Vimscript object /// /// @param[in] buf String to convert. UTF-8 encoding is assumed. /// @param[in] buf_len Length of the string. @@ -922,7 +918,7 @@ json_decode_string_ret: #undef DICT_LEN -/// Convert msgpack object to a VimL one +/// Convert msgpack object to a Vimscript one int msgpack_to_vim(const msgpack_object mobj, typval_T *const rettv) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT { @@ -966,7 +962,7 @@ int msgpack_to_vim(const msgpack_object mobj, typval_T *const rettv) } break; case MSGPACK_OBJECT_NEGATIVE_INTEGER: - if (mobj.via.i64 >= VARNUMBER_MIN) { // -V547 + if (mobj.via.i64 >= VARNUMBER_MIN) { *rettv = (typval_T) { .v_type = VAR_NUMBER, .v_lock = VAR_UNLOCKED, @@ -987,12 +983,8 @@ int msgpack_to_vim(const msgpack_object mobj, typval_T *const rettv) tv_list_append_number(list, (varnumber_T)(n & 0x7FFFFFFF)); } break; -#ifdef NVIM_MSGPACK_HAS_FLOAT32 case MSGPACK_OBJECT_FLOAT32: case MSGPACK_OBJECT_FLOAT64: -#else - case MSGPACK_OBJECT_FLOAT: -#endif *rettv = (typval_T) { .v_type = VAR_FLOAT, .v_lock = VAR_UNLOCKED, diff --git a/src/nvim/eval/decode.h b/src/nvim/eval/decode.h index f1be5a1f69..c0d10a469a 100644 --- a/src/nvim/eval/decode.h +++ b/src/nvim/eval/decode.h @@ -1,13 +1,11 @@ -#ifndef NVIM_EVAL_DECODE_H -#define NVIM_EVAL_DECODE_H +#pragma once -#include <msgpack.h> -#include <stddef.h> +#include <msgpack.h> // IWYU pragma: keep +#include <stddef.h> // IWYU pragma: keep -#include "nvim/eval/typval.h" -#include "nvim/globals.h" +#include "nvim/eval/typval_defs.h" // IWYU pragma: keep +#include "nvim/types_defs.h" // IWYU pragma: keep #ifdef INCLUDE_GENERATED_DECLARATIONS # include "eval/decode.h.generated.h" #endif -#endif // NVIM_EVAL_DECODE_H diff --git a/src/nvim/eval/encode.c b/src/nvim/eval/encode.c index c2f1eae8af..8505c30fad 100644 --- a/src/nvim/eval/encode.c +++ b/src/nvim/eval/encode.c @@ -1,9 +1,6 @@ -// This is an open source non-commercial project. Dear PVS-Studio, please check -// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com - /// @file encode.c /// -/// File containing functions for encoding and decoding VimL values. +/// File containing functions for encoding and decoding Vimscript values. /// /// Split out from eval.c. @@ -17,7 +14,7 @@ #include "klib/kvec.h" #include "msgpack/pack.h" -#include "nvim/ascii.h" +#include "nvim/ascii_defs.h" #include "nvim/eval.h" #include "nvim/eval/encode.h" #include "nvim/eval/typval.h" @@ -25,14 +22,14 @@ #include "nvim/garray.h" #include "nvim/gettext.h" #include "nvim/hashtab.h" -#include "nvim/macros.h" +#include "nvim/macros_defs.h" #include "nvim/math.h" #include "nvim/mbyte.h" #include "nvim/memory.h" #include "nvim/message.h" #include "nvim/strings.h" -#include "nvim/types.h" -#include "nvim/vim.h" // For _() +#include "nvim/types_defs.h" +#include "nvim/vim_defs.h" // For _() const char *const encode_bool_var_names[] = { [kBoolVarTrue] = "v:true", @@ -152,9 +149,9 @@ static int conv_error(const char *const msg, const MPConvStack *const mpstack, ? 0 : (v.data.l.li == NULL ? tv_list_len(v.data.l.list) - 1 - : (int)tv_list_idx_of_item(v.data.l.list, - TV_LIST_ITEM_PREV(v.data.l.list, - v.data.l.li)))); + : tv_list_idx_of_item(v.data.l.list, + TV_LIST_ITEM_PREV(v.data.l.list, + v.data.l.li)))); const listitem_T *const li = (v.data.l.li == NULL ? tv_list_last(v.data.l.list) : TV_LIST_ITEM_PREV(v.data.l.list, @@ -298,7 +295,7 @@ int encode_read_from_list(ListReaderState *const state, char *const buf, const s #define TYPVAL_ENCODE_CONV_STRING(tv, buf, len) \ do { \ - const char *const buf_ = (const char *)(buf); \ + const char *const buf_ = (buf); \ if ((buf) == NULL) { \ ga_concat(gap, "''"); \ } else { \ @@ -376,7 +373,7 @@ int encode_read_from_list(ListReaderState *const state, char *const buf, const s #define TYPVAL_ENCODE_CONV_FUNC_START(tv, fun) \ do { \ - const char *const fun_ = (const char *)(fun); \ + const char *const fun_ = (fun); \ if (fun_ == NULL) { \ internal_error("string(): NULL function name"); \ ga_concat(gap, "function(NULL"); \ @@ -418,7 +415,7 @@ int encode_read_from_list(ListReaderState *const state, char *const buf, const s ga_concat(gap, "v:null") #define TYPVAL_ENCODE_CONV_BOOL(tv, num) \ - ga_concat(gap, ((num)? "v:true": "v:false")) + ga_concat(gap, ((num) ? "v:true" : "v:false")) #define TYPVAL_ENCODE_CONV_UNSIGNED_NUMBER(tv, num) @@ -544,7 +541,7 @@ int encode_read_from_list(ListReaderState *const state, char *const buf, const s #undef TYPVAL_ENCODE_CONV_BOOL #define TYPVAL_ENCODE_CONV_BOOL(tv, num) \ - ga_concat(gap, ((num)? "true": "false")) + ga_concat(gap, ((num) ? "true" : "false")) #undef TYPVAL_ENCODE_CONV_UNSIGNED_NUMBER #define TYPVAL_ENCODE_CONV_UNSIGNED_NUMBER(tv, num) \ @@ -712,7 +709,7 @@ static inline int convert_to_json_string(garray_T *const gap, const char *const #undef TYPVAL_ENCODE_CONV_STRING #define TYPVAL_ENCODE_CONV_STRING(tv, buf, len) \ do { \ - if (convert_to_json_string(gap, (const char *)(buf), (len)) != OK) { \ + if (convert_to_json_string(gap, (buf), (len)) != OK) { \ return FAIL; \ } \ } while (0) diff --git a/src/nvim/eval/encode.h b/src/nvim/eval/encode.h index 41e7614fc0..26a3286f2b 100644 --- a/src/nvim/eval/encode.h +++ b/src/nvim/eval/encode.h @@ -1,5 +1,4 @@ -#ifndef NVIM_EVAL_ENCODE_H -#define NVIM_EVAL_ENCODE_H +#pragma once #include <msgpack.h> #include <msgpack/pack.h> @@ -9,10 +8,9 @@ #include "nvim/eval.h" #include "nvim/eval/typval.h" #include "nvim/eval/typval_defs.h" -#include "nvim/garray.h" -#include "nvim/vim.h" +#include "nvim/garray_defs.h" -/// Convert VimL value to msgpack string +/// Convert Vimscript value to msgpack string /// /// @param[out] packer Packer to save results in. /// @param[in] tv Dumped value. @@ -21,7 +19,7 @@ /// @return OK in case of success, FAIL otherwise. int encode_vim_to_msgpack(msgpack_packer *packer, typval_T *tv, const char *objname); -/// Convert VimL value to :echo output +/// Convert Vimscript value to :echo output /// /// @param[out] packer Packer to save results in. /// @param[in] tv Dumped value. @@ -74,4 +72,3 @@ extern const char *const encode_special_var_names[]; #ifdef INCLUDE_GENERATED_DECLARATIONS # include "eval/encode.h.generated.h" #endif -#endif // NVIM_EVAL_ENCODE_H diff --git a/src/nvim/eval/executor.c b/src/nvim/eval/executor.c index 9caea2fef1..dc23fcdc72 100644 --- a/src/nvim/eval/executor.c +++ b/src/nvim/eval/executor.c @@ -1,6 +1,3 @@ -// This is an open source non-commercial project. Dear PVS-Studio, please check -// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com - #include <inttypes.h> #include <stdlib.h> @@ -8,19 +5,21 @@ #include "nvim/eval/executor.h" #include "nvim/eval/typval.h" #include "nvim/eval/typval_defs.h" +#include "nvim/func_attr.h" #include "nvim/garray.h" #include "nvim/gettext.h" #include "nvim/globals.h" #include "nvim/message.h" #include "nvim/strings.h" -#include "nvim/types.h" -#include "nvim/vim.h" +#include "nvim/types_defs.h" +#include "nvim/vim_defs.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "eval/executor.c.generated.h" // IWYU pragma: export #endif -char *e_listidx = N_("E684: list index out of range: %" PRId64); +char *e_list_index_out_of_range_nr + = N_("E684: List index out of range: %" PRId64); /// Handle tv1 += tv2, -=, *=, /=, %=, .= /// diff --git a/src/nvim/eval/executor.h b/src/nvim/eval/executor.h index 3d789f76a5..d36ce08542 100644 --- a/src/nvim/eval/executor.h +++ b/src/nvim/eval/executor.h @@ -1,11 +1,9 @@ -#ifndef NVIM_EVAL_EXECUTOR_H -#define NVIM_EVAL_EXECUTOR_H +#pragma once -#include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" // IWYU pragma: keep -extern char *e_listidx; +extern char *e_list_index_out_of_range_nr; #ifdef INCLUDE_GENERATED_DECLARATIONS # include "eval/executor.h.generated.h" #endif -#endif // NVIM_EVAL_EXECUTOR_H diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index 6b580b5312..310ac29f88 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -1,6 +1,3 @@ -// This is an open source non-commercial project. Dear PVS-Studio, please check -// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com - #include <assert.h> #include <fcntl.h> #include <float.h> @@ -11,11 +8,13 @@ #include <msgpack/pack.h> #include <msgpack/unpack.h> #include <signal.h> +#include <stdarg.h> #include <stddef.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/stat.h> +#include <sys/types.h> #include <time.h> #include <uv.h> @@ -25,14 +24,15 @@ #include "nvim/api/private/dispatch.h" #include "nvim/api/private/helpers.h" #include "nvim/api/vim.h" -#include "nvim/ascii.h" -#include "nvim/assert.h" +#include "nvim/ascii_defs.h" +#include "nvim/assert_defs.h" #include "nvim/autocmd.h" #include "nvim/buffer.h" #include "nvim/buffer_defs.h" #include "nvim/channel.h" #include "nvim/charset.h" #include "nvim/cmdexpand.h" +#include "nvim/cmdexpand_defs.h" #include "nvim/context.h" #include "nvim/cursor.h" #include "nvim/diff.h" @@ -44,6 +44,7 @@ #include "nvim/eval/executor.h" #include "nvim/eval/funcs.h" #include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" #include "nvim/eval/userfunc.h" #include "nvim/eval/vars.h" #include "nvim/eval/window.h" @@ -57,41 +58,40 @@ #include "nvim/ex_getln.h" #include "nvim/file_search.h" #include "nvim/fileio.h" +#include "nvim/func_attr.h" #include "nvim/garray.h" #include "nvim/getchar.h" #include "nvim/gettext.h" #include "nvim/globals.h" -#include "nvim/grid_defs.h" -#include "nvim/hashtab.h" +#include "nvim/grid.h" #include "nvim/highlight_defs.h" #include "nvim/highlight_group.h" #include "nvim/indent.h" #include "nvim/indent_c.h" #include "nvim/input.h" +#include "nvim/insexpand.h" #include "nvim/keycodes.h" #include "nvim/lua/executor.h" -#include "nvim/macros.h" +#include "nvim/macros_defs.h" #include "nvim/main.h" #include "nvim/mark.h" #include "nvim/math.h" #include "nvim/mbyte.h" -#include "nvim/memfile_defs.h" #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/menu.h" #include "nvim/message.h" -#include "nvim/mouse.h" #include "nvim/move.h" #include "nvim/msgpack_rpc/channel.h" -#include "nvim/msgpack_rpc/channel_defs.h" #include "nvim/msgpack_rpc/server.h" #include "nvim/normal.h" #include "nvim/ops.h" #include "nvim/option.h" +#include "nvim/option_vars.h" #include "nvim/optionstr.h" #include "nvim/os/dl.h" #include "nvim/os/fileio.h" -#include "nvim/os/fs_defs.h" +#include "nvim/os/fs.h" #include "nvim/os/os.h" #include "nvim/os/pty_process.h" #include "nvim/os/shell.h" @@ -100,7 +100,7 @@ #include "nvim/path.h" #include "nvim/plines.h" #include "nvim/popupmenu.h" -#include "nvim/pos.h" +#include "nvim/pos_defs.h" #include "nvim/profile.h" #include "nvim/regexp.h" #include "nvim/runtime.h" @@ -113,9 +113,8 @@ #include "nvim/syntax.h" #include "nvim/tag.h" #include "nvim/ui.h" -#include "nvim/undo.h" #include "nvim/version.h" -#include "nvim/vim.h" +#include "nvim/vim_defs.h" #include "nvim/window.h" /// Describe data to return from find_some_match() @@ -145,11 +144,17 @@ PRAGMA_DIAG_POP PRAGMA_DIAG_POP #endif -static char *e_listblobarg = N_("E899: Argument of %s must be a List or Blob"); -static char *e_invalwindow = N_("E957: Invalid window number"); -static char *e_reduceempty = N_("E998: Reduce of an empty %s with no initial value"); -static char e_using_number_as_bool_nr[] - = N_("E1023: Using a Number as a Bool: %d"); +static const char *e_listblobarg = N_("E899: Argument of %s must be a List or Blob"); +static const char *e_invalwindow = N_("E957: Invalid window number"); +static const char e_argument_of_str_must_be_list_string_or_dictionary[] + = N_("E706: Argument of %s must be a List, String or Dictionary"); +static const char e_invalid_submatch_number_nr[] + = N_("E935: Invalid submatch number: %d"); +static const char *e_reduceempty = N_("E998: Reduce of an empty %s with no initial value"); +static const char e_string_list_or_blob_required[] + = N_("E1098: String, List or Blob required"); +static const char e_missing_function_argument[] + = N_("E1132: Missing function argument"); /// Dummy va_list for passing to vim_snprintf /// @@ -226,6 +231,31 @@ const EvalFuncDef *find_internal_func(const char *const name) return index >= 0 ? &functions[index] : NULL; } +/// Check the argument count to use for internal function "fdef". +/// @return -1 for failure, 0 if no method base accepted, 1 if method base is +/// first argument, 2 if method base is second argument, etc. +int check_internal_func(const EvalFuncDef *const fdef, const int argcount) + FUNC_ATTR_NONNULL_ALL +{ + int res; + + if (argcount < fdef->min_argc) { + res = FCERR_TOOFEW; + } else if (argcount > fdef->max_argc) { + res = FCERR_TOOMANY; + } else { + return fdef->base_arg; + } + + const char *const name = fdef->name; + if (res == FCERR_TOOMANY) { + semsg(_(e_toomanyarg), name); + } else { + semsg(_(e_toofewarg), name); + } + return -1; +} + int call_internal_func(const char *const fname, const int argcount, typval_T *const argvars, typval_T *const rettv) FUNC_ATTR_NONNULL_ALL @@ -261,6 +291,9 @@ int call_internal_method(const char *const fname, const int argcount, typval_T * typval_T argv[MAX_FUNC_ARGS + 1]; const ptrdiff_t base_index = fdef->base_arg == BASE_LAST ? argcount : fdef->base_arg - 1; + if (argcount < base_index) { + return FCERR_TOOFEW; + } memcpy(argv, argvars, (size_t)base_index * sizeof(typval_T)); argv[base_index] = *basetv; memcpy(argv + base_index + 1, argvars + base_index, @@ -319,11 +352,12 @@ static void api_wrapper(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) Object result = handler.fn(VIML_INTERNAL_CALL, args, &res_arena, &err); if (ERROR_SET(&err)) { - semsg_multiline((const char *)e_api_error, err.msg); + semsg_multiline(e_api_error, err.msg); goto end; } if (!object_to_vim(result, rettv, &err)) { + assert(ERROR_SET(&err)); semsg(_("Error converting the call result: %s"), err.msg); } @@ -448,7 +482,7 @@ buf_T *tv_get_buf(typval_T *tv, int curtab_only) int save_magic = p_magic; p_magic = true; char *save_cpo = p_cpo; - p_cpo = empty_option; + p_cpo = empty_string_option; buf_T *buf = buflist_findnr(buflist_findpat(name, name + strlen(name), true, false, curtab_only)); @@ -492,7 +526,7 @@ buf_T *get_buf_arg(typval_T *arg) /// "byte2line(byte)" function static void f_byte2line(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - long boff = tv_get_number(&argvars[0]) - 1; + int boff = (int)tv_get_number(&argvars[0]) - 1; if (boff < 0) { rettv->vval.v_number = -1; } else { @@ -501,41 +535,6 @@ static void f_byte2line(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) } } -static void byteidx(typval_T *argvars, typval_T *rettv, int comp) -{ - const char *const str = tv_get_string_chk(&argvars[0]); - varnumber_T idx = tv_get_number_chk(&argvars[1], NULL); - rettv->vval.v_number = -1; - if (str == NULL || idx < 0) { - return; - } - - const char *t = str; - for (; idx > 0; idx--) { - if (*t == NUL) { // EOL reached. - return; - } - if (comp) { - t += utf_ptr2len(t); - } else { - t += utfc_ptr2len(t); - } - } - rettv->vval.v_number = (varnumber_T)(t - str); -} - -/// "byteidx()" function -static void f_byteidx(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - byteidx(argvars, rettv, false); -} - -/// "byteidxcomp()" function -static void f_byteidxcomp(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - byteidx(argvars, rettv, true); -} - /// "call(func, arglist [, dict])" function static void f_call(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { @@ -563,14 +562,13 @@ static void f_call(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) func = (char *)tv_get_string(&argvars[0]); } - if (*func == NUL) { - return; // type error or empty name + if (func == NULL || *func == NUL) { + return; // type error, empty name or null function } dict_T *selfdict = NULL; if (argvars[2].v_type != VAR_UNKNOWN) { - if (argvars[2].v_type != VAR_DICT) { - emsg(_(e_dictreq)); + if (tv_check_for_dict_arg(argvars, 2) == FAIL) { if (owned) { func_unref(func); } @@ -653,7 +651,7 @@ static void f_chansend(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) bool crlf = false; #else Channel *chan = find_channel(id); - bool crlf = (chan != NULL && chan->term) ? true: false; + bool crlf = (chan != NULL && chan->term) ? true : false; #endif if (argvars[1].v_type == VAR_BLOB) { @@ -761,53 +759,6 @@ static void f_charcol(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) get_col(argvars, rettv, true); } -/// "charidx()" function -static void f_charidx(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - rettv->vval.v_number = -1; - - if (argvars[0].v_type != VAR_STRING - || argvars[1].v_type != VAR_NUMBER - || (argvars[2].v_type != VAR_UNKNOWN - && argvars[2].v_type != VAR_NUMBER - && argvars[2].v_type != VAR_BOOL)) { - emsg(_(e_invarg)); - return; - } - - const char *str = tv_get_string_chk(&argvars[0]); - varnumber_T idx = tv_get_number_chk(&argvars[1], NULL); - if (str == NULL || idx < 0) { - return; - } - int countcc = 0; - if (argvars[2].v_type != VAR_UNKNOWN) { - countcc = (int)tv_get_number(&argvars[2]); - } - if (countcc < 0 || countcc > 1) { - semsg(_(e_using_number_as_bool_nr), countcc); - return; - } - - int (*ptr2len)(const char *); - if (countcc) { - ptr2len = utf_ptr2len; - } else { - ptr2len = utfc_ptr2len; - } - - const char *p; - int len; - for (p = str, len = 0; p <= str + idx; len++) { - if (*p == NUL) { - return; - } - p += ptr2len(p); - } - - rettv->vval.v_number = len > 0 ? len - 1 : 0; -} - /// "chdir(dir)" function static void f_chdir(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { @@ -925,8 +876,7 @@ static void f_confirm(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) } if (!error) { - rettv->vval.v_number = do_dialog(type, NULL, (char *)message, (char *)buttons, def, NULL, - false); + rettv->vval.v_number = do_dialog(type, NULL, message, buttons, def, NULL, false); } } @@ -936,10 +886,90 @@ static void f_copy(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) var_item_copy(NULL, &argvars[0], rettv, false, 0); } +/// Count the number of times "needle" occurs in string "haystack". +/// +/// @param ic ignore case +static varnumber_T count_string(const char *haystack, const char *needle, bool ic) +{ + varnumber_T n = 0; + const char *p = haystack; + + if (p == NULL || needle == NULL || *needle == NUL) { + return 0; + } + + if (ic) { + const size_t len = strlen(needle); + + while (*p != NUL) { + if (mb_strnicmp(p, needle, len) == 0) { + n++; + p += len; + } else { + MB_PTR_ADV(p); + } + } + } else { + const char *next; + while ((next = strstr(p, needle)) != NULL) { + n++; + p = next + strlen(needle); + } + } + + return n; +} + +/// Count the number of times item "needle" occurs in List "l" starting at index "idx". +/// +/// @param ic ignore case +static varnumber_T count_list(list_T *l, typval_T *needle, int64_t idx, bool ic) +{ + if (tv_list_len(l) == 0) { + return 0; + } + + listitem_T *li = tv_list_find(l, (int)idx); + if (li == NULL) { + semsg(_(e_list_index_out_of_range_nr), idx); + return 0; + } + + varnumber_T n = 0; + + for (; li != NULL; li = TV_LIST_ITEM_NEXT(l, li)) { + if (tv_equal(TV_LIST_ITEM_TV(li), needle, ic, false)) { + n++; + } + } + + return n; +} + +/// Count the number of times item "needle" occurs in Dict "d". +/// +/// @param ic ignore case +static varnumber_T count_dict(dict_T *d, typval_T *needle, bool ic) +{ + if (d == NULL) { + return 0; + } + + varnumber_T n = 0; + + TV_DICT_ITER(d, di, { + if (tv_equal(&di->di_tv, needle, ic, false)) { + n++; + } + }); + + return n; +} + /// "count()" function static void f_count(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - long n = 0; + varnumber_T n = 0; int ic = 0; bool error = false; @@ -947,78 +977,30 @@ static void f_count(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) ic = (int)tv_get_number_chk(&argvars[2], &error); } - if (argvars[0].v_type == VAR_STRING) { - const char *expr = tv_get_string_chk(&argvars[1]); - const char *p = argvars[0].vval.v_string; - - if (!error && expr != NULL && *expr != NUL && p != NULL) { - if (ic) { - const size_t len = strlen(expr); - - while (*p != NUL) { - if (mb_strnicmp((char *)p, (char *)expr, len) == 0) { - n++; - p += len; - } else { - MB_PTR_ADV(p); - } - } - } else { - char *next; - while ((next = strstr((char *)p, (char *)expr)) != NULL) { - n++; - p = next + strlen(expr); - } - } + if (!error && argvars[0].v_type == VAR_STRING) { + n = count_string(argvars[0].vval.v_string, tv_get_string_chk(&argvars[1]), ic); + } else if (!error && argvars[0].v_type == VAR_LIST) { + int64_t idx = 0; + if (argvars[2].v_type != VAR_UNKNOWN + && argvars[3].v_type != VAR_UNKNOWN) { + idx = (int64_t)tv_get_number_chk(&argvars[3], &error); } - } else if (argvars[0].v_type == VAR_LIST) { - list_T *l = argvars[0].vval.v_list; - - if (l != NULL) { - listitem_T *li = tv_list_first(l); - if (argvars[2].v_type != VAR_UNKNOWN) { - if (argvars[3].v_type != VAR_UNKNOWN) { - long idx = tv_get_number_chk(&argvars[3], &error); - if (!error) { - li = tv_list_find(l, (int)idx); - if (li == NULL) { - semsg(_(e_listidx), (int64_t)idx); - } - } - } - if (error) { - li = NULL; - } - } - - for (; li != NULL; li = TV_LIST_ITEM_NEXT(l, li)) { - if (tv_equal(TV_LIST_ITEM_TV(li), &argvars[1], ic, false)) { - n++; - } - } + if (!error) { + n = count_list(argvars[0].vval.v_list, &argvars[1], idx, ic); } - } else if (argvars[0].v_type == VAR_DICT) { + } else if (!error && argvars[0].v_type == VAR_DICT) { dict_T *d = argvars[0].vval.v_dict; if (d != NULL) { - if (argvars[2].v_type != VAR_UNKNOWN) { - if (argvars[3].v_type != VAR_UNKNOWN) { - emsg(_(e_invarg)); - } - } - - int todo = error ? 0 : (int)d->dv_hashtab.ht_used; - for (hashitem_T *hi = d->dv_hashtab.ht_array; todo > 0; hi++) { - if (!HASHITEM_EMPTY(hi)) { - todo--; - if (tv_equal(&TV_DICT_HI2DI(hi)->di_tv, &argvars[1], ic, false)) { - n++; - } - } + if (argvars[2].v_type != VAR_UNKNOWN + && argvars[3].v_type != VAR_UNKNOWN) { + emsg(_(e_invarg)); + } else { + n = count_dict(argvars[0].vval.v_dict, &argvars[1], ic); } } - } else { - semsg(_(e_listdictarg), "count()"); + } else if (!error) { + semsg(_(e_argument_of_str_must_be_list_string_or_dictionary), "count()"); } rettv->vval.v_number = n; } @@ -1042,7 +1024,7 @@ static void f_ctxget(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) Dictionary ctx_dict = ctx_to_dict(ctx); Error err = ERROR_INIT; - object_to_vim(DICTIONARY_OBJ(ctx_dict), rettv, &err); + (void)object_to_vim(DICTIONARY_OBJ(ctx_dict), rettv, &err); api_free_dictionary(ctx_dict); api_clear_error(&err); } @@ -1064,17 +1046,17 @@ static void f_ctxpush(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) TV_LIST_ITER(argvars[0].vval.v_list, li, { typval_T *tv_li = TV_LIST_ITEM_TV(li); if (tv_li->v_type == VAR_STRING) { - if (strequal((char *)tv_li->vval.v_string, "regs")) { + if (strequal(tv_li->vval.v_string, "regs")) { types |= kCtxRegs; - } else if (strequal((char *)tv_li->vval.v_string, "jumps")) { + } else if (strequal(tv_li->vval.v_string, "jumps")) { types |= kCtxJumps; - } else if (strequal((char *)tv_li->vval.v_string, "bufs")) { + } else if (strequal(tv_li->vval.v_string, "bufs")) { types |= kCtxBufs; - } else if (strequal((char *)tv_li->vval.v_string, "gvars")) { + } else if (strequal(tv_li->vval.v_string, "gvars")) { types |= kCtxGVars; - } else if (strequal((char *)tv_li->vval.v_string, "sfuncs")) { + } else if (strequal(tv_li->vval.v_string, "sfuncs")) { types |= kCtxSFuncs; - } else if (strequal((char *)tv_li->vval.v_string, "funcs")) { + } else if (strequal(tv_li->vval.v_string, "funcs")) { types |= kCtxFuncs; } } @@ -1108,14 +1090,16 @@ static void f_ctxset(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) return; } - int save_did_emsg = did_emsg; + const int save_did_emsg = did_emsg; did_emsg = false; Dictionary dict = vim_to_object(&argvars[0]).data.dictionary; Context tmp = CONTEXT_INIT; - ctx_from_dict(dict, &tmp); + Error err = ERROR_INIT; + ctx_from_dict(dict, &tmp, &err); - if (did_emsg) { + if (ERROR_SET(&err)) { + semsg("%s", err.msg); ctx_free(&tmp); } else { ctx_free(ctx); @@ -1123,6 +1107,7 @@ static void f_ctxset(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) } api_free_dictionary(dict); + api_clear_error(&err); did_emsg = save_did_emsg; } @@ -1134,12 +1119,13 @@ static void f_ctxsize(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) } /// Set the cursor position. -/// If 'charcol' is true, then use the column number as a character offset. +/// If "charcol" is true, then use the column number as a character offset. /// Otherwise use the column number as a byte offset. static void set_cursorpos(typval_T *argvars, typval_T *rettv, bool charcol) { - long lnum, col; - long coladd = 0; + linenr_T lnum; + colnr_T col; + colnr_T coladd = 0; bool set_curswant = true; rettv->vval.v_number = -1; @@ -1167,12 +1153,12 @@ static void set_cursorpos(typval_T *argvars, typval_T *rettv, bool charcol) } else if (lnum == 0) { lnum = curwin->w_cursor.lnum; } - col = (long)tv_get_number_chk(&argvars[1], NULL); + col = (colnr_T)tv_get_number_chk(&argvars[1], NULL); if (charcol) { - col = buf_charidx_to_byteidx(curbuf, (linenr_T)lnum, (int)col) + 1; + col = buf_charidx_to_byteidx(curbuf, lnum, (int)col) + 1; } if (argvars[2].v_type != VAR_UNKNOWN) { - coladd = (long)tv_get_number_chk(&argvars[2], NULL); + coladd = (colnr_T)tv_get_number_chk(&argvars[2], NULL); } } else { emsg(_(e_invarg)); @@ -1182,12 +1168,12 @@ static void set_cursorpos(typval_T *argvars, typval_T *rettv, bool charcol) return; // type error; errmsg already given } if (lnum > 0) { - curwin->w_cursor.lnum = (linenr_T)lnum; + curwin->w_cursor.lnum = lnum; } if (col > 0) { - curwin->w_cursor.col = (colnr_T)col - 1; + curwin->w_cursor.col = col - 1; } - curwin->w_cursor.coladd = (colnr_T)coladd; + curwin->w_cursor.coladd = coladd; // Make sure the cursor is in a valid position. check_cursor(); @@ -1236,18 +1222,16 @@ static void f_debugbreak(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) /// "deepcopy()" function static void f_deepcopy(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - int noref = 0; + if (tv_check_for_opt_bool_arg(argvars, 1) == FAIL) { + return; + } + varnumber_T noref = 0; if (argvars[1].v_type != VAR_UNKNOWN) { - noref = (int)tv_get_bool_chk(&argvars[1], NULL); - } - if (noref < 0 || noref > 1) { - semsg(_(e_using_number_as_bool_nr), noref); - } else { - var_item_copy(NULL, &argvars[0], rettv, true, (noref == 0 - ? get_copyID() - : 0)); + noref = tv_get_bool_chk(&argvars[1], NULL); } + + var_item_copy(NULL, &argvars[0], rettv, true, (noref == 0 ? get_copyID() : 0)); } /// "delete()" function @@ -1546,11 +1530,11 @@ static void f_eval(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { const char *s = tv_get_string_chk(&argvars[0]); if (s != NULL) { - s = (const char *)skipwhite(s); + s = skipwhite(s); } const char *const expr_start = s; - if (s == NULL || eval1((char **)&s, rettv, true) == FAIL) { + if (s == NULL || eval1((char **)&s, rettv, &EVALARG_EVALUATE) == FAIL) { if (expr_start != NULL && !aborting()) { semsg(_(e_invexpr2), expr_start); } @@ -1722,7 +1706,7 @@ static void f_exists(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) xfree(exp); } } else if (*p == '&' || *p == '+') { // Option. - n = (get_option_tv(&p, NULL, true) == OK); + n = (eval_option(&p, NULL, true) == OK); if (*skipwhite(p) != NUL) { n = false; // Trailing garbage. } @@ -1752,7 +1736,7 @@ static void f_expand(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) char *p_csl_save = p_csl; // avoid using 'completeslash' here - p_csl = empty_option; + p_csl = empty_string_option; #endif rettv->v_type = VAR_STRING; @@ -1769,7 +1753,7 @@ static void f_expand(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) emsg_off++; } size_t len; - char *errormsg = NULL; + const char *errormsg = NULL; char *result = eval_vars((char *)s, s, &len, NULL, &errormsg, NULL, false); if (p_verbose == 0) { emsg_off--; @@ -1779,7 +1763,7 @@ static void f_expand(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) if (rettv->v_type == VAR_LIST) { tv_list_alloc_ret(rettv, (result != NULL)); if (result != NULL) { - tv_list_append_string(rettv->vval.v_list, (const char *)result, -1); + tv_list_append_string(rettv->vval.v_list, result, -1); } XFREE_CLEAR(result); } else { @@ -1805,8 +1789,7 @@ static void f_expand(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) ExpandOne(&xpc, (char *)s, NULL, options, WILD_ALL_KEEP); tv_list_alloc_ret(rettv, xpc.xp_numfiles); for (int i = 0; i < xpc.xp_numfiles; i++) { - tv_list_append_string(rettv->vval.v_list, - (const char *)xpc.xp_files[i], -1); + tv_list_append_string(rettv->vval.v_list, xpc.xp_files[i], -1); } ExpandCleanup(&xpc); } @@ -1835,7 +1818,7 @@ static void f_menu_get(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) /// Expand all the special characters in a command string. static void f_expandcmd(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - char *errormsg = NULL; + const char *errormsg = NULL; bool emsgoff = true; if (argvars[1].v_type == VAR_DICT @@ -1870,8 +1853,8 @@ static void f_expandcmd(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) rettv->vval.v_string = cmdstr; } -/// "flatten(list[, {maxdepth}])" function -static void f_flatten(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +/// "flatten()" and "flattennew()" functions +static void flatten_common(typval_T *argvars, typval_T *rettv, bool make_copy) { bool error = false; @@ -1880,11 +1863,11 @@ static void f_flatten(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) return; } - long maxdepth; + int maxdepth; if (argvars[1].v_type == VAR_UNKNOWN) { maxdepth = 999999; } else { - maxdepth = (long)tv_get_number_chk(&argvars[1], &error); + maxdepth = (int)tv_get_number_chk(&argvars[1], &error); if (error) { return; } @@ -1895,92 +1878,179 @@ static void f_flatten(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) } list_T *list = argvars[0].vval.v_list; - if (list != NULL - && !value_check_lock(tv_list_locked(list), - N_("flatten() argument"), - TV_TRANSLATE) - && tv_list_flatten(list, maxdepth) == OK) { - tv_copy(&argvars[0], rettv); + rettv->v_type = VAR_LIST; + rettv->vval.v_list = list; + if (list == NULL) { + return; } + + if (make_copy) { + list = tv_list_copy(NULL, list, false, get_copyID()); + rettv->vval.v_list = list; + if (list == NULL) { + return; + } + } else { + if (value_check_lock(tv_list_locked(list), N_("flatten() argument"), TV_TRANSLATE)) { + return; + } + tv_list_ref(list); + } + + tv_list_flatten(list, NULL, tv_list_len(list), maxdepth); } -/// "extend(list, list [, idx])" function -/// "extend(dict, dict [, action])" function -static void f_extend(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +/// "flatten(list[, {maxdepth}])" function +static void f_flatten(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - const char *const arg_errmsg = N_("extend() argument"); + flatten_common(argvars, rettv, false); +} - if (argvars[0].v_type == VAR_LIST && argvars[1].v_type == VAR_LIST) { - bool error = false; +/// "flattennew(list[, {maxdepth}])" function +static void f_flattennew(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + flatten_common(argvars, rettv, true); +} - list_T *const l1 = argvars[0].vval.v_list; - list_T *const l2 = argvars[1].vval.v_list; - if (!value_check_lock(tv_list_locked(l1), arg_errmsg, TV_TRANSLATE)) { - listitem_T *item; - if (argvars[2].v_type != VAR_UNKNOWN) { - long before = (long)tv_get_number_chk(&argvars[2], &error); - if (error) { - return; // Type error; errmsg already given. - } +/// extend() a List. Append List argvars[1] to List argvars[0] before index +/// argvars[3] and return the resulting list in "rettv". +/// +/// @param is_new true for extendnew() +static void extend_list(typval_T *argvars, const char *arg_errmsg, bool is_new, typval_T *rettv) +{ + bool error = false; - if (before == tv_list_len(l1)) { - item = NULL; - } else { - item = tv_list_find(l1, (int)before); - if (item == NULL) { - semsg(_(e_listidx), (int64_t)before); - return; - } - } - } else { + list_T *l1 = argvars[0].vval.v_list; + list_T *const l2 = argvars[1].vval.v_list; + if (is_new || !value_check_lock(tv_list_locked(l1), arg_errmsg, TV_TRANSLATE)) { + if (is_new) { + l1 = tv_list_copy(NULL, l1, false, get_copyID()); + if (l1 == NULL) { + return; + } + } + + listitem_T *item; + if (argvars[2].v_type != VAR_UNKNOWN) { + int before = (int)tv_get_number_chk(&argvars[2], &error); + if (error) { + return; // Type error; errmsg already given. + } + + if (before == tv_list_len(l1)) { item = NULL; + } else { + item = tv_list_find(l1, before); + if (item == NULL) { + semsg(_(e_list_index_out_of_range_nr), (int64_t)before); + return; + } } - tv_list_extend(l1, l2, item); + } else { + item = NULL; + } + tv_list_extend(l1, l2, item); + if (is_new) { + *rettv = (typval_T){ + .v_type = VAR_LIST, + .v_lock = VAR_UNLOCKED, + .vval.v_list = l1, + }; + } else { tv_copy(&argvars[0], rettv); } - } else if (argvars[0].v_type == VAR_DICT && argvars[1].v_type == - VAR_DICT) { - dict_T *const d1 = argvars[0].vval.v_dict; - dict_T *const d2 = argvars[1].vval.v_dict; - if (d1 == NULL) { - const bool locked = value_check_lock(VAR_FIXED, arg_errmsg, TV_TRANSLATE); - (void)locked; - assert(locked == true); - } else if (d2 == NULL) { - // Do nothing - tv_copy(&argvars[0], rettv); - } else if (!value_check_lock(d1->dv_lock, arg_errmsg, TV_TRANSLATE)) { - const char *action = "force"; - // Check the third argument. - if (argvars[2].v_type != VAR_UNKNOWN) { - const char *const av[] = { "keep", "force", "error" }; + } +} - action = tv_get_string_chk(&argvars[2]); - if (action == NULL) { - return; // Type error; error message already given. - } - size_t i; - for (i = 0; i < ARRAY_SIZE(av); i++) { - if (strcmp(action, av[i]) == 0) { - break; - } - } - if (i == 3) { - semsg(_(e_invarg2), action); - return; +/// extend() a Dict. Append Dict argvars[1] to Dict argvars[0] and return the +/// resulting Dict in "rettv". +/// +/// @param is_new true for extendnew() +static void extend_dict(typval_T *argvars, const char *arg_errmsg, bool is_new, typval_T *rettv) +{ + dict_T *d1 = argvars[0].vval.v_dict; + dict_T *const d2 = argvars[1].vval.v_dict; + if (d1 == NULL) { + const bool locked = value_check_lock(VAR_FIXED, arg_errmsg, TV_TRANSLATE); + (void)locked; + assert(locked == true); + } else if (d2 == NULL) { + // Do nothing + tv_copy(&argvars[0], rettv); + } else if (is_new || !value_check_lock(d1->dv_lock, arg_errmsg, TV_TRANSLATE)) { + if (is_new) { + d1 = tv_dict_copy(NULL, d1, false, get_copyID()); + if (d1 == NULL) { + return; + } + } + + const char *action = "force"; + // Check the third argument. + if (argvars[2].v_type != VAR_UNKNOWN) { + const char *const av[] = { "keep", "force", "error" }; + + action = tv_get_string_chk(&argvars[2]); + if (action == NULL) { + return; // Type error; error message already given. + } + size_t i; + for (i = 0; i < ARRAY_SIZE(av); i++) { + if (strcmp(action, av[i]) == 0) { + break; } } + if (i == 3) { + semsg(_(e_invarg2), action); + return; + } + } - tv_dict_extend(d1, d2, action); + tv_dict_extend(d1, d2, action); + if (is_new) { + *rettv = (typval_T){ + .v_type = VAR_DICT, + .v_lock = VAR_UNLOCKED, + .vval.v_dict = d1, + }; + } else { tv_copy(&argvars[0], rettv); } + } +} + +/// "extend()" or "extendnew()" function. +/// +/// @param is_new true for extendnew() +static void extend(typval_T *argvars, typval_T *rettv, char *arg_errmsg, bool is_new) +{ + if (argvars[0].v_type == VAR_LIST && argvars[1].v_type == VAR_LIST) { + extend_list(argvars, arg_errmsg, is_new, rettv); + } else if (argvars[0].v_type == VAR_DICT && argvars[1].v_type == VAR_DICT) { + extend_dict(argvars, arg_errmsg, is_new, rettv); } else { - semsg(_(e_listdictarg), "extend()"); + semsg(_(e_listdictarg), is_new ? "extendnew()" : "extend()"); } } +/// "extend(list, list [, idx])" function +/// "extend(dict, dict [, action])" function +static void f_extend(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + char *errmsg = N_("extend() argument"); + extend(argvars, rettv, errmsg, false); +} + +/// "extendnew(list, list [, idx])" function +/// "extendnew(dict, dict [, action])" function +static void f_extendnew(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + char *errmsg = N_("extendnew() argument"); + extend(argvars, rettv, errmsg, true); +} + /// "feedkeys()" function static void f_feedkeys(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { @@ -2053,6 +2123,9 @@ static void findfilendir(typval_T *argvars, typval_T *rettv, int find_what) } if (*fname != NUL && !error) { + char *file_to_find = NULL; + char *search_ctx = NULL; + do { if (rettv->v_type == VAR_STRING || rettv->v_type == VAR_LIST) { xfree(fresult); @@ -2063,13 +2136,17 @@ static void findfilendir(typval_T *argvars, typval_T *rettv, int find_what) find_what, curbuf->b_ffname, (find_what == FINDFILE_DIR ? "" - : curbuf->b_p_sua)); + : curbuf->b_p_sua), + &file_to_find, &search_ctx); first = false; if (fresult != NULL && rettv->v_type == VAR_LIST) { - tv_list_append_string(rettv->vval.v_list, (const char *)fresult, -1); + tv_list_append_string(rettv->vval.v_list, fresult, -1); } } while ((rettv->v_type == VAR_LIST || --count > 0) && fresult != NULL); + + xfree(file_to_find); + vim_findfile_cleanup(search_ctx); } if (rettv->v_type == VAR_STRING) { @@ -2077,12 +2154,6 @@ static void findfilendir(typval_T *argvars, typval_T *rettv, int find_what) } } -/// "filter()" function -static void f_filter(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - filter_map(argvars, rettv, false); -} - /// "finddir({fname}[, {path}[, {count}]])" function static void f_finddir(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { @@ -2164,7 +2235,8 @@ static void f_fnamemodify(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) /// "foreground()" function static void f_foreground(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{} +{ +} static void f_funcref(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { @@ -2433,7 +2505,7 @@ static void f_getcwd(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) [kCdScopeTabpage] = 0, // Number of tab to look at. }; - char *cwd = NULL; // Current working directory to print + char *cwd = NULL; // Current working directory to print char *from = NULL; // The original string to copy tabpage_T *tp = curtab; // The tabpage to look at. @@ -2681,47 +2753,6 @@ static void f_getmarklist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) get_buf_local_marks(buf, rettv->vval.v_list); } -/// "getmousepos()" function -static void f_getmousepos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - int row = mouse_row; - int col = mouse_col; - int grid = mouse_grid; - varnumber_T winid = 0; - varnumber_T winrow = 0; - varnumber_T wincol = 0; - linenr_T lnum = 0; - varnumber_T column = 0; - - tv_dict_alloc_ret(rettv); - dict_T *d = rettv->vval.v_dict; - - tv_dict_add_nr(d, S_LEN("screenrow"), (varnumber_T)mouse_row + 1); - tv_dict_add_nr(d, S_LEN("screencol"), (varnumber_T)mouse_col + 1); - - win_T *wp = mouse_find_win(&grid, &row, &col); - if (wp != NULL) { - int height = wp->w_height + wp->w_hsep_height + wp->w_status_height; - // The height is adjusted by 1 when there is a bottom border. This is not - // necessary for a top border since `row` starts at -1 in that case. - if (row < height + wp->w_border_adj[2]) { - winid = wp->handle; - winrow = row + 1 + wp->w_winrow_off; // Adjust by 1 for top border - wincol = col + 1 + wp->w_wincol_off; // Adjust by 1 for left border - if (row >= 0 && row < wp->w_height && col >= 0 && col < wp->w_width) { - (void)mouse_comp_pos(wp, &row, &col, &lnum); - col = vcol2col(wp, lnum, col); - column = col + 1; - } - } - } - tv_dict_add_nr(d, S_LEN("winid"), winid); - tv_dict_add_nr(d, S_LEN("winrow"), winrow); - tv_dict_add_nr(d, S_LEN("wincol"), wincol); - tv_dict_add_nr(d, S_LEN("line"), (varnumber_T)lnum); - tv_dict_add_nr(d, S_LEN("column"), column); -} - /// "getpid()" function static void f_getpid(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { @@ -2840,7 +2871,8 @@ static void f_gettagstack(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) /// Dummy timer callback. Used by f_wait(). static void dummy_timer_due_cb(TimeWatcher *tw, void *data) -{} +{ +} /// Dummy timer close callback. Used by f_wait(). static void dummy_timer_close_cb(TimeWatcher *tw, void *data) @@ -2867,8 +2899,8 @@ static void f_wait(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) int timeout = (int)argvars[0].vval.v_number; typval_T expr = argvars[1]; int interval = argvars[2].v_type == VAR_NUMBER - ? (int)argvars[2].vval.v_number - : 200; // Default. + ? (int)argvars[2].vval.v_number + : 200; // Default. TimeWatcher *tw = xmalloc(sizeof(TimeWatcher)); // Start dummy timer. @@ -2882,8 +2914,11 @@ static void f_wait(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) bool error = false; const int called_emsg_before = called_emsg; + // Flush screen updates before blocking. + ui_flush(); + LOOP_PROCESS_EVENTS_UNTIL(&main_loop, main_loop.events, timeout, - eval_expr_typval(&expr, &argv, 0, &exprval) != OK + eval_expr_typval(&expr, false, &argv, 0, &exprval) != OK || tv_get_number_chk(&exprval, &error) || called_emsg > called_emsg_before || error || got_int); @@ -2941,8 +2976,7 @@ static void f_glob(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) WILD_ALL_KEEP); tv_list_alloc_ret(rettv, xpc.xp_numfiles); for (int i = 0; i < xpc.xp_numfiles; i++) { - tv_list_append_string(rettv->vval.v_list, (const char *)xpc.xp_files[i], - -1); + tv_list_append_string(rettv->vval.v_list, xpc.xp_files[i], -1); } ExpandCleanup(&xpc); } @@ -2983,7 +3017,7 @@ static void f_globpath(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) if (file != NULL && !error) { garray_T ga; ga_init(&ga, (int)sizeof(char *), 10); - globpath((char *)tv_get_string(&argvars[0]), (char *)file, &ga, flags); + globpath((char *)tv_get_string(&argvars[0]), (char *)file, &ga, flags, false); if (rettv->v_type == VAR_STRING) { rettv->vval.v_string = ga_concat_strings_sep(&ga, "\n"); @@ -3013,14 +3047,12 @@ static void f_glob2regpat(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) /// "gettext()" function static void f_gettext(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - if (argvars[0].v_type != VAR_STRING - || argvars[0].vval.v_string == NULL - || *argvars[0].vval.v_string == NUL) { - semsg(_(e_invarg2), tv_get_string(&argvars[0])); - } else { - rettv->v_type = VAR_STRING; - rettv->vval.v_string = xstrdup(_(argvars[0].vval.v_string)); + if (tv_check_for_nonempty_string_arg(argvars, 0) == FAIL) { + return; } + + rettv->v_type = VAR_STRING; + rettv->vval.v_string = xstrdup(_(argvars[0].vval.v_string)); } /// "has()" function @@ -3064,9 +3096,6 @@ static void f_has(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) "conceal", "cursorbind", "cursorshape", -#ifdef DEBUG - "debug", -#endif "dialog_con", "diff", "digraphs", @@ -3149,8 +3178,11 @@ static void f_has(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) "windows", "winaltkeys", "writebackup", +#ifdef HAVE_XATTR + "xattr", +#endif "nvim", - "userreg", + "rneovim", }; // XXX: eval_has_provider() may shell out :( @@ -3165,7 +3197,9 @@ static void f_has(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) } if (!n) { - if (STRNICMP(name, "patch", 5) == 0) { + if (STRNICMP(name, "gui_running", 11) == 0) { + n = ui_gui_attached(); + } else if (STRNICMP(name, "patch", 5) == 0) { if (name[5] == '-' && strlen(name) >= 11 && ascii_isdigit(name[6]) @@ -3220,7 +3254,7 @@ static bool has_wsl(void) static TriState has_wsl = kNone; if (has_wsl == kNone) { Error err = ERROR_INIT; - Object o = nlua_exec(STATIC_CSTR_AS_STRING("return vim.loop.os_uname()['release']:lower()" + Object o = nlua_exec(STATIC_CSTR_AS_STRING("return vim.uv.os_uname()['release']:lower()" ":match('microsoft') and true or false"), (Array)ARRAY_DICT_INIT, &err); assert(!ERROR_SET(&err)); @@ -3254,7 +3288,7 @@ static void f_haslocaldir(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) [kCdScopeTabpage] = 0, // Number of tab to look at. }; - tabpage_T *tp = curtab; // The tabpage to look at. + tabpage_T *tp = curtab; // The tabpage to look at. win_T *win = curwin; // The window to look at. rettv->v_type = VAR_NUMBER; @@ -3353,34 +3387,6 @@ static void f_hostname(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) rettv->vval.v_string = xstrdup(hostname); } -/// iconv() function -static void f_iconv(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - vimconv_T vimconv; - - rettv->v_type = VAR_STRING; - rettv->vval.v_string = NULL; - - const char *const str = tv_get_string(&argvars[0]); - char buf1[NUMBUFLEN]; - char *const from = enc_canonize(enc_skip((char *)tv_get_string_buf(&argvars[1], buf1))); - char buf2[NUMBUFLEN]; - char *const to = enc_canonize(enc_skip((char *)tv_get_string_buf(&argvars[2], buf2))); - vimconv.vc_type = CONV_NONE; - convert_setup(&vimconv, from, to); - - // If the encodings are equal, no conversion needed. - if (vimconv.vc_type == CONV_NONE) { - rettv->vval.v_string = xstrdup(str); - } else { - rettv->vval.v_string = string_convert(&vimconv, (char *)str, NULL); - } - - convert_setup(&vimconv, NULL, NULL); - xfree(from); - xfree(to); -} - /// "indent()" function static void f_indent(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { @@ -3395,7 +3401,7 @@ static void f_indent(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) /// "index()" function static void f_index(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - long idx = 0; + int idx = 0; bool ic = false; rettv->vval.v_number = -1; @@ -3422,7 +3428,7 @@ static void f_index(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) for (idx = start; idx < tv_blob_len(b); idx++) { typval_T tv; tv.v_type = VAR_NUMBER; - tv.vval.v_number = tv_blob_get(b, (int)idx); + tv.vval.v_number = tv_blob_get(b, idx); if (tv_equal(&tv, &argvars[1], ic, false)) { rettv->vval.v_number = idx; return; @@ -3448,7 +3454,7 @@ static void f_index(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) if (error || idx == -1) { item = NULL; } else { - item = tv_list_find(l, (int)idx); + item = tv_list_find(l, idx); assert(item != NULL); } if (argvars[3].v_type != VAR_UNKNOWN) { @@ -3467,6 +3473,138 @@ static void f_index(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) } } +/// Evaluate "expr" with the v:key and v:val arguments and return the result. +/// The expression is expected to return a boolean value. The caller should set +/// the VV_KEY and VV_VAL vim variables before calling this function. +static varnumber_T indexof_eval_expr(typval_T *expr) +{ + typval_T argv[3]; + argv[0] = *get_vim_var_tv(VV_KEY); + argv[1] = *get_vim_var_tv(VV_VAL); + typval_T newtv; + newtv.v_type = VAR_UNKNOWN; + + if (eval_expr_typval(expr, false, argv, 2, &newtv) == FAIL) { + return false; + } + + bool error = false; + varnumber_T found = tv_get_bool_chk(&newtv, &error); + tv_clear(&newtv); + + return error ? false : found; +} + +/// Evaluate "expr" for each byte in the Blob "b" starting with the byte at +/// "startidx" and return the index of the byte where "expr" is TRUE. Returns +/// -1 if "expr" doesn't evaluate to TRUE for any of the bytes. +static varnumber_T indexof_blob(blob_T *b, varnumber_T startidx, typval_T *expr) +{ + if (b == NULL) { + return -1; + } + + if (startidx < 0) { + // negative index: index from the last byte + startidx = tv_blob_len(b) + startidx; + if (startidx < 0) { + startidx = 0; + } + } + + for (varnumber_T idx = startidx; idx < tv_blob_len(b); idx++) { + set_vim_var_nr(VV_KEY, idx); + set_vim_var_nr(VV_VAL, tv_blob_get(b, (int)idx)); + + if (indexof_eval_expr(expr)) { + return idx; + } + } + + return -1; +} + +/// Evaluate "expr" for each item in the List "l" starting with the item at +/// "startidx" and return the index of the item where "expr" is TRUE. Returns +/// -1 if "expr" doesn't evaluate to TRUE for any of the items. +static varnumber_T indexof_list(list_T *l, varnumber_T startidx, typval_T *expr) +{ + if (l == NULL) { + return -1; + } + + listitem_T *item; + varnumber_T idx = 0; + if (startidx == 0) { + item = tv_list_first(l); + } else { + // Start at specified item. + idx = tv_list_uidx(l, (int)startidx); + if (idx == -1) { + item = NULL; + } else { + item = tv_list_find(l, (int)idx); + assert(item != NULL); + } + } + + for (; item != NULL; item = TV_LIST_ITEM_NEXT(l, item), idx++) { + set_vim_var_nr(VV_KEY, idx); + tv_copy(TV_LIST_ITEM_TV(item), get_vim_var_tv(VV_VAL)); + + bool found = indexof_eval_expr(expr); + tv_clear(get_vim_var_tv(VV_VAL)); + + if (found) { + return idx; + } + } + + return -1; +} + +/// "indexof()" function +static void f_indexof(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + rettv->vval.v_number = -1; + + if (tv_check_for_list_or_blob_arg(argvars, 0) == FAIL + || tv_check_for_string_or_func_arg(argvars, 1) == FAIL + || tv_check_for_opt_dict_arg(argvars, 2) == FAIL) { + return; + } + + if ((argvars[1].v_type == VAR_STRING && argvars[1].vval.v_string == NULL) + || (argvars[1].v_type == VAR_FUNC && argvars[1].vval.v_partial == NULL)) { + return; + } + + varnumber_T startidx = 0; + if (argvars[2].v_type == VAR_DICT) { + startidx = tv_dict_get_number_def(argvars[2].vval.v_dict, "startidx", 0); + } + + typval_T save_val; + typval_T save_key; + prepare_vimvar(VV_VAL, &save_val); + prepare_vimvar(VV_KEY, &save_key); + + // We reset "did_emsg" to be able to detect whether an error occurred + // during evaluation of the expression. + const int save_did_emsg = did_emsg; + did_emsg = false; + + if (argvars[0].v_type == VAR_BLOB) { + rettv->vval.v_number = indexof_blob(argvars[0].vval.v_blob, startidx, &argvars[1]); + } else { + rettv->vval.v_number = indexof_list(argvars[0].vval.v_list, startidx, &argvars[1]); + } + + restore_vimvar(VV_KEY, &save_key); + restore_vimvar(VV_VAL, &save_val); + did_emsg |= save_did_emsg; +} + static bool inputsecret_flag = false; /// "input()" function @@ -3548,7 +3686,6 @@ static void f_inputsecret(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) /// "insert()" function static void f_insert(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - list_T *l; bool error = false; if (argvars[0].v_type == VAR_BLOB) { @@ -3560,11 +3697,11 @@ static void f_insert(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) return; } - long before = 0; + int before = 0; const int len = tv_blob_len(b); if (argvars[2].v_type != VAR_UNKNOWN) { - before = (long)tv_get_number_chk(&argvars[2], &error); + before = (int)tv_get_number_chk(&argvars[2], &error); if (error) { return; // type error; errmsg already given } @@ -3591,9 +3728,13 @@ static void f_insert(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) tv_copy(&argvars[0], rettv); } else if (argvars[0].v_type != VAR_LIST) { semsg(_(e_listblobarg), "insert()"); - } else if (!value_check_lock(tv_list_locked((l = argvars[0].vval.v_list)), - N_("insert() argument"), TV_TRANSLATE)) { - long before = 0; + } else { + list_T *l = argvars[0].vval.v_list; + if (value_check_lock(tv_list_locked(l), N_("insert() argument"), TV_TRANSLATE)) { + return; + } + + int64_t before = 0; if (argvars[2].v_type != VAR_UNKNOWN) { before = tv_get_number_chk(&argvars[2], &error); } @@ -3606,7 +3747,7 @@ static void f_insert(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) if (before != tv_list_len(l)) { item = tv_list_find(l, (int)before); if (item == NULL) { - semsg(_(e_listidx), (int64_t)before); + semsg(_(e_list_index_out_of_range_nr), before); l = NULL; } } @@ -3618,8 +3759,7 @@ static void f_insert(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) } /// "interrupt()" function -static void f_interrupt(typval_T *argvars FUNC_ATTR_UNUSED, typval_T *rettv FUNC_ATTR_UNUSED, - EvalFuncData fptr FUNC_ATTR_UNUSED) +static void f_interrupt(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { got_int = true; } @@ -3796,7 +3936,7 @@ static dict_T *create_environment(const dictitem_T *job_env, const bool clear_en if (!clear_env) { typval_T temp_env = TV_INITIAL_VALUE; - f_environ(NULL, &temp_env, (EvalFuncData){ .nullptr = NULL }); + f_environ(NULL, &temp_env, (EvalFuncData){ .null = NULL }); tv_dict_extend(env, temp_env.vval.v_dict, "force"); tv_dict_free(temp_env.vval.v_dict); @@ -3813,12 +3953,13 @@ static dict_T *create_environment(const dictitem_T *job_env, const bool clear_en } } #ifndef MSWIN - // Set COLORTERM to "truecolor" if termguicolors is set and 256 - // otherwise, but only if it was set in the parent terminal at all - dictitem_T *dv = tv_dict_find(env, S_LEN("COLORTERM")); - if (dv) { - tv_dict_item_remove(env, dv); - tv_dict_add_str(env, S_LEN("COLORTERM"), p_tgc ? "truecolor" : "256"); + // Set COLORTERM to "truecolor" if termguicolors is set + if (p_tgc) { + dictitem_T *dv = tv_dict_find(env, S_LEN("COLORTERM")); + if (dv) { + tv_dict_item_remove(env, dv); + } + tv_dict_add_str(env, S_LEN("COLORTERM"), "truecolor"); } #endif } @@ -3850,7 +3991,7 @@ static dict_T *create_environment(const dictitem_T *job_env, const bool clear_en #ifdef MSWIN TV_DICT_ITER(job_env->di_tv.vval.v_dict, var, { // Always use upper-case keys for Windows so we detect duplicate keys - char *const key = strcase_save((const char *)var->di_key, true); + char *const key = strcase_save(var->di_key, true); size_t len = strlen(key); dictitem_T *dv = tv_dict_find(env, key, len); if (dv) { @@ -3996,7 +4137,7 @@ static void f_jobstart(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) env = create_environment(job_env, clear_env, pty, term_name); - Channel *chan = channel_job_start(argv, on_stdout, on_stderr, on_exit, pty, + Channel *chan = channel_job_start(argv, NULL, on_stdout, on_stderr, on_exit, pty, rpc, overlapped, detach, stdin_mode, cwd, width, height, env, &rettv->vval.v_number); if (chan) { @@ -4053,6 +4194,7 @@ static void f_jobwait(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) } ui_busy_start(); + ui_flush(); list_T *args = argvars[0].vval.v_list; Channel **jobs = xcalloc((size_t)tv_list_len(args), sizeof(*jobs)); MultiQueue *waiting_jobs = multiqueue_new_parent(loop_on_put, &main_loop); @@ -4365,30 +4507,24 @@ static void f_luaeval(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) nlua_typval_eval(cstr_as_string((char *)str), &argvars[1], rettv); } -/// "map()" function -static void f_map(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - filter_map(argvars, rettv, true); -} - static void find_some_match(typval_T *const argvars, typval_T *const rettv, const SomeMatchType type) { char *str = NULL; - long len = 0; + int64_t len = 0; char *expr = NULL; regmatch_T regmatch; - long start = 0; - long nth = 1; + int64_t start = 0; + int64_t nth = 1; colnr_T startcol = 0; bool match = false; list_T *l = NULL; - long idx = 0; + int idx = 0; char *tofree = NULL; // Make 'cpoptions' empty, the 'l' flag should not be used here. char *save_cpo = p_cpo; - p_cpo = empty_option; + p_cpo = empty_string_option; rettv->vval.v_number = -1; switch (type) { @@ -4422,7 +4558,7 @@ static void find_some_match(typval_T *const argvars, typval_T *const rettv, li = tv_list_first(l); } else { expr = str = (char *)tv_get_string(&argvars[0]); - len = (long)strlen(str); + len = (int64_t)strlen(str); } char patbuf[NUMBUFLEN]; @@ -4443,7 +4579,7 @@ static void find_some_match(typval_T *const argvars, typval_T *const rettv, if (idx == -1) { goto theend; } - li = tv_list_find(l, (int)idx); + li = tv_list_find(l, idx); } else { if (start < 0) { start = 0; @@ -4470,11 +4606,11 @@ static void find_some_match(typval_T *const argvars, typval_T *const rettv, } } - regmatch.regprog = vim_regcomp((char *)pat, RE_MAGIC + RE_STRING); + regmatch.regprog = vim_regcomp(pat, RE_MAGIC + RE_STRING); if (regmatch.regprog != NULL) { regmatch.rm_ic = p_ic; - for (;;) { + while (true) { if (l != NULL) { if (li == NULL) { match = false; @@ -4535,8 +4671,7 @@ static void find_some_match(typval_T *const argvars, typval_T *const rettv, if (regmatch.endp[i] == NULL) { tv_list_append_string(rettv->vval.v_list, NULL, 0); } else { - tv_list_append_string(rettv->vval.v_list, - (const char *)regmatch.startp[i], + tv_list_append_string(rettv->vval.v_list, regmatch.startp[i], (regmatch.endp[i] - regmatch.startp[i])); } } @@ -4546,7 +4681,7 @@ static void find_some_match(typval_T *const argvars, typval_T *const rettv, if (l != NULL) { tv_copy(TV_LIST_ITEM_TV(li), rettv); } else { - rettv->vval.v_string = xmemdupz((const char *)regmatch.startp[0], + rettv->vval.v_string = xmemdupz(regmatch.startp[0], (size_t)(regmatch.endp[0] - regmatch.startp[0])); } @@ -4675,7 +4810,7 @@ static void f_min(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) /// "mkdir()" function static void f_mkdir(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - int prot = 0755; // -V536 + int prot = 0755; rettv->vval.v_number = FAIL; if (check_secure()) { @@ -4693,6 +4828,9 @@ static void f_mkdir(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) *path_tail_with_sep((char *)dir) = NUL; } + bool defer = false; + bool defer_recurse = false; + char *created = NULL; if (argvars[1].v_type != VAR_UNKNOWN) { if (argvars[2].v_type != VAR_UNKNOWN) { prot = (int)tv_get_number_chk(&argvars[2], NULL); @@ -4700,9 +4838,17 @@ static void f_mkdir(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) return; } } - if (strcmp(tv_get_string(&argvars[1]), "p") == 0) { + const char *arg2 = tv_get_string(&argvars[1]); + defer = vim_strchr(arg2, 'D') != NULL; + defer_recurse = vim_strchr(arg2, 'R') != NULL; + if ((defer || defer_recurse) && !can_add_defer()) { + return; + } + + if (vim_strchr(arg2, 'p') != NULL) { char *failed_dir; - int ret = os_mkdir_recurse(dir, prot, &failed_dir); + int ret = os_mkdir_recurse(dir, prot, &failed_dir, + defer || defer_recurse ? &created : NULL); if (ret != 0) { semsg(_(e_mkdir), failed_dir, os_strerror(ret)); xfree(failed_dir); @@ -4710,10 +4856,27 @@ static void f_mkdir(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) return; } rettv->vval.v_number = OK; - return; } } - rettv->vval.v_number = vim_mkdir_emsg(dir, prot); + if (rettv->vval.v_number == FAIL) { + rettv->vval.v_number = vim_mkdir_emsg(dir, prot); + } + + // Handle "D" and "R": deferred deletion of the created directory. + if (rettv->vval.v_number == OK + && created == NULL && (defer || defer_recurse)) { + created = FullName_save(dir, false); + } + if (created != NULL) { + typval_T tv[2]; + tv[0].v_type = VAR_STRING; + tv[0].v_lock = VAR_UNLOCKED; + tv[0].vval.v_string = created; + tv[1].v_type = VAR_STRING; + tv[1].v_lock = VAR_UNLOCKED; + tv[1].vval.v_string = xstrdup(defer_recurse ? "rf" : "d"); + add_defer("delete", 2, tv); + } } /// "mode()" function @@ -4733,6 +4896,50 @@ static void f_mode(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) rettv->v_type = VAR_STRING; } +static void may_add_state_char(garray_T *gap, const char *include, uint8_t c) +{ + if (include == NULL || vim_strchr(include, c) != NULL) { + ga_append(gap, c); + } +} + +/// "state()" function +static void f_state(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + garray_T ga; + ga_init(&ga, 1, 20); + const char *include = NULL; + + if (argvars[0].v_type != VAR_UNKNOWN) { + include = tv_get_string(&argvars[0]); + } + + if (!(stuff_empty() && typebuf.tb_len == 0 && !using_script())) { + may_add_state_char(&ga, include, 'm'); + } + if (op_pending()) { + may_add_state_char(&ga, include, 'o'); + } + if (autocmd_busy) { + may_add_state_char(&ga, include, 'x'); + } + if (ins_compl_active()) { + may_add_state_char(&ga, include, 'a'); + } + if (!get_was_safe_state()) { + may_add_state_char(&ga, include, 'S'); + } + for (int i = 0; i < get_callback_depth() && i < 3; i++) { + may_add_state_char(&ga, include, 'c'); + } + if (msg_scrolled > 0) { + may_add_state_char(&ga, include, 's'); + } + + rettv->v_type = VAR_STRING; + rettv->vval.v_string = ga.ga_data; +} + /// "msgpackdump()" function static void f_msgpackdump(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) FUNC_ATTR_NONNULL_ALL @@ -4756,7 +4963,7 @@ static void f_msgpackdump(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) char msgbuf[sizeof("msgpackdump() argument, index ") * 4 + NUMBUFLEN]; int idx = 0; TV_LIST_ITER(list, li, { - vim_snprintf(msgbuf, sizeof(msgbuf), (char *)msg, idx); + vim_snprintf(msgbuf, sizeof(msgbuf), msg, idx); idx++; if (encode_vim_to_msgpack(packer, TV_LIST_ITEM_TV(li), msgbuf) == FAIL) { break; @@ -4791,9 +4998,10 @@ static int msgpackparse_convert_item(const msgpack_object data, const msgpack_un tv_list_append_owned_tv(ret_list, tv); return OK; } - default: + case MSGPACK_UNPACK_EXTRA_BYTES: abort(); } + UNREACHABLE; } static void msgpackparse_unpack_list(const list_T *const list, list_T *const ret_list) @@ -4814,7 +5022,7 @@ static void msgpackparse_unpack_list(const list_T *const list, list_T *const ret } msgpack_unpacked unpacked; msgpack_unpacked_init(&unpacked); - do { + while (true) { if (!msgpack_unpacker_reserve_buffer(unpacker, IOSIZE)) { emsg(_(e_outofmem)); goto end; @@ -4844,7 +5052,7 @@ static void msgpackparse_unpack_list(const list_T *const list, list_T *const ret if (rlret == OK) { break; } - } while (true); + } end: msgpack_unpacker_free(unpacker); @@ -4929,7 +5137,7 @@ static void f_nr2char(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) return; } - char buf[MB_MAXBYTES]; + char buf[MB_MAXCHAR]; const int len = utf_char2bytes((int)num, buf); rettv->v_type = VAR_STRING; @@ -5058,7 +5266,7 @@ static void f_prompt_setinterrupt(typval_T *argvars, typval_T *rettv, EvalFuncDa } callback_free(&buf->b_prompt_interrupt); - buf->b_prompt_interrupt= interrupt_callback; + buf->b_prompt_interrupt = interrupt_callback; } /// "prompt_getprompt({buffer})" function @@ -5214,10 +5422,10 @@ static void f_rand(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) goto theend; } - typval_T *const tvx = TV_LIST_ITEM_TV(tv_list_find(l, 0L)); - typval_T *const tvy = TV_LIST_ITEM_TV(tv_list_find(l, 1L)); - typval_T *const tvz = TV_LIST_ITEM_TV(tv_list_find(l, 2L)); - typval_T *const tvw = TV_LIST_ITEM_TV(tv_list_find(l, 3L)); + typval_T *const tvx = TV_LIST_ITEM_TV(tv_list_find(l, 0)); + typval_T *const tvy = TV_LIST_ITEM_TV(tv_list_find(l, 1)); + typval_T *const tvz = TV_LIST_ITEM_TV(tv_list_find(l, 2)); + typval_T *const tvw = TV_LIST_ITEM_TV(tv_list_find(l, 3)); if (tvx->v_type != VAR_NUMBER) { goto theend; } @@ -5345,7 +5553,7 @@ static varnumber_T readdir_checkitem(void *context, const char *name) argv[0].vval.v_string = (char *)name; typval_T rettv; - if (eval_expr_typval(expr, argv, 1, &rettv) == FAIL) { + if (eval_expr_typval(expr, false, argv, 1, &rettv) == FAIL) { goto theend; } @@ -5389,18 +5597,27 @@ static void read_file_or_blob(typval_T *argvars, typval_T *rettv, bool always_bl char buf[(IOSIZE/256) * 256]; // rounded to avoid odd + 1 int io_size = sizeof(buf); char *prev = NULL; // previously read bytes, if any - ptrdiff_t prevlen = 0; // length of data in prev + ptrdiff_t prevlen = 0; // length of data in prev ptrdiff_t prevsize = 0; // size of prev buffer - long maxline = MAXLNUM; + int64_t maxline = MAXLNUM; + off_T offset = 0; + off_T size = -1; if (argvars[1].v_type != VAR_UNKNOWN) { - if (strcmp(tv_get_string(&argvars[1]), "b") == 0) { - binary = true; - } else if (strcmp(tv_get_string(&argvars[1]), "B") == 0) { - blob = true; - } - if (argvars[2].v_type != VAR_UNKNOWN) { - maxline = tv_get_number(&argvars[2]); + if (always_blob) { + offset = (off_T)tv_get_number(&argvars[1]); + if (argvars[2].v_type != VAR_UNKNOWN) { + size = (off_T)tv_get_number(&argvars[2]); + } + } else { + if (strcmp(tv_get_string(&argvars[1]), "b") == 0) { + binary = true; + } else if (strcmp(tv_get_string(&argvars[1]), "B") == 0) { + blob = true; + } + if (argvars[2].v_type != VAR_UNKNOWN) { + maxline = tv_get_number(&argvars[2]); + } } } @@ -5419,11 +5636,8 @@ static void read_file_or_blob(typval_T *argvars, typval_T *rettv, bool always_bl if (blob) { tv_blob_alloc_ret(rettv); - if (!read_blob(fd, rettv->vval.v_blob)) { + if (read_blob(fd, rettv, offset, size) == FAIL) { semsg(_(e_notread), fname); - // An empty blob is returned on error. - tv_blob_free(rettv->vval.v_blob); - rettv->vval.v_blob = NULL; } fclose(fd); return; @@ -5445,7 +5659,7 @@ static void read_file_or_blob(typval_T *argvars, typval_T *rettv, bool always_bl p < buf + readlen || (readlen <= 0 && (prevlen > 0 || binary)); p++) { if (readlen <= 0 || *p == '\n') { - char *s = NULL; + char *s = NULL; size_t len = (size_t)(p - start); // Finished a line. Remove CRs before NL. @@ -5462,7 +5676,7 @@ static void read_file_or_blob(typval_T *argvars, typval_T *rettv, bool always_bl } if (prevlen == 0) { assert(len < INT_MAX); - s = xstrnsave(start, len); + s = xmemdupz(start, len); } else { // Change "prev" buffer to be the right size. This way // the bytes are only copied once, and very long lines are @@ -5501,11 +5715,11 @@ static void read_file_or_blob(typval_T *argvars, typval_T *rettv, bool always_bl // Find the two bytes before the 0xbf. If p is at buf, or buf + 1, // these may be in the "prev" string. char back1 = p >= buf + 1 ? p[-1] - : prevlen >= 1 ? prev[prevlen - 1] : NUL; + : prevlen >= 1 ? prev[prevlen - 1] : NUL; char back2 = p >= buf + 2 ? p[-2] - : p == buf + 1 && prevlen >= 1 ? prev[prevlen - 1] - : prevlen >= - 2 ? prev[prevlen - 2] : NUL; + : (p == buf + 1 && prevlen >= 1 + ? prev[prevlen - 1] + : prevlen >= 2 ? prev[prevlen - 2] : NUL); if ((uint8_t)back2 == 0xef && (uint8_t)back1 == 0xbb) { char *dest = p - 2; @@ -5518,9 +5732,9 @@ static void read_file_or_blob(typval_T *argvars, typval_T *rettv, bool always_bl // have to shuffle buf to close gap int adjust_prevlen = 0; - if (dest < buf) { // -V782 + if (dest < buf) { // adjust_prevlen must be 1 or 2. - adjust_prevlen = (int)(buf - dest); // -V782 + adjust_prevlen = (int)(buf - dest); dest = buf; } if (readlen > p - buf + 1) { @@ -5548,7 +5762,7 @@ static void read_file_or_blob(typval_T *argvars, typval_T *rettv, bool always_bl prevsize = p - start; } else { ptrdiff_t grow50pc = (prevsize * 3) / 2; - ptrdiff_t growmin = (p - start) * 2 + prevlen; + ptrdiff_t growmin = (p - start) * 2 + prevlen; prevsize = grow50pc > growmin ? grow50pc : growmin; } prev = xrealloc(prev, (size_t)prevsize); @@ -5655,8 +5869,8 @@ static int list2proftime(typval_T *arg, proftime_T *tm) FUNC_ATTR_NONNULL_ALL } bool error = false; - varnumber_T n1 = tv_list_find_nr(arg->vval.v_list, 0L, &error); - varnumber_T n2 = tv_list_find_nr(arg->vval.v_list, 1L, &error); + varnumber_T n1 = tv_list_find_nr(arg->vval.v_list, 0, &error); + varnumber_T n2 = tv_list_find_nr(arg->vval.v_list, 1, &error); if (error) { return FAIL; } @@ -5770,6 +5984,37 @@ static void f_repeat(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) while (n-- > 0) { tv_list_extend(rettv->vval.v_list, argvars[0].vval.v_list, NULL); } + } else if (argvars[0].v_type == VAR_BLOB) { + tv_blob_alloc_ret(rettv); + if (argvars[0].vval.v_blob == NULL || n <= 0) { + return; + } + + const int slen = argvars[0].vval.v_blob->bv_ga.ga_len; + const int len = (int)(slen * n); + if (len <= 0) { + return; + } + + ga_grow(&rettv->vval.v_blob->bv_ga, len); + + rettv->vval.v_blob->bv_ga.ga_len = len; + + int i; + for (i = 0; i < slen; i++) { + if (tv_blob_get(argvars[0].vval.v_blob, i) != 0) { + break; + } + } + + if (i == slen) { + // No need to copy since all bytes are already zero + return; + } + + for (i = 0; i < n; i++) { + tv_blob_set_range(rettv->vval.v_blob, i * slen, (i + 1) * slen - 1, argvars); + } } else { rettv->v_type = VAR_STRING; rettv->vval.v_string = NULL; @@ -5843,8 +6088,8 @@ static void f_resolve(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) char *const buf = xmallocz(MAXPATHL); char *cpy; - for (;;) { - for (;;) { + while (true) { + while (true) { len = readlink(p, buf, MAXPATHL); if (len <= 0) { break; @@ -5861,13 +6106,13 @@ static void f_resolve(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) } // Ensure that the result will have a trailing path separator - // if the argument has one. */ + // if the argument has one. if (remain == NULL && has_trailing_pathsep) { add_pathsep(buf); } // Separate the first path component in the link value and - // concatenate the remainders. */ + // concatenate the remainders. q = (char *)path_next_component(vim_ispathsep(*buf) ? buf + 1 : buf); if (*q != NUL) { cpy = remain; @@ -5881,7 +6126,7 @@ static void f_resolve(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) q = path_tail(p); if (q > p && *q == NUL) { // Ignore trailing path separator. - q[-1] = NUL; + p[q - p - 1] = NUL; q = path_tail(p); } if (q > p && !path_is_absolute(buf)) { @@ -5960,7 +6205,7 @@ static void f_resolve(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) } # else char *v = os_realpath(fname, NULL); - rettv->vval.v_string = (char_u *)(v == NULL ? xstrdup(fname) : v); + rettv->vval.v_string = v == NULL ? xstrdup(fname) : v; # endif #endif @@ -5970,6 +6215,10 @@ static void f_resolve(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) /// "reverse({list})" function static void f_reverse(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { + if (tv_check_for_string_or_list_or_blob_arg(argvars, 0) == FAIL) { + return; + } + if (argvars[0].v_type == VAR_BLOB) { blob_T *const b = argvars[0].vval.v_blob; const int len = tv_blob_len(b); @@ -5980,9 +6229,14 @@ static void f_reverse(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) tv_blob_set(b, len - i - 1, tmp); } tv_blob_set_ret(rettv, b); - } else if (argvars[0].v_type != VAR_LIST) { - semsg(_(e_listblobarg), "reverse()"); - } else { + } else if (argvars[0].v_type == VAR_STRING) { + rettv->v_type = VAR_STRING; + if (argvars[0].vval.v_string != NULL) { + rettv->vval.v_string = reverse_text(argvars[0].vval.v_string); + } else { + rettv->vval.v_string = NULL; + } + } else if (argvars[0].v_type == VAR_LIST) { list_T *const l = argvars[0].vval.v_list; if (!value_check_lock(tv_list_locked(l), N_("reverse() argument"), TV_TRANSLATE)) { @@ -5992,102 +6246,181 @@ static void f_reverse(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) } } -/// "reduce(list, { accumulator, element -> value } [, initial])" function -static void f_reduce(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +/// Implementation of reduce() for list "argvars[0]", using the function "expr" +/// starting with the optional initial value argvars[2] and return the result in +/// "rettv". +static void reduce_list(typval_T *argvars, typval_T *expr, typval_T *rettv) { - if (argvars[0].v_type != VAR_LIST && argvars[0].v_type != VAR_BLOB) { - emsg(_(e_listblobreq)); - return; - } + list_T *const l = argvars[0].vval.v_list; + const int called_emsg_start = called_emsg; - const char *func_name; - partial_T *partial = NULL; - if (argvars[1].v_type == VAR_FUNC) { - func_name = argvars[1].vval.v_string; - } else if (argvars[1].v_type == VAR_PARTIAL) { - partial = argvars[1].vval.v_partial; - func_name = partial_name(partial); + typval_T initial; + const listitem_T *li = NULL; + if (argvars[2].v_type == VAR_UNKNOWN) { + if (tv_list_len(l) == 0) { + semsg(_(e_reduceempty), "List"); + return; + } + const listitem_T *const first = tv_list_first(l); + initial = *TV_LIST_ITEM_TV(first); + li = TV_LIST_ITEM_NEXT(l, first); } else { - func_name = tv_get_string(&argvars[1]); + initial = argvars[2]; + li = tv_list_first(l); } - if (*func_name == NUL) { - return; // type error or empty name + + tv_copy(&initial, rettv); + + if (l == NULL) { + return; } - funcexe_T funcexe = FUNCEXE_INIT; - funcexe.fe_evaluate = true; - funcexe.fe_partial = partial; + const VarLockStatus prev_locked = tv_list_locked(l); - typval_T initial; - typval_T argv[3]; - if (argvars[0].v_type == VAR_LIST) { - list_T *const l = argvars[0].vval.v_list; - const listitem_T *li; + tv_list_set_lock(l, VAR_FIXED); // disallow the list changing here + for (; li != NULL; li = TV_LIST_ITEM_NEXT(l, li)) { + typval_T argv[3]; + argv[0] = *rettv; + argv[1] = *TV_LIST_ITEM_TV(li); + rettv->v_type = VAR_UNKNOWN; - if (argvars[2].v_type == VAR_UNKNOWN) { - if (tv_list_len(l) == 0) { - semsg(_(e_reduceempty), "List"); - return; - } - const listitem_T *const first = tv_list_first(l); - initial = *TV_LIST_ITEM_TV(first); - li = TV_LIST_ITEM_NEXT(l, first); - } else { - initial = argvars[2]; - li = tv_list_first(l); + const int r = eval_expr_typval(expr, true, argv, 2, rettv); + + tv_clear(&argv[0]); + if (r == FAIL || called_emsg != called_emsg_start) { + break; } + } + tv_list_set_lock(l, prev_locked); +} - tv_copy(&initial, rettv); +/// Implementation of reduce() for String "argvars[0]" using the function "expr" +/// starting with the optional initial value "argvars[2]" and return the result +/// in "rettv". +static void reduce_string(typval_T *argvars, typval_T *expr, typval_T *rettv) +{ + const char *p = tv_get_string(&argvars[0]); + int len; + const int called_emsg_start = called_emsg; - if (l != NULL) { - const VarLockStatus prev_locked = tv_list_locked(l); - const int called_emsg_start = called_emsg; - - tv_list_set_lock(l, VAR_FIXED); // disallow the list changing here - for (; li != NULL; li = TV_LIST_ITEM_NEXT(l, li)) { - argv[0] = *rettv; - argv[1] = *TV_LIST_ITEM_TV(li); - rettv->v_type = VAR_UNKNOWN; - const int r = call_func((char *)func_name, -1, rettv, 2, argv, &funcexe); - tv_clear(&argv[0]); - if (r == FAIL || called_emsg != called_emsg_start) { - break; - } - } - tv_list_set_lock(l, prev_locked); + if (argvars[2].v_type == VAR_UNKNOWN) { + if (*p == NUL) { + semsg(_(e_reduceempty), "String"); + return; } + len = utfc_ptr2len(p); + *rettv = (typval_T){ + .v_type = VAR_STRING, + .v_lock = VAR_UNLOCKED, + .vval.v_string = xmemdupz(p, (size_t)len), + }; + p += len; + } else if (tv_check_for_string_arg(argvars, 2) == FAIL) { + return; } else { - const blob_T *const b = argvars[0].vval.v_blob; - int i; + tv_copy(&argvars[2], rettv); + } - if (argvars[2].v_type == VAR_UNKNOWN) { - if (tv_blob_len(b) == 0) { - semsg(_(e_reduceempty), "Blob"); - return; - } - initial.v_type = VAR_NUMBER; - initial.vval.v_number = tv_blob_get(b, 0); - i = 1; - } else if (argvars[2].v_type != VAR_NUMBER) { - emsg(_(e_number_exp)); + for (; *p != NUL; p += len) { + typval_T argv[3]; + argv[0] = *rettv; + len = utfc_ptr2len(p); + argv[1] = (typval_T){ + .v_type = VAR_STRING, + .v_lock = VAR_UNLOCKED, + .vval.v_string = xmemdupz(p, (size_t)len), + }; + + const int r = eval_expr_typval(expr, true, argv, 2, rettv); + + tv_clear(&argv[0]); + tv_clear(&argv[1]); + if (r == FAIL || called_emsg != called_emsg_start) { + break; + } + } +} + +/// Implementation of reduce() for Blob "argvars[0]" using the function "expr" +/// starting with the optional initial value "argvars[2]" and return the result +/// in "rettv". +static void reduce_blob(typval_T *argvars, typval_T *expr, typval_T *rettv) +{ + const blob_T *const b = argvars[0].vval.v_blob; + const int called_emsg_start = called_emsg; + + typval_T initial; + int i; + if (argvars[2].v_type == VAR_UNKNOWN) { + if (tv_blob_len(b) == 0) { + semsg(_(e_reduceempty), "Blob"); return; - } else { - initial = argvars[2]; - i = 0; } + initial = (typval_T){ + .v_type = VAR_NUMBER, + .v_lock = VAR_UNLOCKED, + .vval.v_number = tv_blob_get(b, 0), + }; + i = 1; + } else if (tv_check_for_number_arg(argvars, 2) == FAIL) { + return; + } else { + initial = argvars[2]; + i = 0; + } + + tv_copy(&initial, rettv); + for (; i < tv_blob_len(b); i++) { + typval_T argv[3]; + argv[0] = *rettv; + argv[1] = (typval_T){ + .v_type = VAR_NUMBER, + .v_lock = VAR_UNLOCKED, + .vval.v_number = tv_blob_get(b, i), + }; - tv_copy(&initial, rettv); - for (; i < tv_blob_len(b); i++) { - argv[0] = *rettv; - argv[1].v_type = VAR_NUMBER; - argv[1].vval.v_number = tv_blob_get(b, i); - if (call_func((char *)func_name, -1, rettv, 2, argv, &funcexe) == FAIL) { - return; - } + const int r = eval_expr_typval(expr, true, argv, 2, rettv); + + if (r == FAIL || called_emsg != called_emsg_start) { + return; } } } +/// "reduce(list, { accumulator, element -> value } [, initial])" function +/// "reduce(blob, { accumulator, element -> value } [, initial])" function +/// "reduce(string, { accumulator, element -> value } [, initial])" function +static void f_reduce(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + if (argvars[0].v_type != VAR_STRING + && argvars[0].v_type != VAR_LIST + && argvars[0].v_type != VAR_BLOB) { + emsg(_(e_string_list_or_blob_required)); + return; + } + + const char *func_name; + if (argvars[1].v_type == VAR_FUNC) { + func_name = argvars[1].vval.v_string; + } else if (argvars[1].v_type == VAR_PARTIAL) { + func_name = partial_name(argvars[1].vval.v_partial); + } else { + func_name = tv_get_string(&argvars[1]); + } + if (func_name == NULL || *func_name == NUL) { + emsg(_(e_missing_function_argument)); + return; + } + + if (argvars[0].v_type == VAR_LIST) { + reduce_list(argvars, &argvars[1], rettv); + } else if (argvars[0].v_type == VAR_STRING) { + reduce_string(argvars, &argvars[1], rettv); + } else { + reduce_blob(argvars, &argvars[1], rettv); + } +} + #define SP_NOMOVE 0x01 ///< don't move cursor #define SP_REPEAT 0x02 ///< repeat to find outer pair #define SP_RETCOUNT 0x04 ///< return matchcount @@ -6165,8 +6498,8 @@ static int search_cmn(typval_T *argvars, pos_T *match_pos, int *flagsp) { bool save_p_ws = p_ws; int retval = 0; // default: FAIL - long lnum_stop = 0; - long time_limit = 0; + linenr_T lnum_stop = 0; + int64_t time_limit = 0; int options = SEARCH_KEEP; bool use_skip = false; @@ -6188,7 +6521,7 @@ static int search_cmn(typval_T *argvars, pos_T *match_pos, int *flagsp) // Optional arguments: line number to stop searching, timeout and skip. if (argvars[1].v_type != VAR_UNKNOWN && argvars[2].v_type != VAR_UNKNOWN) { - lnum_stop = tv_get_number_chk(&argvars[2], NULL); + lnum_stop = (linenr_T)tv_get_number_chk(&argvars[2], NULL); if (lnum_stop < 0) { goto theend; } @@ -6218,14 +6551,14 @@ static int search_cmn(typval_T *argvars, pos_T *match_pos, int *flagsp) pos_T pos = save_cursor = curwin->w_cursor; pos_T firstpos = { 0 }; searchit_arg_T sia = { - .sa_stop_lnum = (linenr_T)lnum_stop, + .sa_stop_lnum = lnum_stop, .sa_tm = &tm, }; int subpatnum; // Repeat until {skip} returns false. - for (;;) { + while (true) { subpatnum = searchit(curwin, curbuf, &pos, NULL, dir, (char *)pat, 1, options, RE_SEARCH, &sia); // finding the first match again means there is no match where {skip} @@ -6363,6 +6696,7 @@ static void f_rpcrequest(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) sctx_T save_current_sctx; char *save_autocmd_fname, *save_autocmd_match; + bool save_autocmd_fname_full; int save_autocmd_bufnr; funccal_entry_T funccal_entry; @@ -6372,6 +6706,7 @@ static void f_rpcrequest(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) save_current_sctx = current_sctx; save_autocmd_fname = autocmd_fname; save_autocmd_match = autocmd_match; + save_autocmd_fname_full = autocmd_fname_full; save_autocmd_bufnr = autocmd_bufnr; save_funccal(&funccal_entry); @@ -6380,6 +6715,7 @@ static void f_rpcrequest(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) ((estack_T *)exestack.ga_data)[exestack.ga_len++] = provider_caller_scope.es_entry; autocmd_fname = provider_caller_scope.autocmd_fname; autocmd_match = provider_caller_scope.autocmd_match; + autocmd_fname_full = provider_caller_scope.autocmd_fname_full; autocmd_bufnr = provider_caller_scope.autocmd_bufnr; set_current_funccal((funccall_T *)(provider_caller_scope.funccalp)); } @@ -6397,6 +6733,7 @@ static void f_rpcrequest(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) exestack.ga_len--; autocmd_fname = save_autocmd_fname; autocmd_match = save_autocmd_match; + autocmd_fname_full = save_autocmd_fname_full; autocmd_bufnr = save_autocmd_bufnr; restore_funccal(); } @@ -6405,7 +6742,7 @@ static void f_rpcrequest(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) const char *name = NULL; Channel *chan = find_channel(chan_id); if (chan) { - name = rpc_client_name(chan); + name = get_client_info(chan, "name"); } msg_ext_set_kind("rpc_error"); if (name) { @@ -6420,6 +6757,7 @@ static void f_rpcrequest(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) } if (!object_to_vim(result, rettv, &err)) { + assert(ERROR_SET(&err)); semsg(_("Error converting the call result: %s"), err.msg); } @@ -6485,7 +6823,7 @@ static void f_rpcstart(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) // The last item of argv must be NULL argv[i] = NULL; - Channel *chan = channel_job_start(argv, CALLBACK_READER_INIT, + Channel *chan = channel_job_start(argv, NULL, CALLBACK_READER_INIT, CALLBACK_READER_INIT, CALLBACK_NONE, false, true, false, false, kChannelStdinPipe, NULL, 0, 0, NULL, @@ -6556,7 +6894,9 @@ static void f_screenchar(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) if (row < 0 || row >= grid->rows || col < 0 || col >= grid->cols) { c = -1; } else { - c = utf_ptr2char((char *)grid->chars[grid->line_offset[row] + (size_t)col]); + char buf[MAX_SCHAR_SIZE + 1]; + schar_get(buf, grid_getchar(grid, row, col, NULL)); + c = utf_ptr2char(buf); } rettv->vval.v_number = c; } @@ -6570,21 +6910,22 @@ static void f_screenchars(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) ScreenGrid *grid; screenchar_adjust(&grid, &row, &col); + tv_list_alloc_ret(rettv, kListLenMayKnow); if (row < 0 || row >= grid->rows || col < 0 || col >= grid->cols) { - tv_list_alloc_ret(rettv, 0); return; } - int pcc[MAX_MCO]; - int c = utfc_ptr2char((char *)grid->chars[grid->line_offset[row] + (size_t)col], pcc); - int composing_len = 0; - while (pcc[composing_len] != 0) { - composing_len++; - } - tv_list_alloc_ret(rettv, composing_len + 1); - tv_list_append_number(rettv->vval.v_list, c); - for (int i = 0; i < composing_len; i++) { - tv_list_append_number(rettv->vval.v_list, pcc[i]); - } + + char buf[MAX_SCHAR_SIZE + 1]; + schar_get(buf, grid_getchar(grid, row, col, NULL)); + + // schar values are already processed chars which are always NUL-terminated. + // A single [0] is expected when char is NUL. + size_t i = 0; + do { + int c = utf_ptr2char(buf + i); + tv_list_append_number(rettv->vval.v_list, c); + i += (size_t)utf_ptr2len(buf + i); + } while (buf[i] != NUL); } /// "screencol()" function @@ -6617,7 +6958,9 @@ static void f_screenstring(typval_T *argvars, typval_T *rettv, EvalFuncData fptr return; } - rettv->vval.v_string = xstrdup((char *)grid->chars[grid->line_offset[row] + (size_t)col]); + char buf[MAX_SCHAR_SIZE + 1]; + schar_get(buf, grid_getchar(grid, row, col, NULL)); + rettv->vval.v_string = xstrdup(buf); } /// "search()" function @@ -6656,8 +6999,8 @@ static int searchpair_cmn(typval_T *argvars, pos_T *match_pos) bool save_p_ws = p_ws; int flags = 0; int retval = 0; // default: FAIL - long lnum_stop = 0; - long time_limit = 0; + linenr_T lnum_stop = 0; + int64_t time_limit = 0; // Get the three pattern arguments: start, middle, end. Will result in an // error if not a valid argument. @@ -6699,7 +7042,7 @@ static int searchpair_cmn(typval_T *argvars, pos_T *match_pos) skip = &argvars[4]; if (argvars[5].v_type != VAR_UNKNOWN) { - lnum_stop = tv_get_number_chk(&argvars[5], NULL); + lnum_stop = (linenr_T)tv_get_number_chk(&argvars[5], NULL); if (lnum_stop < 0) { semsg(_(e_invarg2), tv_get_string(&argvars[5])); goto theend; @@ -6714,8 +7057,8 @@ static int searchpair_cmn(typval_T *argvars, pos_T *match_pos) } } - retval = (int)do_searchpair(spat, mpat, epat, dir, skip, - flags, match_pos, (linenr_T)lnum_stop, time_limit); + retval = do_searchpair(spat, mpat, epat, dir, skip, + flags, match_pos, lnum_stop, time_limit); theend: p_ws = save_p_ws; @@ -6760,19 +7103,19 @@ static void f_searchpairpos(typval_T *argvars, typval_T *rettv, EvalFuncData fpt /// @param time_limit stop after this many msec /// /// @returns 0 or -1 for no match, -long do_searchpair(const char *spat, const char *mpat, const char *epat, int dir, - const typval_T *skip, int flags, pos_T *match_pos, linenr_T lnum_stop, - long time_limit) +int do_searchpair(const char *spat, const char *mpat, const char *epat, int dir, + const typval_T *skip, int flags, pos_T *match_pos, linenr_T lnum_stop, + int64_t time_limit) FUNC_ATTR_NONNULL_ARG(1, 2, 3) { - long retval = 0; + int retval = 0; int nest = 1; bool use_skip = false; int options = SEARCH_KEEP; // Make 'cpoptions' empty, the 'l' flag should not be used here. char *save_cpo = p_cpo; - p_cpo = empty_option; + p_cpo = empty_string_option; // Set the time limit, if there is one. proftime_T tm = profile_setlimit(time_limit); @@ -6805,13 +7148,13 @@ long do_searchpair(const char *spat, const char *mpat, const char *epat, int dir pos_T foundpos; clearpos(&foundpos); char *pat = pat3; - for (;;) { + while (true) { searchit_arg_T sia = { .sa_stop_lnum = lnum_stop, .sa_tm = &tm, }; - int n = searchit(curwin, curbuf, &pos, NULL, dir, pat, 1L, + int n = searchit(curwin, curbuf, &pos, NULL, dir, pat, 1, options, RE_SEARCH, &sia); if (n == FAIL || (firstpos.lnum != 0 && equalpos(pos, firstpos))) { // didn't find it or found the first match again: FAIL @@ -6898,14 +7241,14 @@ long do_searchpair(const char *spat, const char *mpat, const char *epat, int dir xfree(pat2); xfree(pat3); - if (p_cpo == empty_option) { + if (p_cpo == empty_string_option) { p_cpo = save_cpo; } else { // Darn, evaluating the {skip} expression changed the value. // If it's still empty it was changed and restored, need to restore in // the complicated way. if (*p_cpo == NUL) { - set_option_value_give_err("cpo", 0L, save_cpo, 0); + set_option_value_give_err("cpo", CSTR_AS_OPTVAL(save_cpo), 0); } free_string_option(save_cpo); } @@ -7012,7 +7355,7 @@ static void f_serverstop(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) } /// Set the cursor or mark position. -/// If 'charpos' is true, then use the column number as a character offset. +/// If "charpos" is true, then use the column number as a character offset. /// Otherwise use the column number as a byte offset. static void set_position(typval_T *argvars, typval_T *rettv, bool charpos) { @@ -7060,8 +7403,7 @@ static void f_setcharpos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) static void f_setcharsearch(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - if (argvars[0].v_type != VAR_DICT) { - emsg(_(e_dictreq)); + if (tv_check_for_dict_arg(argvars, 0) == FAIL) { return; } @@ -7072,8 +7414,7 @@ static void f_setcharsearch(typval_T *argvars, typval_T *rettv, EvalFuncData fpt char *const csearch = tv_dict_get_string(d, "char", false); if (csearch != NULL) { - int pcc[MAX_MCO]; - const int c = utfc_ptr2char(csearch, pcc); + int c = utf_ptr2char(csearch); set_last_csearch(c, csearch, utfc_ptr2len(csearch)); } @@ -7101,6 +7442,13 @@ static void f_setenv(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) char valbuf[NUMBUFLEN]; const char *name = tv_get_string_buf(&argvars[0], namebuf); + // setting an environment variable may be dangerous, e.g. you could + // setenv GCONV_PATH=/tmp and then have iconv() unexpectedly call + // a shell command using some shared library: + if (check_secure()) { + return; + } + if (argvars[1].v_type == VAR_SPECIAL && argvars[1].vval.v_special == kSpecialVarNull) { vim_unsetenv_ext(name); @@ -7147,7 +7495,7 @@ static void f_setpos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) } /// Translate a register type string to the yank type and block length -static int get_yank_type(char **const pp, MotionType *const yank_type, long *const block_len) +static int get_yank_type(char **const pp, MotionType *const yank_type, int *const block_len) FUNC_ATTR_NONNULL_ALL { char *stropt = *pp; @@ -7165,7 +7513,7 @@ static int get_yank_type(char **const pp, MotionType *const yank_type, long *con *yank_type = kMTBlockWise; if (ascii_isdigit(stropt[1])) { stropt++; - *block_len = getdigits_long(&stropt, false, 0) - 1; + *block_len = getdigits_int(&stropt, false, 0) - 1; stropt--; } break; @@ -7181,7 +7529,7 @@ static void f_setreg(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { bool append = false; - long block_len = -1; + int block_len = -1; MotionType yank_type = kMTUnknown; rettv->vval.v_number = 1; // FAIL is default. @@ -7320,7 +7668,7 @@ free_lstval: /// "settagstack()" function static void f_settagstack(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - static char *e_invact2 = N_("E962: Invalid action: '%s'"); + static const char *e_invact2 = N_("E962: Invalid action: '%s'"); char action = 'r'; rettv->vval.v_number = -1; @@ -7332,8 +7680,7 @@ static void f_settagstack(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) } // second argument: dict with items to set in the tag stack - if (argvars[1].v_type != VAR_DICT) { - emsg(_(e_dictreq)); + if (tv_check_for_dict_arg(argvars, 1) == FAIL) { return; } dict_T *d = argvars[1].vval.v_dict; @@ -7344,8 +7691,10 @@ static void f_settagstack(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) // third argument: action - 'a' for append and 'r' for replace. // default is to replace the stack. if (argvars[2].v_type == VAR_UNKNOWN) { - action = 'r'; - } else if (argvars[2].v_type == VAR_STRING) { + // action = 'r'; + } else if (tv_check_for_string_arg(argvars, 2) == FAIL) { + return; + } else { const char *actstr; actstr = tv_get_string_chk(&argvars[2]); if (actstr == NULL) { @@ -7358,9 +7707,6 @@ static void f_settagstack(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) semsg(_(e_invact2), actstr); return; } - } else { - emsg(_(e_stringreq)); - return; } if (set_tagstack(wp, d, action) == OK) { @@ -7395,11 +7741,11 @@ static void f_shiftwidth(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) rettv->vval.v_number = 0; if (argvars[0].v_type != VAR_UNKNOWN) { - long col = (long)tv_get_number_chk(argvars, NULL); + colnr_T col = (colnr_T)tv_get_number_chk(argvars, NULL); if (col < 0) { return; // type error; errmsg already given } - rettv->vval.v_number = get_sw_value_col(curbuf, (colnr_T)col); + rettv->vval.v_number = get_sw_value_col(curbuf, col); return; } rettv->vval.v_number = get_sw_value(curbuf); @@ -7527,7 +7873,7 @@ static void f_spellbadword(typval_T *argvars, typval_T *rettv, EvalFuncData fptr const int wo_spell_save = curwin->w_p_spell; if (!curwin->w_p_spell) { - did_set_spelllang(curwin); + parse_spelllang(curwin); curwin->w_p_spell = true; } @@ -7571,10 +7917,11 @@ static void f_spellbadword(typval_T *argvars, typval_T *rettv, EvalFuncData fptr tv_list_alloc_ret(rettv, 2); tv_list_append_string(rettv->vval.v_list, word, (ssize_t)len); tv_list_append_string(rettv->vval.v_list, - (attr == HLF_SPB ? "bad" : - attr == HLF_SPR ? "rare" : - attr == HLF_SPL ? "local" : - attr == HLF_SPC ? "caps" : NULL), -1); + (attr == HLF_SPB + ? "bad" : (attr == HLF_SPR + ? "rare" : (attr == HLF_SPL + ? "local" : (attr == HLF_SPC + ? "caps" : NULL)))), -1); } /// "spellsuggest()" function @@ -7584,7 +7931,7 @@ static void f_spellsuggest(typval_T *argvars, typval_T *rettv, EvalFuncData fptr const int wo_spell_save = curwin->w_p_spell; if (!curwin->w_p_spell) { - did_set_spelllang(curwin); + parse_spelllang(curwin); curwin->w_p_spell = true; } @@ -7633,7 +7980,7 @@ static void f_split(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) // Make 'cpoptions' empty, the 'l' flag should not be used here. char *save_cpo = p_cpo; - p_cpo = empty_option; + p_cpo = empty_string_option; const char *str = tv_get_string(&argvars[0]); const char *pat = NULL; @@ -7658,7 +8005,7 @@ static void f_split(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) } regmatch_T regmatch = { - .regprog = vim_regcomp((char *)pat, RE_MAGIC + RE_STRING), + .regprog = vim_regcomp(pat, RE_MAGIC + RE_STRING), .startp = { NULL }, .endp = { NULL }, .rm_ic = false, @@ -7669,18 +8016,18 @@ static void f_split(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) if (*str == NUL) { match = false; // Empty item at the end. } else { - match = vim_regexec_nl(®match, (char *)str, col); + match = vim_regexec_nl(®match, str, col); } const char *end; if (match) { - end = (const char *)regmatch.startp[0]; + end = regmatch.startp[0]; } else { end = str + strlen(str); } if (keepempty || end > str || (tv_list_len(rettv->vval.v_list) > 0 && *str != NUL && match - && end < (const char *)regmatch.endp[0])) { + && end < regmatch.endp[0])) { tv_list_append_string(rettv->vval.v_list, str, end - str); } if (!match) { @@ -7693,7 +8040,7 @@ static void f_split(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) // Don't get stuck at the same match. col = utfc_ptr2len(regmatch.endp[0]); } - str = (const char *)regmatch.endp[0]; + str = regmatch.endp[0]; } vim_regfree(regmatch.regprog); @@ -7751,60 +8098,6 @@ static void f_str2float(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) rettv->v_type = VAR_FLOAT; } -/// "str2list()" function -static void f_str2list(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - tv_list_alloc_ret(rettv, kListLenUnknown); - const char *p = tv_get_string(&argvars[0]); - - for (; *p != NUL; p += utf_ptr2len(p)) { - tv_list_append_number(rettv->vval.v_list, utf_ptr2char(p)); - } -} - -/// "str2nr()" function -static void f_str2nr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - int base = 10; - int what = 0; - - if (argvars[1].v_type != VAR_UNKNOWN) { - base = (int)tv_get_number(&argvars[1]); - if (base != 2 && base != 8 && base != 10 && base != 16) { - emsg(_(e_invarg)); - return; - } - if (argvars[2].v_type != VAR_UNKNOWN && tv_get_bool(&argvars[2])) { - what |= STR2NR_QUOTE; - } - } - - char *p = skipwhite(tv_get_string(&argvars[0])); - bool isneg = (*p == '-'); - if (*p == '+' || *p == '-') { - p = skipwhite(p + 1); - } - switch (base) { - case 2: - what |= STR2NR_BIN | STR2NR_FORCE; - break; - case 8: - what |= STR2NR_OCT | STR2NR_OOCT | STR2NR_FORCE; - break; - case 16: - what |= STR2NR_HEX | STR2NR_FORCE; - break; - } - varnumber_T n; - vim_str2nr(p, NULL, NULL, what, &n, NULL, 0, false); - // Text after the number is silently ignored. - if (isneg) { - rettv->vval.v_number = -n; - } else { - rettv->vval.v_number = n; - } -} - /// "strftime({format}[, {time}])" function static void f_strftime(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { @@ -7824,263 +8117,35 @@ static void f_strftime(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) // MSVC returns NULL for an invalid value of seconds. if (curtime_ptr == NULL) { rettv->vval.v_string = xstrdup(_("(Invalid)")); - } else { - vimconv_T conv; - - conv.vc_type = CONV_NONE; - char *enc = enc_locale(); - convert_setup(&conv, p_enc, enc); - if (conv.vc_type != CONV_NONE) { - p = string_convert(&conv, p, NULL); - } - char result_buf[256]; - if (p == NULL || strftime(result_buf, sizeof(result_buf), p, curtime_ptr) == 0) { - result_buf[0] = NUL; - } - - if (conv.vc_type != CONV_NONE) { - xfree(p); - } - convert_setup(&conv, enc, p_enc); - if (conv.vc_type != CONV_NONE) { - rettv->vval.v_string = string_convert(&conv, result_buf, NULL); - } else { - rettv->vval.v_string = xstrdup(result_buf); - } - - // Release conversion descriptors. - convert_setup(&conv, NULL, NULL); - xfree(enc); - } -} - -/// "strgetchar()" function -static void f_strgetchar(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - rettv->vval.v_number = -1; - - const char *const str = tv_get_string_chk(&argvars[0]); - if (str == NULL) { return; } - bool error = false; - varnumber_T charidx = tv_get_number_chk(&argvars[1], &error); - if (error) { - return; - } - - const size_t len = strlen(str); - size_t byteidx = 0; - - while (charidx >= 0 && byteidx < len) { - if (charidx == 0) { - rettv->vval.v_number = utf_ptr2char(str + byteidx); - break; - } - charidx--; - byteidx += (size_t)utf_ptr2len(str + byteidx); - } -} - -/// "stridx()" function -static void f_stridx(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - rettv->vval.v_number = -1; - - char buf[NUMBUFLEN]; - const char *const needle = tv_get_string_chk(&argvars[1]); - const char *haystack = tv_get_string_buf_chk(&argvars[0], buf); - const char *const haystack_start = haystack; - if (needle == NULL || haystack == NULL) { - return; // Type error; errmsg already given. - } - - if (argvars[2].v_type != VAR_UNKNOWN) { - bool error = false; - - const ptrdiff_t start_idx = (ptrdiff_t)tv_get_number_chk(&argvars[2], - &error); - if (error || start_idx >= (ptrdiff_t)strlen(haystack)) { - return; - } - if (start_idx >= 0) { - haystack += start_idx; - } - } - - const char *pos = strstr(haystack, needle); - if (pos != NULL) { - rettv->vval.v_number = (varnumber_T)(pos - haystack_start); - } -} - -/// "string()" function -static void f_string(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - rettv->v_type = VAR_STRING; - rettv->vval.v_string = encode_tv2string(&argvars[0], NULL); -} - -/// "strlen()" function -static void f_strlen(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - rettv->vval.v_number = (varnumber_T)strlen(tv_get_string(&argvars[0])); -} - -static void strchar_common(typval_T *argvars, typval_T *rettv, bool skipcc) -{ - const char *s = tv_get_string(&argvars[0]); - varnumber_T len = 0; - int (*func_mb_ptr2char_adv)(const char **pp); - - func_mb_ptr2char_adv = skipcc ? mb_ptr2char_adv : mb_cptr2char_adv; - while (*s != NUL) { - func_mb_ptr2char_adv(&s); - len++; - } - rettv->vval.v_number = len; -} - -/// "strcharlen()" function -static void f_strcharlen(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - strchar_common(argvars, rettv, true); -} - -/// "strchars()" function -static void f_strchars(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - int skipcc = false; - - if (argvars[1].v_type != VAR_UNKNOWN) { - skipcc = (int)tv_get_bool(&argvars[1]); - } - if (skipcc < 0 || skipcc > 1) { - semsg(_(e_using_number_as_bool_nr), skipcc); - } else { - strchar_common(argvars, rettv, skipcc); - } -} - -/// "strdisplaywidth()" function -static void f_strdisplaywidth(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - const char *const s = tv_get_string(&argvars[0]); - int col = 0; - - if (argvars[1].v_type != VAR_UNKNOWN) { - col = (int)tv_get_number(&argvars[1]); - } - - rettv->vval.v_number = (varnumber_T)(linetabsize_col(col, (char *)s) - col); -} - -/// "strwidth()" function -static void f_strwidth(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - const char *const s = tv_get_string(&argvars[0]); - - rettv->vval.v_number = (varnumber_T)mb_string2cells(s); -} -/// "strcharpart()" function -static void f_strcharpart(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - const char *const p = tv_get_string(&argvars[0]); - const size_t slen = strlen(p); + vimconv_T conv; - int nbyte = 0; - bool error = false; - varnumber_T nchar = tv_get_number_chk(&argvars[1], &error); - if (!error) { - if (nchar > 0) { - while (nchar > 0 && (size_t)nbyte < slen) { - nbyte += utf_ptr2len(p + nbyte); - nchar--; - } - } else { - nbyte = (int)nchar; - } + conv.vc_type = CONV_NONE; + char *enc = enc_locale(); + convert_setup(&conv, p_enc, enc); + if (conv.vc_type != CONV_NONE) { + p = string_convert(&conv, p, NULL); } - int len = 0; - if (argvars[2].v_type != VAR_UNKNOWN) { - int charlen = (int)tv_get_number(&argvars[2]); - while (charlen > 0 && nbyte + len < (int)slen) { - int off = nbyte + len; - - if (off < 0) { - len += 1; - } else { - len += utf_ptr2len(p + off); - } - charlen--; - } - } else { - len = (int)slen - nbyte; // default: all bytes that are available. + char result_buf[256]; + if (p == NULL || strftime(result_buf, sizeof(result_buf), p, curtime_ptr) == 0) { + result_buf[0] = NUL; } - // Only return the overlap between the specified part and the actual - // string. - if (nbyte < 0) { - len += nbyte; - nbyte = 0; - } else if ((size_t)nbyte > slen) { - nbyte = (int)slen; - } - if (len < 0) { - len = 0; - } else if (nbyte + len > (int)slen) { - len = (int)slen - nbyte; + if (conv.vc_type != CONV_NONE) { + xfree(p); } - - rettv->v_type = VAR_STRING; - rettv->vval.v_string = xstrndup(p + nbyte, (size_t)len); -} - -/// "strpart()" function -static void f_strpart(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - bool error = false; - - const char *const p = tv_get_string(&argvars[0]); - const size_t slen = strlen(p); - - varnumber_T n = tv_get_number_chk(&argvars[1], &error); - varnumber_T len; - if (error) { - len = 0; - } else if (argvars[2].v_type != VAR_UNKNOWN) { - len = tv_get_number(&argvars[2]); + convert_setup(&conv, enc, p_enc); + if (conv.vc_type != CONV_NONE) { + rettv->vval.v_string = string_convert(&conv, result_buf, NULL); } else { - len = (varnumber_T)slen - n; // Default len: all bytes that are available. - } - - // Only return the overlap between the specified part and the actual - // string. - if (n < 0) { - len += n; - n = 0; - } else if (n > (varnumber_T)slen) { - n = (varnumber_T)slen; - } - if (len < 0) { - len = 0; - } else if (n + len > (varnumber_T)slen) { - len = (varnumber_T)slen - n; - } - - if (argvars[2].v_type != VAR_UNKNOWN && argvars[3].v_type != VAR_UNKNOWN) { - int off; - - // length in characters - for (off = (int)n; off < (int)slen && len > 0; len--) { - off += utfc_ptr2len(p + off); - } - len = off - n; + rettv->vval.v_string = xstrdup(result_buf); } - rettv->v_type = VAR_STRING; - rettv->vval.v_string = xmemdupz(p + n, (size_t)len); + // Release conversion descriptors. + convert_setup(&conv, NULL, NULL); + xfree(enc); } /// "strptime({format}, {timestring})" function @@ -8115,56 +8180,6 @@ static void f_strptime(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) xfree(enc); } -/// "strridx()" function -static void f_strridx(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - char buf[NUMBUFLEN]; - const char *const needle = tv_get_string_chk(&argvars[1]); - const char *const haystack = tv_get_string_buf_chk(&argvars[0], buf); - - rettv->vval.v_number = -1; - if (needle == NULL || haystack == NULL) { - return; // Type error; errmsg already given. - } - - const size_t haystack_len = strlen(haystack); - ptrdiff_t end_idx; - if (argvars[2].v_type != VAR_UNKNOWN) { - // Third argument: upper limit for index. - end_idx = (ptrdiff_t)tv_get_number_chk(&argvars[2], NULL); - if (end_idx < 0) { - return; // Can never find a match. - } - } else { - end_idx = (ptrdiff_t)haystack_len; - } - - const char *lastmatch = NULL; - if (*needle == NUL) { - // Empty string matches past the end. - lastmatch = haystack + end_idx; - } else { - for (const char *rest = haystack; *rest != NUL; rest++) { - rest = strstr(rest, needle); - if (rest == NULL || rest > haystack + end_idx) { - break; - } - lastmatch = rest; - } - } - - if (lastmatch != NULL) { - rettv->vval.v_number = (varnumber_T)(lastmatch - haystack); - } -} - -/// "strtrans()" function -static void f_strtrans(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - rettv->v_type = VAR_STRING; - rettv->vval.v_string = transstr(tv_get_string(&argvars[0]), true); -} - /// "submatch()" function static void f_submatch(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { @@ -8175,7 +8190,7 @@ static void f_submatch(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) } if (no < 0 || no >= NSUBEXP) { - semsg(_("E935: invalid submatch number: %d"), no); + semsg(_(e_invalid_submatch_number_nr), no); return; } int retList = 0; @@ -8225,11 +8240,18 @@ static void f_substitute(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) } } +/// "swapfilelist()" function +static void f_swapfilelist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + tv_list_alloc_ret(rettv, kListLenUnknown); + recover_names(NULL, false, rettv->vval.v_list, 0, NULL); +} + /// "swapinfo(swap_filename)" function static void f_swapinfo(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { tv_dict_alloc_ret(rettv); - get_b0_dict(tv_get_string(argvars), rettv->vval.v_dict); + swapfile_dict(tv_get_string(argvars), rettv->vval.v_dict); } /// "swapname(expr)" function @@ -8349,7 +8371,7 @@ static void f_synIDattr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) } rettv->v_type = VAR_STRING; - rettv->vval.v_string = (char *)(p == NULL ? p : xstrdup(p)); + rettv->vval.v_string = p == NULL ? NULL : xstrdup(p); } /// "synIDtrans(id)" function @@ -8392,8 +8414,8 @@ static void f_synconcealed(typval_T *argvars, typval_T *rettv, EvalFuncData fptr cchar = syn_get_sub_char(); if (cchar == NUL && curwin->w_p_cole == 1) { cchar = (curwin->w_p_lcs_chars.conceal == NUL) - ? ' ' - : curwin->w_p_lcs_chars.conceal; + ? ' ' + : curwin->w_p_lcs_chars.conceal; } if (cchar != NUL) { utf_char2bytes(cchar, str); @@ -8432,7 +8454,7 @@ static void f_synstack(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) } } -/// f_system - the VimL system() function +/// f_system - the Vimscript system() function static void f_system(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { get_system_output_as_rettv(argvars, rettv, false); @@ -8513,7 +8535,10 @@ static void f_termopen(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) if (check_secure()) { return; } - + if (text_locked()) { + text_locked_msg(); + return; + } if (curbuf->b_changed) { emsg(_("Can only call this function in an unmodified buffer")); return; @@ -8580,7 +8605,7 @@ static void f_termopen(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) const bool detach = false; ChannelStdinMode stdin_mode = kChannelStdinPipe; uint16_t term_width = (uint16_t)MAX(0, curwin->w_width_inner - win_col_off(curwin)); - Channel *chan = channel_job_start(argv, on_stdout, on_stderr, on_exit, + Channel *chan = channel_job_start(argv, NULL, on_stdout, on_stderr, on_exit, pty, rpc, overlapped, detach, stdin_mode, cwd, term_width, (uint16_t)curwin->w_height_inner, env, &rettv->vval.v_number); @@ -8626,21 +8651,24 @@ static void f_termopen(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) INTEGER_OBJ(pid), false, false, &err); api_clear_error(&err); + channel_incref(chan); channel_terminal_open(curbuf, chan); channel_create_event(chan, NULL); + channel_decref(chan); } /// "timer_info([timer])" function static void f_timer_info(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { + tv_list_alloc_ret(rettv, kListLenUnknown); + + if (tv_check_for_opt_number_arg(argvars, 0) == FAIL) { + return; + } + if (argvars[0].v_type != VAR_UNKNOWN) { - if (argvars[0].v_type != VAR_NUMBER) { - emsg(_(e_number_exp)); - return; - } - tv_list_alloc_ret(rettv, 1); timer_T *timer = find_timer_by_nr(tv_get_number(&argvars[0])); - if (timer != NULL && !timer->stopped) { + if (timer != NULL && (!timer->stopped || timer->refcount > 1)) { add_timer_info(rettv, timer); } } else { @@ -8655,6 +8683,7 @@ static void f_timer_pause(typval_T *argvars, typval_T *unused, EvalFuncData fptr emsg(_(e_number_exp)); return; } + int paused = (bool)tv_get_number(&argvars[1]); timer_T *timer = find_timer_by_nr(tv_get_number(&argvars[0])); if (timer != NULL) { @@ -8679,11 +8708,10 @@ static void f_timer_start(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) } if (argvars[2].v_type != VAR_UNKNOWN) { - dict_T *dict = argvars[2].vval.v_dict; - if (argvars[2].v_type != VAR_DICT || dict == NULL) { - semsg(_(e_invarg2), tv_get_string(&argvars[2])); + if (tv_check_for_nonnull_dict_arg(argvars, 2) == FAIL) { return; } + dict_T *dict = argvars[2].vval.v_dict; dictitem_T *const di = tv_dict_find(dict, S_LEN("repeat")); if (di != NULL) { repeat = (int)tv_get_number(&di->di_tv); @@ -8703,8 +8731,7 @@ static void f_timer_start(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) /// "timer_stop(timerid)" function static void f_timer_stop(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - if (argvars[0].v_type != VAR_NUMBER) { - emsg(_(e_number_exp)); + if (tv_check_for_number_arg(argvars, 0) == FAIL) { return; } @@ -8721,186 +8748,6 @@ static void f_timer_stopall(typval_T *argvars, typval_T *unused, EvalFuncData fp timer_stop_all(); } -/// "tolower(string)" function -static void f_tolower(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - rettv->v_type = VAR_STRING; - rettv->vval.v_string = strcase_save(tv_get_string(&argvars[0]), false); -} - -/// "toupper(string)" function -static void f_toupper(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - rettv->v_type = VAR_STRING; - rettv->vval.v_string = strcase_save(tv_get_string(&argvars[0]), true); -} - -/// "tr(string, fromstr, tostr)" function -static void f_tr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - char buf[NUMBUFLEN]; - char buf2[NUMBUFLEN]; - - const char *in_str = tv_get_string(&argvars[0]); - const char *fromstr = tv_get_string_buf_chk(&argvars[1], buf); - const char *tostr = tv_get_string_buf_chk(&argvars[2], buf2); - - // Default return value: empty string. - rettv->v_type = VAR_STRING; - rettv->vval.v_string = NULL; - if (fromstr == NULL || tostr == NULL) { - return; // Type error; errmsg already given. - } - garray_T ga; - ga_init(&ga, (int)sizeof(char), 80); - - // fromstr and tostr have to contain the same number of chars. - bool first = true; - while (*in_str != NUL) { - const char *cpstr = in_str; - const int inlen = utfc_ptr2len(in_str); - int cplen = inlen; - int idx = 0; - int fromlen; - for (const char *p = fromstr; *p != NUL; p += fromlen) { - fromlen = utfc_ptr2len(p); - if (fromlen == inlen && strncmp(in_str, p, (size_t)inlen) == 0) { - int tolen; - for (p = tostr; *p != NUL; p += tolen) { - tolen = utfc_ptr2len(p); - if (idx-- == 0) { - cplen = tolen; - cpstr = (char *)p; - break; - } - } - if (*p == NUL) { // tostr is shorter than fromstr. - goto error; - } - break; - } - idx++; - } - - if (first && cpstr == in_str) { - // Check that fromstr and tostr have the same number of - // (multi-byte) characters. Done only once when a character - // of in_str doesn't appear in fromstr. - first = false; - int tolen; - for (const char *p = tostr; *p != NUL; p += tolen) { - tolen = utfc_ptr2len(p); - idx--; - } - if (idx != 0) { - goto error; - } - } - - ga_grow(&ga, cplen); - memmove((char *)ga.ga_data + ga.ga_len, cpstr, (size_t)cplen); - ga.ga_len += cplen; - - in_str += inlen; - } - - // add a terminating NUL - ga_append(&ga, NUL); - - rettv->vval.v_string = ga.ga_data; - return; -error: - semsg(_(e_invarg2), fromstr); - ga_clear(&ga); -} - -/// "trim({expr})" function -static void f_trim(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - char buf1[NUMBUFLEN]; - char buf2[NUMBUFLEN]; - const char *head = tv_get_string_buf_chk(&argvars[0], buf1); - const char *mask = NULL; - const char *prev; - const char *p; - int dir = 0; - - rettv->v_type = VAR_STRING; - rettv->vval.v_string = NULL; - if (head == NULL) { - return; - } - - if (argvars[1].v_type != VAR_UNKNOWN && argvars[1].v_type != VAR_STRING) { - semsg(_(e_invarg2), tv_get_string(&argvars[1])); - return; - } - - if (argvars[1].v_type == VAR_STRING) { - mask = tv_get_string_buf_chk(&argvars[1], buf2); - if (argvars[2].v_type != VAR_UNKNOWN) { - bool error = false; - // leading or trailing characters to trim - dir = (int)tv_get_number_chk(&argvars[2], &error); - if (error) { - return; - } - if (dir < 0 || dir > 2) { - semsg(_(e_invarg2), tv_get_string(&argvars[2])); - return; - } - } - } - - int c1; - if (dir == 0 || dir == 1) { - // Trim leading characters - while (*head != NUL) { - c1 = utf_ptr2char((char *)head); - if (mask == NULL) { - if (c1 > ' ' && c1 != 0xa0) { - break; - } - } else { - for (p = mask; *p != NUL; MB_PTR_ADV(p)) { - if (c1 == utf_ptr2char((char *)p)) { - break; - } - } - if (*p == NUL) { - break; - } - } - MB_PTR_ADV(head); - } - } - - const char *tail = head + strlen(head); - if (dir == 0 || dir == 2) { - // Trim trailing characters - for (; tail > head; tail = prev) { - prev = tail; - MB_PTR_BACK(head, prev); - c1 = utf_ptr2char((char *)prev); - if (mask == NULL) { - if (c1 > ' ' && c1 != 0xa0) { - break; - } - } else { - for (p = mask; *p != NUL; MB_PTR_ADV(p)) { - if (c1 == utf_ptr2char((char *)p)) { - break; - } - } - if (*p == NUL) { - break; - } - } - } - } - rettv->vval.v_string = xstrnsave(head, (size_t)(tail - head)); -} - /// "type(expr)" function static void f_type(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { @@ -8933,49 +8780,31 @@ static void f_type(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) rettv->vval.v_number = n; } -/// "undofile(name)" function -static void f_undofile(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +/// "virtcol({expr}, [, {list} [, {winid}]])" function +static void f_virtcol(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - rettv->v_type = VAR_STRING; - const char *const fname = tv_get_string(&argvars[0]); - - if (*fname == NUL) { - // If there is no file name there will be no undo file. - rettv->vval.v_string = NULL; - } else { - char *ffname = FullName_save(fname, true); + colnr_T vcol_start = 0; + colnr_T vcol_end = 0; + switchwin_T switchwin; + bool winchanged = false; - if (ffname != NULL) { - rettv->vval.v_string = u_get_undo_file_name(ffname, false); + if (argvars[1].v_type != VAR_UNKNOWN && argvars[2].v_type != VAR_UNKNOWN) { + // use the window specified in the third argument + tabpage_T *tp; + win_T *wp = win_id2wp_tp((int)tv_get_number(&argvars[2]), &tp); + if (wp == NULL || tp == NULL) { + goto theend; } - xfree(ffname); - } -} -/// "undotree()" function -static void f_undotree(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - tv_dict_alloc_ret(rettv); - - dict_T *dict = rettv->vval.v_dict; - - tv_dict_add_nr(dict, S_LEN("synced"), (varnumber_T)curbuf->b_u_synced); - tv_dict_add_nr(dict, S_LEN("seq_last"), (varnumber_T)curbuf->b_u_seq_last); - tv_dict_add_nr(dict, S_LEN("save_last"), - (varnumber_T)curbuf->b_u_save_nr_last); - tv_dict_add_nr(dict, S_LEN("seq_cur"), (varnumber_T)curbuf->b_u_seq_cur); - tv_dict_add_nr(dict, S_LEN("time_cur"), (varnumber_T)curbuf->b_u_time_cur); - tv_dict_add_nr(dict, S_LEN("save_cur"), (varnumber_T)curbuf->b_u_save_nr_cur); + if (switch_win_noblock(&switchwin, wp, tp, true) != OK) { + goto theend; + } - tv_dict_add_list(dict, S_LEN("entries"), u_eval_tree(curbuf->b_u_oldhead)); -} + check_cursor(); + winchanged = true; + } -/// "virtcol(string)" function -static void f_virtcol(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) -{ - colnr_T vcol = 0; int fnum = curbuf->b_fnum; - pos_T *fp = var2fpos(&argvars[0], false, &fnum, false); if (fp != NULL && fp->lnum <= curbuf->b_ml.ml_line_count && fnum == curbuf->b_fnum) { @@ -8988,11 +8817,23 @@ static void f_virtcol(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) fp->col = (colnr_T)len; } } - getvvcol(curwin, fp, NULL, NULL, &vcol); - vcol++; + getvvcol(curwin, fp, &vcol_start, NULL, &vcol_end); + vcol_start++; + vcol_end++; } - rettv->vval.v_number = vcol; +theend: + if (argvars[1].v_type != VAR_UNKNOWN && tv_get_bool(&argvars[1])) { + tv_list_alloc_ret(rettv, 2); + tv_list_append_number(rettv->vval.v_list, vcol_start); + tv_list_append_number(rettv->vval.v_list, vcol_end); + } else { + rettv->vval.v_number = vcol_end; + } + + if (winchanged) { + restore_win_noblock(&switchwin, true); + } } /// "visualmode()" function @@ -9056,6 +8897,7 @@ static void f_writefile(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) bool binary = false; bool append = false; + bool defer = false; bool do_fsync = !!p_fs; bool mkdir_p = false; if (argvars[2].v_type != VAR_UNKNOWN) { @@ -9069,6 +8911,8 @@ static void f_writefile(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) binary = true; break; case 'a': append = true; break; + case 'D': + defer = true; break; case 's': do_fsync = true; break; case 'S': @@ -9088,6 +8932,11 @@ static void f_writefile(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) if (fname == NULL) { return; } + + if (defer && !can_add_defer()) { + return; + } + FileDescriptor fp; int error; if (*fname == NUL) { @@ -9096,9 +8945,17 @@ static void f_writefile(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) ((append ? kFileAppend : kFileTruncate) | (mkdir_p ? kFileMkDir : kFileCreate) | kFileCreate), 0666)) != 0) { - semsg(_("E482: Can't open file %s for writing: %s"), - fname, os_strerror(error)); + semsg(_("E482: Can't open file %s for writing: %s"), fname, os_strerror(error)); } else { + if (defer) { + typval_T tv = { + .v_type = VAR_STRING, + .v_lock = VAR_UNLOCKED, + .vval.v_string = FullName_save(fname, false), + }; + add_defer("delete", 1, &tv); + } + bool write_ok; if (argvars[0].v_type == VAR_BLOB) { write_ok = write_blob(&fp, argvars[0].vval.v_blob); diff --git a/src/nvim/eval/funcs.h b/src/nvim/eval/funcs.h index 1ae031a952..0c345dacb4 100644 --- a/src/nvim/eval/funcs.h +++ b/src/nvim/eval/funcs.h @@ -1,23 +1,24 @@ -#ifndef NVIM_EVAL_FUNCS_H -#define NVIM_EVAL_FUNCS_H +#pragma once #include <stdbool.h> #include <stdint.h> -#include "nvim/api/private/dispatch.h" -#include "nvim/buffer_defs.h" -#include "nvim/eval/typval.h" +#include "nvim/buffer_defs.h" // IWYU pragma: keep +#include "nvim/cmdexpand_defs.h" // IWYU pragma: keep #include "nvim/eval/typval_defs.h" -#include "nvim/types.h" +#include "nvim/pos_defs.h" // IWYU pragma: keep +#include "nvim/types_defs.h" -/// Prototype of C function that implements VimL function +/// Prototype of C function that implements Vimscript function typedef void (*VimLFunc)(typval_T *args, typval_T *rvar, EvalFuncData data); /// Special flags for base_arg @see EvalFuncDef -#define BASE_NONE 0 ///< Not a method (no base argument). -#define BASE_LAST UINT8_MAX ///< Use the last argument as the method base. +enum { + BASE_NONE = 0, ///< Not a method (no base argument). + BASE_LAST = UINT8_MAX, ///< Use the last argument as the method base. +}; -/// Structure holding VimL function definition +/// Structure holding Vimscript function definition typedef struct { char *name; ///< Name of the function. uint8_t min_argc; ///< Minimal number of arguments. @@ -31,4 +32,3 @@ typedef struct { #ifdef INCLUDE_GENERATED_DECLARATIONS # include "eval/funcs.h.generated.h" #endif -#endif // NVIM_EVAL_FUNCS_H diff --git a/src/nvim/eval/gc.c b/src/nvim/eval/gc.c index 6a54c4ddc1..bcebd87f71 100644 --- a/src/nvim/eval/gc.c +++ b/src/nvim/eval/gc.c @@ -1,6 +1,3 @@ -// This is an open source non-commercial project. Dear PVS-Studio, please check -// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com - #include <stddef.h> #include "nvim/eval/gc.h" diff --git a/src/nvim/eval/gc.h b/src/nvim/eval/gc.h index 3185750c3b..36149ec060 100644 --- a/src/nvim/eval/gc.h +++ b/src/nvim/eval/gc.h @@ -1,13 +1,10 @@ -#ifndef NVIM_EVAL_GC_H -#define NVIM_EVAL_GC_H +#pragma once -#include "nvim/eval/typval.h" #include "nvim/eval/typval_defs.h" extern dict_T *gc_first_dict; extern list_T *gc_first_list; #ifdef INCLUDE_GENERATED_DECLARATIONS -# include "eval/gc.h.generated.h" +# include "eval/gc.h.generated.h" // IWYU pragma: export #endif -#endif // NVIM_EVAL_GC_H diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index 05b4737206..069cdced34 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -1,16 +1,14 @@ -// This is an open source non-commercial project. Dear PVS-Studio, please check -// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com - #include <assert.h> +#include <lauxlib.h> #include <stdbool.h> #include <stddef.h> #include <stdio.h> #include <stdlib.h> #include <string.h> +#include <sys/types.h> -#include "lauxlib.h" -#include "nvim/ascii.h" -#include "nvim/assert.h" +#include "nvim/ascii_defs.h" +#include "nvim/assert_defs.h" #include "nvim/charset.h" #include "nvim/eval.h" #include "nvim/eval/encode.h" @@ -21,33 +19,82 @@ #include "nvim/eval/userfunc.h" #include "nvim/eval/vars.h" #include "nvim/garray.h" +#include "nvim/garray_defs.h" #include "nvim/gettext.h" #include "nvim/globals.h" #include "nvim/hashtab.h" #include "nvim/lib/queue.h" #include "nvim/lua/executor.h" -#include "nvim/macros.h" +#include "nvim/macros_defs.h" #include "nvim/mbyte.h" #include "nvim/mbyte_defs.h" #include "nvim/memory.h" #include "nvim/message.h" #include "nvim/os/input.h" -#include "nvim/pos.h" -#include "nvim/types.h" -#include "nvim/vim.h" +#include "nvim/pos_defs.h" +#include "nvim/strings.h" +#include "nvim/types_defs.h" +#include "nvim/vim_defs.h" + +/// struct storing information about current sort +typedef struct { + int item_compare_ic; + bool item_compare_lc; + bool item_compare_numeric; + bool item_compare_numbers; + bool item_compare_float; + const char *item_compare_func; + partial_T *item_compare_partial; + dict_T *item_compare_selfdict; + bool item_compare_func_err; +} sortinfo_T; + +/// Structure representing one list item, used for sort array. +typedef struct { + listitem_T *item; ///< Sorted list item. + int idx; ///< Sorted list item index. +} ListSortItem; + +typedef int (*ListSorter)(const void *, const void *); #ifdef INCLUDE_GENERATED_DECLARATIONS # include "eval/typval.c.generated.h" #endif -static char e_string_required_for_argument_nr[] +static const char e_variable_nested_too_deep_for_unlock[] + = N_("E743: Variable nested too deep for (un)lock"); +static const char e_using_invalid_value_as_string[] + = N_("E908: Using an invalid value as a String"); +static const char e_string_required_for_argument_nr[] = N_("E1174: String required for argument %d"); -static char e_non_empty_string_required_for_argument_nr[] +static const char e_non_empty_string_required_for_argument_nr[] = N_("E1175: Non-empty string required for argument %d"); -static char e_number_required_for_argument_nr[] +static const char e_dict_required_for_argument_nr[] + = N_("E1206: Dictionary required for argument %d"); +static const char e_number_required_for_argument_nr[] = N_("E1210: Number required for argument %d"); -static char e_string_or_list_required_for_argument_nr[] +static const char e_list_required_for_argument_nr[] + = N_("E1211: List required for argument %d"); +static const char e_bool_required_for_argument_nr[] + = N_("E1212: Bool required for argument %d"); +static const char e_float_or_number_required_for_argument_nr[] + = N_("E1219: Float or Number required for argument %d"); +static const char e_string_or_number_required_for_argument_nr[] + = N_("E1220: String or Number required for argument %d"); +static const char e_string_or_list_required_for_argument_nr[] = N_("E1222: String or List required for argument %d"); +static const char e_list_or_blob_required_for_argument_nr[] + = N_("E1226: List or Blob required for argument %d"); +static const char e_blob_required_for_argument_nr[] + = N_("E1238: Blob required for argument %d"); +static const char e_invalid_value_for_blob_nr[] + = N_("E1239: Invalid value for blob: %d"); +static const char e_string_list_or_blob_required_for_argument_nr[] + = N_("E1252: String, List or Blob required for argument %d"); +static const char e_string_or_function_required_for_argument_nr[] + = N_("E1256: String or function required for argument %d"); +static const char e_non_null_dict_required_for_argument_nr[] + = N_("E1297: Non-NULL Dictionary required for argument %d"); bool tv_in_free_unref_items = false; @@ -58,70 +105,6 @@ bool tv_in_free_unref_items = false; const char *const tv_empty_string = ""; //{{{1 Lists -//{{{2 List log -#ifdef LOG_LIST_ACTIONS -ListLog *list_log_first = NULL; -ListLog *list_log_last = NULL; - -/// Write list log to the given file -/// -/// @param[in] fname File to write log to. Will be appended to if already -/// present. -void list_write_log(const char *const fname) - FUNC_ATTR_NONNULL_ALL -{ - FileDescriptor fp; - const int fo_ret = file_open(&fp, fname, kFileCreate|kFileAppend, 0600); - if (fo_ret != 0) { - semsg(_("E5142: Failed to open file %s: %s"), fname, os_strerror(fo_ret)); - return; - } - for (ListLog *chunk = list_log_first; chunk != NULL;) { - for (size_t i = 0; i < chunk->size; i++) { - char buf[10 + 1 + ((16 + 3) * 3) + (8 + 2) + 2]; - // act : hex " c:" len "[]" "\n\0" - const ListLogEntry entry = chunk->entries[i]; - const size_t snp_len = (size_t)snprintf(buf, sizeof(buf), - "%-10.10s: l:%016" PRIxPTR "[%08d] 1:%016" PRIxPTR " 2:%016" PRIxPTR - "\n", - entry.action, entry.l, entry.len, entry.li1, - entry.li2); - assert(snp_len + 1 == sizeof(buf)); - const ptrdiff_t fw_ret = file_write(&fp, buf, snp_len); - if (fw_ret != (ptrdiff_t)snp_len) { - assert(fw_ret < 0); - if (i) { - memmove(chunk->entries, chunk->entries + i, - sizeof(chunk->entries[0]) * (chunk->size - i)); - chunk->size -= i; - } - semsg(_("E5143: Failed to write to file %s: %s"), - fname, os_strerror((int)fw_ret)); - return; - } - } - list_log_first = chunk->next; - xfree(chunk); - chunk = list_log_first; - } - const int fc_ret = file_close(&fp, true); - if (fc_ret != 0) { - semsg(_("E5144: Failed to close file %s: %s"), fname, os_strerror(fc_ret)); - } -} - -# ifdef EXITFREE -/// Free list log -void list_free_log(void) -{ - for (ListLog *chunk = list_log_first; chunk != NULL;) { - list_log_first = chunk->next; - xfree(chunk); - chunk = list_log_first; - } -} -# endif -#endif //{{{2 List item /// Allocate a list item @@ -210,7 +193,7 @@ void tv_list_watch_fix(list_T *const l, const listitem_T *const item) /// Caller should take care of the reference count. /// /// @param[in] len Expected number of items to be populated before list -/// becomes accessible from VimL. It is still valid to +/// becomes accessible from Vimscript. It is still valid to /// underpopulate a list, value only controls how many elements /// will be allocated in advance. Currently does nothing. /// @see ListLenSpecials. @@ -228,7 +211,6 @@ list_T *tv_list_alloc(const ptrdiff_t len) list->lv_used_prev = NULL; list->lv_used_next = gc_first_list; gc_first_list = list; - list_log(list, NULL, (void *)(uintptr_t)len, "alloc"); list->lua_table_ref = LUA_NOREF; return list; } @@ -259,8 +241,6 @@ void tv_list_init_static10(staticList10_T *const sl) li->li_prev = li - 1; li->li_next = li + 1; } - list_log((const list_T *)sl, &sl->sl_items[0], &sl->sl_items[SL_SIZE - 1], - "s10init"); #undef SL_SIZE } @@ -272,7 +252,6 @@ void tv_list_init_static(list_T *const l) { CLEAR_POINTER(l); l->lv_refcount = DO_NOT_FREE_CNT; - list_log(l, NULL, NULL, "sinit"); } /// Free items contained in a list @@ -281,7 +260,6 @@ void tv_list_init_static(list_T *const l) void tv_list_free_contents(list_T *const l) FUNC_ATTR_NONNULL_ALL { - list_log(l, NULL, NULL, "freecont"); for (listitem_T *item = l->lv_first; item != NULL; item = l->lv_first) { // Remove the item before deleting it. l->lv_first = item->li_next; @@ -311,7 +289,6 @@ void tv_list_free_list(list_T *const l) if (l->lv_used_next != NULL) { l->lv_used_next->lv_used_prev = l->lv_used_prev; } - list_log(l, NULL, NULL, "freelist"); NLUA_CLEAR_REF(l->lua_table_ref); xfree(l); @@ -358,7 +335,6 @@ void tv_list_unref(list_T *const l) void tv_list_drop_items(list_T *const l, listitem_T *const item, listitem_T *const item2) FUNC_ATTR_NONNULL_ALL { - list_log(l, item, item2, "drop"); // Notify watchers. for (listitem_T *ip = item; ip != item2->li_next; ip = ip->li_next) { l->lv_len--; @@ -376,14 +352,12 @@ void tv_list_drop_items(list_T *const l, listitem_T *const item, listitem_T *con item->li_prev->li_next = item2->li_next; } l->lv_idx_item = NULL; - list_log(l, l->lv_first, l->lv_last, "afterdrop"); } /// Like tv_list_drop_items, but also frees all removed items void tv_list_remove_items(list_T *const l, listitem_T *const item, listitem_T *const item2) FUNC_ATTR_NONNULL_ALL { - list_log(l, item, item2, "remove"); tv_list_drop_items(l, item, item2); for (listitem_T *li = item;;) { tv_clear(TV_LIST_ITEM_TV(li)); @@ -407,7 +381,6 @@ void tv_list_move_items(list_T *const l, listitem_T *const item, listitem_T *con list_T *const tgt_l, const int cnt) FUNC_ATTR_NONNULL_ALL { - list_log(l, item, item2, "move"); tv_list_drop_items(l, item, item2); item->li_prev = tgt_l->lv_last; item2->li_next = NULL; @@ -418,7 +391,6 @@ void tv_list_move_items(list_T *const l, listitem_T *const item, listitem_T *con } tgt_l->lv_last = item2; tgt_l->lv_len += cnt; - list_log(tgt_l, tgt_l->lv_first, tgt_l->lv_last, "movetgt"); } /// Insert list item @@ -446,11 +418,10 @@ void tv_list_insert(list_T *const l, listitem_T *const ni, listitem_T *const ite } item->li_prev = ni; l->lv_len++; - list_log(l, ni, item, "insert"); } } -/// Insert VimL value into a list +/// Insert Vimscript value into a list /// /// @param[out] l List to insert to. /// @param[in,out] tv Value to insert. Is copied (@see tv_copy()) to an @@ -472,7 +443,6 @@ void tv_list_insert_tv(list_T *const l, typval_T *const tv, listitem_T *const it void tv_list_append(list_T *const l, listitem_T *const item) FUNC_ATTR_NONNULL_ALL { - list_log(l, item, NULL, "append"); if (l->lv_last == NULL) { // empty list l->lv_first = item; @@ -487,7 +457,7 @@ void tv_list_append(list_T *const l, listitem_T *const item) item->li_next = NULL; } -/// Append VimL value to the end of list +/// Append Vimscript value to the end of list /// /// @param[out] l List to append to. /// @param[in,out] tv Value to append. Is copied (@see tv_copy()) to an @@ -647,55 +617,166 @@ tv_list_copy_error: return NULL; } -/// Flatten "list" in place to depth "maxdepth". +/// Get the list item in "l" with index "n1". "n1" is adjusted if needed. +/// Return NULL if there is no such item. +listitem_T *tv_list_check_range_index_one(list_T *const l, int *const n1, const bool quiet) +{ + listitem_T *li = tv_list_find_index(l, n1); + if (li == NULL) { + if (!quiet) { + semsg(_(e_list_index_out_of_range_nr), (int64_t)(*n1)); + } + return NULL; + } + return li; +} + +/// Check that "n2" can be used as the second index in a range of list "l". +/// If "n1" or "n2" is negative it is changed to the positive index. +/// "li1" is the item for item "n1". +/// Return OK or FAIL. +int tv_list_check_range_index_two(list_T *const l, int *const n1, const listitem_T *const li1, + int *const n2, const bool quiet) +{ + if (*n2 < 0) { + listitem_T *ni = tv_list_find(l, *n2); + if (ni == NULL) { + if (!quiet) { + semsg(_(e_list_index_out_of_range_nr), (int64_t)(*n2)); + } + return FAIL; + } + *n2 = tv_list_idx_of_item(l, ni); + } + + // Check that n2 isn't before n1. + if (*n1 < 0) { + *n1 = tv_list_idx_of_item(l, li1); + } + if (*n2 < *n1) { + if (!quiet) { + semsg(_(e_list_index_out_of_range_nr), (int64_t)(*n2)); + } + return FAIL; + } + return OK; +} + +/// Assign values from list "src" into a range of "dest". +/// "idx1_arg" is the index of the first item in "dest" to be replaced. +/// "idx2" is the index of last item to be replaced, but when "empty_idx2" is +/// true then replace all items after "idx1". +/// "op" is the operator, normally "=" but can be "+=" and the like. +/// "varname" is used for error messages. +/// Returns OK or FAIL. +int tv_list_assign_range(list_T *const dest, list_T *const src, const int idx1_arg, const int idx2, + const bool empty_idx2, const char *const op, const char *const varname) +{ + int idx1 = idx1_arg; + listitem_T *const first_li = tv_list_find_index(dest, &idx1); + listitem_T *src_li; + + // Check whether any of the list items is locked before making any changes. + int idx = idx1; + listitem_T *dest_li = first_li; + for (src_li = tv_list_first(src); src_li != NULL && dest_li != NULL;) { + if (value_check_lock(TV_LIST_ITEM_TV(dest_li)->v_lock, varname, TV_CSTRING)) { + return FAIL; + } + src_li = TV_LIST_ITEM_NEXT(src, src_li); + if (src_li == NULL || (!empty_idx2 && idx2 == idx)) { + break; + } + dest_li = TV_LIST_ITEM_NEXT(dest, dest_li); + idx++; + } + + // Assign the List values to the list items. + idx = idx1; + dest_li = first_li; + for (src_li = tv_list_first(src); src_li != NULL;) { + assert(dest_li != NULL); + if (op != NULL && *op != '=') { + eexe_mod_op(TV_LIST_ITEM_TV(dest_li), TV_LIST_ITEM_TV(src_li), op); + } else { + tv_clear(TV_LIST_ITEM_TV(dest_li)); + tv_copy(TV_LIST_ITEM_TV(src_li), TV_LIST_ITEM_TV(dest_li)); + } + src_li = TV_LIST_ITEM_NEXT(src, src_li); + if (src_li == NULL || (!empty_idx2 && idx2 == idx)) { + break; + } + if (TV_LIST_ITEM_NEXT(dest, dest_li) == NULL) { + // Need to add an empty item. + tv_list_append_number(dest, 0); + // "dest_li" may have become invalid after append, don’t use it. + dest_li = tv_list_last(dest); // Valid again. + } else { + dest_li = TV_LIST_ITEM_NEXT(dest, dest_li); + } + idx++; + } + if (src_li != NULL) { + emsg(_("E710: List value has more items than target")); + return FAIL; + } + if (empty_idx2 + ? (dest_li != NULL && TV_LIST_ITEM_NEXT(dest, dest_li) != NULL) + : idx != idx2) { + emsg(_("E711: List value has not enough items")); + return FAIL; + } + return OK; +} + +/// Flatten up to "maxitems" in "list", starting at "first" to depth "maxdepth". +/// When "first" is NULL use the first item. /// Does nothing if "maxdepth" is 0. /// /// @param[in,out] list List to flatten /// @param[in] maxdepth Maximum depth that will be flattened /// /// @return OK or FAIL -int tv_list_flatten(list_T *list, long maxdepth) - FUNC_ATTR_NONNULL_ARG(1) FUNC_ATTR_WARN_UNUSED_RESULT +void tv_list_flatten(list_T *list, listitem_T *first, int64_t maxitems, int64_t maxdepth) + FUNC_ATTR_NONNULL_ARG(1) { listitem_T *item; - listitem_T *to_free; - int n; + int done = 0; if (maxdepth == 0) { - return OK; + return; + } + + if (first == NULL) { + item = list->lv_first; + } else { + item = first; } - n = 0; - item = list->lv_first; - while (item != NULL) { + while (item != NULL && done < maxitems) { + listitem_T *next = item->li_next; + fast_breakcheck(); if (got_int) { - return FAIL; + return; } if (item->li_tv.v_type == VAR_LIST) { - listitem_T *next = item->li_next; + list_T *itemlist = item->li_tv.vval.v_list; tv_list_drop_items(list, item, item); - tv_list_extend(list, item->li_tv.vval.v_list, next); - tv_clear(&item->li_tv); - to_free = item; + tv_list_extend(list, itemlist, next); - if (item->li_prev == NULL) { - item = list->lv_first; - } else { - item = item->li_prev->li_next; - } - xfree(to_free); - - if (++n >= maxdepth) { - n = 0; - item = next; + if (maxdepth > 0) { + tv_list_flatten(list, + item->li_prev == NULL ? list->lv_first : item->li_prev->li_next, + itemlist->lv_len, maxdepth - 1); } - } else { - n = 0; - item = item->li_next; + tv_clear(&item->li_tv); + xfree(item); } + + done++; + item = next; } - return OK; } /// Extend first list with the second @@ -750,9 +831,67 @@ int tv_list_concat(list_T *const l1, list_T *const l2, typval_T *const tv) return OK; } +static list_T *tv_list_slice(list_T *ol, varnumber_T n1, varnumber_T n2) +{ + list_T *l = tv_list_alloc(n2 - n1 + 1); + listitem_T *item = tv_list_find(ol, (int)n1); + for (; n1 <= n2; n1++) { + tv_list_append_tv(l, TV_LIST_ITEM_TV(item)); + item = TV_LIST_ITEM_NEXT(rettv->vval.v_list, item); + } + return l; +} + +int tv_list_slice_or_index(list_T *list, bool range, varnumber_T n1_arg, varnumber_T n2_arg, + bool exclusive, typval_T *rettv, bool verbose) +{ + int len = tv_list_len(rettv->vval.v_list); + varnumber_T n1 = n1_arg; + varnumber_T n2 = n2_arg; + + if (n1 < 0) { + n1 = len + n1; + } + if (n1 < 0 || n1 >= len) { + // For a range we allow invalid values and return an empty list. + // A list index out of range is an error. + if (!range) { + if (verbose) { + semsg(_(e_list_index_out_of_range_nr), (int64_t)n1); + } + return FAIL; + } + n1 = len; + } + if (range) { + if (n2 < 0) { + n2 = len + n2; + } else if (n2 >= len) { + n2 = len - (exclusive ? 0 : 1); + } + if (exclusive) { + n2--; + } + if (n2 < 0 || n2 + 1 < n1) { + n2 = -1; + } + list_T *l = tv_list_slice(rettv->vval.v_list, n1, n2); + tv_clear(rettv); + tv_list_set_ret(rettv, l); + } else { + // copy the item to "var1" to avoid that freeing the list makes it + // invalid. + typval_T var1; + tv_copy(TV_LIST_ITEM_TV(tv_list_find(rettv->vval.v_list, (int)n1)), &var1); + tv_clear(rettv); + *rettv = var1; + } + return OK; +} + typedef struct { - char_u *s; - char_u *tofree; + char *s; + char *tofree; } Join; /// Join list into a string, helper function @@ -785,7 +924,7 @@ static int list_join_inner(garray_T *const gap, list_T *const l, const char *con sumlen += len; Join *const p = GA_APPEND_VIA_PTR(Join, join_gap); - p->tofree = p->s = (char_u *)s; + p->tofree = p->s = s; line_breakcheck(); }); @@ -806,7 +945,7 @@ static int list_join_inner(garray_T *const gap, list_T *const l, const char *con const Join *const p = ((const Join *)join_gap->ga_data) + i; if (p->s != NULL) { - ga_concat(gap, (char *)p->s); + ga_concat(gap, p->s); } line_breakcheck(); } @@ -886,8 +1025,8 @@ void f_list2str(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) char buf[MB_MAXBYTES + 1]; TV_LIST_ITER_CONST(l, li, { - buf[utf_char2bytes((int)tv_get_number(TV_LIST_ITEM_TV(li)), (char *)buf)] = NUL; - ga_concat(&ga, (char *)buf); + buf[utf_char2bytes((int)tv_get_number(TV_LIST_ITEM_TV(li)), buf)] = NUL; + ga_concat(&ga, buf); }); ga_append(&ga, NUL); @@ -905,14 +1044,14 @@ void tv_list_remove(typval_T *argvars, typval_T *rettv, const char *arg_errmsg) return; } - long idx = tv_get_number_chk(&argvars[1], &error); + int64_t idx = tv_get_number_chk(&argvars[1], &error); listitem_T *item; if (error) { // Type error: do nothing, errmsg already given. } else if ((item = tv_list_find(l, (int)idx)) == NULL) { - semsg(_(e_listidx), (int64_t)idx); + semsg(_(e_list_index_out_of_range_nr), idx); } else { if (argvars[2].v_type == VAR_UNKNOWN) { // Remove one item, return its value. @@ -922,11 +1061,11 @@ void tv_list_remove(typval_T *argvars, typval_T *rettv, const char *arg_errmsg) } else { listitem_T *item2; // Remove range of items, return list with values. - long end = tv_get_number_chk(&argvars[2], &error); + int64_t end = tv_get_number_chk(&argvars[2], &error); if (error) { // Type error: do nothing. } else if ((item2 = tv_list_find(l, (int)end)) == NULL) { - semsg(_(e_listidx), (int64_t)end); + semsg(_(e_list_index_out_of_range_nr), end); } else { int cnt = 0; @@ -948,18 +1087,6 @@ void tv_list_remove(typval_T *argvars, typval_T *rettv, const char *arg_errmsg) } } -/// struct storing information about current sort -typedef struct { - int item_compare_ic; - bool item_compare_lc; - bool item_compare_numeric; - bool item_compare_numbers; - bool item_compare_float; - const char *item_compare_func; - partial_T *item_compare_partial; - dict_T *item_compare_selfdict; - bool item_compare_func_err; -} sortinfo_T; static sortinfo_T *sortinfo = NULL; #define ITEM_COMPARE_FAIL 999 @@ -1027,12 +1154,11 @@ static int item_compare(const void *s1, const void *s2, bool keep_zero) if (sortinfo->item_compare_lc) { res = strcoll(p1, p2); } else { - res = sortinfo->item_compare_ic ? STRICMP(p1, p2): strcmp(p1, p2); + res = sortinfo->item_compare_ic ? STRICMP(p1, p2) : strcmp(p1, p2); } } else { - double n1, n2; - n1 = strtod(p1, &p1); - n2 = strtod(p2, &p2); + double n1 = strtod(p1, &p1); + double n2 = strtod(p2, &p2); res = n1 == n2 ? 0 : n1 > n2 ? 1 : -1; } @@ -1062,8 +1188,6 @@ static int item_compare_not_keeping_zero(const void *s1, const void *s2) static int item_compare2(const void *s1, const void *s2, bool keep_zero) { - ListSortItem *si1, *si2; - int res; typval_T rettv; typval_T argv[3]; const char *func_name; @@ -1074,13 +1198,13 @@ static int item_compare2(const void *s1, const void *s2, bool keep_zero) return 0; } - si1 = (ListSortItem *)s1; - si2 = (ListSortItem *)s2; + ListSortItem *si1 = (ListSortItem *)s1; + ListSortItem *si2 = (ListSortItem *)s2; if (partial == NULL) { func_name = sortinfo->item_compare_func; } else { - func_name = (const char *)partial_name(partial); + func_name = partial_name(partial); } // Copy the values. This is needed to be able to set v_lock to VAR_FIXED @@ -1093,7 +1217,7 @@ static int item_compare2(const void *s1, const void *s2, bool keep_zero) funcexe.fe_evaluate = true; funcexe.fe_partial = partial; funcexe.fe_selfdict = sortinfo->item_compare_selfdict; - res = call_func(func_name, -1, &rettv, 2, argv, &funcexe); + int res = call_func(func_name, -1, &rettv, 2, argv, &funcexe); tv_clear(&argv[0]); tv_clear(&argv[1]); @@ -1135,149 +1259,188 @@ static int item_compare2_not_keeping_zero(const void *s1, const void *s2) return item_compare2(s1, s2, false); } -/// "sort({list})" function -static void do_sort_uniq(typval_T *argvars, typval_T *rettv, bool sort) +/// sort() List "l" +static void do_sort(list_T *l, sortinfo_T *info) { - ListSortItem *ptrs; - long len; - long i; + const int len = tv_list_len(l); - // Pointer to current info struct used in compare function. Save and restore - // the current one for nested calls. - sortinfo_T info; - sortinfo_T *old_sortinfo = sortinfo; - sortinfo = &info; + // Make an array with each entry pointing to an item in the List. + ListSortItem *ptrs = xmalloc((size_t)((unsigned)len * sizeof(ListSortItem))); + + // f_sort(): ptrs will be the list to sort + int i = 0; + TV_LIST_ITER(l, li, { + ptrs[i].item = li; + ptrs[i].idx = i; + i++; + }); - const char *const arg_errmsg = (sort - ? N_("sort() argument") - : N_("uniq() argument")); + info->item_compare_func_err = false; + ListSorter item_compare_func = ((info->item_compare_func == NULL + && info->item_compare_partial == NULL) + ? item_compare_not_keeping_zero + : item_compare2_not_keeping_zero); - if (argvars[0].v_type != VAR_LIST) { - semsg(_(e_listarg), sort ? "sort()" : "uniq()"); - } else { - list_T *const l = argvars[0].vval.v_list; - if (value_check_lock(tv_list_locked(l), arg_errmsg, TV_TRANSLATE)) { - goto theend; + // Sort the array with item pointers. + qsort(ptrs, (size_t)len, sizeof(ListSortItem), item_compare_func); + if (!info->item_compare_func_err) { + // Clear the list and append the items in the sorted order. + l->lv_first = NULL; + l->lv_last = NULL; + l->lv_idx_item = NULL; + l->lv_len = 0; + for (i = 0; i < len; i++) { + tv_list_append(l, ptrs[i].item); } - tv_list_set_ret(rettv, l); + } + if (info->item_compare_func_err) { + emsg(_("E702: Sort compare function failed")); + } - len = tv_list_len(l); - if (len <= 1) { - goto theend; // short list sorts pretty quickly - } - - info.item_compare_ic = false; - info.item_compare_lc = false; - info.item_compare_numeric = false; - info.item_compare_numbers = false; - info.item_compare_float = false; - info.item_compare_func = NULL; - info.item_compare_partial = NULL; - info.item_compare_selfdict = NULL; - - if (argvars[1].v_type != VAR_UNKNOWN) { - // optional second argument: {func} - if (argvars[1].v_type == VAR_FUNC) { - info.item_compare_func = (const char *)argvars[1].vval.v_string; - } else if (argvars[1].v_type == VAR_PARTIAL) { - info.item_compare_partial = argvars[1].vval.v_partial; - } else { - bool error = false; + xfree(ptrs); +} - i = tv_get_number_chk(&argvars[1], &error); - if (error) { - goto theend; // type error; errmsg already given - } - if (i == 1) { - info.item_compare_ic = true; - } else if (argvars[1].v_type != VAR_NUMBER) { - info.item_compare_func = tv_get_string(&argvars[1]); - } else if (i != 0) { - emsg(_(e_invarg)); - goto theend; - } - if (info.item_compare_func != NULL) { - if (*info.item_compare_func == NUL) { - // empty string means default sort - info.item_compare_func = NULL; - } else if (strcmp(info.item_compare_func, "n") == 0) { - info.item_compare_func = NULL; - info.item_compare_numeric = true; - } else if (strcmp(info.item_compare_func, "N") == 0) { - info.item_compare_func = NULL; - info.item_compare_numbers = true; - } else if (strcmp(info.item_compare_func, "f") == 0) { - info.item_compare_func = NULL; - info.item_compare_float = true; - } else if (strcmp(info.item_compare_func, "i") == 0) { - info.item_compare_func = NULL; - info.item_compare_ic = true; - } else if (strcmp(info.item_compare_func, "l") == 0) { - info.item_compare_func = NULL; - info.item_compare_lc = true; - } - } - } +/// uniq() List "l" +static void do_uniq(list_T *l, sortinfo_T *info) +{ + const int len = tv_list_len(l); - if (argvars[2].v_type != VAR_UNKNOWN) { - // optional third argument: {dict} - if (argvars[2].v_type != VAR_DICT) { - emsg(_(e_dictreq)); - goto theend; - } - info.item_compare_selfdict = argvars[2].vval.v_dict; - } - } + // Make an array with each entry pointing to an item in the List. + ListSortItem *ptrs = xmalloc((size_t)((unsigned)len * sizeof(ListSortItem))); - // Make an array with each entry pointing to an item in the List. - ptrs = xmalloc((size_t)((unsigned)len * sizeof(ListSortItem))); + // f_uniq(): ptrs will be a stack of items to remove. - if (sort) { - info.item_compare_func_err = false; - tv_list_item_sort(l, ptrs, - ((info.item_compare_func == NULL - && info.item_compare_partial == NULL) - ? item_compare_not_keeping_zero - : item_compare2_not_keeping_zero), - &info.item_compare_func_err); - if (info.item_compare_func_err) { - emsg(_("E702: Sort compare function failed")); - } + info->item_compare_func_err = false; + ListSorter item_compare_func = ((info->item_compare_func == NULL + && info->item_compare_partial == NULL) + ? item_compare_keeping_zero + : item_compare2_keeping_zero); + + for (listitem_T *li = TV_LIST_ITEM_NEXT(l, tv_list_first(l)); li != NULL;) { + listitem_T *const prev_li = TV_LIST_ITEM_PREV(l, li); + if (item_compare_func(&prev_li, &li) == 0) { + li = tv_list_item_remove(l, li); } else { - ListSorter item_compare_func_ptr; + li = TV_LIST_ITEM_NEXT(l, li); + } + if (info->item_compare_func_err) { + emsg(_("E882: Uniq compare function failed")); + break; + } + } - // f_uniq(): ptrs will be a stack of items to remove. - info.item_compare_func_err = false; - if (info.item_compare_func != NULL - || info.item_compare_partial != NULL) { - item_compare_func_ptr = item_compare2_keeping_zero; - } else { - item_compare_func_ptr = item_compare_keeping_zero; - } + xfree(ptrs); +} - for (listitem_T *li = TV_LIST_ITEM_NEXT(l, tv_list_first(l)) - ; li != NULL;) { - listitem_T *const prev_li = TV_LIST_ITEM_PREV(l, li); - if (item_compare_func_ptr(&prev_li, &li) == 0) { - li = tv_list_item_remove(l, li); - } else { - li = TV_LIST_ITEM_NEXT(l, li); - } - if (info.item_compare_func_err) { - emsg(_("E882: Uniq compare function failed")); - break; - } +/// Parse the optional arguments to sort() and uniq() and return the values in "info". +static int parse_sort_uniq_args(typval_T *argvars, sortinfo_T *info) +{ + info->item_compare_ic = false; + info->item_compare_lc = false; + info->item_compare_numeric = false; + info->item_compare_numbers = false; + info->item_compare_float = false; + info->item_compare_func = NULL; + info->item_compare_partial = NULL; + info->item_compare_selfdict = NULL; + + if (argvars[1].v_type == VAR_UNKNOWN) { + return OK; + } + + // optional second argument: {func} + if (argvars[1].v_type == VAR_FUNC) { + info->item_compare_func = argvars[1].vval.v_string; + } else if (argvars[1].v_type == VAR_PARTIAL) { + info->item_compare_partial = argvars[1].vval.v_partial; + } else { + bool error = false; + int nr = (int)tv_get_number_chk(&argvars[1], &error); + if (error) { + return FAIL; // type error; errmsg already given + } + if (nr == 1) { + info->item_compare_ic = true; + } else if (argvars[1].v_type != VAR_NUMBER) { + info->item_compare_func = tv_get_string(&argvars[1]); + } else if (nr != 0) { + emsg(_(e_invarg)); + return FAIL; + } + if (info->item_compare_func != NULL) { + if (*info->item_compare_func == NUL) { + // empty string means default sort + info->item_compare_func = NULL; + } else if (strcmp(info->item_compare_func, "n") == 0) { + info->item_compare_func = NULL; + info->item_compare_numeric = true; + } else if (strcmp(info->item_compare_func, "N") == 0) { + info->item_compare_func = NULL; + info->item_compare_numbers = true; + } else if (strcmp(info->item_compare_func, "f") == 0) { + info->item_compare_func = NULL; + info->item_compare_float = true; + } else if (strcmp(info->item_compare_func, "i") == 0) { + info->item_compare_func = NULL; + info->item_compare_ic = true; + } else if (strcmp(info->item_compare_func, "l") == 0) { + info->item_compare_func = NULL; + info->item_compare_lc = true; } } + } + + if (argvars[2].v_type != VAR_UNKNOWN) { + // optional third argument: {dict} + if (tv_check_for_dict_arg(argvars, 2) == FAIL) { + return FAIL; + } + info->item_compare_selfdict = argvars[2].vval.v_dict; + } + + return OK; +} + +/// "sort()" or "uniq()" function +static void do_sort_uniq(typval_T *argvars, typval_T *rettv, bool sort) +{ + if (argvars[0].v_type != VAR_LIST) { + semsg(_(e_listarg), sort ? "sort()" : "uniq()"); + return; + } - xfree(ptrs); + // Pointer to current info struct used in compare function. Save and restore + // the current one for nested calls. + sortinfo_T info; + sortinfo_T *old_sortinfo = sortinfo; + sortinfo = &info; + + const char *const arg_errmsg = (sort ? N_("sort() argument") : N_("uniq() argument")); + list_T *const l = argvars[0].vval.v_list; + if (value_check_lock(tv_list_locked(l), arg_errmsg, TV_TRANSLATE)) { + goto theend; + } + tv_list_set_ret(rettv, l); + + const int len = tv_list_len(l); + if (len <= 1) { + goto theend; // short list sorts pretty quickly + } + if (parse_sort_uniq_args(argvars, &info) == FAIL) { + goto theend; + } + + if (sort) { + do_sort(l, &info); + } else { + do_uniq(l, &info); } theend: sortinfo = old_sortinfo; } -/// "sort"({list})" function +/// "sort({list})" function void f_sort(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { do_sort_uniq(argvars, rettv, true); @@ -1336,7 +1499,6 @@ void tv_list_reverse(list_T *const l) if (tv_list_len(l) <= 1) { return; } - list_log(l, NULL, NULL, "reverse"); #define SWAP(a, b) \ do { \ tmp = (a); \ @@ -1354,47 +1516,6 @@ void tv_list_reverse(list_T *const l) l->lv_idx = l->lv_len - l->lv_idx - 1; } -// FIXME Add unit tests for tv_list_item_sort(). - -/// Sort list using libc qsort -/// -/// @param[in,out] l List to sort, will be sorted in-place. -/// @param ptrs Preallocated array of items to sort, must have at least -/// tv_list_len(l) entries. Should not be initialized. -/// @param[in] item_compare_func Function used to compare list items. -/// @param errp Location where information about whether error occurred is -/// saved by item_compare_func. If boolean there appears to be -/// true list will not be modified. Must be initialized to false -/// by the caller. -void tv_list_item_sort(list_T *const l, ListSortItem *const ptrs, - const ListSorter item_compare_func, const bool *errp) - FUNC_ATTR_NONNULL_ARG(3, 4) -{ - const int len = tv_list_len(l); - if (len <= 1) { - return; - } - list_log(l, NULL, NULL, "sort"); - int i = 0; - TV_LIST_ITER(l, li, { - ptrs[i].item = li; - ptrs[i].idx = i; - i++; - }); - // Sort the array with item pointers. - qsort(ptrs, (size_t)len, sizeof(ListSortItem), item_compare_func); - if (!(*errp)) { - // Clear the list and append the items in the sorted order. - l->lv_first = NULL; - l->lv_last = NULL; - l->lv_idx_item = NULL; - l->lv_len = 0; - for (i = 0; i < len; i++) { - tv_list_append(l, ptrs[i].item); - } - } -} - //{{{2 Indexing/searching /// Locate item with a given index in a list and return it @@ -1463,7 +1584,6 @@ listitem_T *tv_list_find(list_T *const l, int n) // Cache the used index. l->lv_idx = idx; l->lv_idx_item = item; - list_log(l, l->lv_idx_item, (void *)(uintptr_t)l->lv_idx, "find"); return item; } @@ -1501,19 +1621,34 @@ const char *tv_list_find_str(list_T *const l, const int n) { const listitem_T *const li = tv_list_find(l, n); if (li == NULL) { - semsg(_(e_listidx), (int64_t)n); + semsg(_(e_list_index_out_of_range_nr), (int64_t)n); return NULL; } return tv_get_string(TV_LIST_ITEM_TV(li)); } +/// Like tv_list_find() but when a negative index is used that is not found use +/// zero and set "idx" to zero. Used for first index of a range. +static listitem_T *tv_list_find_index(list_T *const l, int *const idx) + FUNC_ATTR_WARN_UNUSED_RESULT +{ + listitem_T *li = tv_list_find(l, *idx); + if (li == NULL) { + if (*idx < 0) { + *idx = 0; + li = tv_list_find(l, *idx); + } + } + return li; +} + /// Locate item in a list and return its index /// /// @param[in] l List to search. /// @param[in] item Item to search for. /// /// @return Index of an item or -1 if item is not in the list. -long tv_list_idx_of_item(const list_T *const l, const listitem_T *const item) +int tv_list_idx_of_item(const list_T *const l, const listitem_T *const item) FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE { if (l == NULL) { @@ -1673,7 +1808,7 @@ char *callback_to_string(Callback *cb) } const size_t msglen = 100; - char *msg = (char *)xmallocz(msglen); + char *msg = xmallocz(msglen); switch (cb->type) { case kCallbackFuncref: @@ -1883,7 +2018,7 @@ void tv_dict_item_free(dictitem_T *const item) dictitem_T *tv_dict_item_copy(dictitem_T *const di) FUNC_ATTR_NONNULL_RET FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT { - dictitem_T *const new_di = tv_dict_item_alloc((const char *)di->di_key); + dictitem_T *const new_di = tv_dict_item_alloc(di->di_key); tv_copy(&di->di_tv, &new_di->di_tv); return new_di; } @@ -1895,7 +2030,7 @@ dictitem_T *tv_dict_item_copy(dictitem_T *const di) void tv_dict_item_remove(dict_T *const dict, dictitem_T *const item) FUNC_ATTR_NONNULL_ALL { - hashitem_T *const hi = hash_find(&dict->dv_hashtab, (char *)item->di_key); + hashitem_T *const hi = hash_find(&dict->dv_hashtab, item->di_key); if (HASHITEM_EMPTY(hi)) { semsg(_(e_intern2), "tv_dict_item_remove()"); } else { @@ -2086,6 +2221,16 @@ varnumber_T tv_dict_get_number_def(const dict_T *const d, const char *const key, return tv_get_number(&di->di_tv); } +varnumber_T tv_dict_get_bool(const dict_T *const d, const char *const key, const int def) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT +{ + dictitem_T *const di = tv_dict_find(d, key, -1); + if (di == NULL) { + return def; + } + return tv_get_bool(&di->di_tv); +} + /// Converts a dict to an environment char **tv_dict_to_env(dict_T *denv) { @@ -2100,9 +2245,9 @@ char **tv_dict_to_env(dict_T *denv) TV_DICT_ITER(denv, var, { const char *str = tv_get_string(&var->di_tv); assert(str); - size_t len = strlen((char *)var->di_key) + strlen(str) + strlen("=") + 1; + size_t len = strlen(var->di_key) + strlen(str) + strlen("=") + 1; env[i] = xmalloc(len); - snprintf(env[i], len, "%s=%s", (char *)var->di_key, str); + snprintf(env[i], len, "%s=%s", var->di_key, str); i++; }); @@ -2229,10 +2374,10 @@ int tv_dict_wrong_func_name(dict_T *d, typval_T *tv, const char *name) int tv_dict_add(dict_T *const d, dictitem_T *const item) FUNC_ATTR_NONNULL_ALL { - if (tv_dict_wrong_func_name(d, &item->di_tv, (const char *)item->di_key)) { + if (tv_dict_wrong_func_name(d, &item->di_tv, item->di_key)) { return FAIL; } - return hash_add(&d->dv_hashtab, (char *)item->di_key); + return hash_add(&d->dv_hashtab, item->di_key); } /// Add a list entry to dictionary @@ -2466,9 +2611,9 @@ void tv_dict_extend(dict_T *const d1, dict_T *const d2, const char *const action HASHTAB_ITER(&d2->dv_hashtab, hi2, { dictitem_T *const di2 = TV_DICT_HI2DI(hi2); - dictitem_T *const di1 = tv_dict_find(d1, (const char *)di2->di_key, -1); + dictitem_T *const di1 = tv_dict_find(d1, di2->di_key, -1); // Check the key to be valid when adding to any scope. - if (d1->dv_scope != VAR_NO_SCOPE && !valid_varname((const char *)di2->di_key)) { + if (d1->dv_scope != VAR_NO_SCOPE && !valid_varname(di2->di_key)) { break; } if (di1 == NULL) { @@ -2478,14 +2623,14 @@ void tv_dict_extend(dict_T *const d1, dict_T *const d2, const char *const action dictitem_T *const new_di = di2; if (tv_dict_add(d1, new_di) == OK) { hash_remove(&d2->dv_hashtab, hi2); - tv_dict_watcher_notify(d1, (const char *)new_di->di_key, &new_di->di_tv, NULL); + tv_dict_watcher_notify(d1, new_di->di_key, &new_di->di_tv, NULL); } } else { dictitem_T *const new_di = tv_dict_item_copy(di2); if (tv_dict_add(d1, new_di) == FAIL) { tv_dict_item_free(new_di); } else if (watched) { - tv_dict_watcher_notify(d1, (const char *)new_di->di_key, &new_di->di_tv, NULL); + tv_dict_watcher_notify(d1, new_di->di_key, &new_di->di_tv, NULL); } } } else if (*action == 'e') { @@ -2499,7 +2644,7 @@ void tv_dict_extend(dict_T *const d1, dict_T *const d2, const char *const action break; } // Disallow replacing a builtin function. - if (tv_dict_wrong_func_name(d1, &di2->di_tv, (const char *)di2->di_key)) { + if (tv_dict_wrong_func_name(d1, &di2->di_tv, di2->di_key)) { break; } @@ -2511,8 +2656,7 @@ void tv_dict_extend(dict_T *const d1, dict_T *const d2, const char *const action tv_copy(&di2->di_tv, &di1->di_tv); if (watched) { - tv_dict_watcher_notify(d1, (const char *)di1->di_key, &di1->di_tv, - &oldtv); + tv_dict_watcher_notify(d1, di1->di_key, &di1->di_tv, &oldtv); tv_clear(&oldtv); } } @@ -2547,7 +2691,7 @@ bool tv_dict_equal(dict_T *const d1, dict_T *const d2, const bool ic, const bool } TV_DICT_ITER(d1, di1, { - dictitem_T *const di2 = tv_dict_find(d2, (const char *)di1->di_key, -1); + dictitem_T *const di2 = tv_dict_find(d2, di1->di_key, -1); if (di2 == NULL) { return false; } @@ -2586,12 +2730,12 @@ dict_T *tv_dict_copy(const vimconv_T *const conv, dict_T *const orig, const bool } dictitem_T *new_di; if (conv == NULL || conv->vc_type == CONV_NONE) { - new_di = tv_dict_item_alloc((const char *)di->di_key); + new_di = tv_dict_item_alloc(di->di_key); } else { - size_t len = strlen((char *)di->di_key); - char *const key = (char *)string_convert(conv, (char *)di->di_key, &len); + size_t len = strlen(di->di_key); + char *const key = string_convert(conv, di->di_key, &len); if (key == NULL) { - new_di = tv_dict_item_alloc_len((const char *)di->di_key, len); + new_di = tv_dict_item_alloc_len(di->di_key, len); } else { new_di = tv_dict_item_alloc_len(key, len); xfree(key); @@ -2705,6 +2849,136 @@ bool tv_blob_equal(const blob_T *const b1, const blob_T *const b2) return true; } +/// Returns a slice of "blob" from index "n1" to "n2" in "rettv". The length of +/// the blob is "len". Returns an empty blob if the indexes are out of range. +static int tv_blob_slice(const blob_T *blob, int len, varnumber_T n1, varnumber_T n2, + bool exclusive, typval_T *rettv) +{ + // The resulting variable is a sub-blob. If the indexes + // are out of range the result is empty. + if (n1 < 0) { + n1 = len + n1; + if (n1 < 0) { + n1 = 0; + } + } + if (n2 < 0) { + n2 = len + n2; + } else if (n2 >= len) { + n2 = len - (exclusive ? 0 : 1); + } + if (exclusive) { + n2--; + } + if (n1 >= len || n2 < 0 || n1 > n2) { + tv_clear(rettv); + rettv->v_type = VAR_BLOB; + rettv->vval.v_blob = NULL; + } else { + blob_T *const new_blob = tv_blob_alloc(); + ga_grow(&new_blob->bv_ga, (int)(n2 - n1 + 1)); + new_blob->bv_ga.ga_len = (int)(n2 - n1 + 1); + for (int i = (int)n1; i <= (int)n2; i++) { + tv_blob_set(new_blob, i - (int)n1, tv_blob_get(rettv->vval.v_blob, i)); + } + tv_clear(rettv); + tv_blob_set_ret(rettv, new_blob); + } + + return OK; +} + +/// Return the byte value in "blob" at index "idx" in "rettv". If the index is +/// too big or negative that is an error. The length of the blob is "len". +static int tv_blob_index(const blob_T *blob, int len, varnumber_T idx, typval_T *rettv) +{ + // The resulting variable is a byte value. + // If the index is too big or negative that is an error. + if (idx < 0) { + idx = len + idx; + } + if (idx < len && idx >= 0) { + const int v = (int)tv_blob_get(rettv->vval.v_blob, (int)idx); + tv_clear(rettv); + rettv->v_type = VAR_NUMBER; + rettv->vval.v_number = v; + } else { + semsg(_(e_blobidx), idx); + return FAIL; + } + + return OK; +} + +int tv_blob_slice_or_index(const blob_T *blob, int is_range, varnumber_T n1, varnumber_T n2, + bool exclusive, typval_T *rettv) +{ + int len = tv_blob_len(rettv->vval.v_blob); + + if (is_range) { + return tv_blob_slice(blob, len, n1, n2, exclusive, rettv); + } else { + return tv_blob_index(blob, len, n1, rettv); + } +} + +/// Check if "n1" is a valid index for a blob with length "bloblen". +int tv_blob_check_index(int bloblen, varnumber_T n1, bool quiet) +{ + if (n1 < 0 || n1 > bloblen) { + if (!quiet) { + semsg(_(e_blobidx), n1); + } + return FAIL; + } + return OK; +} + +/// Check if "n1"-"n2" is a valid range for a blob with length "bloblen". +int tv_blob_check_range(int bloblen, varnumber_T n1, varnumber_T n2, bool quiet) +{ + if (n2 < 0 || n2 >= bloblen || n2 < n1) { + if (!quiet) { + semsg(_(e_blobidx), n2); + } + return FAIL; + } + return OK; +} + +/// Set bytes "n1" to "n2" (inclusive) in "dest" to the value of "src". +/// Caller must make sure "src" is a blob. +/// Returns FAIL if the number of bytes does not match. +int tv_blob_set_range(blob_T *dest, varnumber_T n1, varnumber_T n2, typval_T *src) +{ + if (n2 - n1 + 1 != tv_blob_len(src->vval.v_blob)) { + emsg(_("E972: Blob value does not have the right number of bytes")); + return FAIL; + } + + for (int il = (int)n1, ir = 0; il <= (int)n2; il++) { + tv_blob_set(dest, il, tv_blob_get(src->vval.v_blob, ir++)); + } + return OK; +} + +/// Store one byte "byte" in blob "blob" at "idx". +/// Append one byte if needed. +void tv_blob_set_append(blob_T *blob, int idx, uint8_t byte) +{ + garray_T *gap = &blob->bv_ga; + + // Allow for appending a byte. Setting a byte beyond + // the end is an error otherwise. + if (idx <= gap->ga_len) { + if (idx == gap->ga_len) { + ga_grow(gap, 1); + gap->ga_len++; + } + tv_blob_set(blob, idx, byte); + } +} + /// "remove({blob})" function void tv_blob_remove(typval_T *argvars, typval_T *rettv, const char *arg_errmsg) { @@ -2715,7 +2989,7 @@ void tv_blob_remove(typval_T *argvars, typval_T *rettv, const char *arg_errmsg) } bool error = false; - long idx = tv_get_number_chk(&argvars[1], &error); + int64_t idx = tv_get_number_chk(&argvars[1], &error); if (!error) { const int len = tv_blob_len(b); @@ -2725,7 +2999,7 @@ void tv_blob_remove(typval_T *argvars, typval_T *rettv, const char *arg_errmsg) idx = len + idx; } if (idx < 0 || idx >= len) { - semsg(_(e_blobidx), (int64_t)idx); + semsg(_(e_blobidx), idx); return; } if (argvars[2].v_type == VAR_UNKNOWN) { @@ -2736,7 +3010,7 @@ void tv_blob_remove(typval_T *argvars, typval_T *rettv, const char *arg_errmsg) b->bv_ga.ga_len--; } else { // Remove range of items, return blob with values. - long end = tv_get_number_chk(&argvars[2], &error); + int64_t end = tv_get_number_chk(&argvars[2], &error); if (error) { return; } @@ -2745,7 +3019,7 @@ void tv_blob_remove(typval_T *argvars, typval_T *rettv, const char *arg_errmsg) end = len + end; } if (end >= len || idx > end) { - semsg(_(e_blobidx), (int64_t)end); + semsg(_(e_blobidx), end); return; } blob_T *const blob = tv_blob_alloc(); @@ -2764,6 +3038,51 @@ void tv_blob_remove(typval_T *argvars, typval_T *rettv, const char *arg_errmsg) } } +/// blob2list() function +void f_blob2list(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + tv_list_alloc_ret(rettv, kListLenMayKnow); + + if (tv_check_for_blob_arg(argvars, 0) == FAIL) { + return; + } + + blob_T *const blob = argvars->vval.v_blob; + list_T *const l = rettv->vval.v_list; + for (int i = 0; i < tv_blob_len(blob); i++) { + tv_list_append_number(l, tv_blob_get(blob, i)); + } +} + +/// list2blob() function +void f_list2blob(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + tv_blob_alloc_ret(rettv); + blob_T *const blob = rettv->vval.v_blob; + + if (tv_check_for_list_arg(argvars, 0) == FAIL) { + return; + } + + list_T *const l = argvars->vval.v_list; + if (l == NULL) { + return; + } + + TV_LIST_ITER_CONST(l, li, { + bool error = false; + varnumber_T n = tv_get_number_chk(TV_LIST_ITEM_TV(li), &error); + if (error || n < 0 || n > 255) { + if (!error) { + semsg(_(e_invalid_value_for_blob_nr), (int)n); + } + ga_clear(&blob->bv_ga); + return; + } + ga_append(&blob->bv_ga, (uint8_t)n); + }); +} + //{{{1 Generic typval operations //{{{2 Init/alloc/clear //{{{3 Alloc @@ -2774,7 +3093,7 @@ void tv_blob_remove(typval_T *argvars, typval_T *rettv, const char *arg_errmsg) /// /// @param[out] ret_tv Structure where list is saved. /// @param[in] len Expected number of items to be populated before list -/// becomes accessible from VimL. It is still valid to +/// becomes accessible from Vimscript. It is still valid to /// underpopulate a list, value only controls how many elements /// will be allocated in advance. @see ListLenSpecials. /// @@ -2820,19 +3139,20 @@ static void tv_dict_list(typval_T *const tv, typval_T *const rettv, const DictLi emsg(_(e_dictreq)); return; } + + tv_list_alloc_ret(rettv, tv_dict_len(tv->vval.v_dict)); if (tv->vval.v_dict == NULL) { + // NULL dict behaves like an empty dict return; } - tv_list_alloc_ret(rettv, tv_dict_len(tv->vval.v_dict)); - TV_DICT_ITER(tv->vval.v_dict, di, { typval_T tv_item = { .v_lock = VAR_UNLOCKED }; switch (what) { case kDictListKeys: tv_item.v_type = VAR_STRING; - tv_item.vval.v_string = xstrdup((char *)di->di_key); + tv_item.vval.v_string = xstrdup(di->di_key); break; case kDictListValues: tv_copy(&di->di_tv, &tv_item); @@ -2847,7 +3167,7 @@ static void tv_dict_list(typval_T *const tv, typval_T *const rettv, const DictLi tv_list_append_owned_tv(sub_l, (typval_T) { .v_type = VAR_STRING, .v_lock = VAR_UNLOCKED, - .vval.v_string = xstrdup((const char *)di->di_key), + .vval.v_string = xstrdup(di->di_key), }); tv_list_append_tv(sub_l, &di->di_tv); @@ -2881,10 +3201,10 @@ void f_values(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) /// "has_key()" function void f_has_key(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - if (argvars[0].v_type != VAR_DICT) { - emsg(_(e_dictreq)); + if (tv_check_for_dict_arg(argvars, 0) == FAIL) { return; } + if (argvars[0].vval.v_dict == NULL) { return; } @@ -2936,22 +3256,19 @@ void tv_blob_alloc_ret(typval_T *const ret_tv) /// /// @param[in] from Blob object to copy from. /// @param[out] to Blob object to copy to. -void tv_blob_copy(typval_T *const from, typval_T *const to) - FUNC_ATTR_NONNULL_ALL +void tv_blob_copy(blob_T *const from, typval_T *const to) + FUNC_ATTR_NONNULL_ARG(2) { - assert(from->v_type == VAR_BLOB); - to->v_type = VAR_BLOB; to->v_lock = VAR_UNLOCKED; - if (from->vval.v_blob == NULL) { + if (from == NULL) { to->vval.v_blob = NULL; } else { tv_blob_alloc_ret(to); - int len = from->vval.v_blob->bv_ga.ga_len; + int len = from->bv_ga.ga_len; if (len > 0) { - to->vval.v_blob->bv_ga.ga_data - = xmemdup(from->vval.v_blob->bv_ga.ga_data, (size_t)len); + to->vval.v_blob->bv_ga.ga_data = xmemdup(from->bv_ga.ga_data, (size_t)len); } to->vval.v_blob->bv_ga.ga_len = len; to->vval.v_blob->bv_ga.ga_maxlen = len; @@ -3019,7 +3336,7 @@ static inline int _nothing_conv_func_start(typval_T *const tv, char *const fun) } } else { func_unref(fun); - if ((const char *)fun != tv_empty_string) { + if (fun != tv_empty_string) { xfree(fun); } tv->vval.v_string = NULL; @@ -3208,55 +3525,59 @@ static inline void _nothing_conv_dict_end(typval_T *const tv, dict_T **const dic /// @param[in,out] tv Value to free. void tv_clear(typval_T *const tv) { - if (tv != NULL && tv->v_type != VAR_UNKNOWN) { - // WARNING: do not translate the string here, gettext is slow and function - // is used *very* often. At the current state encode_vim_to_nothing() does - // not error out and does not use the argument anywhere. - // - // If situation changes and this argument will be used, translate it in the - // place where it is used. - const int evn_ret = encode_vim_to_nothing(NULL, tv, "tv_clear() argument"); - (void)evn_ret; - assert(evn_ret == OK); + if (tv == NULL || tv->v_type == VAR_UNKNOWN) { + return; } + + // WARNING: do not translate the string here, gettext is slow and function + // is used *very* often. At the current state encode_vim_to_nothing() does + // not error out and does not use the argument anywhere. + // + // If situation changes and this argument will be used, translate it in the + // place where it is used. + const int evn_ret = encode_vim_to_nothing(NULL, tv, "tv_clear() argument"); + (void)evn_ret; + assert(evn_ret == OK); } //{{{3 Free -/// Free allocated VimL object and value stored inside +/// Free allocated Vimscript object and value stored inside /// /// @param tv Object to free. void tv_free(typval_T *tv) { - if (tv != NULL) { - switch (tv->v_type) { - case VAR_PARTIAL: - partial_unref(tv->vval.v_partial); - break; - case VAR_FUNC: - func_unref(tv->vval.v_string); - FALLTHROUGH; - case VAR_STRING: - xfree(tv->vval.v_string); - break; - case VAR_BLOB: - tv_blob_unref(tv->vval.v_blob); - break; - case VAR_LIST: - tv_list_unref(tv->vval.v_list); - break; - case VAR_DICT: - tv_dict_unref(tv->vval.v_dict); - break; - case VAR_BOOL: - case VAR_SPECIAL: - case VAR_NUMBER: - case VAR_FLOAT: - case VAR_UNKNOWN: - break; - } - xfree(tv); + if (tv == NULL) { + return; } + + switch (tv->v_type) { + case VAR_PARTIAL: + partial_unref(tv->vval.v_partial); + break; + case VAR_FUNC: + func_unref(tv->vval.v_string); + FALLTHROUGH; + case VAR_STRING: + xfree(tv->vval.v_string); + break; + case VAR_BLOB: + tv_blob_unref(tv->vval.v_blob); + break; + case VAR_LIST: + tv_list_unref(tv->vval.v_list); + break; + case VAR_DICT: + tv_dict_unref(tv->vval.v_dict); + break; + case VAR_BOOL: + case VAR_SPECIAL: + case VAR_NUMBER: + case VAR_FLOAT: + case VAR_UNKNOWN: + break; + } + xfree(tv); } //{{{3 Copy @@ -3331,7 +3652,7 @@ void tv_item_lock(typval_T *const tv, const int deep, const bool lock, const boo static int recurse = 0; if (recurse >= DICT_MAXNEST) { - emsg(_("E743: variable nested too deep for (un)lock")); + emsg(_(e_variable_nested_too_deep_for_unlock)); return; } if (deep == 0) { @@ -3399,7 +3720,7 @@ void tv_item_lock(typval_T *const tv, const int deep, const bool lock, const boo recurse--; } -/// Check whether VimL value is locked itself or refers to a locked container +/// Check whether Vimscript value is locked itself or refers to a locked container /// /// @warning Fixed container is not the same as locked. /// @@ -3498,7 +3819,7 @@ bool value_check_lock(VarLockStatus lock, const char *name, size_t name_len) static int tv_equal_recurse_limit; -/// Compare two VimL values +/// Compare two Vimscript values /// /// Like "==", but strings and numbers are different, as well as floats and /// numbers. @@ -3639,13 +3960,13 @@ bool tv_check_str_or_nr(const typval_T *const tv) #define FUNC_ERROR "E703: Using a Funcref as a Number" static const char *const num_errors[] = { - [VAR_PARTIAL]= N_(FUNC_ERROR), - [VAR_FUNC]= N_(FUNC_ERROR), - [VAR_LIST]= N_("E745: Using a List as a Number"), - [VAR_DICT]= N_("E728: Using a Dictionary as a Number"), - [VAR_FLOAT]= N_("E805: Using a Float as a Number"), - [VAR_BLOB]= N_("E974: Using a Blob as a Number"), - [VAR_UNKNOWN]= N_("E685: using an invalid value as a Number"), + [VAR_PARTIAL] = N_(FUNC_ERROR), + [VAR_FUNC] = N_(FUNC_ERROR), + [VAR_LIST] = N_("E745: Using a List as a Number"), + [VAR_DICT] = N_("E728: Using a Dictionary as a Number"), + [VAR_FLOAT] = N_("E805: Using a Float as a Number"), + [VAR_BLOB] = N_("E974: Using a Blob as a Number"), + [VAR_UNKNOWN] = N_("E685: using an invalid value as a Number"), }; #undef FUNC_ERROR @@ -3681,21 +4002,20 @@ bool tv_check_num(const typval_T *const tv) return false; } -#define FUNC_ERROR "E729: using Funcref as a String" +#define FUNC_ERROR "E729: Using a Funcref as a String" static const char *const str_errors[] = { - [VAR_PARTIAL]= N_(FUNC_ERROR), - [VAR_FUNC]= N_(FUNC_ERROR), - [VAR_LIST]= N_("E730: using List as a String"), - [VAR_DICT]= N_("E731: using Dictionary as a String"), - [VAR_FLOAT]= ((const char *)e_float_as_string), - [VAR_BLOB]= N_("E976: using Blob as a String"), - [VAR_UNKNOWN]= N_("E908: using an invalid value as a String"), + [VAR_PARTIAL] = N_(FUNC_ERROR), + [VAR_FUNC] = N_(FUNC_ERROR), + [VAR_LIST] = N_("E730: Using a List as a String"), + [VAR_DICT] = N_("E731: Using a Dictionary as a String"), + [VAR_BLOB] = N_("E976: Using a Blob as a String"), + [VAR_UNKNOWN] = e_using_invalid_value_as_string, }; #undef FUNC_ERROR -/// Check that given value is a VimL String or can be "cast" to it. +/// Check that given value is a Vimscript String or can be "cast" to it. /// /// Error messages are compatible with tv_get_string_chk() previously used for /// the same purpose. @@ -3711,12 +4031,12 @@ bool tv_check_str(const typval_T *const tv) case VAR_BOOL: case VAR_SPECIAL: case VAR_STRING: + case VAR_FLOAT: return true; case VAR_PARTIAL: case VAR_FUNC: case VAR_LIST: case VAR_DICT: - case VAR_FLOAT: case VAR_BLOB: case VAR_UNKNOWN: emsg(_(str_errors[tv->v_type])); @@ -3728,7 +4048,7 @@ bool tv_check_str(const typval_T *const tv) //{{{2 Get -/// Get the number value of a VimL object +/// Get the number value of a Vimscript object /// /// @note Use tv_get_number_chk() if you need to determine whether there was an /// error. @@ -3744,7 +4064,7 @@ varnumber_T tv_get_number(const typval_T *const tv) return tv_get_number_chk(tv, &error); } -/// Get the number value of a VimL object +/// Get the number value of a Vimscript object /// /// @param[in] tv Object to get value from. /// @param[out] ret_error If type error occurred then `true` will be written @@ -3773,7 +4093,7 @@ varnumber_T tv_get_number_chk(const typval_T *const tv, bool *const ret_error) case VAR_STRING: { varnumber_T n = 0; if (tv->vval.v_string != NULL) { - vim_str2nr(tv->vval.v_string, NULL, NULL, STR2NR_ALL, &n, NULL, 0, false); + vim_str2nr(tv->vval.v_string, NULL, NULL, STR2NR_ALL, &n, NULL, 0, false, NULL); } return n; } @@ -3791,7 +4111,19 @@ varnumber_T tv_get_number_chk(const typval_T *const tv, bool *const ret_error) return (ret_error == NULL ? -1 : 0); } -/// Get the line number from VimL object +varnumber_T tv_get_bool(const typval_T *const tv) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT +{ + return tv_get_number_chk(tv, NULL); +} + +varnumber_T tv_get_bool_chk(const typval_T *const tv, bool *const ret_error) + FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ARG(1) +{ + return tv_get_number_chk(tv, ret_error); +} + +/// Get the line number from Vimscript object /// /// @param[in] tv Object to get value from. Is expected to be a number or /// a special string like ".", "$", … (works with current buffer @@ -3814,7 +4146,7 @@ linenr_T tv_get_lnum(const typval_T *const tv) return lnum; } -/// Get the floating-point value of a VimL object +/// Get the floating-point value of a Vimscript object /// /// Raises an error if object is not number or floating-point. /// @@ -3883,6 +4215,14 @@ int tv_check_for_nonempty_string_arg(const typval_T *const args, const int idx) return OK; } +/// Check for an optional string argument at "idx" +int tv_check_for_opt_string_arg(const typval_T *const args, const int idx) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE +{ + return (args[idx].v_type == VAR_UNKNOWN + || tv_check_for_string_arg(args, idx) != FAIL) ? OK : FAIL; +} + /// Give an error and return FAIL unless "args[idx]" is a number. int tv_check_for_number_arg(const typval_T *const args, const int idx) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE @@ -3902,6 +4242,109 @@ int tv_check_for_opt_number_arg(const typval_T *const args, const int idx) || tv_check_for_number_arg(args, idx) != FAIL) ? OK : FAIL; } +/// Give an error and return FAIL unless "args[idx]" is a float or a number. +int tv_check_for_float_or_nr_arg(const typval_T *const args, const int idx) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE +{ + if (args[idx].v_type != VAR_FLOAT && args[idx].v_type != VAR_NUMBER) { + semsg(_(e_float_or_number_required_for_argument_nr), idx + 1); + return FAIL; + } + return OK; +} + +/// Give an error and return FAIL unless "args[idx]" is a bool. +int tv_check_for_bool_arg(const typval_T *const args, const int idx) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE +{ + if (args[idx].v_type != VAR_BOOL + && !(args[idx].v_type == VAR_NUMBER + && (args[idx].vval.v_number == 0 + || args[idx].vval.v_number == 1))) { + semsg(_(e_bool_required_for_argument_nr), idx + 1); + return FAIL; + } + return OK; +} + +/// Check for an optional bool argument at "idx". +/// Return FAIL if the type is wrong. +int tv_check_for_opt_bool_arg(const typval_T *const args, const int idx) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE +{ + if (args[idx].v_type == VAR_UNKNOWN) { + return OK; + } + return tv_check_for_bool_arg(args, idx); +} + +/// Give an error and return FAIL unless "args[idx]" is a blob. +int tv_check_for_blob_arg(const typval_T *const args, const int idx) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE +{ + if (args[idx].v_type != VAR_BLOB) { + semsg(_(e_blob_required_for_argument_nr), idx + 1); + return FAIL; + } + return OK; +} + +/// Give an error and return FAIL unless "args[idx]" is a list. +int tv_check_for_list_arg(const typval_T *const args, const int idx) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE +{ + if (args[idx].v_type != VAR_LIST) { + semsg(_(e_list_required_for_argument_nr), idx + 1); + return FAIL; + } + return OK; +} + +/// Give an error and return FAIL unless "args[idx]" is a dict. +int tv_check_for_dict_arg(const typval_T *const args, const int idx) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE +{ + if (args[idx].v_type != VAR_DICT) { + semsg(_(e_dict_required_for_argument_nr), idx + 1); + return FAIL; + } + return OK; +} + +/// Give an error and return FAIL unless "args[idx]" is a non-NULL dict. +int tv_check_for_nonnull_dict_arg(const typval_T *const args, const int idx) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE +{ + if (tv_check_for_dict_arg(args, idx) == FAIL) { + return FAIL; + } + if (args[idx].vval.v_dict == NULL) { + semsg(_(e_non_null_dict_required_for_argument_nr), idx + 1); + return FAIL; + } + return OK; +} + +/// Check for an optional dict argument at "idx" +int tv_check_for_opt_dict_arg(const typval_T *const args, const int idx) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE +{ + return (args[idx].v_type == VAR_UNKNOWN + || tv_check_for_dict_arg(args, idx) != FAIL) ? OK : FAIL; +} + +/// Give an error and return FAIL unless "args[idx]" is a string or +/// a number. +int tv_check_for_string_or_number_arg(const typval_T *const args, const int idx) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE +{ + if (args[idx].v_type != VAR_STRING && args[idx].v_type != VAR_NUMBER) { + semsg(_(e_string_or_number_required_for_argument_nr), idx + 1); + return FAIL; + } + return OK; +} + /// Give an error and return FAIL unless "args[idx]" is a string or a list. int tv_check_for_string_or_list_arg(const typval_T *const args, const int idx) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE @@ -3913,7 +4356,53 @@ int tv_check_for_string_or_list_arg(const typval_T *const args, const int idx) return OK; } -/// Get the string value of a "stringish" VimL object. +/// Give an error and return FAIL unless "args[idx]" is a string, a list or a blob. +int tv_check_for_string_or_list_or_blob_arg(const typval_T *const args, const int idx) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE +{ + if (args[idx].v_type != VAR_STRING + && args[idx].v_type != VAR_LIST + && args[idx].v_type != VAR_BLOB) { + semsg(_(e_string_list_or_blob_required_for_argument_nr), idx + 1); + return FAIL; + } + return OK; +} + +/// Check for an optional string or list argument at "idx" +int tv_check_for_opt_string_or_list_arg(const typval_T *const args, const int idx) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE +{ + return (args[idx].v_type == VAR_UNKNOWN + || tv_check_for_string_or_list_arg(args, idx) != FAIL) ? OK : FAIL; +} + +/// Give an error and return FAIL unless "args[idx]" is a string +/// or a function reference. +int tv_check_for_string_or_func_arg(const typval_T *const args, const int idx) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE +{ + if (args[idx].v_type != VAR_PARTIAL + && args[idx].v_type != VAR_FUNC + && args[idx].v_type != VAR_STRING) { + semsg(_(e_string_or_function_required_for_argument_nr), idx + 1); + return FAIL; + } + return OK; +} + +/// Give an error and return FAIL unless "args[idx]" is a list or a blob. +int tv_check_for_list_or_blob_arg(const typval_T *const args, const int idx) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE +{ + if (args[idx].v_type != VAR_LIST && args[idx].v_type != VAR_BLOB) { + semsg(_(e_list_or_blob_required_for_argument_nr), idx + 1); + return FAIL; + } + return OK; +} + +/// Get the string value of a "stringish" Vimscript object. /// /// @param[in] tv Object to get value of. /// @param buf Buffer used to hold numbers and special variables converted to @@ -3929,11 +4418,14 @@ const char *tv_get_string_buf_chk(const typval_T *const tv, char *const buf) { switch (tv->v_type) { case VAR_NUMBER: - snprintf(buf, NUMBUFLEN, "%" PRIdVARNUMBER, tv->vval.v_number); // -V576 + snprintf(buf, NUMBUFLEN, "%" PRIdVARNUMBER, tv->vval.v_number); + return buf; + case VAR_FLOAT: + vim_snprintf(buf, NUMBUFLEN, "%g", tv->vval.v_float); return buf; case VAR_STRING: if (tv->vval.v_string != NULL) { - return (const char *)tv->vval.v_string; + return tv->vval.v_string; } return ""; case VAR_BOOL: @@ -3946,7 +4438,6 @@ const char *tv_get_string_buf_chk(const typval_T *const tv, char *const buf) case VAR_FUNC: case VAR_LIST: case VAR_DICT: - case VAR_FLOAT: case VAR_BLOB: case VAR_UNKNOWN: emsg(_(str_errors[tv->v_type])); @@ -3956,7 +4447,7 @@ const char *tv_get_string_buf_chk(const typval_T *const tv, char *const buf) return NULL; } -/// Get the string value of a "stringish" VimL object. +/// Get the string value of a "stringish" Vimscript object. /// /// @warning For number and special values it uses a single, static buffer. It /// may be used only once, next call to tv_get_string may reuse it. Use @@ -3975,7 +4466,7 @@ const char *tv_get_string_chk(const typval_T *const tv) return tv_get_string_buf_chk(tv, mybuf); } -/// Get the string value of a "stringish" VimL object. +/// Get the string value of a "stringish" Vimscript object. /// /// @warning For number and special values it uses a single, static buffer. It /// may be used only once, next call to tv_get_string may reuse it. Use @@ -3997,7 +4488,7 @@ const char *tv_get_string(const typval_T *const tv) return tv_get_string_buf((typval_T *)tv, mybuf); } -/// Get the string value of a "stringish" VimL object. +/// Get the string value of a "stringish" Vimscript object. /// /// @note tv_get_string_chk() and tv_get_string_buf_chk() are similar, but /// return NULL on error. @@ -4019,3 +4510,34 @@ const char *tv_get_string_buf(const typval_T *const tv, char *const buf) return res != NULL ? res : ""; } + +/// Return true when "tv" is not falsy: non-zero, non-empty string, non-empty +/// list, etc. Mostly like what JavaScript does, except that empty list and +/// empty dictionary are false. +bool tv2bool(const typval_T *const tv) +{ + switch (tv->v_type) { + case VAR_NUMBER: + return tv->vval.v_number != 0; + case VAR_FLOAT: + return tv->vval.v_float != 0.0; + case VAR_PARTIAL: + return tv->vval.v_partial != NULL; + case VAR_FUNC: + case VAR_STRING: + return tv->vval.v_string != NULL && *tv->vval.v_string != NUL; + case VAR_LIST: + return tv->vval.v_list != NULL && tv->vval.v_list->lv_len > 0; + case VAR_DICT: + return tv->vval.v_dict != NULL && tv->vval.v_dict->dv_hashtab.ht_used > 0; + case VAR_BOOL: + return tv->vval.v_bool == kBoolVarTrue; + case VAR_SPECIAL: + return tv->vval.v_special != kSpecialVarNull; + case VAR_BLOB: + return tv->vval.v_blob != NULL && tv->vval.v_blob->bv_ga.ga_len > 0; + case VAR_UNKNOWN: + break; + } + return false; +} diff --git a/src/nvim/eval/typval.h b/src/nvim/eval/typval.h index 3f59cd3547..58f74a9796 100644 --- a/src/nvim/eval/typval.h +++ b/src/nvim/eval/typval.h @@ -1,5 +1,4 @@ -#ifndef NVIM_EVAL_TYPVAL_H -#define NVIM_EVAL_TYPVAL_H +#pragma once #include <assert.h> #include <stdbool.h> @@ -7,84 +6,16 @@ #include <stdint.h> #include <string.h> -#include "nvim/eval/typval_defs.h" +#include "nvim/eval/typval_defs.h" // IWYU pragma: export #include "nvim/func_attr.h" -#include "nvim/garray.h" +#include "nvim/garray_defs.h" #include "nvim/gettext.h" #include "nvim/hashtab.h" #include "nvim/lib/queue.h" -#include "nvim/macros.h" +#include "nvim/macros_defs.h" #include "nvim/mbyte_defs.h" #include "nvim/message.h" -#include "nvim/types.h" - -#ifdef LOG_LIST_ACTIONS -# include "nvim/memory.h" - -extern ListLog *list_log_first; ///< First list log chunk, NULL if missing -extern ListLog *list_log_last; ///< Last list log chunk - -static inline ListLog *list_log_alloc(const size_t size) - REAL_FATTR_ALWAYS_INLINE REAL_FATTR_WARN_UNUSED_RESULT; - -/// Allocate a new log chunk and update globals -/// -/// @param[in] size Number of entries in a new chunk. -/// -/// @return [allocated] Newly allocated chunk. -static inline ListLog *list_log_new(const size_t size) -{ - ListLog *ret = xmalloc(offsetof(ListLog, entries) - + size * sizeof(ret->entries[0])); - ret->size = 0; - ret->capacity = size; - ret->next = NULL; - if (list_log_first == NULL) { - list_log_first = ret; - } else { - list_log_last->next = ret; - } - list_log_last = ret; - return ret; -} - -static inline void list_log(const list_T *const l, const listitem_T *const li1, - const listitem_T *const li2, const char *const action) - REAL_FATTR_ALWAYS_INLINE; - -/// Add new entry to log -/// -/// If last chunk was filled it uses twice as much memory to allocate the next -/// chunk. -/// -/// @param[in] l List to which entry belongs. -/// @param[in] li1 List item 1. -/// @param[in] li2 List item 2, often used for integers and not list items. -/// @param[in] action Logged action. -static inline void list_log(const list_T *const l, const listitem_T *const li1, - const listitem_T *const li2, const char *const action) -{ - ListLog *tgt; - if (list_log_first == NULL) { - tgt = list_log_new(128); - } else if (list_log_last->size == list_log_last->capacity) { - tgt = list_log_new(list_log_last->capacity * 2); - } else { - tgt = list_log_last; - } - tgt->entries[tgt->size++] = (ListLogEntry) { - .l = (uintptr_t)l, - .li1 = (uintptr_t)li1, - .li2 = (uintptr_t)li2, - .len = (l == NULL ? 0 : l->lv_len), - .action = action, - }; -} -#else -# define list_log(...) -# define list_write_log(...) -# define list_free_log() -#endif +#include "nvim/types_defs.h" // In a hashtab item "hi_key" points to "di_key" in a dictitem. // This avoids adding a pointer to the hashtab item. @@ -174,7 +105,6 @@ static inline int tv_list_len(const list_T *l) /// @param[in] l List to check. static inline int tv_list_len(const list_T *const l) { - list_log(l, NULL, NULL, "len"); if (l == NULL) { return 0; } @@ -258,10 +188,8 @@ static inline listitem_T *tv_list_first(const list_T *l) static inline listitem_T *tv_list_first(const list_T *const l) { if (l == NULL) { - list_log(l, NULL, NULL, "first"); return NULL; } - list_log(l, l->lv_first, NULL, "first"); return l->lv_first; } @@ -276,10 +204,8 @@ static inline listitem_T *tv_list_last(const list_T *l) static inline listitem_T *tv_list_last(const list_T *const l) { if (l == NULL) { - list_log(l, NULL, NULL, "last"); return NULL; } - list_log(l, l->lv_last, NULL, "last"); return l->lv_last; } @@ -308,7 +234,7 @@ static inline long tv_dict_len(const dict_T *d) static inline long tv_dict_len(const dict_T *const d) { if (d == NULL) { - return 0L; + return 0; } return (long)d->dv_hashtab.ht_used; } @@ -372,7 +298,7 @@ static inline uint8_t tv_blob_get(const blob_T *const b, int idx) return ((uint8_t *)b->bv_ga.ga_data)[idx]; } -static inline void tv_blob_set(blob_T *b, int idx, uint8_t c) +static inline void tv_blob_set(blob_T *blob, int idx, uint8_t c) REAL_FATTR_ALWAYS_INLINE REAL_FATTR_NONNULL_ALL; /// Store the byte `c` at index `idx` in the blob. @@ -380,12 +306,12 @@ static inline void tv_blob_set(blob_T *b, int idx, uint8_t c) /// @param[in] b Blob to index. Cannot be NULL. /// @param[in] idx Index in a blob. Must be valid. /// @param[in] c Value to store. -static inline void tv_blob_set(blob_T *const b, int idx, uint8_t c) +static inline void tv_blob_set(blob_T *const blob, int idx, uint8_t c) { - ((uint8_t *)b->bv_ga.ga_data)[idx] = c; + ((uint8_t *)blob->bv_ga.ga_data)[idx] = c; } -/// Initialize VimL object +/// Initialize Vimscript object /// /// Initializes to unlocked VAR_UNKNOWN object. /// @@ -413,10 +339,9 @@ extern bool tv_in_free_unref_items; /// @param[in] l List to iterate over. /// @param li Name of the variable with current listitem_T entry. /// @param code Cycle body. -#define _TV_LIST_ITER_MOD(modifier, l, li, code) \ +#define TV_LIST_ITER_MOD(modifier, l, li, code) \ do { \ modifier list_T *const l_ = (l); \ - list_log(l_, NULL, NULL, "iter" #modifier); \ if (l_ != NULL) { \ for (modifier listitem_T *li = l_->lv_first; \ li != NULL; li = li->li_next) { \ @@ -434,7 +359,7 @@ extern bool tv_in_free_unref_items; /// @param li Name of the variable with current listitem_T entry. /// @param code Cycle body. #define TV_LIST_ITER(l, li, code) \ - _TV_LIST_ITER_MOD( , l, li, code) // NOLINT(whitespace/parens) + TV_LIST_ITER_MOD( , l, li, code) // NOLINT(whitespace/parens) /// Iterate over a list /// @@ -445,7 +370,7 @@ extern bool tv_in_free_unref_items; /// @param li Name of the variable with current listitem_T entry. /// @param code Cycle body. #define TV_LIST_ITER_CONST(l, li, code) \ - _TV_LIST_ITER_MOD(const, l, li, code) + TV_LIST_ITER_MOD(const, l, li, code) // Below macros are macros to avoid duplicating code for functionally identical // const and non-const function variants. @@ -498,7 +423,7 @@ static inline bool tv_get_float_chk(const typval_T *tv, float_T *ret_f) /// /// Raises an error if object is not number or floating-point. /// -/// @param[in] tv VimL object to get value from. +/// @param[in] tv Vimscript object to get value from. /// @param[out] ret_f Location where resulting float is stored. /// /// @return true in case of success, false if tv is not a number or float. @@ -518,13 +443,15 @@ static inline bool tv_get_float_chk(const typval_T *const tv, float_T *const ret static inline DictWatcher *tv_dict_watcher_node_data(QUEUE *q) REAL_FATTR_NONNULL_ALL REAL_FATTR_NONNULL_RET REAL_FATTR_PURE - REAL_FATTR_WARN_UNUSED_RESULT REAL_FATTR_ALWAYS_INLINE; + REAL_FATTR_WARN_UNUSED_RESULT REAL_FATTR_ALWAYS_INLINE + FUNC_ATTR_NO_SANITIZE_ADDRESS; /// Compute the `DictWatcher` address from a QUEUE node. /// /// This only exists for .asan-blacklist (ASAN doesn't handle QUEUE_DATA pointer /// arithmetic). static inline DictWatcher *tv_dict_watcher_node_data(QUEUE *q) + FUNC_ATTR_NO_SANITIZE_ADDRESS { return QUEUE_DATA(q, DictWatcher, node); } @@ -557,16 +484,10 @@ static inline bool tv_is_func(const typval_T tv) #ifdef UNIT_TESTING // Do not use enum constants, see commit message. -EXTERN const size_t kTVCstring INIT(= TV_CSTRING); -EXTERN const size_t kTVTranslate INIT(= TV_TRANSLATE); +EXTERN const size_t kTVCstring INIT( = TV_CSTRING); +EXTERN const size_t kTVTranslate INIT( = TV_TRANSLATE); #endif #ifdef INCLUDE_GENERATED_DECLARATIONS # include "eval/typval.h.generated.h" #endif - -#define tv_get_bool tv_get_number -#define tv_get_bool_chk tv_get_number_chk -#define tv_dict_get_bool tv_dict_get_number_def - -#endif // NVIM_EVAL_TYPVAL_H diff --git a/src/nvim/eval/typval_defs.h b/src/nvim/eval/typval_defs.h index 4615198441..c6bd11ccdb 100644 --- a/src/nvim/eval/typval_defs.h +++ b/src/nvim/eval/typval_defs.h @@ -1,16 +1,15 @@ -#ifndef NVIM_EVAL_TYPVAL_DEFS_H -#define NVIM_EVAL_TYPVAL_DEFS_H +#pragma once #include <inttypes.h> #include <limits.h> -#include "nvim/garray.h" -#include "nvim/hashtab.h" +#include "nvim/garray_defs.h" +#include "nvim/hashtab_defs.h" #include "nvim/lib/queue.h" -#include "nvim/pos.h" -#include "nvim/types.h" +#include "nvim/pos_defs.h" +#include "nvim/types_defs.h" -/// Type used for VimL VAR_NUMBER values +/// Type used for Vimscript VAR_NUMBER values typedef int64_t varnumber_T; typedef uint64_t uvarnumber_T; @@ -100,7 +99,7 @@ typedef enum { VAR_FIXED = 2, ///< Locked forever. } VarLockStatus; -/// VimL variable types, for use in typval_T.v_type +/// Vimscript variable types, for use in typval_T.v_type typedef enum { VAR_UNKNOWN = 0, ///< Unknown (unspecified) value. VAR_NUMBER, ///< Number, .v_number is used. @@ -115,6 +114,19 @@ typedef enum { VAR_BLOB, ///< Blob, .v_blob is used. } VarType; +/// Type values for type(). +enum { + VAR_TYPE_NUMBER = 0, + VAR_TYPE_STRING = 1, + VAR_TYPE_FUNC = 2, + VAR_TYPE_LIST = 3, + VAR_TYPE_DICT = 4, + VAR_TYPE_FLOAT = 5, + VAR_TYPE_BOOL = 6, + VAR_TYPE_SPECIAL = 7, + VAR_TYPE_BLOB = 10, +}; + /// Structure that holds an internal variable value typedef struct { VarType v_type; ///< Variable type. @@ -206,7 +218,7 @@ typedef struct { struct { \ typval_T di_tv; /* Structure that holds scope dictionary itself. */ \ uint8_t di_flags; /* Flags. */ \ - char_u di_key[__VA_ARGS__]; /* Key value. */ /* NOLINT(runtime/arrays)*/ \ + char di_key[__VA_ARGS__]; /* Key value. */ /* NOLINT(runtime/arrays)*/ \ } /// Structure to hold a scope dictionary @@ -273,6 +285,12 @@ typedef struct { linenr_T sc_lnum; ///< line number } sctx_T; +/// Stores an identifier of a script or channel that last set an option. +typedef struct { + sctx_T script_ctx; /// script context where the option was last set + uint64_t channel_id; /// Only used when script_id is SID_API_CLIENT. +} LastSet; + /// Maximum number of function arguments enum { MAX_FUNC_ARGS = 20, }; /// Short variable name length @@ -284,27 +302,27 @@ enum { FIXVAR_CNT = 12, }; typedef struct funccall_S funccall_T; struct funccall_S { - ufunc_T *func; ///< Function being called. - int linenr; ///< Next line to be executed. - int returned; ///< ":return" used. - /// Fixed variables for arguments. - TV_DICTITEM_STRUCT(VAR_SHORT_LEN + 1) fixvar[FIXVAR_CNT]; - dict_T l_vars; ///< l: local function variables. - ScopeDictDictItem l_vars_var; ///< Variable for l: scope. - dict_T l_avars; ///< a: argument variables. - ScopeDictDictItem l_avars_var; ///< Variable for a: scope. - list_T l_varlist; ///< List for a:000. - listitem_T l_listitems[MAX_FUNC_ARGS]; ///< List items for a:000. - typval_T *rettv; ///< Return value. - linenr_T breakpoint; ///< Next line with breakpoint or zero. - int dbg_tick; ///< debug_tick when breakpoint was set. - int level; ///< Top nesting level of executed function. - proftime_T prof_child; ///< Time spent in a child. - funccall_T *caller; ///< Calling function or NULL; or next funccal in - ///< list pointed to by previous_funccal. - int fc_refcount; ///< Number of user functions that reference this funccall. - int fc_copyID; ///< CopyID used for garbage collection. - garray_T fc_funcs; ///< List of ufunc_T* which keep a reference to "func". + ufunc_T *fc_func; ///< Function being called. + int fc_linenr; ///< Next line to be executed. + int fc_returned; ///< ":return" used. + TV_DICTITEM_STRUCT(VAR_SHORT_LEN + 1) fc_fixvar[FIXVAR_CNT]; ///< Fixed variables for arguments. + dict_T fc_l_vars; ///< l: local function variables. + ScopeDictDictItem fc_l_vars_var; ///< Variable for l: scope. + dict_T fc_l_avars; ///< a: argument variables. + ScopeDictDictItem fc_l_avars_var; ///< Variable for a: scope. + list_T fc_l_varlist; ///< List for a:000. + listitem_T fc_l_listitems[MAX_FUNC_ARGS]; ///< List items for a:000. + typval_T *fc_rettv; ///< Return value. + linenr_T fc_breakpoint; ///< Next line with breakpoint or zero. + int fc_dbg_tick; ///< "debug_tick" when breakpoint was set. + int fc_level; ///< Top nesting level of executed function. + garray_T fc_defer; ///< Functions to be called on return. + proftime_T fc_prof_child; ///< Time spent in a child. + funccall_T *fc_caller; ///< Calling function or NULL; or next funccal in + ///< list pointed to by previous_funccal. + int fc_refcount; ///< Number of user functions that reference this funccall. + int fc_copyID; ///< CopyID used for garbage collection. + garray_T fc_ufuncs; ///< List of ufunc_T* which keep a reference to "fc_func". }; /// Structure to hold info for a user function. @@ -366,34 +384,3 @@ typedef struct list_stack_S { list_T *list; struct list_stack_S *prev; } list_stack_T; - -/// Structure representing one list item, used for sort array. -typedef struct { - listitem_T *item; ///< Sorted list item. - int idx; ///< Sorted list item index. -} ListSortItem; - -typedef int (*ListSorter)(const void *, const void *); - -#ifdef LOG_LIST_ACTIONS -/// List actions log entry -typedef struct { - uintptr_t l; ///< List log entry belongs to. - uintptr_t li1; ///< First list item log entry belongs to, if applicable. - uintptr_t li2; ///< Second list item log entry belongs to, if applicable. - int len; ///< List length when log entry was created. - const char *action; ///< Logged action. -} ListLogEntry; - -typedef struct list_log ListLog; - -/// List actions log -struct list_log { - ListLog *next; ///< Next chunk or NULL. - size_t capacity; ///< Number of entries in current chunk. - size_t size; ///< Current chunk size. - ListLogEntry entries[]; ///< Actual log entries. -}; -#endif - -#endif // NVIM_EVAL_TYPVAL_DEFS_H diff --git a/src/nvim/eval/typval_encode.c.h b/src/nvim/eval/typval_encode.c.h index 6d29286a58..2e0b68d486 100644 --- a/src/nvim/eval/typval_encode.c.h +++ b/src/nvim/eval/typval_encode.c.h @@ -252,15 +252,13 @@ #include "nvim/func_attr.h" #include "klib/kvec.h" -// -V::1063 - /// Dummy variable used because some macros need lvalue /// /// Must not be written to, if needed one must check that address of the /// macros argument is (not) equal to `&TYPVAL_ENCODE_NODICT_VAR`. const dict_T *const TYPVAL_ENCODE_NODICT_VAR = NULL; -static inline int _TYPVAL_ENCODE_CHECK_SELF_REFERENCE( +static inline int TYPVAL_ENCODE_CHECK_SELF_REFERENCE( TYPVAL_ENCODE_FIRST_ARG_TYPE TYPVAL_ENCODE_FIRST_ARG_NAME, void *const val, int *const val_copyID, const MPConvStack *const mpstack, const int copyID, @@ -283,7 +281,7 @@ static inline int _TYPVAL_ENCODE_CHECK_SELF_REFERENCE( /// @param[in] objname Object name, used for error reporting. /// /// @return NOTDONE in case of success, what to return in case of failure. -static inline int _TYPVAL_ENCODE_CHECK_SELF_REFERENCE( +static inline int TYPVAL_ENCODE_CHECK_SELF_REFERENCE( TYPVAL_ENCODE_FIRST_ARG_TYPE TYPVAL_ENCODE_FIRST_ARG_NAME, void *const val, int *const val_copyID, const MPConvStack *const mpstack, const int copyID, const MPConvStackValType conv_type, const char *const objname) @@ -296,7 +294,7 @@ static inline int _TYPVAL_ENCODE_CHECK_SELF_REFERENCE( return NOTDONE; } -static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( +static int TYPVAL_ENCODE_CONVERT_ONE_VALUE( TYPVAL_ENCODE_FIRST_ARG_TYPE TYPVAL_ENCODE_FIRST_ARG_NAME, MPConvStack *const mpstack, MPConvStackVal *const cur_mpsv, typval_T *const tv, const int copyID, @@ -320,7 +318,7 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( /// @param[in] objname Object name, used for error reporting. /// /// @return OK in case of success, FAIL in case of failure. -static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( +static int TYPVAL_ENCODE_CONVERT_ONE_VALUE( TYPVAL_ENCODE_FIRST_ARG_TYPE TYPVAL_ENCODE_FIRST_ARG_NAME, MPConvStack *const mpstack, MPConvStackVal *const cur_mpsv, typval_T *const tv, const int copyID, const char *const objname) { @@ -347,8 +345,8 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( case VAR_PARTIAL: { partial_T *const pt = tv->vval.v_partial; (void)pt; - TYPVAL_ENCODE_CONV_FUNC_START(tv, (pt == NULL ? NULL : partial_name(pt))); // -V547 - _mp_push(*mpstack, ((MPConvStackVal) { // -V779 + TYPVAL_ENCODE_CONV_FUNC_START(tv, (pt == NULL ? NULL : partial_name(pt))); + kvi_push(*mpstack, ((MPConvStackVal) { .type = kMPConvPartial, .tv = tv, .saved_copyID = copyID - 1, @@ -367,11 +365,11 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( break; } const int saved_copyID = tv_list_copyid(tv->vval.v_list); - _TYPVAL_ENCODE_DO_CHECK_SELF_REFERENCE(tv->vval.v_list, lv_copyID, copyID, - kMPConvList); + TYPVAL_ENCODE_DO_CHECK_SELF_REFERENCE(tv->vval.v_list, lv_copyID, copyID, + kMPConvList); TYPVAL_ENCODE_CONV_LIST_START(tv, tv_list_len(tv->vval.v_list)); assert(saved_copyID != copyID); - _mp_push(*mpstack, ((MPConvStackVal) { + kvi_push(*mpstack, ((MPConvStackVal) { .type = kMPConvList, .tv = tv, .saved_copyID = saved_copyID, @@ -382,7 +380,7 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( }, }, })); - TYPVAL_ENCODE_CONV_REAL_LIST_AFTER_START(tv, _mp_last(*mpstack)); + TYPVAL_ENCODE_CONV_REAL_LIST_AFTER_START(tv, kv_last(*mpstack)); break; } case VAR_BOOL: @@ -396,7 +394,7 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( case VAR_SPECIAL: switch (tv->vval.v_special) { case kSpecialVarNull: - TYPVAL_ENCODE_CONV_NIL(tv); // -V1037 + TYPVAL_ENCODE_CONV_NIL(tv); break; } break; @@ -509,7 +507,7 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( } if (is_string) { TYPVAL_ENCODE_CONV_STR_STRING(tv, buf, len); - } else { // -V523 + } else { TYPVAL_ENCODE_CONV_STRING(tv, buf, len); } xfree(buf); @@ -520,12 +518,12 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( goto _convert_one_value_regular_dict; } const int saved_copyID = tv_list_copyid(val_di->di_tv.vval.v_list); - _TYPVAL_ENCODE_DO_CHECK_SELF_REFERENCE(val_di->di_tv.vval.v_list, - lv_copyID, copyID, - kMPConvList); + TYPVAL_ENCODE_DO_CHECK_SELF_REFERENCE(val_di->di_tv.vval.v_list, + lv_copyID, copyID, + kMPConvList); TYPVAL_ENCODE_CONV_LIST_START(tv, tv_list_len(val_di->di_tv.vval.v_list)); assert(saved_copyID != copyID && saved_copyID != copyID - 1); - _mp_push(*mpstack, ((MPConvStackVal) { + kvi_push(*mpstack, ((MPConvStackVal) { .tv = tv, .type = kMPConvList, .saved_copyID = saved_copyID, @@ -544,8 +542,7 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( } list_T *const val_list = val_di->di_tv.vval.v_list; if (val_list == NULL || tv_list_len(val_list) == 0) { - TYPVAL_ENCODE_CONV_EMPTY_DICT( // -V501 - tv, TYPVAL_ENCODE_NODICT_VAR); + TYPVAL_ENCODE_CONV_EMPTY_DICT(tv, TYPVAL_ENCODE_NODICT_VAR); break; } TV_LIST_ITER_CONST(val_list, li, { @@ -555,12 +552,12 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( } }); const int saved_copyID = tv_list_copyid(val_di->di_tv.vval.v_list); - _TYPVAL_ENCODE_DO_CHECK_SELF_REFERENCE(val_list, lv_copyID, copyID, - kMPConvPairs); + TYPVAL_ENCODE_DO_CHECK_SELF_REFERENCE(val_list, lv_copyID, copyID, + kMPConvPairs); TYPVAL_ENCODE_CONV_DICT_START(tv, TYPVAL_ENCODE_NODICT_VAR, tv_list_len(val_list)); assert(saved_copyID != copyID && saved_copyID != copyID - 1); - _mp_push(*mpstack, ((MPConvStackVal) { + kvi_push(*mpstack, ((MPConvStackVal) { .tv = tv, .type = kMPConvPairs, .saved_copyID = saved_copyID, @@ -603,12 +600,12 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( } _convert_one_value_regular_dict: {} const int saved_copyID = tv->vval.v_dict->dv_copyID; - _TYPVAL_ENCODE_DO_CHECK_SELF_REFERENCE(tv->vval.v_dict, dv_copyID, copyID, - kMPConvDict); + TYPVAL_ENCODE_DO_CHECK_SELF_REFERENCE(tv->vval.v_dict, dv_copyID, copyID, + kMPConvDict); TYPVAL_ENCODE_CONV_DICT_START(tv, tv->vval.v_dict, tv->vval.v_dict->dv_hashtab.ht_used); assert(saved_copyID != copyID); - _mp_push(*mpstack, ((MPConvStackVal) { + kvi_push(*mpstack, ((MPConvStackVal) { .tv = tv, .type = kMPConvDict, .saved_copyID = saved_copyID, @@ -622,20 +619,20 @@ _convert_one_value_regular_dict: {} }, })); TYPVAL_ENCODE_CONV_REAL_DICT_AFTER_START(tv, tv->vval.v_dict, - _mp_last(*mpstack)); + kv_last(*mpstack)); break; } case VAR_UNKNOWN: - internal_error(STR(_TYPVAL_ENCODE_CONVERT_ONE_VALUE) "()"); + internal_error(STR(TYPVAL_ENCODE_CONVERT_ONE_VALUE) "()"); return FAIL; } typval_encode_stop_converting_one_item: return OK; // Prevent “unused label” warnings. - goto typval_encode_stop_converting_one_item; // -V779 + goto typval_encode_stop_converting_one_item; } -TYPVAL_ENCODE_SCOPE int _TYPVAL_ENCODE_ENCODE( +TYPVAL_ENCODE_SCOPE int TYPVAL_ENCODE_ENCODE( TYPVAL_ENCODE_FIRST_ARG_TYPE TYPVAL_ENCODE_FIRST_ARG_NAME, typval_T *const tv, const char *const objname) REAL_FATTR_NONNULL_ARG(2, 3) REAL_FATTR_WARN_UNUSED_RESULT; @@ -649,29 +646,29 @@ TYPVAL_ENCODE_SCOPE int _TYPVAL_ENCODE_ENCODE( /// @param[in] objname Object name, used for error reporting. /// /// @return OK in case of success, FAIL in case of failure. -TYPVAL_ENCODE_SCOPE int _TYPVAL_ENCODE_ENCODE( +TYPVAL_ENCODE_SCOPE int TYPVAL_ENCODE_ENCODE( TYPVAL_ENCODE_FIRST_ARG_TYPE TYPVAL_ENCODE_FIRST_ARG_NAME, typval_T *const top_tv, const char *const objname) { const int copyID = get_copyID(); MPConvStack mpstack; - _mp_init(mpstack); - if (_TYPVAL_ENCODE_CONVERT_ONE_VALUE(TYPVAL_ENCODE_FIRST_ARG_NAME, &mpstack, - NULL, - top_tv, copyID, objname) + kvi_init(mpstack); + if (TYPVAL_ENCODE_CONVERT_ONE_VALUE(TYPVAL_ENCODE_FIRST_ARG_NAME, &mpstack, + NULL, + top_tv, copyID, objname) == FAIL) { goto encode_vim_to__error_ret; } /// Label common for this and convert_one_value functions, used for escaping /// from macros like TYPVAL_ENCODE_CONV_DICT_START. typval_encode_stop_converting_one_item: - while (_mp_size(mpstack)) { - MPConvStackVal *cur_mpsv = &_mp_last(mpstack); + while (kv_size(mpstack)) { + MPConvStackVal *cur_mpsv = &kv_last(mpstack); typval_T *tv = NULL; switch (cur_mpsv->type) { case kMPConvDict: { if (!cur_mpsv->data.d.todo) { - (void)_mp_pop(mpstack); + (void)kv_pop(mpstack); cur_mpsv->data.d.dict->dv_copyID = cur_mpsv->saved_copyID; TYPVAL_ENCODE_CONV_DICT_END(cur_mpsv->tv, *cur_mpsv->data.d.dictp); continue; @@ -695,7 +692,7 @@ typval_encode_stop_converting_one_item: } case kMPConvList: if (cur_mpsv->data.l.li == NULL) { - (void)_mp_pop(mpstack); + (void)kv_pop(mpstack); tv_list_set_copyid(cur_mpsv->data.l.list, cur_mpsv->saved_copyID); TYPVAL_ENCODE_CONV_LIST_END(cur_mpsv->tv); continue; @@ -709,7 +706,7 @@ typval_encode_stop_converting_one_item: break; case kMPConvPairs: { if (cur_mpsv->data.l.li == NULL) { - (void)_mp_pop(mpstack); + (void)kv_pop(mpstack); tv_list_set_copyid(cur_mpsv->data.l.list, cur_mpsv->saved_copyID); TYPVAL_ENCODE_CONV_DICT_END(cur_mpsv->tv, TYPVAL_ENCODE_NODICT_VAR); continue; @@ -722,8 +719,8 @@ typval_encode_stop_converting_one_item: TYPVAL_ENCODE_SPECIAL_DICT_KEY_CHECK(encode_vim_to__error_ret, *TV_LIST_ITEM_TV(tv_list_first(kv_pair))); if ( - _TYPVAL_ENCODE_CONVERT_ONE_VALUE(TYPVAL_ENCODE_FIRST_ARG_NAME, &mpstack, cur_mpsv, - TV_LIST_ITEM_TV(tv_list_first(kv_pair)), copyID, objname) + TYPVAL_ENCODE_CONVERT_ONE_VALUE(TYPVAL_ENCODE_FIRST_ARG_NAME, &mpstack, cur_mpsv, + TV_LIST_ITEM_TV(tv_list_first(kv_pair)), copyID, objname) == FAIL) { goto encode_vim_to__error_ret; } @@ -745,7 +742,7 @@ typval_encode_stop_converting_one_item: cur_mpsv->data.p.stage = kMPConvPartialSelf; if (pt != NULL && pt->pt_argc > 0) { TYPVAL_ENCODE_CONV_LIST_START(NULL, pt->pt_argc); - _mp_push(mpstack, ((MPConvStackVal) { + kvi_push(mpstack, ((MPConvStackVal) { .type = kMPConvPartialList, .tv = NULL, .saved_copyID = copyID - 1, @@ -769,10 +766,10 @@ typval_encode_stop_converting_one_item: continue; } const int saved_copyID = dict->dv_copyID; - const int te_csr_ret = _TYPVAL_ENCODE_CHECK_SELF_REFERENCE(TYPVAL_ENCODE_FIRST_ARG_NAME, - dict, &dict->dv_copyID, - &mpstack, copyID, kMPConvDict, - objname); + const int te_csr_ret = TYPVAL_ENCODE_CHECK_SELF_REFERENCE(TYPVAL_ENCODE_FIRST_ARG_NAME, + dict, &dict->dv_copyID, + &mpstack, copyID, kMPConvDict, + objname); if (te_csr_ret != NOTDONE) { if (te_csr_ret == FAIL) { goto encode_vim_to__error_ret; @@ -783,7 +780,7 @@ typval_encode_stop_converting_one_item: TYPVAL_ENCODE_CONV_DICT_START(NULL, pt->pt_dict, dict->dv_hashtab.ht_used); assert(saved_copyID != copyID && saved_copyID != copyID - 1); - _mp_push(mpstack, ((MPConvStackVal) { + kvi_push(mpstack, ((MPConvStackVal) { .type = kMPConvDict, .tv = NULL, .saved_copyID = saved_copyID, @@ -797,7 +794,7 @@ typval_encode_stop_converting_one_item: }, })); TYPVAL_ENCODE_CONV_REAL_DICT_AFTER_START(NULL, pt->pt_dict, - _mp_last(mpstack)); + kv_last(mpstack)); } else { TYPVAL_ENCODE_CONV_FUNC_BEFORE_SELF(tv, -1); } @@ -805,14 +802,14 @@ typval_encode_stop_converting_one_item: } case kMPConvPartialEnd: TYPVAL_ENCODE_CONV_FUNC_END(tv); - (void)_mp_pop(mpstack); + (void)kv_pop(mpstack); break; } continue; } case kMPConvPartialList: if (!cur_mpsv->data.a.todo) { - (void)_mp_pop(mpstack); + (void)kv_pop(mpstack); TYPVAL_ENCODE_CONV_LIST_END(NULL); continue; } else if (cur_mpsv->data.a.argv != cur_mpsv->data.a.arg) { @@ -823,17 +820,17 @@ typval_encode_stop_converting_one_item: break; } assert(tv != NULL); - if (_TYPVAL_ENCODE_CONVERT_ONE_VALUE(TYPVAL_ENCODE_FIRST_ARG_NAME, &mpstack, - cur_mpsv, tv, copyID, objname) + if (TYPVAL_ENCODE_CONVERT_ONE_VALUE(TYPVAL_ENCODE_FIRST_ARG_NAME, &mpstack, + cur_mpsv, tv, copyID, objname) == FAIL) { goto encode_vim_to__error_ret; } } - _mp_destroy(mpstack); + kvi_destroy(mpstack); return OK; encode_vim_to__error_ret: - _mp_destroy(mpstack); + kvi_destroy(mpstack); return FAIL; // Prevent “unused label” warnings. - goto typval_encode_stop_converting_one_item; // -V779 + goto typval_encode_stop_converting_one_item; } diff --git a/src/nvim/eval/typval_encode.h b/src/nvim/eval/typval_encode.h index 2f19144da3..a6e0bd4b2b 100644 --- a/src/nvim/eval/typval_encode.h +++ b/src/nvim/eval/typval_encode.h @@ -2,8 +2,7 @@ /// /// Contains common definitions for eval/typval_encode.c.h. Most of time should /// not be included directly. -#ifndef NVIM_EVAL_TYPVAL_ENCODE_H -#define NVIM_EVAL_TYPVAL_ENCODE_H +#pragma once #include <assert.h> #include <inttypes.h> @@ -11,7 +10,7 @@ #include <string.h> #include "klib/kvec.h" -#include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" #include "nvim/func_attr.h" /// Type of the stack entry @@ -30,7 +29,7 @@ typedef enum { kMPConvPartialEnd, ///< Already converted everything. } MPConvPartialStage; -/// Structure representing current VimL to messagepack conversion state +/// Structure representing current Vimscript to messagepack conversion state typedef struct { MPConvStackValType type; ///< Type of the stack entry. typval_T *tv; ///< Currently converted typval_T. @@ -60,17 +59,9 @@ typedef struct { } data; ///< Data to convert. } MPConvStackVal; -/// Stack used to convert VimL values to messagepack. +/// Stack used to convert Vimscript values to messagepack. typedef kvec_withinit_t(MPConvStackVal, 8) MPConvStack; -// Defines for MPConvStack -#define _mp_size kv_size -#define _mp_init kvi_init -#define _mp_destroy kvi_destroy -#define _mp_push kvi_push -#define _mp_pop kv_pop -#define _mp_last kv_last - static inline size_t tv_strlen(const typval_T *tv) REAL_FATTR_ALWAYS_INLINE REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT REAL_FATTR_NONNULL_ALL; @@ -96,21 +87,21 @@ static inline size_t tv_strlen(const typval_T *const tv) /// copyID (variable) it is set to copyID. /// @param[in] copyID CopyID used by the caller. /// @param conv_type Type of the conversion, @see MPConvStackValType. -#define _TYPVAL_ENCODE_DO_CHECK_SELF_REFERENCE(val, copyID_attr, copyID, \ - conv_type) \ +#define TYPVAL_ENCODE_DO_CHECK_SELF_REFERENCE(val, copyID_attr, copyID, \ + conv_type) \ do { \ - const int te_csr_ret = _TYPVAL_ENCODE_CHECK_SELF_REFERENCE(TYPVAL_ENCODE_FIRST_ARG_NAME, \ - (val), &(val)->copyID_attr, mpstack, \ - copyID, conv_type, objname); \ + const int te_csr_ret = TYPVAL_ENCODE_CHECK_SELF_REFERENCE(TYPVAL_ENCODE_FIRST_ARG_NAME, \ + (val), &(val)->copyID_attr, mpstack, \ + copyID, conv_type, objname); \ if (te_csr_ret != NOTDONE) { \ return te_csr_ret; \ } \ } while (0) -#define _TYPVAL_ENCODE_FUNC_NAME_INNER_2(pref, name, suf) \ +#define TYPVAL_ENCODE_FUNC_NAME_INNER_2(pref, name, suf) \ pref##name##suf -#define _TYPVAL_ENCODE_FUNC_NAME_INNER(pref, name, suf) \ - _TYPVAL_ENCODE_FUNC_NAME_INNER_2(pref, name, suf) +#define TYPVAL_ENCODE_FUNC_NAME_INNER(pref, name, suf) \ + TYPVAL_ENCODE_FUNC_NAME_INNER_2(pref, name, suf) /// Construct function name, possibly using macros /// @@ -122,23 +113,21 @@ static inline size_t tv_strlen(const typval_T *const tv) /// @param[in] suf Suffix. /// /// @return Concat: pref + #TYPVAL_ENCODE_NAME + suf. -#define _TYPVAL_ENCODE_FUNC_NAME(pref, suf) \ - _TYPVAL_ENCODE_FUNC_NAME_INNER(pref, TYPVAL_ENCODE_NAME, suf) +#define TYPVAL_ENCODE_FUNC_NAME(pref, suf) \ + TYPVAL_ENCODE_FUNC_NAME_INNER(pref, TYPVAL_ENCODE_NAME, suf) /// Self reference checker function name -#define _TYPVAL_ENCODE_CHECK_SELF_REFERENCE \ - _TYPVAL_ENCODE_FUNC_NAME(_typval_encode_, _check_self_reference) +#define TYPVAL_ENCODE_CHECK_SELF_REFERENCE \ + TYPVAL_ENCODE_FUNC_NAME(_typval_encode_, _check_self_reference) /// Entry point function name -#define _TYPVAL_ENCODE_ENCODE \ - _TYPVAL_ENCODE_FUNC_NAME(encode_vim_to_, ) +#define TYPVAL_ENCODE_ENCODE \ + TYPVAL_ENCODE_FUNC_NAME(encode_vim_to_, ) /// Name of the …convert_one_value function -#define _TYPVAL_ENCODE_CONVERT_ONE_VALUE \ - _TYPVAL_ENCODE_FUNC_NAME(_typval_encode_, _convert_one_value) +#define TYPVAL_ENCODE_CONVERT_ONE_VALUE \ + TYPVAL_ENCODE_FUNC_NAME(_typval_encode_, _convert_one_value) /// Name of the dummy const dict_T *const variable #define TYPVAL_ENCODE_NODICT_VAR \ - _TYPVAL_ENCODE_FUNC_NAME(_typval_encode_, _nodict_var) - -#endif // NVIM_EVAL_TYPVAL_ENCODE_H + TYPVAL_ENCODE_FUNC_NAME(_typval_encode_, _nodict_var) diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c index 22c5b1954d..23b3c4e1b2 100644 --- a/src/nvim/eval/userfunc.c +++ b/src/nvim/eval/userfunc.c @@ -1,20 +1,17 @@ -// This is an open source non-commercial project. Dear PVS-Studio, please check -// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com - // User defined function support #include <assert.h> #include <ctype.h> #include <inttypes.h> +#include <lauxlib.h> #include <stdio.h> #include <stdlib.h> #include <string.h> -#include "lauxlib.h" -#include "nvim/ascii.h" +#include "nvim/ascii_defs.h" #include "nvim/autocmd.h" -#include "nvim/buffer_defs.h" #include "nvim/charset.h" +#include "nvim/cmdexpand_defs.h" #include "nvim/debugger.h" #include "nvim/eval.h" #include "nvim/eval/encode.h" @@ -26,18 +23,20 @@ #include "nvim/ex_docmd.h" #include "nvim/ex_eval.h" #include "nvim/ex_getln.h" +#include "nvim/func_attr.h" +#include "nvim/garray.h" #include "nvim/getchar.h" #include "nvim/gettext.h" #include "nvim/globals.h" +#include "nvim/hashtab.h" #include "nvim/insexpand.h" #include "nvim/keycodes.h" #include "nvim/lua/executor.h" -#include "nvim/macros.h" +#include "nvim/macros_defs.h" #include "nvim/mbyte.h" -#include "nvim/memline_defs.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/option_defs.h" +#include "nvim/option_vars.h" #include "nvim/os/input.h" #include "nvim/path.h" #include "nvim/profile.h" @@ -45,14 +44,21 @@ #include "nvim/runtime.h" #include "nvim/search.h" #include "nvim/strings.h" -#include "nvim/types.h" +#include "nvim/types_defs.h" #include "nvim/ui.h" -#include "nvim/vim.h" +#include "nvim/vim_defs.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "eval/userfunc.c.generated.h" #endif +/// structure used as item in "fc_defer" +typedef struct { + char *dr_name; ///< function name, allocated + typval_T dr_argvars[MAX_FUNC_ARGS + 1]; + int dr_argcount; +} defer_T; + static hashtab_T func_hashtab; // Used by get_func_tv() @@ -65,12 +71,21 @@ static funccall_T *current_funccal = NULL; // item in it is still being used. static funccall_T *previous_funccal = NULL; -static char *e_funcexts = N_("E122: Function %s already exists, add ! to replace it"); -static char *e_funcdict = N_("E717: Dictionary entry already exists"); -static char *e_funcref = N_("E718: Funcref required"); -static char *e_nofunc = N_("E130: Unknown function: %s"); -static char e_no_white_space_allowed_before_str_str[] +static const char *e_unknown_function_str = N_("E117: Unknown function: %s"); +static const char *e_funcexts = N_("E122: Function %s already exists, add ! to replace it"); +static const char *e_funcdict = N_("E717: Dictionary entry already exists"); +static const char *e_funcref = N_("E718: Funcref required"); +static const char *e_nofunc = N_("E130: Unknown function: %s"); +static const char e_function_list_was_modified[] + = N_("E454: Function list was modified"); +static const char e_function_nesting_too_deep[] + = N_("E1058: Function nesting too deep"); +static const char e_no_white_space_allowed_before_str_str[] = N_("E1068: No white space allowed before '%s': %s"); +static const char e_missing_heredoc_end_marker_str[] + = N_("E1145: Missing heredoc end marker: %s"); +static const char e_cannot_use_partial_with_dictionary_for_defer[] + = N_("E1300: Cannot use a partial with dictionary for :defer"); void func_init(void) { @@ -90,7 +105,7 @@ static int get_function_args(char **argp, char endchar, garray_T *newargs, int * bool mustend = false; char *arg = *argp; char *p = arg; - char_u c; + uint8_t c; int i; if (newargs != NULL) { @@ -128,7 +143,7 @@ static int get_function_args(char **argp, char endchar, garray_T *newargs, int * } if (newargs != NULL) { ga_grow(newargs, 1); - c = (char_u)(*p); + c = (uint8_t)(*p); *p = NUL; arg = xstrdup(arg); @@ -152,14 +167,14 @@ static int get_function_args(char **argp, char endchar, garray_T *newargs, int * p = skipwhite(p) + 1; p = skipwhite(p); char *expr = p; - if (eval1(&p, &rettv, false) != FAIL) { + if (eval1(&p, &rettv, NULL) != FAIL) { ga_grow(default_args, 1); // trim trailing whitespace while (p > expr && ascii_iswhite(p[-1])) { p--; } - c = (char_u)(*p); + c = (uint8_t)(*p); *p = NUL; expr = xstrdup(expr); ((char **)(default_args->ga_data))[default_args->ga_len] = expr; @@ -223,9 +238,9 @@ static void register_closure(ufunc_T *fp) funccal_unref(fp->uf_scoped, fp, false); fp->uf_scoped = current_funccal; current_funccal->fc_refcount++; - ga_grow(¤t_funccal->fc_funcs, 1); - ((ufunc_T **)current_funccal->fc_funcs.ga_data) - [current_funccal->fc_funcs.ga_len++] = fp; + ga_grow(¤t_funccal->fc_ufuncs, 1); + ((ufunc_T **)current_funccal->fc_ufuncs.ga_data) + [current_funccal->fc_ufuncs.ga_len++] = fp; } /// @return a name for a lambda. Returned in static memory. @@ -252,22 +267,22 @@ static void set_ufunc_name(ufunc_T *fp, char *name) /// Parse a lambda expression and get a Funcref from "*arg". /// /// @return OK or FAIL. Returns NOTDONE for dict or {expr}. -int get_lambda_tv(char **arg, typval_T *rettv, bool evaluate) +int get_lambda_tv(char **arg, typval_T *rettv, evalarg_T *evalarg) { + const bool evaluate = evalarg != NULL && (evalarg->eval_flags & EVAL_EVALUATE); garray_T newargs = GA_EMPTY_INIT_VALUE; garray_T *pnewargs; ufunc_T *fp = NULL; partial_T *pt = NULL; int varargs; - int ret; - char *start = skipwhite(*arg + 1); - char *s, *e; bool *old_eval_lavars = eval_lavars_used; bool eval_lavars = false; + char *tofree = NULL; // First, check if this is a lambda expression. "->" must exists. - ret = get_function_args(&start, '-', NULL, NULL, NULL, true); - if (ret == FAIL || *start != '>') { + char *s = skipwhite(*arg + 1); + int ret = get_function_args(&s, '-', NULL, NULL, NULL, true); + if (ret == FAIL || *s != '>') { return NOTDONE; } @@ -290,12 +305,18 @@ int get_lambda_tv(char **arg, typval_T *rettv, bool evaluate) // Get the start and the end of the expression. *arg = skipwhite((*arg) + 1); - s = *arg; - ret = skip_expr(arg); + char *start = *arg; + ret = skip_expr(arg, evalarg); + char *end = *arg; if (ret == FAIL) { goto errret; } - e = *arg; + if (evalarg != NULL) { + // avoid that the expression gets freed when another line break follows + tofree = evalarg->eval_tofree; + evalarg->eval_tofree = NULL; + } + *arg = skipwhite(*arg); if (**arg != '}') { semsg(_("E451: Expected }: %s"), *arg); @@ -317,11 +338,11 @@ int get_lambda_tv(char **arg, typval_T *rettv, bool evaluate) ga_grow(&newlines, 1); // Add "return " before the expression. - size_t len = (size_t)(7 + e - s + 1); + size_t len = (size_t)(7 + end - start + 1); p = xmalloc(len); ((char **)(newlines.ga_data))[newlines.ga_len++] = p; STRCPY(p, "return "); - xstrlcpy(p + 7, s, (size_t)(e - s) + 1); + xstrlcpy(p + 7, start, (size_t)(end - start) + 1); if (strstr(p + 7, "a:") == NULL) { // No a: variables are used for sure. flags |= FC_NOARGS; @@ -359,12 +380,22 @@ int get_lambda_tv(char **arg, typval_T *rettv, bool evaluate) } eval_lavars_used = old_eval_lavars; + if (evalarg != NULL && evalarg->eval_tofree == NULL) { + evalarg->eval_tofree = tofree; + } else { + xfree(tofree); + } return OK; errret: ga_clear_strings(&newargs); xfree(fp); xfree(pt); + if (evalarg != NULL && evalarg->eval_tofree == NULL) { + evalarg->eval_tofree = tofree; + } else { + xfree(tofree); + } eval_lavars_used = old_eval_lavars; return FAIL; } @@ -382,9 +413,11 @@ errret: /// is not needed. /// @param[in] no_autoload If true, do not source autoload scripts if function /// was not found. +/// @param[out] found_var If not NULL and a variable was found set it to true. /// /// @return name of the function. -char *deref_func_name(const char *name, int *lenp, partial_T **const partialp, bool no_autoload) +char *deref_func_name(const char *name, int *lenp, partial_T **const partialp, bool no_autoload, + bool *found_var) FUNC_ATTR_NONNULL_ARG(1, 2) { if (partialp != NULL) { @@ -392,18 +425,25 @@ char *deref_func_name(const char *name, int *lenp, partial_T **const partialp, b } dictitem_T *const v = find_var(name, (size_t)(*lenp), NULL, no_autoload); - if (v != NULL && v->di_tv.v_type == VAR_FUNC) { - if (v->di_tv.vval.v_string == NULL) { // just in case + if (v == NULL) { + return (char *)name; + } + typval_T *const tv = &v->di_tv; + if (found_var != NULL) { + *found_var = true; + } + + if (tv->v_type == VAR_FUNC) { + if (tv->vval.v_string == NULL) { // just in case *lenp = 0; return ""; } - *lenp = (int)strlen(v->di_tv.vval.v_string); - return v->di_tv.vval.v_string; + *lenp = (int)strlen(tv->vval.v_string); + return tv->vval.v_string; } - if (v != NULL && v->di_tv.v_type == VAR_PARTIAL) { - partial_T *const pt = v->di_tv.vval.v_partial; - + if (tv->v_type == VAR_PARTIAL) { + partial_T *const pt = tv->vval.v_partial; if (pt == NULL) { // just in case *lenp = 0; return ""; @@ -421,63 +461,82 @@ char *deref_func_name(const char *name, int *lenp, partial_T **const partialp, b /// Give an error message with a function name. Handle <SNR> things. /// -/// @param ermsg must be passed without translation (use N_() instead of _()). +/// @param errmsg must be passed without translation (use N_() instead of _()). /// @param name function name -void emsg_funcname(char *ermsg, const char *name) +void emsg_funcname(const char *errmsg, const char *name) { - char *p; + char *p = (char *)name; - if ((uint8_t)(*name) == K_SPECIAL) { + if ((uint8_t)name[0] == K_SPECIAL && name[1] != NUL && name[2] != NUL) { p = concat_str("<SNR>", name + 3); - } else { - p = (char *)name; } - semsg(_(ermsg), p); + semsg(_(errmsg), p); if (p != name) { xfree(p); } } -/// Allocate a variable for the result of a function. -/// -/// @param name name of the function -/// @param len length of "name" or -1 to use strlen() -/// @param arg argument, pointing to the '(' -/// @param funcexe various values -/// -/// @return OK or FAIL. -int get_func_tv(const char *name, int len, typval_T *rettv, char **arg, funcexe_T *funcexe) +/// Get function arguments at "*arg" and advance it. +/// Return them in "*argvars[MAX_FUNC_ARGS + 1]" and the count in "argcount". +/// On failure FAIL is returned but the "argvars[argcount]" are still set. +static int get_func_arguments(char **arg, evalarg_T *const evalarg, int partial_argc, + typval_T *argvars, int *argcount) { - char *argp; + char *argp = *arg; int ret = OK; - typval_T argvars[MAX_FUNC_ARGS + 1]; // vars for arguments - int argcount = 0; // number of arguments found // Get the arguments. - argp = *arg; - while (argcount < MAX_FUNC_ARGS - - (funcexe->fe_partial == NULL ? 0 : funcexe->fe_partial->pt_argc)) { + while (*argcount < MAX_FUNC_ARGS - partial_argc) { argp = skipwhite(argp + 1); // skip the '(' or ',' + if (*argp == ')' || *argp == ',' || *argp == NUL) { break; } - if (eval1(&argp, &argvars[argcount], funcexe->fe_evaluate) == FAIL) { + if (eval1(&argp, &argvars[*argcount], evalarg) == FAIL) { ret = FAIL; break; } - argcount++; + (*argcount)++; if (*argp != ',') { break; } } + + argp = skipwhite(argp); if (*argp == ')') { argp++; } else { ret = FAIL; } + *arg = argp; + return ret; +} + +/// Call a function and put the result in "rettv". +/// +/// @param name name of the function +/// @param len length of "name" or -1 to use strlen() +/// @param arg argument, pointing to the '(' +/// @param funcexe various values +/// +/// @return OK or FAIL. +int get_func_tv(const char *name, int len, typval_T *rettv, char **arg, evalarg_T *const evalarg, + funcexe_T *funcexe) +{ + typval_T argvars[MAX_FUNC_ARGS + 1]; // vars for arguments + int argcount = 0; // number of arguments found + const bool evaluate = evalarg == NULL ? false : (evalarg->eval_flags & EVAL_EVALUATE); + + char *argp = *arg; + int ret = get_func_arguments(&argp, evalarg, + (funcexe->fe_partial == NULL + ? 0 + : funcexe->fe_partial->pt_argc), + argvars, &argcount); + assert(ret == OK || ret == FAIL); // suppress clang false positive if (ret == OK) { int i = 0; @@ -495,7 +554,7 @@ int get_func_tv(const char *name, int len, typval_T *rettv, char **arg, funcexe_ ret = call_func(name, len, rettv, argcount, argvars, funcexe); funcargs.ga_len -= i; - } else if (!aborting()) { + } else if (!aborting() && evaluate) { if (argcount == MAX_FUNC_ARGS) { emsg_funcname(N_("E740: Too many arguments for function %s"), name); } else { @@ -594,24 +653,28 @@ ufunc_T *find_func(const char *name) /// Copy the function name of "fp" to buffer "buf". /// "buf" must be able to hold the function name plus three bytes. /// Takes care of script-local function names. -static void cat_func_name(char *buf, ufunc_T *fp) +static void cat_func_name(char *buf, size_t buflen, ufunc_T *fp) { - if ((uint8_t)fp->uf_name[0] == K_SPECIAL) { - STRCPY(buf, "<SNR>"); - STRCAT(buf, fp->uf_name + 3); + int len = -1; + size_t uflen = strlen(fp->uf_name); + assert(uflen > 0); + + if ((uint8_t)fp->uf_name[0] == K_SPECIAL && uflen > 3) { + len = snprintf(buf, buflen, "<SNR>%s", fp->uf_name + 3); } else { - STRCPY(buf, fp->uf_name); + len = snprintf(buf, buflen, "%s", fp->uf_name); } + + (void)len; // Avoid unused warning on release builds + assert(len > 0); } /// Add a number variable "name" to dict "dp" with value "nr". static void add_nr_var(dict_T *dp, dictitem_T *v, char *name, varnumber_T nr) { -#ifndef __clang_analyzer__ STRCPY(v->di_key, name); -#endif v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX; - hash_add(&dp->dv_hashtab, (char *)v->di_key); + hash_add(&dp->dv_hashtab, v->di_key); v->di_tv.v_type = VAR_NUMBER; v->di_tv.v_lock = VAR_FIXED; v->di_tv.vval.v_number = nr; @@ -620,8 +683,8 @@ static void add_nr_var(dict_T *dp, dictitem_T *v, char *name, varnumber_T nr) /// Free "fc" static void free_funccal(funccall_T *fc) { - for (int i = 0; i < fc->fc_funcs.ga_len; i++) { - ufunc_T *fp = ((ufunc_T **)(fc->fc_funcs.ga_data))[i]; + for (int i = 0; i < fc->fc_ufuncs.ga_len; i++) { + ufunc_T *fp = ((ufunc_T **)(fc->fc_ufuncs.ga_data))[i]; // When garbage collecting a funccall_T may be freed before the // function that references it, clear its uf_scoped field. @@ -631,9 +694,9 @@ static void free_funccal(funccall_T *fc) fp->uf_scoped = NULL; } } - ga_clear(&fc->fc_funcs); + ga_clear(&fc->fc_ufuncs); - func_ptr_unref(fc->func); + func_ptr_unref(fc->fc_func); xfree(fc); } @@ -643,13 +706,13 @@ static void free_funccal(funccall_T *fc) static void free_funccal_contents(funccall_T *fc) { // Free all l: variables. - vars_clear(&fc->l_vars.dv_hashtab); + vars_clear(&fc->fc_l_vars.dv_hashtab); // Free all a: variables. - vars_clear(&fc->l_avars.dv_hashtab); + vars_clear(&fc->fc_l_avars.dv_hashtab); // Free the a:000 variables. - TV_LIST_ITER(&fc->l_varlist, li, { + TV_LIST_ITER(&fc->fc_l_varlist, li, { tv_clear(TV_LIST_ITEM_TV(li)); }); @@ -663,11 +726,11 @@ static void cleanup_function_call(funccall_T *fc) bool may_free_fc = fc->fc_refcount <= 0; bool free_fc = true; - current_funccal = fc->caller; + current_funccal = fc->fc_caller; // Free all l: variables if not referred. - if (may_free_fc && fc->l_vars.dv_refcount == DO_NOT_FREE_CNT) { - vars_clear(&fc->l_vars.dv_hashtab); + if (may_free_fc && fc->fc_l_vars.dv_refcount == DO_NOT_FREE_CNT) { + vars_clear(&fc->fc_l_vars.dv_hashtab); } else { free_fc = false; } @@ -675,25 +738,25 @@ static void cleanup_function_call(funccall_T *fc) // If the a:000 list and the l: and a: dicts are not referenced and // there is no closure using it, we can free the funccall_T and what's // in it. - if (may_free_fc && fc->l_avars.dv_refcount == DO_NOT_FREE_CNT) { - vars_clear_ext(&fc->l_avars.dv_hashtab, false); + if (may_free_fc && fc->fc_l_avars.dv_refcount == DO_NOT_FREE_CNT) { + vars_clear_ext(&fc->fc_l_avars.dv_hashtab, false); } else { free_fc = false; // Make a copy of the a: variables, since we didn't do that above. - TV_DICT_ITER(&fc->l_avars, di, { + TV_DICT_ITER(&fc->fc_l_avars, di, { tv_copy(&di->di_tv, &di->di_tv); }); } - if (may_free_fc && fc->l_varlist.lv_refcount // NOLINT(runtime/deprecated) + if (may_free_fc && fc->fc_l_varlist.lv_refcount // NOLINT(runtime/deprecated) == DO_NOT_FREE_CNT) { - fc->l_varlist.lv_first = NULL; // NOLINT(runtime/deprecated) + fc->fc_l_varlist.lv_first = NULL; // NOLINT(runtime/deprecated) } else { free_fc = false; // Make a copy of the a:000 items, since we didn't do that above. - TV_LIST_ITER(&fc->l_varlist, li, { + TV_LIST_ITER(&fc->fc_l_varlist, li, { tv_copy(TV_LIST_ITEM_TV(li), TV_LIST_ITEM_TV(li)); }); } @@ -706,7 +769,7 @@ static void cleanup_function_call(funccall_T *fc) // "fc" is still in use. This can happen when returning "a:000", // assigning "l:" to a global variable or defining a closure. // Link "fc" in the list for garbage collection later. - fc->caller = previous_funccal; + fc->fc_caller = previous_funccal; previous_funccal = fc; if (want_garbage_collect) { @@ -729,26 +792,23 @@ static void cleanup_function_call(funccall_T *fc) /// @param[in] force When true, we are exiting. static void funccal_unref(funccall_T *fc, ufunc_T *fp, bool force) { - funccall_T **pfc; - int i; - if (fc == NULL) { return; } fc->fc_refcount--; if (force ? fc->fc_refcount <= 0 : !fc_referenced(fc)) { - for (pfc = &previous_funccal; *pfc != NULL; pfc = &(*pfc)->caller) { + for (funccall_T **pfc = &previous_funccal; *pfc != NULL; pfc = &(*pfc)->fc_caller) { if (fc == *pfc) { - *pfc = fc->caller; + *pfc = fc->fc_caller; free_funccal_contents(fc); return; } } } - for (i = 0; i < fc->fc_funcs.ga_len; i++) { - if (((ufunc_T **)(fc->fc_funcs.ga_data))[i] == fp) { - ((ufunc_T **)(fc->fc_funcs.ga_data))[i] = NULL; + for (int i = 0; i < fc->fc_ufuncs.ga_len; i++) { + if (((ufunc_T **)(fc->fc_ufuncs.ga_data))[i] == fp) { + ((ufunc_T **)(fc->fc_ufuncs.ga_data))[i] = NULL; } } } @@ -759,14 +819,13 @@ static void funccal_unref(funccall_T *fc, ufunc_T *fp, bool force) /// @return true if the entry was deleted, false if it wasn't found. static bool func_remove(ufunc_T *fp) { - hashitem_T *hi = hash_find(&func_hashtab, (char *)UF2HIKEY(fp)); - - if (!HASHITEM_EMPTY(hi)) { - hash_remove(&func_hashtab, hi); - return true; + hashitem_T *hi = hash_find(&func_hashtab, UF2HIKEY(fp)); + if (HASHITEM_EMPTY(hi)) { + return false; } - return false; + hash_remove(&func_hashtab, hi); + return true; } static void func_clear_items(ufunc_T *fp) @@ -825,6 +884,27 @@ static void func_clear_free(ufunc_T *fp, bool force) func_free(fp); } +/// Allocate a funccall_T, link it in current_funccal and fill in "fp" and "rettv". +/// Must be followed by one call to remove_funccal() or cleanup_function_call(). +funccall_T *create_funccal(ufunc_T *fp, typval_T *rettv) +{ + funccall_T *fc = xcalloc(1, sizeof(funccall_T)); + fc->fc_caller = current_funccal; + current_funccal = fc; + fc->fc_func = fp; + func_ptr_ref(fp); + fc->fc_rettv = rettv; + return fc; +} + +/// Restore current_funccal. +void remove_funccal(void) +{ + funccall_T *fc = current_funccal; + current_funccal = fc->fc_caller; + free_funccal(fc); +} + /// Call a user function /// /// @param fp Function to call. @@ -839,11 +919,10 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett FUNC_ATTR_NONNULL_ARG(1, 3, 4) { bool using_sandbox = false; - funccall_T *fc; int save_did_emsg; static int depth = 0; dictitem_T *v; - int fixvar_idx = 0; // index in fixvar[] + int fixvar_idx = 0; // index in fc_fixvar[] int ai; bool islambda = false; char numbuf[NUMBUFLEN]; @@ -874,40 +953,32 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett // check for CTRL-C hit line_breakcheck(); // prepare the funccall_T structure - fc = xcalloc(1, sizeof(funccall_T)); - fc->caller = current_funccal; - current_funccal = fc; - fc->func = fp; - fc->rettv = rettv; - fc->level = ex_nesting_level; + funccall_T *fc = create_funccal(fp, rettv); + fc->fc_level = ex_nesting_level; // Check if this function has a breakpoint. - fc->breakpoint = dbg_find_breakpoint(false, (char *)fp->uf_name, (linenr_T)0); - fc->dbg_tick = debug_tick; - + fc->fc_breakpoint = dbg_find_breakpoint(false, fp->uf_name, 0); + fc->fc_dbg_tick = debug_tick; // Set up fields for closure. - ga_init(&fc->fc_funcs, sizeof(ufunc_T *), 1); - func_ptr_ref(fp); + ga_init(&fc->fc_ufuncs, sizeof(ufunc_T *), 1); if (strncmp(fp->uf_name, "<lambda>", 8) == 0) { islambda = true; } - // Note about using fc->fixvar[]: This is an array of FIXVAR_CNT variables + // Note about using fc->fc_fixvar[]: This is an array of FIXVAR_CNT variables // with names up to VAR_SHORT_LEN long. This avoids having to alloc/free // each argument variable and saves a lot of time. // // Init l: variables. - init_var_dict(&fc->l_vars, &fc->l_vars_var, VAR_DEF_SCOPE); + init_var_dict(&fc->fc_l_vars, &fc->fc_l_vars_var, VAR_DEF_SCOPE); if (selfdict != NULL) { // Set l:self to "selfdict". Use "name" to avoid a warning from // some compiler that checks the destination size. - v = (dictitem_T *)&fc->fixvar[fixvar_idx++]; -#ifndef __clang_analyzer__ + v = (dictitem_T *)&fc->fc_fixvar[fixvar_idx++]; name = (char *)v->di_key; STRCPY(name, "self"); -#endif v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX; - hash_add(&fc->l_vars.dv_hashtab, (char *)v->di_key); + hash_add(&fc->fc_l_vars.dv_hashtab, v->di_key); v->di_tv.v_type = VAR_DICT; v->di_tv.v_lock = VAR_UNLOCKED; v->di_tv.vval.v_dict = selfdict; @@ -917,38 +988,36 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett // Init a: variables, unless none found (in lambda). // Set a:0 to "argcount" less number of named arguments, if >= 0. // Set a:000 to a list with room for the "..." arguments. - init_var_dict(&fc->l_avars, &fc->l_avars_var, VAR_SCOPE); + init_var_dict(&fc->fc_l_avars, &fc->fc_l_avars_var, VAR_SCOPE); if ((fp->uf_flags & FC_NOARGS) == 0) { - add_nr_var(&fc->l_avars, (dictitem_T *)&fc->fixvar[fixvar_idx++], "0", + add_nr_var(&fc->fc_l_avars, (dictitem_T *)&fc->fc_fixvar[fixvar_idx++], "0", (varnumber_T)(argcount >= fp->uf_args.ga_len ? argcount - fp->uf_args.ga_len : 0)); } - fc->l_avars.dv_lock = VAR_FIXED; + fc->fc_l_avars.dv_lock = VAR_FIXED; if ((fp->uf_flags & FC_NOARGS) == 0) { // Use "name" to avoid a warning from some compiler that checks the // destination size. - v = (dictitem_T *)&fc->fixvar[fixvar_idx++]; -#ifndef __clang_analyzer__ + v = (dictitem_T *)&fc->fc_fixvar[fixvar_idx++]; name = (char *)v->di_key; STRCPY(name, "000"); -#endif v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX; - hash_add(&fc->l_avars.dv_hashtab, (char *)v->di_key); + hash_add(&fc->fc_l_avars.dv_hashtab, v->di_key); v->di_tv.v_type = VAR_LIST; v->di_tv.v_lock = VAR_FIXED; - v->di_tv.vval.v_list = &fc->l_varlist; + v->di_tv.vval.v_list = &fc->fc_l_varlist; } - tv_list_init_static(&fc->l_varlist); - tv_list_set_lock(&fc->l_varlist, VAR_FIXED); + tv_list_init_static(&fc->fc_l_varlist); + tv_list_set_lock(&fc->fc_l_varlist, VAR_FIXED); // Set a:firstline to "firstline" and a:lastline to "lastline". // Set a:name to named arguments. // Set a:N to the "..." arguments. // Skipped when no a: variables used (in lambda). if ((fp->uf_flags & FC_NOARGS) == 0) { - add_nr_var(&fc->l_avars, (dictitem_T *)&fc->fixvar[fixvar_idx++], + add_nr_var(&fc->fc_l_avars, (dictitem_T *)&fc->fc_fixvar[fixvar_idx++], "firstline", (varnumber_T)firstline); - add_nr_var(&fc->l_avars, (dictitem_T *)&fc->fixvar[fixvar_idx++], + add_nr_var(&fc->fc_l_avars, (dictitem_T *)&fc->fc_fixvar[fixvar_idx++], "lastline", (varnumber_T)lastline); } bool default_arg_err = false; @@ -974,7 +1043,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett default_expr = ((char **)(fp->uf_def_args.ga_data)) [ai + fp->uf_def_args.ga_len]; - if (eval1(&default_expr, &def_rettv, true) == FAIL) { + if (eval1(&default_expr, &def_rettv, &EVALARG_EVALUATE) == FAIL) { default_arg_err = true; break; } @@ -989,7 +1058,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett name = numbuf; } if (fixvar_idx < FIXVAR_CNT && strlen(name) <= VAR_SHORT_LEN) { - v = (dictitem_T *)&fc->fixvar[fixvar_idx++]; + v = (dictitem_T *)&fc->fc_fixvar[fixvar_idx++]; v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX; } else { v = xmalloc(sizeof(dictitem_T) + strlen(name)); @@ -1011,17 +1080,17 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett // Named arguments can be accessed without the "a:" prefix in lambda // expressions. Add to the l: dict. tv_copy(&v->di_tv, &v->di_tv); - hash_add(&fc->l_vars.dv_hashtab, (char *)v->di_key); + hash_add(&fc->fc_l_vars.dv_hashtab, v->di_key); } else { - hash_add(&fc->l_avars.dv_hashtab, (char *)v->di_key); + hash_add(&fc->fc_l_avars.dv_hashtab, v->di_key); } if (ai >= 0 && ai < MAX_FUNC_ARGS) { - listitem_T *li = &fc->l_listitems[ai]; + listitem_T *li = &fc->fc_l_listitems[ai]; *TV_LIST_ITEM_TV(li) = argvars[i]; TV_LIST_ITEM_TV(li)->v_lock = VAR_FIXED; - tv_list_append(&fc->l_varlist, li); + tv_list_append(&fc->fc_l_varlist, li); } } @@ -1038,7 +1107,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett no_wait_return++; verbose_enter_scroll(); - smsg(_("calling %s"), SOURCING_NAME); + smsg(0, _("calling %s"), SOURCING_NAME); if (p_verbose >= 14) { msg_puts("("); for (int i = 0; i < argcount; i++) { @@ -1046,7 +1115,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett msg_puts(", "); } if (argvars[i].v_type == VAR_NUMBER) { - msg_outnum((long)argvars[i].vval.v_number); + msg_outnum((int)argvars[i].vval.v_number); } else { // Do not want errors such as E724 here. emsg_off++; @@ -1076,7 +1145,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett bool func_not_yet_profiling_but_should = do_profiling_yes - && !fp->uf_profiling && has_profiling(false, (char *)fp->uf_name, NULL); + && !fp->uf_profiling && has_profiling(false, fp->uf_name, NULL); if (func_not_yet_profiling_but_should) { started_profiling = true; @@ -1086,7 +1155,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett bool func_or_func_caller_profiling = do_profiling_yes && (fp->uf_profiling - || (fc->caller != NULL && fc->caller->func->uf_profiling)); + || (fc->fc_caller != NULL && fc->fc_caller->fc_func->uf_profiling)); if (func_or_func_caller_profiling) { fp->uf_tm_count++; @@ -1111,7 +1180,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett // A Lambda always has the command "return {expr}". It is much faster // to evaluate {expr} directly. ex_nesting_level++; - (void)eval1(&p, rettv, true); + (void)eval1(&p, rettv, &EVALARG_EVALUATE); ex_nesting_level--; } else { // call do_cmdline() to execute the lines @@ -1119,6 +1188,9 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett DOCMD_NOWAIT|DOCMD_VERBOSE|DOCMD_REPEAT); } + // Invoke functions added with ":defer". + handle_defer_one(current_funccal); + RedrawingDisabled--; // when the function was aborted because of an error, return -1 @@ -1131,15 +1203,15 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett if (func_or_func_caller_profiling) { call_start = profile_end(call_start); - call_start = profile_sub_wait(wait_start, call_start); // -V614 + call_start = profile_sub_wait(wait_start, call_start); fp->uf_tm_total = profile_add(fp->uf_tm_total, call_start); fp->uf_tm_self = profile_self(fp->uf_tm_self, call_start, fp->uf_tm_children); - if (fc->caller != NULL && fc->caller->func->uf_profiling) { - fc->caller->func->uf_tm_children = - profile_add(fc->caller->func->uf_tm_children, call_start); - fc->caller->func->uf_tml_children = - profile_add(fc->caller->func->uf_tml_children, call_start); + if (fc->fc_caller != NULL && fc->fc_caller->fc_func->uf_profiling) { + fc->fc_caller->fc_func->uf_tm_children = + profile_add(fc->fc_caller->fc_func->uf_tm_children, call_start); + fc->fc_caller->fc_func->uf_tml_children = + profile_add(fc->fc_caller->fc_func->uf_tml_children, call_start); } if (started_profiling) { // make a ":profdel func" stop profiling the function @@ -1153,10 +1225,10 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett verbose_enter_scroll(); if (aborting()) { - smsg(_("%s aborted"), SOURCING_NAME); - } else if (fc->rettv->v_type == VAR_NUMBER) { - smsg(_("%s returning #%" PRId64 ""), - SOURCING_NAME, (int64_t)fc->rettv->vval.v_number); + smsg(0, _("%s aborted"), SOURCING_NAME); + } else if (fc->fc_rettv->v_type == VAR_NUMBER) { + smsg(0, _("%s returning #%" PRId64 ""), + SOURCING_NAME, (int64_t)fc->fc_rettv->vval.v_number); } else { char buf[MSG_BUF_LEN]; @@ -1164,7 +1236,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett // have some idea how it starts and ends. smsg() would always // truncate it at the end. Don't want errors such as E724 here. emsg_off++; - char *s = encode_tv2string(fc->rettv, NULL); + char *s = encode_tv2string(fc->fc_rettv, NULL); char *tofree = s; emsg_off--; if (s != NULL) { @@ -1172,7 +1244,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett trunc_string(s, buf, MSG_BUF_CLEN, MSG_BUF_LEN); s = buf; } - smsg(_("%s returning %s"), SOURCING_NAME, s); + smsg(0, _("%s returning %s"), SOURCING_NAME, s); xfree(tofree); } } @@ -1195,7 +1267,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett no_wait_return++; verbose_enter_scroll(); - smsg(_("continuing in %s"), SOURCING_NAME); + smsg(0, _("continuing in %s"), SOURCING_NAME); msg_puts("\n"); // don't overwrite this either verbose_leave_scroll(); @@ -1231,6 +1303,21 @@ static bool func_name_refcount(const char *name) return isdigit((uint8_t)(*name)) || *name == '<'; } +/// Check the argument count for user function "fp". +/// @return FCERR_UNKNOWN if OK, FCERR_TOOFEW or FCERR_TOOMANY otherwise. +static int check_user_func_argcount(ufunc_T *fp, int argcount) + FUNC_ATTR_NONNULL_ALL +{ + const int regular_args = fp->uf_args.ga_len; + + if (argcount < regular_args - fp->uf_def_args.ga_len) { + return FCERR_TOOFEW; + } else if (!fp->uf_varargs && argcount > regular_args) { + return FCERR_TOOMANY; + } + return FCERR_UNKNOWN; +} + /// Call a user function after checking the arguments. static int call_user_func_check(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rettv, funcexe_T *funcexe, dict_T *selfdict) @@ -1243,12 +1330,11 @@ static int call_user_func_check(ufunc_T *fp, int argcount, typval_T *argvars, ty if ((fp->uf_flags & FC_RANGE) && funcexe->fe_doesrange != NULL) { *funcexe->fe_doesrange = true; } - int error; - if (argcount < fp->uf_args.ga_len - fp->uf_def_args.ga_len) { - error = FCERR_TOOFEW; - } else if (!fp->uf_varargs && argcount > fp->uf_args.ga_len) { - error = FCERR_TOOMANY; - } else if ((fp->uf_flags & FC_DICT) && selfdict == NULL) { + int error = check_user_func_argcount(fp, argcount); + if (error != FCERR_UNKNOWN) { + return error; + } + if ((fp->uf_flags & FC_DICT) && selfdict == NULL) { error = FCERR_DICT; } else { // Call the user function. @@ -1302,8 +1388,8 @@ void free_all_functions(void) // Clean up the current_funccal chain and the funccal stack. while (current_funccal != NULL) { - tv_clear(current_funccal->rettv); - cleanup_function_call(current_funccal); // -V595 + tv_clear(current_funccal->fc_rettv); + cleanup_function_call(current_funccal); if (current_funccal == NULL && funccal_stack != NULL) { restore_funccal(); } @@ -1435,12 +1521,16 @@ varnumber_T callback_call_retnr(Callback *callback, int argcount, typval_T *argv /// Give an error message for the result of a function. /// Nothing if "error" is FCERR_NONE. -static void user_func_error(int error, const char *name) - FUNC_ATTR_NONNULL_ALL +static void user_func_error(int error, const char *name, funcexe_T *funcexe) + FUNC_ATTR_NONNULL_ARG(2) { switch (error) { case FCERR_UNKNOWN: - emsg_funcname(N_("E117: Unknown function: %s"), name); + if (funcexe->fe_found_var) { + semsg(_(e_not_callable_type_str), name); + } else { + emsg_funcname(e_unknown_function_str, name); + } break; case FCERR_NOTMETHOD: emsg_funcname(N_("E276: Cannot use function as a method: %s"), name); @@ -1452,7 +1542,7 @@ static void user_func_error(int error, const char *name) emsg_funcname(_(e_toomanyarg), name); break; case FCERR_TOOFEW: - emsg_funcname(N_("E119: Not enough arguments for function: %s"), name); + emsg_funcname(_(e_toofewarg), name); break; case FCERR_SCRIPT: emsg_funcname(N_("E120: Using <SID> not in a script context: %s"), name); @@ -1511,7 +1601,7 @@ int call_func(const char *funcname, int len, typval_T *rettv, int argcount_in, t int argv_base = 0; partial_T *partial = funcexe->fe_partial; - // Initialize rettv so that it is safe for caller to invoke clear_tv(rettv) + // Initialize rettv so that it is safe for caller to invoke tv_clear(rettv) // even when call_func() returns FAIL. rettv->v_type = VAR_UNKNOWN; @@ -1524,7 +1614,7 @@ int call_func(const char *funcname, int len, typval_T *rettv, int argcount_in, t if (fp == NULL) { // Make a copy of the name, if it comes from a funcref variable it could // be changed or deleted in the called function. - name = xstrnsave(funcname, (size_t)len); + name = xmemdupz(funcname, (size_t)len); fname = fname_trans_sid(name, fname_buf, &tofree, &error); } @@ -1577,7 +1667,7 @@ int call_func(const char *funcname, int len, typval_T *rettv, int argcount_in, t XFREE_CLEAR(name); funcname = "v:lua"; } - } else if (fp != NULL || !builtin_function((const char *)rfname, -1)) { + } else if (fp != NULL || !builtin_function(rfname, -1)) { // User defined function. if (fp == NULL) { fp = find_func(rfname); @@ -1591,8 +1681,7 @@ int call_func(const char *funcname, int len, typval_T *rettv, int argcount_in, t fp = find_func(rfname); } // Try loading a package. - if (fp == NULL && script_autoload((const char *)rfname, strlen(rfname), - true) && !aborting()) { + if (fp == NULL && script_autoload(rfname, strlen(rfname), true) && !aborting()) { // Loaded a package, search for the function again. fp = find_func(rfname); } @@ -1636,7 +1725,7 @@ theend: // Report an error unless the argument evaluation or function call has been // cancelled due to an aborting error, an interrupt, or an exception. if (!aborting()) { - user_func_error(error, (name != NULL) ? name : funcname); + user_func_error(error, (name != NULL) ? name : funcname, funcexe); } // clear the copies made from the partial @@ -1655,22 +1744,41 @@ char *printable_func_name(ufunc_T *fp) return fp->uf_name_exp != NULL ? fp->uf_name_exp : fp->uf_name; } +/// When "prev_ht_changed" does not equal "ht_changed" give an error and return +/// true. Otherwise return false. +static int function_list_modified(const int prev_ht_changed) +{ + if (prev_ht_changed != func_hashtab.ht_changed) { + emsg(_(e_function_list_was_modified)); + return true; + } + return false; +} + /// List the head of the function: "name(arg1, arg2)". /// /// @param[in] fp Function pointer. /// @param[in] indent Indent line. /// @param[in] force Include bang "!" (i.e.: "function!"). -static void list_func_head(ufunc_T *fp, int indent, bool force) +static int list_func_head(ufunc_T *fp, bool indent, bool force) { + const int prev_ht_changed = func_hashtab.ht_changed; + msg_start(); + + // a callback at the more prompt may have deleted the function + if (function_list_modified(prev_ht_changed)) { + return FAIL; + } + if (indent) { msg_puts(" "); } msg_puts(force ? "function! " : "function "); if (fp->uf_name_exp != NULL) { - msg_puts((const char *)fp->uf_name_exp); + msg_puts(fp->uf_name_exp); } else { - msg_puts((const char *)fp->uf_name); + msg_puts(fp->uf_name); } msg_putchar('('); int j; @@ -1678,7 +1786,7 @@ static void list_func_head(ufunc_T *fp, int indent, bool force) if (j) { msg_puts(", "); } - msg_puts((const char *)FUNCARG(fp, j)); + msg_puts(FUNCARG(fp, j)); if (j >= fp->uf_args.ga_len - fp->uf_def_args.ga_len) { msg_puts(" = "); msg_puts(((char **)(fp->uf_def_args.ga_data)) @@ -1708,6 +1816,8 @@ static void list_func_head(ufunc_T *fp, int indent, bool force) if (p_verbose > 0) { last_set_msg(fp->uf_script_ctx); } + + return OK; } /// Get a function name, translating "<SID>" and "<SNR>". @@ -1728,21 +1838,17 @@ char *trans_function_name(char **pp, bool skip, int flags, funcdict_T *fdp, part FUNC_ATTR_NONNULL_ARG(1) { char *name = NULL; - const char *start; - const char *end; - int lead; int len; lval_T lv; if (fdp != NULL) { CLEAR_POINTER(fdp); } - start = *pp; + const char *start = *pp; // Check for hard coded <SNR>: already translated function ID (from a user // command). - if ((unsigned char)(*pp)[0] == K_SPECIAL && (unsigned char)(*pp)[1] == KS_EXTRA - && (*pp)[2] == KE_SNR) { + if ((uint8_t)(*pp)[0] == K_SPECIAL && (uint8_t)(*pp)[1] == KS_EXTRA && (*pp)[2] == KE_SNR) { *pp += 3; len = get_id_len((const char **)pp) + 3; return xmemdupz(start, (size_t)len); @@ -1750,14 +1856,14 @@ char *trans_function_name(char **pp, bool skip, int flags, funcdict_T *fdp, part // A name starting with "<SID>" or "<SNR>" is local to a script. But // don't skip over "s:", get_lval() needs it for "s:dict.func". - lead = eval_fname_script(start); + int lead = eval_fname_script(start); if (lead > 2) { start += lead; } // Note that TFN_ flags use the same values as GLV_ flags. - end = get_lval((char *)start, NULL, &lv, false, skip, flags | GLV_READ_ONLY, - lead > 2 ? 0 : FNE_CHECK_START); + const char *end = get_lval((char *)start, NULL, &lv, false, skip, flags | GLV_READ_ONLY, + lead > 2 ? 0 : FNE_CHECK_START); if (end == start) { if (!skip) { emsg(_("E129: Function name required")); @@ -1773,7 +1879,7 @@ char *trans_function_name(char **pp, bool skip, int flags, funcdict_T *fdp, part semsg(_(e_invarg2), start); } } else { - *pp = (char *)find_name_end((char *)start, NULL, NULL, FNE_INCL_BR); + *pp = (char *)find_name_end(start, NULL, NULL, FNE_INCL_BR); } goto theend; } @@ -1828,14 +1934,13 @@ char *trans_function_name(char **pp, bool skip, int flags, funcdict_T *fdp, part // Check if the name is a Funcref. If so, use the value. if (lv.ll_exp_name != NULL) { len = (int)strlen(lv.ll_exp_name); - name = deref_func_name(lv.ll_exp_name, &len, partial, - flags & TFN_NO_AUTOLOAD); - if ((const char *)name == lv.ll_exp_name) { + name = deref_func_name(lv.ll_exp_name, &len, partial, flags & TFN_NO_AUTOLOAD, NULL); + if (name == lv.ll_exp_name) { name = NULL; } } else if (!(flags & TFN_NO_DEREF)) { len = (int)(end - *pp); - name = deref_func_name(*pp, &len, partial, flags & TFN_NO_AUTOLOAD); + name = deref_func_name(*pp, &len, partial, flags & TFN_NO_AUTOLOAD, NULL); if (name == *pp) { name = NULL; } @@ -1883,8 +1988,7 @@ char *trans_function_name(char **pp, bool skip, int flags, funcdict_T *fdp, part lead = 0; // do nothing } else if (lead > 0) { lead = 3; - if ((lv.ll_exp_name != NULL && eval_fname_sid(lv.ll_exp_name)) - || eval_fname_sid((const char *)(*pp))) { + if ((lv.ll_exp_name != NULL && eval_fname_sid(lv.ll_exp_name)) || eval_fname_sid(*pp)) { // It's "s:" or "<SID>". if (current_sctx.sc_sid <= 0) { emsg(_(e_usingsid)); @@ -1971,7 +2075,7 @@ char *save_function_name(char **name, bool skip, int flags, funcdict_T *fudi) if (strncmp(p, "<lambda>", 8) == 0) { p += 8; (void)getdigits(&p, false, 0); - saved = xstrndup(*name, (size_t)(p - *name)); + saved = xmemdupz(*name, (size_t)(p - *name)); if (fudi != NULL) { CLEAR_POINTER(fudi); } @@ -1990,7 +2094,7 @@ char *save_function_name(char **name, bool skip, int flags, funcdict_T *fudi) /// Otherwise functions matching "regmatch". static void list_functions(regmatch_T *regmatch) { - const int changed = func_hashtab.ht_changed; + const int prev_ht_changed = func_hashtab.ht_changed; size_t todo = func_hashtab.ht_used; const hashitem_T *const ht_array = func_hashtab.ht_array; @@ -1998,15 +2102,15 @@ static void list_functions(regmatch_T *regmatch) if (!HASHITEM_EMPTY(hi)) { ufunc_T *fp = HI2UF(hi); todo--; - if ((fp->uf_flags & FC_DEAD) == 0 - && (regmatch == NULL - ? (!message_filtered((char *)fp->uf_name) - && !func_name_refcount(fp->uf_name)) - : (!isdigit((uint8_t)(*fp->uf_name)) - && vim_regexec(regmatch, (char *)fp->uf_name, 0)))) { - list_func_head(fp, false, false); - if (changed != func_hashtab.ht_changed) { - emsg(_("E454: function list was modified")); + if (regmatch == NULL + ? (!message_filtered(fp->uf_name) + && !func_name_refcount(fp->uf_name)) + : (!isdigit((uint8_t)(*fp->uf_name)) + && vim_regexec(regmatch, fp->uf_name, 0))) { + if (list_func_head(fp, false, false) == FAIL) { + return; + } + if (function_list_modified(prev_ht_changed)) { return; } } @@ -2019,11 +2123,7 @@ void ex_function(exarg_T *eap) { char *theline; char *line_to_free = NULL; - char c; - int saved_did_emsg; bool saved_wait_return = need_wait_return; - char *name = NULL; - char *p; char *arg; char *line_arg = NULL; garray_T newargs; @@ -2033,16 +2133,9 @@ void ex_function(exarg_T *eap) int flags = 0; ufunc_T *fp; bool overwrite = false; - int indent; - int nesting; - dictitem_T *v; funcdict_T fudi; static int func_nr = 0; // number for nameless function - int paren; hashtab_T *ht; - hashitem_T *hi; - linenr_T sourcing_lnum_off; - linenr_T sourcing_lnum_top; bool is_heredoc = false; char *skip_until = NULL; char *heredoc_trimmed = NULL; @@ -2060,11 +2153,11 @@ void ex_function(exarg_T *eap) // ":function /pat": list functions matching pattern. if (*eap->arg == '/') { - p = skip_regexp(eap->arg + 1, '/', true); + char *p = skip_regexp(eap->arg + 1, '/', true); if (!eap->skip) { regmatch_T regmatch; - c = *p; + char c = *p; *p = NUL; regmatch.regprog = vim_regcomp(eap->arg + 1, RE_MAGIC); *p = c; @@ -2095,9 +2188,9 @@ void ex_function(exarg_T *eap) // "fudi.fd_di" set, "fudi.fd_newkey" == NULL // s:func script-local function name // g:func global function name, same as "func" - p = eap->arg; - name = save_function_name(&p, eap->skip, TFN_NO_AUTOLOAD, &fudi); - paren = (vim_strchr(p, '(') != NULL); + char *p = eap->arg; + char *name = save_function_name(&p, eap->skip, TFN_NO_AUTOLOAD, &fudi); + int paren = (vim_strchr(p, '(') != NULL); if (name == NULL && (fudi.fd_dict == NULL || !paren) && !eap->skip) { // Return on an invalid expression in braces, unless the expression // evaluation has been cancelled due to an aborting error, an @@ -2114,7 +2207,7 @@ void ex_function(exarg_T *eap) // An error in a function call during evaluation of an expression in magic // braces should not cause the function not to be defined. - saved_did_emsg = did_emsg; + const int saved_did_emsg = did_emsg; did_emsg = false; // @@ -2135,28 +2228,37 @@ void ex_function(exarg_T *eap) if (!eap->skip && !got_int) { fp = find_func(name); if (fp != NULL) { - list_func_head(fp, !eap->forceit, eap->forceit); - for (int j = 0; j < fp->uf_lines.ga_len && !got_int; j++) { - if (FUNCLINE(fp, j) == NULL) { - continue; - } - msg_putchar('\n'); - if (!eap->forceit) { - msg_outnum((long)j + 1); - if (j < 9) { - msg_putchar(' '); + // Check no function was added or removed from a callback, e.g. at + // the more prompt. "fp" may then be invalid. + const int prev_ht_changed = func_hashtab.ht_changed; + + if (list_func_head(fp, !eap->forceit, eap->forceit) == OK) { + for (int j = 0; j < fp->uf_lines.ga_len && !got_int; j++) { + if (FUNCLINE(fp, j) == NULL) { + continue; } - if (j < 99) { - msg_putchar(' '); + msg_putchar('\n'); + if (!eap->forceit) { + msg_outnum(j + 1); + if (j < 9) { + msg_putchar(' '); + } + if (j < 99) { + msg_putchar(' '); + } + if (function_list_modified(prev_ht_changed)) { + break; + } + } + msg_prt_line(FUNCLINE(fp, j), false); + line_breakcheck(); // show multiple lines at a time! + } + if (!got_int) { + msg_putchar('\n'); + if (!function_list_modified(prev_ht_changed)) { + msg_puts(eap->forceit ? "endfunction" : " endfunction"); } } - msg_prt_line(FUNCLINE(fp, j), false); - ui_flush(); // show a line at a time - os_breakcheck(); - } - if (!got_int) { - msg_putchar('\n'); - msg_puts(eap->forceit ? "endfunction" : " endfunction"); } } else { emsg_funcname(N_("E123: Undefined function: %s"), name); @@ -2196,7 +2298,7 @@ void ex_function(exarg_T *eap) j++; } if (arg[j] != NUL) { - emsg_funcname((char *)e_invarg2, arg); + emsg_funcname(e_invarg2, arg); } } // Disallow using the g: dict. @@ -2212,11 +2314,11 @@ void ex_function(exarg_T *eap) if (KeyTyped && ui_has(kUICmdline)) { show_block = true; - ui_ext_cmdline_block_append(0, (const char *)eap->cmd); + ui_ext_cmdline_block_append(0, eap->cmd); } // find extra arguments "range", "dict", "abort" and "closure" - for (;;) { + while (true) { p = skipwhite(p); if (strncmp(p, "range", 5) == 0) { flags |= FC_RANGE; @@ -2272,11 +2374,11 @@ void ex_function(exarg_T *eap) } // Save the starting line number. - sourcing_lnum_top = SOURCING_LNUM; + linenr_T sourcing_lnum_top = SOURCING_LNUM; - indent = 2; - nesting = 0; - for (;;) { + int indent = 2; + int nesting = 0; + while (true) { if (KeyTyped) { msg_scroll = true; saved_wait_return = false; @@ -2296,7 +2398,7 @@ void ex_function(exarg_T *eap) } else { xfree(line_to_free); if (eap->getline == NULL) { - theline = getcmdline(':', 0L, indent, do_concat); + theline = getcmdline(':', 0, indent, do_concat); } else { theline = eap->getline(':', eap->cookie, indent, do_concat); } @@ -2306,16 +2408,20 @@ void ex_function(exarg_T *eap) lines_left = Rows - 1; } if (theline == NULL) { - emsg(_("E126: Missing :endfunction")); + if (skip_until != NULL) { + semsg(_(e_missing_heredoc_end_marker_str), skip_until); + } else { + emsg(_("E126: Missing :endfunction")); + } goto erret; } if (show_block) { assert(indent >= 0); - ui_ext_cmdline_block_append((size_t)indent, (const char *)theline); + ui_ext_cmdline_block_append((size_t)indent, theline); } // Detect line continuation: SOURCING_LNUM increased more than one. - sourcing_lnum_off = get_sourced_lnum(eap->getline, eap->cookie); + linenr_T sourcing_lnum_off = get_sourced_lnum(eap->getline, eap->cookie); if (SOURCING_LNUM < sourcing_lnum_off) { sourcing_lnum_off -= SOURCING_LNUM; } else { @@ -2335,7 +2441,7 @@ void ex_function(exarg_T *eap) p = theline; } else if (is_heredoc) { p = skipwhite(theline) == theline - ? theline : theline + strlen(heredoc_trimmed); + ? theline : theline + strlen(heredoc_trimmed); } else { p = theline + strlen(heredoc_trimmed); } @@ -2361,7 +2467,7 @@ void ex_function(exarg_T *eap) } else if (line_arg != NULL && *skipwhite(line_arg) != NUL) { nextcmd = line_arg; } else if (*p != NUL && *p != '"' && p_verbose > 0) { - give_warning2(_("W22: Text found after :endfunction: %s"), p, true); + swmsg(true, _("W22: Text found after :endfunction: %s"), p); } if (nextcmd != NULL) { // Another command follows. If the line came from "eap" we @@ -2393,11 +2499,11 @@ void ex_function(exarg_T *eap) if (*p == '!') { p = skipwhite(p + 1); } - p += eval_fname_script((const char *)p); + p += eval_fname_script(p); xfree(trans_function_name(&p, true, 0, NULL, NULL)); if (*skipwhite(p) == '(') { if (nesting == MAX_FUNC_NESTING - 1) { - emsg(_("E1058: function nesting too deep")); + emsg(_(e_function_nesting_too_deep)); } else { nesting++; indent += 2; @@ -2440,35 +2546,45 @@ void ex_function(exarg_T *eap) && (!ASCII_ISALPHA(p[2]) || p[2] == 's')))) { // ":python <<" continues until a dot, like ":append" p = skipwhite(arg + 2); + if (strncmp(p, "trim", 4) == 0) { + // Ignore leading white space. + p = skipwhite(p + 4); + heredoc_trimmed = xmemdupz(theline, (size_t)(skipwhite(theline) - theline)); + } if (*p == NUL) { skip_until = xstrdup("."); } else { - skip_until = xstrdup(p); + skip_until = xmemdupz(p, (size_t)(skiptowhite(p) - p)); } + do_concat = false; + is_heredoc = true; } // Check for ":let v =<< [trim] EOF" // and ":let [a, b] =<< [trim] EOF" - arg = skipwhite(skiptowhite(p)); - if (*arg == '[') { - arg = vim_strchr(arg, ']'); - } - if (arg != NULL) { - arg = skipwhite(skiptowhite(arg)); - if (arg[0] == '=' - && arg[1] == '<' - && arg[2] == '<' - && (p[0] == 'l' - && p[1] == 'e' - && (!ASCII_ISALNUM(p[2]) - || (p[2] == 't' && !ASCII_ISALNUM(p[3]))))) { + arg = p; + if (checkforcmd(&arg, "let", 2)) { + while (vim_strchr("$@&", *arg) != NULL) { + arg++; + } + arg = skipwhite(find_name_end(arg, NULL, NULL, FNE_INCL_BR)); + if (arg[0] == '=' && arg[1] == '<' && arg[2] == '<') { p = skipwhite(arg + 3); - if (strncmp(p, "trim", 4) == 0) { - // Ignore leading white space. - p = skipwhite(p + 4); - heredoc_trimmed = xstrnsave(theline, (size_t)(skipwhite(theline) - theline)); + while (true) { + if (strncmp(p, "trim", 4) == 0) { + // Ignore leading white space. + p = skipwhite(p + 4); + heredoc_trimmed = xmemdupz(theline, (size_t)(skipwhite(theline) - theline)); + continue; + } + if (strncmp(p, "eval", 4) == 0) { + // Ignore leading white space. + p = skipwhite(p + 4); + continue; + } + break; } - skip_until = xstrnsave(p, (size_t)(skiptowhite(p) - p)); + skip_until = xmemdupz(p, (size_t)(skiptowhite(p) - p)); do_concat = false; is_heredoc = true; } @@ -2504,7 +2620,7 @@ void ex_function(exarg_T *eap) // If there are no errors, add the function if (fudi.fd_dict == NULL) { - v = find_var((const char *)name, strlen(name), &ht, false); + dictitem_T *v = find_var(name, strlen(name), &ht, false); if (v != NULL && v->di_tv.v_type == VAR_FUNC) { emsg_funcname(N_("E707: Function name conflicts with variable: %s"), name); goto erret; @@ -2551,13 +2667,11 @@ void ex_function(exarg_T *eap) goto erret; } if (fudi.fd_di == NULL) { - if (value_check_lock(fudi.fd_dict->dv_lock, (const char *)eap->arg, - TV_CSTRING)) { + if (value_check_lock(fudi.fd_dict->dv_lock, eap->arg, TV_CSTRING)) { // Can't add a function to a locked dictionary goto erret; } - } else if (value_check_lock(fudi.fd_di->di_tv.v_lock, (const char *)eap->arg, - TV_CSTRING)) { + } else if (value_check_lock(fudi.fd_di->di_tv.v_lock, eap->arg, TV_CSTRING)) { // Can't change an existing function if it is locked goto erret; } @@ -2565,22 +2679,19 @@ void ex_function(exarg_T *eap) // Give the function a sequential number. Can only be used with a // Funcref! xfree(name); - sprintf(numbuf, "%d", ++func_nr); // NOLINT(runtime/printf) + snprintf(numbuf, sizeof(numbuf), "%d", ++func_nr); name = xstrdup(numbuf); } if (fp == NULL) { if (fudi.fd_dict == NULL && vim_strchr(name, AUTOLOAD_CHAR) != NULL) { - int slen, plen; - char *scriptname; - // Check that the autoload name matches the script name. int j = FAIL; if (SOURCING_NAME != NULL) { - scriptname = autoload_name(name, strlen(name)); + char *scriptname = autoload_name(name, strlen(name)); p = vim_strchr(scriptname, '/'); - plen = (int)strlen(p); - slen = (int)strlen(SOURCING_NAME); + int plen = (int)strlen(p); + int slen = (int)strlen(SOURCING_NAME); if (slen > plen && path_fnamecmp(p, SOURCING_NAME + slen - plen) == 0) { j = OK; } @@ -2598,7 +2709,7 @@ void ex_function(exarg_T *eap) if (fudi.fd_dict != NULL) { if (fudi.fd_di == NULL) { // Add new dict entry - fudi.fd_di = tv_dict_item_alloc((const char *)fudi.fd_newkey); + fudi.fd_di = tv_dict_item_alloc(fudi.fd_newkey); if (tv_dict_add(fudi.fd_dict, fudi.fd_di) == FAIL) { xfree(fudi.fd_di); xfree(fp); @@ -2618,8 +2729,8 @@ void ex_function(exarg_T *eap) // insert the new function in the function list set_ufunc_name(fp, name); if (overwrite) { - hi = hash_find(&func_hashtab, name); - hi->hi_key = (char *)UF2HIKEY(fp); + hashitem_T *hi = hash_find(&func_hashtab, name); + hi->hi_key = UF2HIKEY(fp); } else if (hash_add(&func_hashtab, UF2HIKEY(fp)) == FAIL) { xfree(fp); goto erret; @@ -2688,7 +2799,7 @@ int eval_fname_script(const char *const p) bool translated_function_exists(const char *name) { if (builtin_function(name, -1)) { - return find_internal_func((char *)name) != NULL; + return find_internal_func(name) != NULL; } return find_func(name) != NULL; } @@ -2727,7 +2838,6 @@ char *get_user_func_name(expand_T *xp, int idx) static size_t done; static int changed; static hashitem_T *hi; - ufunc_T *fp; if (idx == 0) { done = 0; @@ -2742,7 +2852,7 @@ char *get_user_func_name(expand_T *xp, int idx) while (HASHITEM_EMPTY(hi)) { hi++; } - fp = HI2UF(hi); + ufunc_T *fp = HI2UF(hi); if ((fp->uf_flags & FC_DICT) || strncmp(fp->uf_name, "<lambda>", 8) == 0) { @@ -2750,14 +2860,14 @@ char *get_user_func_name(expand_T *xp, int idx) } if (strlen(fp->uf_name) + 4 >= IOSIZE) { - return (char *)fp->uf_name; // Prevent overflow. + return fp->uf_name; // Prevent overflow. } - cat_func_name(IObuff, fp); + cat_func_name(IObuff, IOSIZE, fp); if (xp->xp_context != EXPAND_USER_FUNC) { - STRCAT(IObuff, "("); + xstrlcat(IObuff, "(", IOSIZE); if (!fp->uf_varargs && GA_EMPTY(&fp->uf_args)) { - STRCAT(IObuff, ")"); + xstrlcat(IObuff, ")", IOSIZE); } } return IObuff; @@ -2769,12 +2879,10 @@ char *get_user_func_name(expand_T *xp, int idx) void ex_delfunction(exarg_T *eap) { ufunc_T *fp = NULL; - char *p; - char *name; funcdict_T fudi; - p = eap->arg; - name = trans_function_name(&p, eap->skip, 0, &fudi, NULL); + char *p = eap->arg; + char *name = trans_function_name(&p, eap->skip, 0, &fudi, NULL); xfree(fudi.fd_newkey); if (name == NULL) { if (fudi.fd_dict != NULL && !eap->skip) { @@ -2851,13 +2959,11 @@ void ex_delfunction(exarg_T *eap) /// becomes zero. void func_unref(char *name) { - ufunc_T *fp = NULL; - if (name == NULL || !func_name_refcount(name)) { return; } - fp = find_func(name); + ufunc_T *fp = find_func(name); if (fp == NULL && isdigit((uint8_t)(*name))) { #ifdef EXITFREE if (!entered_free_all_mem) { @@ -2893,12 +2999,10 @@ void func_ptr_unref(ufunc_T *fp) /// Count a reference to a Function. void func_ref(char *name) { - ufunc_T *fp; - if (name == NULL || !func_name_refcount(name)) { return; } - fp = find_func(name); + ufunc_T *fp = find_func(name); if (fp != NULL) { (fp->uf_refcount)++; } else if (isdigit((uint8_t)(*name))) { @@ -2925,10 +3029,10 @@ static inline bool fc_referenced(const funccall_T *const fc) FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL { - return ((fc->l_varlist.lv_refcount // NOLINT(runtime/deprecated) + return ((fc->fc_l_varlist.lv_refcount // NOLINT(runtime/deprecated) != DO_NOT_FREE_CNT) - || fc->l_vars.dv_refcount != DO_NOT_FREE_CNT - || fc->l_avars.dv_refcount != DO_NOT_FREE_CNT + || fc->fc_l_vars.dv_refcount != DO_NOT_FREE_CNT + || fc->fc_l_avars.dv_refcount != DO_NOT_FREE_CNT || fc->fc_refcount > 0); } @@ -2936,9 +3040,9 @@ static inline bool fc_referenced(const funccall_T *const fc) /// referenced from anywhere that is in use. static int can_free_funccal(funccall_T *fc, int copyID) { - return fc->l_varlist.lv_copyID != copyID - && fc->l_vars.dv_copyID != copyID - && fc->l_avars.dv_copyID != copyID + return fc->fc_l_varlist.lv_copyID != copyID + && fc->fc_l_vars.dv_copyID != copyID + && fc->fc_l_avars.dv_copyID != copyID && fc->fc_copyID != copyID; } @@ -2954,13 +3058,15 @@ void ex_return(exarg_T *eap) return; } + evalarg_T evalarg = { .eval_flags = eap->skip ? 0 : EVAL_EVALUATE }; + if (eap->skip) { emsg_skip++; } eap->nextcmd = NULL; if ((*arg != NUL && *arg != '|' && *arg != '\n') - && eval0(arg, &rettv, &eap->nextcmd, !eap->skip) != FAIL) { + && eval0(arg, &rettv, eap, &evalarg) != FAIL) { if (!eap->skip) { returning = do_return(eap, false, true, &rettv); } else { @@ -2989,36 +3095,234 @@ void ex_return(exarg_T *eap) if (eap->skip) { emsg_skip--; } + clear_evalarg(&evalarg, eap); +} + +/// Lower level implementation of "call". Only called when not skipping. +static int ex_call_inner(exarg_T *eap, char *name, char **arg, char *startarg, + const funcexe_T *const funcexe_init, evalarg_T *const evalarg) +{ + bool doesrange; + bool failed = false; + + for (linenr_T lnum = eap->line1; lnum <= eap->line2; lnum++) { + if (eap->addr_count > 0) { + if (lnum > curbuf->b_ml.ml_line_count) { + // If the function deleted lines or switched to another buffer + // the line number may become invalid. + emsg(_(e_invrange)); + break; + } + curwin->w_cursor.lnum = lnum; + curwin->w_cursor.col = 0; + curwin->w_cursor.coladd = 0; + } + *arg = startarg; + + funcexe_T funcexe = *funcexe_init; + funcexe.fe_doesrange = &doesrange; + typval_T rettv; + rettv.v_type = VAR_UNKNOWN; // tv_clear() uses this + if (get_func_tv(name, -1, &rettv, arg, evalarg, &funcexe) == FAIL) { + failed = true; + break; + } + + // Handle a function returning a Funcref, Dictionary or List. + if (handle_subscript((const char **)arg, &rettv, &EVALARG_EVALUATE, true) == FAIL) { + failed = true; + break; + } + + tv_clear(&rettv); + if (doesrange) { + break; + } + + // Stop when immediately aborting on error, or when an interrupt + // occurred or an exception was thrown but not caught. + // get_func_tv() returned OK, so that the check for trailing + // characters below is executed. + if (aborting()) { + break; + } + } + + return failed; +} + +/// Core part of ":defer func(arg)". "arg" points to the "(" and is advanced. +/// +/// @return FAIL or OK. +static int ex_defer_inner(char *name, char **arg, const partial_T *const partial, + evalarg_T *const evalarg) +{ + typval_T argvars[MAX_FUNC_ARGS + 1]; // vars for arguments + int partial_argc = 0; // number of partial arguments + int argcount = 0; // number of arguments found + + if (current_funccal == NULL) { + semsg(_(e_str_not_inside_function), "defer"); + return FAIL; + } + if (partial != NULL) { + if (partial->pt_dict != NULL) { + emsg(_(e_cannot_use_partial_with_dictionary_for_defer)); + return FAIL; + } + if (partial->pt_argc > 0) { + partial_argc = partial->pt_argc; + for (int i = 0; i < partial_argc; i++) { + tv_copy(&partial->pt_argv[i], &argvars[i]); + } + } + } + int r = get_func_arguments(arg, evalarg, false, argvars + partial_argc, &argcount); + argcount += partial_argc; + + if (r == OK) { + if (builtin_function(name, -1)) { + const EvalFuncDef *const fdef = find_internal_func(name); + if (fdef == NULL) { + emsg_funcname(e_unknown_function_str, name); + r = FAIL; + } else if (check_internal_func(fdef, argcount) == -1) { + r = FAIL; + } + } else { + ufunc_T *ufunc = find_func(name); + // we tolerate an unknown function here, it might be defined later + if (ufunc != NULL) { + int error = check_user_func_argcount(ufunc, argcount); + if (error != FCERR_UNKNOWN) { + user_func_error(error, name, NULL); + r = FAIL; + } + } + } + } + + if (r == FAIL) { + while (--argcount >= 0) { + tv_clear(&argvars[argcount]); + } + return FAIL; + } + add_defer(name, argcount, argvars); + return OK; +} + +/// Return true if currently inside a function call. +/// Give an error message and return false when not. +bool can_add_defer(void) +{ + if (get_current_funccal() == NULL) { + semsg(_(e_str_not_inside_function), "defer"); + return false; + } + return true; +} + +/// Add a deferred call for "name" with arguments "argvars[argcount]". +/// Consumes "argvars[]". +/// Caller must check that current_funccal is not NULL. +void add_defer(char *name, int argcount_arg, typval_T *argvars) +{ + char *saved_name = xstrdup(name); + int argcount = argcount_arg; + + if (current_funccal->fc_defer.ga_itemsize == 0) { + ga_init(¤t_funccal->fc_defer, sizeof(defer_T), 10); + } + defer_T *dr = GA_APPEND_VIA_PTR(defer_T, ¤t_funccal->fc_defer); + dr->dr_name = saved_name; + dr->dr_argcount = argcount; + while (argcount > 0) { + argcount--; + dr->dr_argvars[argcount] = argvars[argcount]; + } +} + +/// Invoked after a function has finished: invoke ":defer" functions. +static void handle_defer_one(funccall_T *funccal) +{ + for (int idx = funccal->fc_defer.ga_len - 1; idx >= 0; idx--) { + defer_T *dr = ((defer_T *)funccal->fc_defer.ga_data) + idx; + + if (dr->dr_name == NULL) { + // already being called, can happen if function does ":qa" + continue; + } + + funcexe_T funcexe = { .fe_evaluate = true }; + + typval_T rettv; + rettv.v_type = VAR_UNKNOWN; // tv_clear() uses this + + char *name = dr->dr_name; + dr->dr_name = NULL; + + // If the deferred function is called after an exception, then only the + // first statement in the function will be executed (because of the + // exception). So save and restore the try/catch/throw exception + // state. + exception_state_T estate; + exception_state_save(&estate); + exception_state_clear(); + + call_func(name, -1, &rettv, dr->dr_argcount, dr->dr_argvars, &funcexe); + + exception_state_restore(&estate); + + tv_clear(&rettv); + xfree(name); + for (int i = dr->dr_argcount - 1; i >= 0; i--) { + tv_clear(&dr->dr_argvars[i]); + } + } + ga_clear(&funccal->fc_defer); +} + +/// Called when exiting: call all defer functions. +void invoke_all_defer(void) +{ + for (funccall_T *fc = current_funccal; fc != NULL; fc = fc->fc_caller) { + handle_defer_one(fc); + } + + for (funccal_entry_T *fce = funccal_stack; fce != NULL; fce = fce->next) { + for (funccall_T *fc = fce->top_funccal; fc != NULL; fc = fc->fc_caller) { + handle_defer_one(fc); + } + } } /// ":1,25call func(arg1, arg2)" function call. +/// ":defer func(arg1, arg2)" deferred function call. void ex_call(exarg_T *eap) { char *arg = eap->arg; - char *startarg; - char *name; - char *tofree; - int len; - typval_T rettv; - linenr_T lnum; - bool doesrange; bool failed = false; funcdict_T fudi; partial_T *partial = NULL; + evalarg_T evalarg; + fill_evalarg_from_eap(&evalarg, eap, eap->skip); if (eap->skip) { + typval_T rettv; // trans_function_name() doesn't work well when skipping, use eval0() // instead to skip to any following command, e.g. for: // :if 0 | call dict.foo().bar() | endif. emsg_skip++; - if (eval0(eap->arg, &rettv, &eap->nextcmd, false) != FAIL) { + if (eval0(eap->arg, &rettv, eap, &evalarg) != FAIL) { tv_clear(&rettv); } emsg_skip--; + clear_evalarg(&evalarg, eap); return; } - tofree = trans_function_name(&arg, false, TFN_INT, &fudi, &partial); + char *tofree = trans_function_name(&arg, false, TFN_INT, &fudi, &partial); if (fudi.fd_newkey != NULL) { // Still need to give an error message for missing key. semsg(_(e_dictkey), fudi.fd_newkey); @@ -3037,66 +3341,31 @@ void ex_call(exarg_T *eap) // If it is the name of a variable of type VAR_FUNC or VAR_PARTIAL use its // contents. For VAR_PARTIAL get its partial, unless we already have one // from trans_function_name(). - len = (int)strlen(tofree); - name = deref_func_name(tofree, &len, partial != NULL ? NULL : &partial, false); + int len = (int)strlen(tofree); + bool found_var = false; + char *name = deref_func_name(tofree, &len, partial != NULL ? NULL : &partial, false, &found_var); // Skip white space to allow ":call func ()". Not good, but required for // backward compatibility. - startarg = skipwhite(arg); - rettv.v_type = VAR_UNKNOWN; // tv_clear() uses this. + char *startarg = skipwhite(arg); if (*startarg != '(') { semsg(_(e_missingparen), eap->arg); goto end; } - lnum = eap->line1; - for (; lnum <= eap->line2; lnum++) { - if (eap->addr_count > 0) { // -V560 - if (lnum > curbuf->b_ml.ml_line_count) { - // If the function deleted lines or switched to another buffer - // the line number may become invalid. - emsg(_(e_invrange)); - break; - } - curwin->w_cursor.lnum = lnum; - curwin->w_cursor.col = 0; - curwin->w_cursor.coladd = 0; - } + if (eap->cmdidx == CMD_defer) { arg = startarg; - + failed = ex_defer_inner(name, &arg, partial, &evalarg) == FAIL; + } else { funcexe_T funcexe = FUNCEXE_INIT; + funcexe.fe_partial = partial; + funcexe.fe_selfdict = fudi.fd_dict; funcexe.fe_firstline = eap->line1; funcexe.fe_lastline = eap->line2; - funcexe.fe_doesrange = &doesrange; + funcexe.fe_found_var = found_var; funcexe.fe_evaluate = true; - funcexe.fe_partial = partial; - funcexe.fe_selfdict = fudi.fd_dict; - if (get_func_tv(name, -1, &rettv, &arg, &funcexe) == FAIL) { - failed = true; - break; - } - - // Handle a function returning a Funcref, Dictionary or List. - if (handle_subscript((const char **)&arg, &rettv, true, true, - (const char *)name, (const char **)&name) - == FAIL) { - failed = true; - break; - } - - tv_clear(&rettv); - if (doesrange) { - break; - } - - // Stop when immediately aborting on error, or when an interrupt - // occurred or an exception was thrown but not caught. - // get_func_tv() returned OK, so that the check for trailing - // characters below is executed. - if (aborting()) { - break; - } + failed = ex_call_inner(eap, name, &arg, startarg, &funcexe, &evalarg); } // When inside :try we need to check for following "| catch" or "| endtry". @@ -3112,6 +3381,7 @@ void ex_call(exarg_T *eap) eap->nextcmd = check_nextcmd(arg); } } + clear_evalarg(&evalarg, eap); end: tv_dict_unref(fudi.fd_dict); @@ -3130,19 +3400,18 @@ end: /// false when the return gets pending. int do_return(exarg_T *eap, int reanimate, int is_cmd, void *rettv) { - int idx; cstack_T *const cstack = eap->cstack; if (reanimate) { // Undo the return. - current_funccal->returned = false; + current_funccal->fc_returned = false; } // Cleanup (and deactivate) conditionals, but stop when a try conditional // not in its finally clause (which then is to be executed next) is found. // In this case, make the ":return" pending for execution at the ":endtry". // Otherwise, return normally. - idx = cleanup_conditionals(eap->cstack, 0, true); + int idx = cleanup_conditionals(eap->cstack, 0, true); if (idx >= 0) { cstack->cs_pending[idx] = CSTP_RETURN; @@ -3155,8 +3424,8 @@ int do_return(exarg_T *eap, int reanimate, int is_cmd, void *rettv) // When undoing a return in order to make it pending, get the stored // return rettv. if (reanimate) { - assert(current_funccal->rettv); - rettv = current_funccal->rettv; + assert(current_funccal->fc_rettv); + rettv = current_funccal->fc_rettv; } if (rettv != NULL) { @@ -3171,20 +3440,20 @@ int do_return(exarg_T *eap, int reanimate, int is_cmd, void *rettv) // The pending return value could be overwritten by a ":return" // without argument in a finally clause; reset the default // return value. - current_funccal->rettv->v_type = VAR_NUMBER; - current_funccal->rettv->vval.v_number = 0; + current_funccal->fc_rettv->v_type = VAR_NUMBER; + current_funccal->fc_rettv->vval.v_number = 0; } } report_make_pending(CSTP_RETURN, rettv); } else { - current_funccal->returned = true; + current_funccal->fc_returned = true; // If the return is carried out now, store the return value. For // a return immediately after reanimation, the value is already // there. if (!reanimate && rettv != NULL) { - tv_clear(current_funccal->rettv); - *current_funccal->rettv = *(typval_T *)rettv; + tv_clear(current_funccal->fc_rettv); + *current_funccal->fc_rettv = *(typval_T *)rettv; if (!is_cmd) { xfree(rettv); } @@ -3208,7 +3477,7 @@ char *get_return_cmd(void *rettv) s = ""; } - STRCPY(IObuff, ":return "); + xstrlcpy(IObuff, ":return ", IOSIZE); xstrlcpy(IObuff + 8, s, IOSIZE - 8); if (strlen(s) + 8 >= IOSIZE) { STRCPY(IObuff + IOSIZE - 4, "..."); @@ -3224,34 +3493,33 @@ char *get_return_cmd(void *rettv) char *get_func_line(int c, void *cookie, int indent, bool do_concat) { funccall_T *fcp = (funccall_T *)cookie; - ufunc_T *fp = fcp->func; + ufunc_T *fp = fcp->fc_func; char *retval; - garray_T *gap; // growarray with function lines // If breakpoints have been added/deleted need to check for it. - if (fcp->dbg_tick != debug_tick) { - fcp->breakpoint = dbg_find_breakpoint(false, (char *)fp->uf_name, SOURCING_LNUM); - fcp->dbg_tick = debug_tick; + if (fcp->fc_dbg_tick != debug_tick) { + fcp->fc_breakpoint = dbg_find_breakpoint(false, fp->uf_name, SOURCING_LNUM); + fcp->fc_dbg_tick = debug_tick; } if (do_profiling == PROF_YES) { func_line_end(cookie); } - gap = &fp->uf_lines; + garray_T *gap = &fp->uf_lines; // growarray with function lines if (((fp->uf_flags & FC_ABORT) && did_emsg && !aborted_in_try()) - || fcp->returned) { + || fcp->fc_returned) { retval = NULL; } else { // Skip NULL lines (continuation lines). - while (fcp->linenr < gap->ga_len - && ((char **)(gap->ga_data))[fcp->linenr] == NULL) { - fcp->linenr++; + while (fcp->fc_linenr < gap->ga_len + && ((char **)(gap->ga_data))[fcp->fc_linenr] == NULL) { + fcp->fc_linenr++; } - if (fcp->linenr >= gap->ga_len) { + if (fcp->fc_linenr >= gap->ga_len) { retval = NULL; } else { - retval = xstrdup(((char **)(gap->ga_data))[fcp->linenr++]); - SOURCING_LNUM = fcp->linenr; + retval = xstrdup(((char **)(gap->ga_data))[fcp->fc_linenr++]); + SOURCING_LNUM = fcp->fc_linenr; if (do_profiling == PROF_YES) { func_line_start(cookie); } @@ -3259,11 +3527,11 @@ char *get_func_line(int c, void *cookie, int indent, bool do_concat) } // Did we encounter a breakpoint? - if (fcp->breakpoint != 0 && fcp->breakpoint <= SOURCING_LNUM) { - dbg_breakpoint((char *)fp->uf_name, SOURCING_LNUM); + if (fcp->fc_breakpoint != 0 && fcp->fc_breakpoint <= SOURCING_LNUM) { + dbg_breakpoint(fp->uf_name, SOURCING_LNUM); // Find next breakpoint. - fcp->breakpoint = dbg_find_breakpoint(false, (char *)fp->uf_name, SOURCING_LNUM); - fcp->dbg_tick = debug_tick; + fcp->fc_breakpoint = dbg_find_breakpoint(false, fp->uf_name, SOURCING_LNUM); + fcp->fc_dbg_tick = debug_tick; } return retval; @@ -3277,21 +3545,20 @@ int func_has_ended(void *cookie) // Ignore the "abort" flag if the abortion behavior has been changed due to // an error inside a try conditional. - return ((fcp->func->uf_flags & FC_ABORT) && did_emsg && !aborted_in_try()) - || fcp->returned; + return ((fcp->fc_func->uf_flags & FC_ABORT) && did_emsg && !aborted_in_try()) + || fcp->fc_returned; } /// @return true if cookie indicates a function which "abort"s on errors. int func_has_abort(void *cookie) { - return ((funccall_T *)cookie)->func->uf_flags & FC_ABORT; + return ((funccall_T *)cookie)->fc_func->uf_flags & FC_ABORT; } /// Turn "dict.Func" into a partial for "Func" bound to "dict". /// Changes "rettv" in-place. void make_partial(dict_T *const selfdict, typval_T *const rettv) { - char *fname; char *tofree = NULL; ufunc_T *fp; char fname_buf[FLEN_FIXED + 1]; @@ -3300,9 +3567,9 @@ void make_partial(dict_T *const selfdict, typval_T *const rettv) if (rettv->v_type == VAR_PARTIAL && rettv->vval.v_partial->pt_func != NULL) { fp = rettv->vval.v_partial->pt_func; } else { - fname = rettv->v_type == VAR_FUNC || rettv->v_type == VAR_STRING - ? rettv->vval.v_string - : rettv->vval.v_partial->pt_name; + char *fname = rettv->v_type == VAR_FUNC || rettv->v_type == VAR_STRING + ? rettv->vval.v_string + : rettv->vval.v_partial->pt_name; // Translate "s:func" to the stored function name. fname = fname_trans_sid(fname, fname_buf, &tofree, &error); fp = find_func(fname); @@ -3321,7 +3588,6 @@ void make_partial(dict_T *const selfdict, typval_T *const rettv) pt->pt_name = rettv->vval.v_string; } else { partial_T *ret_pt = rettv->vval.v_partial; - int i; // Partial: copy the function name, use selfdict and copy // args. Can't take over name or args, the partial might @@ -3337,7 +3603,7 @@ void make_partial(dict_T *const selfdict, typval_T *const rettv) size_t arg_size = sizeof(typval_T) * (size_t)ret_pt->pt_argc; pt->pt_argv = (typval_T *)xmalloc(arg_size); pt->pt_argc = ret_pt->pt_argc; - for (i = 0; i < pt->pt_argc; i++) { + for (int i = 0; i < pt->pt_argc; i++) { tv_copy(&ret_pt->pt_argv[i], &pt->pt_argv[i]); } } @@ -3351,31 +3617,31 @@ void make_partial(dict_T *const selfdict, typval_T *const rettv) /// @return the name of the executed function. char *func_name(void *cookie) { - return ((funccall_T *)cookie)->func->uf_name; + return ((funccall_T *)cookie)->fc_func->uf_name; } /// @return the address holding the next breakpoint line for a funccall cookie. linenr_T *func_breakpoint(void *cookie) { - return &((funccall_T *)cookie)->breakpoint; + return &((funccall_T *)cookie)->fc_breakpoint; } /// @return the address holding the debug tick for a funccall cookie. int *func_dbg_tick(void *cookie) { - return &((funccall_T *)cookie)->dbg_tick; + return &((funccall_T *)cookie)->fc_dbg_tick; } /// @return the nesting level for a funccall cookie. int func_level(void *cookie) { - return ((funccall_T *)cookie)->level; + return ((funccall_T *)cookie)->fc_level; } /// @return true when a function was ended by a ":return" command. int current_func_returned(void) { - return current_funccal->returned; + return current_funccal->fc_returned; } bool free_unref_funccal(int copyID, int testing) @@ -3386,12 +3652,12 @@ bool free_unref_funccal(int copyID, int testing) for (funccall_T **pfc = &previous_funccal; *pfc != NULL;) { if (can_free_funccal(*pfc, copyID)) { funccall_T *fc = *pfc; - *pfc = fc->caller; + *pfc = fc->fc_caller; free_funccal_contents(fc); did_free = true; did_free_funccal = true; } else { - pfc = &(*pfc)->caller; + pfc = &(*pfc)->fc_caller; } } if (did_free_funccal) { @@ -3408,7 +3674,7 @@ funccall_T *get_funccal(void) funccall_T *funccal = current_funccal; if (debug_backtrace_level > 0) { for (int i = 0; i < debug_backtrace_level; i++) { - funccall_T *temp_funccal = funccal->caller; + funccall_T *temp_funccal = funccal->fc_caller; if (temp_funccal) { funccal = temp_funccal; } else { @@ -3428,7 +3694,7 @@ hashtab_T *get_funccal_local_ht(void) if (current_funccal == NULL) { return NULL; } - return &get_funccal()->l_vars.dv_hashtab; + return &get_funccal()->fc_l_vars.dv_hashtab; } /// @return the l: scope variable or @@ -3438,7 +3704,7 @@ dictitem_T *get_funccal_local_var(void) if (current_funccal == NULL) { return NULL; } - return (dictitem_T *)&get_funccal()->l_vars_var; + return (dictitem_T *)&get_funccal()->fc_l_vars_var; } /// @return the hashtable used for argument in the current funccal or @@ -3448,7 +3714,7 @@ hashtab_T *get_funccal_args_ht(void) if (current_funccal == NULL) { return NULL; } - return &get_funccal()->l_avars.dv_hashtab; + return &get_funccal()->fc_l_avars.dv_hashtab; } /// @return the a: scope variable or @@ -3458,14 +3724,14 @@ dictitem_T *get_funccal_args_var(void) if (current_funccal == NULL) { return NULL; } - return (dictitem_T *)¤t_funccal->l_avars_var; + return (dictitem_T *)¤t_funccal->fc_l_avars_var; } /// List function variables, if there is a function. void list_func_vars(int *first) { if (current_funccal != NULL) { - list_hashtable_vars(¤t_funccal->l_vars.dv_hashtab, "l:", false, + list_hashtable_vars(¤t_funccal->fc_l_vars.dv_hashtab, "l:", false, first); } } @@ -3474,8 +3740,8 @@ void list_func_vars(int *first) /// funccal, return the dict that contains it. Otherwise return NULL. dict_T *get_current_funccal_dict(hashtab_T *ht) { - if (current_funccal != NULL && ht == ¤t_funccal->l_vars.dv_hashtab) { - return ¤t_funccal->l_vars; + if (current_funccal != NULL && ht == ¤t_funccal->fc_l_vars.dv_hashtab) { + return ¤t_funccal->fc_l_vars; } return NULL; } @@ -3483,7 +3749,7 @@ dict_T *get_current_funccal_dict(hashtab_T *ht) /// Search hashitem in parent scope. hashitem_T *find_hi_in_scoped_ht(const char *name, hashtab_T **pht) { - if (current_funccal == NULL || current_funccal->func->uf_scoped == NULL) { + if (current_funccal == NULL || current_funccal->fc_func->uf_scoped == NULL) { return NULL; } @@ -3493,7 +3759,7 @@ hashitem_T *find_hi_in_scoped_ht(const char *name, hashtab_T **pht) const char *varname; // Search in parent scope which is possible to reference from lambda - current_funccal = current_funccal->func->uf_scoped; + current_funccal = current_funccal->fc_func->uf_scoped; while (current_funccal != NULL) { hashtab_T *ht = find_var_ht(name, namelen, &varname); if (ht != NULL && *varname != NUL) { @@ -3503,10 +3769,10 @@ hashitem_T *find_hi_in_scoped_ht(const char *name, hashtab_T **pht) break; } } - if (current_funccal == current_funccal->func->uf_scoped) { + if (current_funccal == current_funccal->fc_func->uf_scoped) { break; } - current_funccal = current_funccal->func->uf_scoped; + current_funccal = current_funccal->fc_func->uf_scoped; } current_funccal = old_current_funccal; @@ -3516,7 +3782,7 @@ hashitem_T *find_hi_in_scoped_ht(const char *name, hashtab_T **pht) /// Search variable in parent scope. dictitem_T *find_var_in_scoped_ht(const char *name, const size_t namelen, int no_autoload) { - if (current_funccal == NULL || current_funccal->func->uf_scoped == NULL) { + if (current_funccal == NULL || current_funccal->fc_func->uf_scoped == NULL) { return NULL; } @@ -3525,7 +3791,7 @@ dictitem_T *find_var_in_scoped_ht(const char *name, const size_t namelen, int no const char *varname; // Search in parent scope which is possible to reference from lambda - current_funccal = current_funccal->func->uf_scoped; + current_funccal = current_funccal->fc_func->uf_scoped; while (current_funccal) { hashtab_T *ht = find_var_ht(name, namelen, &varname); if (ht != NULL && *varname != NUL) { @@ -3535,10 +3801,10 @@ dictitem_T *find_var_in_scoped_ht(const char *name, const size_t namelen, int no break; } } - if (current_funccal == current_funccal->func->uf_scoped) { + if (current_funccal == current_funccal->fc_func->uf_scoped) { break; } - current_funccal = current_funccal->func->uf_scoped; + current_funccal = current_funccal->fc_func->uf_scoped; } current_funccal = old_current_funccal; @@ -3549,11 +3815,11 @@ dictitem_T *find_var_in_scoped_ht(const char *name, const size_t namelen, int no bool set_ref_in_previous_funccal(int copyID) { for (funccall_T *fc = previous_funccal; fc != NULL; - fc = fc->caller) { + fc = fc->fc_caller) { fc->fc_copyID = copyID + 1; - if (set_ref_in_ht(&fc->l_vars.dv_hashtab, copyID + 1, NULL) - || set_ref_in_ht(&fc->l_avars.dv_hashtab, copyID + 1, NULL) - || set_ref_in_list(&fc->l_varlist, copyID + 1, NULL)) { + if (set_ref_in_ht(&fc->fc_l_vars.dv_hashtab, copyID + 1, NULL) + || set_ref_in_ht(&fc->fc_l_avars.dv_hashtab, copyID + 1, NULL) + || set_ref_in_list(&fc->fc_l_varlist, copyID + 1, NULL)) { return true; } } @@ -3564,10 +3830,10 @@ static bool set_ref_in_funccal(funccall_T *fc, int copyID) { if (fc->fc_copyID != copyID) { fc->fc_copyID = copyID; - if (set_ref_in_ht(&fc->l_vars.dv_hashtab, copyID, NULL) - || set_ref_in_ht(&fc->l_avars.dv_hashtab, copyID, NULL) - || set_ref_in_list(&fc->l_varlist, copyID, NULL) - || set_ref_in_func(NULL, fc->func, copyID)) { + if (set_ref_in_ht(&fc->fc_l_vars.dv_hashtab, copyID, NULL) + || set_ref_in_ht(&fc->fc_l_avars.dv_hashtab, copyID, NULL) + || set_ref_in_list(&fc->fc_l_varlist, copyID, NULL) + || set_ref_in_func(NULL, fc->fc_func, copyID)) { return true; } } @@ -3578,7 +3844,7 @@ static bool set_ref_in_funccal(funccall_T *fc, int copyID) bool set_ref_in_call_stack(int copyID) { for (funccall_T *fc = current_funccal; fc != NULL; - fc = fc->caller) { + fc = fc->fc_caller) { if (set_ref_in_funccal(fc, copyID)) { return true; } @@ -3588,7 +3854,7 @@ bool set_ref_in_call_stack(int copyID) for (funccal_entry_T *entry = funccal_stack; entry != NULL; entry = entry->next) { for (funccall_T *fc = entry->top_funccal; fc != NULL; - fc = fc->caller) { + fc = fc->fc_caller) { if (set_ref_in_funccal(fc, copyID)) { return true; } @@ -3601,15 +3867,11 @@ bool set_ref_in_call_stack(int copyID) /// Set "copyID" in all functions available by name. bool set_ref_in_functions(int copyID) { - int todo; - hashitem_T *hi = NULL; - ufunc_T *fp; - - todo = (int)func_hashtab.ht_used; - for (hi = func_hashtab.ht_array; todo > 0 && !got_int; hi++) { + int todo = (int)func_hashtab.ht_used; + for (hashitem_T *hi = func_hashtab.ht_array; todo > 0 && !got_int; hi++) { if (!HASHITEM_EMPTY(hi)) { todo--; - fp = HI2UF(hi); + ufunc_T *fp = HI2UF(hi); if (!func_name_refcount(fp->uf_name) && set_ref_in_func(NULL, fp, copyID)) { return true; @@ -3639,22 +3901,20 @@ bool set_ref_in_func_args(int copyID) bool set_ref_in_func(char *name, ufunc_T *fp_in, int copyID) { ufunc_T *fp = fp_in; - funccall_T *fc; int error = FCERR_NONE; char fname_buf[FLEN_FIXED + 1]; char *tofree = NULL; - char *fname; bool abort = false; if (name == NULL && fp_in == NULL) { return false; } if (fp_in == NULL) { - fname = fname_trans_sid(name, fname_buf, &tofree, &error); + char *fname = fname_trans_sid(name, fname_buf, &tofree, &error); fp = find_func(fname); } if (fp != NULL) { - for (fc = fp->uf_scoped; fc != NULL; fc = fc->func->uf_scoped) { + for (funccall_T *fc = fp->uf_scoped; fc != NULL; fc = fc->fc_func->uf_scoped) { abort = abort || set_ref_in_funccal(fc, copyID); } } diff --git a/src/nvim/eval/userfunc.h b/src/nvim/eval/userfunc.h index c8583f232c..8050caab2b 100644 --- a/src/nvim/eval/userfunc.h +++ b/src/nvim/eval/userfunc.h @@ -1,22 +1,21 @@ -#ifndef NVIM_EVAL_USERFUNC_H -#define NVIM_EVAL_USERFUNC_H +#pragma once #include <stdbool.h> #include <stddef.h> -#include "nvim/eval/typval.h" +#include "nvim/cmdexpand_defs.h" // IWYU pragma: keep +#include "nvim/eval.h" #include "nvim/eval/typval_defs.h" -#include "nvim/ex_cmds_defs.h" -#include "nvim/garray.h" -#include "nvim/hashtab.h" -#include "nvim/pos.h" -#include "nvim/types.h" +#include "nvim/ex_cmds_defs.h" // IWYU pragma: keep +#include "nvim/hashtab_defs.h" // IWYU pragma: keep +#include "nvim/pos_defs.h" +#include "nvim/types_defs.h" // IWYU pragma: keep struct funccal_entry; // From user function to hashitem and back. #define UF2HIKEY(fp) ((fp)->uf_name) -#define HIKEY2UF(p) ((ufunc_T *)(p - offsetof(ufunc_T, uf_name))) +#define HIKEY2UF(p) ((ufunc_T *)((p) - offsetof(ufunc_T, uf_name))) #define HI2UF(hi) HIKEY2UF((hi)->hi_key) // flags used in uf_flags @@ -27,10 +26,10 @@ struct funccal_entry; #define FC_DELETED 0x10 // :delfunction used while uf_refcount > 0 #define FC_REMOVED 0x20 // function redefined while uf_refcount > 0 #define FC_SANDBOX 0x40 // function defined in the sandbox -#define FC_DEAD 0x80 // function kept only for reference to dfunc -#define FC_EXPORT 0x100 // "export def Func()" +// #define FC_DEAD 0x80 // function kept only for reference to dfunc +// #define FC_EXPORT 0x100 // "export def Func()" #define FC_NOARGS 0x200 // no a: variables in lambda -#define FC_VIM9 0x400 // defined in vim9 script file +// #define FC_VIM9 0x400 // defined in vim9 script file #define FC_LUAREF 0x800 // luaref callback /// Structure used by trans_function_name() @@ -74,6 +73,8 @@ typedef struct { partial_T *fe_partial; ///< for extra arguments dict_T *fe_selfdict; ///< Dictionary for "self" typval_T *fe_basetv; ///< base for base->method() + bool fe_found_var; ///< if the function is not found then give an + ///< error that a variable is not callable. } funcexe_T; #define FUNCEXE_INIT (funcexe_T) { \ @@ -85,6 +86,7 @@ typedef struct { .fe_partial = NULL, \ .fe_selfdict = NULL, \ .fe_basetv = NULL, \ + .fe_found_var = false, \ } #define FUNCARG(fp, j) ((char **)(fp->uf_args.ga_data))[j] @@ -93,4 +95,3 @@ typedef struct { #ifdef INCLUDE_GENERATED_DECLARATIONS # include "eval/userfunc.h.generated.h" #endif -#endif // NVIM_EVAL_USERFUNC_H diff --git a/src/nvim/eval/vars.c b/src/nvim/eval/vars.c index 58fb2211fc..10a2f40800 100644 --- a/src/nvim/eval/vars.c +++ b/src/nvim/eval/vars.c @@ -1,6 +1,3 @@ -// This is an open source non-commercial project. Dear PVS-Studio, please check -// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com - // eval/vars.c: functions for dealing with variables #include <assert.h> @@ -8,9 +5,11 @@ #include <stdbool.h> #include <stddef.h> #include <stdint.h> +#include <stdlib.h> #include <string.h> +#include <sys/types.h> -#include "nvim/ascii.h" +#include "nvim/ascii_defs.h" #include "nvim/autocmd.h" #include "nvim/buffer_defs.h" #include "nvim/charset.h" @@ -19,7 +18,6 @@ #include "nvim/eval/encode.h" #include "nvim/eval/funcs.h" #include "nvim/eval/typval.h" -#include "nvim/eval/typval_defs.h" #include "nvim/eval/userfunc.h" #include "nvim/eval/vars.h" #include "nvim/eval/window.h" @@ -27,19 +25,23 @@ #include "nvim/ex_cmds_defs.h" #include "nvim/ex_docmd.h" #include "nvim/ex_eval.h" +#include "nvim/func_attr.h" +#include "nvim/garray.h" #include "nvim/gettext.h" #include "nvim/globals.h" #include "nvim/hashtab.h" -#include "nvim/macros.h" +#include "nvim/macros_defs.h" #include "nvim/memory.h" #include "nvim/message.h" #include "nvim/ops.h" #include "nvim/option.h" +#include "nvim/option_defs.h" +#include "nvim/option_vars.h" #include "nvim/os/os.h" #include "nvim/search.h" #include "nvim/strings.h" -#include "nvim/types.h" -#include "nvim/vim.h" +#include "nvim/types_defs.h" +#include "nvim/vim_defs.h" #include "nvim/window.h" #ifdef INCLUDE_GENERATED_DECLARATIONS @@ -50,8 +52,102 @@ #define DICT_MAXNEST 100 // maximum nesting of lists and dicts -static char *e_letunexp = N_("E18: Unexpected characters in :let"); -static char *e_lock_unlock = N_("E940: Cannot lock or unlock variable %s"); +static const char *e_letunexp = N_("E18: Unexpected characters in :let"); +static const char *e_lock_unlock = N_("E940: Cannot lock or unlock variable %s"); +static const char e_setting_v_str_to_value_with_wrong_type[] + = N_("E963: Setting v:%s to value with wrong type"); +static const char e_cannot_use_heredoc_here[] + = N_("E991: Cannot use =<< here"); + +/// Evaluate one Vim expression {expr} in string "p" and append the +/// resulting string to "gap". "p" points to the opening "{". +/// When "evaluate" is false only skip over the expression. +/// Return a pointer to the character after "}", NULL for an error. +char *eval_one_expr_in_str(char *p, garray_T *gap, bool evaluate) +{ + char *block_start = skipwhite(p + 1); // skip the opening { + char *block_end = block_start; + + if (*block_start == NUL) { + semsg(_(e_missing_close_curly_str), p); + return NULL; + } + if (skip_expr(&block_end, NULL) == FAIL) { + return NULL; + } + block_end = skipwhite(block_end); + if (*block_end != '}') { + semsg(_(e_missing_close_curly_str), p); + return NULL; + } + if (evaluate) { + *block_end = NUL; + char *expr_val = eval_to_string(block_start, true); + *block_end = '}'; + if (expr_val == NULL) { + return NULL; + } + ga_concat(gap, expr_val); + xfree(expr_val); + } + + return block_end + 1; +} + +/// Evaluate all the Vim expressions {expr} in "str" and return the resulting +/// string in allocated memory. "{{" is reduced to "{" and "}}" to "}". +/// Used for a heredoc assignment. +/// Returns NULL for an error. +char *eval_all_expr_in_str(char *str) +{ + garray_T ga; + ga_init(&ga, 1, 80); + char *p = str; + + while (*p != NUL) { + bool escaped_brace = false; + + // Look for a block start. + char *lit_start = p; + while (*p != '{' && *p != '}' && *p != NUL) { + p++; + } + + if (*p != NUL && *p == p[1]) { + // Escaped brace, unescape and continue. + // Include the brace in the literal string. + p++; + escaped_brace = true; + } else if (*p == '}') { + semsg(_(e_stray_closing_curly_str), str); + ga_clear(&ga); + return NULL; + } + + // Append the literal part. + ga_concat_len(&ga, lit_start, (size_t)(p - lit_start)); + + if (*p == NUL) { + break; + } + + if (escaped_brace) { + // Skip the second brace. + p++; + continue; + } + + // Evaluate the expression and append the result. + p = eval_one_expr_in_str(p, &ga, true); + if (p == NULL) { + ga_clear(&ga); + return NULL; + } + } + ga_append(&ga, NUL); + + return ga.ga_data; +} /// Get a list of lines from a HERE document. The here document is a list of /// lines surrounded by a marker. @@ -65,64 +161,91 @@ static char *e_lock_unlock = N_("E940: Cannot lock or unlock variable %s"); /// marker, then the leading indentation before the lines (matching the /// indentation in the 'cmd' line) is stripped. /// -/// @return a List with {lines} or NULL. -static list_T *heredoc_get(exarg_T *eap, char *cmd) +/// When getting lines for an embedded script (e.g. python, lua, perl, ruby, +/// tcl, mzscheme), "script_get" is set to true. In this case, if the marker is +/// missing, then '.' is accepted as a marker. +/// +/// @return a List with {lines} or NULL on failure. +list_T *heredoc_get(exarg_T *eap, char *cmd, bool script_get) { char *marker; - char *p; int marker_indent_len = 0; int text_indent_len = 0; char *text_indent = NULL; + char dot[] = "."; if (eap->getline == NULL) { - emsg(_("E991: cannot use =<< here")); + emsg(_(e_cannot_use_heredoc_here)); return NULL; } // Check for the optional 'trim' word before the marker cmd = skipwhite(cmd); - if (strncmp(cmd, "trim", 4) == 0 - && (cmd[4] == NUL || ascii_iswhite(cmd[4]))) { - cmd = skipwhite(cmd + 4); - - // Trim the indentation from all the lines in the here document. - // The amount of indentation trimmed is the same as the indentation of - // the first line after the :let command line. To find the end marker - // the indent of the :let command line is trimmed. - p = *eap->cmdlinep; - while (ascii_iswhite(*p)) { - p++; - marker_indent_len++; + bool evalstr = false; + bool eval_failed = false; + while (true) { + if (strncmp(cmd, "trim", 4) == 0 + && (cmd[4] == NUL || ascii_iswhite(cmd[4]))) { + cmd = skipwhite(cmd + 4); + + // Trim the indentation from all the lines in the here document. + // The amount of indentation trimmed is the same as the indentation + // of the first line after the :let command line. To find the end + // marker the indent of the :let command line is trimmed. + char *p = *eap->cmdlinep; + while (ascii_iswhite(*p)) { + p++; + marker_indent_len++; + } + text_indent_len = -1; + + continue; + } + if (strncmp(cmd, "eval", 4) == 0 + && (cmd[4] == NUL || ascii_iswhite(cmd[4]))) { + cmd = skipwhite(cmd + 4); + evalstr = true; + continue; } - text_indent_len = -1; + break; } // The marker is the next word. if (*cmd != NUL && *cmd != '"') { marker = skipwhite(cmd); - p = skiptowhite(marker); + char *p = skiptowhite(marker); if (*skipwhite(p) != NUL && *skipwhite(p) != '"') { semsg(_(e_trailing_arg), p); return NULL; } *p = NUL; - if (islower((uint8_t)(*marker))) { + if (!script_get && islower((uint8_t)(*marker))) { emsg(_("E221: Marker cannot start with lower case letter")); return NULL; } } else { - emsg(_("E172: Missing marker")); - return NULL; + // When getting lines for an embedded script, if the marker is missing, + // accept '.' as the marker. + if (script_get) { + marker = dot; + } else { + emsg(_("E172: Missing marker")); + return NULL; + } } + char *theline = NULL; list_T *l = tv_list_alloc(0); - for (;;) { + while (true) { int mi = 0; int ti = 0; - char *theline = eap->getline(NUL, eap->cookie, 0, false); + xfree(theline); + theline = eap->getline(NUL, eap->cookie, 0, false); if (theline == NULL) { - semsg(_("E990: Missing end marker '%s'"), marker); + if (!script_get) { + semsg(_("E990: Missing end marker '%s'"), marker); + } break; } @@ -133,18 +256,24 @@ static list_T *heredoc_get(exarg_T *eap, char *cmd) mi = marker_indent_len; } if (strcmp(marker, theline + mi) == 0) { - xfree(theline); break; } + + // If expression evaluation failed in the heredoc, then skip till the + // end marker. + if (eval_failed) { + continue; + } + if (text_indent_len == -1 && *theline != NUL) { // set the text indent from the first line. - p = theline; + char *p = theline; text_indent_len = 0; while (ascii_iswhite(*p)) { p++; text_indent_len++; } - text_indent = xstrnsave(theline, (size_t)text_indent_len); + text_indent = xmemdupz(theline, (size_t)text_indent_len); } // with "trim": skip the indent matching the first line if (text_indent != NULL) { @@ -155,11 +284,28 @@ static list_T *heredoc_get(exarg_T *eap, char *cmd) } } - tv_list_append_string(l, theline + ti, -1); - xfree(theline); + char *str = theline + ti; + if (evalstr && !eap->skip) { + str = eval_all_expr_in_str(str); + if (str == NULL) { + // expression evaluation failed + eval_failed = true; + continue; + } + xfree(theline); + theline = str; + } + + tv_list_append_string(l, str, -1); } + xfree(theline); xfree(text_indent); + if (eval_failed) { + // expression evaluation in the heredoc failed + tv_list_free(l); + return NULL; + } return l; } @@ -175,32 +321,23 @@ static list_T *heredoc_get(exarg_T *eap, char *cmd) /// ":let var ..= expr" assignment command. /// ":let [var1, var2] = expr" unpack list. /// ":let [name, ..., ; lastname] = expr" unpack list. -void ex_let(exarg_T *eap) -{ - ex_let_const(eap, false); -} - +/// /// ":cons[t] var = expr1" define constant /// ":cons[t] [name1, name2, ...] = expr1" define constants unpacking list /// ":cons[t] [name, ..., ; lastname] = expr" define constants unpacking list -void ex_const(exarg_T *eap) -{ - ex_let_const(eap, true); -} - -static void ex_let_const(exarg_T *eap, const bool is_const) +void ex_let(exarg_T *eap) { + const bool is_const = eap->cmdidx == CMD_const; char *arg = eap->arg; char *expr = NULL; typval_T rettv; - int i; int var_count = 0; int semicolon = 0; char op[2]; - char *argend; + const char *argend; int first = true; - argend = (char *)skip_var_list(arg, &var_count, &semicolon); + argend = skip_var_list(arg, &var_count, &semicolon); if (argend == NULL) { return; } @@ -208,14 +345,16 @@ static void ex_let_const(exarg_T *eap, const bool is_const) argend--; } expr = skipwhite(argend); - if (*expr != '=' && !((vim_strchr("+-*/%.", (uint8_t)(*expr)) != NULL - && expr[1] == '=') || strncmp(expr, "..=", 3) == 0)) { + bool concat = strncmp(expr, "..=", 3) == 0; + bool has_assign = *expr == '=' || (vim_strchr("+-*/%.", (uint8_t)(*expr)) != NULL + && expr[1] == '='); + if (!has_assign && !concat) { // ":let" without "=": list variables if (*arg == '[') { emsg(_(e_invarg)); } else if (!ends_excmd(*arg)) { // ":let var1 var2" - arg = (char *)list_arg_vars(eap, (const char *)arg, &first); + arg = (char *)list_arg_vars(eap, arg, &first); } else if (!eap->skip) { // ":let" list_glob_vars(&first); @@ -227,50 +366,58 @@ static void ex_let_const(exarg_T *eap, const bool is_const) list_vim_vars(&first); } eap->nextcmd = check_nextcmd(arg); - } else if (expr[0] == '=' && expr[1] == '<' && expr[2] == '<') { + return; + } + + if (expr[0] == '=' && expr[1] == '<' && expr[2] == '<') { // HERE document - list_T *l = heredoc_get(eap, expr + 3); + list_T *l = heredoc_get(eap, expr + 3, false); if (l != NULL) { tv_list_set_ret(&rettv, l); if (!eap->skip) { op[0] = '='; op[1] = NUL; - (void)ex_let_vars(eap->arg, &rettv, false, semicolon, var_count, - is_const, (char *)op); + (void)ex_let_vars(eap->arg, &rettv, false, semicolon, var_count, is_const, op); } tv_clear(&rettv); } - } else { - op[0] = '='; - op[1] = NUL; - if (*expr != '=') { - if (vim_strchr("+-*/%.", (uint8_t)(*expr)) != NULL) { - op[0] = *expr; // +=, -=, *=, /=, %= or .= - if (expr[0] == '.' && expr[1] == '.') { // ..= - expr++; - } - } - expr += 2; - } else { - expr += 1; - } + return; + } - expr = skipwhite(expr); + rettv.v_type = VAR_UNKNOWN; - if (eap->skip) { - emsg_skip++; - } - i = eval0(expr, &rettv, &eap->nextcmd, !eap->skip); - if (eap->skip) { - if (i != FAIL) { - tv_clear(&rettv); + op[0] = '='; + op[1] = NUL; + if (*expr != '=') { + if (vim_strchr("+-*/%.", (uint8_t)(*expr)) != NULL) { + op[0] = *expr; // +=, -=, *=, /=, %= or .= + if (expr[0] == '.' && expr[1] == '.') { // ..= + expr++; } - emsg_skip--; - } else if (i != FAIL) { - (void)ex_let_vars(eap->arg, &rettv, false, semicolon, var_count, - is_const, (char *)op); - tv_clear(&rettv); } + expr += 2; + } else { + expr += 1; + } + + expr = skipwhite(expr); + + if (eap->skip) { + emsg_skip++; + } + evalarg_T evalarg; + fill_evalarg_from_eap(&evalarg, eap, eap->skip); + int eval_res = eval0(expr, &rettv, eap, &evalarg); + if (eap->skip) { + emsg_skip--; + } + clear_evalarg(&evalarg, eap); + + if (!eap->skip && eval_res != FAIL) { + (void)ex_let_vars(eap->arg, &rettv, false, semicolon, var_count, is_const, op); + } + if (eval_res != FAIL) { + tv_clear(&rettv); } } @@ -369,15 +516,13 @@ int ex_let_vars(char *arg_start, typval_T *tv, int copy, int semicolon, int var_ /// @return NULL for an error. const char *skip_var_list(const char *arg, int *var_count, int *semicolon) { - const char *p; - const char *s; - if (*arg == '[') { + const char *s; // "[var, var]": find the matching ']'. - p = arg; - for (;;) { + const char *p = arg; + while (true) { p = skipwhite(p + 1); // skip whites after '[', ';' or ',' - s = skip_var_one((char *)p); + s = skip_var_one(p); if (s == p) { semsg(_(e_invarg2), p); return NULL; @@ -400,7 +545,7 @@ const char *skip_var_list(const char *arg, int *var_count, int *semicolon) } return p + 1; } - return skip_var_one((char *)arg); + return skip_var_one(arg); } /// Skip one (assignable) variable name, including @r, $VAR, &option, d.key, @@ -410,8 +555,8 @@ static const char *skip_var_one(const char *arg) if (*arg == '@' && arg[1] != NUL) { return arg + 1 + utfc_ptr2len(arg + 1); } - return (char *)find_name_end(*arg == '$' || *arg == '&' ? arg + 1 : arg, - NULL, NULL, FNE_INCL_BR | FNE_CHECK_START); + return find_name_end(*arg == '$' || *arg == '&' ? arg + 1 : arg, + NULL, NULL, FNE_INCL_BR | FNE_CHECK_START); } /// List variables for hashtab "ht" with prefix "prefix". @@ -432,7 +577,7 @@ void list_hashtable_vars(hashtab_T *ht, const char *prefix, int empty, int *firs // apply :filter /pat/ to variable name xstrlcpy(buf, prefix, IOSIZE); - xstrlcat(buf, (char *)di->di_key, IOSIZE); + xstrlcat(buf, di->di_key, IOSIZE); if (message_filtered(buf)) { continue; } @@ -504,13 +649,12 @@ static const char *list_arg_vars(exarg_T *eap, const char *arg, int *first) if (tofree != NULL) { name = tofree; } - if (get_var_tv(name, len, &tv, NULL, true, false) - == FAIL) { + if (eval_variable(name, len, &tv, NULL, true, false) == FAIL) { error = true; } else { // handle d.key, l[idx], f(expr) const char *const arg_subsc = arg; - if (handle_subscript(&arg, &tv, true, true, name, &name) == FAIL) { + if (handle_subscript(&arg, &tv, &EVALARG_EVALUATE, true) == FAIL) { error = true; } else { if (arg == arg_subsc && len == 2 && name[1] == ':') { @@ -553,12 +697,196 @@ static const char *list_arg_vars(exarg_T *eap, const char *arg, int *first) xfree(tofree); } - arg = (const char *)skipwhite(arg); + arg = skipwhite(arg); } return arg; } +/// Set an environment variable, part of ex_let_one(). +static char *ex_let_env(char *arg, typval_T *const tv, const bool is_const, + const char *const endchars, const char *const op) + FUNC_ATTR_NONNULL_ARG(1, 2) FUNC_ATTR_WARN_UNUSED_RESULT +{ + if (is_const) { + emsg(_("E996: Cannot lock an environment variable")); + return NULL; + } + + // Find the end of the name. + char *arg_end = NULL; + arg++; + char *name = arg; + int len = get_env_len((const char **)&arg); + if (len == 0) { + semsg(_(e_invarg2), name - 1); + } else { + if (op != NULL && vim_strchr("+-*/%", (uint8_t)(*op)) != NULL) { + semsg(_(e_letwrong), op); + } else if (endchars != NULL + && vim_strchr(endchars, (uint8_t)(*skipwhite(arg))) == NULL) { + emsg(_(e_letunexp)); + } else if (!check_secure()) { + char *tofree = NULL; + const char c1 = name[len]; + name[len] = NUL; + const char *p = tv_get_string_chk(tv); + if (p != NULL && op != NULL && *op == '.') { + char *s = vim_getenv(name); + if (s != NULL) { + tofree = concat_str(s, p); + p = tofree; + xfree(s); + } + } + if (p != NULL) { + vim_setenv_ext(name, p); + arg_end = arg; + } + name[len] = c1; + xfree(tofree); + } + } + return arg_end; +} + +/// Set an option, part of ex_let_one(). +static char *ex_let_option(char *arg, typval_T *const tv, const bool is_const, + const char *const endchars, const char *const op) + FUNC_ATTR_NONNULL_ARG(1, 2) FUNC_ATTR_WARN_UNUSED_RESULT +{ + if (is_const) { + emsg(_("E996: Cannot lock an option")); + return NULL; + } + + // Find the end of the name. + char *arg_end = NULL; + int scope; + char *const p = (char *)find_option_end((const char **)&arg, &scope); + if (p == NULL + || (endchars != NULL + && vim_strchr(endchars, (uint8_t)(*skipwhite(p))) == NULL)) { + emsg(_(e_letunexp)); + return NULL; + } + + const char c1 = *p; + *p = NUL; + + uint32_t opt_p_flags; + bool hidden; + OptVal curval = get_option_value(arg, &opt_p_flags, scope, &hidden); + OptVal newval = NIL_OPTVAL; + if (curval.type == kOptValTypeNil && arg[0] != 't' && arg[1] != '_') { + semsg(_(e_unknown_option2), arg); + goto theend; + } + if (op != NULL && *op != '=' + && ((curval.type != kOptValTypeString && *op == '.') + || (curval.type == kOptValTypeString && *op != '.'))) { + semsg(_(e_letwrong), op); + goto theend; + } + + bool error; + newval = tv_to_optval(tv, arg, opt_p_flags, &error); + if (error) { + goto theend; + } + + // Don't assume current and new values are of the same type in order to future-proof the code for + // when an option can have multiple types. + const bool is_num = ((curval.type == kOptValTypeNumber || curval.type == kOptValTypeBoolean) + && (newval.type == kOptValTypeNumber || newval.type == kOptValTypeBoolean)); + const bool is_string = curval.type == kOptValTypeString && newval.type == kOptValTypeString; + + if (op != NULL && *op != '=') { + if (!hidden && is_num) { // number or bool + OptInt cur_n = curval.type == kOptValTypeNumber ? curval.data.number : curval.data.boolean; + OptInt new_n = newval.type == kOptValTypeNumber ? newval.data.number : newval.data.boolean; + + switch (*op) { + case '+': + new_n = cur_n + new_n; break; + case '-': + new_n = cur_n - new_n; break; + case '*': + new_n = cur_n * new_n; break; + case '/': + new_n = num_divide(cur_n, new_n); break; + case '%': + new_n = num_modulus(cur_n, new_n); break; + } + + if (curval.type == kOptValTypeNumber) { + newval = NUMBER_OPTVAL(new_n); + } else { + newval = BOOLEAN_OPTVAL(TRISTATE_FROM_INT(new_n)); + } + } else if (!hidden && is_string + && curval.data.string.data != NULL && newval.data.string.data != NULL) { // string + OptVal newval_old = newval; + newval = CSTR_AS_OPTVAL(concat_str(curval.data.string.data, newval.data.string.data)); + optval_free(newval_old); + } + } + + const char *err = set_option_value(arg, newval, scope); + arg_end = p; + if (err != NULL) { + emsg(_(err)); + } + +theend: + *p = c1; + optval_free(curval); + optval_free(newval); + return arg_end; +} + +/// Set a register, part of ex_let_one(). +static char *ex_let_register(char *arg, typval_T *const tv, const bool is_const, + const char *const endchars, const char *const op) + FUNC_ATTR_NONNULL_ARG(1, 2) FUNC_ATTR_WARN_UNUSED_RESULT +{ + if (is_const) { + emsg(_("E996: Cannot lock a register")); + return NULL; + } + + char *arg_end = NULL; + arg++; + + int regname = utf_ptr2char(arg); + int mblen = utf_ptr2len(arg); + + if (op != NULL && vim_strchr("+-*/%", (uint8_t)(*op)) != NULL) { + semsg(_(e_letwrong), op); + } else if (endchars != NULL + && vim_strchr(endchars, (uint8_t)(*skipwhite(arg + mblen))) == NULL) { + emsg(_(e_letunexp)); + } else { + char *ptofree = NULL; + const char *p = tv_get_string_chk(tv); + if (p != NULL && op != NULL && *op == '.') { + char *s = get_reg_contents(*arg == '@' ? '"' : regname, kGRegExprSrc); + if (s != NULL) { + ptofree = concat_str(s, p); + p = ptofree; + xfree(s); + } + } + if (p != NULL) { + write_reg_contents(*arg == '@' ? '"' : regname, + p, (ssize_t)strlen(p), false); + arg_end = arg + mblen; + } + xfree(ptofree); + } + return arg_end; +} + /// Set one item of `:let var = expr` or `:let [v1, v2] = list` to its value /// /// @param[in] arg Start of the variable name. @@ -575,181 +903,22 @@ static char *ex_let_one(char *arg, typval_T *const tv, const bool copy, const bo FUNC_ATTR_NONNULL_ARG(1, 2) FUNC_ATTR_WARN_UNUSED_RESULT { char *arg_end = NULL; - int len; - // ":let $VAR = expr": Set environment variable. if (*arg == '$') { - if (is_const) { - emsg(_("E996: Cannot lock an environment variable")); - return NULL; - } - // Find the end of the name. - arg++; - char *name = arg; - len = get_env_len((const char **)&arg); - if (len == 0) { - semsg(_(e_invarg2), name - 1); - } else { - if (op != NULL && vim_strchr("+-*/%", (uint8_t)(*op)) != NULL) { - semsg(_(e_letwrong), op); - } else if (endchars != NULL - && vim_strchr(endchars, (uint8_t)(*skipwhite(arg))) == NULL) { - emsg(_(e_letunexp)); - } else if (!check_secure()) { - char *tofree = NULL; - const char c1 = name[len]; - name[len] = NUL; - const char *p = tv_get_string_chk(tv); - if (p != NULL && op != NULL && *op == '.') { - char *s = vim_getenv(name); - if (s != NULL) { - tofree = concat_str(s, p); - p = (const char *)tofree; - xfree(s); - } - } - if (p != NULL) { - vim_setenv_ext(name, p); - arg_end = arg; - } - name[len] = c1; - xfree(tofree); - } - } + // ":let $VAR = expr": Set environment variable. + return ex_let_env(arg, tv, is_const, endchars, op); + } else if (*arg == '&') { // ":let &option = expr": Set option value. // ":let &l:option = expr": Set local option value. // ":let &g:option = expr": Set global option value. - } else if (*arg == '&') { - if (is_const) { - emsg(_("E996: Cannot lock an option")); - return NULL; - } - // Find the end of the name. - int scope; - char *const p = (char *)find_option_end((const char **)&arg, &scope); - if (p == NULL - || (endchars != NULL - && vim_strchr(endchars, (uint8_t)(*skipwhite(p))) == NULL)) { - emsg(_(e_letunexp)); - } else { - varnumber_T n = 0; - getoption_T opt_type; - long numval; - char *stringval = NULL; - const char *s = NULL; - bool failed = false; - uint32_t opt_p_flags; - char *tofree = NULL; - - const char c1 = *p; - *p = NUL; - - opt_type = get_option_value(arg, &numval, &stringval, &opt_p_flags, scope); - if (opt_type == gov_bool - || opt_type == gov_number - || opt_type == gov_hidden_bool - || opt_type == gov_hidden_number) { - // number, possibly hidden - n = (long)tv_get_number(tv); - } - - if ((opt_p_flags & P_FUNC) && tv_is_func(*tv)) { - // If the option can be set to a function reference or a lambda - // and the passed value is a function reference, then convert it to - // the name (string) of the function reference. - s = tofree = encode_tv2string(tv, NULL); - } else if (tv->v_type != VAR_BOOL && tv->v_type != VAR_SPECIAL) { - // Avoid setting a string option to the text "v:false" or similar. - s = tv_get_string_chk(tv); - } - - if (op != NULL && *op != '=') { - if (((opt_type == gov_bool || opt_type == gov_number) && *op == '.') - || (opt_type == gov_string && *op != '.')) { - semsg(_(e_letwrong), op); - failed = true; // don't set the value - } else { - // number or bool - if (opt_type == gov_number || opt_type == gov_bool) { - switch (*op) { - case '+': - n = numval + n; break; - case '-': - n = numval - n; break; - case '*': - n = numval * n; break; - case '/': - n = num_divide(numval, n); break; - case '%': - n = num_modulus(numval, n); break; - } - s = NULL; - } else if (opt_type == gov_string && stringval != NULL && s != NULL) { - // string - char *const oldstringval = stringval; - stringval = concat_str(stringval, s); - xfree(oldstringval); - s = stringval; - } - } - } - - if (!failed) { - if (opt_type != gov_string || s != NULL) { - char *err = set_option_value(arg, n, s, scope); - arg_end = p; - if (err != NULL) { - emsg(_(err)); - } - } else { - emsg(_(e_stringreq)); - } - } - *p = c1; - xfree(stringval); - xfree(tofree); - } - // ":let @r = expr": Set register contents. + return ex_let_option(arg, tv, is_const, endchars, op); } else if (*arg == '@') { - if (is_const) { - emsg(_("E996: Cannot lock a register")); - return NULL; - } - arg++; - - int regname = utf_ptr2char(arg); - int mblen = utf_ptr2len(arg); - - if (op != NULL && vim_strchr("+-*/%", (uint8_t)(*op)) != NULL) { - semsg(_(e_letwrong), op); - } else if (endchars != NULL - && vim_strchr(endchars, (uint8_t)(*skipwhite(arg + mblen))) == NULL) { - emsg(_(e_letunexp)); - } else { - char *s; - - char *ptofree = NULL; - const char *p = tv_get_string_chk(tv); - if (p != NULL && op != NULL && *op == '.') { - s = get_reg_contents(*arg == '@' ? '"' : regname, kGRegExprSrc); - if (s != NULL) { - ptofree = concat_str(s, p); - p = (const char *)ptofree; - xfree(s); - } - } - if (p != NULL) { - write_reg_contents(*arg == '@' ? '"' : regname, - p, (ssize_t)strlen(p), false); - arg_end = arg + mblen; - } - xfree(ptofree); - } + // ":let @r = expr": Set register contents. + return ex_let_register(arg, tv, is_const, endchars, op); + } else if (eval_isnamec1(*arg) || *arg == '{') { // ":let var = expr": Set internal variable. // ":let {expr} = expr": Idem, name made with curly braces - } else if (eval_isnamec1(*arg) || *arg == '{') { lval_T lv; - char *const p = get_lval(arg, tv, &lv, false, false, 0, FNE_CHECK_START); if (p != NULL && lv.ll_name != NULL) { if (endchars != NULL && vim_strchr(endchars, (uint8_t)(*skipwhite(p))) == NULL) { @@ -808,7 +977,7 @@ static void ex_unletlock(exarg_T *eap, char *argstart, int deep, ex_unletlock_ca do { if (*arg == '$') { - lv.ll_name = (const char *)arg; + lv.ll_name = arg; lv.ll_tv = NULL; arg++; if (get_env_len((const char **)&arg) == 0) { @@ -866,10 +1035,9 @@ static int do_unlet_var(lval_T *lp, char *name_end, exarg_T *eap, int deep FUNC_ { int forceit = eap->forceit; int ret = OK; - int cc; if (lp->ll_tv == NULL) { - cc = (uint8_t)(*name_end); + int cc = (uint8_t)(*name_end); *name_end = NUL; // Environment variable, normal name or expanded name. @@ -891,57 +1059,57 @@ static int do_unlet_var(lval_T *lp, char *name_end, exarg_T *eap, int deep FUNC_ lp->ll_name_len))) { return FAIL; } else if (lp->ll_range) { - assert(lp->ll_list != NULL); - // Delete a range of List items. - listitem_T *const first_li = lp->ll_li; - listitem_T *last_li = first_li; - for (;;) { - listitem_T *const li = TV_LIST_ITEM_NEXT(lp->ll_list, lp->ll_li); - if (value_check_lock(TV_LIST_ITEM_TV(lp->ll_li)->v_lock, - lp->ll_name, - lp->ll_name_len)) { - return false; - } - lp->ll_li = li; - lp->ll_n1++; - if (lp->ll_li == NULL || (!lp->ll_empty2 && lp->ll_n2 < lp->ll_n1)) { - break; - } - last_li = lp->ll_li; - } - tv_list_remove_items(lp->ll_list, first_li, last_li); + tv_list_unlet_range(lp->ll_list, lp->ll_li, lp->ll_n1, !lp->ll_empty2, lp->ll_n2); + } else if (lp->ll_list != NULL) { + // unlet a List item. + tv_list_item_remove(lp->ll_list, lp->ll_li); } else { - if (lp->ll_list != NULL) { - // unlet a List item. - tv_list_item_remove(lp->ll_list, lp->ll_li); - } else { - // unlet a Dictionary item. - dict_T *d = lp->ll_dict; - assert(d != NULL); - dictitem_T *di = lp->ll_di; - bool watched = tv_dict_is_watched(d); - char *key = NULL; - typval_T oldtv; + // unlet a Dictionary item. + dict_T *d = lp->ll_dict; + assert(d != NULL); + dictitem_T *di = lp->ll_di; + bool watched = tv_dict_is_watched(d); + char *key = NULL; + typval_T oldtv; - if (watched) { - tv_copy(&di->di_tv, &oldtv); - // need to save key because dictitem_remove will free it - key = xstrdup((char *)di->di_key); - } + if (watched) { + tv_copy(&di->di_tv, &oldtv); + // need to save key because dictitem_remove will free it + key = xstrdup(di->di_key); + } - tv_dict_item_remove(d, di); + tv_dict_item_remove(d, di); - if (watched) { - tv_dict_watcher_notify(d, key, NULL, &oldtv); - tv_clear(&oldtv); - xfree(key); - } + if (watched) { + tv_dict_watcher_notify(d, key, NULL, &oldtv); + tv_clear(&oldtv); + xfree(key); } } return ret; } +/// Unlet one item or a range of items from a list. +/// Return OK or FAIL. +static void tv_list_unlet_range(list_T *const l, listitem_T *const li_first, const int n1_arg, + const bool has_n2, const int n2) +{ + assert(l != NULL); + // Delete a range of List items. + listitem_T *li_last = li_first; + int n1 = n1_arg; + while (true) { + listitem_T *const li = TV_LIST_ITEM_NEXT(l, li_last); + n1++; + if (li == NULL || (has_n2 && n2 < n1)) { + break; + } + li_last = li; + } + tv_list_remove_items(l, li_first, li_last); +} + /// unlet a variable /// /// @param[in] name Variable name to unlet. @@ -1086,8 +1254,8 @@ static int do_lock_var(lval_T *lp, char *name_end FUNC_ATTR_UNUSED, exarg_T *eap /// @param dip non-NULL when typval's dict item is needed /// @param verbose may give error message /// @param no_autoload do not use script autoloading -int get_var_tv(const char *name, int len, typval_T *rettv, dictitem_T **dip, bool verbose, - bool no_autoload) +int eval_variable(const char *name, int len, typval_T *rettv, dictitem_T **dip, bool verbose, + bool no_autoload) { int ret = OK; typval_T *tv = NULL; @@ -1162,7 +1330,7 @@ void vars_clear_ext(hashtab_T *ht, int free_val) } } hash_clear(ht); - ht->ht_used = 0; + hash_init(ht); } /// Delete a variable from hashtab "ht" at item "hi". @@ -1180,7 +1348,7 @@ void delete_var(hashtab_T *ht, hashitem_T *hi) static void list_one_var(dictitem_T *v, const char *prefix, int *first) { char *const s = encode_tv2echo(&v->di_tv, NULL); - list_one_var_a(prefix, (const char *)v->di_key, (ptrdiff_t)strlen((char *)v->di_key), + list_one_var_a(prefix, v->di_key, (ptrdiff_t)strlen(v->di_key), v->di_tv.v_type, (s == NULL ? "" : s), first); xfree(s); } @@ -1191,11 +1359,11 @@ static void list_one_var(dictitem_T *v, const char *prefix, int *first) static void list_one_var_a(const char *prefix, const char *name, const ptrdiff_t name_len, const VarType type, const char *string, int *first) { - // don't use msg() or msg_attr() to avoid overwriting "v:statusmsg" + // don't use msg() to avoid overwriting "v:statusmsg" msg_start(); msg_puts(prefix); if (name != NULL) { // "a:" vars don't have a name stored - msg_puts_attr_len(name, name_len, 0); + msg_puts_len(name, name_len, 0); } msg_putchar(' '); msg_advance(22); @@ -1217,7 +1385,7 @@ static void list_one_var_a(const char *prefix, const char *name, const ptrdiff_t msg_putchar(' '); } - msg_outtrans((char *)string); + msg_outtrans(string, 0); if (type == VAR_FUNC || type == VAR_PARTIAL) { msg_puts("()"); @@ -1228,6 +1396,62 @@ static void list_one_var_a(const char *prefix, const char *name, const ptrdiff_t } } +/// Additional handling for setting a v: variable. +/// +/// @return true if the variable should be set normally, +/// false if nothing else needs to be done. +bool before_set_vvar(const char *const varname, dictitem_T *const di, typval_T *const tv, + const bool copy, const bool watched, bool *const type_error) +{ + if (di->di_tv.v_type == VAR_STRING) { + typval_T oldtv = TV_INITIAL_VALUE; + if (watched) { + tv_copy(&di->di_tv, &oldtv); + } + XFREE_CLEAR(di->di_tv.vval.v_string); + if (copy || tv->v_type != VAR_STRING) { + const char *const val = tv_get_string(tv); + // Careful: when assigning to v:errmsg and tv_get_string() + // causes an error message the variable will already be set. + if (di->di_tv.vval.v_string == NULL) { + di->di_tv.vval.v_string = xstrdup(val); + } + } else { + // Take over the string to avoid an extra alloc/free. + di->di_tv.vval.v_string = tv->vval.v_string; + tv->vval.v_string = NULL; + } + // Notify watchers + if (watched) { + tv_dict_watcher_notify(&vimvardict, varname, &di->di_tv, &oldtv); + tv_clear(&oldtv); + } + return false; + } else if (di->di_tv.v_type == VAR_NUMBER) { + typval_T oldtv = TV_INITIAL_VALUE; + if (watched) { + tv_copy(&di->di_tv, &oldtv); + } + di->di_tv.vval.v_number = tv_get_number(tv); + if (strcmp(varname, "searchforward") == 0) { + set_search_direction(di->di_tv.vval.v_number ? '/' : '?'); + } else if (strcmp(varname, "hlsearch") == 0) { + no_hlsearch = !di->di_tv.vval.v_number; + redraw_all_later(UPD_SOME_VALID); + } + // Notify watchers + if (watched) { + tv_dict_watcher_notify(&vimvardict, varname, &di->di_tv, &oldtv); + tv_clear(&oldtv); + } + return false; + } else if (di->di_tv.v_type != tv->v_type) { + *type_error = true; + return false; + } + return true; +} + /// Set variable to the given value /// /// If the variable already exists, the value is updated. Otherwise the variable @@ -1257,31 +1481,29 @@ void set_var_const(const char *name, const size_t name_len, typval_T *const tv, const bool is_const) FUNC_ATTR_NONNULL_ALL { - dictitem_T *v; - hashtab_T *ht; - dict_T *dict; - const char *varname; - ht = find_var_ht_dict(name, name_len, &varname, &dict); + dict_T *dict; + hashtab_T *ht = find_var_ht_dict(name, name_len, &varname, &dict); const bool watched = tv_dict_is_watched(dict); if (ht == NULL || *varname == NUL) { semsg(_(e_illvar), name); return; } - v = find_var_in_ht(ht, 0, varname, name_len - (size_t)(varname - name), true); + const size_t varname_len = name_len - (size_t)(varname - name); + dictitem_T *di = find_var_in_ht(ht, 0, varname, varname_len, true); // Search in parent scope which is possible to reference from lambda - if (v == NULL) { - v = find_var_in_scoped_ht(name, name_len, true); + if (di == NULL) { + di = find_var_in_scoped_ht(name, name_len, true); } - if (tv_is_func(*tv) && var_wrong_func_name(name, v == NULL)) { + if (tv_is_func(*tv) && var_wrong_func_name(name, di == NULL)) { return; } typval_T oldtv = TV_INITIAL_VALUE; - if (v != NULL) { + if (di != NULL) { if (is_const) { emsg(_(e_cannot_mod)); return; @@ -1291,9 +1513,9 @@ void set_var_const(const char *name, const size_t name_len, typval_T *const tv, // - Whether the variable is read-only // - Whether the variable value is locked // - Whether the variable is locked - if (var_check_ro(v->di_flags, name, name_len) - || value_check_lock(v->di_tv.v_lock, name, name_len) - || var_check_lock(v->di_flags, name, name_len)) { + if (var_check_ro(di->di_flags, name, name_len) + || value_check_lock(di->di_tv.v_lock, name, name_len) + || var_check_lock(di->di_flags, name, name_len)) { return; } @@ -1301,42 +1523,19 @@ void set_var_const(const char *name, const size_t name_len, typval_T *const tv, // Handle setting internal v: variables separately where needed to // prevent changing the type. - if (is_vimvarht(ht)) { - if (v->di_tv.v_type == VAR_STRING) { - XFREE_CLEAR(v->di_tv.vval.v_string); - if (copy || tv->v_type != VAR_STRING) { - const char *const val = tv_get_string(tv); - - // Careful: when assigning to v:errmsg and tv_get_string() - // causes an error message the variable will already be set. - if (v->di_tv.vval.v_string == NULL) { - v->di_tv.vval.v_string = xstrdup(val); - } - } else { - // Take over the string to avoid an extra alloc/free. - v->di_tv.vval.v_string = tv->vval.v_string; - tv->vval.v_string = NULL; - } - return; - } else if (v->di_tv.v_type == VAR_NUMBER) { - v->di_tv.vval.v_number = tv_get_number(tv); - if (strcmp(varname, "searchforward") == 0) { - set_search_direction(v->di_tv.vval.v_number ? '/' : '?'); - } else if (strcmp(varname, "hlsearch") == 0) { - no_hlsearch = !v->di_tv.vval.v_number; - redraw_all_later(UPD_SOME_VALID); - } - return; - } else if (v->di_tv.v_type != tv->v_type) { - semsg(_("E963: setting %s to value with wrong type"), name); - return; + bool type_error = false; + if (is_vimvarht(ht) + && !before_set_vvar(varname, di, tv, copy, watched, &type_error)) { + if (type_error) { + semsg(_(e_setting_v_str_to_value_with_wrong_type), varname); } + return; } if (watched) { - tv_copy(&v->di_tv, &oldtv); + tv_copy(&di->di_tv, &oldtv); } - tv_clear(&v->di_tv); + tv_clear(&di->di_tv); } else { // Add a new variable. // Can't add "v:" or "a:" variable. if (is_vimvarht(ht) || ht == get_funccal_args_ht()) { @@ -1352,28 +1551,28 @@ void set_var_const(const char *name, const size_t name_len, typval_T *const tv, // Make sure dict is valid assert(dict != NULL); - v = xmalloc(sizeof(dictitem_T) + strlen(varname)); - STRCPY(v->di_key, varname); - if (hash_add(ht, (char *)v->di_key) == FAIL) { - xfree(v); + di = xmalloc(offsetof(dictitem_T, di_key) + varname_len + 1); + memcpy(di->di_key, varname, varname_len + 1); + if (hash_add(ht, di->di_key) == FAIL) { + xfree(di); return; } - v->di_flags = DI_FLAGS_ALLOC; + di->di_flags = DI_FLAGS_ALLOC; if (is_const) { - v->di_flags |= DI_FLAGS_LOCK; + di->di_flags |= DI_FLAGS_LOCK; } } if (copy || tv->v_type == VAR_NUMBER || tv->v_type == VAR_FLOAT) { - tv_copy(tv, &v->di_tv); + tv_copy(tv, &di->di_tv); } else { - v->di_tv = *tv; - v->di_tv.v_lock = VAR_UNLOCKED; + di->di_tv = *tv; + di->di_tv.v_lock = VAR_UNLOCKED; tv_init(tv); } if (watched) { - tv_dict_watcher_notify(dict, (char *)v->di_key, &v->di_tv, &oldtv); + tv_dict_watcher_notify(dict, di->di_key, &di->di_tv, &oldtv); tv_clear(&oldtv); } @@ -1381,7 +1580,7 @@ void set_var_const(const char *name, const size_t name_len, typval_T *const tv, // Like :lockvar! name: lock the value and what it contains, but only // if the reference count is up to one. That locks only literal // values. - tv_item_lock(&v->di_tv, DICT_MAXNEST, true, true); + tv_item_lock(&di->di_tv, DICT_MAXNEST, true, true); } } @@ -1574,7 +1773,7 @@ static void get_var_from(const char *varname, typval_T *rettv, typval_T *deftv, tv_dict_set_ret(rettv, opts); done = true; } - } else if (get_option_tv(&varname, rettv, true) == OK) { + } else if (eval_option(&varname, rettv, true) == OK) { // Local option done = true; } @@ -1644,28 +1843,121 @@ static void getwinvar(typval_T *argvars, typval_T *rettv, int off) get_var_from(varname, rettv, &argvars[off + 2], 'w', tp, win, NULL); } -/// Set option "varname" to the value of "varp" for the current buffer/window. -static void set_option_from_tv(const char *varname, typval_T *varp) +/// Convert typval to option value for a particular option. +/// +/// @param[in] tv typval to convert. +/// @param[in] option Option name. +/// @param[in] flags Option flags. +/// @param[out] error Whether an error occurred. +/// +/// @return Typval converted to OptVal. Must be freed by caller. +/// Returns NIL_OPTVAL for invalid option name. +static OptVal tv_to_optval(typval_T *tv, const char *option, uint32_t flags, bool *error) { - long numval = 0; - const char *strval; - bool error = false; + OptVal value = NIL_OPTVAL; char nbuf[NUMBUFLEN]; - - if (varp->v_type == VAR_BOOL) { - if (is_string_option(varname)) { + bool err = false; + + if ((flags & P_FUNC) && tv_is_func(*tv)) { + // If the option can be set to a function reference or a lambda + // and the passed value is a function reference, then convert it to + // the name (string) of the function reference. + char *strval = encode_tv2string(tv, NULL); + err = strval == NULL; + value = CSTR_AS_OPTVAL(strval); + } else if (flags & (P_NUM | P_BOOL)) { + varnumber_T n = (flags & P_NUM) ? tv_get_number_chk(tv, &err) + : tv_get_bool_chk(tv, &err); + // This could be either "0" or a string that's not a number. + // So we need to check if it's actually a number. + if (!err && tv->v_type == VAR_STRING && n == 0) { + unsigned idx; + for (idx = 0; tv->vval.v_string[idx] == '0'; idx++) {} + if (tv->vval.v_string[idx] != NUL || idx == 0) { + // There's another character after zeros or the string is empty. + // In both cases, we are trying to set a num option using a string. + err = true; + semsg(_("E521: Number required: &%s = '%s'"), option, tv->vval.v_string); + } + } + value = (flags & P_NUM) ? NUMBER_OPTVAL((OptInt)n) : BOOLEAN_OPTVAL(TRISTATE_FROM_INT(n)); + } else if ((flags & P_STRING) || is_tty_option(option)) { + // Avoid setting string option to a boolean or a special value. + if (tv->v_type != VAR_BOOL && tv->v_type != VAR_SPECIAL) { + const char *strval = tv_get_string_buf_chk(tv, nbuf); + err = strval == NULL; + value = CSTR_TO_OPTVAL(strval); + } else if (flags & P_STRING) { + err = true; emsg(_(e_stringreq)); - return; } - numval = (long)varp->vval.v_number; - strval = "0"; // avoid using "false" } else { - numval = (long)tv_get_number_chk(varp, &error); - strval = tv_get_string_buf_chk(varp, nbuf); + abort(); // This should never happen. } - if (!error && strval != NULL) { - set_option_value_give_err(varname, numval, strval, OPT_LOCAL); + + if (error != NULL) { + *error = err; } + return value; +} + +/// Convert an option value to typval. +/// +/// @param[in] value Option value to convert. +/// +/// @return OptVal converted to typval. +typval_T optval_as_tv(OptVal value) +{ + typval_T rettv = { .v_type = VAR_SPECIAL, .vval = { .v_special = kSpecialVarNull } }; + + switch (value.type) { + case kOptValTypeNil: + break; + case kOptValTypeBoolean: + switch (value.data.boolean) { + case kTrue: + rettv.v_type = VAR_BOOL; + rettv.vval.v_bool = kBoolVarTrue; + break; + case kFalse: + rettv.v_type = VAR_BOOL; + rettv.vval.v_bool = kBoolVarFalse; + break; + case kNone: + break; // return v:null for None boolean value + } + break; + case kOptValTypeNumber: + rettv.v_type = VAR_NUMBER; + rettv.vval.v_number = value.data.number; + break; + case kOptValTypeString: + rettv.v_type = VAR_STRING; + rettv.vval.v_string = value.data.string.data; + break; + } + + return rettv; +} + +/// Set option "varname" to the value of "varp" for the current buffer/window. +static void set_option_from_tv(const char *varname, typval_T *varp) +{ + int opt_idx = findoption(varname); + if (opt_idx < 0) { + semsg(_(e_unknown_option2), varname); + return; + } + uint32_t opt_p_flags = get_option(opt_idx)->flags; + + bool error = false; + OptVal value = tv_to_optval(varp, varname, opt_p_flags, &error); + + if (!error) { + set_option_value_give_err(varname, value, OPT_LOCAL); + } + + optval_free(value); } /// "setwinvar()" and "settabwinvar()" functions @@ -1723,10 +2015,10 @@ bool var_exists(const char *var) if (tofree != NULL) { name = tofree; } - n = get_var_tv(name, len, &tv, NULL, false, true) == OK; + n = eval_variable(name, len, &tv, NULL, false, true) == OK; if (n) { // Handle d.key, l[idx], f(expr). - n = handle_subscript(&var, &tv, true, false, name, &name) == OK; + n = handle_subscript(&var, &tv, &EVALARG_EVALUATE, false) == OK; if (n) { tv_clear(&tv); } diff --git a/src/nvim/eval/vars.h b/src/nvim/eval/vars.h index b87c9d62cb..6ddf449ff1 100644 --- a/src/nvim/eval/vars.h +++ b/src/nvim/eval/vars.h @@ -1,9 +1,13 @@ -#ifndef NVIM_EVAL_VARS_H -#define NVIM_EVAL_VARS_H +#pragma once -#include "nvim/ex_cmds_defs.h" +#include <stddef.h> // IWYU pragma: keep + +#include "nvim/ex_cmds_defs.h" // IWYU pragma: keep +#include "nvim/garray_defs.h" // IWYU pragma: keep +#include "nvim/hashtab_defs.h" // IWYU pragma: keep +#include "nvim/option_defs.h" // IWYU pragma: keep +#include "nvim/types_defs.h" // IWYU pragma: keep #ifdef INCLUDE_GENERATED_DECLARATIONS # include "eval/vars.h.generated.h" #endif -#endif // NVIM_EVAL_VARS_H diff --git a/src/nvim/eval/window.c b/src/nvim/eval/window.c index f58a0c488a..e0abbad477 100644 --- a/src/nvim/eval/window.c +++ b/src/nvim/eval/window.c @@ -1,6 +1,3 @@ -// This is an open source non-commercial project. Dear PVS-Studio, please check -// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com - // eval/window.c: Window related builtin functions #include <stdbool.h> @@ -9,7 +6,7 @@ #include <stdlib.h> #include <string.h> -#include "nvim/ascii.h" +#include "nvim/ascii_defs.h" #include "nvim/autocmd.h" #include "nvim/buffer.h" #include "nvim/buffer_defs.h" @@ -21,23 +18,22 @@ #include "nvim/garray.h" #include "nvim/gettext.h" #include "nvim/globals.h" -#include "nvim/macros.h" -#include "nvim/memline_defs.h" +#include "nvim/macros_defs.h" #include "nvim/memory.h" #include "nvim/message.h" #include "nvim/move.h" -#include "nvim/option_defs.h" -#include "nvim/pos.h" -#include "nvim/types.h" -#include "nvim/vim.h" +#include "nvim/pos_defs.h" +#include "nvim/types_defs.h" +#include "nvim/vim_defs.h" #include "nvim/window.h" +#include "nvim/winfloat.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "eval/window.c.generated.h" #endif -static char *e_invalwindow = N_("E957: Invalid window number"); -static char e_cannot_resize_window_in_another_tab_page[] +static const char *e_invalwindow = N_("E957: Invalid window number"); +static const char e_cannot_resize_window_in_another_tab_page[] = N_("E1308: Cannot resize a window in another tab page"); static int win_getid(typval_T *argvars) @@ -98,6 +94,7 @@ win_T *win_id2wp(int id) } /// Return the window and tab pointer of window "id". +/// Returns NULL when not found. win_T *win_id2wp_tp(int id, tabpage_T **tpp) { FOR_ALL_TAB_WINDOWS(tp, wp) { @@ -192,9 +189,9 @@ win_T *find_tabwin(typval_T *wvp, typval_T *tvp) if (wvp->v_type != VAR_UNKNOWN) { if (tvp->v_type != VAR_UNKNOWN) { - long n = tv_get_number(tvp); + int n = (int)tv_get_number(tvp); if (n >= 0) { - tp = find_tabpage((int)n); + tp = find_tabpage(n); } } else { tp = curtab; @@ -265,7 +262,7 @@ static int get_winnr(tabpage_T *tp, typval_T *argvar) } else { // Extract the window count (if specified). e.g. winnr('3j') char *endp; - long count = strtol((char *)arg, &endp, 10); + int count = (int)strtol(arg, &endp, 10); if (count <= 0) { // if count is not specified, default to 1 count = 1; @@ -639,7 +636,7 @@ void f_win_splitmove(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) if (wp == NULL || targetwin == NULL || wp == targetwin || !win_valid(wp) || !win_valid(targetwin) - || win_valid_floating(wp) || win_valid_floating(targetwin)) { + || win_float_valid(wp) || win_float_valid(targetwin)) { emsg(_(e_invalwindow)); rettv->vval.v_number = -1; return; @@ -651,8 +648,7 @@ void f_win_splitmove(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) dict_T *d; dictitem_T *di; - if (argvars[2].v_type != VAR_DICT || argvars[2].vval.v_dict == NULL) { - emsg(_(e_invarg)); + if (tv_check_for_nonnull_dict_arg(argvars, 2) == FAIL) { return; } @@ -796,51 +792,50 @@ void f_winrestcmd(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) /// "winrestview()" function void f_winrestview(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - dict_T *dict = argvars[0].vval.v_dict; + if (tv_check_for_nonnull_dict_arg(argvars, 0) == FAIL) { + return; + } - if (argvars[0].v_type != VAR_DICT || dict == NULL) { - emsg(_(e_invarg)); - } else { - dictitem_T *di; - if ((di = tv_dict_find(dict, S_LEN("lnum"))) != NULL) { - curwin->w_cursor.lnum = (linenr_T)tv_get_number(&di->di_tv); - } - if ((di = tv_dict_find(dict, S_LEN("col"))) != NULL) { - curwin->w_cursor.col = (colnr_T)tv_get_number(&di->di_tv); - } - if ((di = tv_dict_find(dict, S_LEN("coladd"))) != NULL) { - curwin->w_cursor.coladd = (colnr_T)tv_get_number(&di->di_tv); - } - if ((di = tv_dict_find(dict, S_LEN("curswant"))) != NULL) { - curwin->w_curswant = (colnr_T)tv_get_number(&di->di_tv); - curwin->w_set_curswant = false; - } - if ((di = tv_dict_find(dict, S_LEN("topline"))) != NULL) { - set_topline(curwin, (linenr_T)tv_get_number(&di->di_tv)); - } - if ((di = tv_dict_find(dict, S_LEN("topfill"))) != NULL) { - curwin->w_topfill = (int)tv_get_number(&di->di_tv); - } - if ((di = tv_dict_find(dict, S_LEN("leftcol"))) != NULL) { - curwin->w_leftcol = (colnr_T)tv_get_number(&di->di_tv); - } - if ((di = tv_dict_find(dict, S_LEN("skipcol"))) != NULL) { - curwin->w_skipcol = (colnr_T)tv_get_number(&di->di_tv); - } + dict_T *dict = argvars[0].vval.v_dict; + dictitem_T *di; + if ((di = tv_dict_find(dict, S_LEN("lnum"))) != NULL) { + curwin->w_cursor.lnum = (linenr_T)tv_get_number(&di->di_tv); + } + if ((di = tv_dict_find(dict, S_LEN("col"))) != NULL) { + curwin->w_cursor.col = (colnr_T)tv_get_number(&di->di_tv); + } + if ((di = tv_dict_find(dict, S_LEN("coladd"))) != NULL) { + curwin->w_cursor.coladd = (colnr_T)tv_get_number(&di->di_tv); + } + if ((di = tv_dict_find(dict, S_LEN("curswant"))) != NULL) { + curwin->w_curswant = (colnr_T)tv_get_number(&di->di_tv); + curwin->w_set_curswant = false; + } + if ((di = tv_dict_find(dict, S_LEN("topline"))) != NULL) { + set_topline(curwin, (linenr_T)tv_get_number(&di->di_tv)); + } + if ((di = tv_dict_find(dict, S_LEN("topfill"))) != NULL) { + curwin->w_topfill = (int)tv_get_number(&di->di_tv); + } + if ((di = tv_dict_find(dict, S_LEN("leftcol"))) != NULL) { + curwin->w_leftcol = (colnr_T)tv_get_number(&di->di_tv); + } + if ((di = tv_dict_find(dict, S_LEN("skipcol"))) != NULL) { + curwin->w_skipcol = (colnr_T)tv_get_number(&di->di_tv); + } - check_cursor(); - win_new_height(curwin, curwin->w_height); - win_new_width(curwin, curwin->w_width); - changed_window_setting(); + check_cursor(); + win_new_height(curwin, curwin->w_height); + win_new_width(curwin, curwin->w_width); + changed_window_setting(); - if (curwin->w_topline <= 0) { - curwin->w_topline = 1; - } - if (curwin->w_topline > curbuf->b_ml.ml_line_count) { - curwin->w_topline = curbuf->b_ml.ml_line_count; - } - check_topfill(curwin, true); + if (curwin->w_topline <= 0) { + curwin->w_topline = 1; + } + if (curwin->w_topline > curbuf->b_ml.ml_line_count) { + curwin->w_topline = curbuf->b_ml.ml_line_count; } + check_topfill(curwin, true); } /// "winsaveview()" function diff --git a/src/nvim/eval/window.h b/src/nvim/eval/window.h index 995f0a55a9..ed879c895a 100644 --- a/src/nvim/eval/window.h +++ b/src/nvim/eval/window.h @@ -1,5 +1,4 @@ -#ifndef NVIM_EVAL_WINDOW_H -#define NVIM_EVAL_WINDOW_H +#pragma once #include <stdbool.h> #include <string.h> @@ -11,9 +10,10 @@ #include "nvim/globals.h" #include "nvim/mark.h" #include "nvim/option_defs.h" -#include "nvim/os/os.h" -#include "nvim/pos.h" -#include "nvim/vim.h" +#include "nvim/option_vars.h" +#include "nvim/os/fs.h" +#include "nvim/pos_defs.h" +#include "nvim/vim_defs.h" #include "nvim/window.h" /// Structure used by switch_win() to pass values to restore_win() @@ -75,4 +75,3 @@ typedef struct { #ifdef INCLUDE_GENERATED_DECLARATIONS # include "eval/window.h.generated.h" #endif -#endif // NVIM_EVAL_WINDOW_H |