changeset 13262:69278c25429d v8.0.1505

patch 8.0.1505: debugger can't break on a condition commit https://github.com/vim/vim/commit/c6f9f739d32084923c3031cbf6f581f8c8bf7fd2 Author: Bram Moolenaar <Bram@vim.org> Date: Sun Feb 11 19:06:26 2018 +0100 patch 8.0.1505: debugger can't break on a condition Problem: Debugger can't break on a condition. (Charles Campbell) Solution: Add ":breakadd expr". (Christian Brabandt, closes https://github.com/vim/vim/issues/859)
author Christian Brabandt <cb@256bit.org>
date Sun, 11 Feb 2018 19:15:05 +0100
parents fa53b212be26
children 789c99fa5bbf
files runtime/doc/repeat.txt src/eval.c src/evalfunc.c src/ex_cmds2.c src/ex_docmd.c src/proto/eval.pro src/proto/ex_cmds2.pro src/structs.h src/userfunc.c src/version.c
diffstat 10 files changed, 460 insertions(+), 261 deletions(-) [+]
line wrap: on
line diff
--- a/runtime/doc/repeat.txt
+++ b/runtime/doc/repeat.txt
@@ -806,6 +806,19 @@ DEFINING BREAKPOINTS
 <		Note that this only works for commands that are executed when
 		sourcing the file, not for a function defined in that file.
 
+:breaka[dd] expr {expression}
+		Sets a breakpoint, that will break whenever the {expression}
+		evaluates to a different value. Example: >
+			:breakadd expr g:lnum
+
+<		Will break, whenever the global variable lnum changes.
+		Note if you watch a |script-variable| this will break
+		when switching scripts, since the script variable is only
+		valid in the script where it has been defined and if that
+		script is called from several other scripts, this will stop
+		whenever that particular variable will become visible or
+		unaccessible again.
+
 The [lnum] is the line number of the breakpoint.  Vim will stop at or after
 this line.  When omitted line 1 is used.
 
--- a/src/eval.c
+++ b/src/eval.c
@@ -3237,22 +3237,6 @@ pattern_match(char_u *pat, char_u *text,
 }
 
 /*
- * types for expressions.
- */
-typedef enum
-{
-    TYPE_UNKNOWN = 0
-    , TYPE_EQUAL	/* == */
-    , TYPE_NEQUAL	/* != */
-    , TYPE_GREATER	/* >  */
-    , TYPE_GEQUAL	/* >= */
-    , TYPE_SMALLER	/* <  */
-    , TYPE_SEQUAL	/* <= */
-    , TYPE_MATCH	/* =~ */
-    , TYPE_NOMATCH	/* !~ */
-} exptype_T;
-
-/*
  * The "evaluate" argument: When FALSE, the argument is only parsed but not
  * executed.  The function may return OK, but the rettv will be of type
  * VAR_UNKNOWN.  The function still returns FAIL for a syntax error.
@@ -3531,9 +3515,6 @@ eval4(char_u **arg, typval_T *rettv, int
     exptype_T	type = TYPE_UNKNOWN;
     int		type_is = FALSE;    /* TRUE for "is" and "isnot" */
     int		len = 2;
