This is an idea to play with.

This variant of the extend() patch implements a fourth, optional
parameter to extend().  With the parameter the error handling method can
be selected: "stop" on error (keep partial changes), "cont"inue after
error or "rollback" changes and return on error.

Documentation is included, and the existing tests have slightly been
updated to test the error handling.  But tests are not complete.

Even extend(list, list2, where, error_action) works: error_action is
ignored, as list extension does not encounter errors (except for "out of
memory").

Out of memory error handling is unchanged: for dictionaries, the
extension always continues, and list extension always stops on the first
error.

For this reason, "rollback" is really the wrong name for that error
handling mode, as it never needs to roll back changes: all error checks
are completed before any changes are made.

But "rollback" implements all-or-nothing behavior (except when
encountering OOM), and it was the best name I could come up with to
convey that idea.

Please comment.


diff -r 18d84ed365a5 runtime/doc/eval.txt
--- a/runtime/doc/eval.txt      Wed Apr 22 22:18:22 2015 +0200
+++ b/runtime/doc/eval.txt      Sat May 02 13:58:02 2015 +0200
@@ -2929,7 +2929,7 @@ expand({expr} [, {nosuf} [, {list}]])                     
                See |glob()| for finding existing files.  See |system()| for
                getting the raw output of an external command.
 
-extend({expr1}, {expr2} [, {expr3}])                   *extend()*
+extend({expr1}, {expr2} [, {expr3} [, {expr4}]])       *extend()*
                {expr1} and {expr2} must be both |Lists| or both
                |Dictionaries|.
 
@@ -2958,6 +2958,12 @@ extend({expr1}, {expr2} [, {expr3}])                     
*
                {expr3} = "error": give an error message                *E737*
                When {expr3} is omitted then "force" is assumed.
 
+               When errors occur, {expr4} is used to decide what to do:
+               {expr4} = "stop": keep partial changes and return
+               {expr4} = "cont": keep partial changes and continue
+               {expr4} = "rollback": roll back partial changes and return
+               When {expr4} is omitted then "stop" is assumed.
+
                {expr1} is changed when {expr2} is not empty.  If necessary
                make a copy of {expr1} first.
                {expr2} remains unchanged.
diff -r 18d84ed365a5 src/eval.c
--- a/src/eval.c        Wed Apr 22 22:18:22 2015 +0200
+++ b/src/eval.c        Sat May 02 13:58:02 2015 +0200
@@ -8108,7 +8108,7 @@ static struct fst
     {"exp",            1, 1, f_exp},
 #endif
     {"expand",         1, 3, f_expand},
-    {"extend",         2, 3, f_extend},
+    {"extend",         2, 4, f_extend},
     {"feedkeys",       1, 2, f_feedkeys},
     {"file_readable",  1, 1, f_filereadable},  /* obsolete */
     {"filereadable",   1, 1, f_filereadable},
@@ -10490,21 +10490,107 @@ f_expand(argvars, rettv)
  * When "action" is "error" then a duplicate key is an error.
  * When "action" is "force" then a duplicate key is overwritten.
  * Otherwise duplicate keys are ignored ("action" is "keep").
- */
-    void
-dict_extend(d1, d2, action)
+ * When "err_act" is "cont" then continue after errors.
+ * When "err_act" is "rollback" then rollback changes and return after an
+ * error.
+ * Otherwise return on the first error ("err_act" is "stop").
+ */
+    void
+dict_extend(d1, d2, action, err_act)
     dict_T     *d1;
     dict_T     *d2;
     char_u     *action;
+    char_u     *err_act;
 {
     dictitem_T *di1;
     hashitem_T *hi2;
-    int                todo;
+    int                todo, err;
     char_u     *arg_errmsg = (char_u *)N_("extend() argument");
 
+    /*
+     * Rollback on error (err_act is "rollback").
+     */
+    if (*err_act == 'r')
+    {
+       /*
+        * Check whether the operation can complete without errors (other than
+        * OOM).
+        */
+       todo = (int)d2->dv_hashtab.ht_used;
+       for (hi2 = d2->dv_hashtab.ht_array; todo > 0; ++hi2)
+       {
+           if (!HASHITEM_EMPTY(hi2))
+           {
+               --todo;
+               di1 = dict_find(d1, hi2->hi_key, -1);
+               if (d1->dv_scope != 0)
+               {
+                   /* Disallow replacing a builtin function in l: and g:.
+                    * Check the key to be valid when adding to any
+                    * scope. */
+                   if (d1->dv_scope == VAR_DEF_SCOPE
+                           && HI2DI(hi2)->di_tv.v_type == VAR_FUNC
+                           && var_check_func_name(hi2->hi_key,
+                                                            di1 == NULL))
+                       return;
+                   if (!valid_varname(hi2->hi_key))
+                       return;
+               }
+               if (di1 == NULL)
+               {
+                   if (tv_check_lock(d1->dv_lock, arg_errmsg, TRUE))
+                       return;
+               }
+               else if (*action == 'e')
+               {
+                   EMSG2(_("E737: Key already exists: %s"), hi2->hi_key);
+                   return;
+               }
+               else if (*action == 'f' && HI2DI(hi2) != di1
+                       && (tv_check_lock(di1->di_tv.v_lock, arg_errmsg, TRUE)
+                           || var_check_ro(di1->di_flags, arg_errmsg, TRUE)))
+                       return;
+           }
+       }
+
+       /*
+        * Replace or add dict items.
+        */
+       todo = (int)d2->dv_hashtab.ht_used;
+       for (hi2 = d2->dv_hashtab.ht_array; todo > 0; ++hi2)
+       {
+           if (!HASHITEM_EMPTY(hi2))
+           {
+               --todo;
+               di1 = dict_find(d1, hi2->hi_key, -1);
+               if (di1 == NULL)
+               {
+                   di1 = dictitem_copy(HI2DI(hi2));
+                   if (di1 != NULL && dict_add(d1, di1) == FAIL)
+                       dictitem_free(di1);
+               }
+               else if (*action == 'f' && HI2DI(hi2) != di1)
+               {
+                   clear_tv(&di1->di_tv);
+                   copy_tv(&HI2DI(hi2)->di_tv, &di1->di_tv);
+               }
+           }
+       }
+
+       return;
+    }
+
+    /*
+     * Return or continue on error, keeping partial changes (err_act is "stop"
+     * or "cont").
+     */
     todo = (int)d2->dv_hashtab.ht_used;
+    err = FALSE;
     for (hi2 = d2->dv_hashtab.ht_array; todo > 0; ++hi2)
     {
+       if (err && *err_act == 's')
+           break;
+       err = TRUE;
        if (!HASHITEM_EMPTY(hi2))
        {
            --todo;
@@ -10518,12 +10604,14 @@ dict_extend(d1, d2, action)
                        && HI2DI(hi2)->di_tv.v_type == VAR_FUNC
                        && var_check_func_name(hi2->hi_key,
                                                         di1 == NULL))
-                   break;
+                   continue;
                if (!valid_varname(hi2->hi_key))
-                   break;
+                   continue;
            }
            if (di1 == NULL)
            {
+               if (tv_check_lock(d1->dv_lock, arg_errmsg, TRUE))
+                   continue;
                di1 = dictitem_copy(HI2DI(hi2));
                if (di1 != NULL && dict_add(d1, di1) == FAIL)
                    dictitem_free(di1);
@@ -10531,23 +10619,24 @@ dict_extend(d1, d2, action)
            else if (*action == 'e')
            {
                EMSG2(_("E737: Key already exists: %s"), hi2->hi_key);
-               break;
+               continue;
            }
            else if (*action == 'f' && HI2DI(hi2) != di1)
            {
                if (tv_check_lock(di1->di_tv.v_lock, arg_errmsg, TRUE)
                      || var_check_ro(di1->di_flags, arg_errmsg, TRUE))
-                   break;
+                   continue;
                clear_tv(&di1->di_tv);
                copy_tv(&HI2DI(hi2)->di_tv, &di1->di_tv);
            }
        }
-    }
-}
-
-/*
- * "extend(list, list [, idx])" function
- * "extend(dict, dict [, action])" function
+       err = FALSE;
+    }
+}
+
+/*
+ * "extend(list, list [, idx [, err_action]])" function
+ * "extend(dict, dict [, action [, err_action]])" function
  */
     static void
 f_extend(argvars, rettv)
