diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/eval.c | 576 | ||||
-rw-r--r-- | src/ex_cmds.c | 4 | ||||
-rw-r--r-- | src/ex_cmds.h | 4 |
3 files changed, 416 insertions, 168 deletions
diff --git a/src/eval.c b/src/eval.c index e812e5329..6ed5c9fab 100644 --- a/src/eval.c +++ b/src/eval.c @@ -82,13 +82,23 @@ struct listitem_S typedef struct listitem_S listitem; /* - * Structure to hold the info about a list. + * Struct used by those that are using an item in a list. + */ +typedef struct listwatch_S +{ + listitem *lw_item; /* item being watched */ + struct listwatch_S *lw_next; /* next watcher */ +} listwatch; + +/* + * Structure to hold info about a list. */ struct listvar_S { int lv_refcount; /* reference count */ listitem *lv_first; /* first item, NULL if none */ listitem *lv_last; /* last item, NULL if none */ + listwatch *lv_watch; /* first watcher, NULL if none */ }; typedef struct listvar_S listvar; @@ -170,6 +180,18 @@ struct funccall }; /* + * Info used by a ":for" loop. + */ +typedef struct forinfo_S +{ + int fi_semicolon; /* TRUE if ending in '; var]' */ + int fi_varcount; /* nr of variables in the list */ + listwatch fi_lw; /* keep an eye on the item used. */ + listvar *fi_list; /* list being used */ +} forinfo; + + +/* * Return the name of the executed function. */ char_u * @@ -504,10 +526,14 @@ static void call_user_func __ARGS((ufunc_T *fp, int argcount, typeval *argvars, static char_u * make_expanded_name __ARGS((char_u *in_start, char_u *expr_start, char_u *expr_end, char_u *in_end)); +static int ex_let_vars __ARGS((char_u *arg, typeval *tv, int copy, int semicolon, int var_count, char_u *nextchars)); +static char_u *skip_var_list __ARGS((char_u *arg, int *var_count, int *semicolon)); +static char_u *skip_var_one __ARGS((char_u *arg)); static void list_all_vars __ARGS((void)); static char_u *list_arg_vars __ARGS((exarg_T *eap, char_u *arg)); static char_u *ex_let_one __ARGS((char_u *arg, typeval *tv, int copy, char_u *endchars)); static char_u *set_var_idx __ARGS((char_u *name, char_u *ip, typeval *rettv, int copy, char_u *endchars)); +static void list_add_watch __ARGS((listvar *l, listwatch *lw)); /* * Set an internal variable to a string value. Creates the variable if it does @@ -1021,14 +1047,16 @@ ex_let(eap) int i; int var_count = 0; int semicolon = 0; - listvar *l; - listitem *item; - if (*arg != '[') - expr = vim_strchr(find_name_end(arg, NULL, NULL, TRUE), '='); - if (*arg != '[' && expr == NULL) + expr = skip_var_list(arg, &var_count, &semicolon); + if (expr == NULL) + return; + expr = vim_strchr(expr, '='); + if (expr == NULL) { - if (!ends_excmd(*arg)) + if (*arg == '[') + EMSG(_(e_invarg)); + else if (!ends_excmd(*arg)) /* ":let var1 var2" */ arg = list_arg_vars(eap, arg); else if (!eap->skip) @@ -1038,54 +1066,11 @@ ex_let(eap) } else { - if (*arg == '[') - { - /* ":let [a, b] = expr": find the matching ']' to get to the - * expression. */ - while (1) - { - arg = skipwhite(arg + 1); - if (vim_strchr((char_u *)"$@&", *arg) != NULL) - ++arg; - expr = find_name_end(arg, NULL, NULL, TRUE); - if (expr == arg) - { - EMSG2(_(e_invarg2), arg); - return; - } - ++var_count; - - arg = skipwhite(expr); - if (*arg == ']') - break; - else if (*arg == ';') - { - if (semicolon == 1) - { - EMSG(_("Double ; in :let")); - return; - } - semicolon = 1; - } - else if (*arg != ',') - { - EMSG2(_(e_invarg2), arg); - return; - } - } - - /* check for '=' after the ']' */ - expr = skipwhite(arg + 1); - if (*expr != '=') - { - EMSG(_(e_letunexp)); - return; - } - } + expr = skipwhite(expr + 1); if (eap->skip) ++emsg_skip; - i = eval0(expr + 1, &rettv, &eap->nextcmd, !eap->skip); + i = eval0(expr, &rettv, &eap->nextcmd, !eap->skip); if (eap->skip) { if (i != FAIL) @@ -1094,70 +1079,169 @@ ex_let(eap) } else if (i != FAIL) { - /* Move "arg" back to the variable name(s). */ - arg = eap->arg; - if (*arg != '[') + (void)ex_let_vars(eap->arg, &rettv, FALSE, semicolon, var_count, + (char_u *)"="); + clear_tv(&rettv); + } + } +} + +/* + * Assign the typevalue "tv" to the variable or variables at "arg_start". + * Handles both "var" with any type and "[var, var; var]" with a list type. + * Returns OK or FAIL; + */ + static int +ex_let_vars(arg_start, tv, copy, semicolon, var_count, nextchars) + char_u *arg_start; + typeval *tv; + int copy; /* copy values from "tv", don't move */ + int semicolon; /* from skip_var_list() */ + int var_count; /* from skip_var_list() */ + char_u *nextchars; /* characters that must follow or NULL */ +{ + char_u *arg = arg_start; + listvar *l; + int i; + listitem *item; + typeval ltv; + + if (*arg != '[') + { + /* + * ":let var = expr" or ":for var in list" + */ + if (ex_let_one(arg, tv, copy, nextchars) == NULL) + return FAIL; + return OK; + } + + /* + * ":let [v1, v2] = list" or ":for [v1, v2] in listlist" + */ + l = tv->vval.v_list; + if (tv->v_type != VAR_LIST || l == NULL) + { + EMSG(_(e_listreq)); + return FAIL; + } + + i = list_len(l); + if (semicolon == 0 && var_count < i) + { + EMSG(_("E999: Less targets than List items")); + return FAIL; + } + if (var_count - semicolon > i) + { + EMSG(_("E999: More targets than List items")); + return FAIL; + } + + item = l->lv_first; + while (*arg != ']') + { + arg = skipwhite(arg + 1); + arg = ex_let_one(arg, &item->li_tv, TRUE, (char_u *)",;]"); + item = item->li_next; + if (arg == NULL) + return FAIL; + + arg = skipwhite(arg); + if (*arg == ';') + { + /* Put the rest of the list (may be empty) in the var after ';'. + * Create a new list for this. */ + l = list_alloc(); + if (l == NULL) + return FAIL; + while (item != NULL) + { + list_append_tv(l, &item->li_tv); + item = item->li_next; + } + + ltv.v_type = VAR_LIST; + ltv.vval.v_list = l; + l->lv_refcount = 1; + + arg = ex_let_one(skipwhite(arg + 1), <v, FALSE, (char_u *)"]"); + clear_tv(<v); + if (arg == NULL) + return FAIL; + break; + } + else if (*arg != ',' && *arg != ']') + { + EMSG2(_(e_intern2), "ex_let_vars()"); + return FAIL; + } + } + + return OK; +} + +/* + * Skip over assignable variable "var" or list of variables "[var, var]". + * Used for ":let varvar = expr" and ":for varvar in expr". + * For "[var, var]" increment "*var_count" for each variable. + * for "[var, var; var]" set "semicolon". + * Return NULL for an error. + */ + static char_u * +skip_var_list(arg, var_count, semicolon) + char_u *arg; + int *var_count; + int *semicolon; +{ + char_u *p, *s; + + if (*arg == '[') + { + /* "[var, var]": find the matching ']'. */ + p = arg; + while (1) + { + p = skipwhite(p + 1); /* skip whites after '[', ';' or ',' */ + s = skip_var_one(p); + if (s == p) { - /* ":let var = expr" */ - (void)ex_let_one(arg, &rettv, FALSE, (char_u *)"="); + EMSG2(_(e_invarg2), p); + return NULL; } - else + ++*var_count; + + p = skipwhite(s); + if (*p == ']') + break; + else if (*p == ';') { - /* ":let [v1, v2] = list" */ - l = rettv.vval.v_list; - if (rettv.v_type != VAR_LIST || l == NULL) - EMSG(_("E999: List required")); - else + if (*semicolon == 1) { - i = list_len(l); - if (semicolon == 0 && var_count < i) - EMSG(_("E999: Less targets than List items")); - else if (var_count - semicolon > i) - EMSG(_("E999: More targets than List items")); - else - { - item = l->lv_first; - while (*arg != ']') - { - arg = skipwhite(arg + 1); - arg = ex_let_one(arg, &item->li_tv, - TRUE, (char_u *)",;]"); - item = item->li_next; - if (arg == NULL) - break; - - arg = skipwhite(arg); - if (*arg == ';') - { - /* Put the rest of the list (may be empty) in - * the var after ';'. */ - l = list_alloc(); - if (l == NULL) - break; - while (item != NULL) - { - list_append_tv(l, &item->li_tv); - item = item->li_next; - } - list_unref(rettv.vval.v_list); - rettv.vval.v_list = l; - l->lv_refcount = 1; - (void)ex_let_one(skipwhite(arg + 1), &rettv, - FALSE, (char_u *)"]"); - break; - } - else if (*arg != ',' && *arg != ']') - { - EMSG2(_(e_intern2), "ex_let()"); - break; - } - } - } + EMSG(_("Double ; in list of variables")); + return NULL; } + *semicolon = 1; + } + else if (*p != ',') + { + EMSG2(_(e_invarg2), p); + return NULL; } - clear_tv(&rettv); } + return p + 1; } + else + return skip_var_one(arg); +} + + static char_u * +skip_var_one(arg) + char_u *arg; +{ + if (vim_strchr((char_u *)"$@&", *arg) != NULL) + ++arg; + return find_name_end(arg, NULL, NULL, TRUE); } static void @@ -1309,7 +1393,7 @@ ex_let_one(arg, tv, copy, endchars) char_u *arg; /* points to variable name */ typeval *tv; /* value to assign to variable */ int copy; /* copy value from "tv" */ - char_u *endchars; /* valid chars after variable name */ + char_u *endchars; /* valid chars after variable name or NULL */ { int c1; char_u *name; @@ -1331,7 +1415,8 @@ ex_let_one(arg, tv, copy, endchars) EMSG2(_(e_invarg2), name - 1); else { - if (vim_strchr(endchars, *skipwhite(arg)) == NULL) + if (endchars != NULL + && vim_strchr(endchars, *skipwhite(arg)) == NULL) EMSG(_(e_letunexp)); else { @@ -1360,7 +1445,8 @@ ex_let_one(arg, tv, copy, endchars) { /* Find the end of the name. */ p = find_option_end(&arg, &opt_flags); - if (p == NULL || vim_strchr(endchars, *skipwhite(p)) == NULL) + if (p == NULL || (endchars != NULL + && vim_strchr(endchars, *skipwhite(p)) == NULL)) EMSG(_(e_letunexp)); else { @@ -1379,7 +1465,8 @@ ex_let_one(arg, tv, copy, endchars) else if (*arg == '@') { ++arg; - if (vim_strchr(endchars, *skipwhite(arg + 1)) == NULL) + if (endchars != NULL + && vim_strchr(endchars, *skipwhite(arg + 1)) == NULL) EMSG(_(e_letunexp)); else { @@ -1415,7 +1502,8 @@ ex_let_one(arg, tv, copy, endchars) } else if (*p == '[') arg_end = set_var_idx(arg, p, tv, copy, endchars); - else if (vim_strchr(endchars, *skipwhite(p)) == NULL) + else if (endchars != NULL + && vim_strchr(endchars, *skipwhite(p)) == NULL) EMSG(_(e_letunexp)); else if (STRNCMP(arg, "b:changedtick", 13) == 0 && !eval_isnamec(arg[13])) @@ -1505,7 +1593,7 @@ set_var_idx(name, ip, rettv, copy, endchars) if (p != NULL) { - if (vim_strchr(endchars, *p) == NULL) + if (endchars != NULL && vim_strchr(endchars, *p) == NULL) { EMSG(_(e_letunexp)); p = NULL; @@ -1525,6 +1613,157 @@ set_var_idx(name, ip, rettv, copy, endchars) return p; } +/* + * Add a watcher to a list. + */ + static void +list_add_watch(l, lw) + listvar *l; + listwatch *lw; +{ + lw->lw_next = l->lv_watch; + l->lv_watch = lw; +} + +/* + * Remove a watches from a list. + * No warning when it isn't found... + */ + static void +list_rem_watch(l, lwrem) + listvar *l; + listwatch *lwrem; +{ + listwatch *lw, **lwp; + + lwp = &l->lv_watch; + for (lw = l->lv_watch; lw != NULL; lw = lw->lw_next) + { + if (lw == lwrem) + { + *lwp = lw->lw_next; + break; + } + lwp = &lw->lw_next; + } +} + +/* + * Just before removing an item from a list: advance watchers to the next + * item. + */ + static void +list_fix_watch(l, item) + listvar *l; + listitem *item; +{ + listwatch *lw; + + for (lw = l->lv_watch; lw != NULL; lw = lw->lw_next) + if (lw->lw_item == item) + lw->lw_item = item->li_next; +} + +/* + * Evaluate the expression used in a ":for var in expr" command. + * "arg" points to "var". + * Set "*errp" to TRUE for an error, FALSE otherwise; + * Return a pointer that holds the info. Null when there is an error. + */ + void * +eval_for_line(arg, errp, nextcmdp, skip) + char_u *arg; + int *errp; + char_u **nextcmdp; + int skip; +{ + forinfo *fi; + char_u *expr; + typeval tv; + listvar *l; + + *errp = TRUE; /* default: there is an error */ + + fi = (forinfo *)alloc_clear(sizeof(forinfo)); + if (fi == NULL) + return NULL; + + expr = skip_var_list(arg, &fi->fi_varcount, &fi->fi_semicolon); + if (expr == NULL) + return fi; + + expr = skipwhite(expr); + if (expr[0] != 'i' || expr[1] != 'n' || !vim_iswhite(expr[2])) + { + EMSG(_("E999: Missing \"in\" after :for")); + return fi; + } + + if (skip) + ++emsg_skip; + if (eval0(skipwhite(expr + 2), &tv, nextcmdp, !skip) == OK) + { + *errp = FALSE; + if (!skip) + { + l = tv.vval.v_list; + if (tv.v_type != VAR_LIST || l == NULL) + EMSG(_(e_listreq)); + else + { + fi->fi_list = l; + list_add_watch(l, &fi->fi_lw); + fi->fi_lw.lw_item = l->lv_first; + } + } + } + if (skip) + --emsg_skip; + + return fi; +} + +/* + * Use the first item in a ":for" list. Advance to the next. + * Assign the values to the variable (list). "arg" points to the first one. + * Return TRUE when a valid item was found, FALSE when at end of list or + * something wrong. + */ + int +next_for_item(fi_void, arg) + void *fi_void; + char_u *arg; +{ + forinfo *fi = (forinfo *)fi_void; + int result; + listitem *item; + + item = fi->fi_lw.lw_item; + if (item == NULL) + result = FALSE; + else + { + fi->fi_lw.lw_item = item->li_next; + result = (ex_let_vars(arg, &item->li_tv, TRUE, + fi->fi_semicolon, fi->fi_varcount, NULL) == OK); + } + return result; +} + +/* + * Free the structure used to store info used by ":for". + */ + void +free_for_info(fi_void) + void *fi_void; +{ + forinfo *fi = (forinfo *)fi_void; + + if (fi->fi_list != NULL) + list_rem_watch(fi->fi_list, &fi->fi_lw); + vim_free(fi); +} + #if defined(FEAT_CMDL_COMPL) || defined(PROTO) void @@ -1535,10 +1774,27 @@ set_context_for_expression(xp, arg, cmdidx) { int got_eq = FALSE; int c; + char_u *p; - xp->xp_context = cmdidx == CMD_let ? EXPAND_USER_VARS - : cmdidx == CMD_call ? EXPAND_FUNCTIONS - : EXPAND_EXPRESSION; + if (cmdidx == CMD_let) + { + xp->xp_context = EXPAND_USER_VARS; + if (vim_strchr(arg, '=') == NULL) + { + /* ":let var1 var2 ...": find last space. */ + for (p = arg + STRLEN(arg); p > arg; ) + { + xp->xp_pattern = p; + p = mb_ptr_back(arg, p); + if (vim_iswhite(*p)) + break; + } + return; + } + } + else + xp->xp_context = cmdidx == CMD_call ? EXPAND_FUNCTIONS + : EXPAND_EXPRESSION; while ((xp->xp_pattern = vim_strpbrk(arg, (char_u *)"\"'+-*/%.=!?~|&$([<>,#")) != NULL) { @@ -1601,7 +1857,9 @@ set_context_for_expression(xp, arg, cmdidx) xp->xp_context = EXPAND_EXPRESSION; } else - xp->xp_context = EXPAND_NOTHING; + /* Doesn't look like something valid, expand as an expression + * anyway. */ + xp->xp_context = EXPAND_EXPRESSION; arg = xp->xp_pattern; if (*arg != NUL) while ((c = *++arg) != NUL && (c == ' ' || c == '\t')) @@ -3303,7 +3561,7 @@ listitem_alloc() } /* - * Free a list item. Also clears the value; + * Free a list item. Also clears the value. Does not notify watchers. */ static void listitem_free(item) @@ -3471,6 +3729,7 @@ list_getrem(l, n) item = list_find(l, n); if (item != NULL) { + list_fix_watch(l, item); /* notify watchers */ if (item->li_next == NULL) l->lv_last = item->li_prev; else @@ -11806,8 +12065,7 @@ read_viminfo_varlist(virp, writing) { char_u *tab; int is_string = FALSE; - typeval *tvp = NULL; - char_u *val; + typeval tv; if (!writing && (find_viminfo_parameter('!') != NULL)) { @@ -11821,29 +12079,20 @@ read_viminfo_varlist(virp, writing) tab = vim_strchr(tab, '\t'); if (tab != NULL) { - /* create a typeval to hold the value */ if (is_string) { - val = viminfo_readstring(virp, + tv.v_type = VAR_STRING; + tv.vval.v_string = viminfo_readstring(virp, (int)(tab - virp->vir_line + 1), TRUE); - if (val != NULL) - tvp = alloc_string_tv(val); } else { - tvp = alloc_tv(); - if (tvp != NULL) - { - tvp->v_type = VAR_NUMBER; - tvp->vval.v_number = atol((char *)tab + 1); - } - } - /* assign the value to the variable */ - if (tvp != NULL) - { - set_var(virp->vir_line + 1, tvp, FALSE); - free_tv(tvp); + tv.v_type = VAR_NUMBER; + tv.vval.v_number = atol((char *)tab + 1); } + set_var(virp->vir_line + 1, &tv, FALSE); + if (is_string) + vim_free(tv.vval.v_string); } } } @@ -11878,11 +12127,7 @@ write_viminfo_varlist(fp) { case VAR_STRING: s = "STR"; break; case VAR_NUMBER: s = "NUM"; break; - case VAR_LIST: s = "LST"; break; - case VAR_FUNC: s = "FUN"; break; - default: - EMSGN(_("E999: Internal error: write_viminfo_varlist(): %ld"), (long)this_var->tv.v_type); - s = "ERR"; + default: continue; } fprintf(fp, "!%s\t%s\t", this_var->v_name, s); viminfo_writestring(fp, tv2string(&this_var->tv, &tofree)); @@ -11905,34 +12150,33 @@ store_session_globals(fd) for (i = gap->ga_len; --i >= 0; ) { this_var = &VAR_GAP_ENTRY(i, gap); - if (this_var->v_name != NULL) + if (this_var->v_name != NULL + && (this_var->tv.v_type == VAR_NUMBER + || this_var->tv.v_type == VAR_STRING) + && var_flavour(this_var->v_name) == VAR_FLAVOUR_SESSION) { - if (var_flavour(this_var->v_name) == VAR_FLAVOUR_SESSION) - { - /* Escapse special characters with a backslash. Turn a LF and - * CR into \n and \r. */ - p = vim_strsave_escaped(get_var_string(this_var), + /* Escape special characters with a backslash. Turn a LF and + * CR into \n and \r. */ + p = vim_strsave_escaped(get_var_string(this_var), (char_u *)"\\\"\n\r"); - if (p == NULL) /* out of memory */ - continue; - for (t = p; *t != NUL; ++t) - if (*t == '\n') - *t = 'n'; - else if (*t == '\r') - *t = 'r'; - if ((fprintf(fd, "let %s = %c%s%c", - this_var->v_name, - (this_var->tv.v_type == VAR_STRING) ? '"' : ' ', - p, - (this_var->tv.v_type == VAR_STRING) ? '"' : ' ') < 0) - || put_eol(fd) == FAIL) - { - vim_free(p); - return FAIL; - } + if (p == NULL) /* out of memory */ + continue; + for (t = p; *t != NUL; ++t) + if (*t == '\n') + *t = 'n'; + else if (*t == '\r') + *t = 'r'; + if ((fprintf(fd, "let %s = %c%s%c", + this_var->v_name, + (this_var->tv.v_type == VAR_STRING) ? '"' : ' ', + p, + (this_var->tv.v_type == VAR_STRING) ? '"' : ' ') < 0) + || put_eol(fd) == FAIL) + { vim_free(p); + return FAIL; } - + vim_free(p); } } return OK; diff --git a/src/ex_cmds.c b/src/ex_cmds.c index adac95b83..7bcdaf2e9 100644 --- a/src/ex_cmds.c +++ b/src/ex_cmds.c @@ -3258,13 +3258,13 @@ ex_append(eap) if (eap->getline == NULL) theline = getcmdline( #ifdef FEAT_EVAL - eap->cstack->cs_whilelevel > 0 ? -1 : + eap->cstack->cs_looplevel > 0 ? -1 : #endif NUL, 0L, 0); else theline = eap->getline( #ifdef FEAT_EVAL - eap->cstack->cs_whilelevel > 0 ? -1 : + eap->cstack->cs_looplevel > 0 ? -1 : #endif NUL, eap->cookie, 0); lines_left = Rows - 1; diff --git a/src/ex_cmds.h b/src/ex_cmds.h index b44dd1de2..870fcb43b 100644 --- a/src/ex_cmds.h +++ b/src/ex_cmds.h @@ -342,6 +342,8 @@ EX(CMD_endif, "endif", ex_endif, TRLBAR|SBOXOK|CMDWIN), EX(CMD_endfunction, "endfunction", ex_endfunction, TRLBAR|CMDWIN), +EX(CMD_endfor, "endfor", ex_endwhile, + TRLBAR|SBOXOK|CMDWIN), EX(CMD_endtry, "endtry", ex_endtry, TRLBAR|SBOXOK|CMDWIN), EX(CMD_endwhile, "endwhile", ex_endwhile, @@ -382,6 +384,8 @@ EX(CMD_folddoclosed, "folddoclosed", ex_folddo, RANGE|DFLALL|NEEDARG|EXTRA|NOTRLCOM), EX(CMD_foldopen, "foldopen", ex_foldopen, RANGE|BANG|WHOLEFOLD|TRLBAR|SBOXOK|CMDWIN), +EX(CMD_for, "for", ex_while, + EXTRA|NOTRLCOM|SBOXOK|CMDWIN), EX(CMD_function, "function", ex_function, EXTRA|BANG|CMDWIN), EX(CMD_global, "global", ex_global, |