diff options
Diffstat (limited to 'src')
51 files changed, 4759 insertions, 2920 deletions
| diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt index 487b554d6d..7be06a99cb 100644 --- a/src/nvim/CMakeLists.txt +++ b/src/nvim/CMakeLists.txt @@ -41,22 +41,25 @@ include_directories(${GENERATED_DIR})  include_directories(${GENERATED_INCLUDES_DIR})  file(MAKE_DIRECTORY ${GENERATED_DIR}) -file(MAKE_DIRECTORY ${GENERATED_DIR}/os) -file(MAKE_DIRECTORY ${GENERATED_DIR}/api) -file(MAKE_DIRECTORY ${GENERATED_DIR}/api/private) -file(MAKE_DIRECTORY ${GENERATED_DIR}/msgpack_rpc) -file(MAKE_DIRECTORY ${GENERATED_DIR}/tui) -file(MAKE_DIRECTORY ${GENERATED_DIR}/event)  file(MAKE_DIRECTORY ${GENERATED_INCLUDES_DIR}) -file(MAKE_DIRECTORY ${GENERATED_INCLUDES_DIR}/os) -file(MAKE_DIRECTORY ${GENERATED_INCLUDES_DIR}/api) -file(MAKE_DIRECTORY ${GENERATED_INCLUDES_DIR}/api/private) -file(MAKE_DIRECTORY ${GENERATED_INCLUDES_DIR}/msgpack_rpc) -file(MAKE_DIRECTORY ${GENERATED_INCLUDES_DIR}/tui) -file(MAKE_DIRECTORY ${GENERATED_INCLUDES_DIR}/event) - -file(GLOB NEOVIM_SOURCES *.c os/*.c api/*.c api/private/*.c msgpack_rpc/*.c -  tui/*.c event/*.c) + +file(GLOB NEOVIM_SOURCES *.c) + +foreach(subdir +        os +        api +        api/private +        msgpack_rpc +        tui +        event +        eval +       ) +  file(MAKE_DIRECTORY ${GENERATED_DIR}/${subdir}) +  file(MAKE_DIRECTORY ${GENERATED_INCLUDES_DIR}/${subdir}) +  file(GLOB sources ${subdir}/*.c) +  list(APPEND NEOVIM_SOURCES ${sources}) +endforeach() +  file(GLOB_RECURSE NEOVIM_HEADERS *.h)  file(GLOB UNIT_TEST_FIXTURES ${PROJECT_SOURCE_DIR}/test/unit/fixtures/*.c) diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index c25a9789c5..55b535c78c 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -423,13 +423,16 @@ Object buffer_get_var(Buffer buffer, String name, Error *err)    return dict_get_value(buf->b_vars, name, err);  } -/// Sets a buffer-scoped (b:) variable. 'nil' value deletes the variable. +/// Sets a buffer-scoped (b:) variable  ///  /// @param buffer The buffer handle  /// @param name The variable name  /// @param value The variable value  /// @param[out] err Details of an error that may have occurred -/// @return The old value +/// @return The old value or nil if there was no previous value. +/// +///         @warning It may return nil if there was no previous value +///                  or if previous value was `v:null`.  Object buffer_set_var(Buffer buffer, String name, Object value, Error *err)  {    buf_T *buf = find_buffer_by_handle(buffer, err); @@ -438,7 +441,27 @@ Object buffer_set_var(Buffer buffer, String name, Object value, Error *err)      return (Object) OBJECT_INIT;    } -  return dict_set_value(buf->b_vars, name, value, err); +  return dict_set_value(buf->b_vars, name, value, false, err); +} + +/// Removes a buffer-scoped (b:) variable +/// +/// @param buffer The buffer handle +/// @param name The variable name +/// @param[out] err Details of an error that may have occurred +/// @return The old value or nil if there was no previous value. +/// +///         @warning It may return nil if there was no previous value +///                  or if previous value was `v:null`. +Object buffer_del_var(Buffer buffer, String name, Error *err) +{ +  buf_T *buf = find_buffer_by_handle(buffer, err); + +  if (!buf) { +    return (Object) OBJECT_INIT; +  } + +  return dict_set_value(buf->b_vars, name, NIL, true, err);  }  /// Gets a buffer option value diff --git a/src/nvim/api/private/defs.h b/src/nvim/api/private/defs.h index 6c8e324649..fbfa87d5ae 100644 --- a/src/nvim/api/private/defs.h +++ b/src/nvim/api/private/defs.h @@ -99,4 +99,3 @@ struct key_value_pair {  #endif  // NVIM_API_PRIVATE_DEFS_H - diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index 7a0b5191d7..db3e499427 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -90,14 +90,17 @@ Object dict_get_value(dict_T *dict, String key, Error *err)  }  /// Set a value in a dict. Objects are recursively expanded into their -/// vimscript equivalents. Passing 'nil' as value deletes the key. +/// vimscript equivalents.  ///  /// @param dict The vimscript dict  /// @param key The key  /// @param value The new value +/// @param del Delete key in place of setting it. Argument `value` is ignored in +///            this case.  /// @param[out] err Details of an error that may have occurred  /// @return the old value, if any -Object dict_set_value(dict_T *dict, String key, Object value, Error *err) +Object dict_set_value(dict_T *dict, String key, Object value, bool del, +                      Error *err)  {    Object rv = OBJECT_INIT; @@ -118,7 +121,7 @@ Object dict_set_value(dict_T *dict, String key, Object value, Error *err)    dictitem_T *di = dict_find(dict, (uint8_t *)key.data, (int)key.size); -  if (value.type == kObjectTypeNil) { +  if (del) {      // Delete the key      if (di == NULL) {        // Doesn't exist, fail @@ -397,13 +400,13 @@ bool object_to_vim(Object obj, typval_T *tv, Error *err)    switch (obj.type) {      case kObjectTypeNil: -      tv->v_type = VAR_NUMBER; -      tv->vval.v_number = 0; +      tv->v_type = VAR_SPECIAL; +      tv->vval.v_special = kSpecialVarNull;        break;      case kObjectTypeBoolean: -      tv->v_type = VAR_NUMBER; -      tv->vval.v_number = obj.data.boolean; +      tv->v_type = VAR_SPECIAL; +      tv->vval.v_special = obj.data.boolean? kSpecialVarTrue: kSpecialVarFalse;        break;      case kObjectTypeBuffer: @@ -651,6 +654,21 @@ static Object vim_to_object_rec(typval_T *obj, PMap(ptr_t) *lookup)    }    switch (obj->v_type) { +    case VAR_SPECIAL: +      switch (obj->vval.v_special) { +        case kSpecialVarTrue: +        case kSpecialVarFalse: { +          rv.type = kObjectTypeBoolean; +          rv.data.boolean = (obj->vval.v_special == kSpecialVarTrue); +          break; +        } +        case kSpecialVarNull: { +          rv.type = kObjectTypeNil; +          break; +        } +      } +      break; +      case VAR_STRING:        rv.type = kObjectTypeString;        rv.data.string = cstr_to_string((char *) obj->vval.v_string); @@ -730,6 +748,10 @@ static Object vim_to_object_rec(typval_T *obj, PMap(ptr_t) *lookup)          }        }        break; + +    case VAR_UNKNOWN: +    case VAR_FUNC: +      break;    }    return rv; diff --git a/src/nvim/api/tabpage.c b/src/nvim/api/tabpage.c index 126ee4072d..c8311b0aa0 100644 --- a/src/nvim/api/tabpage.c +++ b/src/nvim/api/tabpage.c @@ -54,13 +54,16 @@ Object tabpage_get_var(Tabpage tabpage, String name, Error *err)    return dict_get_value(tab->tp_vars, name, err);  } -/// Sets a tab-scoped (t:) variable. 'nil' value deletes the variable. +/// Sets a tab-scoped (t:) variable  ///  /// @param tabpage handle  /// @param name The variable name  /// @param value The variable value  /// @param[out] err Details of an error that may have occurred -/// @return The tab page handle +/// @return The old value or nil if there was no previous value. +/// +///         @warning It may return nil if there was no previous value +///                  or if previous value was `v:null`.  Object tabpage_set_var(Tabpage tabpage, String name, Object value, Error *err)  {    tabpage_T *tab = find_tab_by_handle(tabpage, err); @@ -69,7 +72,27 @@ Object tabpage_set_var(Tabpage tabpage, String name, Object value, Error *err)      return (Object) OBJECT_INIT;    } -  return dict_set_value(tab->tp_vars, name, value, err); +  return dict_set_value(tab->tp_vars, name, value, false, err); +} + +/// Removes a tab-scoped (t:) variable +/// +/// @param tabpage handle +/// @param name The variable name +/// @param[out] err Details of an error that may have occurred +/// @return The old value or nil if there was no previous value. +/// +///         @warning It may return nil if there was no previous value +///                  or if previous value was `v:null`. +Object tabpage_del_var(Tabpage tabpage, String name, Error *err) +{ +  tabpage_T *tab = find_tab_by_handle(tabpage, err); + +  if (!tab) { +    return (Object) OBJECT_INIT; +  } + +  return dict_set_value(tab->tp_vars, name, NIL, true, err);  }  /// Gets the current window in a tab page diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 9279f6b469..10110b0f62 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -331,15 +331,31 @@ Object vim_get_var(String name, Error *err)    return dict_get_value(&globvardict, name, err);  } -/// Sets a global variable. Passing 'nil' as value deletes the variable. +/// Sets a global variable  ///  /// @param name The variable name  /// @param value The variable value  /// @param[out] err Details of an error that may have occurred -/// @return the old value if any +/// @return The old value or nil if there was no previous value. +/// +///         @warning It may return nil if there was no previous value +///                  or if previous value was `v:null`.  Object vim_set_var(String name, Object value, Error *err)  { -  return dict_set_value(&globvardict, name, value, err); +  return dict_set_value(&globvardict, name, value, false, err); +} + +/// Removes a global variable +/// +/// @param name The variable name +/// @param[out] err Details of an error that may have occurred +/// @return The old value or nil if there was no previous value. +/// +///         @warning It may return nil if there was no previous value +///                  or if previous value was `v:null`. +Object vim_del_var(String name, Error *err) +{ +  return dict_set_value(&globvardict, name, NIL, true, err);  }  /// Gets a vim variable diff --git a/src/nvim/api/window.c b/src/nvim/api/window.c index aad616c7bf..a52f53a3e6 100644 --- a/src/nvim/api/window.c +++ b/src/nvim/api/window.c @@ -197,13 +197,16 @@ Object window_get_var(Window window, String name, Error *err)    return dict_get_value(win->w_vars, name, err);  } -/// Sets a window-scoped (w:) variable. 'nil' value deletes the variable. +/// Sets a window-scoped (w:) variable  ///  /// @param window The window handle  /// @param name The variable name  /// @param value The variable value  /// @param[out] err Details of an error that may have occurred -/// @return The old value +/// @return The old value or nil if there was no previous value. +/// +///         @warning It may return nil if there was no previous value +///                  or if previous value was `v:null`.  Object window_set_var(Window window, String name, Object value, Error *err)  {    win_T *win = find_window_by_handle(window, err); @@ -212,7 +215,27 @@ Object window_set_var(Window window, String name, Object value, Error *err)      return (Object) OBJECT_INIT;    } -  return dict_set_value(win->w_vars, name, value, err); +  return dict_set_value(win->w_vars, name, value, false, err); +} + +/// Removes a window-scoped (w:) variable +/// +/// @param window The window handle +/// @param name The variable name +/// @param[out] err Details of an error that may have occurred +/// @return The old value or nil if there was no previous value. +/// +///         @warning It may return nil if there was no previous value +///                  or if previous value was `v:null`. +Object window_del_var(Window window, String name, Error *err) +{ +  win_T *win = find_window_by_handle(window, err); + +  if (!win) { +    return (Object) OBJECT_INIT; +  } + +  return dict_set_value(win->w_vars, name, NIL, true, err);  }  /// Gets a window option value diff --git a/src/nvim/assert.h b/src/nvim/assert.h index 3a900aca65..0ce48e4766 100644 --- a/src/nvim/assert.h +++ b/src/nvim/assert.h @@ -8,17 +8,32 @@  // defined(__has_feature) && __has_feature(...). Therefore we define Clang's  // __has_feature and __has_extension macro's before referring to them.  #ifndef __has_feature -  #define __has_feature(x) 0 +# define __has_feature(x) 0  #endif  #ifndef __has_extension -  #define __has_extension __has_feature +# define __has_extension __has_feature  #endif -/// STATIC_ASSERT(condition, message) - assert at compile time if !cond +/// @def STATIC_ASSERT +/// @brief Assert at compile time if condition is not satisfied.  /// -/// example: -///  STATIC_ASSERT(sizeof(void *) == 8, "need 64-bits mode"); +/// Should be put on its own line, followed by a semicolon. +/// +/// Example: +/// +///     STATIC_ASSERT(sizeof(void *) == 8, "Expected 64-bit mode"); +/// +/// @param[in]  condition  Condition to check, should be an integer constant +///                        expression. +/// @param[in]  message  Message which will be given if check fails. + +/// @def STATIC_ASSERT_EXPR +/// @brief Like #STATIC_ASSERT, but can be used where expressions are used. +/// +/// STATIC_ASSERT_EXPR may be put in brace initializer lists. Error message +/// given in this case is not very nice with the current implementation though +/// and `message` argument is ignored.  // define STATIC_ASSERT as C11's _Static_assert whenever either C11 mode is  // detected or the compiler is known to support it. Note that Clang in C99 @@ -29,50 +44,74 @@  // clearer messages we get from _Static_assert, we suppress the warnings  // temporarily. +#define STATIC_ASSERT_PRAGMA_START +#define STATIC_ASSERT_PRAGMA_END +#define STATIC_ASSERT(...) \ +    do { \ +      STATIC_ASSERT_PRAGMA_START \ +      STATIC_ASSERT_STATEMENT(__VA_ARGS__); \ +      STATIC_ASSERT_PRAGMA_END \ +    } while (0) +  // the easiest case, when the mode is C11 (generic compiler) or Clang  // advertises explicit support for c_static_assert, meaning it won't warn.  #if __STDC_VERSION__ >= 201112L || __has_feature(c_static_assert) -  #define STATIC_ASSERT(cond, msg) _Static_assert(cond, msg) +# define STATIC_ASSERT_STATEMENT(cond, msg) _Static_assert(cond, msg)  // if we're dealing with gcc >= 4.6 in C99 mode, we can still use  // _Static_assert but we need to suppress warnings, this is pretty ugly.  #elif (!defined(__clang__) && !defined(__INTEL_COMPILER)) && \    (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) -  #define STATIC_ASSERT(cond, msg) \ -	_Pragma("GCC diagnostic push") \ -	_Pragma("GCC diagnostic ignored \"-pedantic\"") \ -	_Static_assert(cond, msg); \ -	_Pragma("GCC diagnostic pop") \ + +# define STATIC_ASSERT_STATEMENT(cond, msg) _Static_assert(cond, msg) + +# undef STATIC_ASSERT_PRAGMA_START +# define STATIC_ASSERT_PRAGMA_START \ +    _Pragma("GCC diagnostic push") \ +    _Pragma("GCC diagnostic ignored \"-pedantic\"") \ + +# undef STATIC_ASSERT_PRAGMA_END +# define STATIC_ASSERT_PRAGMA_END \ +    _Pragma("GCC diagnostic pop") \  // the same goes for clang in C99 mode, but we suppress a different warning  #elif defined(__clang__) && __has_extension(c_static_assert) -  #define STATIC_ASSERT(cond, msg) \ -	_Pragma("clang diagnostic push") \ -	_Pragma("clang diagnostic ignored \"-Wc11-extensions\"") \ -	_Static_assert(cond, msg); \ -	_Pragma("clang diagnostic pop") \ + +# define STATIC_ASSERT_STATEMENT(cond, msg) _Static_assert(cond, msg) + +# undef STATIC_ASSERT_PRAGMA_START +# define STATIC_ASSERT_PRAGMA_START \ +    _Pragma("clang diagnostic push") \ +    _Pragma("clang diagnostic ignored \"-Wc11-extensions\"") \ + +# undef STATIC_ASSERT_PRAGMA_END +# define STATIC_ASSERT_PRAGMA_END \ +    _Pragma("clang diagnostic pop") \  // TODO(aktau): verify that this works, don't have MSVC on hand.  #elif _MSC_VER >= 1600 -  #define STATIC_ASSERT(cond, msg) static_assert(cond, msg) + +# define STATIC_ASSERT_STATEMENT(cond, msg) static_assert(cond, msg)  // fallback for compilers that don't support _Static_assert or static_assert  // not as pretty but gets the job done. Credit goes to Pádraig Brady and  // contributors.  #else -  #define ASSERT_CONCAT_(a, b) a##b -  #define ASSERT_CONCAT(a, b) ASSERT_CONCAT_(a, b) -  // These can't be used after statements in c89. -  #ifdef __COUNTER__ -	#define STATIC_ASSERT(e,m) \ -	  { enum { ASSERT_CONCAT(static_assert_, __COUNTER__) = 1/(!!(e)) }; } -  #else -	// This can't be used twice on the same line so ensure if using in headers -	// that the headers are not included twice (by wrapping in #ifndef...#endif) -	// Note it doesn't cause an issue when used on same line of separate modules -	// compiled with gcc -combine -fwhole-program. -	#define STATIC_ASSERT(e,m) \ -	  { enum { ASSERT_CONCAT(assert_line_, __LINE__) = 1/(!!(e)) }; } -  #endif +# define STATIC_ASSERT_STATEMENT STATIC_ASSERT_EXPR +#endif + +#define ASSERT_CONCAT_(a, b) a##b +#define ASSERT_CONCAT(a, b) ASSERT_CONCAT_(a, b) +// These can't be used after statements in c89. +#ifdef __COUNTER__ +# define STATIC_ASSERT_EXPR(e, m) \ +    ((enum { ASSERT_CONCAT(static_assert_, __COUNTER__) = 1/(!!(e)) }) 0) +#else +// This can't be used twice on the same line so ensure if using in headers +// that the headers are not included twice (by wrapping in #ifndef...#endif) +// Note it doesn't cause an issue when used on same line of separate modules +// compiled with gcc -combine -fwhole-program. +# define STATIC_ASSERT_EXPR(e, m) \ +    ((enum { ASSERT_CONCAT(assert_line_, __LINE__) = 1/(!!(e)) }) 0)  #endif  #endif  // NVIM_ASSERT_H diff --git a/src/nvim/diff.c b/src/nvim/diff.c index 1149ca1e62..a3063de869 100644 --- a/src/nvim/diff.c +++ b/src/nvim/diff.c @@ -658,9 +658,9 @@ void ex_diffupdate(exarg_T *eap)    }    // We need three temp file names. -  char_u *tmp_orig = vim_tempname(); -  char_u *tmp_new = vim_tempname(); -  char_u *tmp_diff = vim_tempname(); +  char *tmp_orig = (char *) vim_tempname(); +  char *tmp_new = (char *) vim_tempname(); +  char *tmp_diff = (char *) vim_tempname();    if ((tmp_orig == NULL) || (tmp_new == NULL) || (tmp_diff == NULL)) {      goto theend; @@ -670,11 +670,11 @@ void ex_diffupdate(exarg_T *eap)    // are no differences.  Can't use the return value, it's non-zero when    // there are differences.    // May try twice, first with "-a" and then without. -  int io_error = FALSE; -  int ok = FALSE; +  int io_error = false; +  bool ok = false;    for (;;) { -    ok = FALSE; -    FILE *fd = mch_fopen((char *)tmp_orig, "w"); +    ok = false; +    FILE *fd = mch_fopen(tmp_orig, "w");      if (fd == NULL) {        io_error = TRUE; @@ -683,7 +683,7 @@ void ex_diffupdate(exarg_T *eap)          io_error = TRUE;        }        fclose(fd); -      fd = mch_fopen((char *)tmp_new, "w"); +      fd = mch_fopen(tmp_new, "w");        if (fd == NULL) {          io_error = TRUE; @@ -693,7 +693,7 @@ void ex_diffupdate(exarg_T *eap)          }          fclose(fd);          diff_file(tmp_orig, tmp_new, tmp_diff); -        fd = mch_fopen((char *)tmp_diff, "r"); +        fd = mch_fopen(tmp_diff, "r");          if (fd == NULL) {            io_error = TRUE; @@ -712,10 +712,10 @@ void ex_diffupdate(exarg_T *eap)            }            fclose(fd);          } -        os_remove((char *)tmp_diff); -        os_remove((char *)tmp_new); +        os_remove(tmp_diff); +        os_remove(tmp_new);        } -      os_remove((char *)tmp_orig); +      os_remove(tmp_orig);      }      // When using 'diffexpr' break here. @@ -756,7 +756,7 @@ void ex_diffupdate(exarg_T *eap)    // Write the first buffer to a tempfile.    buf_T *buf = curtab->tp_diffbuf[idx_orig]; -  if (diff_write(buf, tmp_orig) == FAIL) { +  if (diff_write(buf, (char_u *) tmp_orig) == FAIL) {      goto theend;    } @@ -767,17 +767,17 @@ void ex_diffupdate(exarg_T *eap)        continue;  // skip buffer that isn't loaded      } -    if (diff_write(buf, tmp_new) == FAIL) { +    if (diff_write(buf, (char_u *) tmp_new) == FAIL) {        continue;      }      diff_file(tmp_orig, tmp_new, tmp_diff);      // Read the diff output and add each entry to the diff list. -    diff_read(idx_orig, idx_new, tmp_diff); -    os_remove((char *)tmp_diff); -    os_remove((char *)tmp_new); +    diff_read(idx_orig, idx_new, (char_u *) tmp_diff); +    os_remove(tmp_diff); +    os_remove(tmp_new);    } -  os_remove((char *)tmp_orig); +  os_remove(tmp_orig);    // force updating cursor position on screen    curwin->w_valid_cursor.lnum = 0; @@ -795,15 +795,16 @@ theend:  /// @param tmp_orig  /// @param tmp_new  /// @param tmp_diff -static void diff_file(char_u *tmp_orig, char_u *tmp_new, char_u *tmp_diff) +static void diff_file(const char *const tmp_orig, const char *const tmp_new, +                      const char *const tmp_diff)  {    if (*p_dex != NUL) {      // Use 'diffexpr' to generate the diff file.      eval_diff(tmp_orig, tmp_new, tmp_diff);    } else { -    size_t len = STRLEN(tmp_orig) + STRLEN(tmp_new) + STRLEN(tmp_diff) -        + STRLEN(p_srr) + 27; -    char_u *cmd = xmalloc(len); +    const size_t len = (strlen(tmp_orig) + strlen(tmp_new) + strlen(tmp_diff) +                        + STRLEN(p_srr) + 27); +    char *const cmd = xmalloc(len);      /* We don't want $DIFF_OPTIONS to get in the way. */      if (os_getenv("DIFF_OPTIONS")) { @@ -813,19 +814,17 @@ static void diff_file(char_u *tmp_orig, char_u *tmp_new, char_u *tmp_diff)      /* Build the diff command and execute it.  Always use -a, binary       * differences are of no use.  Ignore errors, diff returns       * non-zero when differences have been found. */ -    vim_snprintf((char *)cmd, len, "diff %s%s%s%s%s %s", -                 diff_a_works == FALSE ? "" : "-a ", +    vim_snprintf(cmd, len, "diff %s%s%s%s%s %s", +                 diff_a_works ? "-a " : "",                   "",                   (diff_flags & DIFF_IWHITE) ? "-b " : "",                   (diff_flags & DIFF_ICASE) ? "-i " : "",                   tmp_orig, tmp_new); -    append_redir(cmd, len, p_srr, tmp_diff); +    append_redir(cmd, len, (char *) p_srr, tmp_diff);      block_autocmds();  // Avoid ShellCmdPost stuff -    (void)call_shell( -        cmd, -        kShellOptFilter | kShellOptSilent | kShellOptDoOut, -        NULL -        ); +    (void)call_shell((char_u *) cmd, +                     kShellOptFilter | kShellOptSilent | kShellOptDoOut, +                     NULL);      unblock_autocmds();      xfree(cmd);    } @@ -902,9 +901,11 @@ void ex_diffpatch(exarg_T *eap)    if (*p_pex != NUL) {      // Use 'patchexpr' to generate the new file.  #ifdef UNIX -    eval_patch(tmp_orig, fullname != NULL ? fullname : eap->arg, tmp_new); +    eval_patch((char *) tmp_orig, +               (char *) (fullname != NULL ? fullname : eap->arg), +               (char *) tmp_new);  #else -    eval_patch(tmp_orig, eap->arg, tmp_new); +    eval_patch((char *) tmp_orig, (char *) eap->arg, (char *) tmp_new);  #endif  // ifdef UNIX    } else {      // Build the patch command and execute it. Ignore errors. Switch to diff --git a/src/nvim/edit.c b/src/nvim/edit.c index 614a5d43be..36c88df570 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -199,7 +199,7 @@ typedef struct insert_state {    int did_restart_edit;              // remember if insert mode was restarted                                       // after a ctrl+o    bool nomove; -  uint8_t *ptr; +  char_u *ptr;  } InsertState; @@ -270,8 +270,8 @@ static void insert_enter(InsertState *s)        s->ptr = (char_u *)"i";      } -    set_vim_var_string(VV_INSERTMODE, s->ptr, 1); -    set_vim_var_string(VV_CHAR, NULL, -1);      /* clear v:char */ +    set_vim_var_string(VV_INSERTMODE, (char *) s->ptr, 1); +    set_vim_var_string(VV_CHAR, NULL, -1);      apply_autocmds(EVENT_INSERTENTER, NULL, NULL, false, curbuf);      // Make sure the cursor didn't move.  Do call check_cursor_col() in @@ -7239,15 +7239,15 @@ static void ins_insert(int replaceState)      return;    } -  set_vim_var_string(VV_INSERTMODE, -      (char_u *)((State & REPLACE_FLAG) ? "i" : -                 replaceState == VREPLACE ? "v" : -                 "r"), 1); -  apply_autocmds(EVENT_INSERTCHANGE, NULL, NULL, FALSE, curbuf); -  if (State & REPLACE_FLAG) +  set_vim_var_string(VV_INSERTMODE, ((State & REPLACE_FLAG) ? "i" : +                                     replaceState == VREPLACE ? "v" : +                                     "r"), 1); +  apply_autocmds(EVENT_INSERTCHANGE, NULL, NULL, false, curbuf); +  if (State & REPLACE_FLAG) {      State = INSERT | (State & LANGMAP); -  else +  } else {      State = replaceState | (State & LANGMAP); +  }    AppendCharToRedobuff(K_INS);    showmode();    ui_cursor_shape();            /* may show different cursor shape */ @@ -8480,22 +8480,22 @@ static colnr_T get_nolist_virtcol(void)   */  static char_u *do_insert_char_pre(int c)  { -  char_u buf[MB_MAXBYTES + 1]; +  char buf[MB_MAXBYTES + 1];    // Return quickly when there is nothing to do.    if (!has_event(EVENT_INSERTCHARPRE)) {      return NULL;    }    if (has_mbyte) { -    buf[(*mb_char2bytes)(c, buf)] = NUL; +    buf[(*mb_char2bytes)(c, (char_u *) buf)] = NUL;    } else {      buf[0] = c;      buf[1] = NUL;    } -  /* Lock the text to avoid weird things from happening. */ -  ++textlock; -  set_vim_var_string(VV_CHAR, buf, -1);    /* set v:char */ +  // Lock the text to avoid weird things from happening. +  textlock++; +  set_vim_var_string(VV_CHAR, buf, -1);    char_u *res = NULL;    if (apply_autocmds(EVENT_INSERTCHARPRE, NULL, NULL, FALSE, curbuf)) { @@ -8506,8 +8506,8 @@ static char_u *do_insert_char_pre(int c)        res = vim_strsave(get_vim_var_str(VV_CHAR));    } -  set_vim_var_string(VV_CHAR, NULL, -1);    /* clear v:char */ -  --textlock; +  set_vim_var_string(VV_CHAR, NULL, -1); +  textlock--;    return res;  } diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 0d6e3d3ca3..9d370632a1 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -73,6 +73,8 @@  #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 +89,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 +143,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 +179,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 +282,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), +  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 +421,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 +439,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 +450,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 +467,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 +478,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 +507,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 +524,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 +537,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 +772,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 +828,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 +1713,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 +2397,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 +2470,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 +3110,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 +3197,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 +3259,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 +4152,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 +4341,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 +4562,11 @@ eval_index (          *rettv = var1;        }        break; +    case VAR_SPECIAL: +    case VAR_FUNC: +    case VAR_FLOAT: +    case VAR_UNKNOWN: +      break;  // Not evaluating, skipping over subscript      }    } @@ -5089,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;  }  /* @@ -5493,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; @@ -5503,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 @@ -5557,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.   * @@ -5592,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. @@ -5938,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;  } @@ -6529,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. +/// Convert the string to a floating point number  /// -/// @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(). +/// 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 *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. -/// -/// @param[in]  tv  typval_T to convert. -/// @param[out]  len  Location where length of the result will be 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. @@ -7328,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 }, @@ -8102,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); @@ -8149,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); @@ -8880,12 +8365,11 @@ 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));    }  } @@ -9075,7 +8559,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: @@ -9097,9 +8581,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; @@ -12118,6 +11605,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   */ @@ -12159,7 +11687,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;    } @@ -12482,9 +12013,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); @@ -12882,289 +12414,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 @@ -13178,7 +12427,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]; @@ -13186,307 +12435,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 @@ -13504,7 +12459,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)); @@ -13518,7 +12473,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"); @@ -15779,9 +14734,9 @@ 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. +  // 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 *)"'"; @@ -15789,7 +14744,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) { @@ -15798,7 +14753,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 *)""; @@ -16234,7 +15189,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;  } @@ -16365,7 +15320,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);  }  /* @@ -17098,9 +16053,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; @@ -17286,16 +16241,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;  } @@ -18122,14 +17094,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 @@ -18166,11 +17130,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;    } @@ -18189,47 +17153,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);    } @@ -18240,15 +17225,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); +  }  }  /* @@ -18478,25 +17465,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);    } @@ -18534,10 +17519,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;    } @@ -18592,8 +17578,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) { @@ -18698,8 +17695,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; @@ -18970,7 +17970,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); @@ -19247,42 +18247,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;    }  } @@ -19322,6 +18314,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: @@ -19368,8 +18361,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; @@ -19424,7 +18417,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) { @@ -21076,10 +20069,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); @@ -21170,10 +20163,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); @@ -21444,7 +20437,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 *)""; @@ -22607,4 +21600,3 @@ static bool is_watched(dict_T *d)  {    return d && !QUEUE_EMPTY(&d->watchers);  } - diff --git a/src/nvim/eval.h b/src/nvim/eval.h index f51b0f4921..d6800afd52 100644 --- a/src/nvim/eval.h +++ b/src/nvim/eval.h @@ -1,12 +1,17 @@  #ifndef NVIM_EVAL_H  #define NVIM_EVAL_H -#include <msgpack.h> -  #include "nvim/profile.h" +#include "nvim/hashtab.h"  // For hashtab_T +#include "nvim/garray.h"  // For garray_T +#include "nvim/buffer_defs.h"  // For scid_T +#include "nvim/ex_cmds_defs.h"  // For exarg_T + +#define COPYID_INC 2 +#define COPYID_MASK (~0x1)  // All user-defined functions are found in this hashtable. -EXTERN hashtab_T func_hashtab; +extern hashtab_T func_hashtab;  // Structure to hold info for a user function.  typedef struct ufunc ufunc_T; @@ -46,8 +51,8 @@ EXTERN ufunc_T dumuf;  #define HIKEY2UF(p)  ((ufunc_T *)(p - (dumuf.uf_name - (char_u *)&dumuf)))  #define HI2UF(hi)    HIKEY2UF((hi)->hi_key) -/* Defines for Vim variables.  These must match vimvars[] in eval.c! */ -enum { +/// Defines for Vim variables +typedef enum {      VV_COUNT,      VV_COUNT1,      VV_PREVCOUNT, @@ -114,15 +119,35 @@ enum {      VV_ERRORS,      VV_MSGPACK_TYPES,      VV_EVENT, -    VV_LEN,  // number of v: vars -}; +    VV_FALSE, +    VV_TRUE, +    VV_NULL, +    VV__NULL_LIST,  // List with NULL value. For test purposes only. +    VV__NULL_DICT,  // Dictionary with NULL value. For test purposes only. +} VimVarIndex; + +/// All recognized msgpack types +typedef enum { +  kMPNil, +  kMPBoolean, +  kMPInteger, +  kMPFloat, +  kMPString, +  kMPBinary, +  kMPArray, +  kMPMap, +  kMPExt, +#define LAST_MSGPACK_TYPE kMPExt +} MessagePackType; + +/// Array mapping values from MessagePackType to corresponding list pointers +extern const list_T *eval_msgpack_type_lists[LAST_MSGPACK_TYPE + 1]; + +#undef LAST_MSGPACK_TYPE  /// Maximum number of function arguments  #define MAX_FUNC_ARGS   20 -int vim_to_msgpack(msgpack_packer *const, typval_T *const, -                   const char *const objname); -  #ifdef INCLUDE_GENERATED_DECLARATIONS  # include "eval.h.generated.h"  #endif diff --git a/src/nvim/eval/decode.c b/src/nvim/eval/decode.c new file mode 100644 index 0000000000..0774ef515f --- /dev/null +++ b/src/nvim/eval/decode.c @@ -0,0 +1,1116 @@ +#include <stddef.h> + +#include <msgpack.h> + +#include "nvim/eval_defs.h" +#include "nvim/eval.h" +#include "nvim/eval/encode.h" +#include "nvim/ascii.h" +#include "nvim/message.h" +#include "nvim/charset.h"  // vim_str2nr +#include "nvim/lib/kvec.h" +#include "nvim/vim.h"  // OK, FAIL + +/// Helper structure for container_struct +typedef struct { +  size_t stack_index;   ///< Index of current container in stack. +  list_T *special_val;  ///< _VAL key contents for special maps. +                        ///< When container is not a special dictionary it is +                        ///< NULL. +  const char *s;        ///< Location where container starts. +  typval_T container;   ///< Container. Either VAR_LIST, VAR_DICT or VAR_LIST +                        ///< which is _VAL from special dictionary. +} ContainerStackItem; + +/// Helper structure for values struct +typedef struct { +  bool is_special_string;  ///< Indicates that current value is a special +                           ///< dictionary with string. +  bool didcomma;           ///< True if previous token was comma. +  bool didcolon;           ///< True if previous token was colon. +  typval_T val;            ///< Actual value. +} ValuesStackItem; + +/// Vector containing values not yet saved in any container +typedef kvec_t(ValuesStackItem) ValuesStack; + +/// Vector containing containers, each next container is located inside previous +typedef kvec_t(ContainerStackItem) ContainerStack; + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "eval/decode.c.generated.h" +#endif + +/// Create special dictionary +/// +/// @param[out]  rettv  Location where created dictionary will be saved. +/// @param[in]  type  Type of the dictionary. +/// @param[in]  val  Value associated with the _VAL key. +static inline void create_special_dict(typval_T *const rettv, +                                       const MessagePackType type, +                                       typval_T val) +  FUNC_ATTR_NONNULL_ALL +{ +  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 = VAR_UNLOCKED; +  type_di->di_tv.vval.v_list = (list_T *) eval_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); +  dict->dv_refcount++; +  *rettv = (typval_T) { +    .v_type = VAR_DICT, +    .v_lock = VAR_UNLOCKED, +    .vval = { .v_dict = dict }, +  }; +} + +#define DICT_LEN(dict) (dict)->dv_hashtab.ht_used + +/// Helper function used for working with stack vectors used by JSON decoder +/// +/// @param[in,out]  obj  New object. Will either be put into the stack (and, +///                      probably, also inside container) or freed. +/// @param[out]  stack  Object stack. +/// @param[out]  container_stack  Container objects stack. +/// @param[in,out]  pp  Position in string which is currently being parsed. Used +///                     for error reporting and is also set when decoding is +///                     restarted due to the necessity of converting regular +///                     dictionary to a special map. +/// @param[out]  next_map_special  Is set to true when dictionary needs to be +///                                converted to a special map, otherwise not +///                                touched. Indicates that decoding has been +///                                restarted. +/// @param[out]  didcomma  True if previous token was comma. Is set to recorded +///                        value when decoder is restarted, otherwise unused. +/// @param[out]  didcolon  True if previous token was colon. Is set to recorded +///                        value when decoder is restarted, otherwise unused. +/// +/// @return OK in case of success, FAIL in case of error. +static inline int json_decoder_pop(ValuesStackItem obj, +                                   ValuesStack *const stack, +                                   ContainerStack *const container_stack, +                                   const char **const pp, +                                   bool *const next_map_special, +                                   bool *const didcomma, +                                   bool *const didcolon) +  FUNC_ATTR_NONNULL_ALL +{ +  if (kv_size(*container_stack) == 0) { +    kv_push(ValuesStackItem, *stack, obj); +    return OK; +  } +  ContainerStackItem last_container = kv_last(*container_stack); +  const char *val_location = *pp; +  if (obj.val.v_type == last_container.container.v_type +      // vval.v_list and vval.v_dict should have the same size and offset +      && ((void *) obj.val.vval.v_list +          == (void *) last_container.container.vval.v_list)) { +    (void) kv_pop(*container_stack); +    val_location = last_container.s; +    last_container = kv_last(*container_stack); +  } +  if (last_container.container.v_type == VAR_LIST) { +    if (last_container.container.vval.v_list->lv_len != 0 +        && !obj.didcomma) { +      EMSG2(_("E474: Expected comma before list item: %s"), val_location); +      clear_tv(&obj.val); +      return FAIL; +    } +    assert(last_container.special_val == NULL); +    listitem_T *obj_li = listitem_alloc(); +    obj_li->li_tv = obj.val; +    list_append(last_container.container.vval.v_list, obj_li); +  } else if (last_container.stack_index == kv_size(*stack) - 2) { +    if (!obj.didcolon) { +      EMSG2(_("E474: Expected colon before dictionary value: %s"), +            val_location); +      clear_tv(&obj.val); +      return FAIL; +    } +    ValuesStackItem key = kv_pop(*stack); +    if (last_container.special_val == NULL) { +      // These cases should have already been handled. +      assert(!(key.is_special_string +               || key.val.vval.v_string == NULL +               || *key.val.vval.v_string == NUL)); +      dictitem_T *obj_di = dictitem_alloc(key.val.vval.v_string); +      clear_tv(&key.val); +      if (dict_add(last_container.container.vval.v_dict, obj_di) +          == FAIL) { +        assert(false); +      } +      obj_di->di_tv = obj.val; +    } else { +      list_T *const kv_pair = list_alloc(); +      list_append_list(last_container.special_val, kv_pair); +      listitem_T *const key_li = listitem_alloc(); +      key_li->li_tv = key.val; +      list_append(kv_pair, key_li); +      listitem_T *const val_li = listitem_alloc(); +      val_li->li_tv = obj.val; +      list_append(kv_pair, val_li); +    } +  } else { +    // Object with key only +    if (!obj.is_special_string && obj.val.v_type != VAR_STRING) { +      EMSG2(_("E474: Expected string key: %s"), *pp); +      clear_tv(&obj.val); +      return FAIL; +    } else if (!obj.didcomma +               && (last_container.special_val == NULL +                   && (DICT_LEN(last_container.container.vval.v_dict) != 0))) { +      EMSG2(_("E474: Expected comma before dictionary key: %s"), val_location); +      clear_tv(&obj.val); +      return FAIL; +    } +    // Handle empty key and key represented as special dictionary +    if (last_container.special_val == NULL +        && (obj.is_special_string +            || obj.val.vval.v_string == NULL +            || *obj.val.vval.v_string == NUL +            || dict_find(last_container.container.vval.v_dict, +                         obj.val.vval.v_string, -1))) { +      clear_tv(&obj.val); + +      // Restart +      (void) kv_pop(*container_stack); +      ValuesStackItem last_container_val = +          kv_A(*stack, last_container.stack_index); +      while (kv_size(*stack) > last_container.stack_index) { +        clear_tv(&(kv_pop(*stack).val)); +      } +      *pp = last_container.s; +      *didcomma = last_container_val.didcomma; +      *didcolon = last_container_val.didcolon; +      *next_map_special = true; +      return OK; +    } +    kv_push(ValuesStackItem, *stack, obj); +  } +  return OK; +} + +#define LENP(p, e) \ +    ((int) ((e) - (p))), (p) +#define OBJ(obj_tv, is_sp_string, didcomma_, didcolon_) \ +  ((ValuesStackItem) { \ +    .is_special_string = (is_sp_string), \ +    .val = (obj_tv), \ +    .didcomma = (didcomma_), \ +    .didcolon = (didcolon_), \ +  }) + +#define POP(obj_tv, is_sp_string) \ +  do { \ +    if (json_decoder_pop(OBJ(obj_tv, is_sp_string, *didcomma, *didcolon), \ +                         stack, container_stack, \ +                         &p, next_map_special, didcomma, didcolon) \ +        == FAIL) { \ +      goto parse_json_string_fail; \ +    } \ +    if (*next_map_special) { \ +      goto parse_json_string_ret; \ +    } \ +  } while (0) + +/// Parse JSON double-quoted string +/// +/// @param[in]  conv  Defines conversion necessary to convert UTF-8 string to +///                   &encoding. +/// @param[in]  buf  Buffer being converted. +/// @param[in]  buf_len  Length of the buffer. +/// @param[in,out]  pp  Pointer to the start of the string. Must point to '"'. +///                     Is advanced to the closing '"'. Also see +///                     json_decoder_pop(), it may set pp to another location +///                     and alter next_map_special, didcomma and didcolon. +/// @param[out]  stack  Object stack. +/// @param[out]  container_stack  Container objects stack. +/// @param[out]  next_map_special  Is set to true when dictionary is converted +///                                to a special map, otherwise not touched. +/// @param[out]  didcomma  True if previous token was comma. Is set to recorded +///                        value when decoder is restarted, otherwise unused. +/// @param[out]  didcolon  True if previous token was colon. Is set to recorded +///                        value when decoder is restarted, otherwise unused. +/// +/// @return OK in case of success, FAIL in case of error. +static inline int parse_json_string(vimconv_T *const conv, +                                    const char *const buf, const size_t buf_len, +                                    const char **const pp, +                                    ValuesStack *const stack, +                                    ContainerStack *const container_stack, +                                    bool *const next_map_special, +                                    bool *const didcomma, +                                    bool *const didcolon) +  FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_ALWAYS_INLINE +{ +  const char *const e = buf + buf_len; +  const char *p = *pp; +  size_t len = 0; +  const char *const s = ++p; +  int ret = OK; +  while (p < e && *p != '"') { +    if (*p == '\\') { +      p++; +      if (p == e) { +        emsgf(_("E474: Unfinished escape sequence: %.*s"), +              (int) buf_len, buf); +        goto parse_json_string_fail; +      } +      switch (*p) { +        case 'u': { +          if (p + 4 >= e) { +            emsgf(_("E474: Unfinished unicode escape sequence: %.*s"), +                  (int) buf_len, buf); +            goto parse_json_string_fail; +          } else if (!ascii_isxdigit(p[1]) +                     || !ascii_isxdigit(p[2]) +                     || !ascii_isxdigit(p[3]) +                     || !ascii_isxdigit(p[4])) { +            emsgf(_("E474: Expected four hex digits after \\u: %.*s"), +                  LENP(p - 1, e)); +            goto parse_json_string_fail; +          } +          // One UTF-8 character below U+10000 can take up to 3 bytes, +          // above up to 6, but they are encoded using two \u escapes. +          len += 3; +          p += 5; +          break; +        } +        case '\\': +        case '/': +        case '"': +        case 't': +        case 'b': +        case 'n': +        case 'r': +        case 'f': { +          len++; +          p++; +          break; +        } +        default: { +          emsgf(_("E474: Unknown escape sequence: %.*s"), LENP(p - 1, e)); +          goto parse_json_string_fail; +        } +      } +    } else { +      uint8_t p_byte = (uint8_t) *p; +      // unescaped = %x20-21 / %x23-5B / %x5D-10FFFF +      if (p_byte < 0x20) { +        emsgf(_("E474: ASCII control characters cannot be present " +                "inside string: %.*s"), LENP(p, e)); +        goto parse_json_string_fail; +      } +      const int ch = utf_ptr2char((char_u *) p); +      // All characters above U+007F are encoded using two or more bytes +      // and thus cannot possibly be equal to *p. But utf_ptr2char({0xFF, +      // 0}) will return 0xFF, even though 0xFF cannot start any UTF-8 +      // code point at all. +      // +      // The only exception is U+00C3 which is represented as 0xC3 0x83. +      if (ch >= 0x80 && p_byte == ch +          && !(ch == 0xC3 && p + 1 < e && (uint8_t) p[1] == 0x83)) { +        emsgf(_("E474: Only UTF-8 strings allowed: %.*s"), LENP(p, e)); +        goto parse_json_string_fail; +      } else if (ch > 0x10FFFF) { +        emsgf(_("E474: Only UTF-8 code points up to U+10FFFF " +                "are allowed to appear unescaped: %.*s"), LENP(p, e)); +        goto parse_json_string_fail; +      } +      const size_t ch_len = (size_t) utf_char2len(ch); +      assert(ch_len == (size_t) (ch ? utf_ptr2len((char_u *) p) : 1)); +      len += ch_len; +      p += ch_len; +    } +  } +  if (p == e || *p != '"') { +    emsgf(_("E474: Expected string end: %.*s"), (int) buf_len, buf); +    goto parse_json_string_fail; +  } +  if (len == 0) { +    POP(((typval_T) { +      .v_type = VAR_STRING, +      .vval = { .v_string = NULL }, +    }), false); +    goto parse_json_string_ret; +  } +  char *str = xmalloc(len + 1); +  int fst_in_pair = 0; +  char *str_end = str; +  bool hasnul = false; +#define PUT_FST_IN_PAIR(fst_in_pair, str_end) \ +  do { \ +    if (fst_in_pair != 0) { \ +      str_end += utf_char2bytes(fst_in_pair, (char_u *) str_end); \ +      fst_in_pair = 0; \ +    } \ +  } while (0) +  for (const char *t = s; t < p; t++) { +    if (t[0] != '\\' || t[1] != 'u') { +      PUT_FST_IN_PAIR(fst_in_pair, str_end); +    } +    if (*t == '\\') { +      t++; +      switch (*t) { +        case 'u': { +          const char ubuf[] = { t[1], t[2], t[3], t[4] }; +          t += 4; +          unsigned long ch; +          vim_str2nr((char_u *) ubuf, NULL, NULL, +                     STR2NR_HEX | STR2NR_FORCE, NULL, &ch, 4); +          if (ch == 0) { +            hasnul = true; +          } +          if (SURROGATE_HI_START <= ch && ch <= SURROGATE_HI_END) { +            PUT_FST_IN_PAIR(fst_in_pair, str_end); +            fst_in_pair = (int) ch; +          } else if (SURROGATE_LO_START <= ch && ch <= SURROGATE_LO_END +                     && fst_in_pair != 0) { +            const int full_char = ( +                (int) (ch - SURROGATE_LO_START) +                + ((fst_in_pair - SURROGATE_HI_START) << 10) +                + SURROGATE_FIRST_CHAR); +            str_end += utf_char2bytes(full_char, (char_u *) str_end); +            fst_in_pair = 0; +          } else { +            PUT_FST_IN_PAIR(fst_in_pair, str_end); +            str_end += utf_char2bytes((int) ch, (char_u *) str_end); +          } +          break; +        } +        case '\\': +        case '/': +        case '"': +        case 't': +        case 'b': +        case 'n': +        case 'r': +        case 'f': { +          static const char escapes[] = { +            ['\\'] = '\\', +            ['/'] = '/', +            ['"'] = '"', +            ['t'] = TAB, +            ['b'] = BS, +            ['n'] = NL, +            ['r'] = CAR, +            ['f'] = FF, +          }; +          *str_end++ = escapes[(int) *t]; +          break; +        } +        default: { +          assert(false); +        } +      } +    } else { +      *str_end++ = *t; +    } +  } +  PUT_FST_IN_PAIR(fst_in_pair, str_end); +#undef PUT_FST_IN_PAIR +  if (conv->vc_type != CONV_NONE) { +    size_t str_len = (size_t) (str_end - str); +    char *const new_str = (char *) string_convert(conv, (char_u *) str, +                                                  &str_len); +    if (new_str == NULL) { +      emsgf(_("E474: Failed to convert string \"%.*s\" from UTF-8"), +            (int) str_len, str); +      xfree(str); +      goto parse_json_string_fail; +    } +    xfree(str); +    str = new_str; +    str_end = new_str + str_len; +  } +  if (hasnul) { +    typval_T obj; +    list_T *const list = list_alloc(); +    list->lv_refcount++; +    create_special_dict(&obj, kMPString, ((typval_T) { +      .v_type = VAR_LIST, +      .v_lock = VAR_UNLOCKED, +      .vval = { .v_list = list }, +    })); +    if (encode_list_write((void *) list, str, (size_t) (str_end - str)) +        == -1) { +      clear_tv(&obj); +      goto parse_json_string_fail; +    } +    xfree(str); +    POP(obj, true); +  } else { +    *str_end = NUL; +    POP(((typval_T) { +      .v_type = VAR_STRING, +      .vval = { .v_string = (char_u *) str }, +    }), false); +  } +  goto parse_json_string_ret; +parse_json_string_fail: +  ret = FAIL; +parse_json_string_ret: +  *pp = p; +  return ret; +} + +#undef POP + +/// Parse JSON number: both floating-point and integer +/// +/// Number format: `-?\d+(?:.\d+)?(?:[eE][+-]?\d+)?`. +/// +/// @param[in]  buf  Buffer being converted. +/// @param[in]  buf_len  Length of the buffer. +/// @param[in,out]  pp  Pointer to the start of the number. Must point to +///                     a digit or a minus sign. Is advanced to the last +///                     character of the number. Also see json_decoder_pop(), it +///                     may set pp to another location and alter +///                     next_map_special, didcomma and didcolon. +/// @param[out]  stack  Object stack. +/// @param[out]  container_stack  Container objects stack. +/// @param[out]  next_map_special  Is set to true when dictionary is converted +///                                to a special map, otherwise not touched. +/// @param[out]  didcomma  True if previous token was comma. Is set to recorded +///                        value when decoder is restarted, otherwise unused. +/// @param[out]  didcolon  True if previous token was colon. Is set to recorded +///                        value when decoder is restarted, otherwise unused. +/// +/// @return OK in case of success, FAIL in case of error. +static inline int parse_json_number(const char *const buf, const size_t buf_len, +                                    const char **const pp, +                                    ValuesStack *const stack, +                                    ContainerStack *const container_stack, +                                    bool *const next_map_special, +                                    bool *const didcomma, +                                    bool *const didcolon) +  FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_ALWAYS_INLINE +{ +  const char *const e = buf + buf_len; +  const char *p = *pp; +  int ret = OK; +  const char *const s = p; +  const char *ints = NULL; +  const char *fracs = NULL; +  const char *exps = NULL; +  const char *exps_s = NULL; +  if (*p == '-') { +    p++; +  } +  ints = p; +  if (p >= e) { +    goto parse_json_number_check; +  } +  while (p < e && ascii_isdigit(*p)) { +    p++; +  } +  if (p != ints + 1 && *ints == '0') { +    emsgf(_("E474: Leading zeroes are not allowed: %.*s"), LENP(s, e)); +    goto parse_json_number_fail; +  } +  if (p >= e || p == ints) { +    goto parse_json_number_check; +  } +  if (*p == '.') { +    p++; +    fracs = p; +    while (p < e && ascii_isdigit(*p)) { +      p++; +    } +    if (p >= e || p == fracs) { +      goto parse_json_number_check; +    } +  } +  if (*p == 'e' || *p == 'E') { +    p++; +    exps_s = p; +    if (p < e && (*p == '-' || *p == '+')) { +      p++; +    } +    exps = p; +    while (p < e && ascii_isdigit(*p)) { +      p++; +    } +  } +parse_json_number_check: +  if (p == ints) { +    emsgf(_("E474: Missing number after minus sign: %.*s"), LENP(s, e)); +    goto parse_json_number_fail; +  } else if (p == fracs || exps_s == fracs + 1) { +    emsgf(_("E474: Missing number after decimal dot: %.*s"), LENP(s, e)); +    goto parse_json_number_fail; +  } else if (p == exps) { +    emsgf(_("E474: Missing exponent: %.*s"), LENP(s, e)); +    goto parse_json_number_fail; +  } +  typval_T tv = { +    .v_type = VAR_NUMBER, +    .v_lock = VAR_UNLOCKED, +  }; +  const size_t exp_num_len = (size_t) (p - s); +  if (fracs || exps) { +    // Convert floating-point number +    const size_t num_len = string2float(s, &tv.vval.v_float); +    if (exp_num_len != num_len) { +      emsgf(_("E685: internal error: while converting number \"%.*s\" " +              "to float string2float consumed %zu bytes in place of %zu"), +            (int) exp_num_len, s, num_len, exp_num_len); +    } +    tv.v_type = VAR_FLOAT; +  } else { +    // Convert integer +    long nr; +    int num_len; +    vim_str2nr((char_u *) s, NULL, &num_len, 0, &nr, NULL, (int) (p - s)); +    if ((int) exp_num_len != num_len) { +      emsgf(_("E685: internal error: while converting number \"%.*s\" " +              "to integer vim_str2nr consumed %i bytes in place of %zu"), +            (int) exp_num_len, s, num_len, exp_num_len); +    } +    tv.vval.v_number = (varnumber_T) nr; +  } +  if (json_decoder_pop(OBJ(tv, false, *didcomma, *didcolon), +                       stack, container_stack, +                       &p, next_map_special, didcomma, didcolon) == FAIL) { +    goto parse_json_number_fail; +  } +  if (*next_map_special) { +    goto parse_json_number_ret; +  } +  p--; +  goto parse_json_number_ret; +parse_json_number_fail: +  ret = FAIL; +parse_json_number_ret: +  *pp = p; +  return ret; +} + +#define POP(obj_tv, is_sp_string) \ +  do { \ +    if (json_decoder_pop(OBJ(obj_tv, is_sp_string, didcomma, didcolon), \ +                         &stack, &container_stack, \ +                         &p, &next_map_special, &didcomma, &didcolon) \ +        == FAIL) { \ +      goto json_decode_string_fail; \ +    } \ +    if (next_map_special) { \ +      goto json_decode_string_cycle_start; \ +    } \ +  } while (0) + +/// Convert JSON string into VimL object +/// +/// @param[in]  buf  String to convert. UTF-8 encoding is assumed. +/// @param[in]  buf_len  Length of the string. +/// @param[out]  rettv  Location where to save results. +/// +/// @return OK in case of success, FAIL otherwise. +int json_decode_string(const char *const buf, const size_t buf_len, +                       typval_T *const rettv) +  FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT +{ +  const char *p = buf; +  const char *const e = buf + buf_len; +  while (p < e && (*p == ' ' || *p == TAB || *p == NL || *p == CAR)) { +    p++; +  } +  if (p == e) { +    EMSG(_("E474: Attempt to decode a blank string")); +    return FAIL; +  } +  vimconv_T conv = { .vc_type = CONV_NONE }; +  convert_setup(&conv, (char_u *) "utf-8", p_enc); +  conv.vc_fail = true; +  int ret = OK; +  ValuesStack stack; +  kv_init(stack); +  ContainerStack container_stack; +  kv_init(container_stack); +  rettv->v_type = VAR_UNKNOWN; +  bool didcomma = false; +  bool didcolon = false; +  bool next_map_special = false; +  for (; p < e; p++) { +json_decode_string_cycle_start: +    assert(*p == '{' || next_map_special == false); +    switch (*p) { +      case '}': +      case ']': { +        if (kv_size(container_stack) == 0) { +          emsgf(_("E474: No container to close: %.*s"), LENP(p, e)); +          goto json_decode_string_fail; +        } +        ContainerStackItem last_container = kv_last(container_stack); +        if (*p == '}' && last_container.container.v_type != VAR_DICT) { +          emsgf(_("E474: Closing list with curly bracket: %.*s"), LENP(p, e)); +          goto json_decode_string_fail; +        } else if (*p == ']' && last_container.container.v_type != VAR_LIST) { +          emsgf(_("E474: Closing dictionary with square bracket: %.*s"), +                LENP(p, e)); +          goto json_decode_string_fail; +        } else if (didcomma) { +          emsgf(_("E474: Trailing comma: %.*s"), LENP(p, e)); +          goto json_decode_string_fail; +        } else if (didcolon) { +          emsgf(_("E474: Expected value after colon: %.*s"), LENP(p, e)); +          goto json_decode_string_fail; +        } else if (last_container.stack_index != kv_size(stack) - 1) { +          assert(last_container.stack_index < kv_size(stack) - 1); +          emsgf(_("E474: Expected value: %.*s"), LENP(p, e)); +          goto json_decode_string_fail; +        } +        if (kv_size(stack) == 1) { +          p++; +          (void) kv_pop(container_stack); +          goto json_decode_string_after_cycle; +        } else { +          if (json_decoder_pop(kv_pop(stack), &stack, &container_stack, &p, +                               &next_map_special, &didcomma, &didcolon) +              == FAIL) { +            goto json_decode_string_fail; +          } +          assert(!next_map_special); +          break; +        } +      } +      case ',': { +        if (kv_size(container_stack) == 0) { +          emsgf(_("E474: Comma not inside container: %.*s"), LENP(p, e)); +          goto json_decode_string_fail; +        } +        ContainerStackItem last_container = kv_last(container_stack); +        if (didcomma) { +          emsgf(_("E474: Duplicate comma: %.*s"), LENP(p, e)); +          goto json_decode_string_fail; +        } else if (didcolon) { +          emsgf(_("E474: Comma after colon: %.*s"), LENP(p, e)); +          goto json_decode_string_fail; +        } else if (last_container.container.v_type == VAR_DICT +                   && last_container.stack_index != kv_size(stack) - 1) { +          emsgf(_("E474: Using comma in place of colon: %.*s"), LENP(p, e)); +          goto json_decode_string_fail; +        } else if (last_container.special_val == NULL +                   ? (last_container.container.v_type == VAR_DICT +                      ? (DICT_LEN(last_container.container.vval.v_dict) == 0) +                      : (last_container.container.vval.v_list->lv_len == 0)) +                   : (last_container.special_val->lv_len == 0)) { +          emsgf(_("E474: Leading comma: %.*s"), LENP(p, e)); +          goto json_decode_string_fail; +        } +        didcomma = true; +        continue; +      } +      case ':': { +        if (kv_size(container_stack) == 0) { +          emsgf(_("E474: Colon not inside container: %.*s"), LENP(p, e)); +          goto json_decode_string_fail; +        } +        ContainerStackItem last_container = kv_last(container_stack); +        if (last_container.container.v_type != VAR_DICT) { +          emsgf(_("E474: Using colon not in dictionary: %.*s"), LENP(p, e)); +          goto json_decode_string_fail; +        } else if (last_container.stack_index != kv_size(stack) - 2) { +          emsgf(_("E474: Unexpected colon: %.*s"), LENP(p, e)); +          goto json_decode_string_fail; +        } else if (didcomma) { +          emsgf(_("E474: Colon after comma: %.*s"), LENP(p, e)); +          goto json_decode_string_fail; +        } else if (didcolon) { +          emsgf(_("E474: Duplicate colon: %.*s"), LENP(p, e)); +          goto json_decode_string_fail; +        } +        didcolon = true; +        continue; +      } +      case ' ': +      case TAB: +      case NL: +      case CAR: { +        continue; +      } +      case 'n': { +        if ((p + 3) >= e || strncmp(p + 1, "ull", 3) != 0) { +          emsgf(_("E474: Expected null: %.*s"), LENP(p, e)); +          goto json_decode_string_fail; +        } +        p += 3; +        POP(((typval_T) { +          .v_type = VAR_SPECIAL, +          .v_lock = VAR_UNLOCKED, +          .vval = { .v_special = kSpecialVarNull }, +        }), false); +        break; +      } +      case 't': { +        if ((p + 3) >= e || strncmp(p + 1, "rue", 3) != 0) { +          emsgf(_("E474: Expected true: %.*s"), LENP(p, e)); +          goto json_decode_string_fail; +        } +        p += 3; +        POP(((typval_T) { +          .v_type = VAR_SPECIAL, +          .v_lock = VAR_UNLOCKED, +          .vval = { .v_special = kSpecialVarTrue }, +        }), false); +        break; +      } +      case 'f': { +        if ((p + 4) >= e || strncmp(p + 1, "alse", 4) != 0) { +          emsgf(_("E474: Expected false: %.*s"), LENP(p, e)); +          goto json_decode_string_fail; +        } +        p += 4; +        POP(((typval_T) { +          .v_type = VAR_SPECIAL, +          .v_lock = VAR_UNLOCKED, +          .vval = { .v_special = kSpecialVarFalse }, +        }), false); +        break; +      } +      case '"': { +        if (parse_json_string(&conv, buf, buf_len, &p, &stack, &container_stack, +                              &next_map_special, &didcomma, &didcolon) +            == FAIL) { +          // Error message was already given +          goto json_decode_string_fail; +        } +        if (next_map_special) { +          goto json_decode_string_cycle_start; +        } +        break; +      } +      case '-': +      case '0': +      case '1': +      case '2': +      case '3': +      case '4': +      case '5': +      case '6': +      case '7': +      case '8': +      case '9': { +        if (parse_json_number(buf, buf_len, &p, &stack, &container_stack, +                              &next_map_special, &didcomma, &didcolon) +            == FAIL) { +          // Error message was already given +          goto json_decode_string_fail; +        } +        if (next_map_special) { +          goto json_decode_string_cycle_start; +        } +        break; +      } +      case '[': { +        list_T *list = list_alloc(); +        list->lv_refcount++; +        typval_T tv = { +          .v_type = VAR_LIST, +          .v_lock = VAR_UNLOCKED, +          .vval = { .v_list = list }, +        }; +        kv_push(ContainerStackItem, container_stack, ((ContainerStackItem) { +          .stack_index = kv_size(stack), +          .s = p, +          .container = tv, +          .special_val = NULL, +        })); +        kv_push(ValuesStackItem, stack, OBJ(tv, false, didcomma, didcolon)); +        break; +      } +      case '{': { +        typval_T tv; +        list_T *val_list = NULL; +        if (next_map_special) { +          next_map_special = false; +          val_list = list_alloc(); +          val_list->lv_refcount++; +          create_special_dict(&tv, kMPMap, ((typval_T) { +            .v_type = VAR_LIST, +            .v_lock = VAR_UNLOCKED, +            .vval = { .v_list = val_list }, +          })); +        } else { +          dict_T *dict = dict_alloc(); +          dict->dv_refcount++; +          tv = (typval_T) { +            .v_type = VAR_DICT, +            .v_lock = VAR_UNLOCKED, +            .vval = { .v_dict = dict }, +          }; +        } +        kv_push(ContainerStackItem, container_stack, ((ContainerStackItem) { +          .stack_index = kv_size(stack), +          .s = p, +          .container = tv, +          .special_val = val_list, +        })); +        kv_push(ValuesStackItem, stack, OBJ(tv, false, didcomma, didcolon)); +        break; +      } +      default: { +        emsgf(_("E474: Unidentified byte: %.*s"), LENP(p, e)); +        goto json_decode_string_fail; +      } +    } +    didcomma = false; +    didcolon = false; +    if (kv_size(container_stack) == 0) { +      p++; +      break; +    } +  } +json_decode_string_after_cycle: +  for (; p < e; p++) { +    switch (*p) { +      case NL: +      case ' ': +      case TAB: +      case CAR: { +        break; +      } +      default: { +        emsgf(_("E474: Trailing characters: %.*s"), LENP(p, e)); +        goto json_decode_string_fail; +      } +    } +  } +  if (kv_size(stack) == 1 && kv_size(container_stack) == 0) { +    *rettv = kv_pop(stack).val; +    goto json_decode_string_ret; +  } +  emsgf(_("E474: Unexpected end of input: %.*s"), (int) buf_len, buf); +json_decode_string_fail: +  ret = FAIL; +  while (kv_size(stack)) { +    clear_tv(&(kv_pop(stack).val)); +  } +json_decode_string_ret: +  kv_destroy(stack); +  kv_destroy(container_stack); +  return ret; +} + +#undef LENP +#undef POP + +#undef OBJ + +#undef DICT_LEN + +/// 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 +{ +  switch (mobj.type) { +    case MSGPACK_OBJECT_NIL: { +      *rettv = (typval_T) { +        .v_type = VAR_SPECIAL, +        .v_lock = VAR_UNLOCKED, +        .vval = { .v_special = kSpecialVarNull }, +      }; +      break; +    } +    case MSGPACK_OBJECT_BOOLEAN: { +      *rettv = (typval_T) { +        .v_type = VAR_SPECIAL, +        .v_lock = VAR_UNLOCKED, +        .vval = { +          .v_special = mobj.via.boolean ? kSpecialVarTrue : kSpecialVarFalse +        }, +      }; +      break; +    } +    case MSGPACK_OBJECT_POSITIVE_INTEGER: { +      if (mobj.via.u64 <= VARNUMBER_MAX) { +        *rettv = (typval_T) { +          .v_type = VAR_NUMBER, +          .v_lock = VAR_UNLOCKED, +          .vval = { .v_number = (varnumber_T) mobj.via.u64 }, +        }; +      } else { +        list_T *const list = list_alloc(); +        list->lv_refcount++; +        create_special_dict(rettv, kMPInteger, ((typval_T) { +          .v_type = VAR_LIST, +          .v_lock = VAR_UNLOCKED, +          .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 = VAR_UNLOCKED, +          .vval = { .v_number = (varnumber_T) mobj.via.i64 }, +        }; +      } else { +        list_T *const list = list_alloc(); +        list->lv_refcount++; +        create_special_dict(rettv, kMPInteger, ((typval_T) { +          .v_type = VAR_LIST, +          .v_lock = VAR_UNLOCKED, +          .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 = VAR_UNLOCKED, +        .vval = { .v_float = mobj.via.f64 }, +      }; +      break; +    } +    case MSGPACK_OBJECT_STR: { +      list_T *const list = list_alloc(); +      list->lv_refcount++; +      create_special_dict(rettv, kMPString, ((typval_T) { +        .v_type = VAR_LIST, +        .v_lock = VAR_UNLOCKED, +        .vval = { .v_list = list }, +      })); +      if (encode_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 = VAR_UNLOCKED, +          .vval = { .v_string = xmemdupz(mobj.via.bin.ptr, mobj.via.bin.size) }, +        }; +        break; +      } +      list_T *const list = list_alloc(); +      list->lv_refcount++; +      create_special_dict(rettv, kMPBinary, ((typval_T) { +        .v_type = VAR_LIST, +        .v_lock = VAR_UNLOCKED, +        .vval = { .v_list = list }, +      })); +      if (encode_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 = VAR_UNLOCKED, +        .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 = VAR_UNLOCKED, +        .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++; +      create_special_dict(rettv, kMPMap, ((typval_T) { +        .v_type = VAR_LIST, +        .v_lock = VAR_UNLOCKED, +        .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); +      create_special_dict(rettv, kMPExt, ((typval_T) { +        .v_type = VAR_LIST, +        .v_lock = VAR_UNLOCKED, +        .vval = { .v_list = list }, +      })); +      if (encode_list_write((void *) ext_val_list, mobj.via.ext.ptr, +                            mobj.via.ext.size) == -1) { +        return FAIL; +      } +      break; +    } +  } +  return OK; +} diff --git a/src/nvim/eval/decode.h b/src/nvim/eval/decode.h new file mode 100644 index 0000000000..5c25a64f7a --- /dev/null +++ b/src/nvim/eval/decode.h @@ -0,0 +1,13 @@ +#ifndef NVIM_EVAL_DECODE_H +#define NVIM_EVAL_DECODE_H + +#include <stddef.h> + +#include <msgpack.h> + +#include "nvim/eval_defs.h" + +#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 new file mode 100644 index 0000000000..c651a50be9 --- /dev/null +++ b/src/nvim/eval/encode.c @@ -0,0 +1,1296 @@ +/// @file encode.c +/// +/// File containing functions for encoding and decoding VimL values. +/// +/// Split out from eval.c. + +#include <msgpack.h> +#include <inttypes.h> +#include <assert.h> +#include <math.h> + +#include "nvim/eval/encode.h" +#include "nvim/buffer_defs.h"  // vimconv_T +#include "nvim/eval.h" +#include "nvim/eval_defs.h" +#include "nvim/garray.h" +#include "nvim/mbyte.h" +#include "nvim/message.h" +#include "nvim/memory.h" +#include "nvim/charset.h"  // vim_isprintc() +#include "nvim/macros.h" +#include "nvim/ascii.h" +#include "nvim/vim.h"  // For _() +#include "nvim/lib/kvec.h" + +#define ga_concat(a, b) ga_concat(a, (char_u *)b) +#define utf_ptr2char(b) utf_ptr2char((char_u *)b) +#define utf_ptr2len(b) ((size_t)utf_ptr2len((char_u *)b)) +#define utf_char2len(b) ((size_t)utf_char2len(b)) +#define string_convert(a, b, c) \ +      ((char *)string_convert((vimconv_T *)a, (char_u *)b, c)) +#define convert_setup(vcp, from, to) \ +    (convert_setup(vcp, (char_u *)from, (char_u *)to)) + +/// 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; + +const char *const encode_special_var_names[] = { +  [kSpecialVarNull] = "null", +  [kSpecialVarTrue] = "true", +  [kSpecialVarFalse] = "false", +}; + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "eval/encode.c.generated.h" +#endif + +/// Msgpack callback for writing to readfile()-style list +int encode_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; +  listitem_T *li = list->lv_last; + +  // Continue the last list element +  if (li != NULL) { +    line_end = xmemscan(buf, NL, len); +    if (line_end != buf) { +      const size_t line_length = (size_t)(line_end - buf); +      char *str = (char *)li->li_tv.vval.v_string; +      const size_t li_len = (str == NULL ? 0 : strlen(str)); +      li->li_tv.vval.v_string = xrealloc(str, li_len + line_length + 1); +      str = (char *)li->li_tv.vval.v_string + li_len; +      memcpy(str, buf, line_length); +      str[line_length] = 0; +      memchrsub(str, NUL, NL, line_length); +    } +    line_end++; +  } + +  while (line_end < end) { +    const char *line_start = line_end; +    line_end = xmemscan(line_start, NL, (size_t) (end - line_start)); +    char *str = NULL; +    if (line_end != line_start) { +      const size_t line_length = (size_t)(line_end - line_start); +      str = xmemdupz(line_start, line_length); +      memchrsub(str, NUL, NL, line_length); +    } +    list_append_allocated_string(list, str); +    line_end++; +  } +  if (line_end == end) { +    list_append_allocated_string(list, NULL); +  } +  return 0; +} + +/// Abort conversion to string after a recursion error. +static bool did_echo_string_emsg = false; + +/// 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, ", "); +    } +    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 = encode_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 = encode_tv2echo(&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; +} + +/// 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. +bool encode_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 = encode_init_lrstate(list); +  char *const buf = xmalloc(len); +  size_t read_bytes; +  if (encode_read_from_list(&lrstate, buf, len, &read_bytes) != OK) { +    assert(false); +  } +  assert(len == read_bytes); +  *ret_buf = buf; +  return true; +} + +/// 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. +int encode_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 = (char) state->li->li_tv.vval.v_string[state->offset++]; +      *p++ = (char) ((char) ch == (char) NL ? (char) NUL : (char) 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); +} + +/// Code for checking whether container references itself +/// +/// @param[in,out]  val  Container to check. +/// @param  copyID_attr  Name of the container attribute that holds copyID. +///                      After checking whether value of this attribute is +///                      copyID (variable) it is set to copyID. +#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 TV_STRLEN(tv) \ +    (tv->vval.v_string == NULL ? 0 : STRLEN(tv->vval.v_string)) + +/// 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, TV_STRLEN(tv)); \ +      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_SPECIAL: { \ +      switch (tv->vval.v_special) { \ +        case kSpecialVarNull: { \ +          CONV_NIL(); \ +          break; \ +        } \ +        case kSpecialVarTrue: \ +        case kSpecialVarFalse: { \ +          CONV_BOOL(tv->vval.v_special == kSpecialVarTrue); \ +          break; \ +        } \ +      } \ +      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(eval_msgpack_type_lists); i++) { \ +          if (type_di->di_tv.vval.v_list == eval_msgpack_type_lists[i]) { \ +            break; \ +          } \ +        } \ +        if (i == ARRAY_SIZE(eval_msgpack_type_lists)) { \ +          goto name##_convert_one_value_regular_dict; \ +        } \ +        switch ((MessagePackType) i) { \ +          case kMPNil: { \ +            CONV_NIL(); \ +            break; \ +          } \ +          case kMPBoolean: { \ +            if (val_di->di_tv.v_type != VAR_NUMBER) { \ +              goto name##_convert_one_value_regular_dict; \ +            } \ +            CONV_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 (!encode_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; \ +            } \ +            list_T *const val_list = val_di->di_tv.vval.v_list; \ +            if (val_list == NULL || val_list->lv_len == 0) { \ +              CONV_EMPTY_DICT(); \ +              break; \ +            } \ +            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_DICT_START(val_list->lv_len); \ +            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 (!encode_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->dv_hashtab.ht_used); \ +      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; \ +    } \ +    case VAR_UNKNOWN: { \ +      EMSG2(_(e_intern2), #name "_convert_one_value()"); \ +      return FAIL; \ +    } \ +  } \ +  return OK; \ +} \ +\ +scope int encode_vim_to_##name(firstargtype firstargname, typval_T *const tv, \ +                               const char *const objname) \ +  FUNC_ATTR_WARN_UNUSED_RESULT \ +{ \ +  const int copyID = get_copyID(); \ +  MPConvStack mpstack; \ +  kv_init(mpstack); \ +  if (name##_convert_one_value(firstargname, &mpstack, tv, copyID, objname) \ +      == FAIL) { \ +    goto encode_vim_to_##name##_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(); \ +          continue; \ +        } else if (cur_mpsv->data.d.todo \ +                   != cur_mpsv->data.d.dict->dv_hashtab.ht_used) { \ +          CONV_DICT_BETWEEN_ITEMS(); \ +        } \ +        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_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_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; \ +          CONV_DICT_END(); \ +          continue; \ +        } else if (cur_mpsv->data.l.li != cur_mpsv->data.l.list->lv_first) { \ +          CONV_DICT_BETWEEN_ITEMS(); \ +        } \ +        const list_T *const kv_pair = cur_mpsv->data.l.li->li_tv.vval.v_list; \ +        CONV_SPECIAL_DICT_KEY_CHECK(name, kv_pair); \ +        if (name##_convert_one_value(firstargname, &mpstack, \ +                                     &kv_pair->lv_first->li_tv, copyID, \ +                                     objname) == FAIL) { \ +          goto encode_vim_to_##name##_error_ret; \ +        } \ +        CONV_DICT_AFTER_KEY(); \ +        cur_tv = &kv_pair->lv_last->li_tv; \ +        cur_mpsv->data.l.li = cur_mpsv->data.l.li->li_next; \ +        break; \ +      } \ +    } \ +    assert(cur_tv != NULL); \ +    if (name##_convert_one_value(firstargname, &mpstack, cur_tv, copyID, \ +                                 objname) == FAIL) { \ +      goto encode_vim_to_##name##_error_ret; \ +    } \ +  } \ +  kv_destroy(mpstack); \ +  return OK; \ +encode_vim_to_##name##_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, "''"); \ +      } else { \ +        const size_t len_ = (len); \ +        ga_grow(gap, (int) (2 + len_ + memcnt(buf_, '\'', len_))); \ +        ga_append(gap, '\''); \ +        for (size_t i = 0; i < len_; i++) { \ +          if (buf_[i] == '\'') { \ +            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, ARRAY_SIZE(numbuf), "%" PRId64, (int64_t) (num)); \ +      ga_concat(gap, 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, ARRAY_SIZE(numbuf), "%g", flt_); \ +          ga_concat(gap, (char_u *) numbuf); \ +        } \ +      } \ +    } while (0) + +#define CONV_FUNC(fun) \ +    do { \ +      ga_concat(gap, "function("); \ +      CONV_STRING(fun, STRLEN(fun)); \ +      ga_append(gap, ')'); \ +    } while (0) + +#define CONV_EMPTY_LIST() \ +    ga_concat(gap, "[]") + +#define CONV_LIST_START(lst) \ +    ga_append(gap, '[') + +#define CONV_EMPTY_DICT() \ +    ga_concat(gap, "{}") + +#define CONV_NIL() \ +    ga_concat(gap, "v:null") + +#define CONV_BOOL(num) \ +    ga_concat(gap, ((num)? "v:true": "v:false")) + +#define CONV_UNSIGNED_NUMBER(num) + +#define CONV_DICT_START(len) \ +    ga_append(gap, '{') + +#define CONV_DICT_END() \ +    ga_append(gap, '}') + +#define CONV_DICT_AFTER_KEY() \ +    ga_concat(gap, ": ") + +#define CONV_DICT_BETWEEN_ITEMS() \ +    ga_concat(gap, ", ") + +#define CONV_SPECIAL_DICT_KEY_CHECK(name, kv_pair) + +#define CONV_LIST_END(lst) \ +    ga_append(gap, ']') + +#define CONV_LIST_BETWEEN_ITEMS() \ +    CONV_DICT_BETWEEN_ITEMS() + +#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(*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, ARRAY_SIZE(ebuf), "{E724@%zu}", backref); \ +      ga_concat(gap, &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(*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, ARRAY_SIZE(ebuf), "{...@%zu}", backref); \ +      } else { \ +        vim_snprintf(ebuf, ARRAY_SIZE(ebuf), "[...@%zu]", backref); \ +      } \ +      ga_concat(gap, &ebuf[0]); \ +      return OK; \ +    } while (0) + +DEFINE_VIML_CONV_FUNCTIONS(, echo, garray_T *const, gap) + +#undef CONV_RECURSE +#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")); \ +      } \ +      return OK; \ +    } while (0) + +#undef CONV_ALLOW_SPECIAL +#define CONV_ALLOW_SPECIAL true + +#undef CONV_NIL +#define CONV_NIL() \ +      ga_concat(gap, "null") + +#undef CONV_BOOL +#define CONV_BOOL(num) \ +      ga_concat(gap, ((num)? "true": "false")) + +#undef CONV_UNSIGNED_NUMBER +#define CONV_UNSIGNED_NUMBER(num) \ +      do { \ +        char numbuf[NUMBUFLEN]; \ +        vim_snprintf(numbuf, ARRAY_SIZE(numbuf), "%" PRIu64, (num)); \ +        ga_concat(gap, numbuf); \ +      } while (0) + +#undef CONV_FLOAT +#define CONV_FLOAT(flt) \ +    do { \ +      const float_T flt_ = (flt); \ +      switch (fpclassify(flt_)) { \ +        case FP_NAN: { \ +          EMSG(_("E474: Unable to represent NaN value in JSON")); \ +          return FAIL; \ +        } \ +        case FP_INFINITE: { \ +          EMSG(_("E474: Unable to represent infinity in JSON")); \ +          return FAIL; \ +        } \ +        default: { \ +          char numbuf[NUMBUFLEN]; \ +          vim_snprintf(numbuf, ARRAY_SIZE(numbuf), "%g", flt_); \ +          ga_concat(gap, (char_u *) numbuf); \ +          break; \ +        } \ +      } \ +    } while (0) + +/// Last used p_enc value +/// +/// Generic pointer: it is not used as a string, only pointer comparisons are +/// performed. Must not be freed. +static const void *last_p_enc = NULL; + +/// Conversion setup for converting from last_p_enc to UTF-8 +static vimconv_T p_enc_conv = { +  .vc_type = CONV_NONE, +}; + +/// Escape sequences used in JSON +static const char escapes[][3] = { +  [BS] = "\\b", +  [TAB] = "\\t", +  [NL] = "\\n", +  [CAR] = "\\r", +  ['"'] = "\\\"", +  ['\\'] = "\\\\", +  [FF] = "\\f", +}; + +static const char xdigits[] = "0123456789ABCDEF"; + +/// Convert given string to JSON string +/// +/// @param[out]  gap  Garray where result will be saved. +/// @param[in]  buf  Converted string. +/// @param[in]  len  Converted string length. +/// +/// @return OK in case of success, FAIL otherwise. +static inline int convert_to_json_string(garray_T *const gap, +                                         const char *const buf, +                                         const size_t len) +  FUNC_ATTR_NONNULL_ARG(1) FUNC_ATTR_ALWAYS_INLINE +{ +  const char *utf_buf = buf; +  if (utf_buf == NULL) { +    ga_concat(gap, "\"\""); +  } else { +    size_t utf_len = len; +    char *tofree = NULL; +    if (last_p_enc != (const void *) p_enc) { +      p_enc_conv.vc_type = CONV_NONE; +      convert_setup(&p_enc_conv, p_enc, "utf-8"); +      p_enc_conv.vc_fail = true; +      last_p_enc = p_enc; +    } +    if (p_enc_conv.vc_type != CONV_NONE) { +      tofree = string_convert(&p_enc_conv, buf, &utf_len); +      if (tofree == NULL) { +        emsgf(_("E474: Failed to convert string \"%.*s\" to UTF-8"), +              utf_len, utf_buf); +        return FAIL; +      } +      utf_buf = tofree; +    } +    size_t str_len = 0; +    // Encode character as \u0000 if +    // 1. It is an ASCII control character (0x0 .. 0x1F, 0x7F). +    // 2. &encoding is not UTF-8 and code point is above 0x7F. +    // 3. &encoding is UTF-8 and code point is not printable according to +    //    utf_printable(). +    // This is done to make it possible to :echo values when &encoding is not +    // UTF-8. +#define ENCODE_RAW(p_enc_conv, ch) \ +    (ch >= 0x20 && (p_enc_conv.vc_type == CONV_NONE \ +                    ? utf_printable(ch) \ +                    : ch < 0x7F)) +    for (size_t i = 0; i < utf_len;) { +      const int ch = utf_ptr2char(utf_buf + i); +      const size_t shift = (ch == 0? 1: utf_ptr2len(utf_buf + i)); +      assert(shift > 0); +      i += shift; +      switch (ch) { +        case BS: +        case TAB: +        case NL: +        case FF: +        case CAR: +        case '"': +        case '\\': { +          str_len += 2; +          break; +        } +        default: { +          if (ch > 0x7F && shift == 1) { +            emsgf(_("E474: String \"%.*s\" contains byte that does not start " +                    "any UTF-8 character"), +                  utf_len - (i - shift), utf_buf + i - shift); +            xfree(tofree); +            return FAIL; +          } else if ((SURROGATE_HI_START <= ch && ch <= SURROGATE_HI_END) +                     || (SURROGATE_LO_START <= ch && ch <= SURROGATE_LO_END)) { +            emsgf(_("E474: UTF-8 string contains code point which belongs " +                    "to a surrogate pair: %.*s"), +                  utf_len - (i - shift), utf_buf + i - shift); +            xfree(tofree); +            return FAIL; +          } else if (ENCODE_RAW(p_enc_conv, ch)) { +            str_len += shift; +          } else { +            str_len += ((sizeof("\\u1234") - 1) +                        * (size_t) (1 + (ch >= SURROGATE_FIRST_CHAR))); +          } +          break; +        } +      } +    } +    ga_append(gap, '"'); +    ga_grow(gap, (int) str_len); +    for (size_t i = 0; i < utf_len;) { +      const int ch = utf_ptr2char(utf_buf + i); +      const size_t shift = (ch == 0? 1: utf_char2len(ch)); +      assert(shift > 0); +      // Is false on invalid unicode, but this should already be handled. +      assert(ch == 0 || shift == utf_ptr2len(utf_buf + i)); +      switch (ch) { +        case BS: +        case TAB: +        case NL: +        case FF: +        case CAR: +        case '"': +        case '\\': { +          ga_concat_len(gap, escapes[ch], 2); +          break; +        } +        default: { +          if (ENCODE_RAW(p_enc_conv, ch)) { +            ga_concat_len(gap, utf_buf + i, shift); +          } else if (ch < SURROGATE_FIRST_CHAR) { +            ga_concat_len(gap, ((const char[]) { +                '\\', 'u', +                xdigits[(ch >> (4 * 3)) & 0xF], +                xdigits[(ch >> (4 * 2)) & 0xF], +                xdigits[(ch >> (4 * 1)) & 0xF], +                xdigits[(ch >> (4 * 0)) & 0xF], +            }), sizeof("\\u1234") - 1); +          } else { +            const int tmp = ch - SURROGATE_FIRST_CHAR; +            const int hi = SURROGATE_HI_START + ((tmp >> 10) & ((1 << 10) - 1)); +            const int lo = SURROGATE_LO_END + ((tmp >>  0) & ((1 << 10) - 1)); +            ga_concat_len(gap, ((const char[]) { +                '\\', 'u', +                xdigits[(hi >> (4 * 3)) & 0xF], +                xdigits[(hi >> (4 * 2)) & 0xF], +                xdigits[(hi >> (4 * 1)) & 0xF], +                xdigits[(hi >> (4 * 0)) & 0xF], +                '\\', 'u', +                xdigits[(lo >> (4 * 3)) & 0xF], +                xdigits[(lo >> (4 * 2)) & 0xF], +                xdigits[(lo >> (4 * 1)) & 0xF], +                xdigits[(lo >> (4 * 0)) & 0xF], +            }), (sizeof("\\u1234") - 1) * 2); +          } +          break; +        } +      } +      i += shift; +    } +    ga_append(gap, '"'); +    xfree(tofree); +  } +  return OK; +} + +#undef CONV_STRING +#define CONV_STRING(buf, len) \ +    do { \ +      if (convert_to_json_string(gap, (const char *) (buf), (len)) != OK) { \ +        return FAIL; \ +      } \ +    } while (0) + +#undef CONV_EXT_STRING +#define CONV_EXT_STRING(buf, len, type) \ +    do { \ +      xfree(buf); \ +      EMSG(_("E474: Unable to convert EXT string to JSON")); \ +      return FAIL; \ +    } while (0) + +#undef CONV_FUNC +#define CONV_FUNC(fun) \ +    return conv_error(_("E474: Error while dumping %s, %s: " \ +                        "attempt to dump function reference"), \ +                      mpstack, objname) + +/// Check whether given key can be used in json_encode() +/// +/// @param[in]  tv  Key to check. +static inline bool check_json_key(const typval_T *const tv) +  FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE +  FUNC_ATTR_ALWAYS_INLINE +{ +  if (tv->v_type == VAR_STRING) { +    return true; +  } +  if (tv->v_type != VAR_DICT) { +    return false; +  } +  const dict_T *const spdict = tv->vval.v_dict; +  if (spdict->dv_hashtab.ht_used != 2) { +    return false; +  } +  const dictitem_T *type_di; +  const dictitem_T *val_di; +  if ((type_di = dict_find((dict_T *) spdict, (char_u *) "_TYPE", -1)) == NULL +      || type_di->di_tv.v_type != VAR_LIST +      || (type_di->di_tv.vval.v_list != eval_msgpack_type_lists[kMPString] +          && type_di->di_tv.vval.v_list != eval_msgpack_type_lists[kMPBinary]) +      || (val_di = dict_find((dict_T *) spdict, (char_u *) "_VAL", -1)) == NULL +      || val_di->di_tv.v_type != VAR_LIST) { +    return false; +  } +  if (val_di->di_tv.vval.v_list == NULL) { +    return true; +  } +  for (const listitem_T *li = val_di->di_tv.vval.v_list->lv_first; +       li != NULL; li = li->li_next) { +    if (li->li_tv.v_type != VAR_STRING) { +      return false; +    } +  } +  return true; +} + +#undef CONV_SPECIAL_DICT_KEY_CHECK +#define CONV_SPECIAL_DICT_KEY_CHECK(name, kv_pair) \ +    do { \ +      if (!check_json_key(&kv_pair->lv_first->li_tv)) { \ +        EMSG(_("E474: Invalid key in special dictionary")); \ +        goto encode_vim_to_##name##_error_ret; \ +      } \ +    } while (0) + +DEFINE_VIML_CONV_FUNCTIONS(static, json, 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_NIL +#undef CONV_BOOL +#undef CONV_UNSIGNED_NUMBER +#undef CONV_DICT_START +#undef CONV_DICT_END +#undef CONV_DICT_AFTER_KEY +#undef CONV_DICT_BETWEEN_ITEMS +#undef CONV_SPECIAL_DICT_KEY_CHECK +#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. +/// +/// @return String representation of the variable or NULL. +char *encode_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); +  encode_vim_to_string(&ga, tv, "encode_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. +/// +/// @param[in]  tv  typval_T to convert. +/// @param[out]  len  Location where length of the result will be saved. +/// +/// @return String representation of the variable or NULL. +char *encode_tv2echo(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 { +    encode_vim_to_echo(&ga, tv, ":echo argument"); +  } +  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. +/// 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. +/// +/// @return String representation of the variable or NULL. +char *encode_tv2json(typval_T *tv, size_t *len) +  FUNC_ATTR_NONNULL_ARG(1) FUNC_ATTR_MALLOC +{ +  garray_T ga; +  ga_init(&ga, (int)sizeof(char), 80); +  encode_vim_to_json(&ga, tv, "encode_tv2json() argument"); +  did_echo_string_emsg = false; +  if (len != NULL) { +    *len = (size_t) ga.ga_len; +  } +  ga_append(&ga, '\0'); +  return (char *) ga.ga_data; +} + +#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, (int8_t) 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, (size_t) (lst)->lv_len) + +#define CONV_EMPTY_DICT() \ +    msgpack_pack_map(packer, 0) + +#define CONV_NIL() \ +    msgpack_pack_nil(packer) + +#define CONV_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_DICT_START(len) \ +    msgpack_pack_map(packer, (size_t) (len)) + +#define CONV_DICT_END() + +#define CONV_DICT_AFTER_KEY() + +#define CONV_DICT_BETWEEN_ITEMS() + +#define CONV_SPECIAL_DICT_KEY_CHECK(name, kv_pair) + +#define CONV_LIST_END(lst) + +#define CONV_LIST_BETWEEN_ITEMS() + +#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_NIL +#undef CONV_BOOL +#undef CONV_UNSIGNED_NUMBER +#undef CONV_DICT_START +#undef CONV_DICT_END +#undef CONV_DICT_AFTER_KEY +#undef CONV_DICT_BETWEEN_ITEMS +#undef CONV_SPECIAL_DICT_KEY_CHECK +#undef CONV_LIST_END +#undef CONV_LIST_BETWEEN_ITEMS +#undef CONV_RECURSE +#undef CONV_ALLOW_SPECIAL diff --git a/src/nvim/eval/encode.h b/src/nvim/eval/encode.h new file mode 100644 index 0000000000..9bc665253b --- /dev/null +++ b/src/nvim/eval/encode.h @@ -0,0 +1,75 @@ +#ifndef NVIM_EVAL_ENCODE_H +#define NVIM_EVAL_ENCODE_H + +#include <stddef.h> + +#include <msgpack.h> + +#include "nvim/eval.h" +#include "nvim/garray.h" +#include "nvim/vim.h"  // For STRLEN + +/// Convert VimL value to msgpack string +/// +/// @param[out]  packer  Packer to save results in. +/// @param[in]  tv  Dumped value. +/// @param[in]  objname  Object name, used for error message. +/// +/// @return OK in case of success, FAIL otherwise. +int encode_vim_to_msgpack(msgpack_packer *const packer, +                          typval_T *const tv, +                          const char *const objname); + +/// Convert VimL value to :echo output +/// +/// @param[out]  packer  Packer to save results in. +/// @param[in]  tv  Dumped value. +/// @param[in]  objname  Object name, used for error message. +/// +/// @return OK in case of success, FAIL otherwise. +int encode_vim_to_echo(garray_T *const packer, +                       typval_T *const tv, +                       const char *const objname); + +/// 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; + +/// Initialize ListReaderState structure +static inline ListReaderState encode_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)), +  }; +} + +/// Array mapping values from SpecialVarValue enum to names +extern const char *const encode_special_var_names[]; + +/// First codepoint in high surrogates block +#define SURROGATE_HI_START 0xD800 + +/// Last codepoint in high surrogates block +#define SURROGATE_HI_END   0xDBFF + +/// First codepoint in low surrogates block +#define SURROGATE_LO_START 0xDC00 + +/// Last codepoint in low surrogates block +#define SURROGATE_LO_END   0xDFFF + +/// First character that needs to be encoded as surrogate pair +#define SURROGATE_FIRST_CHAR 0x10000 + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "eval/encode.h.generated.h" +#endif +#endif  // NVIM_EVAL_ENCODE_H diff --git a/src/nvim/eval_defs.h b/src/nvim/eval_defs.h index cdad1f3197..8ffc0c98ce 100644 --- a/src/nvim/eval_defs.h +++ b/src/nvim/eval_defs.h @@ -16,39 +16,52 @@ typedef double float_T;  typedef struct listvar_S list_T;  typedef struct dictvar_S dict_T; -/* - * Structure to hold an internal variable without a name. - */ +/// Special variable values +typedef enum { +  kSpecialVarFalse,  ///< v:false +  kSpecialVarTrue,   ///< v:true +  kSpecialVarNull,   ///< v:null +} SpecialVarValue; + +/// Variable lock status for typval_T.v_lock +typedef enum { +  VAR_UNLOCKED = 0,  ///< Not locked. +  VAR_LOCKED,        ///< User lock, can be unlocked. +  VAR_FIXED,         ///< Locked forever. +} VarLockStatus; + +/// VimL variable types, for use in typval_T.v_type +typedef enum { +  VAR_UNKNOWN = 0,  ///< Unknown (unspecified) value. +  VAR_NUMBER,       ///< Number, .v_number is used. +  VAR_STRING,       ///< String, .v_string is used. +  VAR_FUNC,         ///< Function referene, .v_string is used for function name. +  VAR_LIST,         ///< List, .v_list is used. +  VAR_DICT,         ///< Dictionary, .v_dict is used. +  VAR_FLOAT,        ///< Floating-point value, .v_float is used. +  VAR_SPECIAL,      ///< Special value (true, false, null), .v_special +                    ///< is used. +} VarType; + +/// Structure that holds an internal variable value  typedef struct { -  char v_type;              /* see below: VAR_NUMBER, VAR_STRING, etc. */ -  char v_lock;              /* see below: VAR_LOCKED, VAR_FIXED */ +  VarType v_type;  ///< Variable type. +  VarLockStatus v_lock;  ///< Variable lock status.    union { -    varnumber_T v_number;               /* number value */ -    float_T v_float;                    /* floating number value */ -    char_u          *v_string;          /* string value (can be NULL!) */ -    list_T          *v_list;            /* list value (can be NULL!) */ -    dict_T          *v_dict;            /* dict value (can be NULL!) */ -  }           vval; +    varnumber_T v_number;  ///< Number, for VAR_NUMBER. +    SpecialVarValue v_special;  ///< Special value, for VAR_SPECIAL. +    float_T v_float;  ///< Floating-point number, for VAR_FLOAT. +    char_u *v_string;  ///< String, for VAR_STRING and VAR_FUNC, can be NULL. +    list_T *v_list;  ///< List for VAR_LIST, can be NULL. +    dict_T *v_dict;  ///< Dictionary for VAR_DICT, can be NULL. +  }           vval;  ///< Actual value.  } typval_T; -/* Values for "v_type". */ -#define VAR_UNKNOWN 0 -#define VAR_NUMBER  1   /* "v_number" is used */ -#define VAR_STRING  2   /* "v_string" is used */ -#define VAR_FUNC    3   /* "v_string" is function name */ -#define VAR_LIST    4   /* "v_list" is used */ -#define VAR_DICT    5   /* "v_dict" is used */ -#define VAR_FLOAT   6   /* "v_float" is used */ -  /* Values for "dv_scope". */  #define VAR_SCOPE     1 /* a:, v:, s:, etc. scope dictionaries */  #define VAR_DEF_SCOPE 2 /* l:, g: scope dictionaries: here funcrefs are not                             allowed to mask existing functions */ -/* Values for "v_lock". */ -#define VAR_LOCKED  1   /* locked with lock(), can use unlock() */ -#define VAR_FIXED   2   /* locked forever */ -  /*   * Structure to hold an item of a list: an internal variable without a name.   */ @@ -107,19 +120,18 @@ typedef struct dictitem_S dictitem_T;  #define DI_FLAGS_LOCK   8   // "di_flags" value: locked variable  #define DI_FLAGS_ALLOC  16  // "di_flags" value: separately allocated -/* - * Structure to hold info about a Dictionary. - */ +/// Structure representing a Dictionary  struct dictvar_S { -  char dv_lock;                 /* zero, VAR_LOCKED, VAR_FIXED */ -  char dv_scope;                /* zero, VAR_SCOPE, VAR_DEF_SCOPE */ -  int dv_refcount;              /* reference count */ -  int dv_copyID;                /* ID used by deepcopy() */ -  hashtab_T dv_hashtab;         /* hashtab that refers to the items */ -  dict_T      *dv_copydict;     /* copied dict used by deepcopy() */ -  dict_T      *dv_used_next;    /* next dict in used dicts list */ -  dict_T      *dv_used_prev;    /* previous dict in used dicts list */ -  QUEUE watchers;               // dictionary key watchers set by user code +  VarLockStatus dv_lock;  ///< Whole dictionary lock status. +  char dv_scope;          ///< Non-zero (#VAR_SCOPE, #VAR_DEF_SCOPE) if +                          ///< dictionary represents a scope (i.e. g:, l: …). +  int dv_refcount;        ///< Reference count. +  int dv_copyID;          ///< ID used when recursivery traversing a value. +  hashtab_T dv_hashtab;   ///< Hashtab containing all items. +  dict_T *dv_copydict;    ///< Copied dict used by deepcopy(). +  dict_T *dv_used_next;   ///< Next dictionary in used dictionaries list. +  dict_T *dv_used_prev;   ///< Previous dictionary in used dictionaries list. +  QUEUE watchers;         ///< Dictionary key watchers set by user code.  };  // structure used for explicit stack while garbage collecting hash tables diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index 5ea5beb478..d344daed11 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -1326,15 +1326,17 @@ char_u *make_filter_cmd(char_u *cmd, char_u *itmp, char_u *otmp)  #endif    size_t len = STRLEN(cmd) + 1;  // At least enough space for cmd + NULL. -   +    len += is_fish_shell ?  sizeof("begin; ""; end") - 1                         :  sizeof("("")") - 1; -  if (itmp != NULL) +  if (itmp != NULL) {      len += STRLEN(itmp) + sizeof(" { "" < "" } ") - 1; -  if (otmp != NULL) +  } +  if (otmp != NULL) {      len += STRLEN(otmp) + STRLEN(p_srr) + 2;  // two extra spaces ("  "), -  char_u *buf = xmalloc(len); +  } +  char *const buf = xmalloc(len);  #if defined(UNIX)    // Put delimiters around the command (for concatenated commands) when @@ -1342,19 +1344,19 @@ char_u *make_filter_cmd(char_u *cmd, char_u *itmp, char_u *otmp)    if (itmp != NULL || otmp != NULL) {      char *fmt = is_fish_shell ? "begin; %s; end"                                :       "(%s)"; -    vim_snprintf((char *)buf, len, fmt, (char *)cmd); +    vim_snprintf(buf, len, fmt, (char *)cmd);    } else { -    STRCPY(buf, cmd); +    strncpy(buf, (char *) cmd, len);    }    if (itmp != NULL) { -    STRCAT(buf, " < "); -    STRCAT(buf, itmp); +    strncat(buf, " < ", len); +    strncat(buf, (char *) itmp, len);    }  #else    // For shells that don't understand braces around commands, at least allow    // the use of commands in a pipe. -  STRCPY(buf, cmd); +  strncpy(buf, cmd, len);    if (itmp != NULL) {      char_u  *p; @@ -1362,55 +1364,56 @@ char_u *make_filter_cmd(char_u *cmd, char_u *itmp, char_u *otmp)      // Don't do this when 'shellquote' is not empty, otherwise the      // redirection would be inside the quotes.      if (*p_shq == NUL) { -      p = vim_strchr(buf, '|'); -      if (p != NULL) +      p = strchr(buf, '|'); +      if (p != NULL) {          *p = NUL; +      }      } -    STRCAT(buf, " < "); -    STRCAT(buf, itmp); +    strncat(buf, " < ", len); +    strncat(buf, (char *) itmp, len);      if (*p_shq == NUL) { -      p = vim_strchr(cmd, '|'); +      p = strchr(cmd, '|');        if (p != NULL) { -        STRCAT(buf, " ");  // Insert a space before the '|' for DOS -        STRCAT(buf, p); +        strncat(buf, " ", len);  // Insert a space before the '|' for DOS +        strncat(buf, p, len);        }      }    }  #endif    if (otmp != NULL) { -    append_redir(buf, len, p_srr, otmp); +    append_redir(buf, len, (char *) p_srr, (char *) otmp);    } -  return buf; +  return (char_u *) buf;  } -/* - * Append output redirection for file "fname" to the end of string buffer - * "buf[buflen]" - * Works with the 'shellredir' and 'shellpipe' options. - * The caller should make sure that there is enough room: - *	STRLEN(opt) + STRLEN(fname) + 3 - */ -void append_redir(char_u *buf, size_t buflen, char_u *opt, char_u *fname) +/// Append output redirection for the given file to the end of the buffer +/// +/// @param[out]  buf  Buffer to append to. +/// @param[in]  buflen  Buffer length. +/// @param[in]  opt  Separator or format string to append: will append +///                  `printf(' ' . opt, fname)` if `%s` is found in `opt` or +///                  a space, opt, a space and then fname if `%s` is not found +///                  there. +/// @param[in]  fname  File name to append. +void append_redir(char *const buf, const size_t buflen, +                  const char *const opt, const char *const fname)  { -  char_u      *p; -  char_u      *end; - -  end = buf + STRLEN(buf); -  /* find "%s" */ -  for (p = opt; (p = vim_strchr(p, '%')) != NULL; ++p) { -    if (p[1] == 's')     /* found %s */ +  char *const end = buf + strlen(buf); +  // find "%s" +  const char *p = opt; +  for (; (p = strchr(p, '%')) != NULL; p++) { +    if (p[1] == 's') {  // found %s        break; -    if (p[1] == '%')     /* skip %% */ -      ++p; +    } else if (p[1] == '%') {  // skip %% +      p++; +    }    }    if (p != NULL) { -    *end = ' ';     /* not really needed? Not with sh, ksh or bash */ -    vim_snprintf((char *)end + 1, (size_t)(buflen - (end + 1 - buf)), -        (char *)opt, (char *)fname); -  } else -    vim_snprintf((char *)end, (size_t)(buflen - (end - buf)), -        " %s %s", -        (char *)opt, (char *)fname); +    *end = ' ';  // not really needed? Not with sh, ksh or bash +    vim_snprintf(end + 1, (size_t) (buflen - (end + 1 - buf)), opt, fname); +  } else { +    vim_snprintf(end, (size_t) (buflen - (end - buf)), " %s %s", opt, fname); +  }  }  void print_line_no_prefix(linenr_T lnum, int use_number, int list) @@ -2093,15 +2096,13 @@ do_ecmd (    if ((command != NULL || newlnum > (linenr_T)0)        && *get_vim_var_str(VV_SWAPCOMMAND) == NUL) { -    char_u  *p; - -    /* Set v:swapcommand for the SwapExists autocommands. */ -    size_t len = (command != NULL) ? STRLEN(command) + 3 : 30; -    p = xmalloc(len); +    // Set v:swapcommand for the SwapExists autocommands. +    const size_t len = (command != NULL) ? STRLEN(command) + 3 : 30; +    char *const p = xmalloc(len);      if (command != NULL) { -      vim_snprintf((char *)p, len, ":%s\r", command); +      vim_snprintf(p, len, ":%s\r", command);      } else { -      vim_snprintf((char *)p, len, "%" PRId64 "G", (int64_t)newlnum); +      vim_snprintf(p, len, "%" PRId64 "G", (int64_t)newlnum);      }      set_vim_var_string(VV_SWAPCOMMAND, p, -1);      did_set_swapcommand = TRUE; diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c index 71ea170e1c..2c8271c696 100644 --- a/src/nvim/ex_cmds2.c +++ b/src/nvim/ex_cmds2.c @@ -3168,27 +3168,27 @@ static char_u *get_mess_env(void)   */  void set_lang_var(void)  { -  char_u      *loc; +  const char *loc;  # ifdef HAVE_GET_LOCALE_VAL -  loc = (char_u *)get_locale_val(LC_CTYPE); +  loc = get_locale_val(LC_CTYPE);  # else -  /* setlocale() not supported: use the default value */ -  loc = (char_u *)"C"; +  // setlocale() not supported: use the default value +  loc = "C";  # endif    set_vim_var_string(VV_CTYPE, loc, -1);    /* When LC_MESSAGES isn't defined use the value from $LC_MESSAGES, fall     * back to LC_CTYPE if it's empty. */  # ifdef HAVE_WORKING_LIBINTL -  loc = get_mess_env(); +  loc = (char *) get_mess_env();  # else -  loc = (char_u *)get_locale_val(LC_MESSAGES); +  loc = get_locale_val(LC_MESSAGES);  # endif    set_vim_var_string(VV_LANG, loc, -1);  # ifdef HAVE_GET_LOCALE_VAL -  loc = (char_u *)get_locale_val(LC_TIME); +  loc = get_locale_val(LC_TIME);  # endif    set_vim_var_string(VV_LC_TIME, loc, -1);  } diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index a7e98e7f04..6391e023c3 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -7421,10 +7421,10 @@ static int mksession_nl = FALSE;    /* use NL only in put_eol() */  static void ex_mkrc(exarg_T *eap)  {    FILE        *fd; -  int failed = FALSE; -  int view_session = FALSE; -  int using_vdir = FALSE;               /* using 'viewdir'? */ -  char_u      *viewFile = NULL; +  int failed = false; +  int view_session = false; +  int using_vdir = false;  // using 'viewdir'? +  char *viewFile = NULL;    unsigned    *flagp;    if (eap->cmdidx == CMD_mksession || eap->cmdidx == CMD_mkview) { @@ -7435,32 +7435,34 @@ static void ex_mkrc(exarg_T *eap)     * short file name when 'acd' is set, that is checked later. */    did_lcd = FALSE; -  char_u *fname; -  /* ":mkview" or ":mkview 9": generate file name with 'viewdir' */ +  char *fname; +  // ":mkview" or ":mkview 9": generate file name with 'viewdir'    if (eap->cmdidx == CMD_mkview        && (*eap->arg == NUL            || (ascii_isdigit(*eap->arg) && eap->arg[1] == NUL))) { -    eap->forceit = TRUE; -    fname = (char_u *)get_view_file(*eap->arg); -    if (fname == NULL) +    eap->forceit = true; +    fname = get_view_file(*eap->arg); +    if (fname == NULL) {        return; +    }      viewFile = fname; -    using_vdir = TRUE; -  } else if (*eap->arg != NUL) -    fname = eap->arg; -  else if (eap->cmdidx == CMD_mkvimrc) -    fname = (char_u *)VIMRC_FILE; -  else if (eap->cmdidx == CMD_mksession) -    fname = (char_u *)SESSION_FILE; -  else -    fname = (char_u *)EXRC_FILE; +    using_vdir = true; +  } else if (*eap->arg != NUL) { +    fname = (char *) eap->arg; +  } else if (eap->cmdidx == CMD_mkvimrc) { +    fname = VIMRC_FILE; +  } else if (eap->cmdidx == CMD_mksession) { +    fname = SESSION_FILE; +  } else { +    fname = EXRC_FILE; +  }    /* When using 'viewdir' may have to create the directory. */    if (using_vdir && !os_isdir(p_vdir)) {      vim_mkdir_emsg(p_vdir, 0755);    } -  fd = open_exfile(fname, eap->forceit, WRITEBIN); +  fd = open_exfile((char_u *) fname, eap->forceit, WRITEBIN);    if (fd != NULL) {      if (eap->cmdidx == CMD_mkview)        flagp = &vop_flags; @@ -7504,8 +7506,9 @@ static void ex_mkrc(exarg_T *eap)              || os_chdir((char *)dirnow) != 0)            *dirnow = NUL;          if (*dirnow != NUL && (ssop_flags & SSOP_SESDIR)) { -          if (vim_chdirfile(fname) == OK) -            shorten_fnames(TRUE); +          if (vim_chdirfile((char_u *) fname) == OK) { +            shorten_fnames(true); +          }          } else if (*dirnow != NUL                     && (ssop_flags & SSOP_CURDIR) && globaldir != NULL) {            if (os_chdir((char *)globaldir) == 0) @@ -7550,15 +7553,14 @@ static void ex_mkrc(exarg_T *eap)      failed |= fclose(fd); -    if (failed) +    if (failed) {        EMSG(_(e_write)); -    else if (eap->cmdidx == CMD_mksession) { -      /* successful session write - set this_session var */ -      char_u      *tbuf; - -      tbuf = xmalloc(MAXPATHL); -      if (vim_FullName((char *)fname, (char *)tbuf, MAXPATHL, FALSE) == OK) +    } else if (eap->cmdidx == CMD_mksession) { +      // successful session write - set this_session var +      char *const tbuf = xmalloc(MAXPATHL); +      if (vim_FullName(fname, tbuf, MAXPATHL, false) == OK) {          set_vim_var_string(VV_THIS_SESSION, tbuf, -1); +      }        xfree(tbuf);      }  #ifdef MKSESSION_NL diff --git a/src/nvim/ex_eval.c b/src/nvim/ex_eval.c index a1e54e74a6..41ad96a378 100644 --- a/src/nvim/ex_eval.c +++ b/src/nvim/ex_eval.c @@ -569,17 +569,19 @@ static void catch_exception(except_T *excp)  {    excp->caught = caught_stack;    caught_stack = excp; -  set_vim_var_string(VV_EXCEPTION, excp->value, -1); +  set_vim_var_string(VV_EXCEPTION, (char *) excp->value, -1);    if (*excp->throw_name != NUL) { -    if (excp->throw_lnum != 0) +    if (excp->throw_lnum != 0) {        vim_snprintf((char *)IObuff, IOSIZE, _("%s, line %" PRId64), -          excp->throw_name, (int64_t)excp->throw_lnum); -    else +                   excp->throw_name, (int64_t)excp->throw_lnum); +    } else {        vim_snprintf((char *)IObuff, IOSIZE, "%s", excp->throw_name); -    set_vim_var_string(VV_THROWPOINT, IObuff, -1); -  } else -    /* throw_name not set on an exception from a command that was typed. */ +    } +    set_vim_var_string(VV_THROWPOINT, (char *) IObuff, -1); +  } else { +    // throw_name not set on an exception from a command that was typed.      set_vim_var_string(VV_THROWPOINT, NULL, -1); +  }    if (p_verbose >= 13 || debug_break_level > 0) {      int save_msg_silent = msg_silent; @@ -614,20 +616,22 @@ static void finish_exception(except_T *excp)      EMSG(_(e_internal));    caught_stack = caught_stack->caught;    if (caught_stack != NULL) { -    set_vim_var_string(VV_EXCEPTION, caught_stack->value, -1); +    set_vim_var_string(VV_EXCEPTION, (char *) caught_stack->value, -1);      if (*caught_stack->throw_name != NUL) { -      if (caught_stack->throw_lnum != 0) +      if (caught_stack->throw_lnum != 0) {          vim_snprintf((char *)IObuff, IOSIZE, -            _("%s, line %" PRId64), caught_stack->throw_name, -            (int64_t)caught_stack->throw_lnum); -      else +                     _("%s, line %" PRId64), caught_stack->throw_name, +                     (int64_t)caught_stack->throw_lnum); +      } else {          vim_snprintf((char *)IObuff, IOSIZE, "%s", -            caught_stack->throw_name); -      set_vim_var_string(VV_THROWPOINT, IObuff, -1); -    } else -      /* throw_name not set on an exception from a command that was -       * typed. */ +                     caught_stack->throw_name); +      } +      set_vim_var_string(VV_THROWPOINT, (char *) IObuff, -1); +    } else { +      // throw_name not set on an exception from a command that was +      // typed.        set_vim_var_string(VV_THROWPOINT, NULL, -1); +    }    } else {      set_vim_var_string(VV_EXCEPTION, NULL, -1);      set_vim_var_string(VV_THROWPOINT, NULL, -1); @@ -1370,19 +1374,24 @@ void ex_catch(exarg_T *eap)          }          save_cpo  = p_cpo;          p_cpo = (char_u *)""; +        // Disable error messages, it will make current exception +        // invalid +        emsg_off++;          regmatch.regprog = vim_regcomp(pat, RE_MAGIC + RE_STRING); -        regmatch.rm_ic = FALSE; -        if (end != NULL) +        emsg_off--; +        regmatch.rm_ic = false; +        if (end != NULL) {            *end = save_char; +        }          p_cpo = save_cpo; -        if (regmatch.regprog == NULL) +        if (regmatch.regprog == NULL) {            EMSG2(_(e_invarg2), pat); -        else { -          /* -           * Save the value of got_int and reset it.  We don't want -           * a previous interruption cancel matching, only hitting -           * CTRL-C while matching should abort it. -           */ +        } else { +          // +          // Save the value of got_int and reset it.  We don't want +          // a previous interruption cancel matching, only hitting +          // CTRL-C while matching should abort it. +          //            prev_got_int = got_int;            got_int = FALSE;            caught = vim_regexec_nl(®match, current_exception->value, diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c index 383cd47dbe..32e1b645d0 100644 --- a/src/nvim/fileio.c +++ b/src/nvim/fileio.c @@ -2139,9 +2139,10 @@ readfile_charconvert (    else {      close(*fdp);                /* close the input file, ignore errors */      *fdp = -1; -    if (eval_charconvert(fenc, enc_utf8 ? (char_u *)"utf-8" : p_enc, -            fname, tmpname) == FAIL) +    if (eval_charconvert((char *) fenc, enc_utf8 ? "utf-8" : (char *) p_enc, +                         (char *) fname, (char *) tmpname) == FAIL) {        errmsg = (char_u *)_("Conversion with 'charconvert' failed"); +    }      if (errmsg == NULL && (*fdp = os_open((char *)tmpname, O_RDONLY, 0)) < 0) {        errmsg = (char_u *)_("can't read output of 'charconvert'");      } @@ -3435,9 +3436,9 @@ restore_backup:       * with 'charconvert' to (overwrite) the output file.       */      if (end != 0) { -      if (eval_charconvert(enc_utf8 ? (char_u *)"utf-8" : p_enc, fenc, -              wfname, fname) == FAIL) { -        write_info.bw_conv_error = TRUE; +      if (eval_charconvert(enc_utf8 ? "utf-8" : (char *) p_enc, (char *) fenc, +                           (char *) wfname, (char *) fname) == FAIL) { +        write_info.bw_conv_error = true;          end = 0;        }      } @@ -4740,7 +4741,6 @@ buf_check_timestamp (  {    int retval = 0;    char_u      *path; -  char_u      *tbuf;    char        *mesg = NULL;    char        *mesg2 = "";    int helpmesg = FALSE; @@ -4810,19 +4810,17 @@ buf_check_timestamp (        else          reason = "time"; -      /* -       * Only give the warning if there are no FileChangedShell -       * autocommands. -       * Avoid being called recursively by setting "busy". -       */ -      busy = TRUE; -      set_vim_var_string(VV_FCS_REASON, (char_u *)reason, -1); -      set_vim_var_string(VV_FCS_CHOICE, (char_u *)"", -1); -      ++allbuf_lock; +      // Only give the warning if there are no FileChangedShell +      // autocommands. +      // Avoid being called recursively by setting "busy". +      busy = true; +      set_vim_var_string(VV_FCS_REASON, reason, -1); +      set_vim_var_string(VV_FCS_CHOICE, "", -1); +      allbuf_lock++;        n = apply_autocmds(EVENT_FILECHANGEDSHELL, -          buf->b_fname, buf->b_fname, FALSE, buf); -      --allbuf_lock; -      busy = FALSE; +                         buf->b_fname, buf->b_fname, false, buf); +      allbuf_lock--; +      busy = false;        if (n) {          if (!buf_valid(buf))            EMSG(_("E246: FileChangedShell autocommand deleted buffer")); @@ -4876,35 +4874,39 @@ buf_check_timestamp (    if (mesg != NULL) {      path = home_replace_save(buf, buf->b_fname); -    if (!helpmesg) +    if (!helpmesg) {        mesg2 = ""; -    tbuf = xmalloc(STRLEN(path) + STRLEN(mesg) + STRLEN(mesg2) + 2); -    sprintf((char *)tbuf, mesg, path); -    /* Set warningmsg here, before the unimportant and output-specific -     * mesg2 has been appended. */ +    } +    const size_t tbuf_len = STRLEN(path) + STRLEN(mesg) + STRLEN(mesg2) + 2; +    char *const tbuf = xmalloc(tbuf_len); +    snprintf(tbuf, tbuf_len, mesg, path); +    // Set warningmsg here, before the unimportant and output-specific +    // mesg2 has been appended.      set_vim_var_string(VV_WARNINGMSG, tbuf, -1);      if (can_reload) {        if (*mesg2 != NUL) { -        STRCAT(tbuf, "\n"); -        STRCAT(tbuf, mesg2); +        strncat(tbuf, "\n", tbuf_len); +        strncat(tbuf, mesg2, tbuf_len); +      } +      if (do_dialog(VIM_WARNING, (char_u *) _("Warning"), (char_u *) tbuf, +                    (char_u *) _("&OK\n&Load File"), 1, NULL, true) == 2) { +        reload = true;        } -      if (do_dialog(VIM_WARNING, (char_u *)_("Warning"), tbuf, -                    (char_u *)_("&OK\n&Load File"), 1, NULL, TRUE) == 2) -        reload = TRUE;      } else if (State > NORMAL_BUSY || (State & CMDLINE) || already_warned) {        if (*mesg2 != NUL) { -        STRCAT(tbuf, "; "); -        STRCAT(tbuf, mesg2); +        strncat(tbuf, "; ", tbuf_len); +        strncat(tbuf, mesg2, tbuf_len);        }        EMSG(tbuf);        retval = 2;      } else {        if (!autocmd_busy) {          msg_start(); -        msg_puts_attr(tbuf, hl_attr(HLF_E) + MSG_HIST); -        if (*mesg2 != NUL) +        msg_puts_attr((char_u *) tbuf, hl_attr(HLF_E) + MSG_HIST); +        if (*mesg2 != NUL) {            msg_puts_attr((char_u *)mesg2,                          hl_attr(HLF_W) + MSG_HIST); +        }          msg_clr_eos();          (void)msg_end();          if (emsg_silent == 0) { diff --git a/src/nvim/fold.c b/src/nvim/fold.c index 70ab4ced75..7f46a37315 100644 --- a/src/nvim/fold.c +++ b/src/nvim/fold.c @@ -1699,14 +1699,14 @@ char_u *get_foldtext(win_T *wp, linenr_T lnum, linenr_T lnume,      did_emsg = FALSE;    if (*wp->w_p_fdt != NUL) { -    char_u dashes[MAX_LEVEL + 2]; +    char dashes[MAX_LEVEL + 2];      win_T   *save_curwin;      int level;      char_u  *p; -    /* Set "v:foldstart" and "v:foldend". */ -    set_vim_var_nr(VV_FOLDSTART, lnum); -    set_vim_var_nr(VV_FOLDEND, lnume); +    // Set "v:foldstart" and "v:foldend". +    set_vim_var_nr(VV_FOLDSTART, (varnumber_T) lnum); +    set_vim_var_nr(VV_FOLDEND, (varnumber_T) lnume);      /* Set "v:folddashes" to a string of "level" dashes. */      /* Set "v:foldlevel" to "level". */ @@ -1716,7 +1716,7 @@ char_u *get_foldtext(win_T *wp, linenr_T lnum, linenr_T lnume,      memset(dashes, '-', (size_t)level);      dashes[level] = NUL;      set_vim_var_string(VV_FOLDDASHES, dashes, -1); -    set_vim_var_nr(VV_FOLDLEVEL, (long)level); +    set_vim_var_nr(VV_FOLDLEVEL, (varnumber_T) level);      /* skip evaluating foldtext on errors */      if (!got_fdt_error) { @@ -2676,7 +2676,7 @@ static void foldlevelExpr(fline_T *flp)    win = curwin;    curwin = flp->wp;    curbuf = flp->wp->w_buffer; -  set_vim_var_nr(VV_LNUM, lnum); +  set_vim_var_nr(VV_LNUM, (varnumber_T) lnum);    flp->start = 0;    flp->had_end = flp->end; diff --git a/src/nvim/garray.c b/src/nvim/garray.c index e6cbd9332b..98cec69b54 100644 --- a/src/nvim/garray.c +++ b/src/nvim/garray.c @@ -188,12 +188,23 @@ void ga_concat(garray_T *gap, const char_u *restrict s)      return;    } -  int len = (int)strlen((char *) s); +  ga_concat_len(gap, (const char *restrict) s, strlen((char *) s)); +} + +/// Concatenate a string to a growarray which contains characters +/// +/// @param[out]  gap  Growarray to modify. +/// @param[in]  s  String to concatenate. +/// @param[in]  len  String length. +void ga_concat_len(garray_T *const gap, const char *restrict s, +                   const size_t len) +  FUNC_ATTR_NONNULL_ALL +{    if (len) { -    ga_grow(gap, len); +    ga_grow(gap, (int) len);      char *data = gap->ga_data; -    memcpy(data + gap->ga_len, s, (size_t)len); -    gap->ga_len += len; +    memcpy(data + gap->ga_len, s, len); +    gap->ga_len += (int) len;    }  } diff --git a/src/nvim/globals.h b/src/nvim/globals.h index 40b5718071..49d1de21d9 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -4,17 +4,7 @@  #include <stdbool.h>  #include <inttypes.h> -// EXTERN is only defined in main.c. That's where global variables are -// actually defined and initialized. -#ifndef EXTERN -# define EXTERN extern -# define INIT(...) -#else -# ifndef INIT -#  define INIT(...) __VA_ARGS__ -# endif -#endif - +#include "nvim/macros.h"  #include "nvim/ex_eval.h"  #include "nvim/iconv.h"  #include "nvim/mbyte.h" diff --git a/src/nvim/hardcopy.c b/src/nvim/hardcopy.c index ab8959239b..cc49bcd074 100644 --- a/src/nvim/hardcopy.c +++ b/src/nvim/hardcopy.c @@ -2780,11 +2780,13 @@ void mch_print_end(prt_settings_T *psettings)      }      prt_message((char_u *)_("Sending to printer...")); -    /* Not printing to a file: use 'printexpr' to print the file. */ -    if (eval_printexpr(prt_ps_file_name, psettings->arguments) == FAIL) +    // Not printing to a file: use 'printexpr' to print the file. +    if (eval_printexpr((char *) prt_ps_file_name, (char *) psettings->arguments) +        == FAIL) {        EMSG(_("E365: Failed to print PostScript file")); -    else +    } else {        prt_message((char_u *)_("Print job sent.")); +    }    }    mch_print_cleanup(); diff --git a/src/nvim/indent.c b/src/nvim/indent.c index d3008185dc..f197669a97 100644 --- a/src/nvim/indent.c +++ b/src/nvim/indent.c @@ -529,7 +529,7 @@ int get_expr_indent(void)    save_pos = curwin->w_cursor;    save_curswant = curwin->w_curswant;    save_set_curswant = curwin->w_set_curswant; -  set_vim_var_nr(VV_LNUM, curwin->w_cursor.lnum); +  set_vim_var_nr(VV_LNUM, (varnumber_T) curwin->w_cursor.lnum);    if (use_sandbox) {      sandbox++; diff --git a/src/nvim/lib/kvec.h b/src/nvim/lib/kvec.h index 53ecf232c6..b41ef0cc9f 100644 --- a/src/nvim/lib/kvec.h +++ b/src/nvim/lib/kvec.h @@ -60,6 +60,7 @@ int main() {  #define kv_pop(v) ((v).items[--(v).size])  #define kv_size(v) ((v).size)  #define kv_max(v) ((v).capacity) +#define kv_last(v) kv_A(v, kv_size(v) - 1)  #define kv_resize(type, v, s)  ((v).capacity = (s), (v).items = (type*)xrealloc((v).items, sizeof(type) * (v).capacity)) diff --git a/src/nvim/macros.h b/src/nvim/macros.h index 26ab5a7de7..5f69fa2f6a 100644 --- a/src/nvim/macros.h +++ b/src/nvim/macros.h @@ -1,6 +1,17 @@  #ifndef NVIM_MACROS_H  #define NVIM_MACROS_H +// EXTERN is only defined in main.c. That's where global variables are +// actually defined and initialized. +#ifndef EXTERN +# define EXTERN extern +# define INIT(...) +#else +# ifndef INIT +#  define INIT(...) __VA_ARGS__ +# endif +#endif +  #ifndef MIN  # define MIN(X, Y) ((X) < (Y) ? (X) : (Y))  #endif diff --git a/src/nvim/main.c b/src/nvim/main.c index 23ced5ebe5..71a972e8f6 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -238,8 +238,8 @@ int main(int argc, char **argv)    check_and_set_isatty(¶ms);    // Get the name with which Nvim was invoked, with and without path. -  set_vim_var_string(VV_PROGPATH, (char_u *)argv[0], -1); -  set_vim_var_string(VV_PROGNAME, path_tail((char_u *)argv[0]), -1); +  set_vim_var_string(VV_PROGPATH, argv[0], -1); +  set_vim_var_string(VV_PROGNAME, (char *) path_tail((char_u *) argv[0]), -1);    event_init();    /* @@ -1141,10 +1141,11 @@ scripterror:    /* If there is a "+123" or "-c" command, set v:swapcommand to the first     * one. */    if (parmp->n_commands > 0) { -    p = xmalloc(STRLEN(parmp->commands[0]) + 3); -    sprintf((char *)p, ":%s\r", parmp->commands[0]); -    set_vim_var_string(VV_SWAPCOMMAND, p, -1); -    xfree(p); +    const size_t swcmd_len = STRLEN(parmp->commands[0]) + 3; +    char *const swcmd = xmalloc(swcmd_len); +    snprintf(swcmd, swcmd_len, ":%s\r", parmp->commands[0]); +    set_vim_var_string(VV_SWAPCOMMAND, swcmd, -1); +    xfree(swcmd);    }    TIME_MSG("parsing arguments");  } diff --git a/src/nvim/memline.c b/src/nvim/memline.c index b568279d7d..10176752d5 100644 --- a/src/nvim/memline.c +++ b/src/nvim/memline.c @@ -3194,7 +3194,7 @@ attention_message (   */  static int do_swapexists(buf_T *buf, char_u *fname)  { -  set_vim_var_string(VV_SWAPNAME, fname, -1); +  set_vim_var_string(VV_SWAPNAME, (char *) fname, -1);    set_vim_var_string(VV_SWAPCHOICE, NULL, -1);    /* Trigger SwapExists autocommands with <afile> set to the file being diff --git a/src/nvim/message.c b/src/nvim/message.c index 1dd71baaa4..97b098c6d2 100644 --- a/src/nvim/message.c +++ b/src/nvim/message.c @@ -61,14 +61,8 @@ static int confirm_msg_used = FALSE;            /* displaying confirm_msg */  static char_u   *confirm_msg = NULL;            /* ":confirm" message */  static char_u   *confirm_msg_tail;              /* tail of confirm_msg */ -struct msg_hist { -  struct msg_hist     *next; -  char_u              *msg; -  int attr; -}; - -static struct msg_hist *first_msg_hist = NULL; -static struct msg_hist *last_msg_hist = NULL; +MessageHistoryEntry *first_msg_hist = NULL; +MessageHistoryEntry *last_msg_hist = NULL;  static int msg_hist_len = 0;  static FILE *verbose_fd = NULL; @@ -149,10 +143,11 @@ msg_attr_keep (  {    static int entered = 0;    int retval; -  char_u      *buf = NULL; +  char_u *buf = NULL; -  if (attr == 0) -    set_vim_var_string(VV_STATUSMSG, s, -1); +  if (attr == 0) { +    set_vim_var_string(VV_STATUSMSG, (char *) s, -1); +  }    /*     * It is possible that displaying a messages causes a problem (e.g., @@ -503,8 +498,8 @@ int emsg(char_u *s)        return TRUE;      } -    /* set "v:errmsg", also when using ":silent! cmd" */ -    set_vim_var_string(VV_ERRMSG, s, -1); +    // set "v:errmsg", also when using ":silent! cmd" +    set_vim_var_string(VV_ERRMSG, (char *) s, -1);      /*       * When using ":silent! cmd" ignore error messages. @@ -563,49 +558,23 @@ int emsg(char_u *s)    return msg_attr(s, attr);  } -/* - * Print an error message with one "%s" and one string argument. - */ -int emsg2(char_u *s, char_u *a1) -{ -  return emsg3(s, a1, NULL); -} -  void emsg_invreg(int name)  {    EMSG2(_("E354: Invalid register name: '%s'"), transchar(name));  } -/// Print an error message with one or two "%s" and one or two string arguments. -int emsg3(char_u *s, char_u *a1, char_u *a2) -{ -  if (emsg_not_now()) { -    return TRUE;                // no error messages at the moment -  } - -  vim_snprintf((char *)IObuff, IOSIZE, (char *)s, a1, a2); -  return emsg(IObuff); -} - -/// Print an error message with one "%" PRId64 and one (int64_t) argument. -int emsgn(char_u *s, int64_t n) +/// Print an error message with unknown number of arguments +bool emsgf(const char *const fmt, ...)  {    if (emsg_not_now()) { -    return TRUE;                // no error messages at the moment +    return true;    } -  vim_snprintf((char *)IObuff, IOSIZE, (char *)s, n); -  return emsg(IObuff); -} - -/// Print an error message with one "%" PRIu64 and one (uint64_t) argument. -int emsgu(char_u *s, uint64_t n) -{ -  if (emsg_not_now()) { -    return TRUE;                // no error messages at the moment -  } +  va_list ap; +  va_start(ap, fmt); +  vim_vsnprintf((char *) IObuff, IOSIZE, fmt, ap, NULL); +  va_end(ap); -  vim_snprintf((char *)IObuff, IOSIZE, (char *)s, n);    return emsg(IObuff);  } @@ -1787,25 +1756,24 @@ static void msg_scroll_up(void)  static void inc_msg_scrolled(void)  {    if (*get_vim_var_str(VV_SCROLLSTART) == NUL) { -    char_u      *p = sourcing_name; -    char_u      *tofree = NULL; -    int len; - -    /* v:scrollstart is empty, set it to the script/function name and line -     * number */ -    if (p == NULL) -      p = (char_u *)_("Unknown"); -    else { -      len = (int)STRLEN(p) + 40; +    char *p = (char *) sourcing_name; +    char *tofree = NULL; + +    // v:scrollstart is empty, set it to the script/function name and line +    // number +    if (p == NULL) { +      p = _("Unknown"); +    } else { +      size_t len = strlen(p) + 40;        tofree = xmalloc(len); -      vim_snprintf((char *)tofree, len, _("%s line %" PRId64), -          p, (int64_t)sourcing_lnum); +      vim_snprintf(tofree, len, _("%s line %" PRId64), +                   p, (int64_t) sourcing_lnum);        p = tofree;      }      set_vim_var_string(VV_SCROLLSTART, p, -1);      xfree(tofree);    } -  ++msg_scrolled; +  msg_scrolled++;  }  static msgchunk_T *last_msgchunk = NULL; /* last displayed text */ @@ -2572,7 +2540,7 @@ void give_warning(char_u *message, bool hl) FUNC_ATTR_NONNULL_ARG(1)    /* Don't want a hit-enter prompt here. */    ++no_wait_return; -  set_vim_var_string(VV_WARNINGMSG, message, -1); +  set_vim_var_string(VV_WARNINGMSG, (char *) message, -1);    xfree(keep_msg);    keep_msg = NULL;    if (hl) @@ -3086,7 +3054,7 @@ int vim_snprintf_add(char *str, size_t str_m, char *fmt, ...)    return str_l;  } -int vim_snprintf(char *str, size_t str_m, char *fmt, ...) +int vim_snprintf(char *str, size_t str_m, const char *fmt, ...)  {    va_list ap;    int str_l; @@ -3097,11 +3065,12 @@ int vim_snprintf(char *str, size_t str_m, char *fmt, ...)    return str_l;  } -int vim_vsnprintf(char *str, size_t str_m, char *fmt, va_list ap, typval_T *tvs) +int vim_vsnprintf(char *str, size_t str_m, const char *fmt, va_list ap, +                  typval_T *tvs)  {    size_t str_l = 0;    bool str_avail = str_l < str_m; -  char *p = fmt; +  const char *p = fmt;    int arg_idx = 1;    if (!p) { @@ -3135,7 +3104,7 @@ int vim_vsnprintf(char *str, size_t str_m, char *fmt, va_list ap, typval_T *tvs)        char tmp[TMP_LEN];        // string address in case of string argument -      char *str_arg; +      const char *str_arg;        // natural field width of arg without padding and sign        size_t str_arg_l; diff --git a/src/nvim/message.h b/src/nvim/message.h index 019c7bfb73..d3a16fff93 100644 --- a/src/nvim/message.h +++ b/src/nvim/message.h @@ -4,6 +4,7 @@  #include <stdbool.h>  #include <stdarg.h>  #include "nvim/eval_defs.h"  // for typval_T +#include "nvim/ex_cmds_defs.h"  // for exarg_T  /*   * Types of dialogs passed to do_dialog(). @@ -24,6 +25,56 @@  #define VIM_ALL         5  #define VIM_DISCARDALL  6 +/// Show plain message +#define MSG(s)                      msg((char_u *)(s)) + +/// Show message highlighted according to the attr +#define MSG_ATTR(s, attr)           msg_attr((char_u *)(s), (attr)) + +/// Display error message +/// +/// Sets error flag in process, can be transformed into an exception. +#define EMSG(s)                     emsg((char_u *)(s)) + +/// Like #EMSG, but for messages with one "%s" inside +#define EMSG2(s, p)                 emsgf((const char *) (s), (p)) + +/// Like #EMSG, but for messages with two "%s" inside +#define EMSG3(s, p, q)              emsgf((const char *) (s), (p), (q)) + +/// Like #EMSG, but for messages with one "%" PRId64 inside +#define EMSGN(s, n)                 emsgf((const char *) (s), (int64_t)(n)) + +/// Like #EMSG, but for messages with one "%" PRIu64 inside +#define EMSGU(s, n)                 emsgf((const char *) (s), (uint64_t)(n)) + +/// Display message at the recorded position +#define MSG_PUTS(s)                 msg_puts((char_u *)(s)) + +/// Display message at the recorded position, highlighted +#define MSG_PUTS_ATTR(s, a)         msg_puts_attr((char_u *)(s), (a)) + +/// Like #MSG_PUTS, but highlight like title +#define MSG_PUTS_TITLE(s)           msg_puts_title((char_u *)(s)) + +/// Like #MSG_PUTS, but if middle part of too long messages it will be replaced +#define MSG_PUTS_LONG(s)            msg_puts_long_attr((char_u *)(s), 0) + +/// Like #MSG_PUTS_ATTR, but if middle part of long messages will be replaced +#define MSG_PUTS_LONG_ATTR(s, a)    msg_puts_long_attr((char_u *)(s), (a)) + +/// Message history for `:messages` +typedef struct msg_hist { +  struct msg_hist *next;  ///< Next message. +  char_u *msg;            ///< Message text. +  int attr;               ///< Message highlighting. +} MessageHistoryEntry; + +/// First message +extern MessageHistoryEntry *first_msg_hist; +/// Last message +extern MessageHistoryEntry *last_msg_hist; +  #ifdef INCLUDE_GENERATED_DECLARATIONS  # include "message.h.generated.h"  #endif diff --git a/src/nvim/misc1.c b/src/nvim/misc1.c index db303fd54a..cd29cd6c7e 100644 --- a/src/nvim/misc1.c +++ b/src/nvim/misc1.c @@ -2250,7 +2250,7 @@ change_warning (        msg_col = col;      msg_source(hl_attr(HLF_W));      MSG_PUTS_ATTR(_(w_readonly), hl_attr(HLF_W) | MSG_HIST); -    set_vim_var_string(VV_WARNINGMSG, (char_u *)_(w_readonly), -1); +    set_vim_var_string(VV_WARNINGMSG, _(w_readonly), -1);      msg_clr_eos();      (void)msg_end();      if (msg_silent == 0 && !silent_mode) { diff --git a/src/nvim/misc2.c b/src/nvim/misc2.c index 3c0a1414a6..4b64de1be0 100644 --- a/src/nvim/misc2.c +++ b/src/nvim/misc2.c @@ -327,9 +327,10 @@ int call_shell(char_u *cmd, ShellOpts opts, char_u *extra_shell_arg)      }    } -  set_vim_var_nr(VV_SHELL_ERROR, (long)retval); -  if (do_profiling == PROF_YES) +  set_vim_var_nr(VV_SHELL_ERROR, (varnumber_T) retval); +  if (do_profiling == PROF_YES) {      prof_child_exit(&wait_time); +  }    return retval;  } diff --git a/src/nvim/msgpack_rpc/server.c b/src/nvim/msgpack_rpc/server.c index 474e25ffeb..bf384e3379 100644 --- a/src/nvim/msgpack_rpc/server.c +++ b/src/nvim/msgpack_rpc/server.c @@ -59,7 +59,7 @@ static void set_vservername(garray_T *srvs)    char *default_server = (srvs->ga_len > 0)      ? ((SocketWatcher **)srvs->ga_data)[0]->addr      : NULL; -  set_vim_var_string(VV_SEND_SERVER, (char_u *)default_server, -1); +  set_vim_var_string(VV_SEND_SERVER, default_server, -1);  }  /// Teardown the server module diff --git a/src/nvim/normal.c b/src/nvim/normal.c index 2895816b8f..5b7c4b68b1 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -7057,18 +7057,17 @@ static void nv_operator(cmdarg_T *cap)   */  static void set_op_var(int optype)  { -  char_u opchars[3]; - -  if (optype == OP_NOP) +  if (optype == OP_NOP) {      set_vim_var_string(VV_OP, NULL, 0); -  else { +  } else { +    char opchars[3];      int opchar0 = get_op_char(optype);      assert(opchar0 >= 0 && opchar0 <= UCHAR_MAX); -    opchars[0] = (char_u)opchar0; +    opchars[0] = (char) opchar0;      int opchar1 = get_extra_op_char(optype);       assert(opchar1 >= 0 && opchar1 <= UCHAR_MAX); -    opchars[1] = (char_u)opchar1; +    opchars[1] = (char) opchar1;      opchars[2] = NUL;      set_vim_var_string(VV_OP, opchars, -1); diff --git a/src/nvim/option.c b/src/nvim/option.c index 5efd71444a..f9d1cdbaec 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -1448,7 +1448,7 @@ do_set (              char_u      *oldval = NULL;         // previous value if *varp              char_u      *newval;              char_u      *origval = NULL; -            char_u      *saved_origval = NULL; +            char *saved_origval = NULL;              unsigned newlen;              int comma;              int bs; @@ -1725,7 +1725,7 @@ do_set (              if (!starting && origval != NULL) {                // origval may be freed by                // did_set_string_option(), make a copy. -              saved_origval = vim_strsave(origval); +              saved_origval = xstrdup((char *) origval);              }              /* Handle side effects, and set the global value for @@ -1740,11 +1740,10 @@ do_set (              }              if (saved_origval != NULL) { -              char_u buf_type[7]; -              vim_snprintf((char *)buf_type, ARRAY_SIZE(buf_type), "%s", +              char buf_type[7]; +              vim_snprintf(buf_type, ARRAY_SIZE(buf_type), "%s",                             (opt_flags & OPT_LOCAL) ? "local" : "global"); -              set_vim_var_string(VV_OPTION_NEW, -                                 *(char_u **)varp, -1); +              set_vim_var_string(VV_OPTION_NEW, *(char **) varp, -1);                set_vim_var_string(VV_OPTION_OLD, saved_origval, -1);                set_vim_var_string(VV_OPTION_TYPE, buf_type, -1);                apply_autocmds(EVENT_OPTIONSET, @@ -2324,7 +2323,7 @@ set_string_option (    char_u      *s;    char_u      **varp;    char_u      *oldval; -  char_u      *saved_oldval = NULL; +  char *saved_oldval = NULL;    char_u      *r = NULL;    if (options[opt_idx].var == NULL)     /* don't set hidden option */ @@ -2340,7 +2339,7 @@ set_string_option (    *varp = s;    if (!starting) { -    saved_oldval = vim_strsave(oldval); +    saved_oldval = xstrdup((char *) oldval);    }    if ((r = did_set_string_option(opt_idx, varp, (int)true, oldval, NULL, @@ -2349,10 +2348,10 @@ set_string_option (    // call autocommand after handling side effects    if (saved_oldval != NULL) { -    char_u buf_type[7]; -    vim_snprintf((char *)buf_type, ARRAY_SIZE(buf_type), "%s", +    char buf_type[7]; +    vim_snprintf(buf_type, ARRAY_SIZE(buf_type), "%s",                   (opt_flags & OPT_LOCAL) ? "local" : "global"); -    set_vim_var_string(VV_OPTION_NEW, *varp, -1); +    set_vim_var_string(VV_OPTION_NEW, (char *) (*varp), -1);      set_vim_var_string(VV_OPTION_OLD, saved_oldval, -1);      set_vim_var_string(VV_OPTION_TYPE, buf_type, -1);      apply_autocmds(EVENT_OPTIONSET, @@ -3800,7 +3799,7 @@ set_bool_option (          msg_source(hl_attr(HLF_W));          MSG_ATTR(_(w_arabic), hl_attr(HLF_W)); -        set_vim_var_string(VV_WARNINGMSG, (char_u *)_(w_arabic), -1); +        set_vim_var_string(VV_WARNINGMSG, _(w_arabic), -1);        }        /* set 'delcombine' */ @@ -3847,14 +3846,14 @@ set_bool_option (    options[opt_idx].flags |= P_WAS_SET;    if (!starting) { -    char_u buf_old[2]; -    char_u buf_new[2]; -    char_u buf_type[7]; -    vim_snprintf((char *)buf_old, ARRAY_SIZE(buf_old), "%d", +    char buf_old[2]; +    char buf_new[2]; +    char buf_type[7]; +    vim_snprintf(buf_old, ARRAY_SIZE(buf_old), "%d",                   old_value ? true: false); -    vim_snprintf((char *)buf_new, ARRAY_SIZE(buf_new), "%d", +    vim_snprintf(buf_new, ARRAY_SIZE(buf_new), "%d",                   value ? true: false); -    vim_snprintf((char *)buf_type, ARRAY_SIZE(buf_type), "%s", +    vim_snprintf(buf_type, ARRAY_SIZE(buf_type), "%s",                   (opt_flags & OPT_LOCAL) ? "local" : "global");      set_vim_var_string(VV_OPTION_NEW, buf_new, -1);      set_vim_var_string(VV_OPTION_OLD, buf_old, -1); @@ -4237,12 +4236,12 @@ set_num_option (    options[opt_idx].flags |= P_WAS_SET;    if (!starting && errmsg == NULL) { -    char_u buf_old[NUMBUFLEN]; -    char_u buf_new[NUMBUFLEN]; -    char_u buf_type[7]; -    vim_snprintf((char *)buf_old, ARRAY_SIZE(buf_old), "%ld", old_value); -    vim_snprintf((char *)buf_new, ARRAY_SIZE(buf_new), "%ld", value); -    vim_snprintf((char *)buf_type, ARRAY_SIZE(buf_type), "%s", +    char buf_old[NUMBUFLEN]; +    char buf_new[NUMBUFLEN]; +    char buf_type[7]; +    vim_snprintf(buf_old, ARRAY_SIZE(buf_old), "%ld", old_value); +    vim_snprintf(buf_new, ARRAY_SIZE(buf_new), "%ld", value); +    vim_snprintf(buf_type, ARRAY_SIZE(buf_type), "%s",                   (opt_flags & OPT_LOCAL) ? "local" : "global");      set_vim_var_string(VV_OPTION_NEW, buf_new, -1);      set_vim_var_string(VV_OPTION_OLD, buf_old, -1); diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h index 8e74e5036e..bbe40873ec 100644 --- a/src/nvim/option_defs.h +++ b/src/nvim/option_defs.h @@ -4,6 +4,7 @@  #include <stdbool.h>  #include "nvim/types.h" +#include "nvim/macros.h"  // For EXTERN  // option_defs.h: definition of global variables for settable options diff --git a/src/nvim/path.c b/src/nvim/path.c index 22a3f96cfa..aaf54bc5b4 100644 --- a/src/nvim/path.c +++ b/src/nvim/path.c @@ -1495,13 +1495,12 @@ void simplify_filename(char_u *filename)    } while (*p != NUL);  } -static char_u *eval_includeexpr(char_u *ptr, size_t len) +static char *eval_includeexpr(const char *const ptr, const size_t len)  { -  assert(len <= INT_MAX); -  set_vim_var_string(VV_FNAME, ptr, (int)len); -  char_u *res = eval_to_string_safe(curbuf->b_p_inex, NULL, -                                    was_set_insecurely((char_u *)"includeexpr", -                                                       OPT_LOCAL)); +  set_vim_var_string(VV_FNAME, ptr, (ptrdiff_t) len); +  char *res = (char *) eval_to_string_safe( +      curbuf->b_p_inex, NULL, was_set_insecurely((char_u *)"includeexpr", +                                                 OPT_LOCAL));    set_vim_var_string(VV_FNAME, NULL, 0);    return res;  } @@ -1523,7 +1522,7 @@ find_file_name_in_path (    char_u *tofree = NULL;    if ((options & FNAME_INCL) && *curbuf->b_p_inex != NUL) { -    tofree = eval_includeexpr(ptr, len); +    tofree = (char_u *) eval_includeexpr((char *) ptr, len);      if (tofree != NULL) {        ptr = tofree;        len = STRLEN(ptr); @@ -1540,7 +1539,7 @@ find_file_name_in_path (       */      if (file_name == NULL          && !(options & FNAME_INCL) && *curbuf->b_p_inex != NUL) { -      tofree = eval_includeexpr(ptr, len); +      tofree = (char_u *) eval_includeexpr((char *) ptr, len);        if (tofree != NULL) {          ptr = tofree;          len = STRLEN(ptr); diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index 3bc6d46dd9..28c0425e2c 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -2440,8 +2440,6 @@ int grep_internal(cmdidx_T cmdidx)  void ex_make(exarg_T *eap)  {    char_u      *fname; -  char_u      *cmd; -  size_t len;    win_T       *wp = NULL;    qf_info_T   *qi = &ql_info;    int res; @@ -2479,30 +2477,28 @@ void ex_make(exarg_T *eap)      return;    os_remove((char *)fname);  // in case it's not unique -  /* -   * If 'shellpipe' empty: don't redirect to 'errorfile'. -   */ -  len = STRLEN(p_shq) * 2 + STRLEN(eap->arg) + 1; +  // If 'shellpipe' empty: don't redirect to 'errorfile'. +  const size_t len = (STRLEN(p_shq) * 2 + STRLEN(eap->arg) + 1 +                      + (*p_sp == NUL +                         ? 0 +                         : STRLEN(p_sp) + STRLEN(fname) + 3)); +  char *const cmd = xmalloc(len); +  snprintf(cmd, len, "%s%s%s", (char *)p_shq, (char *)eap->arg, +           (char *)p_shq);    if (*p_sp != NUL) { -    len += STRLEN(p_sp) + STRLEN(fname) + 3; +    append_redir(cmd, len, (char *) p_sp, (char *) fname); +  } +  // Output a newline if there's something else than the :make command that +  // was typed (in which case the cursor is in column 0). +  if (msg_col == 0) { +    msg_didout = false;    } -  cmd = xmalloc(len); -  sprintf((char *)cmd, "%s%s%s", (char *)p_shq, (char *)eap->arg, -      (char *)p_shq); -  if (*p_sp != NUL) -    append_redir(cmd, len, p_sp, fname); -  /* -   * Output a newline if there's something else than the :make command that -   * was typed (in which case the cursor is in column 0). -   */ -  if (msg_col == 0) -    msg_didout = FALSE;    msg_start();    MSG_PUTS(":!"); -  msg_outtrans(cmd);            /* show what we are doing */ +  msg_outtrans((char_u *) cmd);  // show what we are doing -  /* let the shell know if we are redirecting output or not */ -  do_shell(cmd, *p_sp != NUL ? kShellOptDoOut : 0); +  // let the shell know if we are redirecting output or not +  do_shell((char_u *) cmd, *p_sp != NUL ? kShellOptDoOut : 0);    res = qf_init(wp, fname, (eap->cmdidx != CMD_make diff --git a/src/nvim/shada.c b/src/nvim/shada.c index def2de9b1a..32a02b0fb7 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -31,7 +31,6 @@  #include "nvim/misc2.h"  #include "nvim/ex_getln.h"  #include "nvim/search.h" -#include "nvim/eval.h"  #include "nvim/regexp.h"  #include "nvim/eval_defs.h"  #include "nvim/version.h" @@ -39,6 +38,8 @@  #include "nvim/fileio.h"  #include "nvim/strings.h"  #include "nvim/quickfix.h" +#include "nvim/eval/encode.h" +#include "nvim/eval/decode.h"  #include "nvim/lib/khash.h"  #include "nvim/lib/kvec.h" @@ -65,9 +66,6 @@ KHASH_SET_INIT_STR(strset)      ((char *) copy_option_part((char_u **) src, (char_u *) dest, __VA_ARGS__))  #define find_shada_parameter(...) \      ((const char *) find_shada_parameter(__VA_ARGS__)) -#define emsg2(a, b) emsg2((char_u *) a, (char_u *) b) -#define emsg3(a, b, c) emsg3((char_u *) a, (char_u *) b, (char_u *) c) -#define emsgu(a, ...) emsgu((char_u *) a, __VA_ARGS__)  #define home_replace_save(a, b) \      ((char *)home_replace_save(a, (char_u *)b))  #define home_replace(a, b, c, d, e) \ @@ -761,7 +759,7 @@ static void close_sd_writer(ShaDaWriteDef *const sd_writer)  {    const int fd = (int)(intptr_t) sd_writer->cookie;    if (os_fsync(fd) < 0) { -    emsg2(_(SERR "System error while synchronizing ShaDa file: %s"), +    emsgf(_(SERR "System error while synchronizing ShaDa file: %s"),            os_strerror(errno));      errno = 0;    } @@ -811,11 +809,11 @@ static ShaDaReadResult sd_reader_skip(ShaDaReadDef *const sd_reader,  {    if (sd_reader->skip(sd_reader, offset) != OK) {      if (sd_reader->error != NULL) { -      emsg2(_(SERR "System error while skipping in ShaDa file: %s"), +      emsgf(_(SERR "System error while skipping in ShaDa file: %s"),              sd_reader->error);        return kSDReadStatusReadError;      } else if (sd_reader->eof) { -      emsgu(_(RCERR "Error while reading ShaDa file: " +      emsgf(_(RCERR "Error while reading ShaDa file: "                "last entry specified that it occupies %" PRIu64 " bytes, "                "but file ended earlier"),              (uint64_t) offset); @@ -849,7 +847,7 @@ open_file_start:        goto open_file_start;      }      if (fd != UV_EEXIST) { -      emsg3(_(SERR "System error while opening ShaDa file %s: %s"), +      emsgf(_(SERR "System error while opening ShaDa file %s: %s"),              fname, os_strerror(fd));      }      return fd; @@ -897,7 +895,7 @@ close_file_start:        errno = 0;        goto close_file_start;      } else { -      emsg2(_(SERR "System error while closing ShaDa file: %s"), +      emsgf(_(SERR "System error while closing ShaDa file: %s"),              strerror(errno));        errno = 0;      } @@ -934,7 +932,7 @@ static int msgpack_sd_writer_write(void *data, const char *buf, size_t len)    ShaDaWriteDef *const sd_writer = (ShaDaWriteDef *) data;    ptrdiff_t written_bytes = sd_writer->write(sd_writer, buf, len);    if (written_bytes == -1) { -    emsg2(_(SERR "System error while writing ShaDa file: %s"), +    emsgf(_(SERR "System error while writing ShaDa file: %s"),            sd_writer->error);      return -1;    } @@ -981,7 +979,7 @@ static int shada_read_file(const char *const file, const int flags)    if (of_ret != 0) {      if (of_ret == UV_ENOENT && (flags & kShaDaMissingError)) { -      emsg3(_(SERR "System error while opening ShaDa file %s for reading: %s"), +      emsgf(_(SERR "System error while opening ShaDa file %s for reading: %s"),              fname, os_strerror(of_ret));      }      xfree(fname); @@ -1687,8 +1685,9 @@ static ShaDaWriteResult shada_pack_entry(msgpack_packer *const packer,    do { \      if ((src) != NULL) { \        for (listitem_T *li = (src)->lv_first; li != NULL; li = li->li_next) { \ -        if (vim_to_msgpack(spacker, &li->li_tv, \ -                           _("additional elements of ShaDa " what)) == FAIL) { \ +        if (encode_vim_to_msgpack(spacker, &li->li_tv, \ +                                  _("additional elements of ShaDa " what)) \ +            == FAIL) { \            goto shada_pack_entry_error; \          } \        } \ @@ -1706,8 +1705,9 @@ static ShaDaWriteResult shada_pack_entry(msgpack_packer *const packer,            const size_t key_len = strlen((const char *) hi->hi_key); \            msgpack_pack_str(spacker, key_len); \            msgpack_pack_str_body(spacker, (const char *) hi->hi_key, key_len); \ -          if (vim_to_msgpack(spacker, &di->di_tv, \ -                             _("additional data of ShaDa " what)) == FAIL) { \ +          if (encode_vim_to_msgpack(spacker, &di->di_tv, \ +                                    _("additional data of ShaDa " what)) \ +              == FAIL) { \              goto shada_pack_entry_error; \            } \          } \ @@ -1757,7 +1757,7 @@ static ShaDaWriteResult shada_pack_entry(msgpack_packer *const packer,        char vardesc[256] = "variable g:";        memcpy(&vardesc[sizeof("variable g:") - 1], varname.data,               varname.size + 1); -      if (vim_to_msgpack(spacker, &entry.data.global_var.value, vardesc) +      if (encode_vim_to_msgpack(spacker, &entry.data.global_var.value, vardesc)            == FAIL) {          ret = kSDWriteIgnError;          EMSG2(_(WERR "Failed to write variable %s"), @@ -2159,7 +2159,7 @@ shada_parse_msgpack_read_next: {}        break;      }      case MSGPACK_UNPACK_PARSE_ERROR: { -      emsgu(_(RCERR "Failed to parse ShaDa file due to a msgpack parser error " +      emsgf(_(RCERR "Failed to parse ShaDa file due to a msgpack parser error "                "at position %" PRIu64),              (uint64_t) initial_fpos);        ret = kSDReadStatusNotShaDa; @@ -2176,7 +2176,7 @@ shada_parse_msgpack_read_next: {}        break;      }      case MSGPACK_UNPACK_CONTINUE: { -      emsgu(_(RCERR "Failed to parse ShaDa file: incomplete msgpack string " +      emsgf(_(RCERR "Failed to parse ShaDa file: incomplete msgpack string "                "at position %" PRIu64),              (uint64_t) initial_fpos);        ret = kSDReadStatusNotShaDa; @@ -2184,7 +2184,7 @@ shada_parse_msgpack_read_next: {}      }      case MSGPACK_UNPACK_EXTRA_BYTES: {  shada_parse_msgpack_extra_bytes: -      emsgu(_(RCERR "Failed to parse ShaDa file: extra bytes in msgpack string " +      emsgf(_(RCERR "Failed to parse ShaDa file: extra bytes in msgpack string "                "at position %" PRIu64),              (uint64_t) initial_fpos);        ret = kSDReadStatusNotShaDa; @@ -3265,11 +3265,11 @@ static ShaDaReadResult fread_len(ShaDaReadDef *const sd_reader,    (void) read_bytes;    if (sd_reader->error != NULL) { -    emsg2(_(SERR "System error while reading ShaDa file: %s"), +    emsgf(_(SERR "System error while reading ShaDa file: %s"),            sd_reader->error);      return kSDReadStatusReadError;    } else if (sd_reader->eof) { -    emsgu(_(RCERR "Error while reading ShaDa file: " +    emsgf(_(RCERR "Error while reading ShaDa file: "              "last entry specified that it occupies %" PRIu64 " bytes, "              "but file ended earlier"),            (uint64_t) length); @@ -3304,11 +3304,11 @@ static ShaDaReadResult msgpack_read_uint64(ShaDaReadDef *const sd_reader,    if (first_char == EOF) {      if (sd_reader->error) { -      emsg2(_(SERR "System error while reading integer from ShaDa file: %s"), +      emsgf(_(SERR "System error while reading integer from ShaDa file: %s"),              sd_reader->error);        return kSDReadStatusReadError;      } else if (sd_reader->eof) { -      emsgu(_(RCERR "Error while reading ShaDa file: " +      emsgf(_(RCERR "Error while reading ShaDa file: "                "expected positive integer at position %" PRIu64                ", but got nothing"),              (uint64_t) fpos); @@ -3339,7 +3339,7 @@ static ShaDaReadResult msgpack_read_uint64(ShaDaReadDef *const sd_reader,          break;        }        default: { -        emsgu(_(RCERR "Error while reading ShaDa file: " +        emsgf(_(RCERR "Error while reading ShaDa file: "                  "expected positive integer at position %" PRIu64),                (uint64_t) fpos);          return kSDReadStatusNotShaDa; @@ -3403,18 +3403,18 @@ static inline char *get_converted_string(const vimconv_T *const sd_conv,                        proc) \    do { \      if (!(condition)) { \ -      emsgu(_(READERR(entry_name, error_desc)), initial_fpos); \ +      emsgf(_(READERR(entry_name, error_desc)), initial_fpos); \        CLEAR_GA_AND_ERROR_OUT(ad_ga); \      } \      tgt = proc(obj.via.attr); \    } while (0)  #define CHECK_KEY_IS_STR(entry_name) \    if (unpacked.data.via.map.ptr[i].key.type != MSGPACK_OBJECT_STR) { \ -    emsgu(_(READERR(entry_name, "has key which is not a string")), \ +    emsgf(_(READERR(entry_name, "has key which is not a string")), \            initial_fpos); \      CLEAR_GA_AND_ERROR_OUT(ad_ga); \    } else if (unpacked.data.via.map.ptr[i].key.via.str.size == 0) { \ -    emsgu(_(READERR(entry_name, "has empty key")), initial_fpos); \ +    emsgf(_(READERR(entry_name, "has empty key")), initial_fpos); \      CLEAR_GA_AND_ERROR_OUT(ad_ga); \    }  #define CHECKED_KEY(entry_name, name, error_desc, tgt, condition, attr, proc) \ @@ -3477,7 +3477,7 @@ static inline char *get_converted_string(const vimconv_T *const sd_conv,            typval_T adtv; \            if (msgpack_to_vim(obj, &adtv) == FAIL \                || adtv.v_type != VAR_DICT) { \ -            emsgu(_(READERR(name, \ +            emsgf(_(READERR(name, \                              "cannot be converted to a VimL dictionary")), \                    initial_fpos); \              ga_clear(&ad_ga); \ @@ -3502,7 +3502,7 @@ static inline char *get_converted_string(const vimconv_T *const sd_conv,            }; \            typval_T aetv; \            if (msgpack_to_vim(obj, &aetv) == FAIL) { \ -            emsgu(_(READERR(name, "cannot be converted to a VimL list")), \ +            emsgf(_(READERR(name, "cannot be converted to a VimL list")), \                    initial_fpos); \              clear_tv(&aetv); \              goto shada_read_next_item_error; \ @@ -3570,7 +3570,7 @@ shada_read_next_item_start:      // kSDItemUnknown cannot possibly pass that far because it is -1 and that      // will fail in msgpack_read_uint64. But kSDItemMissing may and it will      // otherwise be skipped because (1 << 0) will never appear in flags. -    emsgu(_(RCERR "Error while reading ShaDa file: " +    emsgf(_(RCERR "Error while reading ShaDa file: "              "there is an item at position %" PRIu64 " "              "that must not be there: Missing items are "              "for internal uses only"), @@ -3640,14 +3640,14 @@ shada_read_next_item_start:    switch ((ShadaEntryType) type_u64) {      case kSDItemHeader: {        if (!msgpack_rpc_to_dictionary(&(unpacked.data), &(entry->data.header))) { -        emsgu(_(READERR("header", "is not a dictionary")), initial_fpos); +        emsgf(_(READERR("header", "is not a dictionary")), initial_fpos);          goto shada_read_next_item_error;        }        break;      }      case kSDItemSearchPattern: {        if (unpacked.data.type != MSGPACK_OBJECT_MAP) { -        emsgu(_(READERR("search pattern", "is not a dictionary")), +        emsgf(_(READERR("search pattern", "is not a dictionary")),                initial_fpos);          goto shada_read_next_item_error;        } @@ -3678,7 +3678,7 @@ shada_read_next_item_start:          ADDITIONAL_KEY        }        if (entry->data.search_pattern.pat == NULL) { -        emsgu(_(READERR("search pattern", "has no pattern")), initial_fpos); +        emsgf(_(READERR("search pattern", "has no pattern")), initial_fpos);          CLEAR_GA_AND_ERROR_OUT(ad_ga);        }        SET_ADDITIONAL_DATA(entry->data.search_pattern.additional_data, @@ -3690,7 +3690,7 @@ shada_read_next_item_start:      case kSDItemGlobalMark:      case kSDItemLocalMark: {        if (unpacked.data.type != MSGPACK_OBJECT_MAP) { -        emsgu(_(READERR("mark", "is not a dictionary")), initial_fpos); +        emsgf(_(READERR("mark", "is not a dictionary")), initial_fpos);          goto shada_read_next_item_error;        }        garray_T ad_ga; @@ -3699,7 +3699,7 @@ shada_read_next_item_start:          CHECK_KEY_IS_STR("mark")          if (CHECK_KEY(unpacked.data.via.map.ptr[i].key, KEY_NAME_CHAR)) {            if (type_u64 == kSDItemJump || type_u64 == kSDItemChange) { -            emsgu(_(READERR("mark", "has n key which is only valid for " +            emsgf(_(READERR("mark", "has n key which is only valid for "                              "local and global mark entries")), initial_fpos);              CLEAR_GA_AND_ERROR_OUT(ad_ga);            } @@ -3716,15 +3716,15 @@ shada_read_next_item_start:          ADDITIONAL_KEY        }        if (entry->data.filemark.fname == NULL) { -        emsgu(_(READERR("mark", "is missing file name")), initial_fpos); +        emsgf(_(READERR("mark", "is missing file name")), initial_fpos);          CLEAR_GA_AND_ERROR_OUT(ad_ga);        }        if (entry->data.filemark.mark.lnum <= 0) { -        emsgu(_(READERR("mark", "has invalid line number")), initial_fpos); +        emsgf(_(READERR("mark", "has invalid line number")), initial_fpos);          CLEAR_GA_AND_ERROR_OUT(ad_ga);        }        if (entry->data.filemark.mark.col < 0) { -        emsgu(_(READERR("mark", "has invalid column number")), initial_fpos); +        emsgf(_(READERR("mark", "has invalid column number")), initial_fpos);          CLEAR_GA_AND_ERROR_OUT(ad_ga);        }        SET_ADDITIONAL_DATA(entry->data.filemark.additional_data, "mark"); @@ -3732,7 +3732,7 @@ shada_read_next_item_start:      }      case kSDItemRegister: {        if (unpacked.data.type != MSGPACK_OBJECT_MAP) { -        emsgu(_(READERR("register", "is not a dictionary")), initial_fpos); +        emsgf(_(READERR("register", "is not a dictionary")), initial_fpos);          goto shada_read_next_item_error;        }        garray_T ad_ga; @@ -3742,14 +3742,14 @@ shada_read_next_item_start:          if (CHECK_KEY(unpacked.data.via.map.ptr[i].key,                        REG_KEY_CONTENTS)) {            if (unpacked.data.via.map.ptr[i].val.type != MSGPACK_OBJECT_ARRAY) { -            emsgu(_(READERR("register", +            emsgf(_(READERR("register",                              "has " REG_KEY_CONTENTS                              " key with non-array value")),                    initial_fpos);              CLEAR_GA_AND_ERROR_OUT(ad_ga);            }            if (unpacked.data.via.map.ptr[i].val.via.array.size == 0) { -            emsgu(_(READERR("register", +            emsgf(_(READERR("register",                              "has " REG_KEY_CONTENTS " key with empty array")),                    initial_fpos);              CLEAR_GA_AND_ERROR_OUT(ad_ga); @@ -3758,7 +3758,7 @@ shada_read_next_item_start:                unpacked.data.via.map.ptr[i].val.via.array;            for (size_t i = 0; i < arr.size; i++) {              if (arr.ptr[i].type != MSGPACK_OBJECT_BIN) { -              emsgu(_(READERR("register", "has " REG_KEY_CONTENTS " array " +              emsgf(_(READERR("register", "has " REG_KEY_CONTENTS " array "                                "with non-binary value")), initial_fpos);                CLEAR_GA_AND_ERROR_OUT(ad_ga);              } @@ -3778,7 +3778,7 @@ shada_read_next_item_start:          ADDITIONAL_KEY        }        if (entry->data.reg.contents == NULL) { -        emsgu(_(READERR("register", "has missing " REG_KEY_CONTENTS " array")), +        emsgf(_(READERR("register", "has missing " REG_KEY_CONTENTS " array")),                initial_fpos);          CLEAR_GA_AND_ERROR_OUT(ad_ga);        } @@ -3787,29 +3787,29 @@ shada_read_next_item_start:      }      case kSDItemHistoryEntry: {        if (unpacked.data.type != MSGPACK_OBJECT_ARRAY) { -        emsgu(_(READERR("history", "is not an array")), initial_fpos); +        emsgf(_(READERR("history", "is not an array")), initial_fpos);          goto shada_read_next_item_error;        }        if (unpacked.data.via.array.size < 2) { -        emsgu(_(READERR("history", "does not have enough elements")), +        emsgf(_(READERR("history", "does not have enough elements")),                initial_fpos);          goto shada_read_next_item_error;        }        if (unpacked.data.via.array.ptr[0].type            != MSGPACK_OBJECT_POSITIVE_INTEGER) { -        emsgu(_(READERR("history", "has wrong history type type")), +        emsgf(_(READERR("history", "has wrong history type type")),                initial_fpos);          goto shada_read_next_item_error;        }        if (unpacked.data.via.array.ptr[1].type            != MSGPACK_OBJECT_BIN) { -        emsgu(_(READERR("history", "has wrong history string type")), +        emsgf(_(READERR("history", "has wrong history string type")),                initial_fpos);          goto shada_read_next_item_error;        }        if (memchr(unpacked.data.via.array.ptr[1].via.bin.ptr, 0,                   unpacked.data.via.array.ptr[1].via.bin.size) != NULL) { -        emsgu(_(READERR("history", "contains string with zero byte inside")), +        emsgf(_(READERR("history", "contains string with zero byte inside")),                initial_fpos);          goto shada_read_next_item_error;        } @@ -3819,13 +3819,13 @@ shada_read_next_item_start:            entry->data.history_item.histtype == HIST_SEARCH;        if (is_hist_search) {          if (unpacked.data.via.array.size < 3) { -          emsgu(_(READERR("search history", +          emsgf(_(READERR("search history",                            "does not have separator character")), initial_fpos);            goto shada_read_next_item_error;          }          if (unpacked.data.via.array.ptr[2].type              != MSGPACK_OBJECT_POSITIVE_INTEGER) { -          emsgu(_(READERR("search history", +          emsgf(_(READERR("search history",                            "has wrong history separator type")), initial_fpos);            goto shada_read_next_item_error;          } @@ -3867,22 +3867,16 @@ shada_read_next_item_hist_no_conv:      }      case kSDItemVariable: {        if (unpacked.data.type != MSGPACK_OBJECT_ARRAY) { -        emsgu(_(READERR("variable", "is not an array")), initial_fpos); +        emsgf(_(READERR("variable", "is not an array")), initial_fpos);          goto shada_read_next_item_error;        }        if (unpacked.data.via.array.size < 2) { -        emsgu(_(READERR("variable", "does not have enough elements")), +        emsgf(_(READERR("variable", "does not have enough elements")),                initial_fpos);          goto shada_read_next_item_error;        }        if (unpacked.data.via.array.ptr[0].type != MSGPACK_OBJECT_BIN) { -        emsgu(_(READERR("variable", "has wrong variable name type")), -              initial_fpos); -        goto shada_read_next_item_error; -      } -      if (unpacked.data.via.array.ptr[1].type == MSGPACK_OBJECT_NIL -          || unpacked.data.via.array.ptr[1].type == MSGPACK_OBJECT_EXT) { -        emsgu(_(READERR("variable", "has wrong variable value type")), +        emsgf(_(READERR("variable", "has wrong variable name type")),                initial_fpos);          goto shada_read_next_item_error;        } @@ -3891,7 +3885,7 @@ shada_read_next_item_hist_no_conv:                     unpacked.data.via.array.ptr[0].via.bin.size);        if (msgpack_to_vim(unpacked.data.via.array.ptr[1],                           &(entry->data.global_var.value)) == FAIL) { -        emsgu(_(READERR("variable", "has value that cannot " +        emsgf(_(READERR("variable", "has value that cannot "                          "be converted to the VimL value")), initial_fpos);          goto shada_read_next_item_error;        } @@ -3912,16 +3906,16 @@ shada_read_next_item_hist_no_conv:      }      case kSDItemSubString: {        if (unpacked.data.type != MSGPACK_OBJECT_ARRAY) { -        emsgu(_(READERR("sub string", "is not an array")), initial_fpos); +        emsgf(_(READERR("sub string", "is not an array")), initial_fpos);          goto shada_read_next_item_error;        }        if (unpacked.data.via.array.size < 1) { -        emsgu(_(READERR("sub string", "does not have enough elements")), +        emsgf(_(READERR("sub string", "does not have enough elements")),                initial_fpos);          goto shada_read_next_item_error;        }        if (unpacked.data.via.array.ptr[0].type != MSGPACK_OBJECT_BIN) { -        emsgu(_(READERR("sub string", "has wrong sub string type")), +        emsgf(_(READERR("sub string", "has wrong sub string type")),                initial_fpos);          goto shada_read_next_item_error;        } @@ -3934,7 +3928,7 @@ shada_read_next_item_hist_no_conv:      }      case kSDItemBufferList: {        if (unpacked.data.type != MSGPACK_OBJECT_ARRAY) { -        emsgu(_(READERR("buffer list", "is not an array")), initial_fpos); +        emsgf(_(READERR("buffer list", "is not an array")), initial_fpos);          goto shada_read_next_item_error;        }        if (unpacked.data.via.array.size == 0) { @@ -3951,7 +3945,7 @@ shada_read_next_item_hist_no_conv:          {            msgpack_unpacked unpacked = unpacked_2;            if (unpacked.data.type != MSGPACK_OBJECT_MAP) { -            emsgu(_(RERR "Error while reading ShaDa file: " +            emsgf(_(RERR "Error while reading ShaDa file: "                      "buffer list at position %" PRIu64 " "                      "contains entry that is not a dictionary"),                    initial_fpos); @@ -3976,21 +3970,21 @@ shada_read_next_item_hist_no_conv:              }            }            if (entry->data.buffer_list.buffers[i].pos.lnum <= 0) { -            emsgu(_(RERR "Error while reading ShaDa file: " +            emsgf(_(RERR "Error while reading ShaDa file: "                      "buffer list at position %" PRIu64 " "                      "contains entry with invalid line number"),                    initial_fpos);              CLEAR_GA_AND_ERROR_OUT(ad_ga);            }            if (entry->data.buffer_list.buffers[i].pos.col < 0) { -            emsgu(_(RERR "Error while reading ShaDa file: " +            emsgf(_(RERR "Error while reading ShaDa file: "                      "buffer list at position %" PRIu64 " "                      "contains entry with invalid column number"),                    initial_fpos);              CLEAR_GA_AND_ERROR_OUT(ad_ga);            }            if (entry->data.buffer_list.buffers[i].fname == NULL) { -            emsgu(_(RERR "Error while reading ShaDa file: " +            emsgf(_(RERR "Error while reading ShaDa file: "                      "buffer list at position %" PRIu64 " "                      "contains entry that does not have a file name"),                    initial_fpos); diff --git a/src/nvim/tag.c b/src/nvim/tag.c index 8fcb02c3b6..26a89094aa 100644 --- a/src/nvim/tag.c +++ b/src/nvim/tag.c @@ -872,7 +872,7 @@ do_tag (        /* Let the SwapExists event know what tag we are jumping to. */        vim_snprintf((char *)IObuff, IOSIZE, ":ta %s\r", name); -      set_vim_var_string(VV_SWAPCOMMAND, IObuff, -1); +      set_vim_var_string(VV_SWAPCOMMAND, (char *) IObuff, -1);        /*         * Jump to the desired match. diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c index 63a7e20880..0440272eb9 100644 --- a/src/nvim/terminal.c +++ b/src/nvim/terminal.c @@ -627,6 +627,7 @@ static int term_settermprop(VTermProp prop, VTermValue *val, void *data)        api_free_object(dict_set_value(buf->b_vars,                                       cstr_as_string("term_title"),                                       STRING_OBJ(cstr_as_string(val->string)), +                                     false,                                       &err));        break;      } diff --git a/src/nvim/testdir/Makefile b/src/nvim/testdir/Makefile index ec236e72b4..55a49122d5 100644 --- a/src/nvim/testdir/Makefile +++ b/src/nvim/testdir/Makefile @@ -38,7 +38,7 @@ SCRIPTS := \             test_marks.out         \             test_match_conceal.out \ -NEW_TESTS = +NEW_TESTS = test_viml.res  SCRIPTS_GUI := test16.out diff --git a/src/nvim/testdir/runtest.vim b/src/nvim/testdir/runtest.vim index 8314a45d0c..1c610eab51 100644 --- a/src/nvim/testdir/runtest.vim +++ b/src/nvim/testdir/runtest.vim @@ -33,6 +33,15 @@ if &lines < 24 || &columns < 80    cquit  endif +" This also enables use of line continuation. +set viminfo+=nviminfo + +" Avoid stopping at the "hit enter" prompt +set nomore + +" Output all messages in English. +lang mess C +  " Source the test script.  First grab the file name, in case the script  " navigates away.  let testname = expand('%') @@ -40,12 +49,17 @@ let done = 0  let fail = 0  let errors = []  let messages = [] -try +if expand('%') =~ 'test_viml.vim' +  " this test has intentional errors, don't use try/catch.    source % -catch -  let fail += 1 -  call add(errors, 'Caught exception: ' . v:exception . ' @ ' . v:throwpoint) -endtry +else +  try +    source % +  catch +    let fail += 1 +    call add(errors, 'Caught exception: ' . v:exception . ' @ ' . v:throwpoint) +  endtry +endif  " Locate Test_ functions and execute them.  redir @q diff --git a/src/nvim/testdir/test49.ok b/src/nvim/testdir/test49.ok index bf1ceed9af..50fc5d2cef 100644 --- a/src/nvim/testdir/test49.ok +++ b/src/nvim/testdir/test49.ok @@ -1,19 +1,4 @@  Results of test49.vim: -*** Test   1: OK (34695) -*** Test   2: OK (34695) -*** Test   3: OK (1384648195) -*** Test   4: OK (32883) -*** Test   5: OK (32883) -*** Test   6: OK (603978947) -*** Test   7: OK (90563) -*** Test   8: OK (562493431) -*** Test   9: OK (363) -*** Test  10: OK (559615) -*** Test  11: OK (2049) -*** Test  12: OK (352256) -*** Test  13: OK (145) -*** Test  14: OK (42413) -*** Test  15: OK (42413)  *** Test  16: OK (8722)  *** Test  17: OK (285127993)  *** Test  18: OK (67224583) diff --git a/src/nvim/testdir/test49.vim b/src/nvim/testdir/test49.vim index 70d388b06a..edd49a2b63 100644 --- a/src/nvim/testdir/test49.vim +++ b/src/nvim/testdir/test49.vim @@ -1,6 +1,6 @@  " Vim script language tests  " Author:	Servatius Brandt <Servatius.Brandt@fujitsu-siemens.com> -" Last Change:	2015 Sep 25 +" Last Change:	2016 Feb 07  "-------------------------------------------------------------------------------  " Test environment							    {{{1 @@ -608,850 +608,8 @@ com! -nargs=1 -bar ExecAsScript call ExecAsScript(<f-args>)  " END_OF_TEST_ENVIRONMENT - do not change or remove this line. -"------------------------------------------------------------------------------- -" Test 1:   :endwhile in function					    {{{1 -" -"	    Detect if a broken loop is (incorrectly) reactivated by the -"	    :endwhile.  Use a :return to prevent an endless loop, and make -"	    this test first to get a meaningful result on an error before other -"	    tests will hang. -"------------------------------------------------------------------------------- - -XpathINIT - -function! F() -    Xpath 1					" X: 1 -    let first = 1 -    XloopINIT 2 8 -    while 1 -	Xloop 1					" X: 2	    + 0 * 16 -	if first -	    Xloop 2				" X: 4	    + 0 * 32 -	    let first = 0 -	    XloopNEXT -	    break -	else -	    Xloop 4				" X: 0	    + 0 * 64 -	    return -	endif -    endwhile -endfunction - -call F() -Xpath 128					" X: 128 - -function! G() -    Xpath 256					" X: 256    + 0 * 2048 -    let first = 1 -    XloopINIT 512 8 -    while 1 -	Xloop 1					" X: 512    + 0 * 4096 -	if first -	    Xloop 2				" X: 1024   + 0 * 8192 -	    let first = 0 -	    XloopNEXT -	    break -	else -	    Xloop 4				" X: 0	    + 0 * 16384 -	    return -	endif -	if 1	" unmatched :if -    endwhile -endfunction - -call G() -Xpath 32768					" X: 32768 - -Xcheck 34695 - -" Leave F and G for execution as scripts in the next test. - - -"------------------------------------------------------------------------------- -" Test 2:   :endwhile in script						    {{{1 -" -"	    Detect if a broken loop is (incorrectly) reactivated by the -"	    :endwhile.  Use a :finish to prevent an endless loop, and place -"	    this test before others that might hang to get a meaningful result -"	    on an error. -" -"	    This test executes the bodies of the functions F and G from the -"	    previous test as script files (:return replaced by :finish). -"------------------------------------------------------------------------------- - -XpathINIT - -ExecAsScript F					" X: 1 + 2 + 4 -Xpath 128					" X: 128 - -ExecAsScript G					" X: 256 + 512 + 1024 -Xpath 32768					" X: 32768 - -unlet first -delfunction F -delfunction G - -Xcheck 34695 - - -"------------------------------------------------------------------------------- -" Test 3:   :if, :elseif, :while, :continue, :break			    {{{1 -"------------------------------------------------------------------------------- - -XpathINIT -if 1 -    Xpath 1					" X: 1 -    let loops = 3 -    XloopINIT 2 512 -    while loops > -1	    " main loop: loops == 3, 2, 1 (which breaks) -	if loops <= 0 -	    let break_err = 1 -	    let loops = -1 -	else					"    3:  2:      1: -	    Xloop 1				" X: 2 + 2*512 + 2*512*512 -	endif -	if (loops == 2) -	    while loops == 2 " dummy loop -		Xloop 2				" X:     4*512 -		let loops = loops - 1 -		continue    " stop dummy loop -		Xloop 4				" X:     0 -	    endwhile -	    XloopNEXT -	    continue	    " continue main loop -	    Xloop 8				" X:     0 -	elseif (loops == 1) -	    let p = 1 -	    while p	    " dummy loop -		Xloop 16			" X:		 32*512*512 -		let p = 0 -		break	    " break dummy loop -		Xloop 32			" X:		 0 -	    endwhile -	    Xloop 64				" X:		 128*512*512 -	    unlet p -	    break	    " break main loop -	    Xloop 128				" X:		 0 -	endif -	if (loops > 0) -	    Xloop 256				" X: 512 -	endif -	while loops == 3    " dummy loop -	    let loops = loops - 1 -	endwhile	    " end dummy loop -	XloopNEXT -    endwhile		    " end main loop -    Xpath 268435456				" X: 1024*512*512 -else -    Xpath 536870912				" X: 0 -endif -Xpath 1073741824				" X: 4096*512*512 -if exists("break_err") -    " The Xpath command does not accept 2^31 (negative); add explicitly: -    let Xpath = Xpath + 2147483648		" X: 0 -    unlet break_err -endif - -unlet loops - -Xcheck 1384648195 - - -"------------------------------------------------------------------------------- -" Test 4:   :return							    {{{1 -"------------------------------------------------------------------------------- - -XpathINIT - -function! F() -    if 1 -	Xpath 1					" X: 1 -	let loops = 3 -	XloopINIT 2 16 -	while loops > 0				"    3:  2:     1: -	    Xloop 1				" X: 2 + 2*16 + 0*16*16 -	    if (loops == 2) -		Xloop 2				" X:     4*16 -		return -		Xloop 4				" X:     0 -	    endif -	    Xloop 8				" X: 16 -	    let loops = loops - 1 -	    XloopNEXT -	endwhile -	Xpath 8192				" X: 0 -    else -	Xpath 16384				" X: 0 -    endif -endfunction - -call F() -Xpath 32768					" X: 8*16*16*16 - -Xcheck 32883 - -" Leave F for execution as a script in the next test. - - -"------------------------------------------------------------------------------- -" Test 5:   :finish							    {{{1 -" -"	    This test executes the body of the function F from the previous test -"	    as a script file (:return replaced by :finish). -"------------------------------------------------------------------------------- - -XpathINIT - -ExecAsScript F					" X: 1 + 2 + 2*16 + 4*16 + 16 -Xpath 32768					" X: 32768 - -unlet loops -delfunction F - -Xcheck 32883 - - -"------------------------------------------------------------------------------- -" Test 6:   Defining functions in :while loops				    {{{1 -" -"	     Functions can be defined inside other functions.  An inner function -"	     gets defined when the outer function is executed.  Functions may -"	     also be defined inside while loops.  Expressions in braces for -"	     defining the function name are allowed. -"------------------------------------------------------------------------------- - -XpathINIT - -if ExtraVim() - -    " The command CALL collects the argument of all its invocations in "calls" -    " when used from a function (that is, when the global variable "calls" needs -    " the "g:" prefix).  This is to check that the function code is skipped when -    " the function is defined.  For inner functions, do so only if the outer -    " function is not being executed. -    " -    let calls = "" -    com! -nargs=1 CALL -		\ if !exists("calls") && !exists("outer") | -		\ let g:calls = g:calls . <args> | -		\ endif - - -    XloopINIT! 1 16 - -    let i = 0 -    while i < 3 - -	XloopNEXT -	let i = i + 1 - -	if i == 1 -	    Xloop 1				" X: 1 -	    function! F1(arg) -		CALL a:arg -		let outer = 1 - -		XloopINIT! 4096 4 -		let j = 0 -		while j < 1 -		    XloopNEXT -		    Xloop 1			" X: 4096 -		    let j = j + 1 -		    function! G1(arg) -			CALL a:arg -		    endfunction -		    Xloop 2			" X: 8192 -		endwhile -	    endfunction -	    Xloop 2				" X: 2 - -	    continue -	endif - -	Xloop 4					" X: 4 * (16 + 256) -	function! F{i}(i, arg) -	    CALL a:arg -	    let outer = 1 - -	    XloopINIT! 16384 4 -	    if a:i == 3 -		XloopNEXT -		XloopNEXT -		XloopNEXT -	    endif -	    let k = 0 -	    while k < 3 -		XloopNEXT -		Xloop 1				" X: 16384*(1+4+16+64+256+1024) -		let k = k + 1 -		function! G{a:i}{k}(arg) -		    CALL a:arg -		endfunction -		Xloop 2				" X: 32768*(1+4+16+64+256+1024) -	    endwhile -	endfunction -	Xloop 8					" X: 8 * (16 + 256) - -    endwhile - -    if exists("*G1") -	Xpath 67108864				" X: 0 -    endif -    if exists("*F1") -	call F1("F1") -	if exists("*G1") -	    call G1("G1") -	endif -    endif - -    if exists("G21") || exists("G21") || exists("G21") -	Xpath 134217728				" X: 0 -    endif -    if exists("*F2") -	call F2(2, "F2") -	if exists("*G21") -	    call G21("G21") -	endif -	if exists("*G22") -	    call G22("G22") -	endif -	if exists("*G23") -	    call G23("G23") -	endif -    endif - -    if exists("G31") || exists("G31") || exists("G31") -	Xpath 268435456				" X: 0 -    endif -    if exists("*F3") -	call F3(3, "F3") -	if exists("*G31") -	    call G31("G31") -	endif -	if exists("*G32") -	    call G32("G32") -	endif -	if exists("*G33") -	    call G33("G33") -	endif -    endif - -    Xpath 536870912				" X: 536870912 - -    if calls != "F1G1F2G21G22G23F3G31G32G33" -	Xpath 1073741824			" X: 0 -	Xout "calls is" calls -    endif - -    delfunction F1 -    delfunction G1 -    delfunction F2 -    delfunction G21 -    delfunction G22 -    delfunction G23 -    delfunction G31 -    delfunction G32 -    delfunction G33 - -endif - -Xcheck 603978947 - - -"------------------------------------------------------------------------------- -" Test 7:   Continuing on errors outside functions			    {{{1 -" -"	    On an error outside a function, the script processing continues -"	    at the line following the outermost :endif or :endwhile.  When not -"	    inside an :if or :while, the script processing continues at the next -"	    line. -"------------------------------------------------------------------------------- - -XpathINIT - -if 1 -    Xpath 1					" X: 1 -    while 1 -	Xpath 2					" X: 2 -	asdf -	Xpath 4					" X: 0 -	break -    endwhile | Xpath 8				" X: 0 -    Xpath 16					" X: 0 -endif | Xpath 32				" X: 0 -Xpath 64					" X: 64 - -while 1 -    Xpath 128					" X: 128 -    if 1 -	Xpath 256				" X: 256 -	asdf -	Xpath 512				" X: 0 -    endif | Xpath 1024				" X: 0 -    Xpath 2048					" X: 0 -    break -endwhile | Xpath 4096				" X: 0 -Xpath 8192					" X: 8192 - -asdf -Xpath 16384					" X: 16384 - -asdf | Xpath 32768				" X: 0 -Xpath 65536					" X: 65536 - -Xcheck 90563 - - -"------------------------------------------------------------------------------- -" Test 8:   Aborting and continuing on errors inside functions		    {{{1 -" -"	    On an error inside a function without the "abort" attribute, the -"	    script processing continues at the next line (unless the error was -"	    in a :return command).  On an error inside a function with the -"	    "abort" attribute, the function is aborted and the script processing -"	    continues after the function call; the value -1 is returned then. -"------------------------------------------------------------------------------- - -XpathINIT - -function! F() -    if 1 -	Xpath 1					" X: 1 -	while 1 -	    Xpath 2				" X: 2 -	    asdf -	    Xpath 4				" X: 4 -	    asdf | Xpath 8			" X: 0 -	    Xpath 16				" X: 16 -	    break -	endwhile -	Xpath 32				" X: 32 -    endif | Xpath 64				" X: 64 -    Xpath 128					" X: 128 - -    while 1 -	Xpath 256				" X: 256 -	if 1 -	    Xpath 512				" X: 512 -	    asdf -	    Xpath 1024				" X: 1024 -	    asdf | Xpath 2048			" X: 0 -	    Xpath 4096				" X: 4096 -	endif -	Xpath 8192				" X: 8192 -	break -    endwhile | Xpath 16384			" X: 16384 -    Xpath 32768					" X: 32768 - -    return novar		" returns (default return value 0) -    Xpath 65536					" X: 0 -    return 1			" not reached -endfunction - -function! G() abort -    if 1 -	Xpath 131072				" X: 131072 -	while 1 -	    Xpath 262144			" X: 262144 -	    asdf		" returns -1 -	    Xpath 524288			" X: 0 -	    break -	endwhile -	Xpath 1048576				" X: 0 -    endif | Xpath 2097152			" X: 0 -    Xpath Xpath 4194304				" X: 0 - -    return -4			" not reached -endfunction - -function! H() abort -    while 1 -	Xpath 8388608				" X: 8388608 -	if 1 -	    Xpath 16777216			" X: 16777216 -	    asdf		" returns -1 -	    Xpath 33554432			" X: 0 -	endif -	Xpath 67108864				" X: 0 -	break -    endwhile | Xpath 134217728			" X: 0 -    Xpath 268435456				" X: 0 - -    return -4			" not reached -endfunction - -" Aborted functions (G and H) return -1. -let sum = (F() + 1) - 4*G() - 8*H() -Xpath 536870912					" X: 536870912 -if sum != 13 -    Xpath 1073741824				" X: 0 -    Xout "sum is" sum -endif - -unlet sum -delfunction F -delfunction G -delfunction H - -Xcheck 562493431 - - -"------------------------------------------------------------------------------- -" Test 9:   Continuing after aborted functions				    {{{1 -" -"	    When a function with the "abort" attribute is aborted due to an -"	    error, the next function back in the call hierarchy without an -"	    "abort" attribute continues; the value -1 is returned then. -"------------------------------------------------------------------------------- - -XpathINIT - -function! F() abort -    Xpath 1					" X: 1 -    let result = G()	" not aborted -    Xpath 2					" X: 2 -    if result != 2 -	Xpath 4					" X: 0 -    endif -    return 1 -endfunction - -function! G()		" no abort attribute -    Xpath 8					" X: 8 -    if H() != -1	" aborted -	Xpath 16				" X: 0 -    endif -    Xpath 32					" X: 32 -    return 2 -endfunction - -function! H() abort -    Xpath 64					" X: 64 -    call I()		" aborted -    Xpath 128					" X: 0 -    return 4 -endfunction - -function! I() abort -    Xpath 256					" X: 256 -    asdf		" error -    Xpath 512					" X: 0 -    return 8 -endfunction - -if F() != 1 -    Xpath 1024					" X: 0 -endif - -delfunction F -delfunction G -delfunction H -delfunction I - -Xcheck 363 - - -"------------------------------------------------------------------------------- -" Test 10:  :if, :elseif, :while argument parsing			    {{{1 -" -"	    A '"' or '|' in an argument expression must not be mixed up with -"	    a comment or a next command after a bar.  Parsing errors should -"	    be recognized. -"------------------------------------------------------------------------------- - -XpathINIT - -function! MSG(enr, emsg) -    let english = v:lang == "C" || v:lang =~ '^[Ee]n' -    if a:enr == "" -	Xout "TODO: Add message number for:" a:emsg -	let v:errmsg = ":" . v:errmsg -    endif -    let match = 1 -    if v:errmsg !~ '^'.a:enr.':' || (english && v:errmsg !~ a:emsg) -	let match = 0 -	if v:errmsg == "" -	    Xout "Message missing." -	else -	    let v:errmsg = escape(v:errmsg, '"') -	    Xout "Unexpected message:" v:errmsg -	endif -    endif -    return match -endfunction - -if 1 || strlen("\"") | Xpath 1			" X: 1 -    Xpath 2					" X: 2 -endif -Xpath 4						" X: 4 - -if 0 -elseif 1 || strlen("\"") | Xpath 8		" X: 8 -    Xpath 16					" X: 16 -endif -Xpath 32					" X: 32 - -while 1 || strlen("\"") | Xpath 64		" X: 64 -    Xpath 128					" X: 128 -    break -endwhile -Xpath 256					" X: 256 - -let v:errmsg = "" -if 1 ||| strlen("\"") | Xpath 512		" X: 0 -    Xpath 1024					" X: 0 -endif -Xpath 2048					" X: 2048 -if !MSG('E15', "Invalid expression") -    Xpath 4096					" X: 0 -endif - -let v:errmsg = "" -if 0 -elseif 1 ||| strlen("\"") | Xpath 8192		" X: 0 -    Xpath 16384					" X: 0 -endif -Xpath 32768					" X: 32768 -if !MSG('E15', "Invalid expression") -    Xpath 65536					" X: 0 -endif - -let v:errmsg = "" -while 1 ||| strlen("\"") | Xpath 131072		" X: 0 -    Xpath 262144				" X: 0 -    break -endwhile -Xpath 524288					" X: 524288 -if !MSG('E15', "Invalid expression") -    Xpath 1048576				" X: 0 -endif - -delfunction MSG - -Xcheck 559615 - - -"------------------------------------------------------------------------------- -" Test 11:  :if, :elseif, :while argument evaluation after abort	    {{{1 -" -"	    When code is skipped over due to an error, the boolean argument to -"	    an :if, :elseif, or :while must not be evaluated. -"------------------------------------------------------------------------------- - -XpathINIT - -let calls = 0 - -function! P(num) -    let g:calls = g:calls + a:num   " side effect on call -    return 0 -endfunction - -if 1 -    Xpath 1					" X: 1 -    asdf		" error -    Xpath 2					" X: 0 -    if P(1)		" should not be called -	Xpath 4					" X: 0 -    elseif !P(2)	" should not be called -	Xpath 8					" X: 0 -    else -	Xpath 16				" X: 0 -    endif -    Xpath 32					" X: 0 -    while P(4)		" should not be called -	Xpath 64				" X: 0 -    endwhile -    Xpath 128					" X: 0 -endif - -if calls % 2 -    Xpath 256					" X: 0 -endif -if (calls/2) % 2 -    Xpath 512					" X: 0 -endif -if (calls/4) % 2 -    Xpath 1024					" X: 0 -endif -Xpath 2048					" X: 2048 - -unlet calls -delfunction P - -Xcheck 2049 - - -"------------------------------------------------------------------------------- -" Test 12:  Expressions in braces in skipped code			    {{{1 -" -"	    In code skipped over due to an error or inactive conditional, -"	    an expression in braces as part of a variable or function name -"	    should not be evaluated. -"------------------------------------------------------------------------------- - -XpathINIT - -XloopINIT 1 8 - -function! NULL() -    Xloop 1					" X: 0 -    return 0 -endfunction - -function! ZERO() -    Xloop 2					" X: 0 -    return 0 -endfunction - -function! F0() -    Xloop 4					" X: 0 -endfunction - -function! F1(arg) -    Xpath 4096					" X: 0 -endfunction - -let V0 = 1 - -Xpath 8192					" X: 8192 -echo 0 ? F{NULL() + V{ZERO()}}() : 1 -XloopNEXT - -Xpath 16384					" X: 16384 -if 0 -    Xpath 32768					" X: 0 -    call F{NULL() + V{ZERO()}}() -endif -XloopNEXT - -Xpath 65536					" X: 65536 -if 1 -    asdf		" error -    Xpath 131072				" X: 0 -    call F1(F{NULL() + V{ZERO()}}()) -endif -XloopNEXT - -Xpath 262144					" X: 262144 -if 1 -    asdf		" error -    Xpath 524288				" X: 0 -    call F{NULL() + V{ZERO()}}() -endif - -Xcheck 352256 - - -"------------------------------------------------------------------------------- -" Test 13:  Failure in argument evaluation for :while			    {{{1 -" -"	    A failure in the expression evaluation for the condition of a :while -"	    causes the whole :while loop until the matching :endwhile being -"	    ignored.  Continuation is at the next following line. -"------------------------------------------------------------------------------- - -XpathINIT - -Xpath 1						" X: 1 -while asdf -    Xpath 2					" X: 0 -    while 1 -	Xpath 4					" X: 0 -	break -    endwhile -    Xpath 8					" X: 0 -    break -endwhile -Xpath 16					" X: 16 - -while asdf | Xpath 32 | endwhile | Xpath 64	" X: 0 -Xpath 128					" X: 128 - -Xcheck 145 - - -"------------------------------------------------------------------------------- -" Test 14:  Failure in argument evaluation for :if			    {{{1 -" -"	    A failure in the expression evaluation for the condition of an :if -"	    does not cause the corresponding :else or :endif being matched to -"	    a previous :if/:elseif.  Neither of both branches of the failed :if -"	    are executed. -"------------------------------------------------------------------------------- - -XpathINIT -XloopINIT 1 256 - -function! F() -    Xloop 1					" X: 1	    + 256 * 1 -    let x = 0 -    if x		" false -	Xloop 2					" X: 0	    + 256 * 0 -    elseif !x		" always true -	Xloop 4					" X: 4	    + 256 * 4 -	let x = 1 -	if g:boolvar	" possibly undefined -	    Xloop 8				" X: 8	    + 256 * 0 -	else -	    Xloop 16				" X: 0	    + 256 * 0 -	endif -	Xloop 32				" X: 32	    + 256 * 32 -    elseif x		" never executed -	Xloop 64				" X: 0	    + 256 * 0 -    endif -    Xloop 128					" X: 128    + 256 * 128 -endfunction - -let boolvar = 1 -call F() - -XloopNEXT -unlet boolvar -call F() - -delfunction F - -Xcheck 42413 - - -"------------------------------------------------------------------------------- -" Test 15:  Failure in argument evaluation for :if (bar)		    {{{1 -" -"	    Like previous test, except that the failing :if ... | ... | :endif -"	    is in a single line. -"------------------------------------------------------------------------------- - -XpathINIT -XloopINIT 1 256 - -function! F() -    Xloop 1					" X: 1	    + 256 * 1 -    let x = 0 -    if x		" false -	Xloop 2					" X: 0	    + 256 * 0 -    elseif !x		" always true -	Xloop 4					" X: 4	    + 256 * 4 -	let x = 1 -	if g:boolvar | Xloop 8 | else | Xloop 16 | endif    " X: 8 -	Xloop 32				" X: 32	    + 256 * 32 -    elseif x		" never executed -	Xloop 64				" X: 0	    + 256 * 0 -    endif -    Xloop 128					" X: 128    + 256 * 128 -endfunction - -let boolvar = 1 -call F() - -XloopNEXT -unlet boolvar -call F() - -delfunction F - -Xcheck 42413 - +" Tests 1 to 15 were moved to test_viml.vim +let Xtest = 16  "-------------------------------------------------------------------------------  " Test 16:  Double :else or :elseif after :else				    {{{1 @@ -6591,8 +5749,7 @@ function! F()  	    if !caught && !$VIMNOERRTHROW  		Xpath 8192			" X: 0  	    endif -	    if caught ? !MSG('E55', 'Unmatched \\)') -			\ : !MSG('E475', "Invalid argument") +	    if !MSG('E475', "Invalid argument")  		Xpath 16384			" X: 0  	    endif  	    if !caught diff --git a/src/nvim/testdir/test_viml.vim b/src/nvim/testdir/test_viml.vim new file mode 100644 index 0000000000..5d65953a9e --- /dev/null +++ b/src/nvim/testdir/test_viml.vim @@ -0,0 +1,914 @@ +" Test various aspects of the Vim language. +" This was formerly in test49. + +"------------------------------------------------------------------------------- +" Test environment							    {{{1 +"------------------------------------------------------------------------------- + +com!               XpathINIT  let g:Xpath = '' +com! -nargs=1 -bar Xpath      let g:Xpath = g:Xpath . <args> + +" Append a message to the "messages" file +func! Xout(text) +    split messages +    $put =a:text +    wq +endfunc + +com! -nargs=1	     Xout     call Xout(<args>) + +" MakeScript() - Make a script file from a function.			    {{{2 +" +" Create a script that consists of the body of the function a:funcname. +" Replace any ":return" by a ":finish", any argument variable by a global +" variable, and and every ":call" by a ":source" for the next following argument +" in the variable argument list.  This function is useful if similar tests are +" to be made for a ":return" from a function call or a ":finish" in a script +" file. +function! MakeScript(funcname, ...) +    let script = tempname() +    execute "redir! >" . script +    execute "function" a:funcname +    redir END +    execute "edit" script +    " Delete the "function" and the "endfunction" lines.  Do not include the +    " word "function" in the pattern since it might be translated if LANG is +    " set.  When MakeScript() is being debugged, this deletes also the debugging +    " output of its line 3 and 4. +    exec '1,/.*' . a:funcname . '(.*)/d' +    /^\d*\s*endfunction\>/,$d +    %s/^\d*//e +    %s/return/finish/e +    %s/\<a:\(\h\w*\)/g:\1/ge +    normal gg0 +    let cnt = 0 +    while search('\<call\s*\%(\u\|s:\)\w*\s*(.*)', 'W') > 0 +	let cnt = cnt + 1 +	s/\<call\s*\%(\u\|s:\)\w*\s*(.*)/\='source ' . a:{cnt}/ +    endwhile +    g/^\s*$/d +    write +    bwipeout +    return script +endfunction + +" ExecAsScript - Source a temporary script made from a function.	    {{{2 +" +" Make a temporary script file from the function a:funcname, ":source" it, and +" delete it afterwards. +function! ExecAsScript(funcname) +    " Make a script from the function passed as argument. +    let script = MakeScript(a:funcname) + +    " Source and delete the script. +    exec "source" script +    call delete(script) +endfunction + +com! -nargs=1 -bar ExecAsScript call ExecAsScript(<f-args>) + + +"------------------------------------------------------------------------------- +" Test 1:   :endwhile in function					    {{{1 +" +"	    Detect if a broken loop is (incorrectly) reactivated by the +"	    :endwhile.  Use a :return to prevent an endless loop, and make +"	    this test first to get a meaningful result on an error before other +"	    tests will hang. +"------------------------------------------------------------------------------- + +function! T1_F() +    Xpath 'a' +    let first = 1 +    while 1 +	Xpath 'b' +	if first +	    Xpath 'c' +	    let first = 0 +	    break +	else +	    Xpath 'd' +	    return +	endif +    endwhile +endfunction + +function! T1_G() +    Xpath 'h' +    let first = 1 +    while 1 +	Xpath 'i' +	if first +	    Xpath 'j' +	    let first = 0 +	    break +	else +	    Xpath 'k' +	    return +	endif +	if 1	" unmatched :if +    endwhile +endfunction + +func Test_endwhile_function() +  XpathINIT +  call T1_F() +  Xpath 'F' + +  try +    call T1_G() +  catch +    " Catch missing :endif +    call assert_true(v:exception =~ 'E171') +    Xpath 'x' +  endtry +  Xpath 'G' + +  call assert_equal('abcFhijxG', g:Xpath) +endfunc + +"------------------------------------------------------------------------------- +" Test 2:   :endwhile in script						    {{{1 +" +"	    Detect if a broken loop is (incorrectly) reactivated by the +"	    :endwhile.  Use a :finish to prevent an endless loop, and place +"	    this test before others that might hang to get a meaningful result +"	    on an error. +" +"	    This test executes the bodies of the functions T1_F and T1_G from +"	    the previous test as script files (:return replaced by :finish). +"------------------------------------------------------------------------------- + +func Test_endwhile_script() +  XpathINIT +  ExecAsScript T1_F +  Xpath 'F' + +  try +    ExecAsScript T1_G +  catch +    " Catch missing :endif +    call assert_true(v:exception =~ 'E171') +    Xpath 'x' +  endtry +  Xpath 'G' + +  call assert_equal('abcFhijxG', g:Xpath) +endfunc + +"------------------------------------------------------------------------------- +" Test 3:   :if, :elseif, :while, :continue, :break			    {{{1 +"------------------------------------------------------------------------------- + +function Test_if_while() +    XpathINIT +    if 1 +	Xpath 'a' +	let loops = 3 +	while loops > -1	    " main loop: loops == 3, 2, 1 (which breaks) +	    if loops <= 0 +		let break_err = 1 +		let loops = -1 +	    else +		Xpath 'b' . loops +	    endif +	    if (loops == 2) +		while loops == 2 " dummy loop +		    Xpath 'c' . loops +		    let loops = loops - 1 +		    continue    " stop dummy loop +		    Xpath 'd' . loops +		endwhile +		continue	    " continue main loop +		Xpath 'e' . loops +	    elseif (loops == 1) +		let p = 1 +		while p	    " dummy loop +		    Xpath 'f' . loops +		    let p = 0 +		    break	    " break dummy loop +		    Xpath 'g' . loops +		endwhile +		Xpath 'h' . loops +		unlet p +		break	    " break main loop +		Xpath 'i' . loops +	    endif +	    if (loops > 0) +		Xpath 'j' . loops +	    endif +	    while loops == 3    " dummy loop +		let loops = loops - 1 +	    endwhile	    " end dummy loop +	endwhile		    " end main loop +	Xpath 'k' +    else +	Xpath 'l' +    endif +    Xpath 'm' +    if exists("break_err") +	Xpath 'm' +	unlet break_err +    endif + +    unlet loops + +    call assert_equal('ab3j3b2c2b1f1h1km', g:Xpath) +endfunc + +"------------------------------------------------------------------------------- +" Test 4:   :return							    {{{1 +"------------------------------------------------------------------------------- + +function! T4_F() +    if 1 +	Xpath 'a' +	let loops = 3 +	while loops > 0				"    3:  2:     1: +	    Xpath 'b' . loops +	    if (loops == 2) +		Xpath 'c' . loops +		return +		Xpath 'd' . loops +	    endif +	    Xpath 'e' . loops +	    let loops = loops - 1 +	endwhile +	Xpath 'f' +    else +	Xpath 'g' +    endif +endfunction + +function Test_return() +    XpathINIT +    call T4_F() +    Xpath '4' + +    call assert_equal('ab3e3b2c24', g:Xpath) +endfunction + + +"------------------------------------------------------------------------------- +" Test 5:   :finish							    {{{1 +" +"	    This test executes the body of the function T4_F from the previous +"	    test as a script file (:return replaced by :finish). +"------------------------------------------------------------------------------- + +function Test_finish() +    XpathINIT +    ExecAsScript T4_F +    Xpath '5' + +    call assert_equal('ab3e3b2c25', g:Xpath) +endfunction + + + +"------------------------------------------------------------------------------- +" Test 6:   Defining functions in :while loops				    {{{1 +" +"	     Functions can be defined inside other functions.  An inner function +"	     gets defined when the outer function is executed.  Functions may +"	     also be defined inside while loops.  Expressions in braces for +"	     defining the function name are allowed. +" +"	     The functions are defined when sourcing the script, only the +"	     resulting path is checked in the test function. +"------------------------------------------------------------------------------- + +XpathINIT + +" The command CALL collects the argument of all its invocations in "calls" +" when used from a function (that is, when the global variable "calls" needs +" the "g:" prefix).  This is to check that the function code is skipped when +" the function is defined.  For inner functions, do so only if the outer +" function is not being executed. +" +let calls = "" +com! -nargs=1 CALL +    	\ if !exists("calls") && !exists("outer") | +    	\ let g:calls = g:calls . <args> | +    	\ endif + +let i = 0 +while i < 3 +    let i = i + 1 +    if i == 1 +	Xpath 'a' +	function! F1(arg) +	    CALL a:arg +	    let outer = 1 + +	    let j = 0 +	    while j < 1 +		Xpath 'b' +		let j = j + 1 +		function! G1(arg) +		    CALL a:arg +		endfunction +		Xpath 'c' +	    endwhile +	endfunction +	Xpath 'd' + +	continue +    endif + +    Xpath 'e' . i +    function! F{i}(i, arg) +	CALL a:arg +	let outer = 1 + +	if a:i == 3 +	    Xpath 'f' +	endif +	let k = 0 +	while k < 3 +	    Xpath 'g' . k +	    let k = k + 1 +	    function! G{a:i}{k}(arg) +		CALL a:arg +	    endfunction +	    Xpath 'h' . k +	endwhile +    endfunction +    Xpath 'i' + +endwhile + +if exists("*G1") +    Xpath 'j' +endif +if exists("*F1") +    call F1("F1") +    if exists("*G1") +        call G1("G1") +    endif +endif + +if exists("G21") || exists("G22") || exists("G23") +    Xpath 'k' +endif +if exists("*F2") +    call F2(2, "F2") +    if exists("*G21") +        call G21("G21") +    endif +    if exists("*G22") +        call G22("G22") +    endif +    if exists("*G23") +        call G23("G23") +    endif +endif + +if exists("G31") || exists("G32") || exists("G33") +    Xpath 'l' +endif +if exists("*F3") +    call F3(3, "F3") +    if exists("*G31") +        call G31("G31") +    endif +    if exists("*G32") +        call G32("G32") +    endif +    if exists("*G33") +        call G33("G33") +    endif +endif + +Xpath 'm' + +let g:test6_result = g:Xpath +let g:test6_calls = calls + +unlet calls +delfunction F1 +delfunction G1 +delfunction F2 +delfunction G21 +delfunction G22 +delfunction G23 +delfunction G31 +delfunction G32 +delfunction G33 + +function Test_defining_functions() +    call assert_equal('ade2ie3ibcg0h1g1h2g2h3fg0h1g1h2g2h3m', g:test6_result) +    call assert_equal('F1G1F2G21G22G23F3G31G32G33', g:test6_calls) +endfunc + +"------------------------------------------------------------------------------- +" Test 7:   Continuing on errors outside functions			    {{{1 +" +"	    On an error outside a function, the script processing continues +"	    at the line following the outermost :endif or :endwhile.  When not +"	    inside an :if or :while, the script processing continues at the next +"	    line. +"------------------------------------------------------------------------------- + +XpathINIT + +if 1 +    Xpath 'a' +    while 1 +	Xpath 'b' +	asdf +	Xpath 'c' +	break +    endwhile | Xpath 'd' +    Xpath 'e' +endif | Xpath 'f' +Xpath 'g' + +while 1 +    Xpath 'h' +    if 1 +	Xpath 'i' +	asdf +	Xpath 'j' +    endif | Xpath 'k' +    Xpath 'l' +    break +endwhile | Xpath 'm' +Xpath 'n' + +asdf +Xpath 'o' + +asdf | Xpath 'p' +Xpath 'q' + +let g:test7_result = g:Xpath + +func Test_error_in_script() +    call assert_equal('abghinoq', g:test7_result) +endfunc + +"------------------------------------------------------------------------------- +" Test 8:   Aborting and continuing on errors inside functions		    {{{1 +" +"	    On an error inside a function without the "abort" attribute, the +"	    script processing continues at the next line (unless the error was +"	    in a :return command).  On an error inside a function with the +"	    "abort" attribute, the function is aborted and the script processing +"	    continues after the function call; the value -1 is returned then. +"------------------------------------------------------------------------------- + +XpathINIT + +function! T8_F() +    if 1 +	Xpath 'a' +	while 1 +	    Xpath 'b' +	    asdf +	    Xpath 'c' +	    asdf | Xpath 'd' +	    Xpath 'e' +	    break +	endwhile +	Xpath 'f' +    endif | Xpath 'g' +    Xpath 'h' + +    while 1 +	Xpath 'i' +	if 1 +	    Xpath 'j' +	    asdf +	    Xpath 'k' +	    asdf | Xpath 'l' +	    Xpath 'm' +	endif +	Xpath 'n' +	break +    endwhile | Xpath 'o' +    Xpath 'p' + +    return novar		" returns (default return value 0) +    Xpath 'q' +    return 1			" not reached +endfunction + +function! T8_G() abort +    if 1 +	Xpath 'r' +	while 1 +	    Xpath 's' +	    asdf		" returns -1 +	    Xpath 't' +	    break +	endwhile +	Xpath 'v' +    endif | Xpath 'w' +    Xpath 'x' + +    return -4			" not reached +endfunction + +function! T8_H() abort +    while 1 +	Xpath 'A' +	if 1 +	    Xpath 'B' +	    asdf		" returns -1 +	    Xpath 'C' +	endif +	Xpath 'D' +	break +    endwhile | Xpath 'E' +    Xpath 'F' + +    return -4			" not reached +endfunction + +" Aborted functions (T8_G and T8_H) return -1. +let g:test8_sum = (T8_F() + 1) - 4 * T8_G() - 8 * T8_H() +Xpath 'X' +let g:test8_result = g:Xpath + +func Test_error_in_function() +    call assert_equal(13, g:test8_sum) +    call assert_equal('abcefghijkmnoprsABX', g:test8_result) + +    delfunction T8_F +    delfunction T8_G +    delfunction T8_H +endfunc + + +"------------------------------------------------------------------------------- +" Test 9:   Continuing after aborted functions				    {{{1 +" +"	    When a function with the "abort" attribute is aborted due to an +"	    error, the next function back in the call hierarchy without an +"	    "abort" attribute continues; the value -1 is returned then. +"------------------------------------------------------------------------------- + +XpathINIT + +function! F() abort +    Xpath 'a' +    let result = G()	" not aborted +    Xpath 'b' +    if result != 2 +	Xpath 'c' +    endif +    return 1 +endfunction + +function! G()		" no abort attribute +    Xpath 'd' +    if H() != -1	" aborted +	Xpath 'e' +    endif +    Xpath 'f' +    return 2 +endfunction + +function! H() abort +    Xpath 'g' +    call I()		" aborted +    Xpath 'h' +    return 4 +endfunction + +function! I() abort +    Xpath 'i' +    asdf		" error +    Xpath 'j' +    return 8 +endfunction + +if F() != 1 +    Xpath 'k' +endif + +let g:test9_result = g:Xpath + +delfunction F +delfunction G +delfunction H +delfunction I + +func Test_func_abort() +    call assert_equal('adgifb', g:test9_result) +endfunc + + +"------------------------------------------------------------------------------- +" Test 10:  :if, :elseif, :while argument parsing			    {{{1 +" +"	    A '"' or '|' in an argument expression must not be mixed up with +"	    a comment or a next command after a bar.  Parsing errors should +"	    be recognized. +"------------------------------------------------------------------------------- + +XpathINIT + +function! MSG(enr, emsg) +    let english = v:lang == "C" || v:lang =~ '^[Ee]n' +    if a:enr == "" +	Xout "TODO: Add message number for:" a:emsg +	let v:errmsg = ":" . v:errmsg +    endif +    let match = 1 +    if v:errmsg !~ '^'.a:enr.':' || (english && v:errmsg !~ a:emsg) +	let match = 0 +	if v:errmsg == "" +	    Xout "Message missing." +	else +	    let v:errmsg = escape(v:errmsg, '"') +	    Xout "Unexpected message:" v:errmsg +	endif +    endif +    return match +endfunction + +if 1 || strlen("\"") | Xpath 'a' +    Xpath 'b' +endif +Xpath 'c' + +if 0 +elseif 1 || strlen("\"") | Xpath 'd' +    Xpath 'e' +endif +Xpath 'f' + +while 1 || strlen("\"") | Xpath 'g' +    Xpath 'h' +    break +endwhile +Xpath 'i' + +let v:errmsg = "" +if 1 ||| strlen("\"") | Xpath 'j' +    Xpath 'k' +endif +Xpath 'l' +if !MSG('E15', "Invalid expression") +    Xpath 'm' +endif + +let v:errmsg = "" +if 0 +elseif 1 ||| strlen("\"") | Xpath 'n' +    Xpath 'o' +endif +Xpath 'p' +if !MSG('E15', "Invalid expression") +    Xpath 'q' +endif + +let v:errmsg = "" +while 1 ||| strlen("\"") | Xpath 'r' +    Xpath 's' +    break +endwhile +Xpath 't' +if !MSG('E15', "Invalid expression") +    Xpath 'u' +endif + +let g:test10_result = g:Xpath +delfunction MSG + +func Test_expr_parsing() +    call assert_equal('abcdefghilpt', g:test10_result) +endfunc + + +"------------------------------------------------------------------------------- +" Test 11:  :if, :elseif, :while argument evaluation after abort	    {{{1 +" +"	    When code is skipped over due to an error, the boolean argument to +"	    an :if, :elseif, or :while must not be evaluated. +"------------------------------------------------------------------------------- + +XpathINIT + +let calls = 0 + +function! P(num) +    let g:calls = g:calls + a:num   " side effect on call +    return 0 +endfunction + +if 1 +    Xpath 'a' +    asdf		" error +    Xpath 'b' +    if P(1)		" should not be called +	Xpath 'c' +    elseif !P(2)	" should not be called +	Xpath 'd' +    else +	Xpath 'e' +    endif +    Xpath 'f' +    while P(4)		" should not be called +	Xpath 'g' +    endwhile +    Xpath 'h' +endif +Xpath 'x' + +let g:test11_calls = calls +let g:test11_result = g:Xpath + +unlet calls +delfunction P + +func Test_arg_abort() +    call assert_equal(0, g:test11_calls) +    call assert_equal('ax', g:test11_result) +endfunc + + +"------------------------------------------------------------------------------- +" Test 12:  Expressions in braces in skipped code			    {{{1 +" +"	    In code skipped over due to an error or inactive conditional, +"	    an expression in braces as part of a variable or function name +"	    should not be evaluated. +"------------------------------------------------------------------------------- + +XpathINIT + +function! NULL() +    Xpath 'a' +    return 0 +endfunction + +function! ZERO() +    Xpath 'b' +    return 0 +endfunction + +function! F0() +    Xpath 'c' +endfunction + +function! F1(arg) +    Xpath 'e' +endfunction + +let V0 = 1 + +Xpath 'f' +echo 0 ? F{NULL() + V{ZERO()}}() : 1 + +Xpath 'g' +if 0 +    Xpath 'h' +    call F{NULL() + V{ZERO()}}() +endif + +Xpath 'i' +if 1 +    asdf		" error +    Xpath 'j' +    call F1(F{NULL() + V{ZERO()}}()) +endif + +Xpath 'k' +if 1 +    asdf		" error +    Xpath 'l' +    call F{NULL() + V{ZERO()}}() +endif + +let g:test12_result = g:Xpath + +func Test_braces_skipped() +    call assert_equal('fgik', g:test12_result) +endfunc + + +"------------------------------------------------------------------------------- +" Test 13:  Failure in argument evaluation for :while			    {{{1 +" +"	    A failure in the expression evaluation for the condition of a :while +"	    causes the whole :while loop until the matching :endwhile being +"	    ignored.  Continuation is at the next following line. +"------------------------------------------------------------------------------- + +XpathINIT + +Xpath 'a' +while asdf +    Xpath 'b' +    while 1 +	Xpath 'c' +	break +    endwhile +    Xpath 'd' +    break +endwhile +Xpath 'e' + +while asdf | Xpath 'f' | endwhile | Xpath 'g' +Xpath 'h' +let g:test13_result = g:Xpath + +func Test_while_fail() +    call assert_equal('aeh', g:test13_result) +endfunc + + +"------------------------------------------------------------------------------- +" Test 14:  Failure in argument evaluation for :if			    {{{1 +" +"	    A failure in the expression evaluation for the condition of an :if +"	    does not cause the corresponding :else or :endif being matched to +"	    a previous :if/:elseif.  Neither of both branches of the failed :if +"	    are executed. +"------------------------------------------------------------------------------- + +XpathINIT + +function! F() +    Xpath 'a' +    let x = 0 +    if x		" false +	Xpath 'b' +    elseif !x		" always true +	Xpath 'c' +	let x = 1 +	if g:boolvar	" possibly undefined +	    Xpath 'd' +	else +	    Xpath 'e' +	endif +	Xpath 'f' +    elseif x		" never executed +	Xpath 'g' +    endif +    Xpath 'h' +endfunction + +let boolvar = 1 +call F() +Xpath '-' + +unlet boolvar +call F() +let g:test14_result = g:Xpath + +delfunction F + +func Test_if_fail() +    call assert_equal('acdfh-acfh', g:test14_result) +endfunc + + +"------------------------------------------------------------------------------- +" Test 15:  Failure in argument evaluation for :if (bar)		    {{{1 +" +"	    Like previous test, except that the failing :if ... | ... | :endif +"	    is in a single line. +"------------------------------------------------------------------------------- + +XpathINIT + +function! F() +    Xpath 'a' +    let x = 0 +    if x		" false +	Xpath 'b' +    elseif !x		" always true +	Xpath 'c' +	let x = 1 +	if g:boolvar | Xpath 'd' | else | Xpath 'e' | endif +	Xpath 'f' +    elseif x		" never executed +	Xpath 'g' +    endif +    Xpath 'h' +endfunction + +let boolvar = 1 +call F() +Xpath '-' + +unlet boolvar +call F() +let g:test15_result = g:Xpath + +delfunction F + +func Test_if_bar_fail() +    call assert_equal('acdfh-acfh', g:test15_result) +endfunc + + +"------------------------------------------------------------------------------- +" Modelines								    {{{1 +" vim: ts=8 sw=4 tw=80 fdm=marker +" vim: fdt=substitute(substitute(foldtext(),\ '\\%(^+--\\)\\@<=\\(\\s*\\)\\(.\\{-}\\)\:\ \\%(\"\ \\)\\=\\(Test\ \\d*\\)\:\\s*',\ '\\3\ (\\2)\:\ \\1',\ \"\"),\ '\\(Test\\s*\\)\\(\\d\\)\\D\\@=',\ '\\1\ \\2',\ "") +"------------------------------------------------------------------------------- diff --git a/src/nvim/version.c b/src/nvim/version.c index ebf21d162f..e0d5e984cd 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -78,7 +78,73 @@ static int included_patches[] = {    1511,    1425,    1366, - +  1304, +  1292, +  1284, +  // 1283 +  1282, +  // 1281 +  // 1280 +  // 1279 NA +  // 1278 NA +  // 1277 +  // 1276 +  // 1275 +  // 1274 +  // 1273 +  // 1272 +  1271, +  // 1270 +  1269, +  // 1268 +  1267, +  // 1266 +  // 1265 +  // 1264 +  // 1263 +  // 1262 +  // 1261 +  // 1260 +  // 1259 +  // 1258 +  // 1257 +  // 1256 +  // 1255 +  // 1254 +  // 1253 +  // 1252 +  // 1251 +  // 1250 +  // 1249 +  // 1248 +  // 1247 +  // 1246 +  // 1245 +  // 1244 +  // 1243 NA +  // 1242 +  // 1241 +  // 1240 +  // 1239 +  // 1238 +  // 1237 +  // 1236 +  // 1235 +  // 1234 +  // 1233 +  // 1232 +  // 1231 NA +  // 1230 +  // 1229 +  1228, +  // 1227 +  // 1226 +  // 1225 +  // 1224 +  // 1223 +  // 1223 +  // 1221 +  // 1220    // 1219 NA    // 1218 NA    // 1217 NA @@ -117,32 +183,32 @@ static int included_patches[] = {    // 1184 NA    // 1183 NA    // 1182 NA -  // 1181, +  1181,    1180,    // 1179, -  // 1178, +  1178,    // 1177 NA    // 1176 NA    // 1175 NA    // 1174 NA -  // 1173, +  1173,    // 1172 NA    // 1171 NA    // 1170 NA    // 1169 NA -  // 1168, -  // 1167, -  // 1166, +  1168, +  1167, +  1166,    // 1165 NA -  // 1164, -  // 1163, +  1164, +  1163,    // 1162 NA    // 1161, -  // 1160, +  1160,    // 1159 NA    // 1158 NA -  // 1157, -  // 1156, +  1157, +  // 1156 NA    // 1155 NA    // 1154,    // 1153, @@ -268,7 +334,7 @@ static int included_patches[] = {    // 1033 NA,    1032,    // 1031 NA, -  // 1030, +  1030,    1029,    // 1028 NA,    1027, @@ -297,8 +363,8 @@ static int included_patches[] = {    // 1004 NA,    // 1003 NA,    // 1002 NA, -  // 1001, -  // 1000, +  1001, +  1000,    // 999 NA    // 998,    // 997 NA diff --git a/src/nvim/vim.h b/src/nvim/vim.h index 545b903d2f..623ea19e36 100644 --- a/src/nvim/vim.h +++ b/src/nvim/vim.h @@ -271,26 +271,11 @@ enum {  # define vim_strpbrk(s, cs) (char_u *)strpbrk((char *)(s), (char *)(cs)) -#define MSG(s)                      msg((char_u *)(s)) -#define MSG_ATTR(s, attr)           msg_attr((char_u *)(s), (attr)) -#define EMSG(s)                     emsg((char_u *)(s)) -#define EMSG2(s, p)                 emsg2((char_u *)(s), (char_u *)(p)) -#define EMSG3(s, p, q)              emsg3((char_u *)(s), (char_u *)(p), \ -    (char_u *)(q)) -#define EMSGN(s, n)                 emsgn((char_u *)(s), (int64_t)(n)) -#define EMSGU(s, n)                 emsgu((char_u *)(s), (uint64_t)(n)) -#define OUT_STR(s)                  out_str((char_u *)(s)) -#define OUT_STR_NF(s)               out_str_nf((char_u *)(s)) -#define MSG_PUTS(s)                 msg_puts((char_u *)(s)) -#define MSG_PUTS_ATTR(s, a)         msg_puts_attr((char_u *)(s), (a)) -#define MSG_PUTS_TITLE(s)           msg_puts_title((char_u *)(s)) -#define MSG_PUTS_LONG(s)            msg_puts_long_attr((char_u *)(s), 0) -#define MSG_PUTS_LONG_ATTR(s, a)    msg_puts_long_attr((char_u *)(s), (a)) - -/* Prefer using emsg3(), because perror() may send the output to the wrong - * destination and mess up the screen. */ -#define PERROR(msg) \ -  (void) emsg3((char_u *) "%s: %s", (char_u *)msg, (char_u *)strerror(errno)) +#include "nvim/message.h" + +// Prefer using emsgf(), because perror() may send the output to the wrong +// destination and mess up the screen. +#define PERROR(msg) (void) emsgf("%s: %s", msg, strerror(errno))  #define SHOWCMD_COLS 10                 /* columns needed by shown command */  #define STL_MAX_ITEM 80                 /* max nr of %<flag> in statusline */ | 