-    varnumber_T	n1, n2;
-    char_u	*s1, *s2;
-    char_u	buf1[NUMBUFLEN], buf2[NUMBUFLEN];
     int		ic;
 
     /*
@@ -3615,198 +3596,7 @@ eval4(char_u **arg, typval_T *rettv, int
 	    clear_tv(rettv);
 	    return FAIL;
 	}
-
-	if (evaluate)
-	{
-	    if (type_is && rettv->v_type != var2.v_type)
-	    {
-		/* For "is" a different type always means FALSE, for "notis"
-		 * it means TRUE. */
-		n1 = (type == TYPE_NEQUAL);
-	    }
-	    else if (rettv->v_type == VAR_LIST || var2.v_type == VAR_LIST)
-	    {
-		if (type_is)
-		{
-		    n1 = (rettv->v_type == var2.v_type
-				   && rettv->vval.v_list == var2.vval.v_list);
-		    if (type == TYPE_NEQUAL)
-			n1 = !n1;
-		}
-		else if (rettv->v_type != var2.v_type
-			|| (type != TYPE_EQUAL && type != TYPE_NEQUAL))
-		{
-		    if (rettv->v_type != var2.v_type)
-			EMSG(_("E691: Can only compare List with List"));
-		    else
-			EMSG(_("E692: Invalid operation for List"));
-		    clear_tv(rettv);
-		    clear_tv(&var2);
-		    return FAIL;
-		}
-		else
-		{
-		    /* Compare two Lists for being equal or unequal. */
-		    n1 = list_equal(rettv->vval.v_list, var2.vval.v_list,
-								   ic, FALSE);
-		    if (type == TYPE_NEQUAL)
-			n1 = !n1;
-		}
-	    }
-
-	    else if (rettv->v_type == VAR_DICT || var2.v_type == VAR_DICT)
-	    {
-		if (type_is)
-		{
-		    n1 = (rettv->v_type == var2.v_type
-				   && rettv->vval.v_dict == var2.vval.v_dict);
-		    if (type == TYPE_NEQUAL)
-			n1 = !n1;
-		}
-		else if (rettv->v_type != var2.v_type
-			|| (type != TYPE_EQUAL && type != TYPE_NEQUAL))
-		{
-		    if (rettv->v_type != var2.v_type)
-			EMSG(_("E735: Can only compare Dictionary with Dictionary"));
-		    else
-			EMSG(_("E736: Invalid operation for Dictionary"));
-		    clear_tv(rettv);
-		    clear_tv(&var2);
-		    return FAIL;
-		}
-		else
-		{
-		    /* Compare two Dictionaries for being equal or unequal. */
-		    n1 = dict_equal(rettv->vval.v_dict, var2.vval.v_dict,
-								   ic, FALSE);
-		    if (type == TYPE_NEQUAL)
-			n1 = !n1;
-		}
-	    }
-
-	    else if (rettv->v_type == VAR_FUNC || var2.v_type == VAR_FUNC
-		|| rettv->v_type == VAR_PARTIAL || var2.v_type == VAR_PARTIAL)
-	    {
-		if (type != TYPE_EQUAL && type != TYPE_NEQUAL)
-		{
-		    EMSG(_("E694: Invalid operation for Funcrefs"));
-		    clear_tv(rettv);
-		    clear_tv(&var2);
-		    return FAIL;
-		}
-		if ((rettv->v_type == VAR_PARTIAL
-					     && rettv->vval.v_partial == NULL)
-			|| (var2.v_type == VAR_PARTIAL
-					      && var2.vval.v_partial == NULL))
-		    /* when a partial is NULL assume not equal */
-		    n1 = FALSE;
-		else if (type_is)
-		{
-		    if (rettv->v_type == VAR_FUNC && var2.v_type == VAR_FUNC)
-			/* strings are considered the same if their value is
-			 * the same */
-			n1 = tv_equal(rettv, &var2, ic, FALSE);
-		    else if (rettv->v_type == VAR_PARTIAL
-						&& var2.v_type == VAR_PARTIAL)
-			n1 = (rettv->vval.v_partial == var2.vval.v_partial);
-		    else
-			n1 = FALSE;
-		}
-		else
-		    n1 = tv_equal(rettv, &var2, ic, FALSE);
-		if (type == TYPE_NEQUAL)
-		    n1 = !n1;
-	    }
-
-#ifdef FEAT_FLOAT
-	    /*
-	     * If one of the two variables is a float, compare as a float.
-	     * When using "=~" or "!~", always compare as string.
-	     */
-	    else if ((rettv->v_type == VAR_FLOAT || var2.v_type == VAR_FLOAT)
-		    && type != TYPE_MATCH && type != TYPE_NOMATCH)
-	    {
-		float_T f1, f2;
-
-		if (rettv->v_type == VAR_FLOAT)
-		    f1 = rettv->vval.v_float;
-		else
-		    f1 = get_tv_number(rettv);
-		if (var2.v_type == VAR_FLOAT)
-		    f2 = var2.vval.v_float;
-		else
-		    f2 = get_tv_number(&var2);
-		n1 = FALSE;
-		switch (type)
-		{
-		    case TYPE_EQUAL:    n1 = (f1 == f2); break;
-		    case TYPE_NEQUAL:   n1 = (f1 != f2); break;
-		    case TYPE_GREATER:  n1 = (f1 > f2); break;
-		    case TYPE_GEQUAL:   n1 = (f1 >= f2); break;
-		    case TYPE_SMALLER:  n1 = (f1 < f2); break;
-		    case TYPE_SEQUAL:   n1 = (f1 <= f2); break;
-		    case TYPE_UNKNOWN:
-		    case TYPE_MATCH:
-		    case TYPE_NOMATCH:  break;  /* avoid gcc warning */
-		}
-	    }
-#endif
-
-	    /*
-	     * If one of the two variables is a number, compare as a number.
-	     * When using "=~" or "!~", always compare as string.
-	     */
-	    else if ((rettv->v_type == VAR_NUMBER || var2.v_type == VAR_NUMBER)
-		    && type != TYPE_MATCH && type != TYPE_NOMATCH)
-	    {
-		n1 = get_tv_number(rettv);
-		n2 = get_tv_number(&var2);
-		switch (type)
-		{
-		    case TYPE_EQUAL:    n1 = (n1 == n2); break;
-		    case TYPE_NEQUAL:   n1 = (n1 != n2); break;
-		    case TYPE_GREATER:  n1 = (n1 > n2); break;
-		    case TYPE_GEQUAL:   n1 = (n1 >= n2); break;
-		    case TYPE_SMALLER:  n1 = (n1 < n2); break;
-		    case TYPE_SEQUAL:   n1 = (n1 <= n2); break;
-		    case TYPE_UNKNOWN:
-		    case TYPE_MATCH:
-		    case TYPE_NOMATCH:  break;  /* avoid gcc warning */
-		}
-	    }
-	    else
-	    {
-		s1 = get_tv_string_buf(rettv, buf1);
-		s2 = get_tv_string_buf(&var2, buf2);
-		if (type != TYPE_MATCH && type != TYPE_NOMATCH)
-		    i = ic ? MB_STRICMP(s1, s2) : STRCMP(s1, s2);
-		else
-		    i = 0;
-		n1 = FALSE;
-		switch (type)
-		{
-		    case TYPE_EQUAL:    n1 = (i == 0); break;
-		    case TYPE_NEQUAL:   n1 = (i != 0); break;
-		    case TYPE_GREATER:  n1 = (i > 0); break;
-		    case TYPE_GEQUAL:   n1 = (i >= 0); break;
-		    case TYPE_SMALLER:  n1 = (i < 0); break;
-		    case TYPE_SEQUAL:   n1 = (i <= 0); break;
-
-		    case TYPE_MATCH:
-		    case TYPE_NOMATCH:
-			    n1 = pattern_match(s2, s1, ic);
-			    if (type == TYPE_NOMATCH)
-				n1 = !n1;
-			    break;
-
-		    case TYPE_UNKNOWN:  break;  /* avoid gcc warning */
-		}
-	    }
-	    clear_tv(rettv);
-	    clear_tv(&var2);
-	    rettv->v_type = VAR_NUMBER;
-	    rettv->vval.v_number = n1;
-	}
+	return typval_compare(rettv, &var2, type, type_is, ic, evaluate);
     }
 
     return OK;
