aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--runtime/doc/repeat.txt13
-rw-r--r--src/nvim/eval.c424
-rw-r--r--src/nvim/eval.h13
-rw-r--r--src/nvim/eval/funcs.c25
-rw-r--r--src/nvim/ex_cmds2.c125
5 files changed, 362 insertions, 238 deletions
diff --git a/runtime/doc/repeat.txt b/runtime/doc/repeat.txt
index 604c969c64..3a7337d2e8 100644
--- a/runtime/doc/repeat.txt
+++ b/runtime/doc/repeat.txt
@@ -804,6 +804,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.
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
index 550fe8ab65..ffd5bbc0f7 100644
--- a/src/nvim/eval.c
+++ b/src/nvim/eval.c
@@ -916,6 +916,17 @@ varnumber_T eval_to_number(char_u *expr)
return retval;
}
+// Top level evaluation function.
+// Returns an allocated typval_T with the result.
+// Returns NULL when there is an error.
+typval_T *eval_expr(char_u *arg)
+{
+ typval_T *tv = xmalloc(sizeof(*tv));
+ if (eval0(arg, tv, NULL, true) == FAIL) {
+ XFREE_CLEAR(tv);
+ }
+ return tv;
+}
/*
* Prepare v: variable "idx" to be used.
@@ -3129,21 +3140,6 @@ static int pattern_match(char_u *pat, char_u *text, bool ic)
return matches;
}
-/*
- * 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;
-
// TODO(ZyX-I): move to eval/expressions
/*
@@ -3420,11 +3416,9 @@ static int eval4(char_u **arg, typval_T *rettv, int evaluate)
{
typval_T var2;
char_u *p;
- int i;
exptype_T type = TYPE_UNKNOWN;
bool type_is = false; // true for "is" and "isnot"
int len = 2;
- varnumber_T n1, n2;
bool ic;
/*
@@ -3491,173 +3485,7 @@ static int eval4(char_u **arg, typval_T *rettv, int evaluate)
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"));
- }
- tv_clear(rettv);
- tv_clear(&var2);
- return FAIL;
- } else {
- // Compare two Lists for being equal or unequal.
- n1 = tv_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"));
- tv_clear(rettv);
- tv_clear(&var2);
- return FAIL;
- } else {
- // Compare two Dictionaries for being equal or unequal.
- n1 = tv_dict_equal(rettv->vval.v_dict, var2.vval.v_dict,
- ic, false);
- if (type == TYPE_NEQUAL) {
- n1 = !n1;
- }
- }
- } else if (tv_is_func(*rettv) || tv_is_func(var2)) {
- if (type != TYPE_EQUAL && type != TYPE_NEQUAL) {
- EMSG(_("E694: Invalid operation for Funcrefs"));
- tv_clear(rettv);
- tv_clear(&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;
- }
- }
- /*
- * 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 = tv_get_number(rettv);
- }
- if (var2.v_type == VAR_FLOAT) {
- f2 = var2.vval.v_float;
- } else {
- f2 = tv_get_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;
- }
- }
- /*
- * 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 = tv_get_number(rettv);
- n2 = tv_get_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;
- }
- } else {
- char buf1[NUMBUFLEN];
- char buf2[NUMBUFLEN];
- const char *const s1 = tv_get_string_buf(rettv, buf1);
- const char *const s2 = tv_get_string_buf(&var2, buf2);
- if (type != TYPE_MATCH && type != TYPE_NOMATCH) {
- i = mb_strcmp_ic(ic, 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((char_u *)s2, (char_u *)s1, ic);
- if (type == TYPE_NOMATCH) {
- n1 = !n1;
- }
- break;
- }
- case TYPE_UNKNOWN: break; // Avoid gcc warning.
- }
- }
- tv_clear(rettv);
- tv_clear(&var2);
- rettv->v_type = VAR_NUMBER;
- rettv->vval.v_number = n1;
- }
+ return typval_compare(rettv, &var2, type, type_is, ic, evaluate);
}
return OK;
@@ -8000,8 +7828,8 @@ int get_id_len(const char **const arg)
*/
int get_name_len(const char **const arg,
char **alias,
- int evaluate,
- int verbose)
+ bool evaluate,
+ bool verbose)
{
int len;
@@ -8486,10 +8314,8 @@ char_u *set_cmdarg(exarg_T *eap, char_u *oldarg)
return oldval;
}
-/*
- * Get the value of internal variable "name".
- * Return OK or FAIL.
- */
+// Get the value of internal variable "name".
+// Return OK or FAIL. If OK is returned "rettv" must be cleared.
int get_var_tv(
const char *name,
int len, // length of "name"
@@ -10746,3 +10572,221 @@ bool invoke_prompt_interrupt(void)
tv_clear(&rettv);
return true;
}
+
+int typval_compare(
+ typval_T *typ1, // first operand
+ typval_T *typ2, // second operand
+ exptype_T type, // operator
+ bool type_is, // true for "is" and "isnot"
+ bool ic, // ignore case
+ bool evaluate
+)
+ FUNC_ATTR_NONNULL_ALL
+{
+ varnumber_T n1, n2;
+
+ 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"));
+ }
+ tv_clear(typ1);
+ tv_clear(typ2);
+ return FAIL;
+ } else {
+ // Compare two Lists for being equal or unequal.
+ n1 = tv_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"));
+ }
+ tv_clear(typ1);
+ tv_clear(typ2);
+ return FAIL;
+ } else {
+ // Compare two Dictionaries for being equal or unequal.
+ n1 = tv_dict_equal(typ1->vval.v_dict, typ2->vval.v_dict, ic, false);
+ if (type == TYPE_NEQUAL) {
+ n1 = !n1;
+ }
+ }
+ } else if (tv_is_func(*typ1) || tv_is_func(*typ2)) {
+ if (type != TYPE_EQUAL && type != TYPE_NEQUAL) {
+ EMSG(_("E694: Invalid operation for Funcrefs"));
+ tv_clear(typ1);
+ tv_clear(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;
+ }
+ } else if ((typ1->v_type == VAR_FLOAT || typ2->v_type == VAR_FLOAT)
+ && type != TYPE_MATCH && type != TYPE_NOMATCH) {
+ // If one of the two variables is a float, compare as a float.
+ // When using "=~" or "!~", always compare as string.
+ const float_T f1 = tv_get_float(typ1);
+ const float_T f2 = tv_get_float(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;
+ }
+ } else if ((typ1->v_type == VAR_NUMBER || typ2->v_type == VAR_NUMBER)
+ && type != TYPE_MATCH && type != TYPE_NOMATCH) {
+ // If one of the two variables is a number, compare as a number.
+ // When using "=~" or "!~", always compare as string.
+ n1 = tv_get_number(typ1);
+ n2 = tv_get_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;
+ }
+ } else {
+ char buf1[NUMBUFLEN];
+ char buf2[NUMBUFLEN];
+ const char *const s1 = tv_get_string_buf(typ1, buf1);
+ const char *const s2 = tv_get_string_buf(typ2, buf2);
+ int i;
+ if (type != TYPE_MATCH && type != TYPE_NOMATCH) {
+ i = mb_strcmp_ic(ic, 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((char_u *)s2, (char_u *)s1, ic);
+ if (type == TYPE_NOMATCH) {
+ n1 = !n1;
+ }
+ break;
+ case TYPE_UNKNOWN: break; // Avoid gcc warning.
+ }
+ }
+ tv_clear(typ1);
+ tv_clear(typ2);
+ typ1->v_type = VAR_NUMBER;
+ typ1->vval.v_number = n1;
+ }
+ return OK;
+}
+
+int typval_copy(typval_T *typ1, typval_T *typ2)
+{
+ if (typ2 == NULL) {
+ tv_list_alloc_ret(typ2, kListLenUnknown);
+ }
+ if (typ1 != NULL && typ2 != NULL) {
+ return var_item_copy(NULL, typ1, typ2, true, 0);
+ }
+
+ return FAIL;
+}
+
+char *typval_tostring(typval_T *arg)
+{
+ if (arg == NULL) {
+ return xstrdup("(does not exist)");
+ }
+ return encode_tv2string(arg, NULL);
+}
+
+bool var_exists(const char *var)
+ FUNC_ATTR_NONNULL_ALL
+{
+ char *tofree;
+ bool n = false;
+
+ // get_name_len() takes care of expanding curly braces
+ const char *name = var;
+ const int len = get_name_len((const char **)&var, &tofree, true, false);
+ if (len > 0) {
+ typval_T tv;
+
+ 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) {
+ tv_clear(&tv);
+ }
+ }
+ }
+ if (*var != NUL) {
+ n = false;
+ }
+
+ xfree(tofree);
+ return n;
+}
diff --git a/src/nvim/eval.h b/src/nvim/eval.h
index 4f03d5d259..c681660771 100644
--- a/src/nvim/eval.h
+++ b/src/nvim/eval.h
@@ -227,6 +227,19 @@ typedef enum
ASSERT_OTHER,
} assert_type_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;
+
/// Type for dict_list function
typedef enum {
kDictListKeys, ///< List dictionary keys.
diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c
index fb72b9425e..0d288e2cc2 100644
--- a/src/nvim/eval/funcs.c
+++ b/src/nvim/eval/funcs.c
@@ -2051,7 +2051,6 @@ static void f_exepath(typval_T *argvars, typval_T *rettv, FunPtr fptr)
static void f_exists(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
int n = false;
- int len = 0;
const char *p = tv_get_string(&argvars[0]);
if (*p == '$') { // Environment variable.
@@ -2082,29 +2081,7 @@ static void f_exists(typval_T *argvars, typval_T *rettv, FunPtr fptr)
n = au_exists(p + 1);
}
} else { // Internal variable.
- typval_T tv;
-
- // get_name_len() takes care of expanding curly braces
- const char *name = p;
- char *tofree;
- len = get_name_len((const char **)&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) {
- tv_clear(&tv);
- }
- }
- }
- if (*p != NUL)
- n = FALSE;
-
- xfree(tofree);
+ n = var_exists(p);
}
rettv->vval.v_number = n;
diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c
index cc0ec71627..d2d4ca796f 100644
--- a/src/nvim/ex_cmds2.c
+++ b/src/nvim/ex_cmds2.c
@@ -120,6 +120,9 @@ struct source_cookie {
/// batch mode debugging: don't save and restore typeahead.
static bool debug_greedy = false;
+static char *debug_oldval = NULL; // old and newval for debug expressions
+static char *debug_newval = NULL;
+
/// Debug mode. Repeatedly get Ex commands, until told to continue normal
/// execution.
void do_debug(char_u *cmd)
@@ -166,6 +169,16 @@ void do_debug(char_u *cmd)
if (!debug_did_msg) {
MSG(_("Entering Debug mode. Type \"cont\" to continue."));
}
+ if (debug_oldval != NULL) {
+ smsg(_("Oldval = \"%s\""), debug_oldval);
+ xfree(debug_oldval);
+ debug_oldval = NULL;
+ }
+ if (debug_newval != NULL) {
+ smsg(_("Newval = \"%s\""), debug_newval);
+ xfree(debug_newval);
+ debug_newval = NULL;
+ }
if (sourcing_name != NULL) {
msg(sourcing_name);
}
@@ -174,7 +187,6 @@ void do_debug(char_u *cmd)
} else {
smsg(_("cmd: %s"), cmd);
}
-
// Repeat getting a command and executing it.
for (;; ) {
msg_scroll = true;
@@ -514,11 +526,13 @@ bool dbg_check_skipped(exarg_T *eap)
/// This is a grow-array of structs.
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 or 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
+ typval_T *dbg_val; ///< last result of watchexpression
+ int dbg_level; ///< stored nested level for expr
};
static garray_T dbg_breakp = { 0, 0, sizeof(struct debuggy), 4, NULL };
@@ -530,6 +544,7 @@ static int last_breakp = 0; // nr of last defined breakpoint
static garray_T prof_ga = { 0, 0, sizeof(struct debuggy), 4, NULL };
#define DBG_FUNC 1
#define DBG_FILE 2
+#define DBG_EXPR 3
/// Parse the arguments of ":profile", ":breakadd" or ":breakdel" and put them
@@ -562,6 +577,8 @@ static int dbg_parsearg(char_u *arg, garray_T *gap)
}
bp->dbg_type = DBG_FILE;
here = true;
+ } else if (gap != &prof_ga && STRNCMP(p, "expr", 4) == 0) {
+ bp->dbg_type = DBG_EXPR;
} else {
EMSG2(_(e_invarg2), p);
return FAIL;
@@ -590,6 +607,9 @@ static int dbg_parsearg(char_u *arg, garray_T *gap)
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);
+ bp->dbg_val = eval_expr(bp->dbg_name);
} else {
// Expand the file name in the same way as do_source(). This means
// doing it twice, so that $DIR/file gets expanded when $DIR is
@@ -621,7 +641,6 @@ static int dbg_parsearg(char_u *arg, garray_T *gap)
void ex_breakadd(exarg_T *eap)
{
struct debuggy *bp;
- char_u *pat;
garray_T *gap;
gap = &dbg_breakp;
@@ -633,22 +652,28 @@ void 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) {
- bp->dbg_prog = vim_regcomp(pat, RE_MAGIC + RE_STRING);
- xfree(pat);
- }
- if (pat == NULL || bp->dbg_prog == NULL) {
- xfree(bp->dbg_name);
- } else {
- if (bp->dbg_lnum == 0) { // default line number is 1
- bp->dbg_lnum = 1;
+ if (bp->dbg_type != DBG_EXPR) {
+ char_u *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);
+ xfree(pat);
}
- if (eap->cmdidx != CMD_profile) {
- DEBUGGY(gap, gap->ga_len).dbg_nr = ++last_breakp;
- debug_tick++;
+ if (pat == NULL || bp->dbg_prog == NULL) {
+ xfree(bp->dbg_name);
+ } else {
+ if (bp->dbg_lnum == 0) { // default line number is 1
+ bp->dbg_lnum = 1;
+ }
+ if (eap->cmdidx != CMD_profile) {
+ DEBUGGY(gap, gap->ga_len).dbg_nr = ++last_breakp;
+ debug_tick++;
+ }
+ gap->ga_len++;
}
- gap->ga_len++;
+ } else {
+ // DBG_EXPR
+ DEBUGGY(gap, gap->ga_len++).dbg_nr = ++last_breakp;
+ debug_tick++;
}
}
}
@@ -691,7 +716,7 @@ void ex_breakdel(exarg_T *eap)
todel = 0;
del_all = true;
} else {
- // ":breakdel {func|file} [lnum] {name}"
+ // ":breakdel {func|file|expr} [lnum] {name}"
if (dbg_parsearg(eap->arg, gap) == FAIL) {
return;
}
@@ -716,6 +741,10 @@ void ex_breakdel(exarg_T *eap)
} else {
while (!GA_EMPTY(gap)) {
xfree(DEBUGGY(gap, todel).dbg_name);
+ if (DEBUGGY(gap, todel).dbg_type == DBG_EXPR
+ && DEBUGGY(gap, todel).dbg_val != NULL) {
+ tv_free(DEBUGGY(gap, todel).dbg_val);
+ }
vim_regfree(DEBUGGY(gap, todel).dbg_prog);
gap->ga_len--;
if (todel < gap->ga_len) {
@@ -750,11 +779,15 @@ void ex_breaklist(exarg_T *eap)
if (bp->dbg_type == DBG_FILE) {
home_replace(NULL, bp->dbg_name, NameBuff, MAXPATHL, true);
}
- smsg(_("%3d %s %s line %" PRId64),
- bp->dbg_nr,
- bp->dbg_type == DBG_FUNC ? "func" : "file",
- bp->dbg_type == DBG_FUNC ? bp->dbg_name : NameBuff,
- (int64_t)bp->dbg_lnum);
+ if (bp->dbg_type != DBG_EXPR) {
+ smsg(_("%3d %s %s line %" PRId64),
+ bp->dbg_nr,
+ bp->dbg_type == DBG_FUNC ? "func" : "file",
+ bp->dbg_type == DBG_FUNC ? bp->dbg_name : NameBuff,
+ (int64_t)bp->dbg_lnum);
+ } else {
+ smsg(_("%3d expr %s"), bp->dbg_nr, bp->dbg_name);
+ }
}
}
}
@@ -814,6 +847,7 @@ debuggy_find(
// an already found breakpoint.
bp = &DEBUGGY(gap, i);
if ((bp->dbg_type == DBG_FILE) == file
+ && bp->dbg_type != DBG_EXPR
&& (gap == &prof_ga
|| (bp->dbg_lnum > after && (lnum == 0 || bp->dbg_lnum < lnum)))) {
// Save the value of got_int and reset it. We don't want a
@@ -828,6 +862,49 @@ debuggy_find(
}
}
got_int |= prev_got_int;
+ } else if (bp->dbg_type == DBG_EXPR) {
+ bool line = false;
+
+ prev_got_int = got_int;
+ got_int = false;
+
+ typval_T *tv = eval_expr(bp->dbg_name);
+ 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) {
+ line = true;
+ debug_oldval = typval_tostring(bp->dbg_val);
+ typval_T *v = eval_expr(bp->dbg_name);
+ debug_newval = typval_tostring(v);
+ tv_free(bp->dbg_val);
+ bp->dbg_val = v;
+ }
+ }
+ tv_free(tv);
+ }
+ } else if (bp->dbg_val != NULL) {
+ debug_oldval = typval_tostring(bp->dbg_val);
+ debug_newval = typval_tostring(NULL);
+ tv_free(bp->dbg_val);
+ bp->dbg_val = NULL;
+ line = true;
+ }
+
+ if (line) {
+ lnum = after > 0 ? after : 1;
+ break;
+ }
+
+ got_int |= prev_got_int;
}
}
if (name != fname) {