patch 9.0.0370: cleaning up afterwards can make a function messy
Problem: Cleaning up afterwards can make a function messy.
Solution: Add the :defer command.
diff --git a/src/vim9execute.c b/src/vim9execute.c
index a1311ee..3e0a377 100644
--- a/src/vim9execute.c
+++ b/src/vim9execute.c
@@ -101,9 +101,15 @@
static garray_T profile_info_ga = {0, 0, sizeof(profinfo_T), 20, NULL};
#endif
+// Get pointer to item in the stack.
+#define STACK_TV(idx) (((typval_T *)ectx->ec_stack.ga_data) + idx)
+
// Get pointer to item relative to the bottom of the stack, -1 is the last one.
#define STACK_TV_BOT(idx) (((typval_T *)ectx->ec_stack.ga_data) + ectx->ec_stack.ga_len + (idx))
+// Get pointer to a local variable on the stack. Negative for arguments.
+#define STACK_TV_VAR(idx) (((typval_T *)ectx->ec_stack.ga_data) + ectx->ec_frame_idx + STACK_FRAME_SIZE + idx)
+
void
to_string_error(vartype_T vartype)
{
@@ -610,9 +616,6 @@
return OK;
}
-// Get pointer to item in the stack.
-#define STACK_TV(idx) (((typval_T *)ectx->ec_stack.ga_data) + idx)
-
// Double linked list of funcstack_T in use.
static funcstack_T *first_funcstack = NULL;
@@ -843,6 +846,89 @@
}
/*
+ * Handle ISN_DEFER. Stack has a function reference and "argcount" arguments.
+ * The local variable that lists deferred functions is "var_idx".
+ * Returns OK or FAIL.
+ */
+ static int
+add_defer_func(int var_idx, int argcount, ectx_T *ectx)
+{
+ typval_T *defer_tv = STACK_TV_VAR(var_idx);
+ list_T *defer_l;
+ typval_T *func_tv;
+ list_T *l;
+ int i;
+ typval_T listval;
+
+ if (defer_tv->v_type != VAR_LIST)
+ {
+ // first one, allocate the list
+ if (rettv_list_alloc(defer_tv) == FAIL)
+ return FAIL;
+ }
+ defer_l = defer_tv->vval.v_list;
+
+ l = list_alloc_with_items(argcount + 1);
+ if (l == NULL)
+ return FAIL;
+ listval.v_type = VAR_LIST;
+ listval.vval.v_list = l;
+ listval.v_lock = 0;
+ if (list_insert_tv(defer_l, &listval,
+ defer_l == NULL ? NULL : defer_l->lv_first) == FAIL)
+ {
+ vim_free(l);
+ return FAIL;
+ }
+
+ func_tv = STACK_TV_BOT(-argcount - 1);
+ // TODO: check type is a funcref
+ list_set_item(l, 0, func_tv);
+
+ for (i = 1; i <= argcount; ++i)
+ list_set_item(l, i, STACK_TV_BOT(-argcount + i - 1));
+ ectx->ec_stack.ga_len -= argcount + 1;
+ return OK;
+}
+
+/*
+ * Invoked when returning from a function: Invoke any deferred calls.
+ */
+ static void
+invoke_defer_funcs(ectx_T *ectx)
+{
+ dfunc_T *dfunc = ((dfunc_T *)def_functions.ga_data)
+ + ectx->ec_dfunc_idx;
+ typval_T *defer_tv = STACK_TV_VAR(dfunc->df_defer_var_idx - 1);
+ listitem_T *li;
+
+ if (defer_tv->v_type != VAR_LIST)
+ return; // no function added
+ for (li = defer_tv->vval.v_list->lv_first; li != NULL; li = li->li_next)
+ {
+ list_T *l = li->li_tv.vval.v_list;
+ typval_T rettv;
+ typval_T argvars[MAX_FUNC_ARGS];
+ int i;
+ listitem_T *arg_li = l->lv_first;
+ funcexe_T funcexe;
+
+ for (i = 0; i < l->lv_len - 1; ++i)
+ {
+ arg_li = arg_li->li_next;
+ argvars[i] = arg_li->li_tv;
+ }
+
+ CLEAR_FIELD(funcexe);
+ funcexe.fe_evaluate = TRUE;
+ rettv.v_type = VAR_UNKNOWN;
+ (void)call_func(l->lv_first->li_tv.vval.v_string, -1,
+ &rettv, l->lv_len - 1, argvars, &funcexe);
+ clear_tv(&rettv);
+ }
+}
+
+/*
* Return from the current function.
*/
static int
@@ -876,6 +962,9 @@
}
#endif
+ if (dfunc->df_defer_var_idx > 0)
+ invoke_defer_funcs(ectx);
+
// No check for uf_refcount being zero, cannot think of a way that would
// happen.
--dfunc->df_ufunc->uf_calls;
@@ -949,8 +1038,6 @@
return OK;
}
-#undef STACK_TV
-
/*
* Prepare arguments and rettv for calling a builtin or user function.
*/
@@ -1732,16 +1819,6 @@
int subs_status;
} subs_expr_T;
-// Get pointer to item in the stack.
-#define STACK_TV(idx) (((typval_T *)ectx->ec_stack.ga_data) + idx)
-
-// Get pointer to item at the bottom of the stack, -1 is the bottom.
-#undef STACK_TV_BOT
-#define STACK_TV_BOT(idx) (((typval_T *)ectx->ec_stack.ga_data) + ectx->ec_stack.ga_len + idx)
-
-// Get pointer to a local variable on the stack. Negative for arguments.
-#define STACK_TV_VAR(idx) (((typval_T *)ectx->ec_stack.ga_data) + ectx->ec_frame_idx + STACK_FRAME_SIZE + idx)
-
// Set when calling do_debug().
static ectx_T *debug_context = NULL;
static int debug_var_count;
@@ -3670,6 +3747,13 @@
}
break;
+ // :defer func(arg)
+ case ISN_DEFER:
+ if (add_defer_func(iptr->isn_arg.defer.defer_var_idx,
+ iptr->isn_arg.defer.defer_argcount, ectx) == FAIL)
+ goto on_error;
+ break;
+
// return from a :def function call without a value
case ISN_RETURN_VOID:
if (GA_GROW_FAILS(&ectx->ec_stack, 1))
@@ -5024,6 +5108,14 @@
done:
ret = OK;
theend:
+ {
+ dfunc_T *dfunc = ((dfunc_T *)def_functions.ga_data)
+ + ectx->ec_dfunc_idx;
+
+ if (dfunc->df_defer_var_idx > 0)
+ invoke_defer_funcs(ectx);
+ }
+
dict_stack_clear(dict_stack_len_at_start);
ectx->ec_trylevel_at_start = save_trylevel_at_start;
return ret;
@@ -5903,6 +5995,10 @@
case ISN_PCALL_END:
smsg("%s%4d PCALL end", pfx, current);
break;
+ case ISN_DEFER:
+ smsg("%s%4d DEFER %d args", pfx, current,
+ (int)iptr->isn_arg.defer.defer_argcount);
+ break;
case ISN_RETURN:
smsg("%s%4d RETURN", pfx, current);
break;