@@ -10596,13 +10685,12 @@ f_extend(argvars, rettv)
     else if (argvars[0].v_type == VAR_DICT && argvars[1].v_type == VAR_DICT)
     {
        dict_T  *d1, *d2;
-       char_u  *action;
+       char_u  *action, *err_act;
        int     i;
 
        d1 = argvars[0].vval.v_dict;
        d2 = argvars[1].vval.v_dict;
-       if (d1 != NULL && !tv_check_lock(d1->dv_lock, arg_errmsg, TRUE)
-               && d2 != NULL)
+       if (d1 != NULL && d2 != NULL)
        {
            /* Check the third argument. */
            if (argvars[2].v_type != VAR_UNKNOWN)
@@ -10624,7 +10712,28 @@ f_extend(argvars, rettv)
            else
                action = (char_u *)"force";
 
-           dict_extend(d1, d2, action);
+           /* Check the fourth argument. */
+           if (argvars[2].v_type != VAR_UNKNOWN
+                   && argvars[3].v_type != VAR_UNKNOWN)
+           {
+               static char *(av[]) = {"stop", "cont", "rollback"};
+
+               err_act = get_tv_string_chk(&argvars[3]);
+               if (err_act == NULL)
+                   return;             /* type error; errmsg already given */
+               for (i = 0; i < 3; ++i)
+                   if (STRCMP(err_act, av[i]) == 0)
+                       break;
+               if (i == 3)
+               {
+                   EMSG2(_(e_invarg2), err_act);
+                   return;
+               }
+           }
+           else
+               err_act = (char_u *)"stop";
+
+           dict_extend(d1, d2, action, err_act);
 
            copy_tv(&argvars[0], rettv);
        }
diff -r 18d84ed365a5 src/if_py_both.h
--- a/src/if_py_both.h  Wed Apr 22 22:18:22 2015 +0200
+++ b/src/if_py_both.h  Sat May 02 13:58:02 2015 +0200
@@ -1915,7 +1915,7 @@ DictionaryUpdate(DictionaryObject *self,
            return NULL;
 
        VimTryStart();
-       dict_extend(self->dict, tv.vval.v_dict, (char_u *) "force");
+       dict_extend(self->dict, tv.vval.v_dict, (char_u *) "force", "rollback");
        clear_tv(&tv);
        if (VimTryEnd())
            return NULL;
diff -r 18d84ed365a5 src/proto/eval.pro
--- a/src/proto/eval.pro        Wed Apr 22 22:18:22 2015 +0200
+++ b/src/proto/eval.pro        Sat May 02 13:58:02 2015 +0200
@@ -79,7 +79,7 @@ long get_dict_number __ARGS((dict_T *d, 
 char_u *get_function_name __ARGS((expand_T *xp, int idx));
 char_u *get_expr_name __ARGS((expand_T *xp, int idx));
 int func_call __ARGS((char_u *name, typval_T *args, dict_T *selfdict, typval_T 
*rettv));
-void dict_extend __ARGS((dict_T *d1, dict_T *d2, char_u *action));
+void dict_extend __ARGS((dict_T *d1, dict_T *d2, char_u *action, char_u 
*err_act));
 void mzscheme_call_vim __ARGS((char_u *name, typval_T *args, typval_T *rettv));
 float_T vim_round __ARGS((float_T f));
 long do_searchpair __ARGS((char_u *spat, char_u *mpat, char_u *epat, int dir, 
char_u *skip, int flags, pos_T *match_pos, linenr_T lnum_stop, long 
time_limit));
diff -r 18d84ed365a5 src/testdir/test55.in
--- a/src/testdir/test55.in     Wed Apr 22 22:18:22 2015 +0200
+++ b/src/testdir/test55.in     Sat May 02 13:58:02 2015 +0200
@@ -408,6 +408,48 @@ let l = [0, 1, 2, 3]
 :endtry
 :$put =string(d)
 :"
+:$put ='extend() after lock on dict:'
+:unlet! d
+:let d = {'a': 99, 'b': 100, 'd': 101}
+:lockvar 1 d
+:try
+:  $put =string(extend(d, {'a': 123}))
+:  $put ='ok: did extend() existing item'
+:  $put =string(extend(d, {'x': 'new'}))
+:  $put ='wrong: did extend() with new item'
+:catch
+:  $put =v:exception[:14]
+:endtry
+:$put =string(d)
+:unlet! d2
+:let d2 = {'a': 789, 'b': 42, 'c': 'new', 'd': 52}
+:$put ='key order ok for all-or-nothing test: '.(keys(d2)[0] != 'c')
+:try
+:  $put =string(extend(d, d2, 'force', 'rollback'))
+:  $put ='wrong: did extend() with mixed-in new item on "rollback"'
+:catch
+:  $put =v:exception[:14]
+:endtry
+:$put =string(d)
+:try
+:  $put =string(extend(d, d2, 'force', 'stop'))
+:  $put ='wrong: did extend() without error with mixed-in new item on "stop"'
+:catch
+:  $put =v:exception[:14]
+:endtry
+:$put =string(d)
+:unlet! d
+:let d = {'a': 99, 'b': 100, 'd': 101}
+:lockvar 1 d
+:try
+:  $put =string(extend(d, d2, 'force', 'cont'))
+:  $put ='wrong: did extend() without error with mixed-in new item on "cont"'
+:catch
+:  $put =v:exception[:14]
+:endtry
+:$put =string(d)
+:unlet d d2
+:"
 :$put ='No remove() of write-protected scope-level variable:'
 :fun! Tfunc(this_is_a_loooooooooong_parameter_name)
 :  try
diff -r 18d84ed365a5 src/testdir/test55.ok
--- a/src/testdir/test55.ok     Wed Apr 22 22:18:22 2015 +0200
+++ b/src/testdir/test55.ok     Sat May 02 13:58:02 2015 +0200
@@ -138,6 +138,18 @@ did map()
 No extend() after lock on dict item:
 Vim(put):E741: 
 {'a': 99, 'b': 100}
+extend() after lock on dict:
+{'a': 123, 'b': 100, 'd': 101}
+ok: did extend() existing item
+Vim(put):E741: 
+{'a': 123, 'b': 100, 'd': 101}
+key order ok for all-or-nothing test: 1
+Vim(put):E741: 
+{'a': 123, 'b': 100, 'd': 101}
+Vim(put):E741: 
+{'a': 789, 'b': 42, 'd': 101}
+Vim(put):E741: 
+{'a': 789, 'b': 42, 'd': 52}
 No remove() of write-protected scope-level variable:
 Vim(put):E795: 
 No extend() of write-protected scope-level variable:

-- 
Olaf Dabrunz (oda <at> fctrace.org)

-- 
-- 
You received this message from the "vim_dev" maillist.
Do not top-post! Type your reply below the text you are replying to.
For more information, visit http://www.vim.org/maillist.php

--- 
You received this message because you are subscribed to the Google Groups 
"vim_dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
For more options, visit https://groups.google.com/d/optout.

Raspunde prin e-mail lui