diff options
-rw-r--r-- | runtime/doc/repeat.txt | 13 | ||||
-rw-r--r-- | src/nvim/eval.c | 424 | ||||
-rw-r--r-- | src/nvim/eval.h | 13 | ||||
-rw-r--r-- | src/nvim/eval/funcs.c | 25 | ||||
-rw-r--r-- | src/nvim/ex_cmds2.c | 125 |
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) { |