Mercurial > vim
comparison src/userfunc.c @ 30065:6cf788ab844c v9.0.0370
patch 9.0.0370: cleaning up afterwards can make a function messy
Commit: https://github.com/vim/vim/commit/1d84f7608f1e41dad03b8cc7925895437775f7c0
Author: Bram Moolenaar <Bram@vim.org>
Date: Sat Sep 3 21:35:53 2022 +0100
patch 9.0.0370: cleaning up afterwards can make a function messy
Problem: Cleaning up afterwards can make a function messy.
Solution: Add the :defer command.
author | Bram Moolenaar <Bram@vim.org> |
---|---|
date | Sat, 03 Sep 2022 22:45:03 +0200 |
parents | cd573d7bc30d |
children | d45ee1f829ba |
comparison
equal
deleted
inserted
replaced
30064:a8f1fbaa43c8 | 30065:6cf788ab844c |
---|---|
1726 if (p != name) | 1726 if (p != name) |
1727 vim_free(p); | 1727 vim_free(p); |
1728 } | 1728 } |
1729 | 1729 |
1730 /* | 1730 /* |
1731 * Allocate a variable for the result of a function. | 1731 * Get function arguments at "*arg" and advance it. |
1732 * Return them in "*argvars[MAX_FUNC_ARGS + 1]" and the count in "argcount". | |
1733 */ | |
1734 static int | |
1735 get_func_arguments( | |
1736 char_u **arg, | |
1737 evalarg_T *evalarg, | |
1738 int partial_argc, | |
1739 typval_T *argvars, | |
1740 int *argcount) | |
1741 { | |
1742 char_u *argp = *arg; | |
1743 int ret = OK; | |
1744 int vim9script = in_vim9script(); | |
1745 int evaluate = evalarg == NULL | |
1746 ? FALSE : (evalarg->eval_flags & EVAL_EVALUATE); | |
1747 | |
1748 while (*argcount < MAX_FUNC_ARGS - partial_argc) | |
1749 { | |
1750 // skip the '(' or ',' and possibly line breaks | |
1751 argp = skipwhite_and_linebreak(argp + 1, evalarg); | |
1752 | |
1753 if (*argp == ')' || *argp == ',' || *argp == NUL) | |
1754 break; | |
1755 if (eval1(&argp, &argvars[*argcount], evalarg) == FAIL) | |
1756 { | |
1757 ret = FAIL; | |
1758 break; | |
1759 } | |
1760 ++*argcount; | |
1761 // The comma should come right after the argument, but this wasn't | |
1762 // checked previously, thus only enforce it in Vim9 script. | |
1763 if (vim9script) | |
1764 { | |
1765 if (*argp != ',' && *skipwhite(argp) == ',') | |
1766 { | |
1767 if (evaluate) | |
1768 semsg(_(e_no_white_space_allowed_before_str_str), | |
1769 ",", argp); | |
1770 ret = FAIL; | |
1771 break; | |
1772 } | |
1773 } | |
1774 else | |
1775 argp = skipwhite(argp); | |
1776 if (*argp != ',') | |
1777 break; | |
1778 if (vim9script && !IS_WHITE_OR_NUL(argp[1])) | |
1779 { | |
1780 if (evaluate) | |
1781 semsg(_(e_white_space_required_after_str_str), ",", argp); | |
1782 ret = FAIL; | |
1783 break; | |
1784 } | |
1785 } | |
1786 | |
1787 argp = skipwhite_and_linebreak(argp, evalarg); | |
1788 if (*argp == ')') | |
1789 ++argp; | |
1790 else | |
1791 ret = FAIL; | |
1792 *arg = argp; | |
1793 return ret; | |
1794 } | |
1795 | |
1796 /* | |
1797 * Call a function and put the result in "rettv". | |
1732 * Return OK or FAIL. | 1798 * Return OK or FAIL. |
1733 */ | 1799 */ |
1734 int | 1800 int |
1735 get_func_tv( | 1801 get_func_tv( |
1736 char_u *name, // name of the function | 1802 char_u *name, // name of the function |
1741 funcexe_T *funcexe) // various values | 1807 funcexe_T *funcexe) // various values |
1742 { | 1808 { |
1743 char_u *argp; | 1809 char_u *argp; |
1744 int ret = OK; | 1810 int ret = OK; |
1745 typval_T argvars[MAX_FUNC_ARGS + 1]; // vars for arguments | 1811 typval_T argvars[MAX_FUNC_ARGS + 1]; // vars for arguments |
1746 int argcount = 0; // number of arguments found | 1812 int argcount = 0; // number of arguments found |
1747 int vim9script = in_vim9script(); | 1813 int vim9script = in_vim9script(); |
1748 int evaluate = evalarg == NULL | 1814 int evaluate = evalarg == NULL |
1749 ? FALSE : (evalarg->eval_flags & EVAL_EVALUATE); | 1815 ? FALSE : (evalarg->eval_flags & EVAL_EVALUATE); |
1750 | 1816 |
1751 /* | |
1752 * Get the arguments. | |
1753 */ | |
1754 argp = *arg; | 1817 argp = *arg; |
1755 while (argcount < MAX_FUNC_ARGS - (funcexe->fe_partial == NULL ? 0 | 1818 ret = get_func_arguments(&argp, evalarg, |
1756 : funcexe->fe_partial->pt_argc)) | 1819 (funcexe->fe_partial == NULL ? 0 : funcexe->fe_partial->pt_argc), |
1757 { | 1820 argvars, &argcount); |
1758 // skip the '(' or ',' and possibly line breaks | |
1759 argp = skipwhite_and_linebreak(argp + 1, evalarg); | |
1760 | |
1761 if (*argp == ')' || *argp == ',' || *argp == NUL) | |
1762 break; | |
1763 if (eval1(&argp, &argvars[argcount], evalarg) == FAIL) | |
1764 { | |
1765 ret = FAIL; | |
1766 break; | |
1767 } | |
1768 ++argcount; | |
1769 // The comma should come right after the argument, but this wasn't | |
1770 // checked previously, thus only enforce it in Vim9 script. | |
1771 if (vim9script) | |
1772 { | |
1773 if (*argp != ',' && *skipwhite(argp) == ',') | |
1774 { | |
1775 if (evaluate) | |
1776 semsg(_(e_no_white_space_allowed_before_str_str), | |
1777 ",", argp); | |
1778 ret = FAIL; | |
1779 break; | |
1780 } | |
1781 } | |
1782 else | |
1783 argp = skipwhite(argp); | |
1784 if (*argp != ',') | |
1785 break; | |
1786 if (vim9script && !IS_WHITE_OR_NUL(argp[1])) | |
1787 { | |
1788 if (evaluate) | |
1789 semsg(_(e_white_space_required_after_str_str), ",", argp); | |
1790 ret = FAIL; | |
1791 break; | |
1792 } | |
1793 } | |
1794 argp = skipwhite_and_linebreak(argp, evalarg); | |
1795 if (*argp == ')') | |
1796 ++argp; | |
1797 else | |
1798 ret = FAIL; | |
1799 | 1821 |
1800 if (ret == OK) | 1822 if (ret == OK) |
1801 { | 1823 { |
1802 int i = 0; | 1824 int i = 0; |
1803 int did_emsg_before = did_emsg; | 1825 int did_emsg_before = did_emsg; |
2881 } | 2903 } |
2882 else | 2904 else |
2883 // call do_cmdline() to execute the lines | 2905 // call do_cmdline() to execute the lines |
2884 do_cmdline(NULL, get_func_line, (void *)fc, | 2906 do_cmdline(NULL, get_func_line, (void *)fc, |
2885 DOCMD_NOWAIT|DOCMD_VERBOSE|DOCMD_REPEAT); | 2907 DOCMD_NOWAIT|DOCMD_VERBOSE|DOCMD_REPEAT); |
2908 | |
2909 // Invoke functions added with ":defer". | |
2910 handle_defer(); | |
2886 | 2911 |
2887 --RedrawingDisabled; | 2912 --RedrawingDisabled; |
2888 | 2913 |
2889 // when the function was aborted because of an error, return -1 | 2914 // when the function was aborted because of an error, return -1 |
2890 if ((did_emsg && (fp->uf_flags & FC_ABORT)) || rettv->v_type == VAR_UNKNOWN) | 2915 if ((did_emsg && (fp->uf_flags & FC_ABORT)) || rettv->v_type == VAR_UNKNOWN) |
5455 if (eap->skip) | 5480 if (eap->skip) |
5456 --emsg_skip; | 5481 --emsg_skip; |
5457 clear_evalarg(&evalarg, eap); | 5482 clear_evalarg(&evalarg, eap); |
5458 } | 5483 } |
5459 | 5484 |
5485 static int | |
5486 ex_call_inner( | |
5487 exarg_T *eap, | |
5488 char_u *name, | |
5489 char_u **arg, | |
5490 char_u *startarg, | |
5491 funcexe_T *funcexe_init, | |
5492 evalarg_T *evalarg) | |
5493 { | |
5494 linenr_T lnum; | |
5495 int doesrange; | |
5496 typval_T rettv; | |
5497 int failed = FALSE; | |
5498 | |
5499 /* | |
5500 * When skipping, evaluate the function once, to find the end of the | |
5501 * arguments. | |
5502 * When the function takes a range, this is discovered after the first | |
5503 * call, and the loop is broken. | |
5504 */ | |
5505 if (eap->skip) | |
5506 { | |
5507 ++emsg_skip; | |
5508 lnum = eap->line2; // do it once, also with an invalid range | |
5509 } | |
5510 else | |
5511 lnum = eap->line1; | |
5512 for ( ; lnum <= eap->line2; ++lnum) | |
5513 { | |
5514 funcexe_T funcexe; | |
5515 | |
5516 if (!eap->skip && eap->addr_count > 0) | |
5517 { | |
5518 if (lnum > curbuf->b_ml.ml_line_count) | |
5519 { | |
5520 // If the function deleted lines or switched to another buffer | |
5521 // the line number may become invalid. | |
5522 emsg(_(e_invalid_range)); | |
5523 break; | |
5524 } | |
5525 curwin->w_cursor.lnum = lnum; | |
5526 curwin->w_cursor.col = 0; | |
5527 curwin->w_cursor.coladd = 0; | |
5528 } | |
5529 *arg = startarg; | |
5530 | |
5531 funcexe = *funcexe_init; | |
5532 funcexe.fe_doesrange = &doesrange; | |
5533 rettv.v_type = VAR_UNKNOWN; // clear_tv() uses this | |
5534 if (get_func_tv(name, -1, &rettv, arg, evalarg, &funcexe) == FAIL) | |
5535 { | |
5536 failed = TRUE; | |
5537 break; | |
5538 } | |
5539 if (has_watchexpr()) | |
5540 dbg_check_breakpoint(eap); | |
5541 | |
5542 // Handle a function returning a Funcref, Dictionary or List. | |
5543 if (handle_subscript(arg, NULL, &rettv, | |
5544 eap->skip ? NULL : &EVALARG_EVALUATE, TRUE) == FAIL) | |
5545 { | |
5546 failed = TRUE; | |
5547 break; | |
5548 } | |
5549 | |
5550 clear_tv(&rettv); | |
5551 if (doesrange || eap->skip) | |
5552 break; | |
5553 | |
5554 // Stop when immediately aborting on error, or when an interrupt | |
5555 // occurred or an exception was thrown but not caught. | |
5556 // get_func_tv() returned OK, so that the check for trailing | |
5557 // characters below is executed. | |
5558 if (aborting()) | |
5559 break; | |
5560 } | |
5561 if (eap->skip) | |
5562 --emsg_skip; | |
5563 return failed; | |
5564 } | |
5565 | |
5566 /* | |
5567 * Core part of ":defer func(arg)". "arg" points to the "(" and is advanced. | |
5568 * Returns FAIL or OK. | |
5569 */ | |
5570 static int | |
5571 ex_defer_inner(char_u *name, char_u **arg, evalarg_T *evalarg) | |
5572 { | |
5573 typval_T argvars[MAX_FUNC_ARGS + 1]; // vars for arguments | |
5574 int argcount = 0; // number of arguments found | |
5575 defer_T *dr; | |
5576 int ret = FAIL; | |
5577 char_u *saved_name; | |
5578 | |
5579 if (current_funccal == NULL) | |
5580 { | |
5581 semsg(_(e_str_not_inside_function), "defer"); | |
5582 return FAIL; | |
5583 } | |
5584 if (get_func_arguments(arg, evalarg, FALSE, argvars, &argcount) == FAIL) | |
5585 goto theend; | |
5586 saved_name = vim_strsave(name); | |
5587 if (saved_name == NULL) | |
5588 goto theend; | |
5589 | |
5590 if (current_funccal->fc_defer.ga_itemsize == 0) | |
5591 ga_init2(¤t_funccal->fc_defer, sizeof(defer_T), 10); | |
5592 if (ga_grow(¤t_funccal->fc_defer, 1) == FAIL) | |
5593 goto theend; | |
5594 dr = ((defer_T *)current_funccal->fc_defer.ga_data) | |
5595 + current_funccal->fc_defer.ga_len++; | |
5596 dr->dr_name = saved_name; | |
5597 dr->dr_argcount = argcount; | |
5598 while (argcount > 0) | |
5599 { | |
5600 --argcount; | |
5601 dr->dr_argvars[argcount] = argvars[argcount]; | |
5602 } | |
5603 ret = OK; | |
5604 | |
5605 theend: | |
5606 while (--argcount >= 0) | |
5607 clear_tv(&argvars[argcount]); | |
5608 return ret; | |
5609 } | |
5610 | |
5611 /* | |
5612 * Invoked after a functions has finished: invoke ":defer" functions. | |
5613 */ | |
5614 void | |
5615 handle_defer(void) | |
5616 { | |
5617 int idx; | |
5618 | |
5619 for (idx = current_funccal->fc_defer.ga_len - 1; idx >= 0; --idx) | |
5620 { | |
5621 funcexe_T funcexe; | |
5622 typval_T rettv; | |
5623 defer_T *dr = ((defer_T *)current_funccal->fc_defer.ga_data) + idx; | |
5624 int i; | |
5625 | |
5626 CLEAR_FIELD(funcexe); | |
5627 funcexe.fe_evaluate = TRUE; | |
5628 | |
5629 rettv.v_type = VAR_UNKNOWN; // clear_tv() uses this | |
5630 call_func(dr->dr_name, -1, &rettv, | |
5631 dr->dr_argcount, dr->dr_argvars, &funcexe); | |
5632 clear_tv(&rettv); | |
5633 vim_free(dr->dr_name); | |
5634 for (i = dr->dr_argcount - 1; i >= 0; --i) | |
5635 clear_tv(&dr->dr_argvars[i]); | |
5636 } | |
5637 ga_clear(¤t_funccal->fc_defer); | |
5638 } | |
5639 | |
5460 /* | 5640 /* |
5461 * ":1,25call func(arg1, arg2)" function call. | 5641 * ":1,25call func(arg1, arg2)" function call. |
5642 * ":defer func(arg1, arg2)" deferred function call. | |
5462 */ | 5643 */ |
5463 void | 5644 void |
5464 ex_call(exarg_T *eap) | 5645 ex_call(exarg_T *eap) |
5465 { | 5646 { |
5466 char_u *arg = eap->arg; | 5647 char_u *arg = eap->arg; |
5467 char_u *startarg; | 5648 char_u *startarg; |
5468 char_u *name; | 5649 char_u *name; |
5469 char_u *tofree; | 5650 char_u *tofree; |
5470 int len; | 5651 int len; |
5471 typval_T rettv; | |
5472 linenr_T lnum; | |
5473 int doesrange; | |
5474 int failed = FALSE; | 5652 int failed = FALSE; |
5475 funcdict_T fudi; | 5653 funcdict_T fudi; |
5476 partial_T *partial = NULL; | 5654 partial_T *partial = NULL; |
5477 evalarg_T evalarg; | 5655 evalarg_T evalarg; |
5478 type_T *type = NULL; | 5656 type_T *type = NULL; |
5480 int vim9script = in_vim9script(); | 5658 int vim9script = in_vim9script(); |
5481 | 5659 |
5482 fill_evalarg_from_eap(&evalarg, eap, eap->skip); | 5660 fill_evalarg_from_eap(&evalarg, eap, eap->skip); |
5483 if (eap->skip) | 5661 if (eap->skip) |
5484 { | 5662 { |
5663 typval_T rettv; | |
5664 | |
5485 // trans_function_name() doesn't work well when skipping, use eval0() | 5665 // trans_function_name() doesn't work well when skipping, use eval0() |
5486 // instead to skip to any following command, e.g. for: | 5666 // instead to skip to any following command, e.g. for: |
5487 // :if 0 | call dict.foo().bar() | endif | 5667 // :if 0 | call dict.foo().bar() | endif |
5488 ++emsg_skip; | 5668 ++emsg_skip; |
5489 if (eval0(eap->arg, &rettv, eap, &evalarg) != FAIL) | 5669 if (eval0(eap->arg, &rettv, eap, &evalarg) != FAIL) |
5529 { | 5709 { |
5530 semsg(_(e_no_white_space_allowed_before_str_str), "(", eap->arg); | 5710 semsg(_(e_no_white_space_allowed_before_str_str), "(", eap->arg); |
5531 goto end; | 5711 goto end; |
5532 } | 5712 } |
5533 | 5713 |
5534 /* | 5714 if (eap->cmdidx == CMD_defer) |
5535 * When skipping, evaluate the function once, to find the end of the | 5715 { |
5536 * arguments. | 5716 arg = startarg; |
5537 * When the function takes a range, this is discovered after the first | 5717 failed = ex_defer_inner(name, &arg, &evalarg) == FAIL; |
5538 * call, and the loop is broken. | |
5539 */ | |
5540 if (eap->skip) | |
5541 { | |
5542 ++emsg_skip; | |
5543 lnum = eap->line2; // do it once, also with an invalid range | |
5544 } | 5718 } |
5545 else | 5719 else |
5546 lnum = eap->line1; | |
5547 for ( ; lnum <= eap->line2; ++lnum) | |
5548 { | 5720 { |
5549 funcexe_T funcexe; | 5721 funcexe_T funcexe; |
5550 | 5722 |
5551 if (!eap->skip && eap->addr_count > 0) | |
5552 { | |
5553 if (lnum > curbuf->b_ml.ml_line_count) | |
5554 { | |
5555 // If the function deleted lines or switched to another buffer | |
5556 // the line number may become invalid. | |
5557 emsg(_(e_invalid_range)); | |
5558 break; | |
5559 } | |
5560 curwin->w_cursor.lnum = lnum; | |
5561 curwin->w_cursor.col = 0; | |
5562 curwin->w_cursor.coladd = 0; | |
5563 } | |
5564 arg = startarg; | |
5565 | |
5566 CLEAR_FIELD(funcexe); | 5723 CLEAR_FIELD(funcexe); |
5724 funcexe.fe_check_type = type; | |
5725 funcexe.fe_partial = partial; | |
5726 funcexe.fe_selfdict = fudi.fd_dict; | |
5567 funcexe.fe_firstline = eap->line1; | 5727 funcexe.fe_firstline = eap->line1; |
5568 funcexe.fe_lastline = eap->line2; | 5728 funcexe.fe_lastline = eap->line2; |
5569 funcexe.fe_doesrange = &doesrange; | 5729 funcexe.fe_found_var = found_var; |
5570 funcexe.fe_evaluate = !eap->skip; | 5730 funcexe.fe_evaluate = !eap->skip; |
5571 funcexe.fe_partial = partial; | 5731 failed = ex_call_inner(eap, name, &arg, startarg, &funcexe, &evalarg); |
5572 funcexe.fe_selfdict = fudi.fd_dict; | 5732 } |
5573 funcexe.fe_check_type = type; | |
5574 funcexe.fe_found_var = found_var; | |
5575 rettv.v_type = VAR_UNKNOWN; // clear_tv() uses this | |
5576 if (get_func_tv(name, -1, &rettv, &arg, &evalarg, &funcexe) == FAIL) | |
5577 { | |
5578 failed = TRUE; | |
5579 break; | |
5580 } | |
5581 if (has_watchexpr()) | |
5582 dbg_check_breakpoint(eap); | |
5583 | |
5584 // Handle a function returning a Funcref, Dictionary or List. | |
5585 if (handle_subscript(&arg, NULL, &rettv, | |
5586 eap->skip ? NULL : &EVALARG_EVALUATE, TRUE) == FAIL) | |
5587 { | |
5588 failed = TRUE; | |
5589 break; | |
5590 } | |
5591 | |
5592 clear_tv(&rettv); | |
5593 if (doesrange || eap->skip) | |
5594 break; | |
5595 | |
5596 // Stop when immediately aborting on error, or when an interrupt | |
5597 // occurred or an exception was thrown but not caught. | |
5598 // get_func_tv() returned OK, so that the check for trailing | |
5599 // characters below is executed. | |
5600 if (aborting()) | |
5601 break; | |
5602 } | |
5603 if (eap->skip) | |
5604 --emsg_skip; | |
5605 | 5733 |
5606 // When inside :try we need to check for following "| catch" or "| endtry". | 5734 // When inside :try we need to check for following "| catch" or "| endtry". |
5607 // Not when there was an error, but do check if an exception was thrown. | 5735 // Not when there was an error, but do check if an exception was thrown. |
5608 if ((!aborting() || did_throw) | 5736 if ((!aborting() || did_throw) && (!failed || eap->cstack->cs_trylevel > 0)) |
5609 && (!failed || eap->cstack->cs_trylevel > 0)) | |
5610 { | 5737 { |
5611 // Check for trailing illegal characters and a following command. | 5738 // Check for trailing illegal characters and a following command. |
5612 arg = skipwhite(arg); | 5739 arg = skipwhite(arg); |
5613 if (!ends_excmd2(eap->arg, arg)) | 5740 if (!ends_excmd2(eap->arg, arg)) |
5614 { | 5741 { |