diff options
Diffstat (limited to 'src/nvim/eval.c')
-rw-r--r-- | src/nvim/eval.c | 2372 |
1 files changed, 828 insertions, 1544 deletions
diff --git a/src/nvim/eval.c b/src/nvim/eval.c index a72f17a2b9..8b04d9afa4 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -66,13 +66,14 @@ #include "nvim/strings.h" #include "nvim/syntax.h" #include "nvim/tag.h" -#include "nvim/tempfile.h" #include "nvim/ui.h" #include "nvim/mouse.h" #include "nvim/terminal.h" #include "nvim/undo.h" #include "nvim/version.h" #include "nvim/window.h" +#include "nvim/eval/encode.h" +#include "nvim/eval/decode.h" #include "nvim/os/os.h" #include "nvim/event/libuv_process.h" #include "nvim/event/pty_process.h" @@ -87,7 +88,6 @@ #include "nvim/os/dl.h" #include "nvim/os/input.h" #include "nvim/event/loop.h" -#include "nvim/lib/kvec.h" #include "nvim/lib/queue.h" #define DICT_MAXNEST 100 /* maximum nesting of lists and dicts */ @@ -142,13 +142,6 @@ typedef struct lval_S { char_u *ll_newkey; /* New key for Dict in alloc. mem or NULL. */ } lval_T; -/// Structure defining state for read_from_list() -typedef struct { - const listitem_T *li; ///< Item currently read. - size_t offset; ///< Byte offset inside the read item. - size_t li_length; ///< Length of the string inside the read item. -} ListReaderState; - static char *e_letunexp = N_("E18: Unexpected characters in :let"); static char *e_listidx = N_("E684: list index out of range: %" PRId64); @@ -185,17 +178,7 @@ static dictitem_T globvars_var; /* variable used for g: */ */ static hashtab_T compat_hashtab; -/* - * When recursively copying lists and dicts we need to remember which ones we - * have done to avoid endless recursiveness. This unique ID is used for that. - * The last bit is used for previous_funccal, ignored when comparing. - */ -static int current_copyID = 0; -#define COPYID_INC 2 -#define COPYID_MASK (~0x1) - -/// Abort conversion to string after a recursion error. -static bool did_echo_string_emsg = false; +hashtab_T func_hashtab; /* * Array to hold the hashtab with variables local to each sourced script. @@ -298,94 +281,112 @@ typedef enum { #define VV_RO 2 /* read-only */ #define VV_RO_SBX 4 /* read-only in the sandbox */ -#define VV_NAME(s, t) s, {{t, 0, {0}}, 0, {0}}, {0} +#define VV(idx, name, type, flags) \ + [idx] = { \ + .vv_name = name, \ + .vv_di = { \ + .di_tv = { .v_type = type }, \ + .di_flags = 0, \ + }, \ + .vv_filler = { 0 }, \ + .vv_flags = flags, \ + } // Array to hold the value of v: variables. // The value is in a dictitem, so that it can also be used in the v: scope. // The reason to use this table anyway is for very quick access to the // variables with the VV_ defines. static struct vimvar { - char *vv_name; /* name of variable, without v: */ - dictitem_T vv_di; /* value and name for key */ - char vv_filler[16]; /* space for LONGEST name below!!! */ - char vv_flags; /* VV_COMPAT, VV_RO, VV_RO_SBX */ -} vimvars[VV_LEN] = -{ - /* - * The order here must match the VV_ defines in eval.h! - * Initializing a union does not work, leave tv.vval empty to get zero's. - */ - { VV_NAME("count", VAR_NUMBER), VV_COMPAT+VV_RO }, - { VV_NAME("count1", VAR_NUMBER), VV_RO }, - { VV_NAME("prevcount", VAR_NUMBER), VV_RO }, - { VV_NAME("errmsg", VAR_STRING), VV_COMPAT }, - { VV_NAME("warningmsg", VAR_STRING), 0 }, - { VV_NAME("statusmsg", VAR_STRING), 0 }, - { VV_NAME("shell_error", VAR_NUMBER), VV_COMPAT+VV_RO }, - { VV_NAME("this_session", VAR_STRING), VV_COMPAT }, - { VV_NAME("version", VAR_NUMBER), VV_COMPAT+VV_RO }, - { VV_NAME("lnum", VAR_NUMBER), VV_RO_SBX }, - { VV_NAME("termresponse", VAR_STRING), VV_RO }, - { VV_NAME("fname", VAR_STRING), VV_RO }, - { VV_NAME("lang", VAR_STRING), VV_RO }, - { VV_NAME("lc_time", VAR_STRING), VV_RO }, - { VV_NAME("ctype", VAR_STRING), VV_RO }, - { VV_NAME("charconvert_from", VAR_STRING), VV_RO }, - { VV_NAME("charconvert_to", VAR_STRING), VV_RO }, - { VV_NAME("fname_in", VAR_STRING), VV_RO }, - { VV_NAME("fname_out", VAR_STRING), VV_RO }, - { VV_NAME("fname_new", VAR_STRING), VV_RO }, - { VV_NAME("fname_diff", VAR_STRING), VV_RO }, - { VV_NAME("cmdarg", VAR_STRING), VV_RO }, - { VV_NAME("foldstart", VAR_NUMBER), VV_RO_SBX }, - { VV_NAME("foldend", VAR_NUMBER), VV_RO_SBX }, - { VV_NAME("folddashes", VAR_STRING), VV_RO_SBX }, - { VV_NAME("foldlevel", VAR_NUMBER), VV_RO_SBX }, - { VV_NAME("progname", VAR_STRING), VV_RO }, - { VV_NAME("servername", VAR_STRING), VV_RO }, - { VV_NAME("dying", VAR_NUMBER), VV_RO }, - { VV_NAME("exception", VAR_STRING), VV_RO }, - { VV_NAME("throwpoint", VAR_STRING), VV_RO }, - { VV_NAME("register", VAR_STRING), VV_RO }, - { VV_NAME("cmdbang", VAR_NUMBER), VV_RO }, - { VV_NAME("insertmode", VAR_STRING), VV_RO }, - { VV_NAME("val", VAR_UNKNOWN), VV_RO }, - { VV_NAME("key", VAR_UNKNOWN), VV_RO }, - { VV_NAME("profiling", VAR_NUMBER), VV_RO }, - { VV_NAME("fcs_reason", VAR_STRING), VV_RO }, - { VV_NAME("fcs_choice", VAR_STRING), 0 }, - { VV_NAME("beval_bufnr", VAR_NUMBER), VV_RO }, - { VV_NAME("beval_winnr", VAR_NUMBER), VV_RO }, - { VV_NAME("beval_lnum", VAR_NUMBER), VV_RO }, - { VV_NAME("beval_col", VAR_NUMBER), VV_RO }, - { VV_NAME("beval_text", VAR_STRING), VV_RO }, - { VV_NAME("scrollstart", VAR_STRING), 0 }, - { VV_NAME("swapname", VAR_STRING), VV_RO }, - { VV_NAME("swapchoice", VAR_STRING), 0 }, - { VV_NAME("swapcommand", VAR_STRING), VV_RO }, - { VV_NAME("char", VAR_STRING), 0 }, - { VV_NAME("mouse_win", VAR_NUMBER), 0 }, - { VV_NAME("mouse_lnum", VAR_NUMBER), 0 }, - { VV_NAME("mouse_col", VAR_NUMBER), 0 }, - { VV_NAME("operator", VAR_STRING), VV_RO }, - { VV_NAME("searchforward", VAR_NUMBER), 0 }, - { VV_NAME("hlsearch", VAR_NUMBER), 0 }, - { VV_NAME("oldfiles", VAR_LIST), 0 }, - { VV_NAME("windowid", VAR_NUMBER), VV_RO }, - { VV_NAME("progpath", VAR_STRING), VV_RO }, - { VV_NAME("command_output", VAR_STRING), 0 }, - { VV_NAME("completed_item", VAR_DICT), VV_RO }, - { VV_NAME("option_new", VAR_STRING), VV_RO }, - { VV_NAME("option_old", VAR_STRING), VV_RO }, - { VV_NAME("option_type", VAR_STRING), VV_RO }, - { VV_NAME("errors", VAR_LIST), 0 }, - { VV_NAME("msgpack_types", VAR_DICT), VV_RO }, - { VV_NAME("event", VAR_DICT), VV_RO }, + char *vv_name; ///< Name of the variable, without v:. + dictitem_T vv_di; ///< Value of the variable, with name. + char vv_filler[16]; ///< Space for longest name from below. + char vv_flags; ///< Flags: #VV_COMPAT, #VV_RO, #VV_RO_SBX. +} vimvars[] = +{ + // VV_ tails differing from upcased string literals: + // VV_CC_FROM "charconvert_from" + // VV_CC_TO "charconvert_to" + // VV_SEND_SERVER "servername" + // VV_REG "register" + // VV_OP "operator" + VV(VV_COUNT, "count", VAR_NUMBER, VV_COMPAT+VV_RO), + VV(VV_COUNT1, "count1", VAR_NUMBER, VV_RO), + VV(VV_PREVCOUNT, "prevcount", VAR_NUMBER, VV_RO), + VV(VV_ERRMSG, "errmsg", VAR_STRING, VV_COMPAT), + VV(VV_WARNINGMSG, "warningmsg", VAR_STRING, 0), + VV(VV_STATUSMSG, "statusmsg", VAR_STRING, 0), + VV(VV_SHELL_ERROR, "shell_error", VAR_NUMBER, VV_COMPAT+VV_RO), + VV(VV_THIS_SESSION, "this_session", VAR_STRING, VV_COMPAT), + VV(VV_VERSION, "version", VAR_NUMBER, VV_COMPAT+VV_RO), + VV(VV_LNUM, "lnum", VAR_NUMBER, VV_RO_SBX), + VV(VV_TERMRESPONSE, "termresponse", VAR_STRING, VV_RO), + VV(VV_FNAME, "fname", VAR_STRING, VV_RO), + VV(VV_LANG, "lang", VAR_STRING, VV_RO), + VV(VV_LC_TIME, "lc_time", VAR_STRING, VV_RO), + VV(VV_CTYPE, "ctype", VAR_STRING, VV_RO), + VV(VV_CC_FROM, "charconvert_from", VAR_STRING, VV_RO), + VV(VV_CC_TO, "charconvert_to", VAR_STRING, VV_RO), + VV(VV_FNAME_IN, "fname_in", VAR_STRING, VV_RO), + VV(VV_FNAME_OUT, "fname_out", VAR_STRING, VV_RO), + VV(VV_FNAME_NEW, "fname_new", VAR_STRING, VV_RO), + VV(VV_FNAME_DIFF, "fname_diff", VAR_STRING, VV_RO), + VV(VV_CMDARG, "cmdarg", VAR_STRING, VV_RO), + VV(VV_FOLDSTART, "foldstart", VAR_NUMBER, VV_RO_SBX), + VV(VV_FOLDEND, "foldend", VAR_NUMBER, VV_RO_SBX), + VV(VV_FOLDDASHES, "folddashes", VAR_STRING, VV_RO_SBX), + VV(VV_FOLDLEVEL, "foldlevel", VAR_NUMBER, VV_RO_SBX), + VV(VV_PROGNAME, "progname", VAR_STRING, VV_RO), + VV(VV_SEND_SERVER, "servername", VAR_STRING, VV_RO), + VV(VV_DYING, "dying", VAR_NUMBER, VV_RO), + VV(VV_EXCEPTION, "exception", VAR_STRING, VV_RO), + VV(VV_THROWPOINT, "throwpoint", VAR_STRING, VV_RO), + VV(VV_REG, "register", VAR_STRING, VV_RO), + VV(VV_CMDBANG, "cmdbang", VAR_NUMBER, VV_RO), + VV(VV_INSERTMODE, "insertmode", VAR_STRING, VV_RO), + VV(VV_VAL, "val", VAR_UNKNOWN, VV_RO), + VV(VV_KEY, "key", VAR_UNKNOWN, VV_RO), + VV(VV_PROFILING, "profiling", VAR_NUMBER, VV_RO), + VV(VV_FCS_REASON, "fcs_reason", VAR_STRING, VV_RO), + VV(VV_FCS_CHOICE, "fcs_choice", VAR_STRING, 0), + VV(VV_BEVAL_BUFNR, "beval_bufnr", VAR_NUMBER, VV_RO), + VV(VV_BEVAL_WINNR, "beval_winnr", VAR_NUMBER, VV_RO), + VV(VV_BEVAL_LNUM, "beval_lnum", VAR_NUMBER, VV_RO), + VV(VV_BEVAL_COL, "beval_col", VAR_NUMBER, VV_RO), + VV(VV_BEVAL_TEXT, "beval_text", VAR_STRING, VV_RO), + VV(VV_SCROLLSTART, "scrollstart", VAR_STRING, 0), + VV(VV_SWAPNAME, "swapname", VAR_STRING, VV_RO), + VV(VV_SWAPCHOICE, "swapchoice", VAR_STRING, 0), + VV(VV_SWAPCOMMAND, "swapcommand", VAR_STRING, VV_RO), + VV(VV_CHAR, "char", VAR_STRING, 0), + VV(VV_MOUSE_WIN, "mouse_win", VAR_NUMBER, 0), + VV(VV_MOUSE_LNUM, "mouse_lnum", VAR_NUMBER, 0), + VV(VV_MOUSE_COL, "mouse_col", VAR_NUMBER, 0), + VV(VV_OP, "operator", VAR_STRING, VV_RO), + VV(VV_SEARCHFORWARD, "searchforward", VAR_NUMBER, 0), + VV(VV_HLSEARCH, "hlsearch", VAR_NUMBER, 0), + VV(VV_OLDFILES, "oldfiles", VAR_LIST, 0), + VV(VV_WINDOWID, "windowid", VAR_NUMBER, VV_RO_SBX), + VV(VV_PROGPATH, "progpath", VAR_STRING, VV_RO), + VV(VV_COMMAND_OUTPUT, "command_output", VAR_STRING, 0), + VV(VV_COMPLETED_ITEM, "completed_item", VAR_DICT, VV_RO), + VV(VV_OPTION_NEW, "option_new", VAR_STRING, VV_RO), + VV(VV_OPTION_OLD, "option_old", VAR_STRING, VV_RO), + VV(VV_OPTION_TYPE, "option_type", VAR_STRING, VV_RO), + VV(VV_ERRORS, "errors", VAR_LIST, 0), + VV(VV_MSGPACK_TYPES, "msgpack_types", VAR_DICT, VV_RO), + VV(VV_EVENT, "event", VAR_DICT, VV_RO), + VV(VV_FALSE, "false", VAR_SPECIAL, VV_RO), + VV(VV_TRUE, "true", VAR_SPECIAL, VV_RO), + VV(VV_NULL, "null", VAR_SPECIAL, VV_RO), + VV(VV__NULL_LIST, "_null_list", VAR_LIST, VV_RO), + VV(VV__NULL_DICT, "_null_dict", VAR_DICT, VV_RO), }; +#undef VV /* shorthand */ #define vv_type vv_di.di_tv.v_type #define vv_nr vv_di.di_tv.vval.v_number +#define vv_special vv_di.di_tv.vval.v_special #define vv_float vv_di.di_tv.vval.v_float #define vv_str vv_di.di_tv.vval.v_string #define vv_list vv_di.di_tv.vval.v_list @@ -419,29 +420,6 @@ typedef struct dict_watcher { bool busy; // prevent recursion if the dict is changed in the callback } DictWatcher; -/// Structure representing current VimL to messagepack conversion state -typedef struct { - enum { - kMPConvDict, ///< Convert dict_T *dictionary. - kMPConvList, ///< Convert list_T *list. - kMPConvPairs, ///< Convert mapping represented as a list_T* of pairs. - } type; - union { - struct { - dict_T *dict; ///< Currently converted dictionary. - hashitem_T *hi; ///< Currently converted dictionary item. - size_t todo; ///< Amount of items left to process. - } d; ///< State of dictionary conversion. - struct { - list_T *list; ///< Currently converted list. - listitem_T *li; ///< Currently converted list item. - } l; ///< State of list or generic mapping conversion. - } data; ///< Data to convert. -} MPConvStackVal; - -/// Stack used to convert VimL values to messagepack. -typedef kvec_t(MPConvStackVal) MPConvStack; - typedef struct { TerminalJobData *data; ufunc_T *callback; @@ -460,17 +438,6 @@ typedef struct { static uint64_t current_job_id = 1; static PMap(uint64_t) *jobs = NULL; -typedef enum { - kMPNil, - kMPBoolean, - kMPInteger, - kMPFloat, - kMPString, - kMPBinary, - kMPArray, - kMPMap, - kMPExt, -} MessagePackType; static const char *const msgpack_type_names[] = { [kMPNil] = "nil", [kMPBoolean] = "boolean", @@ -482,7 +449,7 @@ static const char *const msgpack_type_names[] = { [kMPMap] = "map", [kMPExt] = "ext", }; -static const list_T *msgpack_type_lists[] = { +const list_T *eval_msgpack_type_lists[] = { [kMPNil] = NULL, [kMPBoolean] = NULL, [kMPInteger] = NULL, @@ -499,8 +466,9 @@ static const list_T *msgpack_type_lists[] = { */ void eval_init(void) { + vimvars[VV_VERSION].vv_nr = VIM_VERSION_100; + jobs = pmap_new(uint64_t)(); - int i; struct vimvar *p; init_var_dict(&globvardict, &globvars_var, VAR_DEF_SCOPE); @@ -509,7 +477,7 @@ void eval_init(void) hash_init(&compat_hashtab); hash_init(&func_hashtab); - for (i = 0; i < VV_LEN; ++i) { + for (size_t i = 0; i < ARRAY_SIZE(vimvars); i++) { p = &vimvars[i]; STRCPY(p->vv_di.di_key, p->vv_name); if (p->vv_flags & VV_RO) @@ -538,7 +506,7 @@ void eval_init(void) .v_type = VAR_LIST, .vval = { .v_list = type_list, }, }; - msgpack_type_lists[i] = type_list; + eval_msgpack_type_lists[i] = type_list; if (dict_add(msgpack_types_dict, di) == FAIL) { // There must not be duplicate items in this dictionary by definition. assert(false); @@ -555,7 +523,12 @@ void eval_init(void) set_vim_var_list(VV_ERRORS, list_alloc()); set_vim_var_nr(VV_SEARCHFORWARD, 1L); set_vim_var_nr(VV_HLSEARCH, 1L); - set_reg_var(0); /* default for v:register is not 0 but '"' */ + + set_vim_var_special(VV_FALSE, kSpecialVarFalse); + set_vim_var_special(VV_TRUE, kSpecialVarTrue); + set_vim_var_special(VV_NULL, kSpecialVarNull); + + set_reg_var(0); // default for v:register is not 0 but '"' } #if defined(EXITFREE) @@ -563,7 +536,7 @@ void eval_clear(void) { struct vimvar *p; - for (int i = 0; i < VV_LEN; ++i) { + for (size_t i = 0; i < ARRAY_SIZE(vimvars); i++) { p = &vimvars[i]; if (p->vv_di.di_tv.v_type == VAR_STRING) { xfree(p->vv_str); @@ -798,45 +771,50 @@ void var_redir_stop(void) redir_varname = NULL; } -int eval_charconvert(char_u *enc_from, char_u *enc_to, char_u *fname_from, char_u *fname_to) +int eval_charconvert(const char *const enc_from, const char *const enc_to, + const char *const fname_from, const char *const fname_to) { - int err = FALSE; + int err = false; set_vim_var_string(VV_CC_FROM, enc_from, -1); set_vim_var_string(VV_CC_TO, enc_to, -1); set_vim_var_string(VV_FNAME_IN, fname_from, -1); set_vim_var_string(VV_FNAME_OUT, fname_to, -1); - if (eval_to_bool(p_ccv, &err, NULL, FALSE)) - err = TRUE; + if (eval_to_bool(p_ccv, &err, NULL, false)) { + err = true; + } set_vim_var_string(VV_CC_FROM, NULL, -1); set_vim_var_string(VV_CC_TO, NULL, -1); set_vim_var_string(VV_FNAME_IN, NULL, -1); set_vim_var_string(VV_FNAME_OUT, NULL, -1); - if (err) + if (err) { return FAIL; + } return OK; } -int eval_printexpr(char_u *fname, char_u *args) +int eval_printexpr(const char *const fname, const char *const args) { - int err = FALSE; + int err = false; set_vim_var_string(VV_FNAME_IN, fname, -1); set_vim_var_string(VV_CMDARG, args, -1); - if (eval_to_bool(p_pexpr, &err, NULL, FALSE)) - err = TRUE; + if (eval_to_bool(p_pexpr, &err, NULL, false)) { + err = true; + } set_vim_var_string(VV_FNAME_IN, NULL, -1); set_vim_var_string(VV_CMDARG, NULL, -1); if (err) { - os_remove((char *)fname); + os_remove(fname); return FAIL; } return OK; } -void eval_diff(char_u *origfile, char_u *newfile, char_u *outfile) +void eval_diff(const char *const origfile, const char *const newfile, + const char *const outfile) { int err = FALSE; @@ -849,7 +827,8 @@ void eval_diff(char_u *origfile, char_u *newfile, char_u *outfile) set_vim_var_string(VV_FNAME_OUT, NULL, -1); } -void eval_patch(char_u *origfile, char_u *difffile, char_u *outfile) +void eval_patch(const char *const origfile, const char *const difffile, + const char *const outfile) { int err; @@ -1733,7 +1712,7 @@ static char_u *list_arg_vars(exarg_T *eap, char_u *arg, int *first) } else { int c; - char_u *s = (char_u *) echo_string(&tv, NULL); + char_u *s = (char_u *) encode_tv2echo(&tv, NULL); c = *arg; *arg = NUL; list_one_var_a((char_u *)"", @@ -2417,11 +2396,12 @@ static int tv_op(typval_T *tv1, typval_T *tv2, char_u *op) char_u numbuf[NUMBUFLEN]; char_u *s; - /* Can't do anything with a Funcref or a Dict on the right. */ + // Can't do anything with a Funcref, a Dict or special value on the right. if (tv2->v_type != VAR_FUNC && tv2->v_type != VAR_DICT) { switch (tv1->v_type) { case VAR_DICT: case VAR_FUNC: + case VAR_SPECIAL: break; case VAR_LIST: @@ -2489,6 +2469,9 @@ static int tv_op(typval_T *tv1, typval_T *tv2, char_u *op) tv1->vval.v_float -= f; } return OK; + + case VAR_UNKNOWN: + assert(false); } } @@ -3126,6 +3109,15 @@ static void item_lock(typval_T *tv, int deep, int lock) } } } + break; + case VAR_NUMBER: + case VAR_FLOAT: + case VAR_STRING: + case VAR_FUNC: + case VAR_SPECIAL: + break; + case VAR_UNKNOWN: + assert(false); } --recurse; } @@ -3204,7 +3196,7 @@ char_u *get_user_var_name(expand_T *xp, int idx) static size_t bdone; static size_t wdone; static size_t tdone; - static int vidx; + static size_t vidx; static hashitem_T *hi; hashtab_T *ht; @@ -3266,9 +3258,10 @@ char_u *get_user_var_name(expand_T *xp, int idx) return cat_prefix_varname('t', hi->hi_key); } - /* v: variables */ - if (vidx < VV_LEN) + // v: variables + if (vidx < ARRAY_SIZE(vimvars)) { return cat_prefix_varname('v', (char_u *)vimvars[vidx++].vv_name); + } xfree(varnamebuf); varnamebuf = NULL; @@ -4158,7 +4151,7 @@ static int eval7( if (get_float) { float_T f; - *arg += string2float(*arg, &f); + *arg += string2float((char *) *arg, &f); if (evaluate) { rettv->v_type = VAR_FLOAT; rettv->vval.v_float = f; @@ -4347,14 +4340,37 @@ eval_index ( char_u *s; char_u *key = NULL; - if (rettv->v_type == VAR_FUNC) { - if (verbose) - EMSG(_("E695: Cannot index a Funcref")); - return FAIL; - } else if (rettv->v_type == VAR_FLOAT) { - if (verbose) - EMSG(_(e_float_as_string)); - return FAIL; + switch (rettv->v_type) { + case VAR_FUNC: { + if (verbose) { + EMSG(_("E695: Cannot index a Funcref")); + } + return FAIL; + } + case VAR_FLOAT: { + if (verbose) { + EMSG(_(e_float_as_string)); + } + return FAIL; + } + case VAR_SPECIAL: { + if (verbose) { + EMSG(_("E909: Cannot index a special variable")); + } + return FAIL; + } + case VAR_UNKNOWN: { + if (evaluate) { + return FAIL; + } + // fallthrough + } + case VAR_STRING: + case VAR_NUMBER: + case VAR_LIST: + case VAR_DICT: { + break; + } } init_tv(&var1); @@ -4545,6 +4561,11 @@ eval_index ( *rettv = var1; } break; + case VAR_SPECIAL: + case VAR_FUNC: + case VAR_FLOAT: + case VAR_UNKNOWN: + break; // Not evaluating, skipping over subscript } } @@ -5090,10 +5111,18 @@ tv_equal ( s1 = get_tv_string_buf(tv1, buf1); s2 = get_tv_string_buf(tv2, buf2); return (ic ? mb_stricmp(s1, s2) : STRCMP(s1, s2)) == 0; + + case VAR_SPECIAL: + return tv1->vval.v_special == tv2->vval.v_special; + + case VAR_UNKNOWN: + // VAR_UNKNOWN can be the result of an invalid expression, let’s say it does + // not equal anything, not even self. + return false; } - EMSG2(_(e_intern2), "tv_equal()"); - return TRUE; + assert(false); + return false; } /* @@ -5494,9 +5523,10 @@ static int list_join_inner(garray_T *const gap, list_T *const l, for (item = l->lv_first; item != NULL && !got_int; item = item->li_next) { char *s; size_t len; - s = echo_string(&item->li_tv, &len); - if (s == NULL) + s = encode_tv2echo(&item->li_tv, &len); + if (s == NULL) { return FAIL; + } sumlen += (int) len; @@ -5504,9 +5534,6 @@ static int list_join_inner(garray_T *const gap, list_T *const l, p->tofree = p->s = (char_u *) s; line_breakcheck(); - if (did_echo_string_emsg) { // recursion error, bail out - break; - } } /* Allocate result buffer with its total size, avoid re-allocation and @@ -5558,6 +5585,22 @@ static int list_join(garray_T *const gap, list_T *const l, return retval; } +/// Get next (unique) copy ID +/// +/// Used for traversing nested structures e.g. when serializing them or garbage +/// collecting. +int get_copyID(void) + FUNC_ATTR_WARN_UNUSED_RESULT +{ + // CopyID for recursively traversing lists and dicts + // + // This value is needed to avoid endless recursiveness. Last bit is used for + // previous_funccal and normally ignored when comparing. + static int current_copyID = 0; + current_copyID += COPYID_INC; + return current_copyID; +} + /* * Garbage collection for lists and dictionaries. * @@ -5593,8 +5636,7 @@ bool garbage_collect(void) // We advance by two because we add one for items referenced through // previous_funccal. - current_copyID += COPYID_INC; - int copyID = current_copyID; + const int copyID = get_copyID(); // 1. Go through all accessible variables and mark all lists and dicts // with copyID. @@ -5939,6 +5981,15 @@ bool set_ref_in_item(typval_T *tv, int copyID, ht_stack_T **ht_stack, } break; } + + case VAR_FUNC: + case VAR_UNKNOWN: + case VAR_SPECIAL: + case VAR_FLOAT: + case VAR_NUMBER: + case VAR_STRING: { + break; + } } return abort; } @@ -6530,596 +6581,22 @@ failret: return OK; } -#define CHECK_SELF_REFERENCE(val, copyID_attr, conv_type) \ - do { \ - if ((val)->copyID_attr == copyID) { \ - CONV_RECURSE((val), conv_type); \ - } \ - (val)->copyID_attr = copyID; \ - } while (0) - -/// Define functions which convert VimL value to something else -/// -/// Creates function `vim_to_{name}(firstargtype firstargname, typval_T *const -/// tv)` which returns OK or FAIL and helper functions. -/// -/// @param firstargtype Type of the first argument. It will be used to return -/// the results. -/// @param firstargname Name of the first argument. -/// @param name Name of the target converter. -#define DEFINE_VIML_CONV_FUNCTIONS(scope, name, firstargtype, firstargname) \ -static int name##_convert_one_value(firstargtype firstargname, \ - MPConvStack *const mpstack, \ - typval_T *const tv, \ - const int copyID, \ - const char *const objname) \ - FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT \ -{ \ - switch (tv->v_type) { \ - case VAR_STRING: { \ - CONV_STRING(tv->vval.v_string, STRLEN(tv->vval.v_string)); \ - break; \ - } \ - case VAR_NUMBER: { \ - CONV_NUMBER(tv->vval.v_number); \ - break; \ - } \ - case VAR_FLOAT: { \ - CONV_FLOAT(tv->vval.v_float); \ - break; \ - } \ - case VAR_FUNC: { \ - CONV_FUNC(tv->vval.v_string); \ - break; \ - } \ - case VAR_LIST: { \ - if (tv->vval.v_list == NULL || tv->vval.v_list->lv_len == 0) { \ - CONV_EMPTY_LIST(); \ - break; \ - } \ - CHECK_SELF_REFERENCE(tv->vval.v_list, lv_copyID, kMPConvList); \ - CONV_LIST_START(tv->vval.v_list); \ - kv_push( \ - MPConvStackVal, \ - *mpstack, \ - ((MPConvStackVal) { \ - .type = kMPConvList, \ - .data = { \ - .l = { \ - .list = tv->vval.v_list, \ - .li = tv->vval.v_list->lv_first, \ - }, \ - }, \ - })); \ - break; \ - } \ - case VAR_DICT: { \ - if (tv->vval.v_dict == NULL \ - || tv->vval.v_dict->dv_hashtab.ht_used == 0) { \ - CONV_EMPTY_DICT(); \ - break; \ - } \ - const dictitem_T *type_di; \ - const dictitem_T *val_di; \ - if (CONV_ALLOW_SPECIAL \ - && tv->vval.v_dict->dv_hashtab.ht_used == 2 \ - && (type_di = dict_find((dict_T *) tv->vval.v_dict, \ - (char_u *) "_TYPE", -1)) != NULL \ - && type_di->di_tv.v_type == VAR_LIST \ - && (val_di = dict_find((dict_T *) tv->vval.v_dict, \ - (char_u *) "_VAL", -1)) != NULL) { \ - size_t i; \ - for (i = 0; i < ARRAY_SIZE(msgpack_type_lists); i++) { \ - if (type_di->di_tv.vval.v_list == msgpack_type_lists[i]) { \ - break; \ - } \ - } \ - if (i == ARRAY_SIZE(msgpack_type_lists)) { \ - goto name##_convert_one_value_regular_dict; \ - } \ - switch ((MessagePackType) i) { \ - case kMPNil: { \ - CONV_SPECIAL_NIL(); \ - break; \ - } \ - case kMPBoolean: { \ - if (val_di->di_tv.v_type != VAR_NUMBER) { \ - goto name##_convert_one_value_regular_dict; \ - } \ - CONV_SPECIAL_BOOL(val_di->di_tv.vval.v_number); \ - break; \ - } \ - case kMPInteger: { \ - const list_T *val_list; \ - varnumber_T sign; \ - varnumber_T highest_bits; \ - varnumber_T high_bits; \ - varnumber_T low_bits; \ - /* List of 4 integers; first is signed (should be 1 or -1, but */ \ - /* this is not checked), second is unsigned and have at most */ \ - /* one (sign is -1) or two (sign is 1) non-zero bits (number of */ \ - /* bits is not checked), other unsigned and have at most 31 */ \ - /* non-zero bits (number of bits is not checked).*/ \ - if (val_di->di_tv.v_type != VAR_LIST \ - || (val_list = val_di->di_tv.vval.v_list) == NULL \ - || val_list->lv_len != 4 \ - || val_list->lv_first->li_tv.v_type != VAR_NUMBER \ - || (sign = val_list->lv_first->li_tv.vval.v_number) == 0 \ - || val_list->lv_first->li_next->li_tv.v_type != VAR_NUMBER \ - || (highest_bits = \ - val_list->lv_first->li_next->li_tv.vval.v_number) < 0 \ - || val_list->lv_last->li_prev->li_tv.v_type != VAR_NUMBER \ - || (high_bits = \ - val_list->lv_last->li_prev->li_tv.vval.v_number) < 0 \ - || val_list->lv_last->li_tv.v_type != VAR_NUMBER \ - || (low_bits = val_list->lv_last->li_tv.vval.v_number) < 0) { \ - goto name##_convert_one_value_regular_dict; \ - } \ - uint64_t number = ((uint64_t) (((uint64_t) highest_bits) << 62) \ - | (uint64_t) (((uint64_t) high_bits) << 31) \ - | (uint64_t) low_bits); \ - if (sign > 0) { \ - CONV_UNSIGNED_NUMBER(number); \ - } else { \ - CONV_NUMBER(-number); \ - } \ - break; \ - } \ - case kMPFloat: { \ - if (val_di->di_tv.v_type != VAR_FLOAT) { \ - goto name##_convert_one_value_regular_dict; \ - } \ - CONV_FLOAT(val_di->di_tv.vval.v_float); \ - break; \ - } \ - case kMPString: \ - case kMPBinary: { \ - const bool is_string = ((MessagePackType) i == kMPString); \ - if (val_di->di_tv.v_type != VAR_LIST) { \ - goto name##_convert_one_value_regular_dict; \ - } \ - size_t len; \ - char *buf; \ - if (!vim_list_to_buf(val_di->di_tv.vval.v_list, &len, &buf)) { \ - goto name##_convert_one_value_regular_dict; \ - } \ - if (is_string) { \ - CONV_STR_STRING(buf, len); \ - } else { \ - CONV_STRING(buf, len); \ - } \ - xfree(buf); \ - break; \ - } \ - case kMPArray: { \ - if (val_di->di_tv.v_type != VAR_LIST) { \ - goto name##_convert_one_value_regular_dict; \ - } \ - CHECK_SELF_REFERENCE(val_di->di_tv.vval.v_list, lv_copyID, \ - kMPConvList); \ - CONV_LIST_START(val_di->di_tv.vval.v_list); \ - kv_push(MPConvStackVal, *mpstack, ((MPConvStackVal) { \ - .type = kMPConvList, \ - .data = { \ - .l = { \ - .list = val_di->di_tv.vval.v_list, \ - .li = val_di->di_tv.vval.v_list->lv_first, \ - }, \ - }, \ - })); \ - break; \ - } \ - case kMPMap: { \ - if (val_di->di_tv.v_type != VAR_LIST) { \ - goto name##_convert_one_value_regular_dict; \ - } \ - if (val_di->di_tv.vval.v_list == NULL) { \ - CONV_EMPTY_DICT(); \ - break; \ - } \ - list_T *const val_list = val_di->di_tv.vval.v_list; \ - for (const listitem_T *li = val_list->lv_first; li != NULL; \ - li = li->li_next) { \ - if (li->li_tv.v_type != VAR_LIST \ - || li->li_tv.vval.v_list->lv_len != 2) { \ - goto name##_convert_one_value_regular_dict; \ - } \ - } \ - CHECK_SELF_REFERENCE(val_list, lv_copyID, kMPConvPairs); \ - CONV_SPECIAL_MAP_START(val_list); \ - kv_push(MPConvStackVal, *mpstack, ((MPConvStackVal) { \ - .type = kMPConvPairs, \ - .data = { \ - .l = { \ - .list = val_list, \ - .li = val_list->lv_first, \ - }, \ - }, \ - })); \ - break; \ - } \ - case kMPExt: { \ - const list_T *val_list; \ - varnumber_T type; \ - if (val_di->di_tv.v_type != VAR_LIST \ - || (val_list = val_di->di_tv.vval.v_list) == NULL \ - || val_list->lv_len != 2 \ - || (val_list->lv_first->li_tv.v_type != VAR_NUMBER) \ - || (type = val_list->lv_first->li_tv.vval.v_number) > INT8_MAX \ - || type < INT8_MIN \ - || (val_list->lv_last->li_tv.v_type != VAR_LIST)) { \ - goto name##_convert_one_value_regular_dict; \ - } \ - size_t len; \ - char *buf; \ - if (!vim_list_to_buf(val_list->lv_last->li_tv.vval.v_list, \ - &len, &buf)) { \ - goto name##_convert_one_value_regular_dict; \ - } \ - CONV_EXT_STRING(buf, len, type); \ - xfree(buf); \ - break; \ - } \ - } \ - break; \ - } \ -name##_convert_one_value_regular_dict: \ - CHECK_SELF_REFERENCE(tv->vval.v_dict, dv_copyID, kMPConvDict); \ - CONV_DICT_START(tv->vval.v_dict); \ - kv_push(MPConvStackVal, *mpstack, ((MPConvStackVal) { \ - .type = kMPConvDict, \ - .data = { \ - .d = { \ - .dict = tv->vval.v_dict, \ - .hi = tv->vval.v_dict->dv_hashtab.ht_array, \ - .todo = tv->vval.v_dict->dv_hashtab.ht_used, \ - }, \ - }, \ - })); \ - break; \ - } \ - default: { \ - EMSG2(_(e_intern2), #name "_convert_one_value()"); \ - return FAIL; \ - } \ - } \ - return OK; \ -} \ -\ -scope int vim_to_##name(firstargtype firstargname, typval_T *const tv, \ - const char *const objname) \ - FUNC_ATTR_WARN_UNUSED_RESULT \ -{ \ - current_copyID += COPYID_INC; \ - const int copyID = current_copyID; \ - MPConvStack mpstack; \ - kv_init(mpstack); \ - if (name##_convert_one_value(firstargname, &mpstack, tv, copyID, objname) \ - == FAIL) { \ - goto vim_to_msgpack_error_ret; \ - } \ - while (kv_size(mpstack)) { \ - MPConvStackVal *cur_mpsv = &kv_A(mpstack, kv_size(mpstack) - 1); \ - typval_T *cur_tv = NULL; \ - switch (cur_mpsv->type) { \ - case kMPConvDict: { \ - if (!cur_mpsv->data.d.todo) { \ - (void) kv_pop(mpstack); \ - cur_mpsv->data.d.dict->dv_copyID = copyID - 1; \ - CONV_DICT_END(cur_mpsv->data.d.dict); \ - continue; \ - } else if (cur_mpsv->data.d.todo \ - != cur_mpsv->data.d.dict->dv_hashtab.ht_used) { \ - CONV_DICT_BETWEEN_ITEMS(cur_mpsv->data.d.dict); \ - } \ - while (HASHITEM_EMPTY(cur_mpsv->data.d.hi)) { \ - cur_mpsv->data.d.hi++; \ - } \ - dictitem_T *const di = HI2DI(cur_mpsv->data.d.hi); \ - cur_mpsv->data.d.todo--; \ - cur_mpsv->data.d.hi++; \ - CONV_STR_STRING(&di->di_key[0], STRLEN(&di->di_key[0])); \ - CONV_DICT_AFTER_KEY(cur_mpsv->data.d.dict); \ - cur_tv = &di->di_tv; \ - break; \ - } \ - case kMPConvList: { \ - if (cur_mpsv->data.l.li == NULL) { \ - (void) kv_pop(mpstack); \ - cur_mpsv->data.l.list->lv_copyID = copyID - 1; \ - CONV_LIST_END(cur_mpsv->data.l.list); \ - continue; \ - } else if (cur_mpsv->data.l.li != cur_mpsv->data.l.list->lv_first) { \ - CONV_LIST_BETWEEN_ITEMS(cur_mpsv->data.l.list); \ - } \ - cur_tv = &cur_mpsv->data.l.li->li_tv; \ - cur_mpsv->data.l.li = cur_mpsv->data.l.li->li_next; \ - break; \ - } \ - case kMPConvPairs: { \ - if (cur_mpsv->data.l.li == NULL) { \ - (void) kv_pop(mpstack); \ - cur_mpsv->data.l.list->lv_copyID = copyID - 1; \ - continue; \ - } \ - const list_T *const kv_pair = cur_mpsv->data.l.li->li_tv.vval.v_list; \ - if (name##_convert_one_value(firstargname, &mpstack, \ - &kv_pair->lv_first->li_tv, copyID, \ - objname) == FAIL) { \ - goto vim_to_msgpack_error_ret; \ - } \ - cur_tv = &kv_pair->lv_last->li_tv; \ - cur_mpsv->data.l.li = cur_mpsv->data.l.li->li_next; \ - break; \ - } \ - } \ - if (name##_convert_one_value(firstargname, &mpstack, cur_tv, copyID, \ - objname) == FAIL) { \ - goto vim_to_msgpack_error_ret; \ - } \ - } \ - kv_destroy(mpstack); \ - return OK; \ -vim_to_msgpack_error_ret: \ - kv_destroy(mpstack); \ - return FAIL; \ -} - -#define CONV_STRING(buf, len) \ - do { \ - const char *const buf_ = (const char *) buf; \ - if (buf == NULL) { \ - ga_concat(gap, (char_u *) "''"); \ - } else { \ - const size_t len_ = (len); \ - size_t num_quotes = 0; \ - for (size_t i = 0; i < len_; i++) { \ - if (buf_[i] == '\'') { \ - num_quotes++; \ - } \ - } \ - ga_grow(gap, 2 + len_ + num_quotes); \ - ga_append(gap, '\''); \ - for (size_t i = 0; i < len_; i++) { \ - if (buf_[i] == '\'') { \ - num_quotes++; \ - ga_append(gap, '\''); \ - } \ - ga_append(gap, buf_[i]); \ - } \ - ga_append(gap, '\''); \ - } \ - } while (0) - -#define CONV_STR_STRING(buf, len) \ - CONV_STRING(buf, len) - -#define CONV_EXT_STRING(buf, len, type) - -#define CONV_NUMBER(num) \ - do { \ - char numbuf[NUMBUFLEN]; \ - vim_snprintf(numbuf, NUMBUFLEN - 1, "%" PRId64, (int64_t) (num)); \ - ga_concat(gap, (char_u *) numbuf); \ - } while (0) - -#define CONV_FLOAT(flt) \ - do { \ - const float_T flt_ = (flt); \ - switch (fpclassify(flt_)) { \ - case FP_NAN: { \ - ga_concat(gap, (char_u *) "str2float('nan')"); \ - break; \ - } \ - case FP_INFINITE: { \ - if (flt_ < 0) { \ - ga_append(gap, '-'); \ - } \ - ga_concat(gap, (char_u *) "str2float('inf')"); \ - break; \ - } \ - default: { \ - char numbuf[NUMBUFLEN]; \ - vim_snprintf(numbuf, NUMBUFLEN - 1, "%g", flt_); \ - ga_concat(gap, (char_u *) numbuf); \ - } \ - } \ - } while (0) - -#define CONV_FUNC(fun) \ - do { \ - ga_concat(gap, (char_u *) "function("); \ - CONV_STRING(fun, STRLEN(fun)); \ - ga_append(gap, ')'); \ - } while (0) - -#define CONV_EMPTY_LIST() \ - ga_concat(gap, (char_u *) "[]") - -#define CONV_LIST_START(lst) \ - ga_append(gap, '[') - -#define CONV_EMPTY_DICT() \ - ga_concat(gap, (char_u *) "{}") - -#define CONV_SPECIAL_NIL() - -#define CONV_SPECIAL_BOOL(num) - -#define CONV_UNSIGNED_NUMBER(num) - -#define CONV_SPECIAL_MAP_START(lst) - -#define CONV_DICT_START(dct) \ - ga_append(gap, '{') - -#define CONV_DICT_END(dct) \ - ga_append(gap, '}') - -#define CONV_DICT_AFTER_KEY(dct) \ - ga_concat(gap, (char_u *) ": ") - -#define CONV_DICT_BETWEEN_ITEMS(dct) \ - ga_concat(gap, (char_u *) ", ") - -#define CONV_LIST_END(lst) \ - ga_append(gap, ']') - -#define CONV_LIST_BETWEEN_ITEMS(lst) \ - CONV_DICT_BETWEEN_ITEMS(NULL) - -#define CONV_RECURSE(val, conv_type) \ - do { \ - if (!did_echo_string_emsg) { \ - /* Only give this message once for a recursive call to avoid */ \ - /* flooding the user with errors. */ \ - did_echo_string_emsg = true; \ - EMSG(_("E724: unable to correctly dump variable " \ - "with self-referencing container")); \ - } \ - char ebuf[NUMBUFLEN + 7]; \ - size_t backref = 0; \ - for (; backref < kv_size(*mpstack); backref++) { \ - const MPConvStackVal mpval = kv_a(MPConvStackVal, *mpstack, backref); \ - if (mpval.type == conv_type) { \ - if (conv_type == kMPConvDict) { \ - if ((void *) mpval.data.d.dict == (void *) (val)) { \ - break; \ - } \ - } else if (conv_type == kMPConvList) { \ - if ((void *) mpval.data.l.list == (void *) (val)) { \ - break; \ - } \ - } \ - } \ - } \ - vim_snprintf(ebuf, NUMBUFLEN + 6, "{E724@%zu}", backref); \ - ga_concat(gap, (char_u *) &ebuf[0]); \ - return OK; \ - } while (0) - -#define CONV_ALLOW_SPECIAL false - -DEFINE_VIML_CONV_FUNCTIONS(static, string, garray_T *const, gap) - -#undef CONV_RECURSE -#define CONV_RECURSE(val, conv_type) \ - do { \ - char ebuf[NUMBUFLEN + 7]; \ - size_t backref = 0; \ - for (; backref < kv_size(*mpstack); backref++) { \ - const MPConvStackVal mpval = kv_a(MPConvStackVal, *mpstack, backref); \ - if (mpval.type == conv_type) { \ - if (conv_type == kMPConvDict) { \ - if ((void *) mpval.data.d.dict == (void *) val) { \ - break; \ - } \ - } else if (conv_type == kMPConvList) { \ - if ((void *) mpval.data.l.list == (void *) val) { \ - break; \ - } \ - } \ - } \ - } \ - if (conv_type == kMPConvDict) { \ - vim_snprintf(ebuf, NUMBUFLEN + 6, "{...@%zu}", backref); \ - } else { \ - vim_snprintf(ebuf, NUMBUFLEN + 6, "[...@%zu]", backref); \ - } \ - ga_concat(gap, (char_u *) &ebuf[0]); \ - return OK; \ - } while (0) - -DEFINE_VIML_CONV_FUNCTIONS(static, echo, garray_T *const, gap) - -#undef CONV_STRING -#undef CONV_STR_STRING -#undef CONV_EXT_STRING -#undef CONV_NUMBER -#undef CONV_FLOAT -#undef CONV_FUNC -#undef CONV_EMPTY_LIST -#undef CONV_LIST_START -#undef CONV_EMPTY_DICT -#undef CONV_SPECIAL_NIL -#undef CONV_SPECIAL_BOOL -#undef CONV_UNSIGNED_NUMBER -#undef CONV_SPECIAL_MAP_START -#undef CONV_DICT_START -#undef CONV_DICT_END -#undef CONV_DICT_AFTER_KEY -#undef CONV_DICT_BETWEEN_ITEMS -#undef CONV_LIST_END -#undef CONV_LIST_BETWEEN_ITEMS -#undef CONV_RECURSE -#undef CONV_ALLOW_SPECIAL - -/// Return a string with the string representation of a variable. -/// Puts quotes around strings, so that they can be parsed back by eval(). -/// -/// @param[in] tv typval_T to convert. -/// @param[out] len Location where length of the result will be saved. +/// Convert the string to a floating point number /// -/// @return String representation of the variable or NULL. -static char *tv2string(typval_T *tv, size_t *len) - FUNC_ATTR_NONNULL_ARG(1) FUNC_ATTR_MALLOC -{ - garray_T ga; - ga_init(&ga, (int)sizeof(char), 80); - vim_to_string(&ga, tv, "tv2string() argument"); - did_echo_string_emsg = false; - if (len != NULL) { - *len = (size_t) ga.ga_len; - } - ga_append(&ga, '\0'); - return (char *) ga.ga_data; -} - -/// Return a string with the string representation of a variable. -/// Does not put quotes around strings, as ":echo" displays values. +/// This uses strtod(). setlocale(LC_NUMERIC, "C") has been used earlier to +/// make sure this always uses a decimal point. /// -/// @param[in] tv typval_T to convert. -/// @param[out] len Location where length of the result will be saved. +/// @param[in] text String to convert. +/// @param[out] ret_value Location where conversion result is saved. /// -/// @return String representation of the variable or NULL. -static char *echo_string(typval_T *tv, size_t *len) - FUNC_ATTR_NONNULL_ARG(1) FUNC_ATTR_MALLOC -{ - garray_T ga; - ga_init(&ga, (int)sizeof(char), 80); - if (tv->v_type == VAR_STRING || tv->v_type == VAR_FUNC) { - if (tv->vval.v_string != NULL) { - ga_concat(&ga, tv->vval.v_string); - } - } else { - vim_to_echo(&ga, tv, ":echo argument"); - did_echo_string_emsg = false; - } - if (len != NULL) { - *len = (size_t) ga.ga_len; - } - ga_append(&ga, '\0'); - return (char *) ga.ga_data; -} - -/* - * Convert the string "text" to a floating point number. - * This uses strtod(). setlocale(LC_NUMERIC, "C") has been used to make sure - * this always uses a decimal point. - * Returns the length of the text that was consumed. - */ -static int -string2float ( - char_u *text, - float_T *value /* result stored here */ -) +/// @return Length of the text that was consumed. +size_t string2float(const char *const text, float_T *const ret_value) + FUNC_ATTR_NONNULL_ALL { - char *s = (char *)text; - float_T f; + char *s = NULL; - f = strtod(s, &s); - *value = f; - return (int)((char_u *)s - text); + *ret_value = strtod(text, &s); + return (size_t) (s - text); } /// Get the value of an environment variable. @@ -7179,7 +6656,7 @@ static struct fst { } functions[] = { { "abs", 1, 1, f_abs }, - { "acos", 1, 1, f_acos }, // WJMc + { "acos", 1, 1, f_acos }, // WJMc { "add", 2, 2, f_add }, { "and", 2, 2, f_and }, { "append", 2, 2, f_append }, @@ -7196,9 +6673,9 @@ static struct fst { { "browse", 4, 4, f_browse }, { "browsedir", 2, 2, f_browsedir }, { "bufexists", 1, 1, f_bufexists }, - { "buffer_exists", 1, 1, f_bufexists }, // obsolete - { "buffer_name", 1, 1, f_bufname }, // obsolete - { "buffer_number", 1, 1, f_bufnr }, // obsolete + { "buffer_exists", 1, 1, f_bufexists }, // obsolete + { "buffer_name", 1, 1, f_bufname }, // obsolete + { "buffer_number", 1, 1, f_bufnr }, // obsolete { "buflisted", 1, 1, f_buflisted }, { "bufloaded", 1, 1, f_bufloaded }, { "bufname", 1, 1, f_bufname }, @@ -7225,7 +6702,7 @@ static struct fst { { "cscope_connection", 0, 3, f_cscope_connection }, { "cursor", 1, 3, f_cursor }, { "deepcopy", 1, 2, f_deepcopy }, - { "delete", 1, 1, f_delete }, + { "delete", 1, 2, f_delete }, { "dictwatcheradd", 3, 3, f_dictwatcheradd }, { "dictwatcherdel", 3, 3, f_dictwatcherdel }, { "did_filetype", 0, 0, f_did_filetype }, @@ -7242,7 +6719,7 @@ static struct fst { { "expand", 1, 3, f_expand }, { "extend", 2, 3, f_extend }, { "feedkeys", 1, 2, f_feedkeys }, - { "file_readable", 1, 1, f_filereadable }, // obsolete + { "file_readable", 1, 1, f_filereadable }, // obsolete { "filereadable", 1, 1, f_filereadable }, { "filewritable", 1, 1, f_filewritable }, { "filter", 2, 2, f_filter }, @@ -7272,7 +6749,7 @@ static struct fst { { "getcmdtype", 0, 0, f_getcmdtype }, { "getcmdwintype", 0, 0, f_getcmdwintype }, { "getcurpos", 0, 0, f_getcurpos }, - { "getcwd", 0, 0, f_getcwd }, + { "getcwd", 0, 2, f_getcwd }, { "getfontname", 0, 1, f_getfontname }, { "getfperm", 1, 1, f_getfperm }, { "getfsize", 1, 1, f_getfsize }, @@ -7296,7 +6773,7 @@ static struct fst { { "globpath", 2, 5, f_globpath }, { "has", 1, 1, f_has }, { "has_key", 2, 2, f_has_key }, - { "haslocaldir", 0, 0, f_haslocaldir }, + { "haslocaldir", 0, 2, f_haslocaldir }, { "hasmapto", 1, 3, f_hasmapto }, { "highlightID", 1, 1, f_hlID }, // obsolete { "highlight_exists", 1, 1, f_hlexists }, // obsolete @@ -7329,6 +6806,8 @@ static struct fst { { "jobstop", 1, 1, f_jobstop }, { "jobwait", 1, 2, f_jobwait }, { "join", 1, 2, f_join }, + { "json_decode", 1, 1, f_json_decode }, + { "json_encode", 1, 1, f_json_encode }, { "keys", 1, 1, f_keys }, { "last_buffer_nr", 0, 0, f_last_buffer_nr }, // obsolete { "len", 1, 1, f_len }, @@ -8103,19 +7582,19 @@ static void fill_assert_error(garray_T *gap, typval_T *opt_msg_tv, char_u *tofree; if (opt_msg_tv->v_type != VAR_UNKNOWN) { - tofree = (char_u *) tv2string(opt_msg_tv, NULL); + tofree = (char_u *) encode_tv2string(opt_msg_tv, NULL); ga_concat(gap, tofree); xfree(tofree); } else { ga_concat(gap, (char_u *)"Expected "); if (exp_str == NULL) { - tofree = (char_u *) tv2string(exp_tv, NULL); + tofree = (char_u *) encode_tv2string(exp_tv, NULL); ga_concat(gap, tofree); xfree(tofree); } else { ga_concat(gap, exp_str); } - tofree = (char_u *) tv2string(got_tv, NULL); + tofree = (char_u *) encode_tv2string(got_tv, NULL); ga_concat(gap, (char_u *)" but got "); ga_concat(gap, tofree); xfree(tofree); @@ -8150,16 +7629,21 @@ static void f_assert_equal(typval_T *argvars, typval_T *rettv) } // Common for assert_true() and assert_false(). -static void assert_bool(typval_T *argvars, bool isTrue) +static void assert_bool(typval_T *argvars, bool is_true) { int error = (int)false; garray_T ga; - if (argvars[0].v_type != VAR_NUMBER || - (get_tv_number_chk(&argvars[0], &error) == 0) == isTrue || error) { + if ((argvars[0].v_type != VAR_NUMBER || + (get_tv_number_chk(&argvars[0], &error) == 0) == is_true || error) + && (argvars[0].v_type != VAR_SPECIAL + || (argvars[0].vval.v_special + != (SpecialVarValue) (is_true + ? kSpecialVarTrue + : kSpecialVarFalse)))) { prepare_assert_error(&ga); fill_assert_error(&ga, &argvars[1], - (char_u *)(isTrue ? "True" : "False"), + (char_u *)(is_true ? "True" : "False"), NULL, &argvars[0]); assert_error(&ga); ga_clear(&ga); @@ -8821,12 +8305,12 @@ static void f_cscope_connection(typval_T *argvars, typval_T *rettv) rettv->vval.v_number = cs_connection(num, dbpath, prepend); } -/* - * "cursor(lnum, col)" function - * - * Moves the cursor to the specified line and column. - * Returns 0 when the position could be set, -1 otherwise. - */ +/// "cursor(lnum, col)" function, or +/// "cursor(list)" +/// +/// Moves the cursor to the specified line and column. +/// +/// @returns 0 when the position could be set, -1 otherwise. static void f_cursor(typval_T *argvars, typval_T *rettv) { long line, col; @@ -8838,8 +8322,10 @@ static void f_cursor(typval_T *argvars, typval_T *rettv) colnr_T curswant = -1; if (list2fpos(argvars, &pos, NULL, &curswant) == FAIL) { + EMSG(_(e_invarg)); return; } + line = pos.lnum; col = pos.col; coladd = pos.coladd; @@ -8881,25 +8367,51 @@ static void f_deepcopy(typval_T *argvars, typval_T *rettv) if (argvars[1].v_type != VAR_UNKNOWN) noref = get_tv_number_chk(&argvars[1], NULL); - if (noref < 0 || noref > 1) + if (noref < 0 || noref > 1) { EMSG(_(e_invarg)); - else { - current_copyID += COPYID_INC; + } else { var_item_copy(NULL, &argvars[0], rettv, true, (noref == 0 - ? current_copyID + ? get_copyID() : 0)); } } -/* - * "delete()" function - */ +// "delete()" function static void f_delete(typval_T *argvars, typval_T *rettv) { - if (check_restricted() || check_secure()) - rettv->vval.v_number = -1; - else - rettv->vval.v_number = os_remove((char *)get_tv_string(&argvars[0])); + char_u nbuf[NUMBUFLEN]; + char_u *name; + char_u *flags; + + rettv->vval.v_number = -1; + if (check_restricted() || check_secure()) { + return; + } + + name = get_tv_string(&argvars[0]); + if (name == NULL || *name == NUL) { + EMSG(_(e_invarg)); + return; + } + + if (argvars[1].v_type != VAR_UNKNOWN) { + flags = get_tv_string_buf(&argvars[1], nbuf); + } else { + flags = (char_u *)""; + } + + if (*flags == NUL) { + // delete a file + rettv->vval.v_number = os_remove((char *)name) == 0 ? 0 : -1; + } else if (STRCMP(flags, "d") == 0) { + // delete an empty directory + rettv->vval.v_number = os_rmdir((char *)name) == 0 ? 0 : -1; + } else if (STRCMP(flags, "rf") == 0) { + // delete a directory recursively + rettv->vval.v_number = delete_recursive(name); + } else { + EMSG2(_(e_invexpr2), flags); + } } // dictwatcheradd(dict, key, funcref) function @@ -9076,7 +8588,7 @@ static void f_diff_hlID(typval_T *argvars, typval_T *rettv) */ static void f_empty(typval_T *argvars, typval_T *rettv) { - int n; + bool n = true; switch (argvars[0].v_type) { case VAR_STRING: @@ -9098,9 +8610,12 @@ static void f_empty(typval_T *argvars, typval_T *rettv) n = argvars[0].vval.v_dict == NULL || argvars[0].vval.v_dict->dv_hashtab.ht_used == 0; break; - default: - EMSG2(_(e_intern2), "f_empty()"); - n = 0; + case VAR_SPECIAL: + n = argvars[0].vval.v_special != kSpecialVarTrue; + break; + case VAR_UNKNOWN: + EMSG2(_(e_intern2), "f_empty(UNKNOWN)"); + break; } rettv->vval.v_number = n; @@ -10272,22 +9787,143 @@ static void f_getcmdwintype(typval_T *argvars, typval_T *rettv) rettv->vval.v_string[0] = cmdwin_type; } -/* - * "getcwd()" function - */ +/// `getcwd([{win}[, {tab}]])` function +/// +/// Every scope not specified implies the currently selected scope object. +/// +/// @pre The arguments must be of type number. +/// @pre There may not be more than two arguments. +/// @pre An argument may not be -1 if preceding arguments are not all -1. +/// +/// @post The return value will be a string. static void f_getcwd(typval_T *argvars, typval_T *rettv) { - char_u *cwd; + // Possible scope of working directory to return. + CdScope scope = kCdScopeWindow; + + // Numbers of the scope objects (window, tab) we want the working directory + // of. A `-1` means to skip this scope, a `0` means the current object. + int scope_number[] = { + [kCdScopeWindow] = 0, // Number of window to look at. + [kCdScopeTab ] = 0, // Number of tab to look at. + }; + + char_u *cwd = NULL; // Current working directory to print + char_u *from = NULL; // The original string to copy + + tabpage_T *tp = curtab; // The tabpage to look at. + win_T *win = curwin; // The window to look at. rettv->v_type = VAR_STRING; rettv->vval.v_string = NULL; + + // Pre-conditions and scope extraction together + for (int i = 0; i < kCdScopeGlobal; i++) { + if (argvars[i].v_type == VAR_UNKNOWN) { + break; + } + if (argvars[i].v_type != VAR_NUMBER) { + EMSG(_(e_invarg)); + return; + } + scope_number[i] = argvars[i].vval.v_number; + // The scope is the current iteration step. + scope = i; + + if (scope_number[i] < -1) { + EMSG(_(e_invarg)); + return; + } + } + + // Allocate and initialize the string to return. cwd = xmalloc(MAXPATHL); - if (os_dirname(cwd, MAXPATHL) != FAIL) { - rettv->vval.v_string = vim_strsave(cwd); + + // Get the scope and numbers from the arguments + for (int i = 0; i < MAX_CD_SCOPE; i++) { + // If there is no argument there are no more scopes after it, break out. + if (argvars[i].v_type == VAR_UNKNOWN) { + break; + } + scope_number[i] = argvars[i].vval.v_number; + // The scope is the current iteration step. + scope = i; + // It is an error for the scope number to be less than `-1`. + if (scope_number[i] < -1) { + EMSG(_(e_invarg)); + goto end; + } + } + + // If the deepest scope number is `-1` advance the scope. + if (scope_number[scope] < 0) { + scope++; + } + + // Find the tabpage by number + if (scope_number[kCdScopeTab] == -1) { + tp = NULL; + } else if (scope_number[kCdScopeTab] > 0) { + tp = find_tabpage(scope_number[kCdScopeTab]); + if (!tp) { + EMSG(_("E5000: Cannot find tab number.")); + goto end; + } + } + + // Find the window in `tp` by number, `NULL` if none. + if (scope_number[kCdScopeWindow] == -1) { + win = NULL; + } else if (scope_number[kCdScopeWindow] >= 0) { + if (!tp) { + EMSG(_("E5001: Higher scope cannot be -1 if lower scope is >= 0.")); + goto end; + } + + if (scope_number[kCdScopeWindow] > 0) { + win = find_win_by_nr(&argvars[0], curtab); + if (!win) { + EMSG(_("E5002: Cannot find window number.")); + goto end; + } + } + } + + switch (scope) { + case kCdScopeWindow: + from = win->w_localdir; + if (from) { + break; + } + case kCdScopeTab: // FALLTHROUGH + from = tp->localdir; + if (from) { + break; + } + case kCdScopeGlobal: // FALLTHROUGH + // The `globaldir` variable is not always set. + if (globaldir) { + from = globaldir; + } else { + // Copy the OS path directly into output string and jump to the end. + if (os_dirname(cwd, MAXPATHL) == FAIL) { + EMSG(_("E41: Could not display path.")); + goto end; + } + } + break; + } + + if (from) { + xstrlcpy((char *)cwd, (char *)from, MAXPATHL); + } + + rettv->vval.v_string = vim_strsave(cwd); #ifdef BACKSLASH_IN_FILENAME - slash_adjust(rettv->vval.v_string); + slash_adjust(rettv->vval.v_string); #endif - } + +end: xfree(cwd); } @@ -10610,9 +10246,10 @@ static void f_getreg(typval_T *argvars, typval_T *rettv) rettv->v_type = VAR_LIST; rettv->vval.v_list = get_reg_contents(regname, (arg2 ? kGRegExprSrc : 0) | kGRegList); - if (rettv->vval.v_list != NULL) { - rettv->vval.v_list->lv_refcount++; + if (rettv->vval.v_list == NULL) { + rettv->vval.v_list = list_alloc(); } + rettv->vval.v_list->lv_refcount++; } else { rettv->v_type = VAR_STRING; rettv->vval.v_string = get_reg_contents(regname, arg2 ? kGRegExprSrc : 0); @@ -11106,12 +10743,98 @@ static void f_has_key(typval_T *argvars, typval_T *rettv) get_tv_string(&argvars[1]), -1) != NULL; } -/* - * "haslocaldir()" function - */ +/// `haslocaldir([{win}[, {tab}]])` function +/// +/// Returns `1` if the scope object has a local directory, `0` otherwise. If a +/// scope object is not specified the current one is implied. This function +/// share a lot of code with `f_getcwd`. +/// +/// @pre The arguments must be of type number. +/// @pre There may not be more than two arguments. +/// @pre An argument may not be -1 if preceding arguments are not all -1. +/// +/// @post The return value will be either the number `1` or `0`. static void f_haslocaldir(typval_T *argvars, typval_T *rettv) { - rettv->vval.v_number = (curwin->w_localdir != NULL); + // Possible scope of working directory to return. + CdScope scope = kCdScopeWindow; + + // Numbers of the scope objects (window, tab) we want the working directory + // of. A `-1` means to skip this scope, a `0` means the current object. + int scope_number[] = { + [kCdScopeWindow] = 0, // Number of window to look at. + [kCdScopeTab ] = 0, // Number of tab 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; + rettv->vval.v_number = 0; + + // Pre-conditions and scope extraction together + for (int i = 0; i < kCdScopeGlobal; i++) { + if (argvars[i].v_type == VAR_UNKNOWN) { + break; + } + if (argvars[i].v_type != VAR_NUMBER) { + EMSG(_(e_invarg)); + return; + } + scope_number[i] = argvars[i].vval.v_number; + // The scope is the current iteration step. + scope = i; + if (scope_number[i] < -1) { + EMSG(_(e_invarg)); + return; + } + } + + // It the deepest scope number is `-1` advance the scope by one. + if (scope_number[scope] < 0) { + ++scope; + } + + // Find the tabpage by number + if (scope_number[kCdScopeTab] == -1) { + tp = NULL; + } else if (scope_number[kCdScopeTab] > 0) { + tp = find_tabpage(scope_number[kCdScopeTab]); + if (!tp) { + EMSG(_("5000: Cannot find tab number.")); + return; + } + } + + // Find the window in `tp` by number, `NULL` if none. + if (scope_number[kCdScopeWindow] == -1) { + win = NULL; + } else if (scope_number[kCdScopeWindow] >= 0) { + if (!tp) { + EMSG(_("E5001: Higher scope cannot be -1 if lower scope is >= 0.")); + return; + } + + if (scope_number[kCdScopeWindow] > 0) { + win = find_win_by_nr(&argvars[0], curtab); + if (!win) { + EMSG(_("E5002: Cannot find window number.")); + return; + } + } + } + + switch (scope) { + case kCdScopeWindow: + rettv->vval.v_number = win->w_localdir ? 1 : 0; + break; + case kCdScopeTab: + rettv->vval.v_number = tp->localdir ? 1 : 0; + break; + case kCdScopeGlobal: + assert(0); + break; + } } /* @@ -12124,6 +11847,47 @@ static void f_join(typval_T *argvars, typval_T *rettv) rettv->vval.v_string = NULL; } +/// json_decode() function +static void f_json_decode(typval_T *argvars, typval_T *rettv) +{ + char numbuf[NUMBUFLEN]; + char *s = NULL; + char *tofree = NULL; + size_t len; + if (argvars[0].v_type == VAR_LIST) { + if (!encode_vim_list_to_buf(argvars[0].vval.v_list, &len, &s)) { + EMSG(_("E474: Failed to convert list to string")); + return; + } + tofree = s; + if (s == NULL) { + assert(len == 0); + s = ""; + } + } else { + s = (char *) get_tv_string_buf_chk(&argvars[0], (char_u *) numbuf); + if (s) { + len = strlen(s); + } else { + return; + } + } + if (json_decode_string(s, len, rettv) == FAIL) { + emsgf(_("E474: Failed to parse %.*s"), (int) len, s); + rettv->v_type = VAR_NUMBER; + rettv->vval.v_number = 0; + } + assert(rettv->v_type != VAR_UNKNOWN); + xfree(tofree); +} + +/// json_encode() function +static void f_json_encode(typval_T *argvars, typval_T *rettv) +{ + rettv->v_type = VAR_STRING; + rettv->vval.v_string = (char_u *) encode_tv2json(&argvars[0], NULL); +} + /* * "keys()" function */ @@ -12165,7 +11929,10 @@ static void f_len(typval_T *argvars, typval_T *rettv) case VAR_DICT: rettv->vval.v_number = dict_len(argvars[0].vval.v_dict); break; - default: + case VAR_UNKNOWN: + case VAR_SPECIAL: + case VAR_FLOAT: + case VAR_FUNC: EMSG(_("E701: Invalid type for len()")); break; } @@ -12489,9 +12256,10 @@ static void find_some_match(typval_T *argvars, typval_T *rettv, int type) break; } xfree(tofree); - tofree = str = (char_u *) echo_string(&li->li_tv, NULL); - if (str == NULL) + tofree = str = (char_u *) encode_tv2echo(&li->li_tv, NULL); + if (str == NULL) { break; + } } match = vim_regexec_nl(®match, str, (colnr_T)startcol); @@ -12889,289 +12657,6 @@ static void f_mode(typval_T *argvars, typval_T *rettv) rettv->v_type = VAR_STRING; } -/// Msgpack callback for writing to readfile()-style list -static int msgpack_list_write(void *data, const char *buf, size_t len) -{ - if (len == 0) { - return 0; - } - list_T *const list = (list_T *) data; - const char *const end = buf + len; - const char *line_end = buf; - if (list->lv_last == NULL) { - list_append_string(list, NULL, 0); - } - listitem_T *li = list->lv_last; - do { - const char *line_start = line_end; - line_end = xmemscan(line_start, NL, end - line_start); - if (line_end == line_start) { - list_append_allocated_string(list, NULL); - } else { - const size_t line_length = line_end - line_start; - char *str; - if (li == NULL) { - str = xmemdupz(line_start, line_length); - } else { - const size_t li_len = (li->li_tv.vval.v_string == NULL - ? 0 - : STRLEN(li->li_tv.vval.v_string)); - li->li_tv.vval.v_string = xrealloc(li->li_tv.vval.v_string, - li_len + line_length + 1); - str = (char *) li->li_tv.vval.v_string + li_len; - memmove(str, line_start, line_length); - str[line_length] = 0; - } - for (size_t i = 0; i < line_length; i++) { - if (str[i] == NUL) { - str[i] = NL; - } - } - if (li == NULL) { - list_append_allocated_string(list, str); - } else { - li = NULL; - } - if (line_end == end - 1) { - list_append_allocated_string(list, NULL); - } - } - line_end++; - } while (line_end < end); - return 0; -} - -/// Convert readfile()-style list to a char * buffer with length -/// -/// @param[in] list Converted list. -/// @param[out] ret_len Resulting buffer length. -/// @param[out] ret_buf Allocated buffer with the result or NULL if ret_len is -/// zero. -/// -/// @return true in case of success, false in case of failure. -static inline bool vim_list_to_buf(const list_T *const list, - size_t *const ret_len, char **const ret_buf) - FUNC_ATTR_NONNULL_ARG(2,3) FUNC_ATTR_WARN_UNUSED_RESULT -{ - size_t len = 0; - if (list != NULL) { - for (const listitem_T *li = list->lv_first; - li != NULL; - li = li->li_next) { - if (li->li_tv.v_type != VAR_STRING) { - return false; - } - len++; - if (li->li_tv.vval.v_string != 0) { - len += STRLEN(li->li_tv.vval.v_string); - } - } - if (len) { - len--; - } - } - *ret_len = len; - if (len == 0) { - *ret_buf = NULL; - return true; - } - ListReaderState lrstate = init_lrstate(list); - char *const buf = xmalloc(len); - size_t read_bytes; - if (read_from_list(&lrstate, buf, len, &read_bytes) != OK) { - assert(false); - } - assert(len == read_bytes); - *ret_buf = buf; - return true; -} - -/// Show a error message when converting to msgpack value -/// -/// @param[in] msg Error message to dump. Must contain exactly two %s that -/// will be replaced with what was being dumped: first with -/// something like “F” or “function argument”, second with path -/// to the failed value. -/// @param[in] mpstack Path to the failed value. -/// @param[in] objname Dumped object name. -/// -/// @return FAIL. -static int conv_error(const char *const msg, const MPConvStack *const mpstack, - const char *const objname) - FUNC_ATTR_NONNULL_ALL -{ - garray_T msg_ga; - ga_init(&msg_ga, (int)sizeof(char), 80); - char *const key_msg = _("key %s"); - char *const key_pair_msg = _("key %s at index %i from special map"); - char *const idx_msg = _("index %i"); - for (size_t i = 0; i < kv_size(*mpstack); i++) { - if (i != 0) { - ga_concat(&msg_ga, (char_u *) ", "); - } - MPConvStackVal v = kv_A(*mpstack, i); - switch (v.type) { - case kMPConvDict: { - typval_T key_tv = { - .v_type = VAR_STRING, - .vval = { .v_string = (v.data.d.hi == NULL - ? v.data.d.dict->dv_hashtab.ht_array - : (v.data.d.hi - 1))->hi_key }, - }; - char *const key = tv2string(&key_tv, NULL); - vim_snprintf((char *) IObuff, IOSIZE, key_msg, key); - xfree(key); - ga_concat(&msg_ga, IObuff); - break; - } - case kMPConvPairs: - case kMPConvList: { - int idx = 0; - const listitem_T *li; - for (li = v.data.l.list->lv_first; - li != NULL && li->li_next != v.data.l.li; - li = li->li_next) { - idx++; - } - if (v.type == kMPConvList - || li == NULL - || (li->li_tv.v_type != VAR_LIST - && li->li_tv.vval.v_list->lv_len <= 0)) { - vim_snprintf((char *) IObuff, IOSIZE, idx_msg, idx); - ga_concat(&msg_ga, IObuff); - } else { - typval_T key_tv = li->li_tv.vval.v_list->lv_first->li_tv; - char *const key = echo_string(&key_tv, NULL); - vim_snprintf((char *) IObuff, IOSIZE, key_pair_msg, key, idx); - xfree(key); - ga_concat(&msg_ga, IObuff); - } - break; - } - } - } - EMSG3(msg, objname, (kv_size(*mpstack) == 0 - ? _("itself") - : (char *) msg_ga.ga_data)); - ga_clear(&msg_ga); - return FAIL; -} - -#define CONV_STRING(buf, len) \ - do { \ - if (buf == NULL) { \ - msgpack_pack_bin(packer, 0); \ - } else { \ - const size_t len_ = (len); \ - msgpack_pack_bin(packer, len_); \ - msgpack_pack_bin_body(packer, buf, len_); \ - } \ - } while (0) - -#define CONV_STR_STRING(buf, len) \ - do { \ - if (buf == NULL) { \ - msgpack_pack_str(packer, 0); \ - } else { \ - const size_t len_ = (len); \ - msgpack_pack_str(packer, len_); \ - msgpack_pack_str_body(packer, buf, len_); \ - } \ - } while (0) - -#define CONV_EXT_STRING(buf, len, type) \ - do { \ - if (buf == NULL) { \ - msgpack_pack_ext(packer, 0, type); \ - } else { \ - const size_t len_ = (len); \ - msgpack_pack_ext(packer, len_, (int8_t) type); \ - msgpack_pack_ext_body(packer, buf, len_); \ - } \ - } while (0) - -#define CONV_NUMBER(num) \ - msgpack_pack_int64(packer, (int64_t) (num)) - -#define CONV_FLOAT(flt) \ - msgpack_pack_double(packer, (double) (flt)) - -#define CONV_FUNC(fun) \ - return conv_error(_("E951: Error while dumping %s, %s: " \ - "attempt to dump function reference"), \ - mpstack, objname) - -#define CONV_EMPTY_LIST() \ - msgpack_pack_array(packer, 0) - -#define CONV_LIST_START(lst) \ - msgpack_pack_array(packer, (lst)->lv_len) - -#define CONV_EMPTY_DICT() \ - msgpack_pack_map(packer, 0) - -#define CONV_SPECIAL_NIL() \ - msgpack_pack_nil(packer) - -#define CONV_SPECIAL_BOOL(num) \ - do { \ - if ((num)) { \ - msgpack_pack_true(packer); \ - } else { \ - msgpack_pack_false(packer); \ - } \ - } while (0) - -#define CONV_UNSIGNED_NUMBER(num) \ - msgpack_pack_uint64(packer, (num)) - -#define CONV_SPECIAL_MAP_START(lst) \ - msgpack_pack_map(packer, (lst)->lv_len) - -#define CONV_DICT_START(dct) \ - msgpack_pack_map(packer, (dct)->dv_hashtab.ht_used) - -#define CONV_DICT_END(dct) - -#define CONV_DICT_AFTER_KEY(dct) - -#define CONV_DICT_BETWEEN_ITEMS(dct) - -#define CONV_LIST_END(lst) - -#define CONV_LIST_BETWEEN_ITEMS(lst) - -#define CONV_RECURSE(val, conv_type) \ - return conv_error(_("E952: Unable to dump %s: " \ - "container references itself in %s"), \ - mpstack, objname) - -#define CONV_ALLOW_SPECIAL true - -DEFINE_VIML_CONV_FUNCTIONS(, msgpack, msgpack_packer *const, packer) - -#undef CONV_STRING -#undef CONV_STR_STRING -#undef CONV_EXT_STRING -#undef CONV_NUMBER -#undef CONV_FLOAT -#undef CONV_FUNC -#undef CONV_EMPTY_LIST -#undef CONV_LIST_START -#undef CONV_EMPTY_DICT -#undef CONV_SPECIAL_NIL -#undef CONV_SPECIAL_BOOL -#undef CONV_UNSIGNED_NUMBER -#undef CONV_SPECIAL_MAP_START -#undef CONV_DICT_START -#undef CONV_DICT_END -#undef CONV_DICT_AFTER_KEY -#undef CONV_DICT_BETWEEN_ITEMS -#undef CONV_LIST_END -#undef CONV_LIST_BETWEEN_ITEMS -#undef CONV_RECURSE -#undef CONV_ALLOW_SPECIAL - /// "msgpackdump()" function static void f_msgpackdump(typval_T *argvars, typval_T *rettv) FUNC_ATTR_NONNULL_ALL @@ -13185,7 +12670,7 @@ static void f_msgpackdump(typval_T *argvars, typval_T *rettv) if (list == NULL) { return; } - msgpack_packer *lpacker = msgpack_packer_new(ret_list, &msgpack_list_write); + msgpack_packer *lpacker = msgpack_packer_new(ret_list, &encode_list_write); const char *const msg = _("msgpackdump() argument, index %i"); // Assume that translation will not take more then 4 times more space char msgbuf[sizeof("msgpackdump() argument, index ") * 4 + NUMBUFLEN]; @@ -13193,307 +12678,13 @@ static void f_msgpackdump(typval_T *argvars, typval_T *rettv) for (listitem_T *li = list->lv_first; li != NULL; li = li->li_next) { vim_snprintf(msgbuf, sizeof(msgbuf), (char *) msg, idx); idx++; - if (vim_to_msgpack(lpacker, &li->li_tv, msgbuf) == FAIL) { + if (encode_vim_to_msgpack(lpacker, &li->li_tv, msgbuf) == FAIL) { break; } } msgpack_packer_free(lpacker); } -/// Read bytes from list -/// -/// @param[in,out] state Structure describing position in list from which -/// reading should start. Is updated to reflect position -/// at which reading ended. -/// @param[out] buf Buffer to write to. -/// @param[in] nbuf Buffer length. -/// @param[out] read_bytes Is set to amount of bytes read. -/// -/// @return OK when reading was finished, FAIL in case of error (i.e. list item -/// was not a string), NOTDONE if reading was successfull, but there are -/// more bytes to read. -static int read_from_list(ListReaderState *const state, char *const buf, - const size_t nbuf, size_t *const read_bytes) - FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT -{ - char *const buf_end = buf + nbuf; - char *p = buf; - while (p < buf_end) { - for (size_t i = state->offset; i < state->li_length && p < buf_end; i++) { - const char ch = state->li->li_tv.vval.v_string[state->offset++]; - *p++ = (ch == NL ? NUL : ch); - } - if (p < buf_end) { - state->li = state->li->li_next; - if (state->li == NULL) { - *read_bytes = (size_t) (p - buf); - return OK; - } - *p++ = NL; - if (state->li->li_tv.v_type != VAR_STRING) { - *read_bytes = (size_t) (p - buf); - return FAIL; - } - state->offset = 0; - state->li_length = (state->li->li_tv.vval.v_string == NULL - ? 0 - : STRLEN(state->li->li_tv.vval.v_string)); - } - } - *read_bytes = nbuf; - return (state->offset < state->li_length || state->li->li_next != NULL - ? NOTDONE - : OK); -} - -/// Initialize ListReaderState structure -static inline ListReaderState init_lrstate(const list_T *const list) - FUNC_ATTR_NONNULL_ALL -{ - return (ListReaderState) { - .li = list->lv_first, - .offset = 0, - .li_length = (list->lv_first->li_tv.vval.v_string == NULL - ? 0 - : STRLEN(list->lv_first->li_tv.vval.v_string)), - }; -} - -/// Convert msgpack object to a VimL one -int msgpack_to_vim(const msgpack_object mobj, typval_T *const rettv) - FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT -{ -#define INIT_SPECIAL_DICT(tv, type, val) \ - do { \ - dict_T *const dict = dict_alloc(); \ - dictitem_T *const type_di = dictitem_alloc((char_u *) "_TYPE"); \ - type_di->di_tv.v_type = VAR_LIST; \ - type_di->di_tv.v_lock = 0; \ - type_di->di_tv.vval.v_list = (list_T *) msgpack_type_lists[type]; \ - type_di->di_tv.vval.v_list->lv_refcount++; \ - dict_add(dict, type_di); \ - dictitem_T *const val_di = dictitem_alloc((char_u *) "_VAL"); \ - val_di->di_tv = val; \ - dict_add(dict, val_di); \ - tv->v_type = VAR_DICT; \ - dict->dv_refcount++; \ - tv->vval.v_dict = dict; \ - } while (0) - switch (mobj.type) { - case MSGPACK_OBJECT_NIL: { - INIT_SPECIAL_DICT(rettv, kMPNil, ((typval_T) { - .v_type = VAR_NUMBER, - .v_lock = 0, - .vval = { .v_number = 0 }, - })); - break; - } - case MSGPACK_OBJECT_BOOLEAN: { - INIT_SPECIAL_DICT(rettv, kMPBoolean, - ((typval_T) { - .v_type = VAR_NUMBER, - .v_lock = 0, - .vval = { - .v_number = (varnumber_T) mobj.via.boolean, - }, - })); - break; - } - case MSGPACK_OBJECT_POSITIVE_INTEGER: { - if (mobj.via.u64 <= VARNUMBER_MAX) { - *rettv = (typval_T) { - .v_type = VAR_NUMBER, - .v_lock = 0, - .vval = { .v_number = (varnumber_T) mobj.via.u64 }, - }; - } else { - list_T *const list = list_alloc(); - list->lv_refcount++; - INIT_SPECIAL_DICT(rettv, kMPInteger, - ((typval_T) { - .v_type = VAR_LIST, - .v_lock = 0, - .vval = { .v_list = list }, - })); - uint64_t n = mobj.via.u64; - list_append_number(list, 1); - list_append_number(list, (varnumber_T) ((n >> 62) & 0x3)); - list_append_number(list, (varnumber_T) ((n >> 31) & 0x7FFFFFFF)); - list_append_number(list, (varnumber_T) (n & 0x7FFFFFFF)); - } - break; - } - case MSGPACK_OBJECT_NEGATIVE_INTEGER: { - if (mobj.via.i64 >= VARNUMBER_MIN) { - *rettv = (typval_T) { - .v_type = VAR_NUMBER, - .v_lock = 0, - .vval = { .v_number = (varnumber_T) mobj.via.i64 }, - }; - } else { - list_T *const list = list_alloc(); - list->lv_refcount++; - INIT_SPECIAL_DICT(rettv, kMPInteger, - ((typval_T) { - .v_type = VAR_LIST, - .v_lock = 0, - .vval = { .v_list = list }, - })); - uint64_t n = -((uint64_t) mobj.via.i64); - list_append_number(list, -1); - list_append_number(list, (varnumber_T) ((n >> 62) & 0x3)); - list_append_number(list, (varnumber_T) ((n >> 31) & 0x7FFFFFFF)); - list_append_number(list, (varnumber_T) (n & 0x7FFFFFFF)); - } - break; - } - case MSGPACK_OBJECT_FLOAT: { - *rettv = (typval_T) { - .v_type = VAR_FLOAT, - .v_lock = 0, - .vval = { .v_float = mobj.via.f64 }, - }; - break; - } - case MSGPACK_OBJECT_STR: { - list_T *const list = list_alloc(); - list->lv_refcount++; - INIT_SPECIAL_DICT(rettv, kMPString, - ((typval_T) { - .v_type = VAR_LIST, - .v_lock = 0, - .vval = { .v_list = list }, - })); - if (msgpack_list_write((void *) list, mobj.via.str.ptr, mobj.via.str.size) - == -1) { - return FAIL; - } - break; - } - case MSGPACK_OBJECT_BIN: { - if (memchr(mobj.via.bin.ptr, NUL, mobj.via.bin.size) == NULL) { - *rettv = (typval_T) { - .v_type = VAR_STRING, - .v_lock = 0, - .vval = { .v_string = xmemdupz(mobj.via.bin.ptr, mobj.via.bin.size) }, - }; - break; - } - list_T *const list = list_alloc(); - list->lv_refcount++; - INIT_SPECIAL_DICT(rettv, kMPBinary, - ((typval_T) { - .v_type = VAR_LIST, - .v_lock = 0, - .vval = { .v_list = list }, - })); - if (msgpack_list_write((void *) list, mobj.via.bin.ptr, mobj.via.bin.size) - == -1) { - return FAIL; - } - break; - } - case MSGPACK_OBJECT_ARRAY: { - list_T *const list = list_alloc(); - list->lv_refcount++; - *rettv = (typval_T) { - .v_type = VAR_LIST, - .v_lock = 0, - .vval = { .v_list = list }, - }; - for (size_t i = 0; i < mobj.via.array.size; i++) { - listitem_T *const li = listitem_alloc(); - li->li_tv.v_type = VAR_UNKNOWN; - list_append(list, li); - if (msgpack_to_vim(mobj.via.array.ptr[i], &li->li_tv) == FAIL) { - return FAIL; - } - } - break; - } - case MSGPACK_OBJECT_MAP: { - for (size_t i = 0; i < mobj.via.map.size; i++) { - if (mobj.via.map.ptr[i].key.type != MSGPACK_OBJECT_STR - || mobj.via.map.ptr[i].key.via.str.size == 0 - || memchr(mobj.via.map.ptr[i].key.via.str.ptr, NUL, - mobj.via.map.ptr[i].key.via.str.size) != NULL) { - goto msgpack_to_vim_generic_map; - } - } - dict_T *const dict = dict_alloc(); - dict->dv_refcount++; - *rettv = (typval_T) { - .v_type = VAR_DICT, - .v_lock = 0, - .vval = { .v_dict = dict }, - }; - for (size_t i = 0; i < mobj.via.map.size; i++) { - dictitem_T *const di = xmallocz(offsetof(dictitem_T, di_key) - + mobj.via.map.ptr[i].key.via.str.size); - memcpy(&di->di_key[0], mobj.via.map.ptr[i].key.via.str.ptr, - mobj.via.map.ptr[i].key.via.str.size); - di->di_tv.v_type = VAR_UNKNOWN; - if (dict_add(dict, di) == FAIL) { - // Duplicate key: fallback to generic map - clear_tv(rettv); - xfree(di); - goto msgpack_to_vim_generic_map; - } - if (msgpack_to_vim(mobj.via.map.ptr[i].val, &di->di_tv) == FAIL) { - return FAIL; - } - } - break; -msgpack_to_vim_generic_map: {} - list_T *const list = list_alloc(); - list->lv_refcount++; - INIT_SPECIAL_DICT(rettv, kMPMap, - ((typval_T) { - .v_type = VAR_LIST, - .v_lock = 0, - .vval = { .v_list = list }, - })); - for (size_t i = 0; i < mobj.via.map.size; i++) { - list_T *const kv_pair = list_alloc(); - list_append_list(list, kv_pair); - listitem_T *const key_li = listitem_alloc(); - key_li->li_tv.v_type = VAR_UNKNOWN; - list_append(kv_pair, key_li); - listitem_T *const val_li = listitem_alloc(); - val_li->li_tv.v_type = VAR_UNKNOWN; - list_append(kv_pair, val_li); - if (msgpack_to_vim(mobj.via.map.ptr[i].key, &key_li->li_tv) == FAIL) { - return FAIL; - } - if (msgpack_to_vim(mobj.via.map.ptr[i].val, &val_li->li_tv) == FAIL) { - return FAIL; - } - } - break; - } - case MSGPACK_OBJECT_EXT: { - list_T *const list = list_alloc(); - list->lv_refcount++; - list_append_number(list, mobj.via.ext.type); - list_T *const ext_val_list = list_alloc(); - list_append_list(list, ext_val_list); - INIT_SPECIAL_DICT(rettv, kMPExt, - ((typval_T) { - .v_type = VAR_LIST, - .v_lock = 0, - .vval = { .v_list = list }, - })); - if (msgpack_list_write((void *) ext_val_list, mobj.via.ext.ptr, - mobj.via.ext.size) == -1) { - return FAIL; - } - break; - } - } -#undef INIT_SPECIAL_DICT - return OK; -} - /// "msgpackparse" function static void f_msgpackparse(typval_T *argvars, typval_T *rettv) FUNC_ATTR_NONNULL_ALL @@ -13511,7 +12702,7 @@ static void f_msgpackparse(typval_T *argvars, typval_T *rettv) EMSG2(_(e_invarg2), "List item is not a string"); return; } - ListReaderState lrstate = init_lrstate(list); + ListReaderState lrstate = encode_init_lrstate(list); msgpack_unpacker *const unpacker = msgpack_unpacker_new(IOSIZE); if (unpacker == NULL) { EMSG(_(e_outofmem)); @@ -13525,7 +12716,7 @@ static void f_msgpackparse(typval_T *argvars, typval_T *rettv) goto f_msgpackparse_exit; } size_t read_bytes; - const int rlret = read_from_list( + const int rlret = encode_read_from_list( &lrstate, msgpack_unpacker_buffer(unpacker), IOSIZE, &read_bytes); if (rlret == FAIL) { EMSG2(_(e_invarg2), "List item is not a string"); @@ -14354,14 +13545,14 @@ static void f_reverse(typval_T *argvars, typval_T *rettv) } } -#define SP_NOMOVE 0x01 /* don't move cursor */ -#define SP_REPEAT 0x02 /* repeat to find outer pair */ -#define SP_RETCOUNT 0x04 /* return matchcount */ -#define SP_SETPCMARK 0x08 /* set previous context mark */ -#define SP_START 0x10 /* accept match at start position */ -#define SP_SUBPAT 0x20 /* return nr of matching sub-pattern */ -#define SP_END 0x40 /* leave cursor at end of match */ - +#define SP_NOMOVE 0x01 ///< don't move cursor +#define SP_REPEAT 0x02 ///< repeat to find outer pair +#define SP_RETCOUNT 0x04 ///< return matchcount +#define SP_SETPCMARK 0x08 ///< set previous context mark +#define SP_START 0x10 ///< accept match at start position +#define SP_SUBPAT 0x20 ///< return nr of matching sub-pattern +#define SP_END 0x40 ///< leave cursor at end of match +#define SP_COLUMN 0x80 ///< start at cursor column /* * Get flags for a search function. @@ -14387,13 +13578,14 @@ static int get_search_arg(typval_T *varp, int *flagsp) default: mask = 0; if (flagsp != NULL) switch (*flags) { - case 'c': mask = SP_START; break; - case 'e': mask = SP_END; break; - case 'm': mask = SP_RETCOUNT; break; - case 'n': mask = SP_NOMOVE; break; - case 'p': mask = SP_SUBPAT; break; - case 'r': mask = SP_REPEAT; break; - case 's': mask = SP_SETPCMARK; break; + case 'c': mask = SP_START; break; + case 'e': mask = SP_END; break; + case 'm': mask = SP_RETCOUNT; break; + case 'n': mask = SP_NOMOVE; break; + case 'p': mask = SP_SUBPAT; break; + case 'r': mask = SP_REPEAT; break; + case 's': mask = SP_SETPCMARK; break; + case 'z': mask = SP_COLUMN; break; } if (mask == 0) { EMSG2(_(e_invarg2), flags); @@ -14409,9 +13601,7 @@ static int get_search_arg(typval_T *varp, int *flagsp) return dir; } -/* - * Shared by search() and searchpos() functions - */ +// Shared by search() and searchpos() functions. static int search_cmn(typval_T *argvars, pos_T *match_pos, int *flagsp) { int flags; @@ -14432,10 +13622,15 @@ static int search_cmn(typval_T *argvars, pos_T *match_pos, int *flagsp) if (dir == 0) goto theend; flags = *flagsp; - if (flags & SP_START) + if (flags & SP_START) { options |= SEARCH_START; - if (flags & SP_END) + } + if (flags & SP_END) { options |= SEARCH_END; + } + if (flags & SP_COLUMN) { + options |= SEARCH_COL; + } /* Optional arguments: line number to stop searching and timeout. */ if (argvars[1].v_type != VAR_UNKNOWN && argvars[2].v_type != VAR_UNKNOWN) { @@ -15765,6 +14960,8 @@ typedef struct { static int item_compare_ic; static bool item_compare_numeric; +static bool item_compare_numbers; +static bool item_compare_float; static char_u *item_compare_func; static dict_T *item_compare_selfdict; static int item_compare_func_err; @@ -15786,9 +14983,24 @@ static int item_compare(const void *s1, const void *s2, bool keep_zero) si2 = (sortItem_T *)s2; typval_T *tv1 = &si1->item->li_tv; typval_T *tv2 = &si2->item->li_tv; - // tv2string() puts quotes around a string and allocates memory. Don't do - // that for string variables. Use a single quote when comparing with a - // non-string to do what the docs promise. + + if (item_compare_numbers) { + long v1 = get_tv_number(tv1); + long v2 = get_tv_number(tv2); + + return v1 == v2 ? 0 : v1 > v2 ? 1 : -1; + } + + if (item_compare_float) { + float_T v1 = get_tv_float(tv1); + float_T v2 = get_tv_float(tv2); + + return v1 == v2 ? 0 : v1 > v2 ? 1 : -1; + } + + // encode_tv2string() puts quotes around a string and allocates memory. Don't + // do that for string variables. Use a single quote when comparing with + // a non-string to do what the docs promise. if (tv1->v_type == VAR_STRING) { if (tv2->v_type != VAR_STRING || item_compare_numeric) { p1 = (char_u *)"'"; @@ -15796,7 +15008,7 @@ static int item_compare(const void *s1, const void *s2, bool keep_zero) p1 = tv1->vval.v_string; } } else { - tofree1 = p1 = (char_u *) tv2string(tv1, NULL); + tofree1 = p1 = (char_u *) encode_tv2string(tv1, NULL); } if (tv2->v_type == VAR_STRING) { if (tv1->v_type != VAR_STRING || item_compare_numeric) { @@ -15805,7 +15017,7 @@ static int item_compare(const void *s1, const void *s2, bool keep_zero) p2 = tv2->vval.v_string; } } else { - tofree2 = p2 = (char_u *) tv2string(tv2, NULL); + tofree2 = p2 = (char_u *) encode_tv2string(tv2, NULL); } if (p1 == NULL) p1 = (char_u *)""; @@ -15932,6 +15144,8 @@ static void do_sort_uniq(typval_T *argvars, typval_T *rettv, bool sort) item_compare_ic = FALSE; item_compare_numeric = false; + item_compare_numbers = false; + item_compare_float = false; item_compare_func = NULL; item_compare_selfdict = NULL; @@ -15953,6 +15167,12 @@ static void do_sort_uniq(typval_T *argvars, typval_T *rettv, bool sort) if (STRCMP(item_compare_func, "n") == 0) { item_compare_func = NULL; item_compare_numeric = true; + } else if (STRCMP(item_compare_func, "N") == 0) { + item_compare_func = NULL; + item_compare_numbers = true; + } else if (STRCMP(item_compare_func, "f") == 0) { + item_compare_func = NULL; + item_compare_float = true; } else if (STRCMP(item_compare_func, "i") == 0) { item_compare_func = NULL; item_compare_ic = TRUE; @@ -16241,7 +15461,7 @@ static void f_str2float(typval_T *argvars, typval_T *rettv) if (*p == '+') p = skipwhite(p + 1); - (void)string2float(p, &rettv->vval.v_float); + (void) string2float((char *) p, &rettv->vval.v_float); rettv->v_type = VAR_FLOAT; } @@ -16372,7 +15592,7 @@ static void f_stridx(typval_T *argvars, typval_T *rettv) static void f_string(typval_T *argvars, typval_T *rettv) { rettv->v_type = VAR_STRING; - rettv->vval.v_string = (char_u *) tv2string(&argvars[0], NULL); + rettv->vval.v_string = (char_u *) encode_tv2string(&argvars[0], NULL); } /* @@ -17105,9 +16325,9 @@ static void f_termopen(typval_T *argvars, typval_T *rettv) // Save the job id and pid in b:terminal_job_{id,pid} Error err; dict_set_value(curbuf->b_vars, cstr_as_string("terminal_job_id"), - INTEGER_OBJ(rettv->vval.v_number), &err); + INTEGER_OBJ(rettv->vval.v_number), false, &err); dict_set_value(curbuf->b_vars, cstr_as_string("terminal_job_pid"), - INTEGER_OBJ(pid), &err); + INTEGER_OBJ(pid), false, &err); Terminal *term = terminal_open(topts); data->term = term; @@ -17293,16 +16513,33 @@ static void f_trunc(typval_T *argvars, typval_T *rettv) */ static void f_type(typval_T *argvars, typval_T *rettv) { - int n; + int n = -1; switch (argvars[0].v_type) { - case VAR_NUMBER: n = 0; break; - case VAR_STRING: n = 1; break; - case VAR_FUNC: n = 2; break; - case VAR_LIST: n = 3; break; - case VAR_DICT: n = 4; break; - case VAR_FLOAT: n = 5; break; - default: EMSG2(_(e_intern2), "f_type()"); n = 0; break; + case VAR_NUMBER: n = 0; break; + case VAR_STRING: n = 1; break; + case VAR_FUNC: n = 2; break; + case VAR_LIST: n = 3; break; + case VAR_DICT: n = 4; break; + case VAR_FLOAT: n = 5; break; + case VAR_SPECIAL: { + switch (argvars[0].vval.v_special) { + case kSpecialVarTrue: + case kSpecialVarFalse: { + n = 6; + break; + } + case kSpecialVarNull: { + n = 7; + break; + } + } + break; + } + case VAR_UNKNOWN: { + EMSG2(_(e_intern2), "f_type(UNKNOWN)"); + break; + } } rettv->vval.v_number = n; } @@ -18021,9 +17258,9 @@ static char_u *find_name_end(char_u *arg, char_u **expr_start, } } else if (br_nest == 0 && mb_nest == 0 && *p == ':') { // "s:" is start of "s:var", but "n:" is not and can be used in - // slice "[n:]". Also "xx:" is not a namespace. + // slice "[n:]". Also "xx:" is not a namespace. But {ns}: is. */ len = (int)(p - arg); - if (len > 1 + if ((len > 1 && p[-1] != '}') || (len == 1 && vim_strchr(namespace_char, *arg) == NULL)) { break; } @@ -18129,14 +17366,6 @@ static int eval_isnamec1(int c) } /* - * Set number v: variable to "val". - */ -void set_vim_var_nr(int idx, long val) -{ - vimvars[idx].vv_nr = val; -} - -/* * Get number v: variable value. */ long get_vim_var_nr(int idx) FUNC_ATTR_PURE @@ -18173,11 +17402,11 @@ dict_T *get_vim_var_dict(int idx) FUNC_ATTR_PURE */ void set_vim_var_char(int c) { - char_u buf[MB_MAXBYTES + 1]; + char buf[MB_MAXBYTES + 1]; - if (has_mbyte) - buf[(*mb_char2bytes)(c, buf)] = NUL; - else { + if (has_mbyte) { + buf[(*mb_char2bytes)(c, (char_u *) buf)] = NUL; + } else { buf[0] = c; buf[1] = NUL; } @@ -18196,47 +17425,68 @@ void set_vcount(long count, long count1, int set_prevcount) vimvars[VV_COUNT1].vv_nr = count1; } -/* - * Set string v: variable to a copy of "val". - */ -void set_vim_var_string ( - int idx, - char_u *val, - int len /* length of "val" to use or -1 (whole string) */ -) +/// Set number v: variable to the given value +/// +/// @param[in] idx Index of variable to set. +/// @param[in] val Value to set to. +void set_vim_var_nr(const VimVarIndex idx, const varnumber_T val) { - /* Need to do this (at least) once, since we can't initialize a union. - * Will always be invoked when "v:progname" is set. */ - vimvars[VV_VERSION].vv_nr = VIM_VERSION_100; + vimvars[idx].vv_nr = val; +} +/// Set special v: variable to the given value +/// +/// @param[in] idx Index of variable to set. +/// @param[in] val Value to set to. +void set_vim_var_special(const VimVarIndex idx, const SpecialVarValue val) +{ + vimvars[idx].vv_special = val; +} + +/// Set string v: variable to the given string +/// +/// @param[in] idx Index of variable to set. +/// @param[in] val Value to set to. Will be copied. +/// @param[in] len Legth of that value or -1 in which case strlen() will be +/// used. +void set_vim_var_string(const VimVarIndex idx, const char *const val, + const ptrdiff_t len) +{ xfree(vimvars[idx].vv_str); - if (val == NULL) + if (val == NULL) { vimvars[idx].vv_str = NULL; - else if (len == -1) - vimvars[idx].vv_str = vim_strsave(val); - else - vimvars[idx].vv_str = vim_strnsave(val, len); + } else if (len == -1) { + vimvars[idx].vv_str = (char_u *) xstrdup(val); + } else { + vimvars[idx].vv_str = (char_u *) xstrndup(val, (size_t) len); + } } -/* - * Set List v: variable to "val". - */ -void set_vim_var_list(int idx, list_T *val) +/// Set list v: variable to the given list +/// +/// @param[in] idx Index of variable to set. +/// @param[in,out] val Value to set to. Reference count will be incremented. +void set_vim_var_list(const VimVarIndex idx, list_T *const val) { list_unref(vimvars[idx].vv_list); vimvars[idx].vv_list = val; - if (val != NULL) - ++val->lv_refcount; + if (val != NULL) { + val->lv_refcount++; + } } -/// Set Dictionary v: variable to "val". -void set_vim_var_dict(int idx, dict_T *val) +/// Set Dictionary v: variable to the given dictionary +/// +/// @param[in] idx Index of variable to set. +/// @param[in,out] val Value to set to. Reference count will be incremented. +/// Also keys of the dictionary will be made read-only. +void set_vim_var_dict(const VimVarIndex idx, dict_T *const val) { dict_unref(vimvars[idx].vv_dict); vimvars[idx].vv_dict = val; if (val != NULL) { - ++val->dv_refcount; + val->dv_refcount++; // Set readonly dict_set_keys_readonly(val); } @@ -18247,15 +17497,17 @@ void set_vim_var_dict(int idx, dict_T *val) */ void set_reg_var(int c) { - char_u regname; + char regname; - if (c == 0 || c == ' ') + if (c == 0 || c == ' ') { regname = '"'; - else + } else { regname = c; - /* Avoid free/alloc when the value is already right. */ - if (vimvars[VV_REG].vv_str == NULL || vimvars[VV_REG].vv_str[0] != c) + } + // Avoid free/alloc when the value is already right. + if (vimvars[VV_REG].vv_str == NULL || vimvars[VV_REG].vv_str[0] != c) { set_vim_var_string(VV_REG, ®name, 1); + } } /* @@ -18485,25 +17737,23 @@ void free_tv(typval_T *varp) { if (varp != NULL) { switch (varp->v_type) { - case VAR_FUNC: - func_unref(varp->vval.v_string); - /*FALLTHROUGH*/ - case VAR_STRING: - xfree(varp->vval.v_string); - break; - case VAR_LIST: - list_unref(varp->vval.v_list); - break; - case VAR_DICT: - dict_unref(varp->vval.v_dict); - break; - case VAR_NUMBER: - case VAR_FLOAT: - case VAR_UNKNOWN: - break; - default: - EMSG2(_(e_intern2), "free_tv()"); - break; + case VAR_FUNC: + func_unref(varp->vval.v_string); + // FALLTHROUGH + case VAR_STRING: + xfree(varp->vval.v_string); + break; + case VAR_LIST: + list_unref(varp->vval.v_list); + break; + case VAR_DICT: + dict_unref(varp->vval.v_dict); + break; + case VAR_SPECIAL: + case VAR_NUMBER: + case VAR_FLOAT: + case VAR_UNKNOWN: + break; } xfree(varp); } @@ -18541,10 +17791,11 @@ void clear_tv(typval_T *varp) case VAR_FLOAT: varp->vval.v_float = 0.0; break; + case VAR_SPECIAL: + varp->vval.v_special = kSpecialVarFalse; + break; case VAR_UNKNOWN: break; - default: - EMSG2(_(e_intern2), "clear_tv()"); } varp->v_lock = 0; } @@ -18599,8 +17850,19 @@ long get_tv_number_chk(typval_T *varp, int *denote) case VAR_DICT: EMSG(_("E728: Using a Dictionary as a Number")); break; - default: - EMSG2(_(e_intern2), "get_tv_number()"); + case VAR_SPECIAL: + switch (varp->vval.v_special) { + case kSpecialVarTrue: { + return 1; + } + case kSpecialVarFalse: + case kSpecialVarNull: { + return 0; + } + } + break; + case VAR_UNKNOWN: + EMSG2(_(e_intern2), "get_tv_number(UNKNOWN)"); break; } if (denote == NULL) { @@ -18612,6 +17874,33 @@ long get_tv_number_chk(typval_T *varp, int *denote) return n; } +static float_T get_tv_float(typval_T *varp) +{ + switch (varp->v_type) { + case VAR_NUMBER: + return (float_T)(varp->vval.v_number); + case VAR_FLOAT: + return varp->vval.v_float; + break; + case VAR_FUNC: + EMSG(_("E891: Using a Funcref as a Float")); + break; + case VAR_STRING: + EMSG(_("E892: Using a String as a Float")); + break; + case VAR_LIST: + EMSG(_("E893: Using a List as a Float")); + break; + case VAR_DICT: + EMSG(_("E894: Using a Dictionary as a Float")); + break; + default: + EMSG2(_(e_intern2), "get_tv_float()"); + break; + } + return 0; +} + /* * Get the lnum from the first argument. * Also accepts ".", "$", etc., but that only works for the current buffer. @@ -18705,8 +17994,11 @@ static char_u *get_tv_string_buf_chk(const typval_T *varp, char_u *buf) if (varp->vval.v_string != NULL) return varp->vval.v_string; return (char_u *)""; - default: - EMSG2(_(e_intern2), "get_tv_string_buf()"); + case VAR_SPECIAL: + STRCPY(buf, encode_special_var_names[varp->vval.v_special]); + return buf; + case VAR_UNKNOWN: + EMSG(_("E908: using an invalid value as a String")); break; } return NULL; @@ -18977,7 +18269,7 @@ static void delete_var(hashtab_T *ht, hashitem_T *hi) */ static void list_one_var(dictitem_T *v, char_u *prefix, int *first) { - char_u *s = (char_u *) echo_string(&v->di_tv, NULL); + char_u *s = (char_u *) encode_tv2echo(&v->di_tv, NULL); list_one_var_a(prefix, v->di_key, v->di_tv.v_type, s == NULL ? (char_u *)"" : s, first); xfree(s); @@ -19254,42 +18546,34 @@ void copy_tv(typval_T *from, typval_T *to) { to->v_type = from->v_type; to->v_lock = 0; + memmove(&to->vval, &from->vval, sizeof(to->vval)); switch (from->v_type) { - case VAR_NUMBER: - to->vval.v_number = from->vval.v_number; - break; - case VAR_FLOAT: - to->vval.v_float = from->vval.v_float; - break; - case VAR_STRING: - case VAR_FUNC: - if (from->vval.v_string == NULL) - to->vval.v_string = NULL; - else { - to->vval.v_string = vim_strsave(from->vval.v_string); - if (from->v_type == VAR_FUNC) - func_ref(to->vval.v_string); - } - break; - case VAR_LIST: - if (from->vval.v_list == NULL) - to->vval.v_list = NULL; - else { - to->vval.v_list = from->vval.v_list; - ++to->vval.v_list->lv_refcount; - } - break; - case VAR_DICT: - if (from->vval.v_dict == NULL) - to->vval.v_dict = NULL; - else { - to->vval.v_dict = from->vval.v_dict; - ++to->vval.v_dict->dv_refcount; - } - break; - default: - EMSG2(_(e_intern2), "copy_tv()"); - break; + case VAR_NUMBER: + case VAR_FLOAT: + case VAR_SPECIAL: + break; + case VAR_STRING: + case VAR_FUNC: + if (from->vval.v_string != NULL) { + to->vval.v_string = vim_strsave(from->vval.v_string); + if (from->v_type == VAR_FUNC) { + func_ref(to->vval.v_string); + } + } + break; + case VAR_LIST: + if (from->vval.v_list != NULL) { + to->vval.v_list->lv_refcount++; + } + break; + case VAR_DICT: + if (from->vval.v_dict != NULL) { + to->vval.v_dict->dv_refcount++; + } + break; + case VAR_UNKNOWN: + EMSG2(_(e_intern2), "copy_tv(UNKNOWN)"); + break; } } @@ -19329,6 +18613,7 @@ int var_item_copy(const vimconv_T *const conv, case VAR_NUMBER: case VAR_FLOAT: case VAR_FUNC: + case VAR_SPECIAL: copy_tv(from, to); break; case VAR_STRING: @@ -19375,8 +18660,8 @@ int var_item_copy(const vimconv_T *const conv, if (to->vval.v_dict == NULL) ret = FAIL; break; - default: - EMSG2(_(e_intern2), "var_item_copy()"); + case VAR_UNKNOWN: + EMSG2(_(e_intern2), "var_item_copy(UNKNOWN)"); ret = FAIL; } --recurse; @@ -19431,7 +18716,7 @@ void ex_echo(exarg_T *eap) } } else if (eap->cmdidx == CMD_echo) msg_puts_attr((char_u *)" ", echo_attr); - char_u *tofree = p = (char_u *) echo_string(&rettv, NULL); + char_u *tofree = p = (char_u *) encode_tv2echo(&rettv, NULL); if (p != NULL) { for (; *p != NUL && !got_int; ++p) { if (*p == '\n' || *p == '\r' || *p == TAB) { @@ -21083,10 +20368,10 @@ call_user_func ( msg_outnum((long)argvars[i].vval.v_number); } else { // Do not want errors such as E724 here. - ++emsg_off; - char_u *s = (char_u *) tv2string(&argvars[i], NULL); + emsg_off++; + char_u *s = (char_u *) encode_tv2string(&argvars[i], NULL); char_u *tofree = s; - --emsg_off; + emsg_off--; if (s != NULL) { if (vim_strsize(s) > MSG_BUF_CLEN) { trunc_string(s, buf, MSG_BUF_CLEN, MSG_BUF_LEN); @@ -21177,10 +20462,10 @@ call_user_func ( // The value may be very long. Skip the middle part, so that we // 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_u *s = (char_u *) tv2string(fc->rettv, NULL); + emsg_off++; + char_u *s = (char_u *) encode_tv2string(fc->rettv, NULL); char_u *tofree = s; - --emsg_off; + emsg_off--; if (s != NULL) { if (vim_strsize(s) > MSG_BUF_CLEN) { trunc_string(s, buf, MSG_BUF_CLEN, MSG_BUF_LEN); @@ -21451,7 +20736,7 @@ char_u *get_return_cmd(void *rettv) char_u *tofree = NULL; if (rettv != NULL) { - tofree = s = (char_u *) echo_string((typval_T *) rettv, NULL); + tofree = s = (char_u *) encode_tv2echo((typval_T *) rettv, NULL); } if (s == NULL) { s = (char_u *)""; @@ -22614,4 +21899,3 @@ static bool is_watched(dict_T *d) { return d && !QUEUE_EMPTY(&d->watchers); } - |