Mercurial > vim
changeset 36484:a0b0c1e07064 draft v9.1.0844
patch 9.1.0844: if_python: no way to pass local vars to python
Commit: https://github.com/vim/vim/commit/ea19e7856b6c7850eab7ce74aa209e09e2c6eae3
Author: Ben Jackson <puremourning@gmail.com>
Date: Wed Nov 6 21:50:05 2024 +0100
patch 9.1.0844: if_python: no way to pass local vars to python
Problem: if_python: no way to pass local vars to python
Solution: Add locals argument to py3eval(), pyeval() and pyxeval()
(Ben Jackson)
fixes: #8573
closes: #10594
Signed-off-by: Ben Jackson <puremourning@gmail.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
author | Christian Brabandt <cb@256bit.org> |
---|---|
date | Wed, 06 Nov 2024 22:00:04 +0100 |
parents | 618b9621bfdc |
children | bfaec2c19fe7 |
files | runtime/doc/builtin.txt runtime/doc/if_pyth.txt runtime/doc/version9.txt src/evalfunc.c src/if_py_both.h src/if_python.c src/if_python3.c src/proto/if_python.pro src/proto/if_python3.pro src/testdir/Make_all.mak src/testdir/test_python2.vim src/testdir/test_python3.vim src/testdir/test_pyx2.vim src/testdir/test_pyx3.vim src/testdir/test_vim9_python3.vim src/version.c |
diffstat | 16 files changed, 292 insertions(+), 31 deletions(-) [+] |
line wrap: on
line diff
--- a/runtime/doc/builtin.txt +++ b/runtime/doc/builtin.txt @@ -1,4 +1,4 @@ -*builtin.txt* For Vim version 9.1. Last change: 2024 Nov 01 +*builtin.txt* For Vim version 9.1. Last change: 2024 Nov 06 VIM REFERENCE MANUAL by Bram Moolenaar @@ -467,9 +467,9 @@ prop_type_get({name} [, {props}]) prop_type_list([{props}]) List get list of property types pum_getpos() Dict position and size of pum if visible pumvisible() Number whether popup menu is visible -py3eval({expr}) any evaluate |python3| expression -pyeval({expr}) any evaluate |Python| expression -pyxeval({expr}) any evaluate |python_x| expression +py3eval({expr}[, {locals}]) any evaluate |python3| expression +pyeval({expr}[, {locals}]) any evaluate |Python| expression +pyxeval({expr}[, {locals}]) any evaluate |python_x| expression rand([{expr}]) Number get pseudo-random number range({expr} [, {max} [, {stride}]]) List items from {expr} to {max} @@ -8127,9 +8127,14 @@ pumvisible() *pumvisible()* Return type: |Number| -py3eval({expr}) *py3eval()* +py3eval({expr}[, {locals}]) *py3eval()* Evaluate Python expression {expr} and return its result converted to Vim data structures. + If a {locals} |Dictionary| is given, it defines set of local + variables available in the expression. The keys are variable + names and the values are the variable values. |Dictionary| and + |List| values are referenced, and may be updated by the + expression (as if |python-bindeval| was used). Numbers and strings are returned as they are (strings are copied though, Unicode strings are additionally converted to 'encoding'). @@ -8141,15 +8146,17 @@ py3eval({expr}) *py3eval()* Can also be used as a |method|: > GetExpr()->py3eval() + 'b",".join(l)'->py3eval({'l': ['a', 'b', 'c']}) < Return type: any, depending on {expr} {only available when compiled with the |+python3| feature} *E858* *E859* -pyeval({expr}) *pyeval()* +pyeval({expr}[, {locals}]) *pyeval()* Evaluate Python expression {expr} and return its result converted to Vim data structures. + For {locals} see |py3eval()|. Numbers and strings are returned as they are (strings are copied though). Lists are represented as Vim |List| type. @@ -8165,9 +8172,10 @@ pyeval({expr}) *pyeval()* {only available when compiled with the |+python| feature} -pyxeval({expr}) *pyxeval()* +pyxeval({expr}[, {locals}]) *pyxeval()* Evaluate Python expression {expr} and return its result converted to Vim data structures. + For {locals} see |py3eval()|. Uses Python 2 or 3, see |python_x| and 'pyxversion'. See also: |pyeval()|, |py3eval()|
--- a/runtime/doc/if_pyth.txt +++ b/runtime/doc/if_pyth.txt @@ -1,4 +1,4 @@ -*if_pyth.txt* For Vim version 9.1. Last change: 2024 May 16 +*if_pyth.txt* For Vim version 9.1. Last change: 2024 Nov 06 VIM REFERENCE MANUAL by Paul Moore @@ -201,6 +201,10 @@ vim.eval(str) *python-eval* [{'cmd': '/^eval_expr(arg, nextcmd)$/', 'static': 0, 'name': ~ 'eval_expr', 'kind': 'f', 'filename': './src/eval.c'}] ~ + NOTE: In vim9script, local variables in def functions are not visible + to to python evaluations. To pass local variables to python evaluations, + use the {locals} dict when calling |py3eval()| and friends. + vim.bindeval(str) *python-bindeval* Like |python-eval|, but returns special objects described in |python-bindeval-objects|. These python objects let you modify (|List| @@ -741,6 +745,10 @@ To facilitate bi-directional interface, functions to evaluate Python expressions and pass their values to Vim script. |pyxeval()| is also available. +You can inject local variables into the evaluation using the optional {locals} +dict. This can be particularly useful in vim9script where vim.eval +|python-eval| will not find locals in a def func. + The Python value "None" is converted to v:none. ==============================================================================
--- a/runtime/doc/version9.txt +++ b/runtime/doc/version9.txt @@ -1,4 +1,4 @@ -*version9.txt* For Vim version 9.1. Last change: 2024 Nov 03 +*version9.txt* For Vim version 9.1. Last change: 2024 Nov 06 VIM REFERENCE MANUAL by Bram Moolenaar @@ -41606,6 +41606,7 @@ Changed~ - an interactive tutor plugin has been included |vim-tutor-mode|, can be started via |:Tutor| - improve the |vimtutor| and add a second chapter for more advanced tips +- allow to pass local Vim script variables to python interpreter |py3eval()| *added-9.2* Added ~
--- a/src/evalfunc.c +++ b/src/evalfunc.c @@ -2487,7 +2487,7 @@ static funcentry_T global_functions[] = ret_dict_number, f_pum_getpos}, {"pumvisible", 0, 0, 0, NULL, ret_number_bool, f_pumvisible}, - {"py3eval", 1, 1, FEARG_1, arg1_string, + {"py3eval", 1, 2, FEARG_1, arg2_string_dict, ret_any, #ifdef FEAT_PYTHON3 f_py3eval @@ -2495,7 +2495,7 @@ static funcentry_T global_functions[] = NULL #endif }, - {"pyeval", 1, 1, FEARG_1, arg1_string, + {"pyeval", 1, 2, FEARG_1, arg2_string_dict, ret_any, #ifdef FEAT_PYTHON f_pyeval @@ -2503,7 +2503,7 @@ static funcentry_T global_functions[] = NULL #endif }, - {"pyxeval", 1, 1, FEARG_1, arg1_string, + {"pyxeval", 1, 2, FEARG_1, arg2_string_dict, ret_any, #if defined(FEAT_PYTHON) || defined(FEAT_PYTHON3) f_pyxeval @@ -9291,18 +9291,35 @@ f_py3eval(typval_T *argvars, typval_T *r { char_u *str; char_u buf[NUMBUFLEN]; + dict_T *locals; if (check_restricted() || check_secure()) return; - if (in_vim9script() && check_for_string_arg(argvars, 0) == FAIL) + if (in_vim9script() + && (check_for_string_arg(argvars, 0) == FAIL + || check_for_opt_dict_arg(argvars, 1) == FAIL)) return; if (p_pyx == 0) p_pyx = 3; + if (argvars[1].v_type == VAR_DICT) + { + locals = argvars[1].vval.v_dict; + } + else if (argvars[1].v_type != VAR_UNKNOWN) + { + emsg(_(e_dictionary_required)); + return; + } + else + { + locals = NULL; + } + str = tv_get_string_buf(&argvars[0], buf); - do_py3eval(str, rettv); + do_py3eval(str, locals, rettv); } #endif @@ -9315,18 +9332,35 @@ f_pyeval(typval_T *argvars, typval_T *re { char_u *str; char_u buf[NUMBUFLEN]; + dict_T *locals; if (check_restricted() || check_secure()) return; - if (in_vim9script() && check_for_string_arg(argvars, 0) == FAIL) + if (in_vim9script() && ( + check_for_string_arg(argvars, 0) == FAIL || + check_for_opt_dict_arg(argvars, 1) == FAIL ) ) return; if (p_pyx == 0) p_pyx = 2; + if (argvars[1].v_type == VAR_DICT) + { + locals = argvars[1].vval.v_dict; + } + else if (argvars[1].v_type != VAR_UNKNOWN) + { + emsg( "Invalid argument: must be dict" ); + return; + } + else + { + locals = NULL; + } + str = tv_get_string_buf(&argvars[0], buf); - do_pyeval(str, rettv); + do_pyeval(str, locals, rettv); } #endif @@ -9340,7 +9374,9 @@ f_pyxeval(typval_T *argvars, typval_T *r if (check_restricted() || check_secure()) return; - if (in_vim9script() && check_for_string_arg(argvars, 0) == FAIL) + if (in_vim9script() + && (check_for_string_arg(argvars, 0) == FAIL + || check_for_opt_dict_arg(argvars, 1) == FAIL)) return; # if defined(FEAT_PYTHON) && defined(FEAT_PYTHON3)
--- a/src/if_py_both.h +++ b/src/if_py_both.h @@ -328,7 +328,7 @@ static int Vim_PyRun_SimpleString(const #define INVALID_TABPAGE_VALUE ((tabpage_T *)(-1)) typedef void (*rangeinitializer)(void *); -typedef void (*runner)(const char *, void * +typedef void (*runner)(const char *, dict_T *, void * #ifdef PY_CAN_RECURSE , PyGILState_STATE * #endif @@ -6032,7 +6032,7 @@ init_range_eval(void *rettv UNUSED) } static void -run_cmd(const char *cmd, void *arg UNUSED +run_cmd(const char *cmd, dict_T* locals UNUSED, void *arg UNUSED #ifdef PY_CAN_RECURSE , PyGILState_STATE *pygilstate UNUSED #endif @@ -6057,7 +6057,7 @@ static const char *code_hdr = "def " DOP static int code_hdr_len = 30; static void -run_do(const char *cmd, void *arg UNUSED +run_do(const char *cmd, dict_T* locals UNUSED, void *arg UNUSED #ifdef PY_CAN_RECURSE , PyGILState_STATE *pygilstate #endif @@ -6180,7 +6180,7 @@ out: } static void -run_eval(const char *cmd, void *arg +run_eval(const char *cmd, dict_T *locals, void *arg #ifdef PY_CAN_RECURSE , PyGILState_STATE *pygilstate UNUSED #endif @@ -6188,8 +6188,9 @@ run_eval(const char *cmd, void *arg { PyObject *run_ret; typval_T *rettv = (typval_T*)arg; - - run_ret = PyRun_String((char *)cmd, Py_eval_input, globals, globals); + PyObject *pylocals = locals ? NEW_DICTIONARY(locals) : globals; + + run_ret = PyRun_String((char *)cmd, Py_eval_input, globals, pylocals); if (run_ret == NULL) { if (PyErr_Occurred() && PyErr_ExceptionMatches(PyExc_SystemExit))
--- a/src/if_python.c +++ b/src/if_python.c @@ -1009,7 +1009,7 @@ fail: * External interface */ static void -DoPyCommand(const char *cmd, rangeinitializer init_range, runner run, void *arg) +DoPyCommand(const char *cmd, dict_T* locals, rangeinitializer init_range, runner run, void *arg) { #ifndef PY_CAN_RECURSE static int recursive = 0; @@ -1058,7 +1058,7 @@ DoPyCommand(const char *cmd, rangeinitia Python_RestoreThread(); // enter python #endif - run((char *) cmd, arg + run((char *) cmd, locals, arg #ifdef PY_CAN_RECURSE , &pygilstate #endif @@ -1103,6 +1103,7 @@ ex_python(exarg_T *eap) p_pyx = 2; DoPyCommand(script == NULL ? (char *) eap->arg : (char *) script, + NULL, init_range_cmd, (runner) run_cmd, (void *) eap); @@ -1154,6 +1155,7 @@ ex_pyfile(exarg_T *eap) // Execute the file DoPyCommand(buffer, + NULL, init_range_cmd, (runner) run_cmd, (void *) eap); @@ -1166,6 +1168,7 @@ ex_pydo(exarg_T *eap) p_pyx = 2; DoPyCommand((char *)eap->arg, + NULL, init_range_cmd, (runner)run_do, (void *)eap); @@ -1521,9 +1524,10 @@ FunctionGetattr(PyObject *self, char *na } void -do_pyeval(char_u *str, typval_T *rettv) +do_pyeval(char_u *str, dict_T *locals, typval_T *rettv) { DoPyCommand((char *) str, + locals, init_range_eval, (runner) run_eval, (void *) rettv);
--- a/src/if_python3.c +++ b/src/if_python3.c @@ -1436,7 +1436,11 @@ fail: * External interface */ static void -DoPyCommand(const char *cmd, rangeinitializer init_range, runner run, void *arg) +DoPyCommand(const char *cmd, + dict_T* locals, + rangeinitializer init_range, + runner run, + void *arg) { #if defined(HAVE_LOCALE_H) || defined(X_LOCALE) char *saved_locale; @@ -1477,7 +1481,7 @@ DoPyCommand(const char *cmd, rangeinitia cmdbytes = PyUnicode_AsEncodedString(cmdstr, "utf-8", ERRORS_ENCODE_ARG); Py_XDECREF(cmdstr); - run(PyBytes_AsString(cmdbytes), arg, &pygilstate); + run(PyBytes_AsString(cmdbytes), locals, arg, &pygilstate); Py_XDECREF(cmdbytes); PyGILState_Release(pygilstate); @@ -1512,6 +1516,7 @@ ex_py3(exarg_T *eap) p_pyx = 3; DoPyCommand(script == NULL ? (char *) eap->arg : (char *) script, + NULL, init_range_cmd, (runner) run_cmd, (void *) eap); @@ -1578,6 +1583,7 @@ ex_py3file(exarg_T *eap) // Execute the file DoPyCommand(buffer, + NULL, init_range_cmd, (runner) run_cmd, (void *) eap); @@ -1590,6 +1596,7 @@ ex_py3do(exarg_T *eap) p_pyx = 3; DoPyCommand((char *)eap->arg, + NULL, init_range_cmd, (runner)run_do, (void *)eap); @@ -2137,9 +2144,10 @@ LineToString(const char *str) } void -do_py3eval(char_u *str, typval_T *rettv) +do_py3eval(char_u *str, dict_T *locals, typval_T *rettv) { DoPyCommand((char *) str, + locals, init_range_eval, (runner) run_eval, (void *) rettv);
--- a/src/proto/if_python.pro +++ b/src/proto/if_python.pro @@ -8,6 +8,6 @@ void ex_pydo(exarg_T *eap); void python_buffer_free(buf_T *buf); void python_window_free(win_T *win); void python_tabpage_free(tabpage_T *tab); -void do_pyeval(char_u *str, typval_T *rettv); +void do_pyeval(char_u *str, dict_T* locals, typval_T *rettv); int set_ref_in_python(int copyID); /* vim: set ft=c : */
--- a/src/proto/if_python3.pro +++ b/src/proto/if_python3.pro @@ -8,7 +8,7 @@ void ex_py3do(exarg_T *eap); void python3_buffer_free(buf_T *buf); void python3_window_free(win_T *win); void python3_tabpage_free(tabpage_T *tab); -void do_py3eval(char_u *str, typval_T *rettv); +void do_py3eval(char_u *str, dict_T* locals, typval_T *rettv); int set_ref_in_python3(int copyID); int python3_version(void); /* vim: set ft=c : */
--- a/src/testdir/Make_all.mak +++ b/src/testdir/Make_all.mak @@ -47,6 +47,7 @@ TEST_VIM9 = \ test_vim9_fails \ test_vim9_func \ test_vim9_import \ + test_vim9_python3 \ test_vim9_script \ test_vim9_typealias @@ -61,6 +62,7 @@ TEST_VIM9_RES = \ test_vim9_fails.res \ test_vim9_func.res \ test_vim9_import.res \ + test_vim9_python3.res \ test_vim9_script.res \ test_vim9_typealias.res
--- a/src/testdir/test_python2.vim +++ b/src/testdir/test_python2.vim @@ -809,6 +809,49 @@ func Test_python_pyeval() call AssertException(['let v = pyeval("vim")'], 'E859:') endfunc +" Test for py3eval with locals +func Test_python_pyeval_locals() + let str = 'a string' + let num = 0xbadb33f + let d = {'a': 1, 'b': 2, 'c': str} + let l = [ str, num, d ] + + let locals = #{ + \ s: str, + \ n: num, + \ d: d, + \ l: l, + \ } + + " check basics + call assert_equal('a string', pyeval('s', locals)) + call assert_equal(0xbadb33f, pyeval('n', locals)) + call assert_equal(d, pyeval('d', locals)) + call assert_equal(l, pyeval('l', locals)) + + py << trim EOF + def __UpdateDict(d, upd): + d.update(upd) + return d + + def __ExtendList(l, *args): + l.extend(*args) + return l + EOF + + " check assign to dict member works like bindeval + call assert_equal(3, pyeval('__UpdateDict( d, {"c": 3} )["c"]', locals)) + call assert_equal(3, d['c']) + + " check append lo list + call assert_equal(4, pyeval('len(__ExtendList(l, ["new item"]))', locals)) + call assert_equal("new item", l[-1]) + + " check calling a function + let StrLen = function('strlen') + call assert_equal(3, pyeval('f("abc")', {'f': StrLen})) +endfunc + " Test for vim.bindeval() func Test_python_vim_bindeval() " Float
--- a/src/testdir/test_python3.vim +++ b/src/testdir/test_python3.vim @@ -1027,6 +1027,52 @@ func Test_python3_pyeval() call AssertException(['let v = py3eval("vim")'], 'E859:') endfunc +" Test for py3eval with locals +func Test_python3_pyeval_locals() + let str = 'a string' + let num = 0xbadb33f + let d = {'a': 1, 'b': 2, 'c': str} + let l = [ str, num, d ] + + let locals = #{ + \ s: str, + \ n: num, + \ d: d, + \ l: l, + \ } + + " check basics + call assert_equal('a string', py3eval('s', locals)) + call assert_equal(0xbadb33f, py3eval('n', locals)) + call assert_equal(d, py3eval('d', locals)) + call assert_equal(l, py3eval('l', locals)) + call assert_equal('a,b,c', py3eval('b",".join(l)', {'l': ['a', 'b', 'c']})) + call assert_equal('hello', 's'->py3eval({'s': 'hello'})) + call assert_equal('a,b,c', 'b",".join(l)'->py3eval({'l': ['a', 'b', 'c']})) + + py3 << trim EOF + def __UpdateDict(d, upd): + d.update(upd) + return d + + def __ExtendList(l, *args): + l.extend(*args) + return l + EOF + + " check assign to dict member works like bindeval + call assert_equal(3, py3eval('__UpdateDict( d, {"c": 3} )["c"]', locals)) + call assert_equal(3, d['c']) + + " check append lo list + call assert_equal(4, py3eval('len(__ExtendList(l, ["new item"]))', locals)) + call assert_equal("new item", l[-1]) + + " check calling a function + let StrLen = function('strlen') + call assert_equal(3, py3eval('f("abc")', {'f': StrLen})) +endfunc + " Test for vim.bindeval() func Test_python3_vim_bindeval() " Float
--- a/src/testdir/test_pyx2.vim +++ b/src/testdir/test_pyx2.vim @@ -38,6 +38,49 @@ func Test_pyxeval() endfunc +" Test for pyxeval with locals +func Test_python_pyeval_locals() + let str = 'a string' + let num = 0xbadb33f + let d = {'a': 1, 'b': 2, 'c': str} + let l = [ str, num, d ] + + let locals = #{ + \ s: str, + \ n: num, + \ d: d, + \ l: l, + \ } + + " check basics + call assert_equal('a string', pyxeval('s', locals)) + call assert_equal(0xbadb33f, pyxeval('n', locals)) + call assert_equal(d, pyxeval('d', locals)) + call assert_equal(l, pyxeval('l', locals)) + + py << trim EOF + def __UpdateDict(d, upd): + d.update(upd) + return d + + def __ExtendList(l, *args): + l.extend(*args) + return l + EOF + + " check assign to dict member works like bindeval + call assert_equal(3, pyxeval('__UpdateDict( d, {"c": 3} )["c"]', locals)) + call assert_equal(3, d['c']) + + " check append lo list + call assert_equal(4, pyxeval('len(__ExtendList(l, ["new item"]))', locals)) + call assert_equal("new item", l[-1]) + + " check calling a function + let StrLen = function('strlen') + call assert_equal(3, pyxeval('f("abc")', {'f': StrLen})) +endfunc + func Test_pyxfile() " No special comments nor shebangs redir => var
--- a/src/testdir/test_pyx3.vim +++ b/src/testdir/test_pyx3.vim @@ -37,6 +37,48 @@ func Test_pyxeval() call assert_match(s:py3pattern, split(pyxeval('sys.version'))[0]) endfunc +" Test for pyxeval with locals +func Test_python_pyeval_locals() + let str = 'a string' + let num = 0xbadb33f + let d = {'a': 1, 'b': 2, 'c': str} + let l = [ str, num, d ] + + let locals = #{ + \ s: str, + \ n: num, + \ d: d, + \ l: l, + \ } + + " check basics + call assert_equal('a string', pyxeval('s', locals)) + call assert_equal(0xbadb33f, pyxeval('n', locals)) + call assert_equal(d, pyxeval('d', locals)) + call assert_equal(l, pyxeval('l', locals)) + + py3 << trim EOF + def __UpdateDict(d, upd): + d.update(upd) + return d + + def __ExtendList(l, *args): + l.extend(*args) + return l + EOF + + " check assign to dict member works like bindeval + call assert_equal(3, pyxeval('__UpdateDict( d, {"c": 3} )["c"]', locals)) + call assert_equal(3, d['c']) + + " check append lo list + call assert_equal(4, pyxeval('len(__ExtendList(l, ["new item"]))', locals)) + call assert_equal("new item", l[-1]) + + " check calling a function + let StrLen = function('strlen') + call assert_equal(3, pyxeval('f("abc")', {'f': StrLen})) +endfunc func Test_pyxfile() " No special comments nor shebangs
new file mode 100644 --- /dev/null +++ b/src/testdir/test_vim9_python3.vim @@ -0,0 +1,17 @@ + +source check.vim +import './vim9.vim' as v9 +CheckFeature python3 + +def Test_python3_py3eval_locals() + var lines =<< trim EOF + var s = 'string' + var d = {'s': s} + assert_equal('string', py3eval('s', {'s': s})) + py3eval('d.update({"s": "new"})', {'d': d}) + assert_equal('new', d['s']) + EOF + v9.CheckDefAndScriptSuccess(lines) +enddef + +" vim: shiftwidth=2 sts=2 expandtab