To: vim_dev@googlegroups.com Subject: Patch 8.2.1071 Fcc: outbox From: Bram Moolenaar Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ------------ Patch 8.2.1071 Problem: Vim9: no line break allowed inside a lambda. Solution: Handle line break inside a lambda in Vim9 script. Files: src/eval.c, src/proto/eval.pro, src/evalvars.c, src/userfunc.c, src/proto/userfunc.pro, src/popupwin.c, src/vim9compile.c, src/ex_eval.c, src/globals.h, src/structs.h, src/testdir/test_vim9_expr.vim *** ../vim-8.2.1070/src/eval.c 2020-06-27 14:11:50.494644105 +0200 --- src/eval.c 2020-06-27 18:02:08.197832760 +0200 *************** *** 325,332 **** if (skip) ++emsg_skip; ! if (eval0(arg, &tv, eap, skip ? NULL : &EVALARG_EVALUATE) ! == FAIL || skip) retval = NULL; else { --- 325,331 ---- if (skip) ++emsg_skip; ! if (eval0(arg, &tv, eap, skip ? NULL : &EVALARG_EVALUATE) == FAIL || skip) retval = NULL; else { *************** *** 353,358 **** --- 352,412 ---- } /* + * Skip over an expression at "*pp". + * If in Vim9 script and line breaks are encountered, the lines are + * concatenated. "evalarg->eval_tofree" will be set accordingly. + * Return FAIL for an error, OK otherwise. + */ + int + skip_expr_concatenate(char_u **start, char_u **end, evalarg_T *evalarg) + { + typval_T rettv; + int res; + int vim9script = current_sctx.sc_version == SCRIPT_VERSION_VIM9; + garray_T *gap = &evalarg->eval_ga; + int save_flags = evalarg == NULL ? 0 : evalarg->eval_flags; + + if (vim9script && evalarg->eval_cookie != NULL) + { + ga_init2(gap, sizeof(char_u *), 10); + if (ga_grow(gap, 1) == OK) + // leave room for "start" + ++gap->ga_len; + } + + // Don't evaluate the expression. + if (evalarg != NULL) + evalarg->eval_flags &= ~EVAL_EVALUATE; + *end = skipwhite(*end); + res = eval1(end, &rettv, evalarg); + if (evalarg != NULL) + evalarg->eval_flags = save_flags; + + if (vim9script && evalarg->eval_cookie != NULL + && evalarg->eval_ga.ga_len > 1) + { + char_u *p; + size_t endoff = STRLEN(*end); + + // Line breaks encountered, concatenate all the lines. + *((char_u **)gap->ga_data) = *start; + p = ga_concat_strings(gap, ""); + *((char_u **)gap->ga_data) = NULL; + ga_clear_strings(gap); + gap->ga_itemsize = 0; + if (p == NULL) + return FAIL; + *start = p; + vim_free(evalarg->eval_tofree); + evalarg->eval_tofree = p; + // Compute "end" relative to the end. + *end = *start + STRLEN(*start) - endoff; + } + + return res; + } + + /* * Top level evaluation function, returning a string. * When "convert" is TRUE convert a List into a sequence of lines and convert * a Float to a String. *************** *** 1794,1807 **** } /* ! * To be called when eval_next_non_blank() sets "getnext" to TRUE. */ char_u * eval_next_line(evalarg_T *evalarg) { ! vim_free(evalarg->eval_tofree); ! evalarg->eval_tofree = getsourceline(0, evalarg->eval_cookie, 0, TRUE); ! return skipwhite(evalarg->eval_tofree); } /* --- 1848,1874 ---- } /* ! * To be called after eval_next_non_blank() sets "getnext" to TRUE. */ char_u * eval_next_line(evalarg_T *evalarg) { ! garray_T *gap = &evalarg->eval_ga; ! char_u *line; ! ! line = getsourceline(0, evalarg->eval_cookie, 0, TRUE); ! if (gap->ga_itemsize > 0 && ga_grow(gap, 1) == OK) ! { ! // Going to concatenate the lines after parsing. ! ((char_u **)gap->ga_data)[gap->ga_len] = line; ! ++gap->ga_len; ! } ! else ! { ! vim_free(evalarg->eval_tofree); ! evalarg->eval_tofree = line; ! } ! return skipwhite(line); } /* *************** *** 1831,1838 **** int called_emsg_before = called_emsg; int flags = evalarg == NULL ? 0 : evalarg->eval_flags; - if (evalarg != NULL) - evalarg->eval_tofree = NULL; p = skipwhite(arg); ret = eval1(&p, rettv, evalarg); --- 1898,1903 ---- *************** *** 1857,1878 **** if (eap != NULL) eap->nextcmd = check_nextcmd(p); ! if (evalarg != NULL) { ! if (eap != NULL) ! { ! if (evalarg->eval_tofree != NULL) ! { ! // We may need to keep the original command line, e.g. for ! // ":let" it has the variable names. But we may also need the ! // new one, "nextcmd" points into it. Keep both. ! vim_free(eap->cmdline_tofree); ! eap->cmdline_tofree = *eap->cmdlinep; ! *eap->cmdlinep = evalarg->eval_tofree; ! } ! } ! else ! vim_free(evalarg->eval_tofree); } return ret; --- 1922,1936 ---- if (eap != NULL) eap->nextcmd = check_nextcmd(p); ! if (evalarg != NULL && eap != NULL && evalarg->eval_tofree != NULL) { ! // We may need to keep the original command line, e.g. for ! // ":let" it has the variable names. But we may also need the ! // new one, "nextcmd" points into it. Keep both. ! vim_free(eap->cmdline_tofree); ! eap->cmdline_tofree = *eap->cmdlinep; ! *eap->cmdlinep = evalarg->eval_tofree; ! evalarg->eval_tofree = NULL; } return ret; *************** *** 2797,2803 **** * Lambda: {arg, arg -> expr} * Dictionary: {'key': val, 'key': val} */ ! case '{': ret = get_lambda_tv(arg, rettv, evaluate); if (ret == NOTDONE) ret = eval_dict(arg, rettv, evalarg, FALSE); break; --- 2855,2861 ---- * Lambda: {arg, arg -> expr} * Dictionary: {'key': val, 'key': val} */ ! case '{': ret = get_lambda_tv(arg, rettv, evalarg); if (ret == NOTDONE) ret = eval_dict(arg, rettv, evalarg, FALSE); break; *************** *** 2884,2890 **** // Handle following '[', '(' and '.' for expr[expr], expr.name, // expr(expr), expr->name(expr) if (ret == OK) ! ret = handle_subscript(arg, rettv, flags, TRUE); /* * Apply logical NOT and unary '-', from right to left, ignore '+'. --- 2942,2948 ---- // Handle following '[', '(' and '.' for expr[expr], expr.name, // expr(expr), expr->name(expr) if (ret == OK) ! ret = handle_subscript(arg, rettv, evalarg, TRUE); /* * Apply logical NOT and unary '-', from right to left, ignore '+'. *************** *** 3031,3039 **** eval_lambda( char_u **arg, typval_T *rettv, ! int evaluate, int verbose) // give error messages { typval_T base = *rettv; int ret; --- 3089,3099 ---- eval_lambda( char_u **arg, typval_T *rettv, ! evalarg_T *evalarg, int verbose) // give error messages { + int evaluate = evalarg != NULL + && (evalarg->eval_flags & EVAL_EVALUATE); typval_T base = *rettv; int ret; *************** *** 3041,3047 **** *arg += 2; rettv->v_type = VAR_UNKNOWN; ! ret = get_lambda_tv(arg, rettv, evaluate); if (ret != OK) return FAIL; else if (**arg != '(') --- 3101,3107 ---- *arg += 2; rettv->v_type = VAR_UNKNOWN; ! ret = get_lambda_tv(arg, rettv, evalarg); if (ret != OK) return FAIL; else if (**arg != '(') *************** *** 3136,3145 **** eval_index( char_u **arg, typval_T *rettv, ! int flags, int verbose) // give error messages { ! int evaluate = flags & EVAL_EVALUATE; int empty1 = FALSE, empty2 = FALSE; typval_T var1, var2; long i; --- 3196,3206 ---- eval_index( char_u **arg, typval_T *rettv, ! evalarg_T *evalarg, int verbose) // give error messages { ! int evaluate = evalarg != NULL ! && (evalarg->eval_flags & EVAL_EVALUATE); int empty1 = FALSE, empty2 = FALSE; typval_T var1, var2; long i; *************** *** 3200,3210 **** } else { - evalarg_T evalarg; - - CLEAR_FIELD(evalarg); - evalarg.eval_flags = flags; - /* * something[idx] * --- 3261,3266 ---- *************** *** 3213,3219 **** *arg = skipwhite(*arg + 1); if (**arg == ':') empty1 = TRUE; ! else if (eval1(arg, &var1, &evalarg) == FAIL) // recursive! return FAIL; else if (evaluate && tv_get_string_chk(&var1) == NULL) { --- 3269,3275 ---- *arg = skipwhite(*arg + 1); if (**arg == ':') empty1 = TRUE; ! else if (eval1(arg, &var1, evalarg) == FAIL) // recursive! return FAIL; else if (evaluate && tv_get_string_chk(&var1) == NULL) { *************** *** 3231,3237 **** *arg = skipwhite(*arg + 1); if (**arg == ']') empty2 = TRUE; ! else if (eval1(arg, &var2, &evalarg) == FAIL) // recursive! { if (!empty1) clear_tv(&var1); --- 3287,3293 ---- *arg = skipwhite(*arg + 1); if (**arg == ']') empty2 = TRUE; ! else if (eval1(arg, &var2, evalarg) == FAIL) // recursive! { if (!empty1) clear_tv(&var1); *************** *** 4884,4893 **** handle_subscript( char_u **arg, typval_T *rettv, ! int flags, // do more than finding the end int verbose) // give error messages { ! int evaluate = flags & EVAL_EVALUATE; int ret = OK; dict_T *selfdict = NULL; --- 4940,4950 ---- handle_subscript( char_u **arg, typval_T *rettv, ! evalarg_T *evalarg, int verbose) // give error messages { ! int evaluate = evalarg != NULL ! && (evalarg->eval_flags & EVAL_EVALUATE); int ret = OK; dict_T *selfdict = NULL; *************** *** 4926,4932 **** { if ((*arg)[2] == '{') // expr->{lambda}() ! ret = eval_lambda(arg, rettv, evaluate, verbose); else // expr->name() ret = eval_method(arg, rettv, evaluate, verbose); --- 4983,4989 ---- { if ((*arg)[2] == '{') // expr->{lambda}() ! ret = eval_lambda(arg, rettv, evalarg, verbose); else // expr->name() ret = eval_method(arg, rettv, evaluate, verbose); *************** *** 4943,4949 **** } else selfdict = NULL; ! if (eval_index(arg, rettv, flags, verbose) == FAIL) { clear_tv(rettv); ret = FAIL; --- 5000,5006 ---- } else selfdict = NULL; ! if (eval_index(arg, rettv, evalarg, verbose) == FAIL) { clear_tv(rettv); ret = FAIL; *** ../vim-8.2.1070/src/proto/eval.pro 2020-06-27 13:11:46.863462713 +0200 --- src/proto/eval.pro 2020-06-27 15:27:38.043790985 +0200 *************** *** 9,14 **** --- 9,15 ---- int eval_expr_to_bool(typval_T *expr, int *error); char_u *eval_to_string_skip(char_u *arg, exarg_T *eap, int skip); int skip_expr(char_u **pp); + int skip_expr_concatenate(char_u **start, char_u **end, evalarg_T *evalarg); char_u *eval_to_string(char_u *arg, int convert); char_u *eval_to_string_safe(char_u *arg, int use_sandbox); varnumber_T eval_to_number(char_u *expr); *************** *** 53,59 **** char_u *find_name_end(char_u *arg, char_u **expr_start, char_u **expr_end, int flags); int eval_isnamec(int c); int eval_isnamec1(int c); ! int handle_subscript(char_u **arg, typval_T *rettv, int flags, int verbose); int item_copy(typval_T *from, typval_T *to, int deep, int copyID); void echo_one(typval_T *rettv, int with_space, int *atstart, int *needclr); void ex_echo(exarg_T *eap); --- 54,60 ---- char_u *find_name_end(char_u *arg, char_u **expr_start, char_u **expr_end, int flags); int eval_isnamec(int c); int eval_isnamec1(int c); ! int handle_subscript(char_u **arg, typval_T *rettv, evalarg_T *evalarg, int verbose); int item_copy(typval_T *from, typval_T *to, int deep, int copyID); void echo_one(typval_T *rettv, int with_space, int *atstart, int *needclr); void ex_echo(exarg_T *eap); *** ../vim-8.2.1070/src/evalvars.c 2020-06-27 13:11:46.863462713 +0200 --- src/evalvars.c 2020-06-27 17:15:36.878996132 +0200 *************** *** 797,808 **** --- 797,810 ---- if (eap->skip) ++emsg_skip; + CLEAR_FIELD(evalarg); evalarg.eval_flags = eap->skip ? 0 : EVAL_EVALUATE; evalarg.eval_cookie = eap->getline == getsourceline ? eap->cookie : NULL; i = eval0(expr, &rettv, eap, &evalarg); if (eap->skip) --emsg_skip; + vim_free(evalarg.eval_tofree); } if (eap->skip) { *************** *** 1125,1131 **** { // handle d.key, l[idx], f(expr) arg_subsc = arg; ! if (handle_subscript(&arg, &tv, EVAL_EVALUATE, TRUE) == FAIL) error = TRUE; else --- 1127,1133 ---- { // handle d.key, l[idx], f(expr) arg_subsc = arg; ! if (handle_subscript(&arg, &tv, &EVALARG_EVALUATE, TRUE) == FAIL) error = TRUE; else *************** *** 3341,3347 **** if (n) { // handle d.key, l[idx], f(expr) ! n = (handle_subscript(&var, &tv, EVAL_EVALUATE, FALSE) == OK); if (n) clear_tv(&tv); } --- 3343,3349 ---- if (n) { // handle d.key, l[idx], f(expr) ! n = (handle_subscript(&var, &tv, &EVALARG_EVALUATE, FALSE) == OK); if (n) clear_tv(&tv); } *** ../vim-8.2.1070/src/userfunc.c 2020-06-27 13:11:46.863462713 +0200 --- src/userfunc.c 2020-06-27 17:55:25.163278860 +0200 *************** *** 391,398 **** * Return OK or FAIL. Returns NOTDONE for dict or {expr}. */ int ! get_lambda_tv(char_u **arg, typval_T *rettv, int evaluate) { garray_T newargs; garray_T newlines; garray_T *pnewargs; --- 391,400 ---- * Return OK or FAIL. Returns NOTDONE for dict or {expr}. */ int ! get_lambda_tv(char_u **arg, typval_T *rettv, evalarg_T *evalarg) { + int evaluate = evalarg != NULL + && (evalarg->eval_flags & EVAL_EVALUATE); garray_T newargs; garray_T newlines; garray_T *pnewargs; *************** *** 404,409 **** --- 406,413 ---- char_u *s, *e; int *old_eval_lavars = eval_lavars_used; int eval_lavars = FALSE; + int getnext; + char_u *tofree = NULL; ga_init(&newargs); ga_init(&newlines); *************** *** 432,443 **** // Get the start and the end of the expression. *arg = skipwhite(*arg + 1); s = *arg; ! ret = skip_expr(arg); if (ret == FAIL) goto errret; e = *arg; *arg = skipwhite(*arg); if (**arg != '}') { semsg(_("E451: Expected }: %s"), *arg); --- 436,460 ---- // Get the start and the end of the expression. *arg = skipwhite(*arg + 1); + eval_next_non_blank(*arg, evalarg, &getnext); + if (getnext) + *arg = eval_next_line(evalarg); s = *arg; ! ret = skip_expr_concatenate(&s, arg, evalarg); if (ret == FAIL) goto errret; + if (evalarg != NULL) + { + // avoid that the expression gets freed when another line break follows + tofree = evalarg->eval_tofree; + evalarg->eval_tofree = NULL; + } + e = *arg; *arg = skipwhite(*arg); + eval_next_non_blank(*arg, evalarg, &getnext); + if (getnext) + *arg = eval_next_line(evalarg); if (**arg != '}') { semsg(_("E451: Expected }: %s"), *arg); *************** *** 447,453 **** if (evaluate) { ! int len, flags = 0; char_u *p; char_u *name = get_lambda_name(); --- 464,471 ---- if (evaluate) { ! int len; ! int flags = 0; char_u *p; char_u *name = get_lambda_name(); *************** *** 464,470 **** goto errret; // Add "return " before the expression. ! len = 7 + e - s + 1; p = alloc(len); if (p == NULL) goto errret; --- 482,488 ---- goto errret; // Add "return " before the expression. ! len = 7 + (int)(e - s) + 1; p = alloc(len); if (p == NULL) goto errret; *************** *** 510,515 **** --- 528,534 ---- } eval_lavars_used = old_eval_lavars; + vim_free(tofree); return OK; errret: *************** *** 517,522 **** --- 536,542 ---- ga_clear_strings(&newlines); vim_free(fp); vim_free(pt); + vim_free(tofree); eval_lavars_used = old_eval_lavars; return FAIL; } *************** *** 3925,3932 **** dbg_check_breakpoint(eap); // Handle a function returning a Funcref, Dictionary or List. ! if (handle_subscript(&arg, &rettv, eap->skip ? 0 : EVAL_EVALUATE, ! TRUE) == FAIL) { failed = TRUE; break; --- 3945,3952 ---- dbg_check_breakpoint(eap); // Handle a function returning a Funcref, Dictionary or List. ! if (handle_subscript(&arg, &rettv, ! eap->skip ? NULL : &EVALARG_EVALUATE, TRUE) == FAIL) { failed = TRUE; break; *** ../vim-8.2.1070/src/proto/userfunc.pro 2020-06-25 19:27:53.036387595 +0200 --- src/proto/userfunc.pro 2020-06-27 14:42:50.009378168 +0200 *************** *** 3,10 **** hashtab_T *func_tbl_get(void); int get_function_args(char_u **argp, char_u endchar, garray_T *newargs, garray_T *argtypes, int *varargs, garray_T *default_args, int skip, exarg_T *eap, char_u **line_to_free); char_u *get_lambda_name(void); ! int get_lambda_tv(char_u **arg, typval_T *rettv, int evaluate); ! char_u *register_cfunc(cfunc_T cb, cfunc_free_T free_cb, void *state); char_u *deref_func_name(char_u *name, int *lenp, partial_T **partialp, int no_autoload); void emsg_funcname(char *ermsg, char_u *name); int get_func_tv(char_u *name, int len, typval_T *rettv, char_u **arg, funcexe_T *funcexe); --- 3,10 ---- hashtab_T *func_tbl_get(void); int get_function_args(char_u **argp, char_u endchar, garray_T *newargs, garray_T *argtypes, int *varargs, garray_T *default_args, int skip, exarg_T *eap, char_u **line_to_free); char_u *get_lambda_name(void); ! char_u *register_cfunc(cfunc_T cb, cfunc_free_T cb_free, void *state); ! int get_lambda_tv(char_u **arg, typval_T *rettv, evalarg_T *evalarg); char_u *deref_func_name(char_u *name, int *lenp, partial_T **partialp, int no_autoload); void emsg_funcname(char *ermsg, char_u *name); int get_func_tv(char_u *name, int len, typval_T *rettv, char_u **arg, funcexe_T *funcexe); *** ../vim-8.2.1070/src/popupwin.c 2020-06-15 21:19:04.017590497 +0200 --- src/popupwin.c 2020-06-27 14:41:58.969728917 +0200 *************** *** 384,390 **** vim_snprintf((char *)cbbuf, sizeof(cbbuf), "{_ -> popup_close(%d)}", wp->w_id); ! if (get_lambda_tv(&ptr, &tv, TRUE) == OK) { wp->w_popup_timer = create_timer(time, 0); wp->w_popup_timer->tr_callback = get_callback(&tv); --- 384,390 ---- vim_snprintf((char *)cbbuf, sizeof(cbbuf), "{_ -> popup_close(%d)}", wp->w_id); ! if (get_lambda_tv(&ptr, &tv, &EVALARG_EVALUATE) == OK) { wp->w_popup_timer = create_timer(time, 0); wp->w_popup_timer->tr_callback = get_callback(&tv); *** ../vim-8.2.1070/src/vim9compile.c 2020-06-27 14:11:50.494644105 +0200 --- src/vim9compile.c 2020-06-27 14:42:42.725427502 +0200 *************** *** 3001,3007 **** } else if (p == arg && *arg == '{') { ! int ret = get_lambda_tv(&p, &rettv, FALSE); // Can be "{x -> ret}()". // Can be "{'a': 1}->Func()". --- 3001,3007 ---- } else if (p == arg && *arg == '{') { ! int ret = get_lambda_tv(&p, &rettv, NULL); // Can be "{x -> ret}()". // Can be "{'a': 1}->Func()". *************** *** 3065,3071 **** ufunc_T *ufunc; // Get the funcref in "rettv". ! if (get_lambda_tv(arg, &rettv, TRUE) != OK) return FAIL; ufunc = rettv.vval.v_partial->pt_func; --- 3065,3071 ---- ufunc_T *ufunc; // Get the funcref in "rettv". ! if (get_lambda_tv(arg, &rettv, &EVALARG_EVALUATE) != OK) return FAIL; ufunc = rettv.vval.v_partial->pt_func; *************** *** 3095,3101 **** int ret = FAIL; // Get the funcref in "rettv". ! if (get_lambda_tv(arg, &rettv, TRUE) == FAIL) return FAIL; if (**arg != '(') --- 3095,3101 ---- int ret = FAIL; // Get the funcref in "rettv". ! if (get_lambda_tv(arg, &rettv, &EVALARG_EVALUATE) == FAIL) return FAIL; if (**arg != '(') *** ../vim-8.2.1070/src/ex_eval.c 2020-06-24 20:33:59.565106319 +0200 --- src/ex_eval.c 2020-06-27 15:47:09.489105718 +0200 *************** *** 897,902 **** --- 897,903 ---- typval_T tv; evalarg_T evalarg; + CLEAR_FIELD(evalarg); evalarg.eval_flags = eap->skip ? 0 : EVAL_EVALUATE; evalarg.eval_cookie = eap->getline == getsourceline ? eap->cookie : NULL; *** ../vim-8.2.1070/src/globals.h 2020-06-26 19:44:02.972305916 +0200 --- src/globals.h 2020-06-27 16:14:49.255935319 +0200 *************** *** 1883,1889 **** EXTERN listitem_T range_list_item; // Passed to an eval() function to enable evaluation. ! EXTERN evalarg_T EVALARG_EVALUATE INIT3(EVAL_EVALUATE, NULL, NULL); #endif #ifdef MSWIN --- 1883,1893 ---- EXTERN listitem_T range_list_item; // Passed to an eval() function to enable evaluation. ! EXTERN evalarg_T EVALARG_EVALUATE ! # ifdef DO_INIT ! = {EVAL_EVALUATE, NULL, {0, 0, 0, 0, NULL}, NULL} ! # endif ! ; #endif #ifdef MSWIN *** ../vim-8.2.1070/src/structs.h 2020-06-25 19:27:53.036387595 +0200 --- src/structs.h 2020-06-27 15:09:17.951377551 +0200 *************** *** 1763,1768 **** --- 1763,1773 ---- // copied from exarg_T when "getline" is "getsourceline". Can be NULL. void *eval_cookie; // argument for getline() + // Used to collect lines while parsing them, so that they can be + // concatenated later. Used when "eval_ga.ga_itemsize" is not zero. + // "eval_ga.ga_data" is a list of pointers to lines. + garray_T eval_ga; + // pointer to the line obtained with getsourceline() char_u *eval_tofree; } evalarg_T; *** ../vim-8.2.1070/src/testdir/test_vim9_expr.vim 2020-06-27 14:11:50.494644105 +0200 --- src/testdir/test_vim9_expr.vim 2020-06-27 16:06:06.482819706 +0200 *************** *** 1017,1022 **** --- 1017,1034 ---- assert_equal([1, 3, 5], [1, 2, 3]->map({key, val -> key + val})) enddef + def Test_expr7_lambda_vim9script() + let lines =<< trim END + vim9script + let v = 10->{a -> + a + + 2 + }() + assert_equal(12, v) + END + CheckScriptSuccess(lines) + enddef + def Test_expr7_dict() " dictionary assert_equal(g:dict_empty, {}) *** ../vim-8.2.1070/src/version.c 2020-06-27 17:04:01.298972727 +0200 --- src/version.c 2020-06-27 18:06:03.032763405 +0200 *************** *** 756,757 **** --- 756,759 ---- { /* Add new patch number below this line */ + /**/ + 1071, /**/ -- FROG: How you English say: I one more time, mac, I unclog my nose towards you, sons of a window-dresser, so, you think you could out-clever us French fellows with your silly knees-bent creeping about advancing behaviour. (blows a raspberry) I wave my private parts at your aunties, you brightly-coloured, mealy-templed, cranberry-smelling, electric donkey-bottom biters. "Monty Python and the Holy Grail" PYTHON (MONTY) PICTURES LTD /// Bram Moolenaar -- Bram@Moolenaar.net -- http://www.Moolenaar.net \\\ /// sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ \\\ \\\ an exciting new programming language -- http://www.Zimbu.org /// \\\ help me help AIDS victims -- http://ICCF-Holland.org ///