@@ -6840,7 +6630,7 @@ set_cmdarg(exarg_T *eap, char_u *oldarg)
 
 /*
  * Get the value of internal variable "name".
- * Return OK or FAIL.
+ * Return OK or FAIL.  If OK is returned "rettv" must be cleared.
  */
     int
 get_var_tv(
@@ -8419,7 +8209,7 @@ ex_execute(exarg_T *eap)
     win_T *
 find_win_by_nr(
     typval_T	*vp,
-    tabpage_T	*tp UNUSED)	/* NULL for current tab page */
+    tabpage_T	*tp)	/* NULL for current tab page */
 {
     win_T	*wp;
     int		nr;
@@ -9279,6 +9069,277 @@ fill_assert_error(
 }
 
 
+    int
+typval_compare(
+    typval_T	*typ1,   /* first operand */
+    typval_T	*typ2,   /* second operand */
+    exptype_T	type,    /* operator */
+    int		type_is, /* TRUE for "is" and "isnot" */
+    int		ic,      /* ignore case */
+    int		evaluate)
+{
+    int		i;
+    varnumber_T	n1, n2;
+    char_u	*s1, *s2;
+    char_u	buf1[NUMBUFLEN], buf2[NUMBUFLEN];
+
+    if (evaluate)
+    {
+	if (type_is && typ1->v_type != typ2->v_type)
+	{
+	    /* For "is" a different type always means FALSE, for "notis"
+		* it means TRUE. */
+	    n1 = (type == TYPE_NEQUAL);
+	}
+	else if (typ1->v_type == VAR_LIST || typ2->v_type == VAR_LIST)
+	{
+	    if (type_is)
+	    {
+		n1 = (typ1->v_type == typ2->v_type
+				&& typ1->vval.v_list == typ2->vval.v_list);
+		if (type == TYPE_NEQUAL)
+		    n1 = !n1;
+	    }
+	    else if (typ1->v_type != typ2->v_type
+		    || (type != TYPE_EQUAL && type != TYPE_NEQUAL))
+	    {
+		if (typ1->v_type != typ2->v_type)
+		    EMSG(_("E691: Can only compare List with List"));
+		else
+		    EMSG(_("E692: Invalid operation for List"));
+		clear_tv(typ1);
+		clear_tv(typ2);
+		return FAIL;
+	    }
+	    else
+	    {
+		/* Compare two Lists for being equal or unequal. */
+		n1 = list_equal(typ1->vval.v_list, typ2->vval.v_list,
+								ic, FALSE);
+		if (type == TYPE_NEQUAL)
+		    n1 = !n1;
+	    }
+	}
+
+	else if (typ1->v_type == VAR_DICT || typ2->v_type == VAR_DICT)
+	{
+	    if (type_is)
+	    {
+		n1 = (typ1->v_type == typ2->v_type
+				&& typ1->vval.v_dict == typ2->vval.v_dict);
+		if (type == TYPE_NEQUAL)
+		    n1 = !n1;
+	    }
+	    else if (typ1->v_type != typ2->v_type
+		    || (type != TYPE_EQUAL && type != TYPE_NEQUAL))
+	    {
+		if (typ1->v_type != typ2->v_type)
+		    EMSG(_("E735: Can only compare Dictionary with Dictionary"));
+		else
+		    EMSG(_("E736: Invalid operation for Dictionary"));
+		clear_tv(typ1);
+		clear_tv(typ2);
+		return FAIL;
+	    }
+	    else
+	    {
+		/* Compare two Dictionaries for being equal or unequal. */
+		n1 = dict_equal(typ1->vval.v_dict, typ2->vval.v_dict,
+								ic, FALSE);
+		if (type == TYPE_NEQUAL)
+		    n1 = !n1;
+	    }
+	}
+
+	else if (typ1->v_type == VAR_FUNC || typ2->v_type == VAR_FUNC
+	    || typ1->v_type == VAR_PARTIAL || typ2->v_type == VAR_PARTIAL)
+	{
+	    if (type != TYPE_EQUAL && type != TYPE_NEQUAL)
+	    {
+		EMSG(_("E694: Invalid operation for Funcrefs"));
+		clear_tv(typ1);
+		clear_tv(typ2);
+		return FAIL;
+	    }
+	    if ((typ1->v_type == VAR_PARTIAL
+					    && typ1->vval.v_partial == NULL)
+		    || (typ2->v_type == VAR_PARTIAL
+					    && typ2->vval.v_partial == NULL))
+		/* when a partial is NULL assume not equal */
+		n1 = FALSE;
+	    else if (type_is)
+	    {
+		if (typ1->v_type == VAR_FUNC && typ2->v_type == VAR_FUNC)
+		    /* strings are considered the same if their value is
+			* the same */
+		    n1 = tv_equal(typ1, typ2, ic, FALSE);
+		else if (typ1->v_type == VAR_PARTIAL
+					    && typ2->v_type == VAR_PARTIAL)
+		    n1 = (typ1->vval.v_partial == typ2->vval.v_partial);
+		else
+		    n1 = FALSE;
+	    }
+	    else
+		n1 = tv_equal(typ1, typ2, ic, FALSE);
+	    if (type == TYPE_NEQUAL)
+		n1 = !n1;
+	}
+
+#ifdef FEAT_FLOAT
+	/*
+	    * If one of the two variables is a float, compare as a float.
+	    * When using "=~" or "!~", always compare as string.
+	    */
+	else if ((typ1->v_type == VAR_FLOAT || typ2->v_type == VAR_FLOAT)
+		&& type != TYPE_MATCH && type != TYPE_NOMATCH)
+	{
+	    float_T f1, f2;
+
+	    if (typ1->v_type == VAR_FLOAT)
+		f1 = typ1->vval.v_float;
+	    else
+		f1 = get_tv_number(typ1);
+	    if (typ2->v_type == VAR_FLOAT)
+		f2 = typ2->vval.v_float;
+	    else
+		f2 = get_tv_number(typ2);
+	    n1 = FALSE;
+	    switch (type)
+	    {
+		case TYPE_EQUAL:    n1 = (f1 == f2); break;
+		case TYPE_NEQUAL:   n1 = (f1 != f2); break;
+		case TYPE_GREATER:  n1 = (f1 > f2); break;
+		case TYPE_GEQUAL:   n1 = (f1 >= f2); break;
+		case TYPE_SMALLER:  n1 = (f1 < f2); break;
+		case TYPE_SEQUAL:   n1 = (f1 <= f2); break;
+		case TYPE_UNKNOWN:
+		case TYPE_MATCH:
+		case TYPE_NOMATCH:  break;  /* avoid gcc warning */
+	    }
+	}
+#endif
+
+	/*
+	    * If one of the two variables is a number, compare as a number.
+	    * When using "=~" or "!~", always compare as string.
+	    */
+	else if ((typ1->v_type == VAR_NUMBER || typ2->v_type == VAR_NUMBER)
+		&& type != TYPE_MATCH && type != TYPE_NOMATCH)
+	{
+	    n1 = get_tv_number(typ1);
+	    n2 = get_tv_number(typ2);
+	    switch (type)
+	    {
+		case TYPE_EQUAL:    n1 = (n1 == n2); break;
+		case TYPE_NEQUAL:   n1 = (n1 != n2); break;
+		case TYPE_GREATER:  n1 = (n1 > n2); break;
+		case TYPE_GEQUAL:   n1 = (n1 >= n2); break;
+		case TYPE_SMALLER:  n1 = (n1 < n2); break;
+		case TYPE_SEQUAL:   n1 = (n1 <= n2); break;
+		case TYPE_UNKNOWN:
+		case TYPE_MATCH:
+		case TYPE_NOMATCH:  break;  /* avoid gcc warning */
+	    }
+	}
+	else
+	{
+	    s1 = get_tv_string_buf(typ1, buf1);
+	    s2 = get_tv_string_buf(typ2, buf2);
+	    if (type != TYPE_MATCH && type != TYPE_NOMATCH)
+		i = ic ? MB_STRICMP(s1, s2) : STRCMP(s1, s2);
+	    else
+		i = 0;
+	    n1 = FALSE;
+	    switch (type)
+	    {
+		case TYPE_EQUAL:    n1 = (i == 0); break;
+		case TYPE_NEQUAL:   n1 = (i != 0); break;
+		case TYPE_GREATER:  n1 = (i > 0); break;
+		case TYPE_GEQUAL:   n1 = (i >= 0); break;
+		case TYPE_SMALLER:  n1 = (i < 0); break;
+		case TYPE_SEQUAL:   n1 = (i <= 0); break;
+
+		case TYPE_MATCH:
+		case TYPE_NOMATCH:
+			n1 = pattern_match(s2, s1, ic);
+			if (type == TYPE_NOMATCH)
+			    n1 = !n1;
+			break;
+
+		case TYPE_UNKNOWN:  break;  /* avoid gcc warning */
+	    }
+	}
+	clear_tv(typ1);
+	clear_tv(typ2);
+	typ1->v_type = VAR_NUMBER;
+	typ1->vval.v_number = n1;
+    }
+    return OK;
+}
+
+    int
+typval_copy(typ1, typ2)
+    typval_T	*typ1;
+    typval_T	*typ2;
+{
+    if (typ2 == NULL)
+	rettv_list_alloc(typ2);
+
+    if (typ1 != NULL && typ2 != NULL)
+	return item_copy(typ1, typ2, TRUE, 0);
+
+    return FAIL;
+}
+
+    char_u *
+typval_tostring(arg)
+    typval_T	*arg;
+{
+    char_u	*tofree;
+    char_u	numbuf[NUMBUFLEN];
+    char_u	*ret = NULL;
+
+    if (arg == NULL)
+	return vim_strsave((char_u *)"(does not exist)");
+    ret = tv2string(arg, &tofree, numbuf, 0);
+    /* Make a copy if we have a value but it's not in allocated memory. */
+    if (ret != NULL && tofree == NULL)
+	ret = vim_strsave(ret);
+    return ret;
+}
+
+    int
+var_exists(char_u *var)
+{
+    char_u	*name;
+    char_u	*tofree;
+    typval_T    tv;
+    int		len = 0;
+    int		n = FALSE;
+
+    /* get_name_len() takes care of expanding curly braces */
+    name = var;
+    len = get_name_len(&var, &tofree, TRUE, FALSE);
+    if (len > 0)
+    {
+	if (tofree != NULL)
+	    name = tofree;
+	n = (get_var_tv(name, len, &tv, NULL, FALSE, TRUE) == OK);
+	if (n)
+	{
+	    /* handle d.key, l[idx], f(expr) */
+	    n = (handle_subscript(&var, &tv, TRUE, FALSE) == OK);
+	    if (n)
+		clear_tv(&tv);
+	}
+    }
+    if (*var != NUL)
+	n = FALSE;
+
+    vim_free(tofree);
+    return n;
+}
+
 #endif /* FEAT_EVAL */
 
 
--- a/src/evalfunc.c
+++ b/src/evalfunc.c
@@ -2991,9 +2991,7 @@ f_exepath(typval_T *argvars, typval_T *r
 f_exists(typval_T *argvars, typval_T *rettv)
 {
     char_u	*p;
-    char_u	*name;
     int		n = FALSE;
-    int		len = 0;
 
     p = get_tv_string(&argvars[0]);
     if (*p == '$')			/* environment variable */
@@ -3035,29 +3033,7 @@ f_exists(typval_T *argvars, typval_T *re
     }
     else				/* internal variable */
     {
-	char_u	    *tofree;
-	typval_T    tv;
-
-	/* get_name_len() takes care of expanding curly braces */
-	name = p;
-	len = get_name_len(&p, &tofree, TRUE, FALSE);
-	if (len > 0)
-	{
-	    if (tofree != NULL)
-		name = tofree;
-	    n = (get_var_tv(name, len, &tv, NULL, FALSE, TRUE) == OK);
-	    if (n)
-	    {
-		/* handle d.key, l[idx], f(expr) */
-		n = (handle_subscript(&p, &tv, TRUE, FALSE) == OK);
-		if (n)
-		    clear_tv(&tv);
-	    }
-	}
-	if (*p != NUL)
-	    n = FALSE;
-
-	vim_free(tofree);
+	n = var_exists(p);
     }
 
     rettv->vval.v_number = n;
--- a/src/ex_cmds2.c
+++ b/src/ex_cmds2.c
@@ -73,6 +73,16 @@ static void do_setdebugtracelevel(char_u
 static void do_checkbacktracelevel(void);
 static void do_showbacktrace(char_u *cmd);
 
+static char_u *debug_oldval = NULL;	/* old and newval for debug expressions */
+static char_u *debug_newval = NULL;
+static int     debug_expr   = 0;        /* use debug_expr */
+
+    int
+has_watchexpr(void)
+{
+    return debug_expr;
+}
+
 /*
  * do_debug(): Debug mode.
  * Repeatedly get Ex commands, until told to continue normal execution.
@@ -135,13 +145,24 @@ do_debug(char_u *cmd)
 
     if (!debug_did_msg)
 	MSG(_("Entering Debug mode.  Type \"cont\" to continue."));
+    if (debug_oldval != NULL)
+    {
+	smsg((char_u *)_("Oldval = \"%s\""), debug_oldval);
+	vim_free(debug_oldval);
+	debug_oldval = NULL;
+    }
+    if (debug_newval != NULL)
+    {
+	smsg((char_u *)_("Newval = \"%s\""), debug_newval);
+	vim_free(debug_newval);
+	debug_newval = NULL;
+    }
     if (sourcing_name != NULL)
 	msg(sourcing_name);
     if (sourcing_lnum != 0)
 	smsg((char_u *)_("line %ld: %s"), (long)sourcing_lnum, cmd);
     else
 	smsg((char_u *)_("cmd: %s"), cmd);
-
     /*
      * Repeat getting a command and executing it.
      */
@@ -528,11 +549,15 @@ dbg_check_skipped(exarg_T *eap)
 struct debuggy
 {
     int		dbg_nr;		/* breakpoint number */
-    int		dbg_type;	/* DBG_FUNC or DBG_FILE */
-    char_u	*dbg_name;	/* function or file name */
+    int		dbg_type;	/* DBG_FUNC, DBG_FILE or DBG_EXPR */
+    char_u	*dbg_name;	/* function, expression or file name */
     regprog_T	*dbg_prog;	/* regexp program */
     linenr_T	dbg_lnum;	/* line number in function or file */
     int		dbg_forceit;	/* ! used */
+#ifdef FEAT_EVAL
+    typval_T    *dbg_val;       /* last result of watchexpression */
+#endif
+    int		dbg_level;      /* stored nested level for expr */
 };
 
 static garray_T dbg_breakp = {0, 0, sizeof(struct debuggy), 4, NULL};
@@ -546,6 +571,7 @@ static garray_T prof_ga = {0, 0, sizeof(
 #endif
 #define DBG_FUNC	1
 #define DBG_FILE	2
+#define DBG_EXPR	3
 
 static int dbg_parsearg(char_u *arg, garray_T *gap);
 static linenr_T debuggy_find(int file,char_u *fname, linenr_T after, garray_T *gap, int *fp);
@@ -589,6 +615,12 @@ dbg_parsearg(
 	bp->dbg_type = DBG_FILE;
 	here = TRUE;
     }
+    else if (
+#ifdef FEAT_PROFILE
+	    gap != &prof_ga &&
+#endif
+	    STRNCMP(p, "expr", 4) == 0)
+	bp->dbg_type = DBG_EXPR;
     else
     {
 	EMSG2(_(e_invarg2), p);
@@ -624,6 +656,12 @@ dbg_parsearg(
 	bp->dbg_name = vim_strsave(p);
     else if (here)
 	bp->dbg_name = vim_strsave(curbuf->b_ffname);
+    else if (bp->dbg_type == DBG_EXPR)
+    {
+	bp->dbg_name = vim_strsave(p);
+	if (bp->dbg_name != NULL)
+	    bp->dbg_val = eval_expr(bp->dbg_name, NULL);
+    }
     else
     {
 	/* Expand the file name in the same way as do_source().  This means
@@ -671,26 +709,35 @@ ex_breakadd(exarg_T *eap)
 	bp = &DEBUGGY(gap, gap->ga_len);
 	bp->dbg_forceit = eap->forceit;
 
-	pat = file_pat_to_reg_pat(bp->dbg_name, NULL, NULL, FALSE);
-	if (pat != NULL)
+	if (bp->dbg_type != DBG_EXPR)
 	{
-	    bp->dbg_prog = vim_regcomp(pat, RE_MAGIC + RE_STRING);
-	    vim_free(pat);
+	    pat = file_pat_to_reg_pat(bp->dbg_name, NULL, NULL, FALSE);
+	    if (pat != NULL)
+	    {
+		bp->dbg_prog = vim_regcomp(pat, RE_MAGIC + RE_STRING);
+		vim_free(pat);
+	    }
+	    if (pat == NULL || bp->dbg_prog == NULL)
+		vim_free(bp->dbg_name);
+	    else
+	    {
+		if (bp->dbg_lnum == 0)	/* default line number is 1 */
+		    bp->dbg_lnum = 1;
+#ifdef FEAT_PROFILE
+		if (eap->cmdidx != CMD_profile)
+#endif
+		{
+		    DEBUGGY(gap, gap->ga_len).dbg_nr = ++last_breakp;
+		    ++debug_tick;
+		}
+		++gap->ga_len;
+	    }
 	}
-	if (pat == NULL || bp->dbg_prog == NULL)
-	    vim_free(bp->dbg_name);
 	else
 	{
-	    if (bp->dbg_lnum == 0)	/* default line number is 1 */
-		bp->dbg_lnum = 1;
-#ifdef FEAT_PROFILE
-	    if (eap->cmdidx != CMD_profile)
-#endif
-	    {
-		DEBUGGY(gap, gap->ga_len).dbg_nr = ++last_breakp;
-		++debug_tick;
-	    }
-	    ++gap->ga_len;
+	    /* DBG_EXPR */
+	    DEBUGGY(gap, gap->ga_len++).dbg_nr = ++last_breakp;
+	    ++debug_tick;
 	}
     }
 }
@@ -750,7 +797,7 @@ ex_breakdel(exarg_T *eap)
     }
     else
     {
-	/* ":breakdel {func|file} [lnum] {name}" */
+	/* ":breakdel {func|file|expr} [lnum] {name}" */
 	if (dbg_parsearg(eap->arg, gap) == FAIL)
 	    return;
 	bp = &DEBUGGY(gap, gap->ga_len);
@@ -778,6 +825,11 @@ ex_breakdel(exarg_T *eap)
 	while (gap->ga_len > 0)
 	{
 	    vim_free(DEBUGGY(gap, todel).dbg_name);
+#ifdef FEAT_EVAL
+	    if (DEBUGGY(gap, todel).dbg_type == DBG_EXPR
+		    && DEBUGGY(gap, todel).dbg_val != NULL)
+		free_tv(DEBUGGY(gap, todel).dbg_val);
+#endif
 	    vim_regfree(DEBUGGY(gap, todel).dbg_prog);
 	    --gap->ga_len;
 	    if (todel < gap->ga_len)
@@ -814,11 +866,15 @@ ex_breaklist(exarg_T *eap UNUSED)
 	    bp = &BREAKP(i);
 	    if (bp->dbg_type == DBG_FILE)
 		home_replace(NULL, bp->dbg_name, NameBuff, MAXPATHL, TRUE);
-	    smsg((char_u *)_("%3d  %s %s  line %ld"),
+	    if (bp->dbg_type != DBG_EXPR)
+		smsg((char_u *)_("%3d  %s %s  line %ld"),
 		    bp->dbg_nr,
 		    bp->dbg_type == DBG_FUNC ? "func" : "file",
 		    bp->dbg_type == DBG_FUNC ? bp->dbg_name : NameBuff,
 		    (long)bp->dbg_lnum);
+	    else
+		smsg((char_u *)_("%3d  expr %s"),
+		    bp->dbg_nr, bp->dbg_name);
 	}
 }
 
@@ -889,7 +945,8 @@ debuggy_find(
 	/* Skip entries that are not useful or are for a line that is beyond
 	 * an already found breakpoint. */
 	bp = &DEBUGGY(gap, i);
-	if (((bp->dbg_type == DBG_FILE) == file && (
+	if (((bp->dbg_type == DBG_FILE) == file &&
+		bp->dbg_type != DBG_EXPR && (
 #ifdef FEAT_PROFILE
 		gap == &prof_ga ||
 #endif
@@ -910,6 +967,66 @@ debuggy_find(
 	    }
 	    got_int |= prev_got_int;
 	}
+#ifdef FEAT_EVAL
+	else if (bp->dbg_type == DBG_EXPR)
+	{
+	    typval_T *tv;
+	    int	      line = FALSE;
+
+	    prev_got_int = got_int;
+	    got_int = FALSE;
+
+	    tv = eval_expr(bp->dbg_name, NULL);
+	    if (tv != NULL)
+	    {
+		if (bp->dbg_val == NULL)
+		{
+		    debug_oldval = typval_tostring(NULL);
+		    bp->dbg_val = tv;
+		    debug_newval = typval_tostring(bp->dbg_val);
+		    line = TRUE;
+		}
+		else
+		{
+		    typval_T  val3;
+
+		    if (typval_copy(bp->dbg_val, &val3) == OK)
+		    {
+			if (typval_compare(tv, &val3, TYPE_EQUAL,
+						       TRUE, FALSE, TRUE) == OK
+				&& tv->vval.v_number == FALSE)
+			{
+			    typval_T *v;
+
+			    line = TRUE;
+			    debug_oldval = typval_tostring(bp->dbg_val);
+			    v = eval_expr(bp->dbg_name, NULL);
+			    debug_newval = typval_tostring(v);
+			    free_tv(bp->dbg_val);
+			    bp->dbg_val = v;
+			}
+		    }
+		    free_tv(tv);
+		}
+	    }
+	    else if (bp->dbg_val != NULL)
+	    {
+		debug_oldval = typval_tostring(bp->dbg_val);
+		debug_newval = typval_tostring(NULL);
+		free_tv(bp->dbg_val);
+		bp->dbg_val = NULL;
+		line = TRUE;
+	    }
+
+	    if (line)
+	    {
+		lnum = after > 0 ? after : 1;
+		break;
+	    }
+
+	    got_int |= prev_got_int;
+	}
+#endif
     }
     if (name != fname)
 	vim_free(name);
--- a/src/ex_docmd.c
+++ b/src/ex_docmd.c
@@ -1174,6 +1174,13 @@ do_cmdline(
 	    }
 	}
 
+	/* Check for the next breakpoint after a watchexpression */
+	if (breakpoint != NULL && has_watchexpr())
+	{
+	    *breakpoint = dbg_find_breakpoint(FALSE, fname, sourcing_lnum);
+	    *dbg_tick = debug_tick;
+	}
+
 	/*
 	 * When not inside any ":while" loop, clear remembered lines.
 	 */
--- a/src/proto/eval.pro
+++ b/src/proto/eval.pro
@@ -64,7 +64,7 @@ void set_vim_var_nr(int idx, varnumber_T
 varnumber_T get_vim_var_nr(int idx);
 char_u *get_vim_var_str(int idx);
 list_T *get_vim_var_list(int idx);
-dict_T * get_vim_var_dict(int idx);
+dict_T *get_vim_var_dict(int idx);
 void set_vim_var_char(int c);
 void set_vcount(long count, long count1, int set_prevcount);
 void set_vim_var_string(int idx, char_u *val, int len);
@@ -129,6 +129,10 @@ void assert_report(typval_T *argvars);
 void assert_exception(typval_T *argvars);
 void assert_fails(typval_T *argvars);
 void fill_assert_error(garray_T *gap, typval_T *opt_msg_tv, char_u *exp_str, typval_T *exp_tv, typval_T *got_tv, assert_type_T atype);
+int typval_compare(typval_T *typ1, typval_T *typ2, exptype_T type, int type_is, int ic, int evaluate);
+int typval_copy(typval_T *typ1, typval_T *typ2);
+char_u *typval_tostring(typval_T *arg);
+int var_exists(char_u *var);
 int modify_fname(char_u *src, int *usedlen, char_u **fnamep, char_u **bufp, int *fnamelen);
 char_u *do_string_sub(char_u *str, char_u *pat, char_u *sub, typval_T *expr, char_u *flags);
 void filter_map(typval_T *argvars, typval_T *rettv, int map);
--- a/src/proto/ex_cmds2.pro
+++ b/src/proto/ex_cmds2.pro
@@ -1,4 +1,5 @@
 /* ex_cmds2.c */
+int has_watchexpr (void);
 void do_debug(char_u *cmd);
 void ex_debug(exarg_T *eap);
 void dbg_check_breakpoint(exarg_T *eap);
--- a/src/structs.h
+++ b/src/structs.h
@@ -3263,6 +3263,22 @@ typedef struct {
 } context_sha256_T;
 
 /*
+ * types for expressions.
+ */
+typedef enum
+{
+    TYPE_UNKNOWN = 0
+    , TYPE_EQUAL	/* == */
+    , TYPE_NEQUAL	/* != */
+    , TYPE_GREATER	/* >  */
+    , TYPE_GEQUAL	/* >= */
+    , TYPE_SMALLER	/* <  */
+    , TYPE_SEQUAL	/* <= */
+    , TYPE_MATCH	/* =~ */
+    , TYPE_NOMATCH	/* !~ */
+} exptype_T;
+
+/*
  * Structure used for reading in json_decode().
  */
 struct js_reader
--- a/src/userfunc.c
+++ b/src/userfunc.c
@@ -3085,6 +3085,8 @@ ex_call(exarg_T *eap)
 	    failed = TRUE;
 	    break;
 	}
+	if (has_watchexpr())
+	    dbg_check_breakpoint(eap);
 
 	/* Handle a function returning a Funcref, Dictionary or List. */
 	if (handle_subscript(&arg, &rettv, !eap->skip, TRUE) == FAIL)
--- a/src/version.c
+++ b/src/version.c
@@ -772,6 +772,8 @@ static char *(features[]) =
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    1505,
+/**/
     1504,
 /**/
     1503,