To: vim_dev@googlegroups.com Subject: Patch 8.2.0149 Fcc: outbox From: Bram Moolenaar Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ------------ Patch 8.2.0149 Problem: Maintaining a Vim9 branch separately is more work. Solution: Merge the Vim9 script changes. Files: README.md, README_VIM9.md, runtime/doc/Makefile, runtime/doc/eval.txt, runtime/doc/options.txt, runtime/doc/tags, runtime/doc/vim9.txt, runtime/ftplugin/vim.vim, runtime/indent/vim.vim, runtime/syntax/vim.vim, src/Make_cyg_ming.mak, src/Make_mvc.mak, src/Makefile, src/blob.c, src/channel.c, src/dict.c, src/eval.c, src/evalbuffer.c, src/evalfunc.c, src/evalvars.c, src/ex_cmdidxs.h, src/ex_cmds.h, src/ex_docmd.c, src/ex_eval.c, src/filepath.c, src/globals.h, src/gui.c, src/if_lua.c, src/if_py_both.h, src/insexpand.c, src/json.c, src/list.c, src/macros.h, src/main.c, src/message.c, src/misc1.c, src/proto.h, src/proto/blob.pro, src/proto/eval.pro, src/proto/evalfunc.pro, src/proto/evalvars.pro, src/proto/ex_docmd.pro, src/proto/ex_eval.pro, src/proto/list.pro, src/proto/message.pro, src/proto/scriptfile.pro, src/proto/userfunc.pro, src/proto/vim9compile.pro, src/proto/vim9execute.pro, src/proto/vim9script.pro, src/scriptfile.c, src/session.c, src/structs.h, src/syntax.c, src/testdir/Make_all.mak, src/testdir/test_vim9_expr.vim, src/testdir/test_vim9_script.vim, src/testing.c, src/userfunc.c, src/vim.h, src/vim9.h, src/vim9compile.c, src/vim9execute.c, src/vim9script.c, src/viminfo.c *** ../vim-8.2.0148/README.md 2020-01-16 19:40:28.539521562 +0100 --- README.md 2020-01-26 15:37:41.063859539 +0100 *************** *** 31,36 **** --- 31,38 ---- Older versions of Vim run on MS-DOS, MS-Windows 95/98/Me/NT/2000, Amiga DOS, Atari MiNT, BeOS, RISC OS and OS/2. These are no longer maintained. + For Vim9 script see [README_VIM9](README_VIM9.md). + ## Distribution ## You can often use your favorite package manager to install Vim. On Mac and *** ../vim-8.2.0148/README_VIM9.md 2020-01-26 15:45:56.465683478 +0100 --- README_VIM9.md 2020-01-26 15:34:43.960605750 +0100 *************** *** 0 **** --- 1,344 ---- + ![Vim Logo](https://github.com/vim/vim/blob/master/runtime/vimlogo.gif) + + # What is Vim9? + + This is an experimental side of [Vim](https://github.com/vim/vim). + It explores ways of making Vim script faster and better. + + WARNING: The Vim9 script features are in the early stages of development, + anything can break! + + # Why Vim9? + + ## 1. FASTER VIM SCRIPT + + The third item on the poll results of 2018, after popup windows and text + properties, is faster Vim script. So how do we do that? + + I have been throwing some ideas around, and soon came to the conclusion + that the current way functions are called and executed, with + dictionaries for the arguments and local variables, is never going to be + very fast. We're lucky if we can make it twice as fast. The overhead + of a function call and executing every line is just too high. + + So what then? We can only make something fast by having a new way of + defining a function, with similar but different properties of the old + way: + * Arguments are only available by name, not through the a: dictionary or + the a:000 list. + * Local variables are not available in an l: dictionary. + * A few more things that slow us down, such as exception handling details. + + I Implemented a "proof of concept" and measured the time to run a simple + for loop with an addition (Justin used this example in his presentation, + full code is below): + + ``` vim + let sum = 0 + for i in range(1, 2999999) + let sum += i + endfor + ``` + + | how | time in sec | + | --------| -------- | + | Vim old | 5.018541 | + | Python | 0.369598 | + | Lua | 0.078817 | + | Vim new | 0.073595 | + + That looks very promising! It's just one example, but it shows how much + we can gain, and also that Vim script can be faster than builtin + interfaces. + + In practice the script would not do something useless as counting but change + the text. For example, re-indent all the lines: + + ``` vim + let totallen = 0 + for i in range(1, 100000) + call setline(i, ' ' .. getline(i)) + let totallen += len(getline(i)) + endfor + ``` + + | how | time in sec | + | --------| -------- | + | Vim old | 0.853752 | + | Python | 0.304584 | + | Lua | 0.286573 | + | Vim new | 0.190276 | + + The differences are smaller, but Vim 9 script is clearly the fastest. + + How does Vim9 script work? The function is first compiled into a sequence of + instructions. Each instruction has one or two parameters and a stack is + used to store intermediate results. Local variables are also on the + stack, space is reserved during compilation. This is a fairly normal + way of compilation into an intermediate format, specialized for Vim, + e.g. each stack item is a typeval_T. And one of the instructions is + "execute Ex command", for commands that are not compiled. + + + ## 2. PHASING OUT INTERFACES + + Attempts have been made to implement functionality with built-in script + languages such as Python, Perl, Lua, Tcl and Ruby. This never gained much + foothold, for various reasons. + + Instead of using script language support in Vim: + * Encourage implementing external tools in any language and communicate + with them. The job and channel support already makes this possible. + Really any language can be used, also Java and Go, which are not + available built-in. + * Phase out the built-in language interfaces, make maintenance a bit easier + and executables easier to build. They will be kept for backwards + compatibility, no new features. + * Improve the Vim script language, it is used to communicate with the external + tool and implements the Vim side of the interface. Also, it can be used when + an external tool is undesired. + + All together this creates a clear situation: Vim with the +eval feature + will be sufficient for most plugins, while some plugins require + installing a tool that can be written in any language. No confusion + about having Vim but the plugin not working because some specific + language is missing. This is a good long term goal. + + Rationale: Why is it better to run a tool separately from Vim than using a + built-in interface and interpreter? Take for example something that is + written in Python: + * The built-in interface uses the embedded python interpreter. This is less + well maintained than the python command. Building Vim with it requires + installing developer packages. If loaded dynamically there can be a version + mismatch. + * When running the tool externally the standard python command can be used, + which is quite often available by default or can be easily installed. + * The built-in interface has an API that is unique for Vim with Python. This is + an extra API to learn. + * A .py file can be compiled into a .pyc file and execute much faster. + * Inside Vim multi-threading can cause problems, since the Vim core is single + threaded. In an external tool there are no such problems. + * The Vim part is written in .vim files, the Python part is in .py files, this + is nicely separated. + * Disadvantage: An interface needs to be made between Vim and Python. + JSON is available for this, and it's fairly easy to use. But it still + requires implementing asynchronous communication. + + + ## 3. BETTER VIM SCRIPT + + To make Vim faster a new way of defining a function needs to be added. + While we are doing that, since the lines in this function won't be fully + backwards compatible anyway, we can also make Vim script easier to use. + In other words: "less weird". Making it work more like modern + programming languages will help. No surprises. + + A good example is how in a function the arguments are prefixed with + "a:". No other language I know does that, so let's drop it. + + Taking this one step further is also dropping "s:" for script-local variables; + everything at the script level is script-local by default. Since this is not + backwards compatible it requires a new script style: Vim9 script! + + It should be possible to convert code from other languages to Vim + script. We can add functionality to make this easier. This still needs + to be discussed, but we can consider adding type checking and a simple + form of classes. If you look at JavaScript for example, it has gone + through these stages over time, adding real class support and now + TypeScript adds type checking. But we'll have to see how much of that + we actually want to include in Vim script. Ideally a conversion tool + can take Python, JavaScript or TypeScript code and convert it to Vim + script, with only some things that cannot be converted. + + Vim script won't work the same as any specific language, but we can use + mechanisms that are commonly known, ideally with the same syntax. One + thing I have been thinking of is assignments without ":let". I often + make that mistake (after writing JavaScript especially). I think it is + possible, if we make local variables shadow commands. That should be OK, + if you shadow a command you want to use, just rename the variable. + Using "let" and "const" to declare a variable, like in JavaScript and + TypeScript, can work: + + + ``` vim + def MyFunction(arg: number): number + let local = 1 + let todo = arg + const ADD = 88 + while todo > 0 + local += ADD + --todo + endwhile + return local + enddef + ``` + + The similarity with JavaScript/TypeScript can also be used for dependencies + between files. Vim currently uses the `:source` command, which has several + disadvantages: + * In the sourced script, is not clear what it provides. By default all + functions are global and can be used elsewhere. + * In a script that sources other scripts, it is not clear what function comes + from what sourced script. Finding the implementation is a hassle. + * Prevention of loading the whole script twice must be manually implemented. + + We can use the `:import` and `:export` commands from the JavaScript standard to + make this much better. For example, in script "myfunction.vim" define a + function and export it: + + ``` vim + vim9script " Vim9 script syntax used here + + let local = 'local variable is not exported, script-local' + + export def MyFunction() " exported function + ... + + def LocalFunction() " not exported, script-local + ... + ``` + + And in another script import the function: + + ``` vim + vim9script " Vim9 script syntax used here + + import MyFunction from 'myfunction.vim' + ``` + + This looks like JavaScript/TypeScript, thus many users will understand the + syntax. + + These are ideas, this will take time to design, discuss and implement. + Eventually this will lead to Vim 9! + + + ## Code for sum time measurements + + Vim was build with -O2. + + ``` vim + func VimOld() + let sum = 0 + for i in range(1, 2999999) + let sum += i + endfor + return sum + endfunc + + func Python() + py3 << END + sum = 0 + for i in range(1, 3000000): + sum += i + END + return py3eval('sum') + endfunc + + func Lua() + lua << END + sum = 0 + for i = 1, 2999999 do + sum = sum + i + end + END + return luaeval('sum') + endfunc + + def VimNew() + let sum = 0 + for i in range(1, 2999999) + let sum += i + endfor + return sum + enddef + + let start = reltime() + echo VimOld() + echo 'Vim old: ' .. reltimestr(reltime(start)) + + let start = reltime() + echo Python() + echo 'Python: ' .. reltimestr(reltime(start)) + + let start = reltime() + echo Lua() + echo 'Lua: ' .. reltimestr(reltime(start)) + + let start = reltime() + echo VimNew() + echo 'Vim new: ' .. reltimestr(reltime(start)) + ``` + + ## Code for indent time measurements + + ``` vim + def VimNew(): number + let totallen = 0 + for i in range(1, 100000) + setline(i, ' ' .. getline(i)) + totallen += len(getline(i)) + endfor + return totallen + enddef + + func VimOld() + let totallen = 0 + for i in range(1, 100000) + call setline(i, ' ' .. getline(i)) + let totallen += len(getline(i)) + endfor + return totallen + endfunc + + func Lua() + lua << END + b = vim.buffer() + totallen = 0 + for i = 1, 100000 do + b[i] = " " .. b[i] + totallen = totallen + string.len(b[i]) + end + END + return luaeval('totallen') + endfunc + + func Python() + py3 << END + cb = vim.current.buffer + totallen = 0 + for i in range(0, 100000): + cb[i] = ' ' + cb[i] + totallen += len(cb[i]) + END + return py3eval('totallen') + endfunc + + new + call setline(1, range(100000)) + let start = reltime() + echo VimOld() + echo 'Vim old: ' .. reltimestr(reltime(start)) + bwipe! + + new + call setline(1, range(100000)) + let start = reltime() + echo Python() + echo 'Python: ' .. reltimestr(reltime(start)) + bwipe! + + new + call setline(1, range(100000)) + let start = reltime() + echo Lua() + echo 'Lua: ' .. reltimestr(reltime(start)) + bwipe! + + new + call setline(1, range(100000)) + let start = reltime() + echo VimNew() + echo 'Vim new: ' .. reltimestr(reltime(start)) + bwipe! + ``` *** ../vim-8.2.0148/runtime/doc/Makefile 2020-01-21 22:13:23.050546956 +0100 --- runtime/doc/Makefile 2020-01-26 15:30:02.021794430 +0100 *************** *** 149,154 **** --- 149,155 ---- version7.txt \ version8.txt \ vi_diff.txt \ + vim9.txt \ visual.txt \ windows.txt \ workshop.txt *************** *** 289,294 **** --- 290,296 ---- version8.html \ vi_diff.html \ vimindex.html \ + vim9.html \ visual.html \ windows.html \ workshop.html *** ../vim-8.2.0148/runtime/doc/eval.txt 2020-01-02 14:02:12.312159506 +0100 --- runtime/doc/eval.txt 2020-01-26 15:30:02.025794415 +0100 *************** *** 12,17 **** --- 12,21 ---- done, the features in this document are not available. See |+eval| and |no-eval-feature|. + This file is about the backwards compatible Vim script. For Vim9 script, + which executes much faster, supports type checking and much more, see + |vim9.txt|. + 1. Variables |variables| 1.1 Variable types 1.2 Function references |Funcref| *************** *** 2512,2519 **** or |:tcd| hasmapto({what} [, {mode} [, {abbr}]]) Number |TRUE| if mapping to {what} exists ! histadd({history}, {item}) String add an item to a history ! histdel({history} [, {item}]) String remove an item from a history histget({history} [, {index}]) String get the item {index} from a history histnr({history}) Number highest index of a history hlexists({name}) Number |TRUE| if highlight group {name} exists --- 2516,2523 ---- or |:tcd| hasmapto({what} [, {mode} [, {abbr}]]) Number |TRUE| if mapping to {what} exists ! histadd({history}, {item}) Number add an item to a history ! histdel({history} [, {item}]) Number remove an item from a history histget({history} [, {index}]) String get the item {index} from a history histnr({history}) Number highest index of a history hlexists({name}) Number |TRUE| if highlight group {name} exists *************** *** 3047,3056 **** The {winid} argument specifies the window ID, see |argc()|. For the Vim command line arguments see |v:argv|. - - assert_ functions are documented here: |assert-functions-details| - - asin({expr}) *asin()* Return the arc sine of {expr} measured in radians, as a |Float| in the range of [-pi/2, pi/2]. --- 3051,3056 ---- *************** *** 3068,3073 **** --- 3068,3077 ---- {only available when compiled with the |+float| feature} + assert_ functions are documented here: |assert-functions-details| + + + atan({expr}) *atan()* Return the principal value of the arc tangent of {expr}, in the range [-pi/2, +pi/2] radians, as a |Float|. *************** *** 10629,10635 **** < *feature-list* ! There are four types of features: 1. Features that are only supported when they have been enabled when Vim was compiled |+feature-list|. Example: > :if has("cindent") --- 10634,10640 ---- < *feature-list* ! There are three types of features: 1. Features that are only supported when they have been enabled when Vim was compiled |+feature-list|. Example: > :if has("cindent") *************** *** 10893,10898 **** --- 10898,10906 ---- functions. The function executes a sequence of Ex commands. Normal mode commands can be executed with the |:normal| command. + This section is about the legacy functions. For the Vim9 functions, which + execute much faster, support type checking and more, see |vim9.txt|. + The function name must start with an uppercase letter, to avoid confusion with builtin functions. To prevent from using the same name in different scripts avoid obvious, short names. A good habit is to start the function name with *** ../vim-8.2.0148/runtime/doc/options.txt 2020-01-18 15:53:15.954799326 +0100 --- runtime/doc/options.txt 2020-01-26 15:30:02.025794415 +0100 *************** *** 6224,6229 **** --- 6221,6227 ---- compiler/ compiler files |:compiler| doc/ documentation |write-local-help| ftplugin/ filetype plugins |write-filetype-plugin| + import/ files that are found by `:import` indent/ indent scripts |indent-expression| keymap/ key mapping files |mbyte-keymap| lang/ menu translations |:menutrans| *** ../vim-8.2.0148/runtime/doc/tags 2019-12-12 13:13:29.000000000 +0100 --- runtime/doc/tags 2020-01-26 15:30:02.025794415 +0100 *************** *** 4061,4066 **** --- 4061,4067 ---- E288 mbyte.txt /*E288* E289 mbyte.txt /*E289* E29 change.txt /*E29* + E292 message.txt /*E292* E293 message.txt /*E293* E294 message.txt /*E294* E295 message.txt /*E295* *************** *** 4753,4759 **** E948 terminal.txt /*E948* E949 editing.txt /*E949* E95 message.txt /*E95* ! E950 message.txt /*E950* E951 pattern.txt /*E951* E952 autocmd.txt /*E952* E953 terminal.txt /*E953* --- 4754,4760 ---- E948 terminal.txt /*E948* E949 editing.txt /*E949* E95 message.txt /*E95* ! E950 options.txt /*E950* E951 pattern.txt /*E951* E952 autocmd.txt /*E952* E953 terminal.txt /*E953* *************** *** 5989,5995 **** debugbreak() eval.txt /*debugbreak()* debugger-compilation debugger.txt /*debugger-compilation* debugger-features debugger.txt /*debugger-features* - debugger-integration debugger.txt /*debugger-integration* debugger-support debugger.txt /*debugger-support* debugger.txt debugger.txt /*debugger.txt* dec-mouse options.txt /*dec-mouse* --- 5990,5995 ---- *************** *** 6276,6281 **** --- 6276,6282 ---- farsi farsi.txt /*farsi* farsi.txt farsi.txt /*farsi.txt* fasm.vim syntax.txt /*fasm.vim* + fast-functions vim9.txt /*fast-functions* fcs_choice-variable eval.txt /*fcs_choice-variable* fcs_reason-variable eval.txt /*fcs_reason-variable* feature-list eval.txt /*feature-list* *************** *** 6498,6503 **** --- 6499,6505 ---- ft-r-indent indent.txt /*ft-r-indent* ft-r-syntax syntax.txt /*ft-r-syntax* ft-readline-syntax syntax.txt /*ft-readline-syntax* + ft-rego-syntax syntax.txt /*ft-rego-syntax* ft-rexx-syntax syntax.txt /*ft-rexx-syntax* ft-rmd-plugin filetype.txt /*ft-rmd-plugin* ft-rmd-syntax syntax.txt /*ft-rmd-syntax* *************** *** 7260,7265 **** --- 7262,7268 ---- i_CTRL-R_CTRL-O insert.txt /*i_CTRL-R_CTRL-O* i_CTRL-R_CTRL-P insert.txt /*i_CTRL-R_CTRL-P* i_CTRL-R_CTRL-R insert.txt /*i_CTRL-R_CTRL-R* + i_CTRL-SHIFT-Q insert.txt /*i_CTRL-SHIFT-Q* i_CTRL-SHIFT-V insert.txt /*i_CTRL-SHIFT-V* i_CTRL-T insert.txt /*i_CTRL-T* i_CTRL-U insert.txt /*i_CTRL-U* *************** *** 7556,7561 **** --- 7559,7565 ---- logiPat pi_logipat.txt /*logiPat* logiPat-arg pi_logipat.txt /*logiPat-arg* logiPat-caveat pi_logipat.txt /*logiPat-caveat* + logiPat-cmd pi_logipat.txt /*logiPat-cmd* logiPat-contents pi_logipat.txt /*logiPat-contents* logiPat-copyright pi_logipat.txt /*logiPat-copyright* logiPat-examples pi_logipat.txt /*logiPat-examples* *************** *** 8561,8566 **** --- 8565,8571 ---- register-faq sponsor.txt /*register-faq* register-variable eval.txt /*register-variable* registers change.txt /*registers* + rego.vim syntax.txt /*rego.vim* regular-expression pattern.txt /*regular-expression* reload editing.txt /*reload* reltime() eval.txt /*reltime()* *************** *** 9540,9545 **** --- 9545,9551 ---- twice if_cscop.txt /*twice* two-engines pattern.txt /*two-engines* type() eval.txt /*type()* + type-inference vim9.txt /*type-inference* type-mistakes tips.txt /*type-mistakes* typecorr-settings usr_41.txt /*typecorr-settings* typecorr.txt usr_41.txt /*typecorr.txt* *************** *** 9903,9908 **** --- 9909,9918 ---- vim.vim syntax.txt /*vim.vim* vim7 version7.txt /*vim7* vim8 version8.txt /*vim8* + vim9-rationale vim9.txt /*vim9-rationale* + vim9-script vim9.txt /*vim9-script* + vim9-types vim9.txt /*vim9-types* + vim9.txt vim9.txt /*vim9.txt* vim: options.txt /*vim:* vim_announce intro.txt /*vim_announce* vim_dev intro.txt /*vim_dev* *************** *** 10055,10060 **** --- 10065,10071 ---- windows95 os_win32.txt /*windows95* windows98 os_win32.txt /*windows98* windowsme os_win32.txt /*windowsme* + windowsversion() eval.txt /*windowsversion()* winheight() eval.txt /*winheight()* winid windows.txt /*winid* winlayout() eval.txt /*winlayout()* *** ../vim-8.2.0148/runtime/doc/vim9.txt 2020-01-26 15:45:56.501683306 +0100 --- runtime/doc/vim9.txt 2020-01-26 15:30:02.025794415 +0100 *************** *** 0 **** --- 1,561 ---- + *vim9.txt* For Vim version 8.2. Last change: 2019 Dec 06 + + + VIM REFERENCE MANUAL by Bram Moolenaar + + + THIS IS STILL UNDER DEVELOPMENT - ANYTHING CAN BREAK - ANYTHING CAN CHANGE + + Vim9 script commands and expressions. + + Most expression help is in |eval.txt|. This file is about the new syntax and + features in Vim9 script. + + THIS IS STILL UNDER DEVELOPMENT - ANYTHING CAN BREAK - ANYTHING CAN CHANGE + + + 1 What is Vim9 script? |vim9-script| + 2. Differences |vim9-differences| + 3. New style functions |fast-functions| + 4. Types |vim9-types| + 5. Namespace, Import and Export |vim9script| + + 9. Rationale |vim9-rationale| + + ============================================================================== + + 1. What is Vim9 script? *vim9-script* + + THIS IS STILL UNDER DEVELOPMENT - ANYTHING CAN BREAK - ANYTHING CAN CHANGE + + Vim script has been growing over time, while keeping backwards compatibility. + That means bad choices from the past often can't be changed. Execution is + quite slow, every line is parsed every time it is executed. + + The main goal of Vim9 script is to drastically improve performance. An + increase in execution speed of 10 to 100 times can be expected. A secondary + goal is to avoid Vim-specific constructs and get closer to commonly used + programming languages, such as JavaScript, TypeScript and Java. + + The performance improvements can only be achieved by not being 100% backwards + compatible. For example, in a function the arguments are not available in the + "a:" dictionary, as creating that dictionary adds quite a lot of overhead. + Other differences are more subtle, such as how errors are handled. + + The Vim9 script syntax and semantics are used in: + - a function defined with the `:def` command + - a script file where the first command is `vim9script` + + When using `:function` in a Vim9 script file the legacy syntax is used. + However, this is discouraged. + + Vim9 script and legacy Vim script can be mixed. There is no need to rewrite + old scripts, they keep working as before. + + ============================================================================== + + 2. Differences from legacy Vim script *vim9-differences* + + THIS IS STILL UNDER DEVELOPMENT - ANYTHING CAN BREAK - ANYTHING CAN CHANGE + + Vim9 functions ~ + + `:def` has no extra arguments like `:function` does: "range", "abort", "dict" + or "closure". A `:def` function always aborts on an error, does not get a + range passed and cannot be a "dict" function. + + In the function body: + - Arguments are accessed by name, without "a:". + - There is no "a:" dictionary or "a:000" list. Variable arguments are defined + with a name and have a list type: > + def MyFunc(...itemlist: list) + for item in itemlist + ... + + + Variable declarations with :let and :const ~ + + Local variables need to be declared with `:let`. Local constants need to be + declared with `:const`. We refer to both as "variables". + + Variables can be local to a script, function or code block: > + vim9script + let script_var = 123 + def SomeFunc() + let func_var = script_var + if cond + let block_var = func_var + ... + + The variables are only visible in the block where they are defined and nested + blocks. Once the block ends the variable is no longer accessible: > + if cond + let inner = 5 + else + let inner = 0 + endif + echo inner " Error! + + The declaration must be done earlier: > + let inner: number + if cond + inner = 5 + else + inner = 0 + endif + echo inner + + To intentionally use a variable that won't be available later, a block can be + used: > + { + let temp = 'temp' + ... + } + echo temp " Error! + + An existing variable cannot be assigend to with `:let`, since that implies a + declaration. An exception is global variables: these can be both used with + and without `:let`, because there is no rule about where they are declared. + + Variables cannot shadow previously defined variables. + Variables may shadow Ex commands, rename the variable if needed. + + Since "&opt = value" is now assigning a value to option "opt", ":&" cannot be + used to repeat a `:substitute` command. + + + Omitting :call and :eval ~ + + Functions can be called without `:call`: > + writefile(lines, 'file') + Using `:call` is still posible, but this is discouraged. + + A method call without `eval` is possible, so long as the start is an + identifier or can't be an Ex command. It does not work for string constants: > + myList->add(123) " works + g:myList->add(123) " works + [1, 2, 3]->Process() " works + #{a: 1, b: 2}->Process() " works + {'a': 1, 'b': 2}->Process() " works + "foobar"->Process() " does NOT work + eval "foobar"->Process() " works + + + No curly braces expansion ~ + + |curly-braces-names| cannot be used. + + + Comperators ~ + + The 'ignorecase' option is not used for comperators that use strings. + + + White space ~ + + Vim9 script enforces proper use of white space. This is no longer allowed: > + let var=234 " Error! + let var= 234 " Error! + let var =234 " Error! + There must be white space before and after the "=": > + let var = 234 " OK + + White space is required around most operators. + + White space is not allowed: + - Between a function name and the "(": > + call Func (arg) " Error! + call Func + \ (arg) " Error! + call Func(arg) " OK + call Func( + \ arg) " OK + + + Conditions and expressions ~ + + Conditions and expression are mostly working like they do in JavaScript. A + difference is made where JavaScript does not work like most people expect. + Specifically, an empty list is falsey. + + Any type of variable can be used as a condition, there is no error, not even + for using a list or job. This is very much like JavaScript, but there are a + few exceptions. + + type TRUE when ~ + bool v:true + number non-zero + float non-zero + string non-empty + blob non-empty + list non-empty (different from JavaScript) + dictionary non-empty (different from JavaScript) + funcref when not NULL + partial when not NULL + special v:true + job when not NULL + channel when not NULL + class when not NULL + object when not NULL (TODO: when isTrue() returns v:true) + + The boolean operators "||" and "&&" do not change the value: > + 8 || 2 == 8 + 0 || 2 == 2 + 0 || '' == '' + 8 && 2 == 2 + 0 && 2 == 0 + [] && 2 == [] + + When using `..` for string concatenation the arguments are always converted to + string. > + 'hello ' .. 123 == 'hello 123' + 'hello ' .. v:true == 'hello true' + + In Vim9 script one can use "true" for v:true and "false" for v:false. + + + ============================================================================== + + 3. New style functions *fast-functions* + + THIS IS STILL UNDER DEVELOPMENT - ANYTHING CAN BREAK - ANYTHING CAN CHANGE + + *:def* + :def[!] {name}([arguments])[: {return-type} + Define a new function by the name {name}. The body of + the function follows in the next lines, until the + matching `:enddef`. + + When {return-type} is omitted the return type will be + decided upon by the first encountered `return` + statement in the function. E.g., for: > + return 'message' + < The return type will be "string". + + {arguments} is a sequence of zero or more argument + declarations. There are three forms: + {name}: {type} + {name} = {value} + {name}: {type} = {value} + The first form is a mandatory argument, the caller + must always provide them. + The second and third form are optional arguments. + When the caller omits an argument the {value} is used. + + [!] is used as with `:function`. + + *:enddef* + :enddef End of a function defined with `:def`. + + + ============================================================================== + + 4. Types *vim9-types* + + THIS IS STILL UNDER DEVELOPMENT - ANYTHING CAN BREAK - ANYTHING CAN CHANGE + + The following builtin types are supported: + bool + number + float + string + blob + list + dict + (a: type, b: type): type + job + channel + + Not supported yet: + tuple + + These types can be used in declarations, but no variable will have this type: + type|type + void + any + + There is no array type, use list instead. For a list constant an + efficient implementation is used that avoids allocating lot of small pieces of + memory. + + A function defined with `:def` must declare the return type. If there is no + type then the function doesn't return anything. "void" is used in type + declarations. + + Custom types can be defined with `:type`: > + :type MyList list + {not implemented yet} + + And classes and interfaces can be used as types: > + :class MyClass + :let mine: MyClass + + :interface MyInterface + :let mine: MyInterface + + :class MyTemplate + :let mine: MyTemplate + :let mine: MyTemplate + + :class MyInterface + :let mine: MyInterface + :let mine: MyInterface + {not implemented yet} + + + Type inference *type-inference* + + In general: Whenever the type is clear it can be omitted. For example, when + declaring a variable and giving it a value: > + let var = 0 " infers number type + let var = 'hello' " infers string type + + + ============================================================================== + + 5. Namespace, Import and Export + *vim9script* *vim9-export* *vim9-import* + + THIS IS STILL UNDER DEVELOPMENT - ANYTHING CAN BREAK - ANYTHING CAN CHANGE + + A Vim9 script can be written to be imported. This means that everything in + the script is local, unless exported. Those exported items, and only those + items, can then be imported in another script. + + + Namespace ~ + *:vim9script* *:vim9* + To recognize an file that can be imported the `vim9script` statement must + appear as the first statement in the file. It tells Vim to interpret the + script in its own namespace, instead of the global namespace. If a file + starts with: > + vim9script + let myvar = 'yes' + Then "myvar" will only exist in this file. While without `vim9script` it would + be available as `g:myvar` from any other script and function. + + The variables at the file level are very much like the script-local "s:" + variables in legacy Vim script, but the "s:" is omitted. + + In Vim9 script the global "g:" namespace can still be used as before. + + A side effect of `:vim9script` is that the 'cpoptions' option is set to the + Vim default value, like with: > + :set cpo&vim + One of the effects is that |line-continuation| is always enabled. + The original value of 'cpoptions' is restored at the end of the script. + + + Export ~ + *:export* *:exp* + Exporting one item can be written as: > + export const EXPORTED_CONST = 1234 + export let someValue = ... + export def MyFunc() ... + export class MyClass ... + + As this suggests, only constants, variables, `:def` functions and classes can + be exported. + + Alternatively, an export statement can be used to export several already + defined (otherwise script-local) items: > + export {EXPORTED_CONST, someValue, MyFunc, MyClass} + + + Import ~ + *:import* *:imp* + The exported items can be imported individually in another Vim9 script: > + import EXPORTED_CONST from "thatscript.vim" + import MyClass from "myclass.vim" + + To import multiple items at the same time: > + import {someValue, MyClass} from "thatscript.vim" + + In case the name is ambigiuous, another name can be specified: > + import MyClass as ThatClass from "myclass.vim" + import {someValue, MyClass as ThatClass} from "myclass.vim" + + To import all exported items under a specific identifier: > + import * as That from 'thatscript.vim' + + Then you can use "That.EXPORTED_CONST", "That.someValue", etc. You are free + to choose the name "That", but it is highly recommended to use the name of the + script file to avoid confusion. + + The script name after `import` can be: + - A relative path, starting "." or "..". This finds a file relative to the + location of the script file itself. This is useful to split up a large + plugin into several files. + - An absolute path, starting with "/" on Unix or "D:/" on MS-Windows. This + will be rarely used. + - A path not being relative or absolute. This will be found in the + "import" subdirectories of 'runtimepath' entries. The name will usually be + longer and unique, to avoid loading the wrong file. + + Once a vim9 script file has been imported, the result is cached and used the + next time the same script is imported. It will not be read again. + *:import-cycle* + The `import` commands are executed when encountered. If that script (directly + or indirectly) imports the current script, then items defined after the + `import` won't be processed yet. Therefore cyclic imports can exist, but may + result in undefined items. + + + Import in an autoload script ~ + + For optimal startup speed, loading scripts should be postponed until they are + actually needed. A recommended mechamism: + + 1. In the plugin define user commands, functions and/or mappings that refer to + an autoload script. > + command -nargs=1 SearchForStuff call searchfor#Stuff() + + < This goes in .../plugin/anyname.vim. "anyname.vim" can be freely chosen. + + 2. In the autocommand script do the actual work. You can import items from + other files to split up functionality in appropriate pieces. > + vim9script + import FilterFunc from "../import/someother.vim" + def searchfor#Stuff(arg: string) + let filtered = FilterFunc(arg) + ... + < This goes in .../autoload/searchfor.vim. "searchfor" in the file name + must be exactly the same as the prefix for the function name, that is how + Vim finds the file. + + 3. Other functionality, possibly shared between plugins, contains the exported + items and any private items. > + vim9script + let localVar = 'local' + export def FilterFunc(arg: string): string + ... + < This goes in .../import/someother.vim. + + + Import in legacy Vim script ~ + + If an `import` statement is used in legacy Vim script, for identifier the + script-local "s:" namespace will be used, even when "s:" is not specified. + + + ============================================================================== + + 9. Rationale *vim9-rationale* + + The :def command ~ + + Plugin writers have asked for a much faster Vim script. Investigation have + shown that keeping the existing semantics of funtion calls make this close to + impossible, because of the overhead involved with calling a function, setting + up the local function scope and executing lines. There are many details that + need to be handled, such as error messages and exceptions. The need to create + a dictionary for a: and l: scopes, the a:000 list and several others add too + much overhead that cannot be avoided. + + Therefore the `:def` method to define a new-style function had to be added, + which allows for a function with different semantics. Most things still work + as before, but some parts do not. A new way to define a function was + considered the best way to separate the old-style code from Vim9 script code. + + Using "def" to define a function comes from Python. Other languages use + "function" which clashes with legacy Vim script. + + + Type checking ~ + + When compiling lines of Vim commands into instructions as much as possible + should be done at compile time. Postponing it to runtime makes the execution + slower and means mistakes are found only later. For example, when + encountering the "+" character and compiling this into a generic add + instruction, at execution time the instruction would have to inspect the type + of the arguments and decide what kind of addition to do. And when the + type is dictionary throw an error. If the types are known to be numbers then + an "add number" instruction can be used, which is faster. The error can be + given at compile time, no error handling is needed at runtime. + + The syntax for types is similar to Java, since it is easy to understand and + widely used. The type names are what was used in Vim before, with some + additions such as "void" and "bool". + + + JavaScript/TypeScript syntax and semantics ~ + + Script writers have complained that the Vim script syntax is unexpectedly + different from what they are used to. To reduce this complaint popular + languages will be used as an example. At the same time, we do not want to + abondon the well-known parts of legacy Vim script. + + Since Vim already uses `:let` and `:const` and optional type checking is + desirable, the JavaScript/TypeScript syntax fits best for variable + declarations. > + const greeting = 'hello' " string type is inferred + let name: string + ... + name = 'John' + + Expression evaluation was already close to what JavaScript and other languages + are doing. Some details are unexpected and can be fixed. For example how the + || and && operators work. Legacy Vim script: > + let result = 44 + ... + return result || 0 " returns 1 + + Vim9 script works like JavaScript, keep the value: > + let result = 44 + ... + return result || 0 " returns 44 + + On the other hand, overloading "+" to use both for addition and string + concatenation goes against legacy Vim script and often leads to mistakes. + For that reason we will keep using ".." for string concatenation. Lua also + uses ".." this way. + + + Import and Export ~ + + A problem of legacy Vim script is that by default all functions and variables + are global. It is possible to make them script-local, but then they are not + available in other scripts. + + In Vim9 script a mechanism very similar to the Javascript import and export + mechanism is supported. It is a variant to the existing `:source` command + that works like one would expect: + - Instead of making everything global by default, everything is script-local, + unless exported. + - When importing a script the symbols that are imported are listed, avoiding + name conflicts and failures if later functionality is added. + - The mechanism allows for writing a big, long script with a very clear API: + the exported function(s) and class(es). + - By using relative paths loading can be much faster for an import inside of a + package, no need to search many directories. + - Once an import has been used, it can be cached and loading it again can be + avoided. + - The Vim-specific use of "s:" to make things script-local can be dropped. + + + Classes ~ + + Vim supports interfaces to Perl, Python, Lua, Tcl and a few others. But + these have never become widespread. When Vim 9 was designed a decision was + made to phase out these interfaces and concentrate on Vim script, while + encouraging plugin authors to write code in any language and run it as an + external tool, using jobs and channels. + + Still, using an external tool has disadvantages. An alternative is to convert + the tool into Vim script. For that to be possible without too much + translation, and keeping the code fast at the same time, the constructs of the + tool need to be supported. Since most languages support classes the lack of + class support in Vim is then a problem. + + Previously Vim supported a kind-of object oriented programming by adding + methods to a dictionary. With some care this could be made to work, but it + does not look like real classes. On top of that, it's very slow, because of + the use of dictionaries. + + The support of classes in Vim9 script is a "minimal common functionality" of + class support in most languages. It works mostly like Java, which is the most + popular programming language. + + + + vim:tw=78:ts=8:noet:ft=help:norl: *** ../vim-8.2.0148/runtime/ftplugin/vim.vim 2018-09-08 15:51:51.000000000 +0200 --- runtime/ftplugin/vim.vim 2020-01-26 15:30:02.025794415 +0100 *************** *** 84,91 **** --- 84,93 ---- let b:match_ignorecase = 0 let b:match_words = \ '\:\:\,' . + \ '\:\:\,' . \ '\<\(wh\%[ile]\|for\)\>:\:\:\,' . \ '\:\:\,' . + \ '{:},' . \ '\:\:\:\,' . \ '\\)\@!\S:\,' " Ignore syntax region commands and settings, any 'en*' would clobber *** ../vim-8.2.0148/runtime/indent/vim.vim 2019-10-31 20:33:36.000000000 +0100 --- runtime/indent/vim.vim 2020-01-26 15:30:02.025794415 +0100 *************** *** 10,16 **** let b:did_indent = 1 setlocal indentexpr=GetVimIndent() ! setlocal indentkeys+==end,=else,=cat,=fina,=END,0\\,0=\"\\\ let b:undo_indent = "setl indentkeys< indentexpr<" --- 10,16 ---- let b:did_indent = 1 setlocal indentexpr=GetVimIndent() ! setlocal indentkeys+==end,=},=else,=cat,=fina,=END,0\\,0=\"\\\ let b:undo_indent = "setl indentkeys< indentexpr<" *************** *** 92,98 **** else " A line starting with :au does not increment/decrement indent. if prev_text !~ '^\s*au\%[tocmd]' ! let i = match(prev_text, '\(^\||\)\s*\(if\|wh\%[ile]\|for\|try\|cat\%[ch]\|fina\%[lly]\|fu\%[nction]\|el\%[seif]\)\>') if i >= 0 let ind += shiftwidth() if strpart(prev_text, i, 1) == '|' && has('syntax_items') --- 92,98 ---- else " A line starting with :au does not increment/decrement indent. if prev_text !~ '^\s*au\%[tocmd]' ! let i = match(prev_text, '\(^\||\)\s*\({\|\(if\|wh\%[ile]\|for\|try\|cat\%[ch]\|fina\%[lly]\|fu\%[nction]\|def\|el\%[seif]\)\>\)') if i >= 0 let ind += shiftwidth() if strpart(prev_text, i, 1) == '|' && has('syntax_items') *************** *** 115,122 **** " Subtract a 'shiftwidth' on a :endif, :endwhile, :catch, :finally, :endtry, ! " :endfun, :else and :augroup END. ! if cur_text =~ '^\s*\(ene\@!\|cat\|fina\|el\|aug\%[roup]\s\+[eE][nN][dD]\)' let ind = ind - shiftwidth() endif --- 115,122 ---- " Subtract a 'shiftwidth' on a :endif, :endwhile, :catch, :finally, :endtry, ! " :endfun, :enddef, :else and :augroup END. ! if cur_text =~ '^\s*\(ene\@!\|}\|cat\|fina\|el\|aug\%[roup]\s\+[eE][nN][dD]\)' let ind = ind - shiftwidth() endif *** ../vim-8.2.0148/runtime/syntax/vim.vim 2019-11-29 21:22:39.000000000 +0100 --- runtime/syntax/vim.vim 2020-01-26 15:30:02.025794415 +0100 *************** *** 1,8 **** " Vim syntax file " Language: Vim 8.0 script ! " Maintainer: Charles E. Campbell " Last Change: November 29, 2019 ! " Version: 8.0-28 " URL: http://www.drchip.org/astronaut/vim/index.html#SYNTAX_VIM " Automatically generated keyword lists: {{{1 --- 1,8 ---- " Vim syntax file " Language: Vim 8.0 script ! " Maintainer: Charles E. Campbell " Last Change: November 29, 2019 ! " Version: 8.0-29 " URL: http://www.drchip.org/astronaut/vim/index.html#SYNTAX_VIM " Automatically generated keyword lists: {{{1 *************** *** 26,31 **** --- 26,32 ---- syn keyword vimCommand contained addd arge[dit] bN[ext] bf[irst] br[ewind] bufdo c[hange] caddf[ile] cbel[ow] ce[nter] cgetb[uffer] chi[story] cn[ext] colo[rscheme] cons[t] cs d[elete] deletel delm[arks] diffo[ff] dir doaut ea el[se] endt[ry] exu[sage] fin[d] foldc[lose] g h[elp] hi if intro k lN[ext] laddb[uffer] lb[uffer] lcl[ose] lex[pr] lgete[xpr] lla[st] lnew[er] lockv[ar] ls lvimgrepa[dd] mat[ch] mksp[ell] n[ext] noa nu[mber] ownsyntax ped[it] prev[ious] ps[earch] ptn[ext] py3 python3 qa[ll] redr[aw] retu[rn] rubyd[o] sIe sN[ext] sb[uffer] sbp[revious] sci scs sf[ind] sgi si sim[alt] sm[agic] sno[magic] spe[llgood] spellu[ndo] sre[wind] srp startr[eplace] sunme sy t tabc[lose] tabl[ast] tabp[revious] tcd ter[minal] tlm tlunmenu tno[remap] ts[elect] undoj[oin] uns[ilent] vert[ical] viu[sage] w[rite] windo wqa[ll] xmapc[lear] xprop y[ank] syn keyword vimCommand contained al[l] argg[lobal] b[uffer] bl[ast] brea[k] buffers ca caf[ter] cbo[ttom] cex[pr] cgete[xpr] cl[ist] cnew[er] com cope[n] cscope debug deletep delp diffp[atch] dj[ump] dp earlier elsei[f] endw[hile] f[ile] fina[lly] foldd[oopen] go[to] ha[rdcopy] hid[e] ij[ump] is[earch] kee[pmarks] lNf[ile] laddf[ile] lbe[fore] lcs lf[ile] lgr[ep] lli[st] lnf[ile] lol[der] lt[ag] lw[indow] menut[ranslate] mkv[imrc] nb[key] noautocmd o[pen] p[rint] perld[o] pro ptN[ext] ptp[revious] py3do pythonx quita[ll] redraws[tatus] rew[ind] rubyf[ile] sIg sa[rgument] sba[ll] sbr[ewind] scl scscope sfir[st] sgl sic sin sm[ap] snoreme spelld[ump] spellw[rong] syn match vimCommand contained "\" + syn keyword vimCommand contained def endd[ef] disa[ssemble] vim9[script] imp[ort] exp[ort] syn keyword vimStdPlugin contained Arguments Break Cfilter Clear Continue DiffOrig Evaluate Finish Gdb Lfilter Man N[ext] Over P[rint] Program Run S Source Step Stop Termdebug TermdebugCommand TOhtml Winbar XMLent XMLns " vimOptions are caught only when contained in a vimSet {{{2 *************** *** 237,252 **** " ========= syn cluster vimFuncList contains=vimCommand,vimFunctionError,vimFuncKey,Tag,vimFuncSID syn cluster vimFuncBodyList contains=vimAbb,vimAddress,vimAugroupKey,vimAutoCmd,vimCmplxRepeat,vimComment,vimContinue,vimCtrlChar,vimEcho,vimEchoHL,vimExecute,vimIsCommand,vimFBVar,vimFunc,vimFunction,vimFuncVar,vimGlobal,vimHighlight,vimIsCommand,vimLet,vimLetHereDoc,vimLineComment,vimMap,vimMark,vimNorm,vimNotation,vimNotFunc,vimNumber,vimOper,vimOperParen,vimRegion,vimRegister,vimSearch,vimSet,vimSpecFile,vimString,vimSubst,vimSynLine,vimUnmap,vimUserCommand ! syn match vimFunction "\\|[sSgGbBwWtTlL]:\)\=\%(\i\|[#.]\|{.\{-1,}}\)*\ze\s*(" contains=@vimFuncList nextgroup=vimFuncBody if exists("g:vimsyn_folding") && g:vimsyn_folding =~# 'f' ! syn region vimFuncBody contained fold start="\ze\s*(" matchgroup=vimCommand end="\<\(endf\>\|endfu\%[nction]\>\)" contains=@vimFuncBodyList else ! syn region vimFuncBody contained start="\ze\s*(" matchgroup=vimCommand end="\<\(endf\>\|endfu\%[nction]\>\)" contains=@vimFuncBodyList endif syn match vimFuncVar contained "a:\(\K\k*\|\d\+\)" syn match vimFuncSID contained "\c\|\\|[sSgGbBwWtTlL]:\)\=\%(\i\|[#.]\|{.\{-1,}}\)*\ze\s*(" contains=@vimFuncList nextgroup=vimFuncBody if exists("g:vimsyn_folding") && g:vimsyn_folding =~# 'f' ! syn region vimFuncBody contained fold start="\ze\s*(" matchgroup=vimCommand end="\<\(endf\>\|endfu\%[nction]\>\|enddef\>\)" contains=@vimFuncBodyList else ! syn region vimFuncBody contained start="\ze\s*(" matchgroup=vimCommand end="\<\(endf\>\|endfu\%[nction]\>\|enddef\>\)" contains=@vimFuncBodyList endif syn match vimFuncVar contained "a:\(\K\k*\|\d\+\)" syn match vimFuncSID contained "\c\|\v_type = VAR_BLOB; to->v_lock = 0; ! if (from->vval.v_blob == NULL) to->vval.v_blob = NULL; else if (rettv_blob_alloc(to) == FAIL) ret = FAIL; else { ! int len = from->vval.v_blob->bv_ga.ga_len; if (len > 0) { to->vval.v_blob->bv_ga.ga_data = ! vim_memsave(from->vval.v_blob->bv_ga.ga_data, len); if (to->vval.v_blob->bv_ga.ga_data == NULL) len = 0; } --- 58,81 ---- } int ! blob_copy(blob_T *from, typval_T *to) { int ret = OK; to->v_type = VAR_BLOB; to->v_lock = 0; ! if (from == NULL) to->vval.v_blob = NULL; else if (rettv_blob_alloc(to) == FAIL) ret = FAIL; else { ! int len = from->bv_ga.ga_len; if (len > 0) { to->vval.v_blob->bv_ga.ga_data = ! vim_memsave(from->bv_ga.ga_data, len); if (to->vval.v_blob->bv_ga.ga_data == NULL) len = 0; } *** ../vim-8.2.0148/src/channel.c 2020-01-23 15:48:38.481551363 +0100 --- src/channel.c 2020-01-26 15:30:08.613766630 +0100 *************** *** 2263,2269 **** while (item != NULL) { list_T *l = item->jq_value->vval.v_list; ! typval_T *tv = &l->lv_first->li_tv; if ((without_callback || !item->jq_no_callback) && ((id > 0 && tv->v_type == VAR_NUMBER && tv->vval.v_number == id) --- 2263,2272 ---- while (item != NULL) { list_T *l = item->jq_value->vval.v_list; ! typval_T *tv; ! ! range_list_materialize(l); ! tv = &l->lv_first->li_tv; if ((without_callback || !item->jq_no_callback) && ((id > 0 && tv->v_type == VAR_NUMBER && tv->vval.v_number == id) *** ../vim-8.2.0148/src/dict.c 2020-01-11 16:05:19.586287644 +0100 --- src/dict.c 2020-01-26 15:30:08.613766630 +0100 *************** *** 826,832 **** if (**arg != ':') { ! semsg(_("E720: Missing colon in Dictionary: %s"), *arg); clear_tv(&tvkey); goto failret; } --- 826,832 ---- if (**arg != ':') { ! semsg(_(e_missing_dict_colon), *arg); clear_tv(&tvkey); goto failret; } *************** *** 853,859 **** item = dict_find(d, key, -1); if (item != NULL) { ! semsg(_("E721: Duplicate key in Dictionary: \"%s\""), key); clear_tv(&tvkey); clear_tv(&tv); goto failret; --- 853,859 ---- item = dict_find(d, key, -1); if (item != NULL) { ! semsg(_(e_duplicate_key), key); clear_tv(&tvkey); clear_tv(&tv); goto failret; *************** *** 873,879 **** break; if (**arg != ',') { ! semsg(_("E722: Missing comma in Dictionary: %s"), *arg); goto failret; } *arg = skipwhite(*arg + 1); --- 873,879 ---- break; if (**arg != ',') { ! semsg(_(e_missing_dict_comma), *arg); goto failret; } *arg = skipwhite(*arg + 1); *************** *** 881,887 **** if (**arg != '}') { ! semsg(_("E723: Missing end of Dictionary '}': %s"), *arg); failret: if (d != NULL) dict_free(d); --- 881,887 ---- if (**arg != '}') { ! semsg(_(e_missing_dict_end), *arg); failret: if (d != NULL) dict_free(d); *** ../vim-8.2.0148/src/eval.c 2020-01-12 17:42:52.410413873 +0100 --- src/eval.c 2020-01-26 15:30:08.613766630 +0100 *************** *** 20,31 **** # include #endif - static char *e_missbrac = N_("E111: Missing ']'"); static char *e_dictrange = N_("E719: Cannot use [:] with a Dictionary"); #ifdef FEAT_FLOAT static char *e_float_as_string = N_("E806: using Float as a String"); #endif - static char *e_nowhitespace = N_("E274: No white space allowed before parenthesis"); #define NAMESPACE_CHAR (char_u *)"abglstvw" --- 20,29 ---- *************** *** 60,69 **** static int eval7(char_u **arg, typval_T *rettv, int evaluate, int want_string); static int eval7_leader(typval_T *rettv, char_u *start_leader, char_u **end_leaderp); - static int get_string_tv(char_u **arg, typval_T *rettv, int evaluate); - static int get_lit_string_tv(char_u **arg, typval_T *rettv, int evaluate); static int free_unref_items(int copyID); - static int get_env_tv(char_u **arg, typval_T *rettv, int evaluate); static char_u *make_expanded_name(char_u *in_start, char_u *expr_start, char_u *expr_end, char_u *in_end); static int tv_check_lock(typval_T *tv, char_u *name, int use_gettext); --- 58,64 ---- *************** *** 222,227 **** --- 217,227 ---- return ret; } + /* + * Evaluate an expression, which can be a function, partial or string. + * Pass arguments "argv[argc]". + * Return the result in "rettv" and OK or FAIL. + */ int eval_expr_typval(typval_T *expr, typval_T *argv, int argc, typval_T *rettv) { *************** *** 243,256 **** { partial_T *partial = expr->vval.v_partial; ! s = partial_name(partial); ! if (s == NULL || *s == NUL) ! return FAIL; ! vim_memset(&funcexe, 0, sizeof(funcexe)); ! funcexe.evaluate = TRUE; ! funcexe.partial = partial; ! if (call_func(s, -1, rettv, argc, argv, &funcexe) == FAIL) ! return FAIL; } else { --- 243,264 ---- { partial_T *partial = expr->vval.v_partial; ! if (partial->pt_func != NULL && partial->pt_func->uf_dfunc_idx >= 0) ! { ! if (call_def_function(partial->pt_func, argc, argv, rettv) == FAIL) ! return FAIL; ! } ! else ! { ! s = partial_name(partial); ! if (s == NULL || *s == NUL) ! return FAIL; ! vim_memset(&funcexe, 0, sizeof(funcexe)); ! funcexe.evaluate = TRUE; ! funcexe.partial = partial; ! if (call_func(s, -1, rettv, argc, argv, &funcexe) == FAIL) ! return FAIL; ! } } else { *************** *** 652,657 **** --- 660,666 ---- // Find the end of the name. p = find_name_end(name, &expr_start, &expr_end, fne_flags); + lp->ll_name_end = p; if (expr_start != NULL) { // Don't expand the name when we already know there is an error. *************** *** 678,685 **** --- 687,706 ---- lp->ll_name = lp->ll_exp_name; } else + { lp->ll_name = name; + if (current_sctx.sc_version == SCRIPT_VERSION_VIM9 && *p == ':') + { + scriptitem_T *si = &SCRIPT_ITEM(current_sctx.sc_sid); + char_u *tp = skipwhite(p + 1); + + // parse the type after the name + lp->ll_type = parse_type(&tp, &si->sn_type_list); + lp->ll_name_end = tp; + } + } + // Without [idx] or .key we are done. if ((*p != '[' && *p != '.') || lp->ll_name == NULL) return p; *************** *** 1002,1007 **** --- 1023,1029 ---- } clear_tv(&var1); + lp->ll_name_end = p; return p; } *************** *** 1027,1033 **** char_u *endp, typval_T *rettv, int copy, ! int is_const, // Disallow to modify existing variable for :const char_u *op) { int cc; --- 1049,1055 ---- char_u *endp, typval_T *rettv, int copy, ! int flags, // LET_IS_CONST and/or LET_NO_COMMAND char_u *op) { int cc; *************** *** 1093,1099 **** { typval_T tv; ! if (is_const) { emsg(_(e_cannot_mod)); *endp = cc; --- 1115,1121 ---- { typval_T tv; ! if (flags & LET_IS_CONST) { emsg(_(e_cannot_mod)); *endp = cc; *************** *** 1114,1120 **** } } else ! set_var_const(lp->ll_name, rettv, copy, is_const); *endp = cc; } else if (var_check_lock(lp->ll_newkey == NULL --- 1136,1142 ---- } } else ! set_var_const(lp->ll_name, lp->ll_type, rettv, copy, flags); *endp = cc; } else if (var_check_lock(lp->ll_newkey == NULL *************** *** 1126,1132 **** listitem_T *ll_li = lp->ll_li; int ll_n1 = lp->ll_n1; ! if (is_const) { emsg(_("E996: Cannot lock a range")); return; --- 1148,1154 ---- listitem_T *ll_li = lp->ll_li; int ll_n1 = lp->ll_n1; ! if (flags & LET_IS_CONST) { emsg(_("E996: Cannot lock a range")); return; *************** *** 1185,1191 **** /* * Assign to a List or Dictionary item. */ ! if (is_const) { emsg(_("E996: Cannot lock a list or dict")); return; --- 1207,1213 ---- /* * Assign to a List or Dictionary item. */ ! if (flags & LET_IS_CONST) { emsg(_("E996: Cannot lock a list or dict")); return; *************** *** 1250,1255 **** --- 1272,1278 ---- switch (tv1->v_type) { case VAR_UNKNOWN: + case VAR_VOID: case VAR_DICT: case VAR_FUNC: case VAR_PARTIAL: *************** *** 1392,1405 **** 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(_("E690: Missing \"in\" after :for")); return fi; } --- 1415,1428 ---- if (fi == NULL) return NULL; ! expr = skip_var_list(arg, TRUE, &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(_(e_missing_in)); return fi; } *************** *** 1420,1425 **** --- 1443,1451 ---- } else { + // Need a real list here. + range_list_materialize(l); + // No need to increment the refcount, it's already set for // the list being used in "tv". fi->fi_list = l; *************** *** 1436,1442 **** // Make a copy, so that the iteration still works when the // blob is changed. ! blob_copy(&tv, &btv); fi->fi_blob = btv.vval.v_blob; } clear_tv(&tv); --- 1462,1468 ---- // Make a copy, so that the iteration still works when the // blob is changed. ! blob_copy(tv.vval.v_blob, &btv); fi->fi_blob = btv.vval.v_blob; } clear_tv(&tv); *************** *** 1478,1484 **** tv.vval.v_number = blob_get(fi->fi_blob, fi->fi_bi); ++fi->fi_bi; return ex_let_vars(arg, &tv, TRUE, fi->fi_semicolon, ! fi->fi_varcount, FALSE, NULL) == OK; } item = fi->fi_lw.lw_item; --- 1504,1510 ---- tv.vval.v_number = blob_get(fi->fi_blob, fi->fi_bi); ++fi->fi_bi; return ex_let_vars(arg, &tv, TRUE, fi->fi_semicolon, ! fi->fi_varcount, 0, NULL) == OK; } item = fi->fi_lw.lw_item; *************** *** 1488,1494 **** { fi->fi_lw.lw_item = item->li_next; result = (ex_let_vars(arg, &item->li_tv, TRUE, fi->fi_semicolon, ! fi->fi_varcount, FALSE, NULL) == OK); } return result; } --- 1514,1520 ---- { fi->fi_lw.lw_item = item->li_next; result = (ex_let_vars(arg, &item->li_tv, TRUE, fi->fi_semicolon, ! fi->fi_varcount, 0, NULL) == OK); } return result; } *************** *** 1814,1820 **** */ if ((*arg)[0] != ':') { ! emsg(_("E109: Missing ':' after '?'")); if (evaluate && result) clear_tv(rettv); return FAIL; --- 1840,1846 ---- */ if ((*arg)[0] != ':') { ! emsg(_(e_missing_colon)); if (evaluate && result) clear_tv(rettv); return FAIL; *************** *** 2089,2094 **** --- 2115,2157 ---- return OK; } + void + eval_addblob(typval_T *tv1, typval_T *tv2) + { + blob_T *b1 = tv1->vval.v_blob; + blob_T *b2 = tv2->vval.v_blob; + blob_T *b = blob_alloc(); + int i; + + if (b != NULL) + { + for (i = 0; i < blob_len(b1); i++) + ga_append(&b->bv_ga, blob_get(b1, i)); + for (i = 0; i < blob_len(b2); i++) + ga_append(&b->bv_ga, blob_get(b2, i)); + + clear_tv(tv1); + rettv_blob_set(tv1, b); + } + } + + int + eval_addlist(typval_T *tv1, typval_T *tv2) + { + typval_T var3; + + // concatenate Lists + if (list_concat(tv1->vval.v_list, tv2->vval.v_list, &var3) == FAIL) + { + clear_tv(tv1); + clear_tv(tv2); + return FAIL; + } + clear_tv(tv1); + *tv1 = var3; + return OK; + } + /* * Handle fourth level expression: * + number addition *************** *** 2105,2111 **** eval5(char_u **arg, typval_T *rettv, int evaluate) { typval_T var2; - typval_T var3; int op; varnumber_T n1, n2; #ifdef FEAT_FLOAT --- 2168,2173 ---- *************** *** 2189,2224 **** } else if (op == '+' && rettv->v_type == VAR_BLOB && var2.v_type == VAR_BLOB) ! { ! blob_T *b1 = rettv->vval.v_blob; ! blob_T *b2 = var2.vval.v_blob; ! blob_T *b = blob_alloc(); ! int i; ! ! if (b != NULL) ! { ! for (i = 0; i < blob_len(b1); i++) ! ga_append(&b->bv_ga, blob_get(b1, i)); ! for (i = 0; i < blob_len(b2); i++) ! ga_append(&b->bv_ga, blob_get(b2, i)); ! ! clear_tv(rettv); ! rettv_blob_set(rettv, b); ! } ! } else if (op == '+' && rettv->v_type == VAR_LIST && var2.v_type == VAR_LIST) { ! // concatenate Lists ! if (list_concat(rettv->vval.v_list, var2.vval.v_list, ! &var3) == FAIL) ! { ! clear_tv(rettv); ! clear_tv(&var2); return FAIL; - } - clear_tv(rettv); - *rettv = var3; } else { --- 2251,2262 ---- } else if (op == '+' && rettv->v_type == VAR_BLOB && var2.v_type == VAR_BLOB) ! eval_addblob(rettv, &var2); else if (op == '+' && rettv->v_type == VAR_LIST && var2.v_type == VAR_LIST) { ! if (eval_addlist(rettv, &var2) == FAIL) return FAIL; } else { *************** *** 2424,2430 **** } else { ! emsg(_("E804: Cannot use '%' with Float")); return FAIL; } rettv->v_type = VAR_FLOAT; --- 2462,2468 ---- } else { ! emsg(_(e_modulus)); return FAIL; } rettv->v_type = VAR_FLOAT; *************** *** 2462,2467 **** --- 2500,2506 ---- * $VAR environment variable * (expression) nested expression * [expr, expr] List + * {arg, arg -> expr} Lambda * {key: val, key: val} Dictionary * #{key: val, key: val} Dictionary with literal keys * *************** *** 2483,2491 **** char_u **arg, typval_T *rettv, int evaluate, ! int want_string UNUSED) // after "." operator { - varnumber_T n; int len; char_u *s; char_u *start_leader, *end_leader; --- 2522,2529 ---- char_u **arg, typval_T *rettv, int evaluate, ! int want_string) // after "." operator { int len; char_u *s; char_u *start_leader, *end_leader; *************** *** 2532,2636 **** case '7': case '8': case '9': ! case '.': ! { ! #ifdef FEAT_FLOAT ! char_u *p; ! int get_float = FALSE; ! ! // We accept a float when the format matches ! // "[0-9]\+\.[0-9]\+\([eE][+-]\?[0-9]\+\)\?". This is very ! // strict to avoid backwards compatibility problems. ! // With script version 2 and later the leading digit can be ! // omitted. ! // Don't look for a float after the "." operator, so that ! // ":let vers = 1.2.3" doesn't fail. ! if (**arg == '.') ! p = *arg; ! else ! p = skipdigits(*arg + 1); ! if (!want_string && p[0] == '.' && vim_isdigit(p[1])) ! { ! get_float = TRUE; ! p = skipdigits(p + 2); ! if (*p == 'e' || *p == 'E') ! { ! ++p; ! if (*p == '-' || *p == '+') ! ++p; ! if (!vim_isdigit(*p)) ! get_float = FALSE; ! else ! p = skipdigits(p + 1); ! } ! if (ASCII_ISALPHA(*p) || *p == '.') ! get_float = FALSE; ! } ! if (get_float) ! { ! float_T f; ! ! *arg += string2float(*arg, &f); ! if (evaluate) ! { ! rettv->v_type = VAR_FLOAT; ! rettv->vval.v_float = f; ! } ! } ! else ! #endif ! if (**arg == '0' && ((*arg)[1] == 'z' || (*arg)[1] == 'Z')) ! { ! char_u *bp; ! blob_T *blob = NULL; // init for gcc ! ! // Blob constant: 0z0123456789abcdef ! if (evaluate) ! blob = blob_alloc(); ! for (bp = *arg + 2; vim_isxdigit(bp[0]); bp += 2) ! { ! if (!vim_isxdigit(bp[1])) ! { ! if (blob != NULL) ! { ! emsg(_("E973: Blob literal should have an even number of hex characters")); ! ga_clear(&blob->bv_ga); ! VIM_CLEAR(blob); ! } ! ret = FAIL; ! break; ! } ! if (blob != NULL) ! ga_append(&blob->bv_ga, ! (hex2nr(*bp) << 4) + hex2nr(*(bp+1))); ! if (bp[2] == '.' && vim_isxdigit(bp[3])) ! ++bp; ! } ! if (blob != NULL) ! rettv_blob_set(rettv, blob); ! *arg = bp; ! } ! else ! { ! // decimal, hex or octal number ! vim_str2nr(*arg, NULL, &len, current_sctx.sc_version >= 4 ! ? STR2NR_NO_OCT + STR2NR_QUOTE ! : STR2NR_ALL, &n, NULL, 0, TRUE); ! if (len == 0) ! { ! semsg(_(e_invexpr2), *arg); ! ret = FAIL; ! break; ! } ! *arg += len; ! if (evaluate) ! { ! rettv->v_type = VAR_NUMBER; ! rettv->vval.v_number = n; ! } ! } break; - } /* * String constant: "string". --- 2570,2577 ---- case '7': case '8': case '9': ! case '.': ret = get_number_tv(arg, rettv, evaluate, want_string); break; /* * String constant: "string". *************** *** 2647,2653 **** /* * List: [expr, expr] */ ! case '[': ret = get_list_tv(arg, rettv, evaluate); break; /* --- 2588,2594 ---- /* * List: [expr, expr] */ ! case '[': ret = get_list_tv(arg, rettv, evaluate, TRUE); break; /* *************** *** 2706,2712 **** ++*arg; else if (ret == OK) { ! emsg(_("E110: Missing ')'")); clear_tv(rettv); ret = FAIL; } --- 2647,2653 ---- ++*arg; else if (ret == OK) { ! emsg(_(e_missing_close)); clear_tv(rettv); ret = FAIL; } *************** *** 2907,2913 **** if (*skipwhite(*arg) == '(') semsg(_(e_nowhitespace)); else ! semsg(_(e_missingparen), "lambda"); } clear_tv(rettv); ret = FAIL; --- 2848,2854 ---- if (*skipwhite(*arg) == '(') semsg(_(e_nowhitespace)); else ! semsg(_(e_missing_paren), "lambda"); } clear_tv(rettv); ret = FAIL; *************** *** 2961,2967 **** if (**arg != '(') { if (verbose) ! semsg(_(e_missingparen), name); ret = FAIL; } else if (VIM_ISWHITE((*arg)[-1])) --- 2902,2908 ---- if (**arg != '(') { if (verbose) ! semsg(_(e_missing_paren), name); ret = FAIL; } else if (VIM_ISWHITE((*arg)[-1])) *************** *** 3024,3029 **** --- 2965,2971 ---- emsg(_("E909: Cannot index a special variable")); return FAIL; case VAR_UNKNOWN: + case VAR_VOID: if (evaluate) return FAIL; // FALLTHROUGH *************** *** 3129,3134 **** --- 3071,3077 ---- switch (rettv->v_type) { case VAR_UNKNOWN: + case VAR_VOID: case VAR_FUNC: case VAR_PARTIAL: case VAR_FLOAT: *************** *** 3377,3383 **** if (opt_type == -3) // invalid name { if (rettv != NULL) ! semsg(_("E113: Unknown option: %s"), *arg); ret = FAIL; } else if (rettv != NULL) --- 3320,3326 ---- if (opt_type == -3) // invalid name { if (rettv != NULL) ! semsg(_(e_unknown_option), *arg); ret = FAIL; } else if (rettv != NULL) *************** *** 3413,3422 **** } /* * Allocate a variable for a string constant. * Return OK or FAIL. */ ! static int get_string_tv(char_u **arg, typval_T *rettv, int evaluate) { char_u *p; --- 3356,3475 ---- } /* + * Allocate a variable for a number constant. Also deals with "0z" for blob. + * Return OK or FAIL. + */ + int + get_number_tv( + char_u **arg, + typval_T *rettv, + int evaluate, + int want_string UNUSED) + { + int len; + #ifdef FEAT_FLOAT + char_u *p; + int get_float = FALSE; + + // We accept a float when the format matches + // "[0-9]\+\.[0-9]\+\([eE][+-]\?[0-9]\+\)\?". This is very + // strict to avoid backwards compatibility problems. + // With script version 2 and later the leading digit can be + // omitted. + // Don't look for a float after the "." operator, so that + // ":let vers = 1.2.3" doesn't fail. + if (**arg == '.') + p = *arg; + else + p = skipdigits(*arg + 1); + if (!want_string && p[0] == '.' && vim_isdigit(p[1])) + { + get_float = TRUE; + p = skipdigits(p + 2); + if (*p == 'e' || *p == 'E') + { + ++p; + if (*p == '-' || *p == '+') + ++p; + if (!vim_isdigit(*p)) + get_float = FALSE; + else + p = skipdigits(p + 1); + } + if (ASCII_ISALPHA(*p) || *p == '.') + get_float = FALSE; + } + if (get_float) + { + float_T f; + + *arg += string2float(*arg, &f); + if (evaluate) + { + rettv->v_type = VAR_FLOAT; + rettv->vval.v_float = f; + } + } + else + #endif + if (**arg == '0' && ((*arg)[1] == 'z' || (*arg)[1] == 'Z')) + { + char_u *bp; + blob_T *blob = NULL; // init for gcc + + // Blob constant: 0z0123456789abcdef + if (evaluate) + blob = blob_alloc(); + for (bp = *arg + 2; vim_isxdigit(bp[0]); bp += 2) + { + if (!vim_isxdigit(bp[1])) + { + if (blob != NULL) + { + emsg(_("E973: Blob literal should have an even number of hex characters")); + ga_clear(&blob->bv_ga); + VIM_CLEAR(blob); + } + return FAIL; + } + if (blob != NULL) + ga_append(&blob->bv_ga, + (hex2nr(*bp) << 4) + hex2nr(*(bp+1))); + if (bp[2] == '.' && vim_isxdigit(bp[3])) + ++bp; + } + if (blob != NULL) + rettv_blob_set(rettv, blob); + *arg = bp; + } + else + { + varnumber_T n; + + // decimal, hex or octal number + vim_str2nr(*arg, NULL, &len, current_sctx.sc_version >= 4 + ? STR2NR_NO_OCT + STR2NR_QUOTE + : STR2NR_ALL, &n, NULL, 0, TRUE); + if (len == 0) + { + semsg(_(e_invexpr2), *arg); + return FAIL; + } + *arg += len; + if (evaluate) + { + rettv->v_type = VAR_NUMBER; + rettv->vval.v_number = n; + } + } + return OK; + } + + /* * Allocate a variable for a string constant. * Return OK or FAIL. */ ! int get_string_tv(char_u **arg, typval_T *rettv, int evaluate) { char_u *p; *************** *** 3553,3559 **** * Allocate a variable for a 'str''ing' constant. * Return OK or FAIL. */ ! static int get_lit_string_tv(char_u **arg, typval_T *rettv, int evaluate) { char_u *p; --- 3606,3612 ---- * Allocate a variable for a 'str''ing' constant. * Return OK or FAIL. */ ! int get_lit_string_tv(char_u **arg, typval_T *rettv, int evaluate) { char_u *p; *************** *** 3772,3777 **** --- 3825,3832 ---- return blob_equal(tv1->vval.v_blob, tv2->vval.v_blob); case VAR_NUMBER: + case VAR_BOOL: + case VAR_SPECIAL: return tv1->vval.v_number == tv2->vval.v_number; case VAR_STRING: *************** *** 3779,3788 **** s2 = tv_get_string_buf(tv2, buf2); return ((ic ? MB_STRICMP(s1, s2) : STRCMP(s1, s2)) == 0); - case VAR_BOOL: - case VAR_SPECIAL: - return tv1->vval.v_number == tv2->vval.v_number; - case VAR_FLOAT: #ifdef FEAT_FLOAT return tv1->vval.v_float == tv2->vval.v_float; --- 3834,3839 ---- *************** *** 3795,3803 **** --- 3846,3856 ---- #ifdef FEAT_JOB_CHANNEL return tv1->vval.v_channel == tv2->vval.v_channel; #endif + case VAR_FUNC: case VAR_PARTIAL: case VAR_UNKNOWN: + case VAR_VOID: break; } *************** *** 4511,4516 **** --- 4564,4570 ---- case VAR_NUMBER: case VAR_UNKNOWN: + case VAR_VOID: *tofree = NULL; r = tv_get_string_buf(tv, numbuf); break; *************** *** 4668,4674 **** * If the environment variable was not set, silently assume it is empty. * Return FAIL if the name is invalid. */ ! static int get_env_tv(char_u **arg, typval_T *rettv, int evaluate) { char_u *string = NULL; --- 4722,4728 ---- * If the environment variable was not set, silently assume it is empty. * Return FAIL if the name is invalid. */ ! int get_env_tv(char_u **arg, typval_T *rettv, int evaluate) { char_u *string = NULL; *************** *** 5363,5368 **** --- 5417,5423 ---- case VAR_NUMBER: case VAR_FLOAT: case VAR_UNKNOWN: + case VAR_VOID: case VAR_BOOL: case VAR_SPECIAL: break; *************** *** 5425,5430 **** --- 5480,5486 ---- varp->vval.v_channel = NULL; #endif case VAR_UNKNOWN: + case VAR_VOID: break; } varp->v_lock = 0; *************** *** 5503,5508 **** --- 5559,5565 ---- emsg(_("E974: Using a Blob as a Number")); break; case VAR_UNKNOWN: + case VAR_VOID: internal_error("tv_get_number(UNKNOWN)"); break; } *************** *** 5556,5561 **** --- 5613,5619 ---- emsg(_("E975: Using a Blob as a Float")); break; case VAR_UNKNOWN: + case VAR_VOID: internal_error("tv_get_float(UNKNOWN)"); break; } *************** *** 5678,5683 **** --- 5736,5742 ---- #endif break; case VAR_UNKNOWN: + case VAR_VOID: emsg(_(e_inval_string)); break; } *************** *** 5826,5831 **** --- 5885,5891 ---- } break; case VAR_UNKNOWN: + case VAR_VOID: internal_error("copy_tv(UNKNOWN)"); break; } *************** *** 5885,5891 **** ret = FAIL; break; case VAR_BLOB: ! ret = blob_copy(from, to); break; case VAR_DICT: to->v_type = VAR_DICT; --- 5945,5951 ---- ret = FAIL; break; case VAR_BLOB: ! ret = blob_copy(from->vval.v_blob, to); break; case VAR_DICT: to->v_type = VAR_DICT; *************** *** 5904,5909 **** --- 5964,5970 ---- ret = FAIL; break; case VAR_UNKNOWN: + case VAR_VOID: internal_error("item_copy(UNKNOWN)"); ret = FAIL; } *************** *** 5911,5916 **** --- 5972,6030 ---- return ret; } + void + echo_one(typval_T *rettv, int with_space, int *atstart, int *needclr) + { + char_u *tofree; + char_u numbuf[NUMBUFLEN]; + char_u *p = echo_string(rettv, &tofree, numbuf, get_copyID()); + + if (*atstart) + { + *atstart = FALSE; + // Call msg_start() after eval1(), evaluating the expression + // may cause a message to appear. + if (with_space) + { + // Mark the saved text as finishing the line, so that what + // follows is displayed on a new line when scrolling back + // at the more prompt. + msg_sb_eol(); + msg_start(); + } + } + else if (with_space) + msg_puts_attr(" ", echo_attr); + + if (p != NULL) + for ( ; *p != NUL && !got_int; ++p) + { + if (*p == '\n' || *p == '\r' || *p == TAB) + { + if (*p != TAB && *needclr) + { + // remove any text still there from the command + msg_clr_eos(); + *needclr = FALSE; + } + msg_putchar_attr(*p, echo_attr); + } + else + { + if (has_mbyte) + { + int i = (*mb_ptr2len)(p); + + (void)msg_outtrans_len_attr(p, i, echo_attr); + p += i - 1; + } + else + (void)msg_outtrans_len_attr(p, 1, echo_attr); + } + } + vim_free(tofree); + } + /* * ":echo expr1 ..." print each argument separated with a space, add a * newline at the end. *************** *** 5921,5931 **** { char_u *arg = eap->arg; typval_T rettv; - char_u *tofree; char_u *p; int needclr = TRUE; int atstart = TRUE; - char_u numbuf[NUMBUFLEN]; int did_emsg_before = did_emsg; int called_emsg_before = called_emsg; --- 6035,6043 ---- *************** *** 5954,6005 **** need_clr_eos = FALSE; if (!eap->skip) ! { ! if (atstart) ! { ! atstart = FALSE; ! // Call msg_start() after eval1(), evaluating the expression ! // may cause a message to appear. ! if (eap->cmdidx == CMD_echo) ! { ! // Mark the saved text as finishing the line, so that what ! // follows is displayed on a new line when scrolling back ! // at the more prompt. ! msg_sb_eol(); ! msg_start(); ! } ! } ! else if (eap->cmdidx == CMD_echo) ! msg_puts_attr(" ", echo_attr); ! p = echo_string(&rettv, &tofree, numbuf, get_copyID()); ! if (p != NULL) ! for ( ; *p != NUL && !got_int; ++p) ! { ! if (*p == '\n' || *p == '\r' || *p == TAB) ! { ! if (*p != TAB && needclr) ! { ! // remove any text still there from the command ! msg_clr_eos(); ! needclr = FALSE; ! } ! msg_putchar_attr(*p, echo_attr); ! } ! else ! { ! if (has_mbyte) ! { ! int i = (*mb_ptr2len)(p); - (void)msg_outtrans_len_attr(p, i, echo_attr); - p += i - 1; - } - else - (void)msg_outtrans_len_attr(p, 1, echo_attr); - } - } - vim_free(tofree); - } clear_tv(&rettv); arg = skipwhite(arg); } --- 6066,6073 ---- need_clr_eos = FALSE; if (!eap->skip) ! echo_one(&rettv, eap->cmdidx == CMD_echo, &atstart, &needclr); clear_tv(&rettv); arg = skipwhite(arg); } *************** *** 6369,6375 **** case EXPR_SEQUAL: n1 = (f1 <= f2); break; case EXPR_UNKNOWN: case EXPR_MATCH: ! case EXPR_NOMATCH: break; // avoid gcc warning } } #endif --- 6437,6443 ---- case EXPR_SEQUAL: n1 = (f1 <= f2); break; case EXPR_UNKNOWN: case EXPR_MATCH: ! default: break; // avoid gcc warning } } #endif *************** *** 6395,6401 **** case EXPR_SEQUAL: n1 = (n1 <= n2); break; case EXPR_UNKNOWN: case EXPR_MATCH: ! case EXPR_NOMATCH: break; // avoid gcc warning } } else --- 6463,6469 ---- case EXPR_SEQUAL: n1 = (n1 <= n2); break; case EXPR_UNKNOWN: case EXPR_MATCH: ! default: break; // avoid gcc warning } } else *************** *** 6425,6431 **** n1 = !n1; break; ! case EXPR_UNKNOWN: break; // avoid gcc warning } } clear_tv(typ1); --- 6493,6499 ---- n1 = !n1; break; ! default: break; // avoid gcc warning } } clear_tv(typ1); *** ../vim-8.2.0148/src/evalbuffer.c 2019-12-17 22:40:11.934933029 +0100 --- src/evalbuffer.c 2020-01-26 15:30:08.613766630 +0100 *************** *** 176,181 **** --- 176,182 ---- if (lines->v_type == VAR_LIST) { l = lines->vval.v_list; + range_list_materialize(l); li = l->lv_first; } else *************** *** 689,698 **** { char_u *p; ! rettv->v_type = VAR_STRING; ! rettv->vval.v_string = NULL; ! if (retlist && rettv_list_alloc(rettv) == FAIL) ! return; if (buf == NULL || buf->b_ml.ml_mfp == NULL || start < 0) return; --- 690,705 ---- { char_u *p; ! if (retlist) ! { ! if (rettv_list_alloc(rettv) == FAIL) ! return; ! } ! else ! { ! rettv->v_type = VAR_STRING; ! rettv->vval.v_string = NULL; ! } if (buf == NULL || buf->b_ml.ml_mfp == NULL || start < 0) return; *** ../vim-8.2.0148/src/evalfunc.c 2020-01-15 20:36:52.140437085 +0100 --- src/evalfunc.c 2020-01-26 15:30:08.613766630 +0100 *************** *** 290,295 **** --- 290,296 ---- char f_min_argc; // minimal number of arguments char f_max_argc; // maximal number of arguments char f_argtype; // for method: FEARG_ values + type_T *f_rettype; // return type void (*f_func)(typval_T *args, typval_T *rvar); // implementation of function } funcentry_T; *************** *** 304,883 **** static funcentry_T global_functions[] = { #ifdef FEAT_FLOAT ! {"abs", 1, 1, FEARG_1, f_abs}, ! {"acos", 1, 1, FEARG_1, f_acos}, // WJMc #endif ! {"add", 2, 2, FEARG_1, f_add}, ! {"and", 2, 2, FEARG_1, f_and}, ! {"append", 2, 2, FEARG_LAST, f_append}, ! {"appendbufline", 3, 3, FEARG_LAST, f_appendbufline}, ! {"argc", 0, 1, 0, f_argc}, ! {"argidx", 0, 0, 0, f_argidx}, ! {"arglistid", 0, 2, 0, f_arglistid}, ! {"argv", 0, 2, 0, f_argv}, #ifdef FEAT_FLOAT ! {"asin", 1, 1, FEARG_1, f_asin}, // WJMc #endif ! {"assert_beeps", 1, 2, FEARG_1, f_assert_beeps}, ! {"assert_equal", 2, 3, FEARG_2, f_assert_equal}, ! {"assert_equalfile", 2, 2, FEARG_1, f_assert_equalfile}, ! {"assert_exception", 1, 2, 0, f_assert_exception}, ! {"assert_fails", 1, 3, FEARG_1, f_assert_fails}, ! {"assert_false", 1, 2, FEARG_1, f_assert_false}, ! {"assert_inrange", 3, 4, FEARG_3, f_assert_inrange}, ! {"assert_match", 2, 3, FEARG_2, f_assert_match}, ! {"assert_notequal", 2, 3, FEARG_2, f_assert_notequal}, ! {"assert_notmatch", 2, 3, FEARG_2, f_assert_notmatch}, ! {"assert_report", 1, 1, FEARG_1, f_assert_report}, ! {"assert_true", 1, 2, FEARG_1, f_assert_true}, #ifdef FEAT_FLOAT ! {"atan", 1, 1, FEARG_1, f_atan}, ! {"atan2", 2, 2, FEARG_1, f_atan2}, #endif #ifdef FEAT_BEVAL ! {"balloon_gettext", 0, 0, 0, f_balloon_gettext}, ! {"balloon_show", 1, 1, FEARG_1, f_balloon_show}, # if defined(FEAT_BEVAL_TERM) ! {"balloon_split", 1, 1, FEARG_1, f_balloon_split}, # endif #endif ! {"browse", 4, 4, 0, f_browse}, ! {"browsedir", 2, 2, 0, f_browsedir}, ! {"bufadd", 1, 1, FEARG_1, f_bufadd}, ! {"bufexists", 1, 1, FEARG_1, f_bufexists}, ! {"buffer_exists", 1, 1, FEARG_1, f_bufexists}, // obsolete ! {"buffer_name", 0, 1, FEARG_1, f_bufname}, // obsolete ! {"buffer_number", 0, 1, FEARG_1, f_bufnr}, // obsolete ! {"buflisted", 1, 1, FEARG_1, f_buflisted}, ! {"bufload", 1, 1, FEARG_1, f_bufload}, ! {"bufloaded", 1, 1, FEARG_1, f_bufloaded}, ! {"bufname", 0, 1, FEARG_1, f_bufname}, ! {"bufnr", 0, 2, FEARG_1, f_bufnr}, ! {"bufwinid", 1, 1, FEARG_1, f_bufwinid}, ! {"bufwinnr", 1, 1, FEARG_1, f_bufwinnr}, ! {"byte2line", 1, 1, FEARG_1, f_byte2line}, ! {"byteidx", 2, 2, FEARG_1, f_byteidx}, ! {"byteidxcomp", 2, 2, FEARG_1, f_byteidxcomp}, ! {"call", 2, 3, FEARG_1, f_call}, #ifdef FEAT_FLOAT ! {"ceil", 1, 1, FEARG_1, f_ceil}, #endif #ifdef FEAT_JOB_CHANNEL ! {"ch_canread", 1, 1, FEARG_1, f_ch_canread}, ! {"ch_close", 1, 1, FEARG_1, f_ch_close}, ! {"ch_close_in", 1, 1, FEARG_1, f_ch_close_in}, ! {"ch_evalexpr", 2, 3, FEARG_1, f_ch_evalexpr}, ! {"ch_evalraw", 2, 3, FEARG_1, f_ch_evalraw}, ! {"ch_getbufnr", 2, 2, FEARG_1, f_ch_getbufnr}, ! {"ch_getjob", 1, 1, FEARG_1, f_ch_getjob}, ! {"ch_info", 1, 1, FEARG_1, f_ch_info}, ! {"ch_log", 1, 2, FEARG_1, f_ch_log}, ! {"ch_logfile", 1, 2, FEARG_1, f_ch_logfile}, ! {"ch_open", 1, 2, FEARG_1, f_ch_open}, ! {"ch_read", 1, 2, FEARG_1, f_ch_read}, ! {"ch_readblob", 1, 2, FEARG_1, f_ch_readblob}, ! {"ch_readraw", 1, 2, FEARG_1, f_ch_readraw}, ! {"ch_sendexpr", 2, 3, FEARG_1, f_ch_sendexpr}, ! {"ch_sendraw", 2, 3, FEARG_1, f_ch_sendraw}, ! {"ch_setoptions", 2, 2, FEARG_1, f_ch_setoptions}, ! {"ch_status", 1, 2, FEARG_1, f_ch_status}, ! #endif ! {"changenr", 0, 0, 0, f_changenr}, ! {"char2nr", 1, 2, FEARG_1, f_char2nr}, ! {"chdir", 1, 1, FEARG_1, f_chdir}, ! {"cindent", 1, 1, FEARG_1, f_cindent}, ! {"clearmatches", 0, 1, FEARG_1, f_clearmatches}, ! {"col", 1, 1, FEARG_1, f_col}, ! {"complete", 2, 2, FEARG_2, f_complete}, ! {"complete_add", 1, 1, FEARG_1, f_complete_add}, ! {"complete_check", 0, 0, 0, f_complete_check}, ! {"complete_info", 0, 1, FEARG_1, f_complete_info}, ! {"confirm", 1, 4, FEARG_1, f_confirm}, ! {"copy", 1, 1, FEARG_1, f_copy}, #ifdef FEAT_FLOAT ! {"cos", 1, 1, FEARG_1, f_cos}, ! {"cosh", 1, 1, FEARG_1, f_cosh}, #endif ! {"count", 2, 4, FEARG_1, f_count}, ! {"cscope_connection",0,3, 0, f_cscope_connection}, ! {"cursor", 1, 3, FEARG_1, f_cursor}, #ifdef MSWIN ! {"debugbreak", 1, 1, FEARG_1, f_debugbreak}, #endif ! {"deepcopy", 1, 2, FEARG_1, f_deepcopy}, ! {"delete", 1, 2, FEARG_1, f_delete}, ! {"deletebufline", 2, 3, FEARG_1, f_deletebufline}, ! {"did_filetype", 0, 0, 0, f_did_filetype}, ! {"diff_filler", 1, 1, FEARG_1, f_diff_filler}, ! {"diff_hlID", 2, 2, FEARG_1, f_diff_hlID}, ! {"empty", 1, 1, FEARG_1, f_empty}, ! {"environ", 0, 0, 0, f_environ}, ! {"escape", 2, 2, FEARG_1, f_escape}, ! {"eval", 1, 1, FEARG_1, f_eval}, ! {"eventhandler", 0, 0, 0, f_eventhandler}, ! {"executable", 1, 1, FEARG_1, f_executable}, ! {"execute", 1, 2, FEARG_1, f_execute}, ! {"exepath", 1, 1, FEARG_1, f_exepath}, ! {"exists", 1, 1, FEARG_1, f_exists}, #ifdef FEAT_FLOAT ! {"exp", 1, 1, FEARG_1, f_exp}, #endif ! {"expand", 1, 3, FEARG_1, f_expand}, ! {"expandcmd", 1, 1, FEARG_1, f_expandcmd}, ! {"extend", 2, 3, FEARG_1, f_extend}, ! {"feedkeys", 1, 2, FEARG_1, f_feedkeys}, ! {"file_readable", 1, 1, FEARG_1, f_filereadable}, // obsolete ! {"filereadable", 1, 1, FEARG_1, f_filereadable}, ! {"filewritable", 1, 1, FEARG_1, f_filewritable}, ! {"filter", 2, 2, FEARG_1, f_filter}, ! {"finddir", 1, 3, FEARG_1, f_finddir}, ! {"findfile", 1, 3, FEARG_1, f_findfile}, #ifdef FEAT_FLOAT ! {"float2nr", 1, 1, FEARG_1, f_float2nr}, ! {"floor", 1, 1, FEARG_1, f_floor}, ! {"fmod", 2, 2, FEARG_1, f_fmod}, ! #endif ! {"fnameescape", 1, 1, FEARG_1, f_fnameescape}, ! {"fnamemodify", 2, 2, FEARG_1, f_fnamemodify}, ! {"foldclosed", 1, 1, FEARG_1, f_foldclosed}, ! {"foldclosedend", 1, 1, FEARG_1, f_foldclosedend}, ! {"foldlevel", 1, 1, FEARG_1, f_foldlevel}, ! {"foldtext", 0, 0, 0, f_foldtext}, ! {"foldtextresult", 1, 1, FEARG_1, f_foldtextresult}, ! {"foreground", 0, 0, 0, f_foreground}, ! {"funcref", 1, 3, FEARG_1, f_funcref}, ! {"function", 1, 3, FEARG_1, f_function}, ! {"garbagecollect", 0, 1, 0, f_garbagecollect}, ! {"get", 2, 3, FEARG_1, f_get}, ! {"getbufinfo", 0, 1, 0, f_getbufinfo}, ! {"getbufline", 2, 3, FEARG_1, f_getbufline}, ! {"getbufvar", 2, 3, FEARG_1, f_getbufvar}, ! {"getchangelist", 0, 1, FEARG_1, f_getchangelist}, ! {"getchar", 0, 1, 0, f_getchar}, ! {"getcharmod", 0, 0, 0, f_getcharmod}, ! {"getcharsearch", 0, 0, 0, f_getcharsearch}, ! {"getcmdline", 0, 0, 0, f_getcmdline}, ! {"getcmdpos", 0, 0, 0, f_getcmdpos}, ! {"getcmdtype", 0, 0, 0, f_getcmdtype}, ! {"getcmdwintype", 0, 0, 0, f_getcmdwintype}, ! {"getcompletion", 2, 3, FEARG_1, f_getcompletion}, ! {"getcurpos", 0, 0, 0, f_getcurpos}, ! {"getcwd", 0, 2, FEARG_1, f_getcwd}, ! {"getenv", 1, 1, FEARG_1, f_getenv}, ! {"getfontname", 0, 1, 0, f_getfontname}, ! {"getfperm", 1, 1, FEARG_1, f_getfperm}, ! {"getfsize", 1, 1, FEARG_1, f_getfsize}, ! {"getftime", 1, 1, FEARG_1, f_getftime}, ! {"getftype", 1, 1, FEARG_1, f_getftype}, ! {"getimstatus", 0, 0, 0, f_getimstatus}, ! {"getjumplist", 0, 2, FEARG_1, f_getjumplist}, ! {"getline", 1, 2, FEARG_1, f_getline}, ! {"getloclist", 1, 2, 0, f_getloclist}, ! {"getmatches", 0, 1, 0, f_getmatches}, ! {"getmousepos", 0, 0, 0, f_getmousepos}, ! {"getpid", 0, 0, 0, f_getpid}, ! {"getpos", 1, 1, FEARG_1, f_getpos}, ! {"getqflist", 0, 1, 0, f_getqflist}, ! {"getreg", 0, 3, FEARG_1, f_getreg}, ! {"getregtype", 0, 1, FEARG_1, f_getregtype}, ! {"gettabinfo", 0, 1, FEARG_1, f_gettabinfo}, ! {"gettabvar", 2, 3, FEARG_1, f_gettabvar}, ! {"gettabwinvar", 3, 4, FEARG_1, f_gettabwinvar}, ! {"gettagstack", 0, 1, FEARG_1, f_gettagstack}, ! {"getwininfo", 0, 1, FEARG_1, f_getwininfo}, ! {"getwinpos", 0, 1, FEARG_1, f_getwinpos}, ! {"getwinposx", 0, 0, 0, f_getwinposx}, ! {"getwinposy", 0, 0, 0, f_getwinposy}, ! {"getwinvar", 2, 3, FEARG_1, f_getwinvar}, ! {"glob", 1, 4, FEARG_1, f_glob}, ! {"glob2regpat", 1, 1, FEARG_1, f_glob2regpat}, ! {"globpath", 2, 5, FEARG_2, f_globpath}, ! {"has", 1, 1, 0, f_has}, ! {"has_key", 2, 2, FEARG_1, f_has_key}, ! {"haslocaldir", 0, 2, FEARG_1, f_haslocaldir}, ! {"hasmapto", 1, 3, FEARG_1, f_hasmapto}, ! {"highlightID", 1, 1, FEARG_1, f_hlID}, // obsolete ! {"highlight_exists",1, 1, FEARG_1, f_hlexists}, // obsolete ! {"histadd", 2, 2, FEARG_2, f_histadd}, ! {"histdel", 1, 2, FEARG_1, f_histdel}, ! {"histget", 1, 2, FEARG_1, f_histget}, ! {"histnr", 1, 1, FEARG_1, f_histnr}, ! {"hlID", 1, 1, FEARG_1, f_hlID}, ! {"hlexists", 1, 1, FEARG_1, f_hlexists}, ! {"hostname", 0, 0, 0, f_hostname}, ! {"iconv", 3, 3, FEARG_1, f_iconv}, ! {"indent", 1, 1, FEARG_1, f_indent}, ! {"index", 2, 4, FEARG_1, f_index}, ! {"input", 1, 3, FEARG_1, f_input}, ! {"inputdialog", 1, 3, FEARG_1, f_inputdialog}, ! {"inputlist", 1, 1, FEARG_1, f_inputlist}, ! {"inputrestore", 0, 0, 0, f_inputrestore}, ! {"inputsave", 0, 0, 0, f_inputsave}, ! {"inputsecret", 1, 2, FEARG_1, f_inputsecret}, ! {"insert", 2, 3, FEARG_1, f_insert}, ! {"interrupt", 0, 0, 0, f_interrupt}, ! {"invert", 1, 1, FEARG_1, f_invert}, ! {"isdirectory", 1, 1, FEARG_1, f_isdirectory}, #if defined(FEAT_FLOAT) && defined(HAVE_MATH_H) ! {"isinf", 1, 1, FEARG_1, f_isinf}, #endif ! {"islocked", 1, 1, FEARG_1, f_islocked}, #if defined(FEAT_FLOAT) && defined(HAVE_MATH_H) ! {"isnan", 1, 1, FEARG_1, f_isnan}, #endif ! {"items", 1, 1, FEARG_1, f_items}, #ifdef FEAT_JOB_CHANNEL ! {"job_getchannel", 1, 1, FEARG_1, f_job_getchannel}, ! {"job_info", 0, 1, FEARG_1, f_job_info}, ! {"job_setoptions", 2, 2, FEARG_1, f_job_setoptions}, ! {"job_start", 1, 2, FEARG_1, f_job_start}, ! {"job_status", 1, 1, FEARG_1, f_job_status}, ! {"job_stop", 1, 2, FEARG_1, f_job_stop}, ! #endif ! {"join", 1, 2, FEARG_1, f_join}, ! {"js_decode", 1, 1, FEARG_1, f_js_decode}, ! {"js_encode", 1, 1, FEARG_1, f_js_encode}, ! {"json_decode", 1, 1, FEARG_1, f_json_decode}, ! {"json_encode", 1, 1, FEARG_1, f_json_encode}, ! {"keys", 1, 1, FEARG_1, f_keys}, ! {"last_buffer_nr", 0, 0, 0, f_last_buffer_nr}, // obsolete ! {"len", 1, 1, FEARG_1, f_len}, ! {"libcall", 3, 3, FEARG_3, f_libcall}, ! {"libcallnr", 3, 3, FEARG_3, f_libcallnr}, ! {"line", 1, 2, FEARG_1, f_line}, ! {"line2byte", 1, 1, FEARG_1, f_line2byte}, ! {"lispindent", 1, 1, FEARG_1, f_lispindent}, ! {"list2str", 1, 2, FEARG_1, f_list2str}, ! {"listener_add", 1, 2, FEARG_2, f_listener_add}, ! {"listener_flush", 0, 1, FEARG_1, f_listener_flush}, ! {"listener_remove", 1, 1, FEARG_1, f_listener_remove}, ! {"localtime", 0, 0, 0, f_localtime}, #ifdef FEAT_FLOAT ! {"log", 1, 1, FEARG_1, f_log}, ! {"log10", 1, 1, FEARG_1, f_log10}, #endif #ifdef FEAT_LUA ! {"luaeval", 1, 2, FEARG_1, f_luaeval}, #endif ! {"map", 2, 2, FEARG_1, f_map}, ! {"maparg", 1, 4, FEARG_1, f_maparg}, ! {"mapcheck", 1, 3, FEARG_1, f_mapcheck}, ! {"match", 2, 4, FEARG_1, f_match}, ! {"matchadd", 2, 5, FEARG_1, f_matchadd}, ! {"matchaddpos", 2, 5, FEARG_1, f_matchaddpos}, ! {"matcharg", 1, 1, FEARG_1, f_matcharg}, ! {"matchdelete", 1, 2, FEARG_1, f_matchdelete}, ! {"matchend", 2, 4, FEARG_1, f_matchend}, ! {"matchlist", 2, 4, FEARG_1, f_matchlist}, ! {"matchstr", 2, 4, FEARG_1, f_matchstr}, ! {"matchstrpos", 2, 4, FEARG_1, f_matchstrpos}, ! {"max", 1, 1, FEARG_1, f_max}, ! {"min", 1, 1, FEARG_1, f_min}, ! {"mkdir", 1, 3, FEARG_1, f_mkdir}, ! {"mode", 0, 1, FEARG_1, f_mode}, #ifdef FEAT_MZSCHEME ! {"mzeval", 1, 1, FEARG_1, f_mzeval}, #endif ! {"nextnonblank", 1, 1, FEARG_1, f_nextnonblank}, ! {"nr2char", 1, 2, FEARG_1, f_nr2char}, ! {"or", 2, 2, FEARG_1, f_or}, ! {"pathshorten", 1, 1, FEARG_1, f_pathshorten}, #ifdef FEAT_PERL ! {"perleval", 1, 1, FEARG_1, f_perleval}, #endif #ifdef FEAT_PROP_POPUP ! {"popup_atcursor", 2, 2, FEARG_1, f_popup_atcursor}, ! {"popup_beval", 2, 2, FEARG_1, f_popup_beval}, ! {"popup_clear", 0, 0, 0, f_popup_clear}, ! {"popup_close", 1, 2, FEARG_1, f_popup_close}, ! {"popup_create", 2, 2, FEARG_1, f_popup_create}, ! {"popup_dialog", 2, 2, FEARG_1, f_popup_dialog}, ! {"popup_filter_menu", 2, 2, 0, f_popup_filter_menu}, ! {"popup_filter_yesno", 2, 2, 0, f_popup_filter_yesno}, ! {"popup_findinfo", 0, 0, 0, f_popup_findinfo}, ! {"popup_findpreview", 0, 0, 0, f_popup_findpreview}, ! {"popup_getoptions", 1, 1, FEARG_1, f_popup_getoptions}, ! {"popup_getpos", 1, 1, FEARG_1, f_popup_getpos}, ! {"popup_hide", 1, 1, FEARG_1, f_popup_hide}, ! {"popup_locate", 2, 2, 0, f_popup_locate}, ! {"popup_menu", 2, 2, FEARG_1, f_popup_menu}, ! {"popup_move", 2, 2, FEARG_1, f_popup_move}, ! {"popup_notification", 2, 2, FEARG_1, f_popup_notification}, ! {"popup_setoptions", 2, 2, FEARG_1, f_popup_setoptions}, ! {"popup_settext", 2, 2, FEARG_1, f_popup_settext}, ! {"popup_show", 1, 1, FEARG_1, f_popup_show}, #endif #ifdef FEAT_FLOAT ! {"pow", 2, 2, FEARG_1, f_pow}, #endif ! {"prevnonblank", 1, 1, FEARG_1, f_prevnonblank}, ! {"printf", 1, 19, FEARG_2, f_printf}, #ifdef FEAT_JOB_CHANNEL ! {"prompt_setcallback", 2, 2, FEARG_1, f_prompt_setcallback}, ! {"prompt_setinterrupt", 2, 2, FEARG_1, f_prompt_setinterrupt}, ! {"prompt_setprompt", 2, 2, FEARG_1, f_prompt_setprompt}, #endif #ifdef FEAT_PROP_POPUP ! {"prop_add", 3, 3, FEARG_1, f_prop_add}, ! {"prop_clear", 1, 3, FEARG_1, f_prop_clear}, ! {"prop_find", 1, 2, FEARG_1, f_prop_find}, ! {"prop_list", 1, 2, FEARG_1, f_prop_list}, ! {"prop_remove", 1, 3, FEARG_1, f_prop_remove}, ! {"prop_type_add", 2, 2, FEARG_1, f_prop_type_add}, ! {"prop_type_change", 2, 2, FEARG_1, f_prop_type_change}, ! {"prop_type_delete", 1, 2, FEARG_1, f_prop_type_delete}, ! {"prop_type_get", 1, 2, FEARG_1, f_prop_type_get}, ! {"prop_type_list", 0, 1, FEARG_1, f_prop_type_list}, #endif ! {"pum_getpos", 0, 0, 0, f_pum_getpos}, ! {"pumvisible", 0, 0, 0, f_pumvisible}, #ifdef FEAT_PYTHON3 ! {"py3eval", 1, 1, FEARG_1, f_py3eval}, #endif #ifdef FEAT_PYTHON ! {"pyeval", 1, 1, FEARG_1, f_pyeval}, #endif #if defined(FEAT_PYTHON) || defined(FEAT_PYTHON3) ! {"pyxeval", 1, 1, FEARG_1, f_pyxeval}, #endif ! {"rand", 0, 1, FEARG_1, f_rand}, ! {"range", 1, 3, FEARG_1, f_range}, ! {"readdir", 1, 2, FEARG_1, f_readdir}, ! {"readfile", 1, 3, FEARG_1, f_readfile}, ! {"reg_executing", 0, 0, 0, f_reg_executing}, ! {"reg_recording", 0, 0, 0, f_reg_recording}, ! {"reltime", 0, 2, FEARG_1, f_reltime}, #ifdef FEAT_FLOAT ! {"reltimefloat", 1, 1, FEARG_1, f_reltimefloat}, #endif ! {"reltimestr", 1, 1, FEARG_1, f_reltimestr}, ! {"remote_expr", 2, 4, FEARG_1, f_remote_expr}, ! {"remote_foreground", 1, 1, FEARG_1, f_remote_foreground}, ! {"remote_peek", 1, 2, FEARG_1, f_remote_peek}, ! {"remote_read", 1, 2, FEARG_1, f_remote_read}, ! {"remote_send", 2, 3, FEARG_1, f_remote_send}, ! {"remote_startserver", 1, 1, FEARG_1, f_remote_startserver}, ! {"remove", 2, 3, FEARG_1, f_remove}, ! {"rename", 2, 2, FEARG_1, f_rename}, ! {"repeat", 2, 2, FEARG_1, f_repeat}, ! {"resolve", 1, 1, FEARG_1, f_resolve}, ! {"reverse", 1, 1, FEARG_1, f_reverse}, #ifdef FEAT_FLOAT ! {"round", 1, 1, FEARG_1, f_round}, #endif #ifdef FEAT_RUBY ! {"rubyeval", 1, 1, FEARG_1, f_rubyeval}, #endif ! {"screenattr", 2, 2, FEARG_1, f_screenattr}, ! {"screenchar", 2, 2, FEARG_1, f_screenchar}, ! {"screenchars", 2, 2, FEARG_1, f_screenchars}, ! {"screencol", 0, 0, 0, f_screencol}, ! {"screenpos", 3, 3, FEARG_1, f_screenpos}, ! {"screenrow", 0, 0, 0, f_screenrow}, ! {"screenstring", 2, 2, FEARG_1, f_screenstring}, ! {"search", 1, 4, FEARG_1, f_search}, ! {"searchdecl", 1, 3, FEARG_1, f_searchdecl}, ! {"searchpair", 3, 7, 0, f_searchpair}, ! {"searchpairpos", 3, 7, 0, f_searchpairpos}, ! {"searchpos", 1, 4, FEARG_1, f_searchpos}, ! {"server2client", 2, 2, FEARG_1, f_server2client}, ! {"serverlist", 0, 0, 0, f_serverlist}, ! {"setbufline", 3, 3, FEARG_3, f_setbufline}, ! {"setbufvar", 3, 3, FEARG_3, f_setbufvar}, ! {"setcharsearch", 1, 1, FEARG_1, f_setcharsearch}, ! {"setcmdpos", 1, 1, FEARG_1, f_setcmdpos}, ! {"setenv", 2, 2, FEARG_2, f_setenv}, ! {"setfperm", 2, 2, FEARG_1, f_setfperm}, ! {"setline", 2, 2, FEARG_2, f_setline}, ! {"setloclist", 2, 4, FEARG_2, f_setloclist}, ! {"setmatches", 1, 2, FEARG_1, f_setmatches}, ! {"setpos", 2, 2, FEARG_2, f_setpos}, ! {"setqflist", 1, 3, FEARG_1, f_setqflist}, ! {"setreg", 2, 3, FEARG_2, f_setreg}, ! {"settabvar", 3, 3, FEARG_3, f_settabvar}, ! {"settabwinvar", 4, 4, FEARG_4, f_settabwinvar}, ! {"settagstack", 2, 3, FEARG_2, f_settagstack}, ! {"setwinvar", 3, 3, FEARG_3, f_setwinvar}, #ifdef FEAT_CRYPT ! {"sha256", 1, 1, FEARG_1, f_sha256}, #endif ! {"shellescape", 1, 2, FEARG_1, f_shellescape}, ! {"shiftwidth", 0, 1, FEARG_1, f_shiftwidth}, #ifdef FEAT_SIGNS ! {"sign_define", 1, 2, FEARG_1, f_sign_define}, ! {"sign_getdefined", 0, 1, FEARG_1, f_sign_getdefined}, ! {"sign_getplaced", 0, 2, FEARG_1, f_sign_getplaced}, ! {"sign_jump", 3, 3, FEARG_1, f_sign_jump}, ! {"sign_place", 4, 5, FEARG_1, f_sign_place}, ! {"sign_placelist", 1, 1, FEARG_1, f_sign_placelist}, ! {"sign_undefine", 0, 1, FEARG_1, f_sign_undefine}, ! {"sign_unplace", 1, 2, FEARG_1, f_sign_unplace}, ! {"sign_unplacelist", 1, 2, FEARG_1, f_sign_unplacelist}, #endif ! {"simplify", 1, 1, 0, f_simplify}, #ifdef FEAT_FLOAT ! {"sin", 1, 1, FEARG_1, f_sin}, ! {"sinh", 1, 1, FEARG_1, f_sinh}, #endif ! {"sort", 1, 3, FEARG_1, f_sort}, #ifdef FEAT_SOUND ! {"sound_clear", 0, 0, 0, f_sound_clear}, ! {"sound_playevent", 1, 2, FEARG_1, f_sound_playevent}, ! {"sound_playfile", 1, 2, FEARG_1, f_sound_playfile}, ! {"sound_stop", 1, 1, FEARG_1, f_sound_stop}, ! #endif ! {"soundfold", 1, 1, FEARG_1, f_soundfold}, ! {"spellbadword", 0, 1, FEARG_1, f_spellbadword}, ! {"spellsuggest", 1, 3, FEARG_1, f_spellsuggest}, ! {"split", 1, 3, FEARG_1, f_split}, #ifdef FEAT_FLOAT ! {"sqrt", 1, 1, FEARG_1, f_sqrt}, #endif ! {"srand", 0, 1, FEARG_1, f_srand}, ! {"state", 0, 1, FEARG_1, f_state}, #ifdef FEAT_FLOAT ! {"str2float", 1, 1, FEARG_1, f_str2float}, #endif ! {"str2list", 1, 2, FEARG_1, f_str2list}, ! {"str2nr", 1, 3, FEARG_1, f_str2nr}, ! {"strcharpart", 2, 3, FEARG_1, f_strcharpart}, ! {"strchars", 1, 2, FEARG_1, f_strchars}, ! {"strdisplaywidth", 1, 2, FEARG_1, f_strdisplaywidth}, #ifdef HAVE_STRFTIME ! {"strftime", 1, 2, FEARG_1, f_strftime}, #endif ! {"strgetchar", 2, 2, FEARG_1, f_strgetchar}, ! {"stridx", 2, 3, FEARG_1, f_stridx}, ! {"string", 1, 1, FEARG_1, f_string}, ! {"strlen", 1, 1, FEARG_1, f_strlen}, ! {"strpart", 2, 3, FEARG_1, f_strpart}, #ifdef HAVE_STRPTIME ! {"strptime", 2, 2, FEARG_1, f_strptime}, #endif ! {"strridx", 2, 3, FEARG_1, f_strridx}, ! {"strtrans", 1, 1, FEARG_1, f_strtrans}, ! {"strwidth", 1, 1, FEARG_1, f_strwidth}, ! {"submatch", 1, 2, FEARG_1, f_submatch}, ! {"substitute", 4, 4, FEARG_1, f_substitute}, ! {"swapinfo", 1, 1, FEARG_1, f_swapinfo}, ! {"swapname", 1, 1, FEARG_1, f_swapname}, ! {"synID", 3, 3, 0, f_synID}, ! {"synIDattr", 2, 3, FEARG_1, f_synIDattr}, ! {"synIDtrans", 1, 1, FEARG_1, f_synIDtrans}, ! {"synconcealed", 2, 2, 0, f_synconcealed}, ! {"synstack", 2, 2, 0, f_synstack}, ! {"system", 1, 2, FEARG_1, f_system}, ! {"systemlist", 1, 2, FEARG_1, f_systemlist}, ! {"tabpagebuflist", 0, 1, FEARG_1, f_tabpagebuflist}, ! {"tabpagenr", 0, 1, 0, f_tabpagenr}, ! {"tabpagewinnr", 1, 2, FEARG_1, f_tabpagewinnr}, ! {"tagfiles", 0, 0, 0, f_tagfiles}, ! {"taglist", 1, 2, FEARG_1, f_taglist}, #ifdef FEAT_FLOAT ! {"tan", 1, 1, FEARG_1, f_tan}, ! {"tanh", 1, 1, FEARG_1, f_tanh}, #endif ! {"tempname", 0, 0, 0, f_tempname}, #ifdef FEAT_TERMINAL ! {"term_dumpdiff", 2, 3, FEARG_1, f_term_dumpdiff}, ! {"term_dumpload", 1, 2, FEARG_1, f_term_dumpload}, ! {"term_dumpwrite", 2, 3, FEARG_2, f_term_dumpwrite}, ! {"term_getaltscreen", 1, 1, FEARG_1, f_term_getaltscreen}, # if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS) ! {"term_getansicolors", 1, 1, FEARG_1, f_term_getansicolors}, # endif ! {"term_getattr", 2, 2, FEARG_1, f_term_getattr}, ! {"term_getcursor", 1, 1, FEARG_1, f_term_getcursor}, ! {"term_getjob", 1, 1, FEARG_1, f_term_getjob}, ! {"term_getline", 2, 2, FEARG_1, f_term_getline}, ! {"term_getscrolled", 1, 1, FEARG_1, f_term_getscrolled}, ! {"term_getsize", 1, 1, FEARG_1, f_term_getsize}, ! {"term_getstatus", 1, 1, FEARG_1, f_term_getstatus}, ! {"term_gettitle", 1, 1, FEARG_1, f_term_gettitle}, ! {"term_gettty", 1, 2, FEARG_1, f_term_gettty}, ! {"term_list", 0, 0, 0, f_term_list}, ! {"term_scrape", 2, 2, FEARG_1, f_term_scrape}, ! {"term_sendkeys", 2, 2, FEARG_1, f_term_sendkeys}, # if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS) ! {"term_setansicolors", 2, 2, FEARG_1, f_term_setansicolors}, # endif ! {"term_setapi", 2, 2, FEARG_1, f_term_setapi}, ! {"term_setkill", 2, 2, FEARG_1, f_term_setkill}, ! {"term_setrestore", 2, 2, FEARG_1, f_term_setrestore}, ! {"term_setsize", 3, 3, FEARG_1, f_term_setsize}, ! {"term_start", 1, 2, FEARG_1, f_term_start}, ! {"term_wait", 1, 2, FEARG_1, f_term_wait}, ! #endif ! {"test_alloc_fail", 3, 3, FEARG_1, f_test_alloc_fail}, ! {"test_autochdir", 0, 0, 0, f_test_autochdir}, ! {"test_feedinput", 1, 1, FEARG_1, f_test_feedinput}, ! {"test_garbagecollect_now", 0, 0, 0, f_test_garbagecollect_now}, ! {"test_garbagecollect_soon", 0, 0, 0, f_test_garbagecollect_soon}, ! {"test_getvalue", 1, 1, FEARG_1, f_test_getvalue}, ! {"test_ignore_error", 1, 1, FEARG_1, f_test_ignore_error}, ! {"test_null_blob", 0, 0, 0, f_test_null_blob}, #ifdef FEAT_JOB_CHANNEL ! {"test_null_channel", 0, 0, 0, f_test_null_channel}, #endif ! {"test_null_dict", 0, 0, 0, f_test_null_dict}, #ifdef FEAT_JOB_CHANNEL ! {"test_null_job", 0, 0, 0, f_test_null_job}, #endif ! {"test_null_list", 0, 0, 0, f_test_null_list}, ! {"test_null_partial", 0, 0, 0, f_test_null_partial}, ! {"test_null_string", 0, 0, 0, f_test_null_string}, ! {"test_option_not_set", 1, 1, FEARG_1, f_test_option_not_set}, ! {"test_override", 2, 2, FEARG_2, f_test_override}, ! {"test_refcount", 1, 1, FEARG_1, f_test_refcount}, #ifdef FEAT_GUI ! {"test_scrollbar", 3, 3, FEARG_2, f_test_scrollbar}, #endif ! {"test_setmouse", 2, 2, 0, f_test_setmouse}, ! {"test_settime", 1, 1, FEARG_1, f_test_settime}, #ifdef FEAT_TIMERS ! {"timer_info", 0, 1, FEARG_1, f_timer_info}, ! {"timer_pause", 2, 2, FEARG_1, f_timer_pause}, ! {"timer_start", 2, 3, FEARG_1, f_timer_start}, ! {"timer_stop", 1, 1, FEARG_1, f_timer_stop}, ! {"timer_stopall", 0, 0, 0, f_timer_stopall}, ! #endif ! {"tolower", 1, 1, FEARG_1, f_tolower}, ! {"toupper", 1, 1, FEARG_1, f_toupper}, ! {"tr", 3, 3, FEARG_1, f_tr}, ! {"trim", 1, 2, FEARG_1, f_trim}, #ifdef FEAT_FLOAT ! {"trunc", 1, 1, FEARG_1, f_trunc}, #endif ! {"type", 1, 1, FEARG_1, f_type}, ! {"undofile", 1, 1, FEARG_1, f_undofile}, ! {"undotree", 0, 0, 0, f_undotree}, ! {"uniq", 1, 3, FEARG_1, f_uniq}, ! {"values", 1, 1, FEARG_1, f_values}, ! {"virtcol", 1, 1, FEARG_1, f_virtcol}, ! {"visualmode", 0, 1, 0, f_visualmode}, ! {"wildmenumode", 0, 0, 0, f_wildmenumode}, ! {"win_execute", 2, 3, FEARG_2, f_win_execute}, ! {"win_findbuf", 1, 1, FEARG_1, f_win_findbuf}, ! {"win_getid", 0, 2, FEARG_1, f_win_getid}, ! {"win_gotoid", 1, 1, FEARG_1, f_win_gotoid}, ! {"win_id2tabwin", 1, 1, FEARG_1, f_win_id2tabwin}, ! {"win_id2win", 1, 1, FEARG_1, f_win_id2win}, ! {"win_screenpos", 1, 1, FEARG_1, f_win_screenpos}, ! {"win_splitmove", 2, 3, FEARG_1, f_win_splitmove}, ! {"winbufnr", 1, 1, FEARG_1, f_winbufnr}, ! {"wincol", 0, 0, 0, f_wincol}, ! {"windowsversion", 0, 0, 0, f_windowsversion}, ! {"winheight", 1, 1, FEARG_1, f_winheight}, ! {"winlayout", 0, 1, FEARG_1, f_winlayout}, ! {"winline", 0, 0, 0, f_winline}, ! {"winnr", 0, 1, FEARG_1, f_winnr}, ! {"winrestcmd", 0, 0, 0, f_winrestcmd}, ! {"winrestview", 1, 1, FEARG_1, f_winrestview}, ! {"winsaveview", 0, 0, 0, f_winsaveview}, ! {"winwidth", 1, 1, FEARG_1, f_winwidth}, ! {"wordcount", 0, 0, 0, f_wordcount}, ! {"writefile", 2, 3, FEARG_1, f_writefile}, ! {"xor", 2, 2, FEARG_1, f_xor}, }; /* --- 305,884 ---- static funcentry_T global_functions[] = { #ifdef FEAT_FLOAT ! {"abs", 1, 1, FEARG_1, &t_any, f_abs}, ! {"acos", 1, 1, FEARG_1, &t_float, f_acos}, // WJMc #endif ! {"add", 2, 2, FEARG_1, &t_any, f_add}, ! {"and", 2, 2, FEARG_1, &t_number, f_and}, ! {"append", 2, 2, FEARG_LAST, &t_number, f_append}, ! {"appendbufline", 3, 3, FEARG_LAST, &t_number, f_appendbufline}, ! {"argc", 0, 1, 0, &t_number, f_argc}, ! {"argidx", 0, 0, 0, &t_number, f_argidx}, ! {"arglistid", 0, 2, 0, &t_number, f_arglistid}, ! {"argv", 0, 2, 0, &t_any, f_argv}, #ifdef FEAT_FLOAT ! {"asin", 1, 1, FEARG_1, &t_float, f_asin}, // WJMc #endif ! {"assert_beeps", 1, 2, FEARG_1, &t_number, f_assert_beeps}, ! {"assert_equal", 2, 3, FEARG_2, &t_number, f_assert_equal}, ! {"assert_equalfile", 2, 2, FEARG_1, &t_number, f_assert_equalfile}, ! {"assert_exception", 1, 2, 0, &t_number, f_assert_exception}, ! {"assert_fails", 1, 3, FEARG_1, &t_number, f_assert_fails}, ! {"assert_false", 1, 2, FEARG_1, &t_number, f_assert_false}, ! {"assert_inrange", 3, 4, FEARG_3, &t_number, f_assert_inrange}, ! {"assert_match", 2, 3, FEARG_2, &t_number, f_assert_match}, ! {"assert_notequal", 2, 3, FEARG_2, &t_number, f_assert_notequal}, ! {"assert_notmatch", 2, 3, FEARG_2, &t_number, f_assert_notmatch}, ! {"assert_report", 1, 1, FEARG_1, &t_number, f_assert_report}, ! {"assert_true", 1, 2, FEARG_1, &t_number, f_assert_true}, #ifdef FEAT_FLOAT ! {"atan", 1, 1, FEARG_1, &t_float, f_atan}, ! {"atan2", 2, 2, FEARG_1, &t_float, f_atan2}, #endif #ifdef FEAT_BEVAL ! {"balloon_gettext", 0, 0, 0, &t_string, f_balloon_gettext}, ! {"balloon_show", 1, 1, FEARG_1, &t_void, f_balloon_show}, # if defined(FEAT_BEVAL_TERM) ! {"balloon_split", 1, 1, FEARG_1, &t_list_string, f_balloon_split}, # endif #endif ! {"browse", 4, 4, 0, &t_string, f_browse}, ! {"browsedir", 2, 2, 0, &t_string, f_browsedir}, ! {"bufadd", 1, 1, FEARG_1, &t_number, f_bufadd}, ! {"bufexists", 1, 1, FEARG_1, &t_number, f_bufexists}, ! {"buffer_exists", 1, 1, FEARG_1, &t_number, f_bufexists}, // obsolete ! {"buffer_name", 0, 1, FEARG_1, &t_string, f_bufname}, // obsolete ! {"buffer_number", 0, 1, FEARG_1, &t_number, f_bufnr}, // obsolete ! {"buflisted", 1, 1, FEARG_1, &t_number, f_buflisted}, ! {"bufload", 1, 1, FEARG_1, &t_void, f_bufload}, ! {"bufloaded", 1, 1, FEARG_1, &t_number, f_bufloaded}, ! {"bufname", 0, 1, FEARG_1, &t_string, f_bufname}, ! {"bufnr", 0, 2, FEARG_1, &t_number, f_bufnr}, ! {"bufwinid", 1, 1, FEARG_1, &t_number, f_bufwinid}, ! {"bufwinnr", 1, 1, FEARG_1, &t_number, f_bufwinnr}, ! {"byte2line", 1, 1, FEARG_1, &t_number, f_byte2line}, ! {"byteidx", 2, 2, FEARG_1, &t_number, f_byteidx}, ! {"byteidxcomp", 2, 2, FEARG_1, &t_number, f_byteidxcomp}, ! {"call", 2, 3, FEARG_1, &t_any, f_call}, #ifdef FEAT_FLOAT ! {"ceil", 1, 1, FEARG_1, &t_float, f_ceil}, #endif #ifdef FEAT_JOB_CHANNEL ! {"ch_canread", 1, 1, FEARG_1, &t_number, f_ch_canread}, ! {"ch_close", 1, 1, FEARG_1, &t_void, f_ch_close}, ! {"ch_close_in", 1, 1, FEARG_1, &t_void, f_ch_close_in}, ! {"ch_evalexpr", 2, 3, FEARG_1, &t_any, f_ch_evalexpr}, ! {"ch_evalraw", 2, 3, FEARG_1, &t_any, f_ch_evalraw}, ! {"ch_getbufnr", 2, 2, FEARG_1, &t_number, f_ch_getbufnr}, ! {"ch_getjob", 1, 1, FEARG_1, &t_job, f_ch_getjob}, ! {"ch_info", 1, 1, FEARG_1, &t_dict_any, f_ch_info}, ! {"ch_log", 1, 2, FEARG_1, &t_void, f_ch_log}, ! {"ch_logfile", 1, 2, FEARG_1, &t_void, f_ch_logfile}, ! {"ch_open", 1, 2, FEARG_1, &t_channel, f_ch_open}, ! {"ch_read", 1, 2, FEARG_1, &t_string, f_ch_read}, ! {"ch_readblob", 1, 2, FEARG_1, &t_blob, f_ch_readblob}, ! {"ch_readraw", 1, 2, FEARG_1, &t_string, f_ch_readraw}, ! {"ch_sendexpr", 2, 3, FEARG_1, &t_void, f_ch_sendexpr}, ! {"ch_sendraw", 2, 3, FEARG_1, &t_void, f_ch_sendraw}, ! {"ch_setoptions", 2, 2, FEARG_1, &t_void, f_ch_setoptions}, ! {"ch_status", 1, 2, FEARG_1, &t_string, f_ch_status}, ! #endif ! {"changenr", 0, 0, 0, &t_number, f_changenr}, ! {"char2nr", 1, 2, FEARG_1, &t_number, f_char2nr}, ! {"chdir", 1, 1, FEARG_1, &t_string, f_chdir}, ! {"cindent", 1, 1, FEARG_1, &t_number, f_cindent}, ! {"clearmatches", 0, 1, FEARG_1, &t_void, f_clearmatches}, ! {"col", 1, 1, FEARG_1, &t_number, f_col}, ! {"complete", 2, 2, FEARG_2, &t_void, f_complete}, ! {"complete_add", 1, 1, FEARG_1, &t_number, f_complete_add}, ! {"complete_check", 0, 0, 0, &t_number, f_complete_check}, ! {"complete_info", 0, 1, FEARG_1, &t_dict_any, f_complete_info}, ! {"confirm", 1, 4, FEARG_1, &t_number, f_confirm}, ! {"copy", 1, 1, FEARG_1, &t_any, f_copy}, #ifdef FEAT_FLOAT ! {"cos", 1, 1, FEARG_1, &t_float, f_cos}, ! {"cosh", 1, 1, FEARG_1, &t_float, f_cosh}, #endif ! {"count", 2, 4, FEARG_1, &t_number, f_count}, ! {"cscope_connection",0,3, 0, &t_number, f_cscope_connection}, ! {"cursor", 1, 3, FEARG_1, &t_number, f_cursor}, #ifdef MSWIN ! {"debugbreak", 1, 1, FEARG_1, &t_number, f_debugbreak}, #endif ! {"deepcopy", 1, 2, FEARG_1, &t_any, f_deepcopy}, ! {"delete", 1, 2, FEARG_1, &t_number, f_delete}, ! {"deletebufline", 2, 3, FEARG_1, &t_number, f_deletebufline}, ! {"did_filetype", 0, 0, 0, &t_number, f_did_filetype}, ! {"diff_filler", 1, 1, FEARG_1, &t_number, f_diff_filler}, ! {"diff_hlID", 2, 2, FEARG_1, &t_number, f_diff_hlID}, ! {"empty", 1, 1, FEARG_1, &t_number, f_empty}, ! {"environ", 0, 0, 0, &t_dict_string, f_environ}, ! {"escape", 2, 2, FEARG_1, &t_string, f_escape}, ! {"eval", 1, 1, FEARG_1, &t_any, f_eval}, ! {"eventhandler", 0, 0, 0, &t_number, f_eventhandler}, ! {"executable", 1, 1, FEARG_1, &t_number, f_executable}, ! {"execute", 1, 2, FEARG_1, &t_string, f_execute}, ! {"exepath", 1, 1, FEARG_1, &t_string, f_exepath}, ! {"exists", 1, 1, FEARG_1, &t_number, f_exists}, #ifdef FEAT_FLOAT ! {"exp", 1, 1, FEARG_1, &t_float, f_exp}, #endif ! {"expand", 1, 3, FEARG_1, &t_any, f_expand}, ! {"expandcmd", 1, 1, FEARG_1, &t_string, f_expandcmd}, ! {"extend", 2, 3, FEARG_1, &t_any, f_extend}, ! {"feedkeys", 1, 2, FEARG_1, &t_void, f_feedkeys}, ! {"file_readable", 1, 1, FEARG_1, &t_number, f_filereadable}, // obsolete ! {"filereadable", 1, 1, FEARG_1, &t_number, f_filereadable}, ! {"filewritable", 1, 1, FEARG_1, &t_number, f_filewritable}, ! {"filter", 2, 2, FEARG_1, &t_any, f_filter}, ! {"finddir", 1, 3, FEARG_1, &t_string, f_finddir}, ! {"findfile", 1, 3, FEARG_1, &t_string, f_findfile}, #ifdef FEAT_FLOAT ! {"float2nr", 1, 1, FEARG_1, &t_number, f_float2nr}, ! {"floor", 1, 1, FEARG_1, &t_float, f_floor}, ! {"fmod", 2, 2, FEARG_1, &t_float, f_fmod}, ! #endif ! {"fnameescape", 1, 1, FEARG_1, &t_string, f_fnameescape}, ! {"fnamemodify", 2, 2, FEARG_1, &t_string, f_fnamemodify}, ! {"foldclosed", 1, 1, FEARG_1, &t_number, f_foldclosed}, ! {"foldclosedend", 1, 1, FEARG_1, &t_number, f_foldclosedend}, ! {"foldlevel", 1, 1, FEARG_1, &t_number, f_foldlevel}, ! {"foldtext", 0, 0, 0, &t_string, f_foldtext}, ! {"foldtextresult", 1, 1, FEARG_1, &t_string, f_foldtextresult}, ! {"foreground", 0, 0, 0, &t_void, f_foreground}, ! {"funcref", 1, 3, FEARG_1, &t_any, f_funcref}, ! {"function", 1, 3, FEARG_1, &t_any, f_function}, ! {"garbagecollect", 0, 1, 0, &t_void, f_garbagecollect}, ! {"get", 2, 3, FEARG_1, &t_any, f_get}, ! {"getbufinfo", 0, 1, 0, &t_list_dict_any, f_getbufinfo}, ! {"getbufline", 2, 3, FEARG_1, &t_list_string, f_getbufline}, ! {"getbufvar", 2, 3, FEARG_1, &t_any, f_getbufvar}, ! {"getchangelist", 0, 1, FEARG_1, &t_list_any, f_getchangelist}, ! {"getchar", 0, 1, 0, &t_number, f_getchar}, ! {"getcharmod", 0, 0, 0, &t_number, f_getcharmod}, ! {"getcharsearch", 0, 0, 0, &t_dict_any, f_getcharsearch}, ! {"getcmdline", 0, 0, 0, &t_string, f_getcmdline}, ! {"getcmdpos", 0, 0, 0, &t_number, f_getcmdpos}, ! {"getcmdtype", 0, 0, 0, &t_string, f_getcmdtype}, ! {"getcmdwintype", 0, 0, 0, &t_string, f_getcmdwintype}, ! {"getcompletion", 2, 3, FEARG_1, &t_list_string, f_getcompletion}, ! {"getcurpos", 0, 0, 0, &t_list_number, f_getcurpos}, ! {"getcwd", 0, 2, FEARG_1, &t_string, f_getcwd}, ! {"getenv", 1, 1, FEARG_1, &t_string, f_getenv}, ! {"getfontname", 0, 1, 0, &t_string, f_getfontname}, ! {"getfperm", 1, 1, FEARG_1, &t_string, f_getfperm}, ! {"getfsize", 1, 1, FEARG_1, &t_number, f_getfsize}, ! {"getftime", 1, 1, FEARG_1, &t_number, f_getftime}, ! {"getftype", 1, 1, FEARG_1, &t_string, f_getftype}, ! {"getimstatus", 0, 0, 0, &t_number, f_getimstatus}, ! {"getjumplist", 0, 2, FEARG_1, &t_list_any, f_getjumplist}, ! {"getline", 1, 2, FEARG_1, &t_string, f_getline}, ! {"getloclist", 1, 2, 0, &t_list_dict_any, f_getloclist}, ! {"getmatches", 0, 1, 0, &t_list_dict_any, f_getmatches}, ! {"getmousepos", 0, 0, 0, &t_dict_number, f_getmousepos}, ! {"getpid", 0, 0, 0, &t_number, f_getpid}, ! {"getpos", 1, 1, FEARG_1, &t_list_number, f_getpos}, ! {"getqflist", 0, 1, 0, &t_list_dict_any, f_getqflist}, ! {"getreg", 0, 3, FEARG_1, &t_string, f_getreg}, ! {"getregtype", 0, 1, FEARG_1, &t_string, f_getregtype}, ! {"gettabinfo", 0, 1, FEARG_1, &t_list_dict_any, f_gettabinfo}, ! {"gettabvar", 2, 3, FEARG_1, &t_any, f_gettabvar}, ! {"gettabwinvar", 3, 4, FEARG_1, &t_any, f_gettabwinvar}, ! {"gettagstack", 0, 1, FEARG_1, &t_dict_any, f_gettagstack}, ! {"getwininfo", 0, 1, FEARG_1, &t_list_dict_any, f_getwininfo}, ! {"getwinpos", 0, 1, FEARG_1, &t_list_number, f_getwinpos}, ! {"getwinposx", 0, 0, 0, &t_number, f_getwinposx}, ! {"getwinposy", 0, 0, 0, &t_number, f_getwinposy}, ! {"getwinvar", 2, 3, FEARG_1, &t_any, f_getwinvar}, ! {"glob", 1, 4, FEARG_1, &t_any, f_glob}, ! {"glob2regpat", 1, 1, FEARG_1, &t_string, f_glob2regpat}, ! {"globpath", 2, 5, FEARG_2, &t_any, f_globpath}, ! {"has", 1, 1, 0, &t_number, f_has}, ! {"has_key", 2, 2, FEARG_1, &t_number, f_has_key}, ! {"haslocaldir", 0, 2, FEARG_1, &t_number, f_haslocaldir}, ! {"hasmapto", 1, 3, FEARG_1, &t_number, f_hasmapto}, ! {"highlightID", 1, 1, FEARG_1, &t_number, f_hlID}, // obsolete ! {"highlight_exists",1, 1, FEARG_1, &t_number, f_hlexists}, // obsolete ! {"histadd", 2, 2, FEARG_2, &t_number, f_histadd}, ! {"histdel", 1, 2, FEARG_1, &t_number, f_histdel}, ! {"histget", 1, 2, FEARG_1, &t_string, f_histget}, ! {"histnr", 1, 1, FEARG_1, &t_number, f_histnr}, ! {"hlID", 1, 1, FEARG_1, &t_number, f_hlID}, ! {"hlexists", 1, 1, FEARG_1, &t_number, f_hlexists}, ! {"hostname", 0, 0, 0, &t_string, f_hostname}, ! {"iconv", 3, 3, FEARG_1, &t_string, f_iconv}, ! {"indent", 1, 1, FEARG_1, &t_number, f_indent}, ! {"index", 2, 4, FEARG_1, &t_number, f_index}, ! {"input", 1, 3, FEARG_1, &t_string, f_input}, ! {"inputdialog", 1, 3, FEARG_1, &t_string, f_inputdialog}, ! {"inputlist", 1, 1, FEARG_1, &t_number, f_inputlist}, ! {"inputrestore", 0, 0, 0, &t_number, f_inputrestore}, ! {"inputsave", 0, 0, 0, &t_number, f_inputsave}, ! {"inputsecret", 1, 2, FEARG_1, &t_string, f_inputsecret}, ! {"insert", 2, 3, FEARG_1, &t_any, f_insert}, ! {"interrupt", 0, 0, 0, &t_void, f_interrupt}, ! {"invert", 1, 1, FEARG_1, &t_number, f_invert}, ! {"isdirectory", 1, 1, FEARG_1, &t_number, f_isdirectory}, #if defined(FEAT_FLOAT) && defined(HAVE_MATH_H) ! {"isinf", 1, 1, FEARG_1, &t_number, f_isinf}, #endif ! {"islocked", 1, 1, FEARG_1, &t_number, f_islocked}, #if defined(FEAT_FLOAT) && defined(HAVE_MATH_H) ! {"isnan", 1, 1, FEARG_1, &t_number, f_isnan}, #endif ! {"items", 1, 1, FEARG_1, &t_list_any, f_items}, #ifdef FEAT_JOB_CHANNEL ! {"job_getchannel", 1, 1, FEARG_1, &t_channel, f_job_getchannel}, ! {"job_info", 0, 1, FEARG_1, &t_dict_any, f_job_info}, ! {"job_setoptions", 2, 2, FEARG_1, &t_void, f_job_setoptions}, ! {"job_start", 1, 2, FEARG_1, &t_job, f_job_start}, ! {"job_status", 1, 1, FEARG_1, &t_string, f_job_status}, ! {"job_stop", 1, 2, FEARG_1, &t_number, f_job_stop}, ! #endif ! {"join", 1, 2, FEARG_1, &t_string, f_join}, ! {"js_decode", 1, 1, FEARG_1, &t_any, f_js_decode}, ! {"js_encode", 1, 1, FEARG_1, &t_string, f_js_encode}, ! {"json_decode", 1, 1, FEARG_1, &t_any, f_json_decode}, ! {"json_encode", 1, 1, FEARG_1, &t_string, f_json_encode}, ! {"keys", 1, 1, FEARG_1, &t_list_any, f_keys}, ! {"last_buffer_nr", 0, 0, 0, &t_number, f_last_buffer_nr}, // obsolete ! {"len", 1, 1, FEARG_1, &t_number, f_len}, ! {"libcall", 3, 3, FEARG_3, &t_string, f_libcall}, ! {"libcallnr", 3, 3, FEARG_3, &t_number, f_libcallnr}, ! {"line", 1, 2, FEARG_1, &t_number, f_line}, ! {"line2byte", 1, 1, FEARG_1, &t_number, f_line2byte}, ! {"lispindent", 1, 1, FEARG_1, &t_number, f_lispindent}, ! {"list2str", 1, 2, FEARG_1, &t_string, f_list2str}, ! {"listener_add", 1, 2, FEARG_2, &t_number, f_listener_add}, ! {"listener_flush", 0, 1, FEARG_1, &t_void, f_listener_flush}, ! {"listener_remove", 1, 1, FEARG_1, &t_number, f_listener_remove}, ! {"localtime", 0, 0, 0, &t_number, f_localtime}, #ifdef FEAT_FLOAT ! {"log", 1, 1, FEARG_1, &t_float, f_log}, ! {"log10", 1, 1, FEARG_1, &t_float, f_log10}, #endif #ifdef FEAT_LUA ! {"luaeval", 1, 2, FEARG_1, &t_any, f_luaeval}, #endif ! {"map", 2, 2, FEARG_1, &t_any, f_map}, ! {"maparg", 1, 4, FEARG_1, &t_string, f_maparg}, ! {"mapcheck", 1, 3, FEARG_1, &t_string, f_mapcheck}, ! {"match", 2, 4, FEARG_1, &t_any, f_match}, ! {"matchadd", 2, 5, FEARG_1, &t_number, f_matchadd}, ! {"matchaddpos", 2, 5, FEARG_1, &t_number, f_matchaddpos}, ! {"matcharg", 1, 1, FEARG_1, &t_list_string, f_matcharg}, ! {"matchdelete", 1, 2, FEARG_1, &t_number, f_matchdelete}, ! {"matchend", 2, 4, FEARG_1, &t_number, f_matchend}, ! {"matchlist", 2, 4, FEARG_1, &t_list_any, f_matchlist}, ! {"matchstr", 2, 4, FEARG_1, &t_string, f_matchstr}, ! {"matchstrpos", 2, 4, FEARG_1, &t_list_any, f_matchstrpos}, ! {"max", 1, 1, FEARG_1, &t_any, f_max}, ! {"min", 1, 1, FEARG_1, &t_any, f_min}, ! {"mkdir", 1, 3, FEARG_1, &t_number, f_mkdir}, ! {"mode", 0, 1, FEARG_1, &t_string, f_mode}, #ifdef FEAT_MZSCHEME ! {"mzeval", 1, 1, FEARG_1, &t_any, f_mzeval}, #endif ! {"nextnonblank", 1, 1, FEARG_1, &t_number, f_nextnonblank}, ! {"nr2char", 1, 2, FEARG_1, &t_string, f_nr2char}, ! {"or", 2, 2, FEARG_1, &t_number, f_or}, ! {"pathshorten", 1, 1, FEARG_1, &t_string, f_pathshorten}, #ifdef FEAT_PERL ! {"perleval", 1, 1, FEARG_1, &t_any, f_perleval}, #endif #ifdef FEAT_PROP_POPUP ! {"popup_atcursor", 2, 2, FEARG_1, &t_number, f_popup_atcursor}, ! {"popup_beval", 2, 2, FEARG_1, &t_number, f_popup_beval}, ! {"popup_clear", 0, 0, 0, &t_void, f_popup_clear}, ! {"popup_close", 1, 2, FEARG_1, &t_void, f_popup_close}, ! {"popup_create", 2, 2, FEARG_1, &t_number, f_popup_create}, ! {"popup_dialog", 2, 2, FEARG_1, &t_number, f_popup_dialog}, ! {"popup_filter_menu", 2, 2, 0, &t_number, f_popup_filter_menu}, ! {"popup_filter_yesno", 2, 2, 0, &t_number, f_popup_filter_yesno}, ! {"popup_findinfo", 0, 0, 0, &t_number, f_popup_findinfo}, ! {"popup_findpreview", 0, 0, 0, &t_number, f_popup_findpreview}, ! {"popup_getoptions", 1, 1, FEARG_1, &t_dict_any, f_popup_getoptions}, ! {"popup_getpos", 1, 1, FEARG_1, &t_dict_any, f_popup_getpos}, ! {"popup_hide", 1, 1, FEARG_1, &t_void, f_popup_hide}, ! {"popup_locate", 2, 2, 0, &t_number, f_popup_locate}, ! {"popup_menu", 2, 2, FEARG_1, &t_number, f_popup_menu}, ! {"popup_move", 2, 2, FEARG_1, &t_void, f_popup_move}, ! {"popup_notification", 2, 2, FEARG_1, &t_number, f_popup_notification}, ! {"popup_setoptions", 2, 2, FEARG_1, &t_void, f_popup_setoptions}, ! {"popup_settext", 2, 2, FEARG_1, &t_void, f_popup_settext}, ! {"popup_show", 1, 1, FEARG_1, &t_void, f_popup_show}, #endif #ifdef FEAT_FLOAT ! {"pow", 2, 2, FEARG_1, &t_float, f_pow}, #endif ! {"prevnonblank", 1, 1, FEARG_1, &t_number, f_prevnonblank}, ! {"printf", 1, 19, FEARG_2, &t_string, f_printf}, #ifdef FEAT_JOB_CHANNEL ! {"prompt_setcallback", 2, 2, FEARG_1, &t_void, f_prompt_setcallback}, ! {"prompt_setinterrupt", 2, 2, FEARG_1,&t_void, f_prompt_setinterrupt}, ! {"prompt_setprompt", 2, 2, FEARG_1, &t_void, f_prompt_setprompt}, #endif #ifdef FEAT_PROP_POPUP ! {"prop_add", 3, 3, FEARG_1, &t_void, f_prop_add}, ! {"prop_clear", 1, 3, FEARG_1, &t_void, f_prop_clear}, ! {"prop_find", 1, 2, FEARG_1, &t_dict_any, f_prop_find}, ! {"prop_list", 1, 2, FEARG_1, &t_list_any, f_prop_list}, ! {"prop_remove", 1, 3, FEARG_1, &t_number, f_prop_remove}, ! {"prop_type_add", 2, 2, FEARG_1, &t_void, f_prop_type_add}, ! {"prop_type_change", 2, 2, FEARG_1, &t_void, f_prop_type_change}, ! {"prop_type_delete", 1, 2, FEARG_1, &t_void, f_prop_type_delete}, ! {"prop_type_get", 1, 2, FEARG_1, &t_dict_any, f_prop_type_get}, ! {"prop_type_list", 0, 1, FEARG_1, &t_list_string, f_prop_type_list}, #endif ! {"pum_getpos", 0, 0, 0, &t_dict_number, f_pum_getpos}, ! {"pumvisible", 0, 0, 0, &t_number, f_pumvisible}, #ifdef FEAT_PYTHON3 ! {"py3eval", 1, 1, FEARG_1, &t_any, f_py3eval}, #endif #ifdef FEAT_PYTHON ! {"pyeval", 1, 1, FEARG_1, &t_any, f_pyeval}, #endif #if defined(FEAT_PYTHON) || defined(FEAT_PYTHON3) ! {"pyxeval", 1, 1, FEARG_1, &t_any, f_pyxeval}, #endif ! {"rand", 0, 1, FEARG_1, &t_number, f_rand}, ! {"range", 1, 3, FEARG_1, &t_list_number, f_range}, ! {"readdir", 1, 2, FEARG_1, &t_list_string, f_readdir}, ! {"readfile", 1, 3, FEARG_1, &t_any, f_readfile}, ! {"reg_executing", 0, 0, 0, &t_string, f_reg_executing}, ! {"reg_recording", 0, 0, 0, &t_string, f_reg_recording}, ! {"reltime", 0, 2, FEARG_1, &t_list_any, f_reltime}, #ifdef FEAT_FLOAT ! {"reltimefloat", 1, 1, FEARG_1, &t_float, f_reltimefloat}, #endif ! {"reltimestr", 1, 1, FEARG_1, &t_string, f_reltimestr}, ! {"remote_expr", 2, 4, FEARG_1, &t_string, f_remote_expr}, ! {"remote_foreground", 1, 1, FEARG_1, &t_string, f_remote_foreground}, ! {"remote_peek", 1, 2, FEARG_1, &t_number, f_remote_peek}, ! {"remote_read", 1, 2, FEARG_1, &t_string, f_remote_read}, ! {"remote_send", 2, 3, FEARG_1, &t_string, f_remote_send}, ! {"remote_startserver", 1, 1, FEARG_1, &t_void, f_remote_startserver}, ! {"remove", 2, 3, FEARG_1, &t_any, f_remove}, ! {"rename", 2, 2, FEARG_1, &t_number, f_rename}, ! {"repeat", 2, 2, FEARG_1, &t_any, f_repeat}, ! {"resolve", 1, 1, FEARG_1, &t_string, f_resolve}, ! {"reverse", 1, 1, FEARG_1, &t_any, f_reverse}, #ifdef FEAT_FLOAT ! {"round", 1, 1, FEARG_1, &t_float, f_round}, #endif #ifdef FEAT_RUBY ! {"rubyeval", 1, 1, FEARG_1, &t_any, f_rubyeval}, #endif ! {"screenattr", 2, 2, FEARG_1, &t_number, f_screenattr}, ! {"screenchar", 2, 2, FEARG_1, &t_number, f_screenchar}, ! {"screenchars", 2, 2, FEARG_1, &t_list_number, f_screenchars}, ! {"screencol", 0, 0, 0, &t_number, f_screencol}, ! {"screenpos", 3, 3, FEARG_1, &t_dict_number, f_screenpos}, ! {"screenrow", 0, 0, 0, &t_number, f_screenrow}, ! {"screenstring", 2, 2, FEARG_1, &t_string, f_screenstring}, ! {"search", 1, 4, FEARG_1, &t_number, f_search}, ! {"searchdecl", 1, 3, FEARG_1, &t_number, f_searchdecl}, ! {"searchpair", 3, 7, 0, &t_number, f_searchpair}, ! {"searchpairpos", 3, 7, 0, &t_list_number, f_searchpairpos}, ! {"searchpos", 1, 4, FEARG_1, &t_list_number, f_searchpos}, ! {"server2client", 2, 2, FEARG_1, &t_number, f_server2client}, ! {"serverlist", 0, 0, 0, &t_string, f_serverlist}, ! {"setbufline", 3, 3, FEARG_3, &t_number, f_setbufline}, ! {"setbufvar", 3, 3, FEARG_3, &t_void, f_setbufvar}, ! {"setcharsearch", 1, 1, FEARG_1, &t_void, f_setcharsearch}, ! {"setcmdpos", 1, 1, FEARG_1, &t_number, f_setcmdpos}, ! {"setenv", 2, 2, FEARG_2, &t_void, f_setenv}, ! {"setfperm", 2, 2, FEARG_1, &t_number, f_setfperm}, ! {"setline", 2, 2, FEARG_2, &t_number, f_setline}, ! {"setloclist", 2, 4, FEARG_2, &t_number, f_setloclist}, ! {"setmatches", 1, 2, FEARG_1, &t_number, f_setmatches}, ! {"setpos", 2, 2, FEARG_2, &t_number, f_setpos}, ! {"setqflist", 1, 3, FEARG_1, &t_number, f_setqflist}, ! {"setreg", 2, 3, FEARG_2, &t_number, f_setreg}, ! {"settabvar", 3, 3, FEARG_3, &t_void, f_settabvar}, ! {"settabwinvar", 4, 4, FEARG_4, &t_void, f_settabwinvar}, ! {"settagstack", 2, 3, FEARG_2, &t_number, f_settagstack}, ! {"setwinvar", 3, 3, FEARG_3, &t_void, f_setwinvar}, #ifdef FEAT_CRYPT ! {"sha256", 1, 1, FEARG_1, &t_string, f_sha256}, #endif ! {"shellescape", 1, 2, FEARG_1, &t_string, f_shellescape}, ! {"shiftwidth", 0, 1, FEARG_1, &t_number, f_shiftwidth}, #ifdef FEAT_SIGNS ! {"sign_define", 1, 2, FEARG_1, &t_any, f_sign_define}, ! {"sign_getdefined", 0, 1, FEARG_1, &t_list_dict_any, f_sign_getdefined}, ! {"sign_getplaced", 0, 2, FEARG_1, &t_list_dict_any, f_sign_getplaced}, ! {"sign_jump", 3, 3, FEARG_1, &t_number, f_sign_jump}, ! {"sign_place", 4, 5, FEARG_1, &t_number, f_sign_place}, ! {"sign_placelist", 1, 1, FEARG_1, &t_list_number, f_sign_placelist}, ! {"sign_undefine", 0, 1, FEARG_1, &t_number, f_sign_undefine}, ! {"sign_unplace", 1, 2, FEARG_1, &t_number, f_sign_unplace}, ! {"sign_unplacelist", 1, 2, FEARG_1, &t_list_number, f_sign_unplacelist}, #endif ! {"simplify", 1, 1, 0, &t_string, f_simplify}, #ifdef FEAT_FLOAT ! {"sin", 1, 1, FEARG_1, &t_float, f_sin}, ! {"sinh", 1, 1, FEARG_1, &t_float, f_sinh}, #endif ! {"sort", 1, 3, FEARG_1, &t_list_any, f_sort}, #ifdef FEAT_SOUND ! {"sound_clear", 0, 0, 0, &t_void, f_sound_clear}, ! {"sound_playevent", 1, 2, FEARG_1, &t_number, f_sound_playevent}, ! {"sound_playfile", 1, 2, FEARG_1, &t_number, f_sound_playfile}, ! {"sound_stop", 1, 1, FEARG_1, &t_void, f_sound_stop}, ! #endif ! {"soundfold", 1, 1, FEARG_1, &t_string, f_soundfold}, ! {"spellbadword", 0, 1, FEARG_1, &t_list_string, f_spellbadword}, ! {"spellsuggest", 1, 3, FEARG_1, &t_list_string, f_spellsuggest}, ! {"split", 1, 3, FEARG_1, &t_list_string, f_split}, #ifdef FEAT_FLOAT ! {"sqrt", 1, 1, FEARG_1, &t_float, f_sqrt}, #endif ! {"srand", 0, 1, FEARG_1, &t_list_number, f_srand}, ! {"state", 0, 1, FEARG_1, &t_string, f_state}, #ifdef FEAT_FLOAT ! {"str2float", 1, 1, FEARG_1, &t_float, f_str2float}, #endif ! {"str2list", 1, 2, FEARG_1, &t_list_number, f_str2list}, ! {"str2nr", 1, 3, FEARG_1, &t_number, f_str2nr}, ! {"strcharpart", 2, 3, FEARG_1, &t_string, f_strcharpart}, ! {"strchars", 1, 2, FEARG_1, &t_number, f_strchars}, ! {"strdisplaywidth", 1, 2, FEARG_1, &t_number, f_strdisplaywidth}, #ifdef HAVE_STRFTIME ! {"strftime", 1, 2, FEARG_1, &t_string, f_strftime}, #endif ! {"strgetchar", 2, 2, FEARG_1, &t_number, f_strgetchar}, ! {"stridx", 2, 3, FEARG_1, &t_number, f_stridx}, ! {"string", 1, 1, FEARG_1, &t_string, f_string}, ! {"strlen", 1, 1, FEARG_1, &t_number, f_strlen}, ! {"strpart", 2, 3, FEARG_1, &t_string, f_strpart}, #ifdef HAVE_STRPTIME ! {"strptime", 2, 2, FEARG_1, &t_number, f_strptime}, #endif ! {"strridx", 2, 3, FEARG_1, &t_number, f_strridx}, ! {"strtrans", 1, 1, FEARG_1, &t_string, f_strtrans}, ! {"strwidth", 1, 1, FEARG_1, &t_number, f_strwidth}, ! {"submatch", 1, 2, FEARG_1, &t_string, f_submatch}, ! {"substitute", 4, 4, FEARG_1, &t_string, f_substitute}, ! {"swapinfo", 1, 1, FEARG_1, &t_dict_any, f_swapinfo}, ! {"swapname", 1, 1, FEARG_1, &t_string, f_swapname}, ! {"synID", 3, 3, 0, &t_number, f_synID}, ! {"synIDattr", 2, 3, FEARG_1, &t_string, f_synIDattr}, ! {"synIDtrans", 1, 1, FEARG_1, &t_number, f_synIDtrans}, ! {"synconcealed", 2, 2, 0, &t_list_any, f_synconcealed}, ! {"synstack", 2, 2, 0, &t_list_number, f_synstack}, ! {"system", 1, 2, FEARG_1, &t_string, f_system}, ! {"systemlist", 1, 2, FEARG_1, &t_list_string, f_systemlist}, ! {"tabpagebuflist", 0, 1, FEARG_1, &t_list_number, f_tabpagebuflist}, ! {"tabpagenr", 0, 1, 0, &t_number, f_tabpagenr}, ! {"tabpagewinnr", 1, 2, FEARG_1, &t_number, f_tabpagewinnr}, ! {"tagfiles", 0, 0, 0, &t_list_string, f_tagfiles}, ! {"taglist", 1, 2, FEARG_1, &t_list_dict_any, f_taglist}, #ifdef FEAT_FLOAT ! {"tan", 1, 1, FEARG_1, &t_float, f_tan}, ! {"tanh", 1, 1, FEARG_1, &t_float, f_tanh}, #endif ! {"tempname", 0, 0, 0, &t_string, f_tempname}, #ifdef FEAT_TERMINAL ! {"term_dumpdiff", 2, 3, FEARG_1, &t_number, f_term_dumpdiff}, ! {"term_dumpload", 1, 2, FEARG_1, &t_number, f_term_dumpload}, ! {"term_dumpwrite", 2, 3, FEARG_2, &t_void, f_term_dumpwrite}, ! {"term_getaltscreen", 1, 1, FEARG_1, &t_number, f_term_getaltscreen}, # if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS) ! {"term_getansicolors", 1, 1, FEARG_1, &t_list_string, f_term_getansicolors}, # endif ! {"term_getattr", 2, 2, FEARG_1, &t_number, f_term_getattr}, ! {"term_getcursor", 1, 1, FEARG_1, &t_list_any, f_term_getcursor}, ! {"term_getjob", 1, 1, FEARG_1, &t_job, f_term_getjob}, ! {"term_getline", 2, 2, FEARG_1, &t_string, f_term_getline}, ! {"term_getscrolled", 1, 1, FEARG_1, &t_number, f_term_getscrolled}, ! {"term_getsize", 1, 1, FEARG_1, &t_list_number, f_term_getsize}, ! {"term_getstatus", 1, 1, FEARG_1, &t_string, f_term_getstatus}, ! {"term_gettitle", 1, 1, FEARG_1, &t_string, f_term_gettitle}, ! {"term_gettty", 1, 2, FEARG_1, &t_string, f_term_gettty}, ! {"term_list", 0, 0, 0, &t_list_number, f_term_list}, ! {"term_scrape", 2, 2, FEARG_1, &t_list_dict_any, f_term_scrape}, ! {"term_sendkeys", 2, 2, FEARG_1, &t_void, f_term_sendkeys}, # if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS) ! {"term_setansicolors", 2, 2, FEARG_1, &t_void, f_term_setansicolors}, # endif ! {"term_setapi", 2, 2, FEARG_1, &t_void, f_term_setapi}, ! {"term_setkill", 2, 2, FEARG_1, &t_void, f_term_setkill}, ! {"term_setrestore", 2, 2, FEARG_1, &t_void, f_term_setrestore}, ! {"term_setsize", 3, 3, FEARG_1, &t_void, f_term_setsize}, ! {"term_start", 1, 2, FEARG_1, &t_number, f_term_start}, ! {"term_wait", 1, 2, FEARG_1, &t_void, f_term_wait}, ! #endif ! {"test_alloc_fail", 3, 3, FEARG_1, &t_void, f_test_alloc_fail}, ! {"test_autochdir", 0, 0, 0, &t_void, f_test_autochdir}, ! {"test_feedinput", 1, 1, FEARG_1, &t_void, f_test_feedinput}, ! {"test_garbagecollect_now", 0, 0, 0, &t_void, f_test_garbagecollect_now}, ! {"test_garbagecollect_soon", 0, 0, 0, &t_void, f_test_garbagecollect_soon}, ! {"test_getvalue", 1, 1, FEARG_1, &t_number, f_test_getvalue}, ! {"test_ignore_error", 1, 1, FEARG_1, &t_void, f_test_ignore_error}, ! {"test_null_blob", 0, 0, 0, &t_blob, f_test_null_blob}, #ifdef FEAT_JOB_CHANNEL ! {"test_null_channel", 0, 0, 0, &t_channel, f_test_null_channel}, #endif ! {"test_null_dict", 0, 0, 0, &t_dict_any, f_test_null_dict}, #ifdef FEAT_JOB_CHANNEL ! {"test_null_job", 0, 0, 0, &t_job, f_test_null_job}, #endif ! {"test_null_list", 0, 0, 0, &t_list_any, f_test_null_list}, ! {"test_null_partial", 0, 0, 0, &t_partial_void, f_test_null_partial}, ! {"test_null_string", 0, 0, 0, &t_string, f_test_null_string}, ! {"test_option_not_set", 1, 1, FEARG_1,&t_void, f_test_option_not_set}, ! {"test_override", 2, 2, FEARG_2, &t_void, f_test_override}, ! {"test_refcount", 1, 1, FEARG_1, &t_number, f_test_refcount}, #ifdef FEAT_GUI ! {"test_scrollbar", 3, 3, FEARG_2, &t_void, f_test_scrollbar}, #endif ! {"test_setmouse", 2, 2, 0, &t_void, f_test_setmouse}, ! {"test_settime", 1, 1, FEARG_1, &t_void, f_test_settime}, #ifdef FEAT_TIMERS ! {"timer_info", 0, 1, FEARG_1, &t_list_dict_any, f_timer_info}, ! {"timer_pause", 2, 2, FEARG_1, &t_void, f_timer_pause}, ! {"timer_start", 2, 3, FEARG_1, &t_number, f_timer_start}, ! {"timer_stop", 1, 1, FEARG_1, &t_void, f_timer_stop}, ! {"timer_stopall", 0, 0, 0, &t_void, f_timer_stopall}, ! #endif ! {"tolower", 1, 1, FEARG_1, &t_string, f_tolower}, ! {"toupper", 1, 1, FEARG_1, &t_string, f_toupper}, ! {"tr", 3, 3, FEARG_1, &t_string, f_tr}, ! {"trim", 1, 2, FEARG_1, &t_string, f_trim}, #ifdef FEAT_FLOAT ! {"trunc", 1, 1, FEARG_1, &t_float, f_trunc}, #endif ! {"type", 1, 1, FEARG_1, &t_number, f_type}, ! {"undofile", 1, 1, FEARG_1, &t_string, f_undofile}, ! {"undotree", 0, 0, 0, &t_dict_any, f_undotree}, ! {"uniq", 1, 3, FEARG_1, &t_list_any, f_uniq}, ! {"values", 1, 1, FEARG_1, &t_list_any, f_values}, ! {"virtcol", 1, 1, FEARG_1, &t_number, f_virtcol}, ! {"visualmode", 0, 1, 0, &t_string, f_visualmode}, ! {"wildmenumode", 0, 0, 0, &t_number, f_wildmenumode}, ! {"win_execute", 2, 3, FEARG_2, &t_string, f_win_execute}, ! {"win_findbuf", 1, 1, FEARG_1, &t_list_number, f_win_findbuf}, ! {"win_getid", 0, 2, FEARG_1, &t_number, f_win_getid}, ! {"win_gotoid", 1, 1, FEARG_1, &t_number, f_win_gotoid}, ! {"win_id2tabwin", 1, 1, FEARG_1, &t_list_number, f_win_id2tabwin}, ! {"win_id2win", 1, 1, FEARG_1, &t_number, f_win_id2win}, ! {"win_screenpos", 1, 1, FEARG_1, &t_list_number, f_win_screenpos}, ! {"win_splitmove", 2, 3, FEARG_1, &t_number, f_win_splitmove}, ! {"winbufnr", 1, 1, FEARG_1, &t_number, f_winbufnr}, ! {"wincol", 0, 0, 0, &t_number, f_wincol}, ! {"windowsversion", 0, 0, 0, &t_string, f_windowsversion}, ! {"winheight", 1, 1, FEARG_1, &t_number, f_winheight}, ! {"winlayout", 0, 1, FEARG_1, &t_list_any, f_winlayout}, ! {"winline", 0, 0, 0, &t_number, f_winline}, ! {"winnr", 0, 1, FEARG_1, &t_number, f_winnr}, ! {"winrestcmd", 0, 0, 0, &t_string, f_winrestcmd}, ! {"winrestview", 1, 1, FEARG_1, &t_void, f_winrestview}, ! {"winsaveview", 0, 0, 0, &t_dict_any, f_winsaveview}, ! {"winwidth", 1, 1, FEARG_1, &t_number, f_winwidth}, ! {"wordcount", 0, 0, 0, &t_dict_number, f_wordcount}, ! {"writefile", 2, 3, FEARG_1, &t_number, f_writefile}, ! {"xor", 2, 2, FEARG_1, &t_number, f_xor}, }; /* *************** *** 935,941 **** * Find internal function "name" in table "global_functions". * Return index, or -1 if not found */ ! static int find_internal_func(char_u *name) { int first = 0; --- 936,942 ---- * Find internal function "name" in table "global_functions". * Return index, or -1 if not found */ ! int find_internal_func(char_u *name) { int first = 0; *************** *** 966,971 **** --- 967,1013 ---- return find_internal_func(name) >= 0; } + char * + internal_func_name(int idx) + { + return global_functions[idx].f_name; + } + + type_T * + internal_func_ret_type(int idx, int argcount) + { + funcentry_T *fe = &global_functions[idx]; + + if (fe->f_func == f_getline) + return argcount == 1 ? &t_string : &t_list_string; + return fe->f_rettype; + } + + /* + * Check the argument count to use for internal function "idx". + * Returns OK or FAIL; + */ + int + check_internal_func(int idx, int argcount) + { + int res; + char *name; + + if (argcount < global_functions[idx].f_min_argc) + res = FCERR_TOOFEW; + else if (argcount > global_functions[idx].f_max_argc) + res = FCERR_TOOMANY; + else + return OK; + + name = internal_func_name(idx); + if (res == FCERR_TOOMANY) + semsg(_(e_toomanyarg), name); + else + semsg(_(e_toofewarg), name); + return FAIL; + } + int call_internal_func( char_u *name, *************** *** 987,992 **** --- 1029,1043 ---- return FCERR_NONE; } + void + call_internal_func_by_idx( + int idx, + typval_T *argvars, + typval_T *rettv) + { + global_functions[idx].f_func(argvars, rettv); + } + /* * Invoke a method for base->method(). */ *************** *** 1834,1839 **** --- 1885,1891 ---- break; #endif case VAR_UNKNOWN: + case VAR_VOID: internal_error("f_empty(UNKNOWN)"); n = TRUE; break; *************** *** 2487,2493 **** semsg(_(e_invarg2), use_string ? tv_get_string(&argvars[0]) : s); // Don't check an autoload name for existence here. else if (trans_name != NULL && (is_funcref ! ? find_func(trans_name) == NULL : !translated_function_exists(trans_name))) semsg(_("E700: Unknown function: %s"), s); else --- 2539,2545 ---- semsg(_(e_invarg2), use_string ? tv_get_string(&argvars[0]) : s); // Don't check an autoload name for existence here. else if (trans_name != NULL && (is_funcref ! ? find_func(trans_name, NULL) == NULL : !translated_function_exists(trans_name))) semsg(_("E700: Unknown function: %s"), s); else *************** *** 2623,2629 **** } else if (is_funcref) { ! pt->pt_func = find_func(trans_name); func_ptr_ref(pt->pt_func); vim_free(name); } --- 2675,2681 ---- } else if (is_funcref) { ! pt->pt_func = find_func(trans_name, NULL); func_ptr_ref(pt->pt_func); vim_free(name); } *************** *** 4319,4324 **** --- 4371,4377 ---- rettv->vval.v_number = dict_len(argvars[0].vval.v_dict); break; case VAR_UNKNOWN: + case VAR_VOID: case VAR_BOOL: case VAR_SPECIAL: case VAR_FLOAT: *************** *** 5237,5243 **** varnumber_T start; varnumber_T end; varnumber_T stride = 1; - varnumber_T i; int error = FALSE; start = tv_get_number_chk(&argvars[0], &error); --- 5290,5295 ---- *************** *** 5259,5271 **** emsg(_("E726: Stride is zero")); else if (stride > 0 ? end + 1 < start : end - 1 > start) emsg(_("E727: Start past end")); ! else { ! if (rettv_list_alloc(rettv) == OK) ! for (i = start; stride > 0 ? i <= end : i >= end; i += stride) ! if (list_append_number(rettv->vval.v_list, ! (varnumber_T)i) == FAIL) ! break; } } --- 5311,5351 ---- emsg(_("E726: Stride is zero")); else if (stride > 0 ? end + 1 < start : end - 1 > start) emsg(_("E727: Start past end")); ! else if (rettv_list_alloc(rettv) == OK) { ! list_T *list = rettv->vval.v_list; ! ! // Create a non-materialized list. This is much more efficient and ! // works with ":for". If used otherwise range_list_materialize() must ! // be called. ! list->lv_first = &range_list_item; ! list->lv_start = start; ! list->lv_end = end; ! list->lv_stride = stride; ! list->lv_len = (end - start + 1) / stride; ! } ! } ! ! /* ! * If "list" is a non-materialized list then materialize it now. ! */ ! void ! range_list_materialize(list_T *list) ! { ! if (list->lv_first == &range_list_item) ! { ! varnumber_T start = list->lv_start; ! varnumber_T end = list->lv_end; ! int stride = list->lv_stride; ! varnumber_T i; ! ! list->lv_first = NULL; ! list->lv_last = NULL; ! list->lv_len = 0; ! list->lv_idx_item = NULL; ! for (i = start; stride > 0 ? i <= end : i >= end; i += stride) ! if (list_append_number(list, (varnumber_T)i) == FAIL) ! break; } } *************** *** 8356,8361 **** --- 8436,8442 ---- case VAR_CHANNEL: n = VAR_TYPE_CHANNEL; break; case VAR_BLOB: n = VAR_TYPE_BLOB; break; case VAR_UNKNOWN: + case VAR_VOID: internal_error("f_type(UNKNOWN)"); n = -1; break; *** ../vim-8.2.0148/src/evalvars.c 2020-01-12 17:42:52.410413873 +0100 --- src/evalvars.c 2020-01-26 15:30:08.617766613 +0100 *************** *** 15,22 **** #if defined(FEAT_EVAL) || defined(PROTO) - static char *e_letunexp = N_("E18: Unexpected characters in :let"); - static dictitem_T globvars_var; // variable used for g: static dict_T globvardict; // Dictionary with g: variables #define globvarht globvardict.dv_hashtab --- 15,20 ---- *************** *** 41,46 **** --- 39,46 ---- #define VV_NAME(s, t) s, {{t, 0, {0}}, 0, {0}} + typedef struct vimvar vimvar_T; + static struct vimvar { char *vv_name; // name of variable, without v: *************** *** 163,179 **** // for VIM_VERSION_ defines #include "version.h" - #define SCRIPT_SV(id) (SCRIPT_ITEM(id).sn_vars) - #define SCRIPT_VARS(id) (SCRIPT_SV(id)->sv_dict.dv_hashtab) - static void ex_let_const(exarg_T *eap, int is_const); ! static char_u *skip_var_one(char_u *arg); static void list_glob_vars(int *first); static void list_buf_vars(int *first); static void list_win_vars(int *first); static void list_tab_vars(int *first); static char_u *list_arg_vars(exarg_T *eap, char_u *arg, int *first); ! static char_u *ex_let_one(char_u *arg, typval_T *tv, int copy, int is_const, char_u *endchars, char_u *op); static void ex_unletlock(exarg_T *eap, char_u *argstart, int deep); static int do_unlet_var(lval_T *lp, char_u *name_end, int forceit); static int do_lock_var(lval_T *lp, char_u *name_end, int deep, int lock); --- 163,176 ---- // for VIM_VERSION_ defines #include "version.h" static void ex_let_const(exarg_T *eap, int is_const); ! static char_u *skip_var_one(char_u *arg, int include_type); static void list_glob_vars(int *first); static void list_buf_vars(int *first); static void list_win_vars(int *first); static void list_tab_vars(int *first); static char_u *list_arg_vars(exarg_T *eap, char_u *arg, int *first); ! static char_u *ex_let_one(char_u *arg, typval_T *tv, int copy, int flags, char_u *endchars, char_u *op); static void ex_unletlock(exarg_T *eap, char_u *argstart, int deep); static int do_unlet_var(lval_T *lp, char_u *name_end, int forceit); static int do_lock_var(lval_T *lp, char_u *name_end, int deep, int lock); *************** *** 544,550 **** * indentation in the 'cmd' line) is stripped. * Returns a List with {lines} or NULL. */ ! static list_T * heredoc_get(exarg_T *eap, char_u *cmd) { char_u *theline; --- 541,547 ---- * indentation in the 'cmd' line) is stripped. * Returns a List with {lines} or NULL. */ ! list_T * heredoc_get(exarg_T *eap, char_u *cmd) { char_u *theline; *************** *** 669,674 **** --- 666,672 ---- * ":let var .= expr" assignment command. * ":let var ..= expr" assignment command. * ":let [var1, var2] = expr" unpack list. + * ":let var =<< ..." heredoc */ void ex_let(exarg_T *eap) *************** *** 701,708 **** char_u *argend; int first = TRUE; int concat; ! argend = skip_var_list(arg, &var_count, &semicolon); if (argend == NULL) return; if (argend > arg && argend[-1] == '.') // for var.='str' --- 699,711 ---- char_u *argend; int first = TRUE; int concat; + int flags = is_const ? LET_IS_CONST : 0; + + // detect Vim9 assignment without ":let" or ":const" + if (eap->arg == eap->cmd) + flags |= LET_NO_COMMAND; ! argend = skip_var_list(arg, TRUE, &var_count, &semicolon); if (argend == NULL) return; if (argend > arg && argend[-1] == '.') // for var.='str' *************** *** 749,755 **** op[0] = '='; op[1] = NUL; (void)ex_let_vars(eap->arg, &rettv, FALSE, semicolon, var_count, ! is_const, op); } clear_tv(&rettv); } --- 752,758 ---- op[0] = '='; op[1] = NUL; (void)ex_let_vars(eap->arg, &rettv, FALSE, semicolon, var_count, ! flags, op); } clear_tv(&rettv); } *************** *** 783,789 **** else if (i != FAIL) { (void)ex_let_vars(eap->arg, &rettv, FALSE, semicolon, var_count, ! is_const, op); clear_tv(&rettv); } } --- 786,792 ---- else if (i != FAIL) { (void)ex_let_vars(eap->arg, &rettv, FALSE, semicolon, var_count, ! flags, op); clear_tv(&rettv); } } *************** *** 804,810 **** int copy, // copy values from "tv", don't move int semicolon, // from skip_var_list() int var_count, // from skip_var_list() ! int is_const, // lock variables for const char_u *op) { char_u *arg = arg_start; --- 807,813 ---- int copy, // copy values from "tv", don't move int semicolon, // from skip_var_list() int var_count, // from skip_var_list() ! int flags, // LET_IS_CONST and/or LET_NO_COMMAND char_u *op) { char_u *arg = arg_start; *************** *** 816,822 **** if (*arg != '[') { // ":let var = expr" or ":for var in list" ! if (ex_let_one(arg, tv, copy, is_const, op, op) == NULL) return FAIL; return OK; } --- 819,825 ---- if (*arg != '[') { // ":let var = expr" or ":for var in list" ! if (ex_let_one(arg, tv, copy, flags, op, op) == NULL) return FAIL; return OK; } *************** *** 844,851 **** while (*arg != ']') { arg = skipwhite(arg + 1); ! arg = ex_let_one(arg, &item->li_tv, TRUE, is_const, ! (char_u *)",;]", op); item = item->li_next; if (arg == NULL) return FAIL; --- 847,853 ---- while (*arg != ']') { arg = skipwhite(arg + 1); ! arg = ex_let_one(arg, &item->li_tv, TRUE, flags, (char_u *)",;]", op); item = item->li_next; if (arg == NULL) return FAIL; *************** *** 869,875 **** ltv.vval.v_list = l; l->lv_refcount = 1; ! arg = ex_let_one(skipwhite(arg + 1), <v, FALSE, is_const, (char_u *)"]", op); clear_tv(<v); if (arg == NULL) --- 871,877 ---- ltv.vval.v_list = l; l->lv_refcount = 1; ! arg = ex_let_one(skipwhite(arg + 1), <v, FALSE, flags, (char_u *)"]", op); clear_tv(<v); if (arg == NULL) *************** *** 896,901 **** --- 898,904 ---- char_u * skip_var_list( char_u *arg, + int include_type, int *var_count, int *semicolon) { *************** *** 908,914 **** for (;;) { p = skipwhite(p + 1); // skip whites after '[', ';' or ',' ! s = skip_var_one(p); if (s == p) { semsg(_(e_invarg2), p); --- 911,917 ---- for (;;) { p = skipwhite(p + 1); // skip whites after '[', ';' or ',' ! s = skip_var_one(p, TRUE); if (s == p) { semsg(_(e_invarg2), p); *************** *** 937,956 **** return p + 1; } else ! return skip_var_one(arg); } /* * Skip one (assignable) variable name, including @r, $VAR, &option, d.key, * l[idx]. */ static char_u * ! skip_var_one(char_u *arg) { if (*arg == '@' && arg[1] != NUL) return arg + 2; ! return find_name_end(*arg == '$' || *arg == '&' ? arg + 1 : arg, NULL, NULL, FNE_INCL_BR | FNE_CHECK_START); } /* --- 940,968 ---- return p + 1; } else ! return skip_var_one(arg, include_type); } /* * Skip one (assignable) variable name, including @r, $VAR, &option, d.key, * l[idx]. + * In Vim9 script also skip over ": type" if "include_type" is TRUE. */ static char_u * ! skip_var_one(char_u *arg, int include_type) { + char_u *end; + if (*arg == '@' && arg[1] != NUL) return arg + 2; ! end = find_name_end(*arg == '$' || *arg == '&' ? arg + 1 : arg, NULL, NULL, FNE_INCL_BR | FNE_CHECK_START); + if (include_type && current_sctx.sc_version == SCRIPT_VERSION_VIM9 + && *end == ':') + { + end = skip_type(skipwhite(end + 1)); + } + return end; } /* *************** *** 1141,1147 **** char_u *arg, // points to variable name typval_T *tv, // value to assign to variable int copy, // copy value from "tv" ! int is_const, // lock variable for const char_u *endchars, // valid chars after variable name or NULL char_u *op) // "+", "-", "." or NULL { --- 1153,1159 ---- char_u *arg, // points to variable name typval_T *tv, // value to assign to variable int copy, // copy value from "tv" ! int flags, // LET_IS_CONST and/or LET_NO_COMMAND char_u *endchars, // valid chars after variable name or NULL char_u *op) // "+", "-", "." or NULL { *************** *** 1156,1162 **** // ":let $VAR = expr": Set environment variable. if (*arg == '$') { ! if (is_const) { emsg(_("E996: Cannot lock an environment variable")); return NULL; --- 1168,1174 ---- // ":let $VAR = expr": Set environment variable. if (*arg == '$') { ! if (flags & LET_IS_CONST) { emsg(_("E996: Cannot lock an environment variable")); return NULL; *************** *** 1214,1222 **** // ":let &g:option = expr": Set global option value. else if (*arg == '&') { ! if (is_const) { ! emsg(_("E996: Cannot lock an option")); return NULL; } // Find the end of the name. --- 1226,1234 ---- // ":let &g:option = expr": Set global option value. else if (*arg == '&') { ! if (flags & LET_IS_CONST) { ! emsg(_(e_const_option)); return NULL; } // Find the end of the name. *************** *** 1281,1287 **** // ":let @r = expr": Set register contents. else if (*arg == '@') { ! if (is_const) { emsg(_("E996: Cannot lock a register")); return NULL; --- 1293,1299 ---- // ":let @r = expr": Set register contents. else if (*arg == '@') { ! if (flags & LET_IS_CONST) { emsg(_("E996: Cannot lock a register")); return NULL; *************** *** 1317,1322 **** --- 1329,1335 ---- } // ":let var = expr": Set internal variable. + // ":let var: type = expr": Set internal variable with type. // ":let {expr} = expr": Idem, name made with curly braces else if (eval_isnamec1(*arg) || *arg == '{') { *************** *** 1325,1335 **** p = get_lval(arg, tv, &lv, FALSE, FALSE, 0, FNE_CHECK_START); if (p != NULL && lv.ll_name != NULL) { ! if (endchars != NULL && vim_strchr(endchars, *skipwhite(p)) == NULL) emsg(_(e_letunexp)); else { ! set_var_lval(&lv, p, tv, copy, is_const, op); arg_end = p; } } --- 1338,1349 ---- p = get_lval(arg, tv, &lv, FALSE, FALSE, 0, FNE_CHECK_START); if (p != NULL && lv.ll_name != NULL) { ! if (endchars != NULL && vim_strchr(endchars, ! *skipwhite(lv.ll_name_end)) == NULL) emsg(_(e_letunexp)); else { ! set_var_lval(&lv, p, tv, copy, flags, op); arg_end = p; } } *************** *** 1657,1668 **** switch (tv->v_type) { case VAR_UNKNOWN: case VAR_NUMBER: case VAR_STRING: case VAR_FUNC: case VAR_PARTIAL: case VAR_FLOAT: - case VAR_BOOL: case VAR_SPECIAL: case VAR_JOB: case VAR_CHANNEL: --- 1671,1683 ---- switch (tv->v_type) { case VAR_UNKNOWN: + case VAR_VOID: case VAR_NUMBER: + case VAR_BOOL: case VAR_STRING: case VAR_FUNC: case VAR_PARTIAL: case VAR_FLOAT: case VAR_SPECIAL: case VAR_JOB: case VAR_CHANNEL: *************** *** 1901,1906 **** --- 1916,1937 ---- } /* + * Returns the index of a v:variable. Negative if not found. + */ + int + find_vim_var(char_u *name) + { + dictitem_T *di = find_var_in_ht(&vimvarht, 0, name, TRUE); + struct vimvar *vv; + + if (di == NULL) + return -1; + vv = (struct vimvar *)((char *)di - offsetof(vimvar_T, vv_di)); + return (int)(vv - vimvars); + } + + + /* * Set type of v: variable to "type". */ void *************** *** 1919,1924 **** --- 1950,1961 ---- vimvars[idx].vv_nr = val; } + char * + get_vim_var_name(int idx) + { + return vimvars[idx].vv_name; + } + /* * Get typval_T v: variable value. */ *************** *** 2245,2250 **** --- 2282,2301 ---- *dip = v; } + if (tv == NULL && current_sctx.sc_version == SCRIPT_VERSION_VIM9) + { + imported_T *import = find_imported(name, NULL); + + // imported variable from another script + if (import != NULL) + { + scriptitem_T *si = &SCRIPT_ITEM(import->imp_sid); + svar_T *sv = ((svar_T *)si->sn_var_vals.ga_data) + + import->imp_var_vals_idx; + tv = sv->sv_tv; + } + } + if (tv == NULL) { if (rettv != NULL && verbose) *************** *** 2366,2371 **** --- 2417,2474 ---- } /* + * Get the script-local hashtab. NULL if not in a script context. + */ + hashtab_T * + get_script_local_ht(void) + { + scid_T sid = current_sctx.sc_sid; + + if (sid > 0 && sid <= script_items.ga_len) + return &SCRIPT_VARS(sid); + return NULL; + } + + /* + * Look for "name[len]" in script-local variables. + * Return -1 when not found. + */ + int + lookup_scriptvar(char_u *name, size_t len, cctx_T *dummy UNUSED) + { + hashtab_T *ht = get_script_local_ht(); + char_u buffer[30]; + char_u *p; + int res; + hashitem_T *hi; + + if (ht == NULL) + return -1; + if (len < sizeof(buffer) - 1) + { + vim_strncpy(buffer, name, len); + p = buffer; + } + else + { + p = vim_strnsave(name, (int)len); + if (p == NULL) + return -1; + } + + hi = hash_find(ht, p); + res = HASHITEM_EMPTY(hi) ? -1 : 1; + + // if not script-local, then perhaps imported + if (res == -1 && find_imported(p, NULL) != NULL) + res = 1; + + if (p != buffer) + vim_free(p); + return res; + } + + /* * Find the hashtab used for a variable name. * Return NULL if the name is not valid. * Set "varname" to the start of name without ':'. *************** *** 2395,2403 **** } ht = get_funccal_local_ht(); ! if (ht == NULL) ! return &globvarht; // global variable ! return ht; // local variable } *varname = name + 2; if (*name == 'g') // global variable --- 2498,2515 ---- } ht = get_funccal_local_ht(); ! if (ht != NULL) ! return ht; // local variable ! ! // in Vim9 script items at the script level are script-local ! if (current_sctx.sc_version == SCRIPT_VERSION_VIM9) ! { ! ht = get_script_local_ht(); ! if (ht != NULL) ! return ht; ! } ! ! return &globvarht; // global variable } *varname = name + 2; if (*name == 'g') // global variable *************** *** 2414,2427 **** return &curtab->tp_vars->dv_hashtab; if (*name == 'v') // v: variable return &vimvarht; ! if (*name == 'a') // a: function argument ! return get_funccal_args_ht(); ! if (*name == 'l') // l: local function variable ! return get_funccal_local_ht(); ! if (*name == 's' // script variable ! && current_sctx.sc_sid > 0 ! && current_sctx.sc_sid <= script_items.ga_len) ! return &SCRIPT_VARS(current_sctx.sc_sid); return NULL; } --- 2526,2544 ---- return &curtab->tp_vars->dv_hashtab; if (*name == 'v') // v: variable return &vimvarht; ! if (current_sctx.sc_version != SCRIPT_VERSION_VIM9) ! { ! if (*name == 'a') // a: function argument ! return get_funccal_args_ht(); ! if (*name == 'l') // l: local function variable ! return get_funccal_local_ht(); ! } ! if (*name == 's') // script variable ! { ! ht = get_script_local_ht(); ! if (ht != NULL) ! return ht; ! } return NULL; } *************** *** 2617,2623 **** typval_T *tv, int copy) // make copy of value in "tv" { ! set_var_const(name, tv, copy, FALSE); } /* --- 2734,2740 ---- typval_T *tv, int copy) // make copy of value in "tv" { ! set_var_const(name, NULL, tv, copy, 0); } /* *************** *** 2628,2640 **** void set_var_const( char_u *name, typval_T *tv, int copy, // make copy of value in "tv" ! int is_const) // disallow to modify existing variable { ! dictitem_T *v; char_u *varname; hashtab_T *ht; ht = find_var_ht(name, &varname); if (ht == NULL || *varname == NUL) --- 2745,2759 ---- void set_var_const( char_u *name, + type_T *type, typval_T *tv, int copy, // make copy of value in "tv" ! int flags) // LET_IS_CONST and/or LET_NO_COMMAND { ! dictitem_T *di; char_u *varname; hashtab_T *ht; + int is_script_local; ht = find_var_ht(name, &varname); if (ht == NULL || *varname == NUL) *************** *** 2642,2716 **** semsg(_(e_illvar), name); return; } ! v = find_var_in_ht(ht, 0, varname, TRUE); // Search in parent scope which is possible to reference from lambda ! if (v == NULL) ! v = find_var_in_scoped_ht(name, TRUE); if ((tv->v_type == VAR_FUNC || tv->v_type == VAR_PARTIAL) ! && var_check_func_name(name, v == NULL)) return; ! if (v != NULL) { ! if (is_const) { ! emsg(_(e_cannot_mod)); ! return; } // existing variable, need to clear the value - if (var_check_ro(v->di_flags, name, FALSE) - || var_check_lock(v->di_tv.v_lock, name, FALSE)) - return; ! // Handle setting internal v: variables separately where needed to // prevent changing the type. if (ht == &vimvarht) { ! if (v->di_tv.v_type == VAR_STRING) { ! VIM_CLEAR(v->di_tv.vval.v_string); if (copy || tv->v_type != VAR_STRING) { char_u *val = tv_get_string(tv); // Careful: when assigning to v:errmsg and tv_get_string() // causes an error message the variable will alrady be set. ! if (v->di_tv.vval.v_string == NULL) ! v->di_tv.vval.v_string = vim_strsave(val); } else { // Take over the string to avoid an extra alloc/free. ! v->di_tv.vval.v_string = tv->vval.v_string; tv->vval.v_string = NULL; } return; } ! else if (v->di_tv.v_type == VAR_NUMBER) { ! v->di_tv.vval.v_number = tv_get_number(tv); if (STRCMP(varname, "searchforward") == 0) ! set_search_direction(v->di_tv.vval.v_number ? '/' : '?'); #ifdef FEAT_SEARCH_EXTRA else if (STRCMP(varname, "hlsearch") == 0) { ! no_hlsearch = !v->di_tv.vval.v_number; redraw_all_later(SOME_VALID); } #endif return; } ! else if (v->di_tv.v_type != tv->v_type) { semsg(_("E963: setting %s to value with wrong type"), name); return; } } ! clear_tv(&v->di_tv); } else // add a new variable { --- 2761,2852 ---- semsg(_(e_illvar), name); return; } ! is_script_local = ht == get_script_local_ht(); ! ! di = find_var_in_ht(ht, 0, varname, TRUE); // Search in parent scope which is possible to reference from lambda ! if (di == NULL) ! di = find_var_in_scoped_ht(name, TRUE); if ((tv->v_type == VAR_FUNC || tv->v_type == VAR_PARTIAL) ! && var_check_func_name(name, di == NULL)) return; ! if (di != NULL) { ! if ((di->di_flags & DI_FLAGS_RELOAD) == 0) { ! if (flags & LET_IS_CONST) ! { ! emsg(_(e_cannot_mod)); ! return; ! } ! ! if (var_check_ro(di->di_flags, name, FALSE) ! || var_check_lock(di->di_tv.v_lock, name, FALSE)) ! return; ! ! if ((flags & LET_NO_COMMAND) == 0 ! && is_script_local ! && current_sctx.sc_version == SCRIPT_VERSION_VIM9) ! { ! semsg(_("E1041: Redefining script item %s"), name); ! return; ! } } + else + // can only redefine once + di->di_flags &= ~DI_FLAGS_RELOAD; // existing variable, need to clear the value ! // Handle setting internal di: variables separately where needed to // prevent changing the type. if (ht == &vimvarht) { ! if (di->di_tv.v_type == VAR_STRING) { ! VIM_CLEAR(di->di_tv.vval.v_string); if (copy || tv->v_type != VAR_STRING) { char_u *val = tv_get_string(tv); // Careful: when assigning to v:errmsg and tv_get_string() // causes an error message the variable will alrady be set. ! if (di->di_tv.vval.v_string == NULL) ! di->di_tv.vval.v_string = vim_strsave(val); } else { // Take over the string to avoid an extra alloc/free. ! di->di_tv.vval.v_string = tv->vval.v_string; tv->vval.v_string = NULL; } return; } ! else if (di->di_tv.v_type == VAR_NUMBER) { ! di->di_tv.vval.v_number = tv_get_number(tv); if (STRCMP(varname, "searchforward") == 0) ! set_search_direction(di->di_tv.vval.v_number ? '/' : '?'); #ifdef FEAT_SEARCH_EXTRA else if (STRCMP(varname, "hlsearch") == 0) { ! no_hlsearch = !di->di_tv.vval.v_number; redraw_all_later(SOME_VALID); } #endif return; } ! else if (di->di_tv.v_type != tv->v_type) { semsg(_("E963: setting %s to value with wrong type"), name); return; } } ! clear_tv(&di->di_tv); } else // add a new variable { *************** *** 2725,2755 **** if (!valid_varname(varname)) return; ! v = alloc(sizeof(dictitem_T) + STRLEN(varname)); ! if (v == NULL) return; ! STRCPY(v->di_key, varname); ! if (hash_add(ht, DI2HIKEY(v)) == FAIL) { ! vim_free(v); return; } ! v->di_flags = DI_FLAGS_ALLOC; ! if (is_const) ! v->di_flags |= DI_FLAGS_LOCK; } if (copy || tv->v_type == VAR_NUMBER || tv->v_type == VAR_FLOAT) ! copy_tv(tv, &v->di_tv); else { ! v->di_tv = *tv; ! v->di_tv.v_lock = 0; init_tv(tv); } ! if (is_const) ! v->di_tv.v_lock |= VAR_LOCKED; } /* --- 2861,2913 ---- if (!valid_varname(varname)) return; ! di = alloc(sizeof(dictitem_T) + STRLEN(varname)); ! if (di == NULL) return; ! STRCPY(di->di_key, varname); ! if (hash_add(ht, DI2HIKEY(di)) == FAIL) { ! vim_free(di); return; } ! di->di_flags = DI_FLAGS_ALLOC; ! if (flags & LET_IS_CONST) ! di->di_flags |= DI_FLAGS_LOCK; ! ! if (is_script_local && current_sctx.sc_version == SCRIPT_VERSION_VIM9) ! { ! scriptitem_T *si = &SCRIPT_ITEM(current_sctx.sc_sid); ! ! // Store a pointer to the typval_T, so that it can be found by ! // index instead of using a hastab lookup. ! if (ga_grow(&si->sn_var_vals, 1) == OK) ! { ! svar_T *sv = ((svar_T *)si->sn_var_vals.ga_data) ! + si->sn_var_vals.ga_len; ! sv->sv_name = di->di_key; ! sv->sv_tv = &di->di_tv; ! sv->sv_type = type == NULL ? &t_any : type; ! sv->sv_const = (flags & LET_IS_CONST); ! sv->sv_export = is_export; ! ++si->sn_var_vals.ga_len; ! ! // let ex_export() know the export worked. ! is_export = FALSE; ! } ! } } if (copy || tv->v_type == VAR_NUMBER || tv->v_type == VAR_FLOAT) ! copy_tv(tv, &di->di_tv); else { ! di->di_tv = *tv; ! di->di_tv.v_lock = 0; init_tv(tv); } ! if (flags & LET_IS_CONST) ! di->di_tv.v_lock |= VAR_LOCKED; } /* *************** *** 3130,3138 **** tv.v_type = VAR_STRING; tv.vval.v_string = (char_u *)""; if (append) ! set_var_lval(redir_lval, redir_endp, &tv, TRUE, FALSE, (char_u *)"."); else ! set_var_lval(redir_lval, redir_endp, &tv, TRUE, FALSE, (char_u *)"="); clear_lval(redir_lval); err = did_emsg; did_emsg |= save_emsg; --- 3288,3296 ---- tv.v_type = VAR_STRING; tv.vval.v_string = (char_u *)""; if (append) ! set_var_lval(redir_lval, redir_endp, &tv, TRUE, 0, (char_u *)"."); else ! set_var_lval(redir_lval, redir_endp, &tv, TRUE, 0, (char_u *)"="); clear_lval(redir_lval); err = did_emsg; did_emsg |= save_emsg; *************** *** 3205,3211 **** redir_endp = get_lval(redir_varname, NULL, redir_lval, FALSE, FALSE, 0, FNE_CHECK_START); if (redir_endp != NULL && redir_lval->ll_name != NULL) ! set_var_lval(redir_lval, redir_endp, &tv, FALSE, FALSE, (char_u *)"."); clear_lval(redir_lval); } --- 3363,3369 ---- redir_endp = get_lval(redir_varname, NULL, redir_lval, FALSE, FALSE, 0, FNE_CHECK_START); if (redir_endp != NULL && redir_lval->ll_name != NULL) ! set_var_lval(redir_lval, redir_endp, &tv, FALSE, 0, (char_u *)"."); clear_lval(redir_lval); } *** ../vim-8.2.0148/src/ex_cmdidxs.h 2019-08-16 22:40:07.000000000 +0200 --- src/ex_cmdidxs.h 2020-01-26 15:30:08.617766613 +0100 *************** *** 9,36 **** /* b */ 19, /* c */ 42, /* d */ 108, ! /* e */ 130, ! /* f */ 151, ! /* g */ 167, ! /* h */ 173, ! /* i */ 182, ! /* j */ 200, ! /* k */ 202, ! /* l */ 207, ! /* m */ 269, ! /* n */ 287, ! /* o */ 307, ! /* p */ 319, ! /* q */ 358, ! /* r */ 361, ! /* s */ 381, ! /* t */ 450, ! /* u */ 495, ! /* v */ 506, ! /* w */ 524, ! /* x */ 538, ! /* y */ 548, ! /* z */ 549 }; /* --- 9,36 ---- /* b */ 19, /* c */ 42, /* d */ 108, ! /* e */ 132, ! /* f */ 155, ! /* g */ 171, ! /* h */ 177, ! /* i */ 186, ! /* j */ 205, ! /* k */ 207, ! /* l */ 212, ! /* m */ 274, ! /* n */ 292, ! /* o */ 312, ! /* p */ 324, ! /* q */ 363, ! /* r */ 366, ! /* s */ 386, ! /* t */ 455, ! /* u */ 500, ! /* v */ 511, ! /* w */ 530, ! /* x */ 544, ! /* y */ 554, ! /* z */ 555 }; /* *************** *** 44,55 **** /* a */ { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 5, 6, 0, 0, 0, 7, 15, 0, 16, 0, 0, 0, 0, 0 }, /* b */ { 2, 0, 0, 4, 5, 7, 0, 0, 0, 0, 0, 8, 9, 10, 11, 12, 0, 13, 0, 0, 0, 0, 22, 0, 0, 0 }, /* c */ { 3, 12, 16, 18, 20, 22, 25, 0, 0, 0, 0, 33, 37, 40, 46, 56, 58, 59, 60, 0, 62, 0, 65, 0, 0, 0 }, ! /* d */ { 0, 0, 0, 0, 0, 0, 0, 0, 6, 15, 0, 16, 0, 0, 17, 0, 0, 19, 20, 0, 0, 0, 0, 0, 0, 0 }, ! /* e */ { 1, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 7, 9, 10, 0, 0, 0, 0, 0, 0, 0, 16, 0, 17, 0, 0 }, /* f */ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 15, 0, 0, 0, 0, 0 }, /* g */ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 2, 0, 0, 4, 5, 0, 0, 0, 0 }, /* h */ { 5, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, ! /* i */ { 1, 0, 0, 0, 0, 3, 0, 0, 0, 4, 0, 5, 6, 0, 0, 0, 0, 0, 13, 0, 15, 0, 0, 0, 0, 0 }, /* j */ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0 }, /* k */ { 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, /* l */ { 3, 11, 15, 19, 20, 24, 27, 32, 0, 0, 0, 34, 37, 40, 44, 50, 0, 52, 61, 53, 54, 58, 60, 0, 0, 0 }, --- 44,55 ---- /* a */ { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 5, 6, 0, 0, 0, 7, 15, 0, 16, 0, 0, 0, 0, 0 }, /* b */ { 2, 0, 0, 4, 5, 7, 0, 0, 0, 0, 0, 8, 9, 10, 11, 12, 0, 13, 0, 0, 0, 0, 22, 0, 0, 0 }, /* c */ { 3, 12, 16, 18, 20, 22, 25, 0, 0, 0, 0, 33, 37, 40, 46, 56, 58, 59, 60, 0, 62, 0, 65, 0, 0, 0 }, ! /* d */ { 0, 0, 0, 0, 0, 0, 0, 0, 7, 17, 0, 18, 0, 0, 19, 0, 0, 21, 22, 0, 0, 0, 0, 0, 0, 0 }, ! /* e */ { 1, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 7, 9, 10, 0, 0, 0, 0, 0, 0, 0, 17, 0, 18, 0, 0 }, /* f */ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 15, 0, 0, 0, 0, 0 }, /* g */ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 2, 0, 0, 4, 5, 0, 0, 0, 0 }, /* h */ { 5, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, ! /* i */ { 1, 0, 0, 0, 0, 3, 0, 0, 0, 4, 0, 5, 6, 0, 0, 0, 0, 0, 14, 0, 16, 0, 0, 0, 0, 0 }, /* j */ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0 }, /* k */ { 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, /* l */ { 3, 11, 15, 19, 20, 24, 27, 32, 0, 0, 0, 34, 37, 40, 44, 50, 0, 52, 61, 53, 54, 58, 60, 0, 0, 0 }, *************** *** 62,72 **** /* s */ { 2, 6, 15, 0, 19, 23, 0, 25, 26, 0, 0, 29, 31, 35, 39, 41, 0, 50, 0, 51, 0, 63, 64, 0, 65, 0 }, /* t */ { 2, 0, 19, 0, 24, 26, 0, 27, 0, 28, 0, 29, 33, 36, 38, 39, 0, 40, 42, 0, 43, 0, 0, 0, 0, 0 }, /* u */ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, ! /* v */ { 0, 0, 0, 0, 1, 0, 0, 0, 4, 0, 0, 0, 9, 12, 0, 0, 0, 0, 15, 0, 16, 0, 0, 0, 0, 0 }, /* w */ { 2, 0, 0, 0, 0, 0, 0, 3, 4, 0, 0, 0, 0, 8, 0, 9, 10, 0, 0, 0, 12, 13, 0, 0, 0, 0 }, /* x */ { 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 5, 0, 0, 0, 7, 0, 0, 8, 0, 0, 0, 0, 0 }, /* y */ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, /* z */ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } }; ! static const int command_count = 562; --- 62,72 ---- /* s */ { 2, 6, 15, 0, 19, 23, 0, 25, 26, 0, 0, 29, 31, 35, 39, 41, 0, 50, 0, 51, 0, 63, 64, 0, 65, 0 }, /* t */ { 2, 0, 19, 0, 24, 26, 0, 27, 0, 28, 0, 29, 33, 36, 38, 39, 0, 40, 42, 0, 43, 0, 0, 0, 0, 0 }, /* u */ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, ! /* v */ { 0, 0, 0, 0, 1, 0, 0, 0, 4, 0, 0, 0, 10, 13, 0, 0, 0, 0, 16, 0, 17, 0, 0, 0, 0, 0 }, /* w */ { 2, 0, 0, 0, 0, 0, 0, 3, 4, 0, 0, 0, 0, 8, 0, 9, 10, 0, 0, 0, 12, 13, 0, 0, 0, 0 }, /* x */ { 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 5, 0, 0, 0, 7, 0, 0, 8, 0, 0, 0, 0, 0 }, /* y */ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, /* z */ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } }; ! static const int command_count = 568; *** ../vim-8.2.0148/src/ex_cmds.h 2020-01-18 15:53:15.950799342 +0100 --- src/ex_cmds.h 2020-01-26 15:30:08.617766613 +0100 *************** *** 442,447 **** --- 442,450 ---- EXCMD(CMD_debuggreedy, "debuggreedy", ex_debuggreedy, EX_RANGE|EX_ZEROR|EX_TRLBAR|EX_CMDWIN, ADDR_OTHER), + EXCMD(CMD_def, "def", ex_function, + EX_EXTRA|EX_BANG|EX_SBOXOK|EX_CMDWIN, + ADDR_NONE), EXCMD(CMD_delcommand, "delcommand", ex_delcommand, EX_NEEDARG|EX_WORD1|EX_TRLBAR|EX_CMDWIN, ADDR_NONE), *************** *** 475,480 **** --- 478,486 ---- EXCMD(CMD_digraphs, "digraphs", ex_digraphs, EX_BANG|EX_EXTRA|EX_TRLBAR|EX_CMDWIN, ADDR_NONE), + EXCMD(CMD_disassemble, "disassemble", ex_disassemble, + EX_EXTRA|EX_TRLBAR|EX_CMDWIN, + ADDR_NONE), EXCMD(CMD_djump, "djump", ex_findpat, EX_BANG|EX_RANGE|EX_DFLALL|EX_WHOLEFOLD|EX_EXTRA, ADDR_LINES), *************** *** 529,534 **** --- 535,543 ---- EXCMD(CMD_endif, "endif", ex_endif, EX_TRLBAR|EX_SBOXOK|EX_CMDWIN, ADDR_NONE), + EXCMD(CMD_enddef, "enddef", ex_endfunction, + EX_TRLBAR|EX_CMDWIN, + ADDR_NONE), EXCMD(CMD_endfunction, "endfunction", ex_endfunction, EX_TRLBAR|EX_CMDWIN, ADDR_NONE), *************** *** 556,561 **** --- 565,573 ---- EXCMD(CMD_exit, "exit", ex_exit, EX_RANGE|EX_WHOLEFOLD|EX_BANG|EX_FILE1|EX_ARGOPT|EX_DFLALL|EX_TRLBAR|EX_CMDWIN, ADDR_LINES), + EXCMD(CMD_export, "export", ex_export, + EX_EXTRA|EX_NOTRLCOM, + ADDR_NONE), EXCMD(CMD_exusage, "exusage", ex_exusage, EX_TRLBAR, ADDR_NONE), *************** *** 679,684 **** --- 691,699 ---- EXCMD(CMD_imenu, "imenu", ex_menu, EX_RANGE|EX_ZEROR|EX_EXTRA|EX_TRLBAR|EX_NOTRLCOM|EX_CTRLV|EX_CMDWIN, ADDR_OTHER), + EXCMD(CMD_import, "import", ex_import, + EX_EXTRA|EX_NOTRLCOM, + ADDR_NONE), EXCMD(CMD_inoremap, "inoremap", ex_map, EX_EXTRA|EX_TRLBAR|EX_NOTRLCOM|EX_CTRLV|EX_CMDWIN, ADDR_NONE), *************** *** 1648,1653 **** --- 1663,1671 ---- EXCMD(CMD_vimgrepadd, "vimgrepadd", ex_vimgrep, EX_RANGE|EX_BANG|EX_NEEDARG|EX_EXTRA|EX_NOTRLCOM|EX_TRLBAR|EX_XFILE, ADDR_OTHER), + EXCMD(CMD_vim9script, "vim9script", ex_vim9script, + 0, + ADDR_NONE), EXCMD(CMD_viusage, "viusage", ex_viusage, EX_TRLBAR, ADDR_NONE), *** ../vim-8.2.0148/src/ex_docmd.c 2020-01-18 15:53:15.950799342 +0100 --- src/ex_docmd.c 2020-01-26 15:38:56.075543532 +0100 *************** *** 27,33 **** #endif static void free_cmdmod(void); static void append_command(char_u *cmd); - static char_u *find_command(exarg_T *eap, int *full); #ifndef FEAT_MENU # define ex_emenu ex_ni --- 27,32 ---- *************** *** 275,280 **** --- 274,280 ---- # define ex_debug ex_ni # define ex_debuggreedy ex_ni # define ex_delfunction ex_ni + # define ex_disassemble ex_ni # define ex_echo ex_ni # define ex_echohl ex_ni # define ex_else ex_ni *************** *** 300,306 **** --- 300,309 ---- # define ex_try ex_ni # define ex_unlet ex_ni # define ex_unlockvar ex_ni + # define ex_vim9script ex_ni # define ex_while ex_ni + # define ex_import ex_ni + # define ex_export ex_ni #endif #ifndef FEAT_SESSION # define ex_loadview ex_ni *************** *** 1708,1714 **** ea.cmd = skip_range(ea.cmd, NULL); if (*ea.cmd == '*' && vim_strchr(p_cpo, CPO_STAR) == NULL) ea.cmd = skipwhite(ea.cmd + 1); ! p = find_command(&ea, NULL); #ifdef FEAT_EVAL # ifdef FEAT_PROFILE --- 1711,1723 ---- ea.cmd = skip_range(ea.cmd, NULL); if (*ea.cmd == '*' && vim_strchr(p_cpo, CPO_STAR) == NULL) ea.cmd = skipwhite(ea.cmd + 1); ! ! #ifdef FEAT_EVAL ! if (current_sctx.sc_version == SCRIPT_VERSION_VIM9) ! p = find_ex_command(&ea, NULL, lookup_scriptvar, NULL); ! else ! #endif ! p = find_ex_command(&ea, NULL, NULL, NULL); #ifdef FEAT_EVAL # ifdef FEAT_PROFILE *************** *** 1876,1882 **** #ifdef FEAT_EVAL && !aborting() #endif ! ) ? find_command(&ea, NULL) : ea.cmd; } if (p == NULL) --- 1885,1891 ---- #ifdef FEAT_EVAL && !aborting() #endif ! ) ? find_ex_command(&ea, NULL, NULL, NULL) : ea.cmd; } if (p == NULL) *************** *** 2485,2490 **** --- 2494,2503 ---- } #ifdef FEAT_EVAL + // Set flag that any command was executed, used by ex_vim9script(). + if (getline_equal(ea.getline, ea.cookie, getsourceline)) + SCRIPT_ITEM(current_sctx.sc_sid).sn_had_command = TRUE; + /* * If the command just executed called do_cmdline(), any throw or ":return" * or ":finish" encountered there must also check the cstack of the still *************** *** 3108,3122 **** * Start of the name can be found at eap->cmd. * Sets eap->cmdidx and returns a pointer to char after the command name. * "full" is set to TRUE if the whole command name matched. * Returns NULL for an ambiguous user command. */ ! static char_u * ! find_command(exarg_T *eap, int *full UNUSED) { int len; char_u *p; int i; /* * Isolate the command and search for it in the command table. * Exceptions: --- 3121,3184 ---- * Start of the name can be found at eap->cmd. * Sets eap->cmdidx and returns a pointer to char after the command name. * "full" is set to TRUE if the whole command name matched. + * + * If "lookup" is not NULL recognize expression without "eval" or "call" and + * assignment without "let". Sets eap->cmdidx to the command while returning + * "eap->cmd". + * * Returns NULL for an ambiguous user command. */ ! char_u * ! find_ex_command( ! exarg_T *eap, ! int *full UNUSED, ! int (*lookup)(char_u *, size_t, cctx_T *) UNUSED, ! cctx_T *cctx UNUSED) { int len; char_u *p; int i; + #ifdef FEAT_EVAL + /* + * Recognize a Vim9 script function/method call and assignment: + * "lvar = value", "lvar(arg)", "[1, 2 3]->Func()" + */ + if (lookup != NULL && (p = to_name_const_end(eap->cmd)) > eap->cmd + && *p != NUL) + { + int oplen; + int heredoc; + + // "funcname(" is always a function call. + // "varname[]" is an expression. + // "g:varname" is an expression. + // "varname->expr" is an expression. + if (*p == '(' + || *p == '[' + || p[1] == ':' + || (*p == '-' && p[1] == '>')) + { + eap->cmdidx = CMD_eval; + return eap->cmd; + } + + oplen = assignment_len(skipwhite(p), &heredoc); + if (oplen > 0) + { + // Recognize an assignment if we recognize the variable name: + // "g:var = expr" + // "var = expr" where "var" is a local var name. + if (((p - eap->cmd) > 2 && eap->cmd[1] == ':') + || lookup(eap->cmd, p - eap->cmd, cctx) >= 0) + { + eap->cmdidx = CMD_let; + return eap->cmd; + } + } + } + #endif + /* * Isolate the command and search for it in the command table. * Exceptions: *************** *** 3149,3156 **** --- 3211,3227 ---- ++p; // for python 3.x support ":py3", ":python3", ":py3file", etc. if (eap->cmd[0] == 'p' && eap->cmd[1] == 'y') + { while (ASCII_ISALNUM(*p)) ++p; + } + else if (*p == '9' && STRNCMP("vim9", eap->cmd, 4) == 0) + { + // include "9" for "vim9script" + ++p; + while (ASCII_ISALPHA(*p)) + ++p; + } // check for non-alpha command if (p == eap->cmd && vim_strchr((char_u *)"@*!=><&~#", *p) != NULL) *************** *** 3307,3313 **** // For ":2match" and ":3match" we need to skip the number. ea.cmd = (*name == '2' || *name == '3') ? name + 1 : name; ea.cmdidx = (cmdidx_T)0; ! p = find_command(&ea, &full); if (p == NULL) return 3; if (vim_isdigit(*name) && ea.cmdidx != CMD_match) --- 3378,3384 ---- // For ":2match" and ":3match" we need to skip the number. ea.cmd = (*name == '2' || *name == '3') ? name + 1 : name; ea.cmdidx = (cmdidx_T)0; ! p = find_ex_command(&ea, &full, NULL, NULL); if (p == NULL) return 3; if (vim_isdigit(*name) && ea.cmdidx != CMD_match) *************** *** 8558,8564 **** } #endif ! #ifdef FEAT_QUICKFIX /* * Returns TRUE if the supplied Ex cmdidx is for a location list command * instead of a quickfix command. --- 8629,8635 ---- } #endif ! #if defined(FEAT_QUICKFIX) || defined(PROTO) /* * Returns TRUE if the supplied Ex cmdidx is for a location list command * instead of a quickfix command. *** ../vim-8.2.0148/src/ex_eval.c 2019-12-29 23:04:20.290639897 +0100 --- src/ex_eval.c 2020-01-26 15:30:08.617766613 +0100 *************** *** 15,21 **** #if defined(FEAT_EVAL) || defined(PROTO) - static int throw_exception(void *, except_type_T, char_u *); static char *get_end_emsg(cstack_T *cstack); /* --- 15,20 ---- *************** *** 498,504 **** * user or interrupt exception, or points to a message list in case of an * error exception. */ ! static int throw_exception(void *value, except_type_T type, char_u *cmdname) { except_T *excp; --- 497,503 ---- * user or interrupt exception, or points to a message list in case of an * error exception. */ ! int throw_exception(void *value, except_type_T type, char_u *cmdname) { except_T *excp; *************** *** 649,655 **** /* * Put an exception on the caught stack. */ ! static void catch_exception(except_T *excp) { excp->caught = caught_stack; --- 648,654 ---- /* * Put an exception on the caught stack. */ ! void catch_exception(except_T *excp) { excp->caught = caught_stack; *************** *** 932,938 **** if (eap->cstack->cs_idx < 0 || (eap->cstack->cs_flags[eap->cstack->cs_idx] & (CSF_WHILE | CSF_FOR | CSF_TRY))) ! eap->errmsg = N_("E580: :endif without :if"); else { /* --- 931,937 ---- if (eap->cstack->cs_idx < 0 || (eap->cstack->cs_flags[eap->cstack->cs_idx] & (CSF_WHILE | CSF_FOR | CSF_TRY))) ! eap->errmsg = N_(e_endif_without_if); else { /* *************** *** 976,985 **** { if (eap->cmdidx == CMD_else) { ! eap->errmsg = N_("E581: :else without :if"); return; } ! eap->errmsg = N_("E582: :elseif without :if"); skip = TRUE; } else if (cstack->cs_flags[cstack->cs_idx] & CSF_ELSE) --- 975,984 ---- { if (eap->cmdidx == CMD_else) { ! eap->errmsg = N_(e_else_without_if); return; } ! eap->errmsg = N_(e_elseif_without_if); skip = TRUE; } else if (cstack->cs_flags[cstack->cs_idx] & CSF_ELSE) *************** *** 1152,1158 **** cstack_T *cstack = eap->cstack; if (cstack->cs_looplevel <= 0 || cstack->cs_idx < 0) ! eap->errmsg = N_("E586: :continue without :while or :for"); else { // Try to find the matching ":while". This might stop at a try --- 1151,1157 ---- cstack_T *cstack = eap->cstack; if (cstack->cs_looplevel <= 0 || cstack->cs_idx < 0) ! eap->errmsg = N_(e_continue); else { // Try to find the matching ":while". This might stop at a try *************** *** 1190,1196 **** cstack_T *cstack = eap->cstack; if (cstack->cs_looplevel <= 0 || cstack->cs_idx < 0) ! eap->errmsg = N_("E587: :break without :while or :for"); else { // Inactivate conditionals until the matching ":while" or a try --- 1189,1195 ---- cstack_T *cstack = eap->cstack; if (cstack->cs_looplevel <= 0 || cstack->cs_idx < 0) ! eap->errmsg = N_(e_break); else { // Inactivate conditionals until the matching ":while" or a try *************** *** 1492,1498 **** if (cstack->cs_trylevel <= 0 || cstack->cs_idx < 0) { ! eap->errmsg = N_("E603: :catch without :try"); give_up = TRUE; } else --- 1491,1497 ---- if (cstack->cs_trylevel <= 0 || cstack->cs_idx < 0) { ! eap->errmsg = e_catch; give_up = TRUE; } else *************** *** 1648,1654 **** cstack_T *cstack = eap->cstack; if (cstack->cs_trylevel <= 0 || cstack->cs_idx < 0) ! eap->errmsg = N_("E606: :finally without :try"); else { if (!(cstack->cs_flags[cstack->cs_idx] & CSF_TRY)) --- 1647,1653 ---- cstack_T *cstack = eap->cstack; if (cstack->cs_trylevel <= 0 || cstack->cs_idx < 0) ! eap->errmsg = e_finally; else { if (!(cstack->cs_flags[cstack->cs_idx] & CSF_TRY)) *************** *** 1668,1674 **** if (cstack->cs_flags[idx] & CSF_FINALLY) { // Give up for a multiple ":finally" and ignore it. ! eap->errmsg = N_("E607: multiple :finally"); return; } rewind_conditionals(cstack, idx, CSF_WHILE | CSF_FOR, --- 1667,1673 ---- if (cstack->cs_flags[idx] & CSF_FINALLY) { // Give up for a multiple ":finally" and ignore it. ! eap->errmsg = e_finally_dup; return; } rewind_conditionals(cstack, idx, CSF_WHILE | CSF_FOR, *************** *** 1777,1783 **** cstack_T *cstack = eap->cstack; if (cstack->cs_trylevel <= 0 || cstack->cs_idx < 0) ! eap->errmsg = N_("E602: :endtry without :try"); else { /* --- 1776,1782 ---- cstack_T *cstack = eap->cstack; if (cstack->cs_trylevel <= 0 || cstack->cs_idx < 0) ! eap->errmsg = e_no_endtry; else { /* *** ../vim-8.2.0148/src/filepath.c 2019-12-23 18:18:36.616536002 +0100 --- src/filepath.c 2020-01-26 15:30:08.617766613 +0100 *************** *** 1892,1897 **** --- 1892,1898 ---- list = argvars[0].vval.v_list; if (list == NULL) return; + range_list_materialize(list); for (li = list->lv_first; li != NULL; li = li->li_next) if (tv_get_string_chk(&li->li_tv) == NULL) return; *** ../vim-8.2.0148/src/globals.h 2020-01-08 19:32:14.966527459 +0100 --- src/globals.h 2020-01-26 15:30:08.617766613 +0100 *************** *** 286,293 **** EXTERN int do_profiling INIT(= PROF_NONE); // PROF_ values # endif EXTERN garray_T script_items INIT5(0, 0, sizeof(scriptitem_T), 4, NULL); ! #define SCRIPT_ITEM(id) (((scriptitem_T *)script_items.ga_data)[(id) - 1]) ! #define FUNCLINE(fp, j) ((char_u **)(fp->uf_lines.ga_data))[j] /* * The exception currently being thrown. Used to pass an exception to --- 286,296 ---- EXTERN int do_profiling INIT(= PROF_NONE); // PROF_ values # endif EXTERN garray_T script_items INIT5(0, 0, sizeof(scriptitem_T), 4, NULL); ! # define SCRIPT_ITEM(id) (((scriptitem_T *)script_items.ga_data)[(id) - 1]) ! # define SCRIPT_SV(id) (SCRIPT_ITEM(id).sn_vars) ! # define SCRIPT_VARS(id) (SCRIPT_SV(id)->sv_dict.dv_hashtab) ! ! # define FUNCLINE(fp, j) ((char_u **)(fp->uf_lines.ga_data))[j] /* * The exception currently being thrown. Used to pass an exception to *************** *** 359,367 **** */ EXTERN except_T *caught_stack INIT(= NULL); - #endif - - #ifdef FEAT_EVAL /* * Garbage collection can only take place when we are sure there are no Lists * or Dictionaries being used internally. This is flagged with --- 362,367 ---- *************** *** 376,381 **** --- 376,414 ---- // Script CTX being sourced or was sourced to define the current function. EXTERN sctx_T current_sctx INIT4(0, 0, 0, 0); + + + // Commonly used types. + EXTERN type_T t_any INIT4(VAR_UNKNOWN, 0, NULL, NULL); + EXTERN type_T t_void INIT4(VAR_VOID, 0, NULL, NULL); + EXTERN type_T t_bool INIT4(VAR_BOOL, 0, NULL, NULL); + EXTERN type_T t_special INIT4(VAR_SPECIAL, 0, NULL, NULL); + EXTERN type_T t_number INIT4(VAR_NUMBER, 0, NULL, NULL); + #ifdef FEAT_FLOAT + EXTERN type_T t_float INIT4(VAR_FLOAT, 0, NULL, NULL); + #endif + EXTERN type_T t_string INIT4(VAR_STRING, 0, NULL, NULL); + EXTERN type_T t_blob INIT4(VAR_BLOB, 0, NULL, NULL); + EXTERN type_T t_job INIT4(VAR_JOB, 0, NULL, NULL); + EXTERN type_T t_channel INIT4(VAR_CHANNEL, 0, NULL, NULL); + + EXTERN type_T t_func_void INIT4(VAR_FUNC, -1, &t_void, NULL); + EXTERN type_T t_func_any INIT4(VAR_FUNC, -1, &t_any, NULL); + + EXTERN type_T t_partial_void INIT4(VAR_PARTIAL, -1, &t_void, NULL); + EXTERN type_T t_partial_any INIT4(VAR_PARTIAL, -1, &t_any, NULL); + + EXTERN type_T t_list_any INIT4(VAR_LIST, 0, &t_any, NULL); + EXTERN type_T t_dict_any INIT4(VAR_DICT, 0, &t_any, NULL); + + EXTERN type_T t_list_number INIT4(VAR_LIST, 0, &t_number, NULL); + EXTERN type_T t_list_string INIT4(VAR_LIST, 0, &t_string, NULL); + EXTERN type_T t_list_dict_any INIT4(VAR_LIST, 0, &t_dict_any, NULL); + + EXTERN type_T t_dict_number INIT4(VAR_DICT, 0, &t_number, NULL); + EXTERN type_T t_dict_string INIT4(VAR_DICT, 0, &t_string, NULL); + + #endif EXTERN int did_source_packages INIT(= FALSE); *************** *** 1038,1043 **** --- 1071,1078 ---- EXTERN cmdmod_T cmdmod; // Ex command modifiers + EXTERN int is_export INIT(= FALSE); // :export {cmd} + EXTERN int msg_silent INIT(= 0); // don't print messages EXTERN int emsg_silent INIT(= 0); // don't print error messages EXTERN int emsg_noredir INIT(= 0); // don't redirect error messages *************** *** 1465,1473 **** EXTERN char e_curdir[] INIT(= N_("E12: Command not allowed from exrc/vimrc in current dir or tag search")); #ifdef FEAT_EVAL EXTERN char e_endif[] INIT(= N_("E171: Missing :endif")); ! EXTERN char e_endtry[] INIT(= N_("E600: Missing :endtry")); EXTERN char e_endwhile[] INIT(= N_("E170: Missing :endwhile")); ! EXTERN char e_endfor[] INIT(= N_("E170: Missing :endfor")); EXTERN char e_while[] INIT(= N_("E588: :endwhile without :while")); EXTERN char e_for[] INIT(= N_("E588: :endfor without :for")); #endif --- 1500,1512 ---- EXTERN char e_curdir[] INIT(= N_("E12: Command not allowed from exrc/vimrc in current dir or tag search")); #ifdef FEAT_EVAL EXTERN char e_endif[] INIT(= N_("E171: Missing :endif")); ! EXTERN char e_catch[] INIT(= N_("E603: :catch without :try")); ! EXTERN char e_finally[] INIT(= N_("E606: :finally without :try")); ! EXTERN char e_finally_dup[] INIT(= N_("E607: multiple :finally")); ! EXTERN char e_endtry[] INIT(= N_("E600: Missing :endtry")); ! EXTERN char e_no_endtry[] INIT(= N_("E602: :endtry without :try")); EXTERN char e_endwhile[] INIT(= N_("E170: Missing :endwhile")); ! EXTERN char e_endfor[] INIT(= N_("E170: Missing :endfor")); EXTERN char e_while[] INIT(= N_("E588: :endwhile without :while")); EXTERN char e_for[] INIT(= N_("E588: :endfor without :for")); #endif *************** *** 1556,1562 **** EXTERN char e_notopen[] INIT(= N_("E484: Can't open file %s")); EXTERN char e_notread[] INIT(= N_("E485: Can't read file %s")); EXTERN char e_null[] INIT(= N_("E38: Null argument")); ! #if defined(FEAT_DIGRAPHS) || defined(FEAT_TIMERS) EXTERN char e_number_exp[] INIT(= N_("E39: Number expected")); #endif #ifdef FEAT_QUICKFIX --- 1595,1601 ---- EXTERN char e_notopen[] INIT(= N_("E484: Can't open file %s")); EXTERN char e_notread[] INIT(= N_("E485: Can't read file %s")); EXTERN char e_null[] INIT(= N_("E38: Null argument")); ! #if defined(FEAT_DIGRAPHS) || defined(FEAT_TIMERS) || defined(FEAT_EVAL) EXTERN char e_number_exp[] INIT(= N_("E39: Number expected")); #endif #ifdef FEAT_QUICKFIX *************** *** 1575,1584 **** #ifdef FEAT_QUICKFIX EXTERN char e_quickfix[] INIT(= N_("E42: No Errors")); ! EXTERN char e_loclist[] INIT(= N_("E776: No location list")); #endif ! EXTERN char e_re_damg[] INIT(= N_("E43: Damaged match string")); ! EXTERN char e_re_corr[] INIT(= N_("E44: Corrupted regexp program")); EXTERN char e_readonly[] INIT(= N_("E45: 'readonly' option is set (add ! to override)")); #ifdef FEAT_EVAL EXTERN char e_undefvar[] INIT(= N_("E121: Undefined variable: %s")); --- 1614,1623 ---- #ifdef FEAT_QUICKFIX EXTERN char e_quickfix[] INIT(= N_("E42: No Errors")); ! EXTERN char e_loclist[] INIT(= N_("E776: No location list")); #endif ! EXTERN char e_re_damg[] INIT(= N_("E43: Damaged match string")); ! EXTERN char e_re_corr[] INIT(= N_("E44: Corrupted regexp program")); EXTERN char e_readonly[] INIT(= N_("E45: 'readonly' option is set (add ! to override)")); #ifdef FEAT_EVAL EXTERN char e_undefvar[] INIT(= N_("E121: Undefined variable: %s")); *************** *** 1589,1605 **** EXTERN char e_readonlysbx[] INIT(= N_("E794: Cannot set variable in the sandbox: \"%s\"")); EXTERN char e_stringreq[] INIT(= N_("E928: String required")); EXTERN char e_emptykey[] INIT(= N_("E713: Cannot use empty key for Dictionary")); ! EXTERN char e_dictreq[] INIT(= N_("E715: Dictionary required")); ! EXTERN char e_listidx[] INIT(= N_("E684: list index out of range: %ld")); ! EXTERN char e_blobidx[] INIT(= N_("E979: Blob index out of range: %ld")); EXTERN char e_invalblob[] INIT(= N_("E978: Invalid operation for Blob")); EXTERN char e_toomanyarg[] INIT(= N_("E118: Too many arguments for function: %s")); EXTERN char e_dictkey[] INIT(= N_("E716: Key not present in Dictionary: %s")); ! EXTERN char e_listreq[] INIT(= N_("E714: List required")); EXTERN char e_listblobreq[] INIT(= N_("E897: List or Blob required")); EXTERN char e_listdictarg[] INIT(= N_("E712: Argument of %s must be a List or Dictionary")); EXTERN char e_listdictblobarg[] INIT(= N_("E896: Argument of %s must be a List, Dictionary or Blob")); EXTERN char e_inval_string[] INIT(= N_("E908: using an invalid value as a String")); #endif #ifdef FEAT_QUICKFIX EXTERN char e_readerrf[] INIT(= N_("E47: Error while reading errorfile")); --- 1628,1650 ---- EXTERN char e_readonlysbx[] INIT(= N_("E794: Cannot set variable in the sandbox: \"%s\"")); EXTERN char e_stringreq[] INIT(= N_("E928: String required")); EXTERN char e_emptykey[] INIT(= N_("E713: Cannot use empty key for Dictionary")); ! EXTERN char e_dictreq[] INIT(= N_("E715: Dictionary required")); ! EXTERN char e_listidx[] INIT(= N_("E684: list index out of range: %ld")); ! EXTERN char e_blobidx[] INIT(= N_("E979: Blob index out of range: %ld")); EXTERN char e_invalblob[] INIT(= N_("E978: Invalid operation for Blob")); EXTERN char e_toomanyarg[] INIT(= N_("E118: Too many arguments for function: %s")); + EXTERN char e_toofewarg[] INIT(= N_("E119: Not enough arguments for function: %s")); + EXTERN char e_func_deleted[] INIT(= N_("E933: Function was deleted: %s")); EXTERN char e_dictkey[] INIT(= N_("E716: Key not present in Dictionary: %s")); ! EXTERN char e_listreq[] INIT(= N_("E714: List required")); EXTERN char e_listblobreq[] INIT(= N_("E897: List or Blob required")); EXTERN char e_listdictarg[] INIT(= N_("E712: Argument of %s must be a List or Dictionary")); EXTERN char e_listdictblobarg[] INIT(= N_("E896: Argument of %s must be a List, Dictionary or Blob")); + EXTERN char e_modulus[] INIT(= N_("E804: Cannot use '%' with Float")); EXTERN char e_inval_string[] INIT(= N_("E908: using an invalid value as a String")); + EXTERN char e_const_option[] INIT(= N_("E996: Cannot lock an option")); + EXTERN char e_unknown_option[] INIT(= N_("E113: Unknown option: %s")); + EXTERN char e_letunexp[] INIT(= N_("E18: Unexpected characters in :let")); #endif #ifdef FEAT_QUICKFIX EXTERN char e_readerrf[] INIT(= N_("E47: Error while reading errorfile")); *************** *** 1632,1638 **** EXTERN char e_zerocount[] INIT(= N_("E939: Positive count required")); #ifdef FEAT_EVAL EXTERN char e_usingsid[] INIT(= N_("E81: Using not in a script context")); ! EXTERN char e_missingparen[] INIT(= N_("E107: Missing parentheses: %s")); #endif #ifdef FEAT_CLIENTSERVER EXTERN char e_invexprmsg[] INIT(= N_("E449: Invalid expression received")); --- 1677,1688 ---- EXTERN char e_zerocount[] INIT(= N_("E939: Positive count required")); #ifdef FEAT_EVAL EXTERN char e_usingsid[] INIT(= N_("E81: Using not in a script context")); ! EXTERN char e_missing_paren[] INIT(= N_("E107: Missing parentheses: %s")); ! EXTERN char e_missing_close[] INIT(= N_("E110: Missing ')'")); ! EXTERN char e_missing_dict_colon[] INIT(= N_("E720: Missing colon in Dictionary: %s")); ! EXTERN char e_duplicate_key[] INIT(= N_("E721: Duplicate key in Dictionary: \"%s\"")); ! EXTERN char e_missing_dict_comma[] INIT(= N_("E722: Missing comma in Dictionary: %s")); ! EXTERN char e_missing_dict_end[] INIT(= N_("E723: Missing end of Dictionary '}': %s")); #endif #ifdef FEAT_CLIENTSERVER EXTERN char e_invexprmsg[] INIT(= N_("E449: Invalid expression received")); *************** *** 1660,1665 **** --- 1710,1727 ---- #endif EXTERN char e_invalwindow[] INIT(= N_("E957: Invalid window number")); EXTERN char e_listarg[] INIT(= N_("E686: Argument of %s must be a List")); + #ifdef FEAT_EVAL + EXTERN char e_missing_colon[] INIT(= N_("E109: Missing ':' after '?'")); + EXTERN char e_missing_in[] INIT(= N_("E690: Missing \"in\" after :for")); + EXTERN char e_unknownfunc[] INIT(= N_("E117: Unknown function: %s")); + EXTERN char e_missbrac[] INIT(= N_("E111: Missing ']'")); + EXTERN char e_else_without_if[] INIT(= N_("E581: :else without :if")); + EXTERN char e_elseif_without_if[] INIT(= N_("E582: :elseif without :if")); + EXTERN char e_endif_without_if[] INIT(= N_("E580: :endif without :if")); + EXTERN char e_continue[] INIT(= N_("E586: :continue without :while or :for")); + EXTERN char e_break[] INIT(= N_("E587: :break without :while or :for")); + EXTERN char e_nowhitespace[] INIT(= N_("E274: No white space allowed before parenthesis")); + #endif #ifdef FEAT_GUI_MAC EXTERN short disallow_gui INIT(= FALSE); *************** *** 1735,1740 **** --- 1797,1805 ---- // Only filled for Win32. EXTERN char windowsVersion[20] INIT(= {0}); + + // Used for a non-materialized range() list. + EXTERN listitem_T range_list_item; #endif #ifdef MSWIN *** ../vim-8.2.0148/src/gui.c 2019-12-29 15:19:00.470760367 +0100 --- src/gui.c 2020-01-26 15:30:08.617766613 +0100 *************** *** 519,525 **** if (vim_strchr(p_go, GO_NOSYSMENU) == NULL) { sys_menu = TRUE; ! do_source((char_u *)SYS_MENU_FILE, FALSE, DOSO_NONE); sys_menu = FALSE; } #endif --- 519,525 ---- if (vim_strchr(p_go, GO_NOSYSMENU) == NULL) { sys_menu = TRUE; ! do_source((char_u *)SYS_MENU_FILE, FALSE, DOSO_NONE, NULL); sys_menu = FALSE; } #endif *************** *** 540,547 **** { if (STRCMP(use_gvimrc, "NONE") != 0 && STRCMP(use_gvimrc, "NORC") != 0 ! && do_source(use_gvimrc, FALSE, DOSO_NONE) != OK) ! semsg(_("E230: Cannot read from \"%s\""), use_gvimrc); } else { --- 540,547 ---- { if (STRCMP(use_gvimrc, "NONE") != 0 && STRCMP(use_gvimrc, "NORC") != 0 ! && do_source(use_gvimrc, FALSE, DOSO_NONE, NULL) != OK) ! semsg(_("E230: Cannot read from \"%s\""), use_gvimrc, NULL); } else { *************** *** 549,555 **** * Get system wide defaults for gvim, only when file name defined. */ #ifdef SYS_GVIMRC_FILE ! do_source((char_u *)SYS_GVIMRC_FILE, FALSE, DOSO_NONE); #endif /* --- 549,555 ---- * Get system wide defaults for gvim, only when file name defined. */ #ifdef SYS_GVIMRC_FILE ! do_source((char_u *)SYS_GVIMRC_FILE, FALSE, DOSO_NONE, NULL); #endif /* *************** *** 563,581 **** */ if (process_env((char_u *)"GVIMINIT", FALSE) == FAIL && do_source((char_u *)USR_GVIMRC_FILE, TRUE, ! DOSO_GVIMRC) == FAIL #ifdef USR_GVIMRC_FILE2 && do_source((char_u *)USR_GVIMRC_FILE2, TRUE, ! DOSO_GVIMRC) == FAIL #endif #ifdef USR_GVIMRC_FILE3 && do_source((char_u *)USR_GVIMRC_FILE3, TRUE, ! DOSO_GVIMRC) == FAIL #endif ) { #ifdef USR_GVIMRC_FILE4 ! (void)do_source((char_u *)USR_GVIMRC_FILE4, TRUE, DOSO_GVIMRC); #endif } --- 563,582 ---- */ if (process_env((char_u *)"GVIMINIT", FALSE) == FAIL && do_source((char_u *)USR_GVIMRC_FILE, TRUE, ! DOSO_GVIMRC, NULL) == FAIL #ifdef USR_GVIMRC_FILE2 && do_source((char_u *)USR_GVIMRC_FILE2, TRUE, ! DOSO_GVIMRC, NULL) == FAIL #endif #ifdef USR_GVIMRC_FILE3 && do_source((char_u *)USR_GVIMRC_FILE3, TRUE, ! DOSO_GVIMRC, NULL) == FAIL #endif ) { #ifdef USR_GVIMRC_FILE4 ! (void)do_source((char_u *)USR_GVIMRC_FILE4, TRUE, ! DOSO_GVIMRC, NULL); #endif } *************** *** 623,629 **** (char_u *)GVIMRC_FILE, FALSE, TRUE) != FPC_SAME #endif ) ! do_source((char_u *)GVIMRC_FILE, TRUE, DOSO_GVIMRC); if (secure == 2) need_wait_return = TRUE; --- 624,630 ---- (char_u *)GVIMRC_FILE, FALSE, TRUE) != FPC_SAME #endif ) ! do_source((char_u *)GVIMRC_FILE, TRUE, DOSO_GVIMRC, NULL); if (secure == 2) need_wait_return = TRUE; *** ../vim-8.2.0148/src/if_lua.c 2020-01-11 16:05:19.590287629 +0100 --- src/if_lua.c 2020-01-26 15:30:08.617766613 +0100 *************** *** 841,848 **** if (lua_isnil(L, 3)) // remove? { vimlist_remove(l, li, li); ! clear_tv(&li->li_tv); ! vim_free(li); } else { --- 841,847 ---- if (lua_isnil(L, 3)) // remove? { vimlist_remove(l, li, li); ! listitem_free(l, li); } else { *** ../vim-8.2.0148/src/if_py_both.h 2020-01-11 16:05:19.590287629 +0100 --- src/if_py_both.h 2020-01-26 15:30:08.621766594 +0100 *************** *** 785,790 **** --- 785,791 ---- return NULL; } + range_list_materialize(list); for (curr = list->lv_first; curr != NULL; curr = curr->li_next) { if (!(newObj = VimToPython(&curr->li_tv, depth + 1, lookup_dict))) *************** *** 2255,2260 **** --- 2256,2262 ---- return NULL; self->list = list; ++list->lv_refcount; + range_list_materialize(list); pyll_add((PyObject *)(self), &self->ref, &lastlist); *************** *** 2302,2308 **** { Py_DECREF(item); Py_DECREF(iterator); ! listitem_free(li); return -1; } --- 2304,2310 ---- { Py_DECREF(item); Py_DECREF(iterator); ! listitem_free(l, li); return -1; } *************** *** 2662,2668 **** } for (i = 0; i < numreplaced; i++) ! listitem_free(lis[i]); if (step == 1) for (i = numreplaced; i < slicelen; i++) listitem_remove(l, lis[i]); --- 2664,2670 ---- } for (i = 0; i < numreplaced; i++) ! listitem_free(l, lis[i]); if (step == 1) for (i = numreplaced; i < slicelen; i++) listitem_remove(l, lis[i]); *************** *** 2822,2827 **** --- 2824,2830 ---- return NULL; } + range_list_materialize(l); list_add_watch(l, &lii->lw); lii->lw.lw_item = l->lv_first; lii->list = l; *************** *** 3018,3023 **** --- 3021,3027 ---- return NULL; } argslist = argstv.vval.v_list; + range_list_materialize(argslist); argc = argslist->lv_len; if (argc != 0) *************** *** 6386,6391 **** --- 6390,6396 ---- (char*) tv->vval.v_blob->bv_ga.ga_data, (Py_ssize_t) tv->vval.v_blob->bv_ga.ga_len); case VAR_UNKNOWN: + case VAR_VOID: case VAR_CHANNEL: case VAR_JOB: Py_INCREF(Py_None); *** ../vim-8.2.0148/src/insexpand.c 2020-01-17 18:58:37.159223076 +0100 --- src/insexpand.c 2020-01-26 15:30:08.621766594 +0100 *************** *** 576,583 **** char_u *str, int len, char_u *fname, ! char_u **cptext, // extra text for popup menu or NULL ! typval_T *user_data, // "user_data" entry or NULL int cdir, int flags_arg, int adup) // accept duplicate match --- 576,583 ---- char_u *str, int len, char_u *fname, ! char_u **cptext, // extra text for popup menu or NULL ! typval_T *user_data UNUSED, // "user_data" entry or NULL int cdir, int flags_arg, int adup) // accept duplicate match *** ../vim-8.2.0148/src/json.c 2020-01-11 16:05:19.594287610 +0100 --- src/json.c 2020-01-26 15:30:08.621766594 +0100 *************** *** 215,221 **** case VAR_NUMBER: vim_snprintf((char *)numbuf, NUMBUFLEN, "%lld", ! (long_long_T)val->vval.v_number); ga_concat(gap, numbuf); break; --- 215,221 ---- case VAR_NUMBER: vim_snprintf((char *)numbuf, NUMBUFLEN, "%lld", ! (long_long_T)val->vval.v_number); ga_concat(gap, numbuf); break; *************** *** 350,355 **** --- 350,356 ---- break; #endif case VAR_UNKNOWN: + case VAR_VOID: internal_error("json_encode_item()"); return FAIL; } *** ../vim-8.2.0148/src/list.c 2020-01-15 20:51:31.301128122 +0100 --- src/list.c 2020-01-26 15:30:08.621766594 +0100 *************** *** 65,70 **** --- 65,81 ---- lw->lw_item = item->li_next; } + static void + list_init(list_T *l) + { + // Prepend the list to the list of lists for garbage collection. + if (first_list != NULL) + first_list->lv_used_prev = l; + l->lv_used_prev = NULL; + l->lv_used_next = first_list; + first_list = l; + } + /* * Allocate an empty header for a list. * Caller should take care of the reference count. *************** *** 76,89 **** l = ALLOC_CLEAR_ONE(list_T); if (l != NULL) ! { ! // Prepend the list to the list of lists for garbage collection. ! if (first_list != NULL) ! first_list->lv_used_prev = l; ! l->lv_used_prev = NULL; ! l->lv_used_next = first_list; ! first_list = l; ! } return l; } --- 87,93 ---- l = ALLOC_CLEAR_ONE(list_T); if (l != NULL) ! list_init(l); return l; } *************** *** 101,106 **** --- 105,163 ---- } /* + * Allocate space for a list, plus "count" items. + * Next list_set_item() must be called for each item. + */ + list_T * + list_alloc_with_items(int count) + { + list_T *l; + + l = (list_T *)alloc_clear(sizeof(list_T) + count * sizeof(listitem_T)); + if (l != NULL) + { + list_init(l); + + if (count > 0) + { + listitem_T *li = (listitem_T *)(l + 1); + int i; + + l->lv_len = count; + l->lv_with_items = count; + l->lv_first = li; + l->lv_last = li + count - 1; + for (i = 0; i < count; ++i) + { + if (i == 0) + li->li_prev = NULL; + else + li->li_prev = li - 1; + if (i == count - 1) + li->li_next = NULL; + else + li->li_next = li + 1; + ++li; + } + } + } + return l; + } + + /* + * Set item "idx" for a list previously allocated with list_alloc_with_items(). + * The contents of "tv" is moved into the list item. + * Each item must be set exactly once. + */ + void + list_set_item(list_T *l, int idx, typval_T *tv) + { + listitem_T *li = (listitem_T *)(l + 1) + idx; + + li->li_tv = *tv; + } + + /* * Allocate an empty list for a return value, with reference count set. * Returns OK or FAIL. */ *************** *** 163,175 **** { listitem_T *item; ! for (item = l->lv_first; item != NULL; item = l->lv_first) ! { ! // Remove the item before deleting it. ! l->lv_first = item->li_next; ! clear_tv(&item->li_tv); ! vim_free(item); ! } } /* --- 220,233 ---- { listitem_T *item; ! if (l->lv_first != &range_list_item) ! for (item = l->lv_first; item != NULL; item = l->lv_first) ! { ! // Remove the item before deleting it. ! l->lv_first = item->li_next; ! clear_tv(&item->li_tv); ! list_free_item(l, item); ! } } /* *************** *** 250,262 **** } /* ! * Free a list item. Also clears the value. Does not notify watchers. */ void ! listitem_free(listitem_T *item) { clear_tv(&item->li_tv); ! vim_free(item); } /* --- 308,333 ---- } /* ! * Free a list item, unless it was allocated together with the list itself. ! * Does not clear the value. Does not notify watchers. ! */ ! void ! list_free_item(list_T *l, listitem_T *item) ! { ! if (l->lv_with_items == 0 || item < (listitem_T *)l ! || item >= (listitem_T *)(l + 1) + l->lv_with_items) ! vim_free(item); ! } ! ! /* ! * Free a list item, unless it was allocated together with the list itself. ! * Also clears the value. Does not notify watchers. */ void ! listitem_free(list_T *l, listitem_T *item) { clear_tv(&item->li_tv); ! list_free_item(l, item); } /* *************** *** 266,272 **** listitem_remove(list_T *l, listitem_T *item) { vimlist_remove(l, item, item); ! listitem_free(item); } /* --- 337,343 ---- listitem_remove(list_T *l, listitem_T *item) { vimlist_remove(l, item, item); ! listitem_free(l, item); } /* *************** *** 299,304 **** --- 370,378 ---- if (list_len(l1) != list_len(l2)) return FALSE; + range_list_materialize(l1); + range_list_materialize(l2); + for (item1 = l1->lv_first, item2 = l2->lv_first; item1 != NULL && item2 != NULL; item1 = item1->li_next, item2 = item2->li_next) *************** *** 329,334 **** --- 403,410 ---- if (n < 0 || n >= l->lv_len) return NULL; + range_list_materialize(l); + // When there is a cached index may start search from there. if (l->lv_idx_item != NULL) { *************** *** 398,403 **** --- 474,499 ---- { listitem_T *li; + if (l != NULL && l->lv_first == &range_list_item) + { + long n = idx; + + // not materialized range() list: compute the value. + // Negative index is relative to the end. + if (n < 0) + n = l->lv_len + n; + + // Check for index out of range. + if (n < 0 || n >= l->lv_len) + { + if (errorp != NULL) + *errorp = TRUE; + return -1L; + } + + return l->lv_start + n * l->lv_stride; + } + li = list_find(l, idx); if (li == NULL) { *************** *** 437,442 **** --- 533,539 ---- if (l == NULL) return -1; + range_list_materialize(l); idx = 0; for (li = l->lv_first; li != NULL && li != item; li = li->li_next) ++idx; *************** *** 451,456 **** --- 548,554 ---- void list_append(list_T *l, listitem_T *item) { + range_list_materialize(l); if (l->lv_last == NULL) { // empty list *************** *** 469,475 **** } /* ! * Append typval_T "tv" to the end of list "l". * Return FAIL when out of memory. */ int --- 567,573 ---- } /* ! * Append typval_T "tv" to the end of list "l". "tv" is copied. * Return FAIL when out of memory. */ int *************** *** 485,490 **** --- 583,604 ---- } /* + * As list_append_tv() but move the value instead of copying it. + * Return FAIL when out of memory. + */ + int + list_append_tv_move(list_T *l, typval_T *tv) + { + listitem_T *li = listitem_alloc(); + + if (li == NULL) + return FAIL; + li->li_tv = *tv; + list_append(l, li); + return OK; + } + + /* * Add a dictionary to a list. Used by getqflist(). * Return FAIL when out of memory. */ *************** *** 584,589 **** --- 698,704 ---- void list_insert(list_T *l, listitem_T *ni, listitem_T *item) { + range_list_materialize(l); if (item == NULL) // Append new item at end of list. list_append(l, ni); *************** *** 618,623 **** --- 733,741 ---- listitem_T *item; int todo = l2->lv_len; + range_list_materialize(l1); + range_list_materialize(l2); + // We also quit the loop when we have inserted the original item count of // the list, avoid a hang when we extend a list with itself. for (item = l2->lv_first; item != NULL && --todo >= 0; item = item->li_next) *************** *** 675,680 **** --- 793,799 ---- orig->lv_copyID = copyID; orig->lv_copylist = copy; } + range_list_materialize(orig); for (item = orig->lv_first; item != NULL && !got_int; item = item->li_next) { *************** *** 715,720 **** --- 834,841 ---- { listitem_T *ip; + range_list_materialize(l); + // notify watchers for (ip = item; ip != NULL; ip = ip->li_next) { *************** *** 748,753 **** --- 869,875 ---- return NULL; ga_init2(&ga, (int)sizeof(char), 80); ga_append(&ga, '['); + range_list_materialize(tv->vval.v_list); if (list_join(&ga, tv->vval.v_list, (char_u *)", ", FALSE, restore_copyID, copyID) == FAIL) { *************** *** 785,790 **** --- 907,913 ---- char_u *s; // Stringify each item in the list. + range_list_materialize(l); for (item = l->lv_first; item != NULL && !got_int; item = item->li_next) { s = echo_string_core(&item->li_tv, &tofree, numbuf, copyID, *************** *** 915,921 **** * Return OK or FAIL. */ int ! get_list_tv(char_u **arg, typval_T *rettv, int evaluate) { list_T *l = NULL; typval_T tv; --- 1038,1044 ---- * Return OK or FAIL. */ int ! get_list_tv(char_u **arg, typval_T *rettv, int evaluate, int do_error) { list_T *l = NULL; typval_T tv; *************** *** 950,956 **** break; if (**arg != ',') { ! semsg(_("E696: Missing comma in List: %s"), *arg); goto failret; } *arg = skipwhite(*arg + 1); --- 1073,1080 ---- break; if (**arg != ',') { ! if (do_error) ! semsg(_("E696: Missing comma in List: %s"), *arg); goto failret; } *arg = skipwhite(*arg + 1); *************** *** 958,964 **** if (**arg != ']') { ! semsg(_("E697: Missing end of List ']': %s"), *arg); failret: if (evaluate) list_free(l); --- 1082,1089 ---- if (**arg != ']') { ! if (do_error) ! semsg(_("E697: Missing end of List ']': %s"), *arg); failret: if (evaluate) list_free(l); *************** *** 983,988 **** --- 1108,1114 ---- int ret = OK; char_u *s; + range_list_materialize(list); for (li = list->lv_first; li != NULL; li = li->li_next) { for (s = tv_get_string(&li->li_tv); *s != NUL; ++s) *************** *** 1069,1074 **** --- 1195,1201 ---- if (argvars[1].v_type != VAR_UNKNOWN) utf8 = (int)tv_get_number_chk(&argvars[1], NULL); + range_list_materialize(l); ga_init2(&ga, 1, 80); if (has_mbyte || utf8) { *************** *** 1123,1129 **** // Remove one item, return its value. vimlist_remove(l, item, item); *rettv = item->li_tv; ! vim_free(item); } else { --- 1250,1256 ---- // Remove one item, return its value. vimlist_remove(l, item, item); *rettv = item->li_tv; ! list_free_item(l, item); } else { *************** *** 1361,1366 **** --- 1488,1494 ---- TRUE)) goto theend; rettv_list_set(rettv, l); + range_list_materialize(l); len = list_len(l); if (len <= 1) *************** *** 1519,1525 **** else l->lv_last = ptrs[i].item; list_fix_watch(l, li); ! listitem_free(li); l->lv_len--; } } --- 1647,1653 ---- else l->lv_last = ptrs[i].item; list_fix_watch(l, li); ! listitem_free(l, li); l->lv_len--; } } *************** *** 1729,1734 **** --- 1857,1863 ---- // set_vim_var_nr() doesn't set the type set_vim_var_type(VV_KEY, VAR_NUMBER); + range_list_materialize(l); for (li = l->lv_first; li != NULL; li = nli) { if (map && var_check_lock(li->li_tv.v_lock, arg_errmsg, TRUE)) *** ../vim-8.2.0148/src/macros.h 2020-01-07 20:59:30.520926552 +0100 --- src/macros.h 2020-01-26 15:30:08.621766594 +0100 *************** *** 302,307 **** --- 302,311 ---- # endif #endif + #ifdef FEAT_EVAL + # define FUNCARG(fp, j) ((char_u **)(fp->uf_args.ga_data))[j] + #endif + /* * In a hashtab item "hi_key" points to "di_key" in a dictitem. * This avoids adding a pointer to the hashtab item. *** ../vim-8.2.0148/src/main.c 2020-01-07 20:59:30.524926537 +0100 --- src/main.c 2020-01-26 15:30:08.621766594 +0100 *************** *** 482,488 **** # else (char_u *)"plugin/**/*.vim", # endif ! DIP_ALL | DIP_NOAFTER); TIME_MSG("loading plugins"); vim_free(rtp_copy); --- 482,488 ---- # else (char_u *)"plugin/**/*.vim", # endif ! DIP_ALL | DIP_NOAFTER, NULL); TIME_MSG("loading plugins"); vim_free(rtp_copy); *************** *** 3169,3175 **** */ if (parmp->evim_mode) { ! (void)do_source((char_u *)EVIM_FILE, FALSE, DOSO_NONE); TIME_MSG("source evim file"); } --- 3169,3175 ---- */ if (parmp->evim_mode) { ! (void)do_source((char_u *)EVIM_FILE, FALSE, DOSO_NONE, NULL); TIME_MSG("source evim file"); } *************** *** 3180,3186 **** if (parmp->use_vimrc != NULL) { if (STRCMP(parmp->use_vimrc, "DEFAULTS") == 0) ! do_source((char_u *)VIM_DEFAULTS_FILE, FALSE, DOSO_NONE); else if (STRCMP(parmp->use_vimrc, "NONE") == 0 || STRCMP(parmp->use_vimrc, "NORC") == 0) { --- 3180,3186 ---- if (parmp->use_vimrc != NULL) { if (STRCMP(parmp->use_vimrc, "DEFAULTS") == 0) ! do_source((char_u *)VIM_DEFAULTS_FILE, FALSE, DOSO_NONE, NULL); else if (STRCMP(parmp->use_vimrc, "NONE") == 0 || STRCMP(parmp->use_vimrc, "NORC") == 0) { *************** *** 3191,3197 **** } else { ! if (do_source(parmp->use_vimrc, FALSE, DOSO_NONE) != OK) semsg(_("E282: Cannot read from \"%s\""), parmp->use_vimrc); } } --- 3191,3197 ---- } else { ! if (do_source(parmp->use_vimrc, FALSE, DOSO_NONE, NULL) != OK) semsg(_("E282: Cannot read from \"%s\""), parmp->use_vimrc); } } *************** *** 3209,3218 **** * Get system wide defaults, if the file name is defined. */ #ifdef SYS_VIMRC_FILE ! (void)do_source((char_u *)SYS_VIMRC_FILE, FALSE, DOSO_NONE); #endif #ifdef MACOS_X ! (void)do_source((char_u *)"$VIMRUNTIME/macmap.vim", FALSE, DOSO_NONE); #endif /* --- 3209,3219 ---- * Get system wide defaults, if the file name is defined. */ #ifdef SYS_VIMRC_FILE ! (void)do_source((char_u *)SYS_VIMRC_FILE, FALSE, DOSO_NONE, NULL); #endif #ifdef MACOS_X ! (void)do_source((char_u *)"$VIMRUNTIME/macmap.vim", FALSE, ! DOSO_NONE, NULL); #endif /* *************** *** 3227,3254 **** */ if (process_env((char_u *)"VIMINIT", TRUE) != OK) { ! if (do_source((char_u *)USR_VIMRC_FILE, TRUE, DOSO_VIMRC) == FAIL #ifdef USR_VIMRC_FILE2 && do_source((char_u *)USR_VIMRC_FILE2, TRUE, ! DOSO_VIMRC) == FAIL #endif #ifdef USR_VIMRC_FILE3 && do_source((char_u *)USR_VIMRC_FILE3, TRUE, ! DOSO_VIMRC) == FAIL #endif #ifdef USR_VIMRC_FILE4 && do_source((char_u *)USR_VIMRC_FILE4, TRUE, ! DOSO_VIMRC) == FAIL #endif && process_env((char_u *)"EXINIT", FALSE) == FAIL ! && do_source((char_u *)USR_EXRC_FILE, FALSE, DOSO_NONE) == FAIL #ifdef USR_EXRC_FILE2 ! && do_source((char_u *)USR_EXRC_FILE2, FALSE, DOSO_NONE) == FAIL #endif && !has_dash_c_arg) { // When no .vimrc file was found: source defaults.vim. ! do_source((char_u *)VIM_DEFAULTS_FILE, FALSE, DOSO_NONE); } } --- 3228,3258 ---- */ if (process_env((char_u *)"VIMINIT", TRUE) != OK) { ! if (do_source((char_u *)USR_VIMRC_FILE, TRUE, ! DOSO_VIMRC, NULL) == FAIL #ifdef USR_VIMRC_FILE2 && do_source((char_u *)USR_VIMRC_FILE2, TRUE, ! DOSO_VIMRC, NULL) == FAIL #endif #ifdef USR_VIMRC_FILE3 && do_source((char_u *)USR_VIMRC_FILE3, TRUE, ! DOSO_VIMRC, NULL) == FAIL #endif #ifdef USR_VIMRC_FILE4 && do_source((char_u *)USR_VIMRC_FILE4, TRUE, ! DOSO_VIMRC, NULL) == FAIL #endif && process_env((char_u *)"EXINIT", FALSE) == FAIL ! && do_source((char_u *)USR_EXRC_FILE, FALSE, ! DOSO_NONE, NULL) == FAIL #ifdef USR_EXRC_FILE2 ! && do_source((char_u *)USR_EXRC_FILE2, FALSE, ! DOSO_NONE, NULL) == FAIL #endif && !has_dash_c_arg) { // When no .vimrc file was found: source defaults.vim. ! do_source((char_u *)VIM_DEFAULTS_FILE, FALSE, DOSO_NONE, NULL); } } *************** *** 3285,3291 **** (char_u *)VIMRC_FILE, FALSE, TRUE) != FPC_SAME #endif ) ! i = do_source((char_u *)VIMRC_FILE, TRUE, DOSO_VIMRC); if (i == FAIL) { --- 3289,3295 ---- (char_u *)VIMRC_FILE, FALSE, TRUE) != FPC_SAME #endif ) ! i = do_source((char_u *)VIMRC_FILE, TRUE, DOSO_VIMRC, NULL); if (i == FAIL) { *************** *** 3303,3309 **** (char_u *)EXRC_FILE, FALSE, TRUE) != FPC_SAME #endif ) ! (void)do_source((char_u *)EXRC_FILE, FALSE, DOSO_NONE); } } if (secure == 2) --- 3307,3314 ---- (char_u *)EXRC_FILE, FALSE, TRUE) != FPC_SAME #endif ) ! (void)do_source((char_u *)EXRC_FILE, FALSE, ! DOSO_NONE, NULL); } } if (secure == 2) *************** *** 3334,3340 **** #endif // NO_VIM_MAIN /* ! * Get an environment variable, and execute it as Ex commands. * Returns FAIL if the environment variable was not executed, OK otherwise. */ int --- 3339,3345 ---- #endif // NO_VIM_MAIN /* ! * Get an environment variable and execute it as Ex commands. * Returns FAIL if the environment variable was not executed, OK otherwise. */ int *** ../vim-8.2.0148/src/message.c 2019-12-30 17:55:30.460513283 +0100 --- src/message.c 2020-01-26 15:30:08.621766594 +0100 *************** *** 847,852 **** --- 847,863 ---- } /* + * Give an error message which contains %s for "name[len]". + */ + void + emsg_namelen(char *msg, char_u *name, int len) + { + char_u *copy = vim_strnsave((char_u *)name, len); + + semsg(msg, copy == NULL ? "NULL" : (char *)copy); + } + + /* * Like msg(), but truncate to a single line if p_shm contains 't', or when * "force" is TRUE. This truncates in another way as for normal messages. * Careful: The string may be changed by msg_may_trunc()! *** ../vim-8.2.0148/src/misc1.c 2019-12-21 18:25:50.453560468 +0100 --- src/misc1.c 2020-01-26 15:30:08.621766594 +0100 *************** *** 2067,2079 **** concat_str(char_u *str1, char_u *str2) { char_u *dest; ! size_t l = STRLEN(str1); ! dest = alloc(l + STRLEN(str2) + 1L); if (dest != NULL) { ! STRCPY(dest, str1); ! STRCPY(dest + l, str2); } return dest; } --- 2067,2083 ---- concat_str(char_u *str1, char_u *str2) { char_u *dest; ! size_t l = str1 == NULL ? 0 : STRLEN(str1); ! dest = alloc(l + (str2 == NULL ? 0 : STRLEN(str2)) + 1L); if (dest != NULL) { ! if (str1 == NULL) ! *dest = NUL; ! else ! STRCPY(dest, str1); ! if (str2 != NULL) ! STRCPY(dest + l, str2); } return dest; } *** ../vim-8.2.0148/src/proto.h 2019-12-01 18:52:53.000000000 +0100 --- src/proto.h 2020-01-26 15:30:08.621766594 +0100 *************** *** 226,231 **** --- 226,236 ---- # include "usercmd.pro" # include "userfunc.pro" # include "version.pro" + # ifdef FEAT_EVAL + # include "vim9compile.pro" + # include "vim9execute.pro" + # include "vim9script.pro" + # endif # include "window.pro" # ifdef FEAT_LUA *** ../vim-8.2.0148/src/proto/blob.pro 2019-07-27 22:53:56.000000000 +0200 --- src/proto/blob.pro 2020-01-26 15:30:08.621766594 +0100 *************** *** 2,8 **** blob_T *blob_alloc(void); int rettv_blob_alloc(typval_T *rettv); void rettv_blob_set(typval_T *rettv, blob_T *b); ! int blob_copy(typval_T *from, typval_T *to); void blob_free(blob_T *b); void blob_unref(blob_T *b); long blob_len(blob_T *b); --- 2,8 ---- blob_T *blob_alloc(void); int rettv_blob_alloc(typval_T *rettv); void rettv_blob_set(typval_T *rettv, blob_T *b); ! int blob_copy(blob_T *from, typval_T *to); void blob_free(blob_T *b); void blob_unref(blob_T *b); long blob_len(blob_T *b); *** ../vim-8.2.0148/src/proto/eval.pro 2019-12-25 18:14:10.481839651 +0100 --- src/proto/eval.pro 2020-01-26 15:30:08.621766594 +0100 *************** *** 27,33 **** --- 27,38 ---- int pattern_match(char_u *pat, char_u *text, int ic); int eval0(char_u *arg, typval_T *rettv, char_u **nextcmd, int evaluate); int eval1(char_u **arg, typval_T *rettv, int evaluate); + void eval_addblob(typval_T *tv1, typval_T *tv2); + int eval_addlist(typval_T *tv1, typval_T *tv2); int get_option_tv(char_u **arg, typval_T *rettv, int evaluate); + int get_number_tv(char_u **arg, typval_T *rettv, int evaluate, int want_string); + int get_string_tv(char_u **arg, typval_T *rettv, int evaluate); + int get_lit_string_tv(char_u **arg, typval_T *rettv, int evaluate); char_u *partial_name(partial_T *pt); void partial_unref(partial_T *pt); int tv_equal(typval_T *tv1, typval_T *tv2, int ic, int recursive); *************** *** 43,48 **** --- 48,54 ---- char_u *tv2string(typval_T *tv, char_u **tofree, char_u *numbuf, int copyID); char_u *string_quote(char_u *str, int function); int string2float(char_u *text, float_T *value); + int get_env_tv(char_u **arg, typval_T *rettv, int evaluate); pos_T *var2fpos(typval_T *varp, int dollar_lnum, int *fnum); int list2fpos(typval_T *arg, pos_T *posp, int *fnump, colnr_T *curswantp); int get_env_len(char_u **arg); *************** *** 66,71 **** --- 72,78 ---- char_u *tv_get_string_buf_chk(typval_T *varp, char_u *buf); void copy_tv(typval_T *from, typval_T *to); 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); void ex_echohl(exarg_T *eap); int get_echo_attr(void); *** ../vim-8.2.0148/src/proto/evalfunc.pro 2019-12-12 12:55:19.000000000 +0100 --- src/proto/evalfunc.pro 2020-01-26 15:30:08.621766594 +0100 *************** *** 1,8 **** --- 1,13 ---- /* evalfunc.c */ char_u *get_function_name(expand_T *xp, int idx); char_u *get_expr_name(expand_T *xp, int idx); + int find_internal_func(char_u *name); int has_internal_func(char_u *name); + char *internal_func_name(int idx); + type_T *internal_func_ret_type(int idx, int argcount); + int check_internal_func(int idx, int argcount); int call_internal_func(char_u *name, int argcount, typval_T *argvars, typval_T *rettv); + void call_internal_func_by_idx(int idx, typval_T *argvars, typval_T *rettv); int call_internal_method(char_u *name, int argcount, typval_T *argvars, typval_T *rettv, typval_T *basetv); int non_zero_arg(typval_T *argvars); linenr_T tv_get_lnum(typval_T *argvars); *************** *** 13,18 **** --- 18,24 ---- void execute_redir_str(char_u *value, int value_len); void execute_common(typval_T *argvars, typval_T *rettv, int arg_off); void mzscheme_call_vim(char_u *name, typval_T *args, typval_T *rettv); + void range_list_materialize(list_T *list); float_T vim_round(float_T f); long do_searchpair(char_u *spat, char_u *mpat, char_u *epat, int dir, typval_T *skip, int flags, pos_T *match_pos, linenr_T lnum_stop, long time_limit); void f_string(typval_T *argvars, typval_T *rettv); *** ../vim-8.2.0148/src/proto/evalvars.pro 2019-12-12 12:55:19.000000000 +0100 --- src/proto/evalvars.pro 2020-01-26 15:30:08.621766594 +0100 *************** *** 13,22 **** int get_spellword(list_T *list, char_u **pp); void prepare_vimvar(int idx, typval_T *save_tv); void restore_vimvar(int idx, typval_T *save_tv); void ex_let(exarg_T *eap); void ex_const(exarg_T *eap); ! int ex_let_vars(char_u *arg_start, typval_T *tv, int copy, int semicolon, int var_count, int is_const, char_u *op); ! char_u *skip_var_list(char_u *arg, int *var_count, int *semicolon); void list_hashtable_vars(hashtab_T *ht, char *prefix, int empty, int *first); void ex_unlet(exarg_T *eap); void ex_lockvar(exarg_T *eap); --- 13,23 ---- int get_spellword(list_T *list, char_u **pp); void prepare_vimvar(int idx, typval_T *save_tv); void restore_vimvar(int idx, typval_T *save_tv); + list_T *heredoc_get(exarg_T *eap, char_u *cmd); void ex_let(exarg_T *eap); void ex_const(exarg_T *eap); ! int ex_let_vars(char_u *arg_start, typval_T *tv, int copy, int semicolon, int var_count, int flags, char_u *op); ! char_u *skip_var_list(char_u *arg, int include_type, int *var_count, int *semicolon); void list_hashtable_vars(hashtab_T *ht, char *prefix, int empty, int *first); void ex_unlet(exarg_T *eap); void ex_lockvar(exarg_T *eap); *************** *** 27,34 **** --- 28,37 ---- dict_T *get_globvar_dict(void); hashtab_T *get_globvar_ht(void); dict_T *get_vimvar_dict(void); + int find_vim_var(char_u *name); void set_vim_var_type(int idx, vartype_T type); void set_vim_var_nr(int idx, varnumber_T val); + char *get_vim_var_name(int idx); typval_T *get_vim_var_tv(int idx); varnumber_T get_vim_var_nr(int idx); char_u *get_vim_var_str(int idx); *************** *** 50,55 **** --- 53,60 ---- void check_vars(char_u *name, int len); dictitem_T *find_var(char_u *name, hashtab_T **htp, int no_autoload); dictitem_T *find_var_in_ht(hashtab_T *ht, int htname, char_u *varname, int no_autoload); + hashtab_T *get_script_local_ht(void); + int lookup_scriptvar(char_u *name, size_t len, cctx_T *dummy); hashtab_T *find_var_ht(char_u *name, char_u **varname); char_u *get_var_value(char_u *name); void new_script_vars(scid_T id); *************** *** 58,64 **** void vars_clear(hashtab_T *ht); void vars_clear_ext(hashtab_T *ht, int free_val); void set_var(char_u *name, typval_T *tv, int copy); ! void set_var_const(char_u *name, typval_T *tv, int copy, int is_const); int var_check_ro(int flags, char_u *name, int use_gettext); int var_check_fixed(int flags, char_u *name, int use_gettext); int var_check_func_name(char_u *name, int new_var); --- 63,69 ---- void vars_clear(hashtab_T *ht); void vars_clear_ext(hashtab_T *ht, int free_val); void set_var(char_u *name, typval_T *tv, int copy); ! void set_var_const(char_u *name, type_T *type, typval_T *tv, int copy, int flags); int var_check_ro(int flags, char_u *name, int use_gettext); int var_check_fixed(int flags, char_u *name, int use_gettext); int var_check_func_name(char_u *name, int new_var); *** ../vim-8.2.0148/src/proto/ex_docmd.pro 2019-12-12 12:55:20.000000000 +0100 --- src/proto/ex_docmd.pro 2020-01-26 15:30:08.621766594 +0100 *************** *** 7,12 **** --- 7,13 ---- int parse_command_modifiers(exarg_T *eap, char **errormsg, int skip_only); int parse_cmd_address(exarg_T *eap, char **errormsg, int silent); int checkforcmd(char_u **pp, char *cmd, int len); + char_u *find_ex_command(exarg_T *eap, int *full, int (*lookup)(char_u *, size_t, cctx_T *), cctx_T *cctx); int modifier_len(char_u *cmd); int cmd_exists(char_u *name); cmdidx_T excmd_get_cmdidx(char_u *cmd, int len); *** ../vim-8.2.0148/src/proto/ex_eval.pro 2019-12-16 17:10:30.291144418 +0100 --- src/proto/ex_eval.pro 2020-01-26 15:30:08.621766594 +0100 *************** *** 8,14 **** --- 8,16 ---- void do_errthrow(cstack_T *cstack, char_u *cmdname); int do_intthrow(cstack_T *cstack); char *get_exception_string(void *value, except_type_T type, char_u *cmdname, int *should_free); + int throw_exception(void *value, except_type_T type, char_u *cmdname); void discard_current_exception(void); + void catch_exception(except_T *excp); void report_make_pending(int pending, void *value); void ex_eval(exarg_T *eap); void ex_if(exarg_T *eap); *** ../vim-8.2.0148/src/proto/list.pro 2019-12-12 12:55:25.000000000 +0100 --- src/proto/list.pro 2020-01-26 15:30:08.621766594 +0100 *************** *** 3,8 **** --- 3,10 ---- void list_rem_watch(list_T *l, listwatch_T *lwrem); list_T *list_alloc(void); list_T *list_alloc_id(alloc_id_T id); + list_T *list_alloc_with_items(int count); + void list_set_item(list_T *l, int idx, typval_T *tv); int rettv_list_alloc(typval_T *rettv); int rettv_list_alloc_id(typval_T *rettv, alloc_id_T id); void rettv_list_set(typval_T *rettv, list_T *l); *************** *** 11,17 **** void list_free_items(int copyID); void list_free(list_T *l); listitem_T *listitem_alloc(void); ! void listitem_free(listitem_T *item); void listitem_remove(list_T *l, listitem_T *item); long list_len(list_T *l); int list_equal(list_T *l1, list_T *l2, int ic, int recursive); --- 13,20 ---- void list_free_items(int copyID); void list_free(list_T *l); listitem_T *listitem_alloc(void); ! void list_free_item(list_T *l, listitem_T *item); ! void listitem_free(list_T *l, listitem_T *item); void listitem_remove(list_T *l, listitem_T *item); long list_len(list_T *l); int list_equal(list_T *l1, list_T *l2, int ic, int recursive); *************** *** 21,26 **** --- 24,30 ---- long list_idx_of_item(list_T *l, listitem_T *item); void list_append(list_T *l, listitem_T *item); int list_append_tv(list_T *l, typval_T *tv); + int list_append_tv_move(list_T *l, typval_T *tv); int list_append_dict(list_T *list, dict_T *dict); int list_append_list(list_T *list1, list_T *list2); int list_append_string(list_T *l, char_u *str, int len); *************** *** 34,40 **** char_u *list2string(typval_T *tv, int copyID, int restore_copyID); int list_join(garray_T *gap, list_T *l, char_u *sep, int echo_style, int restore_copyID, int copyID); void f_join(typval_T *argvars, typval_T *rettv); ! int get_list_tv(char_u **arg, typval_T *rettv, int evaluate); int write_list(FILE *fd, list_T *list, int binary); void init_static_list(staticList10_T *sl); void f_list2str(typval_T *argvars, typval_T *rettv); --- 38,44 ---- char_u *list2string(typval_T *tv, int copyID, int restore_copyID); int list_join(garray_T *gap, list_T *l, char_u *sep, int echo_style, int restore_copyID, int copyID); void f_join(typval_T *argvars, typval_T *rettv); ! int get_list_tv(char_u **arg, typval_T *rettv, int evaluate, int do_error); int write_list(FILE *fd, list_T *list, int binary); void init_static_list(staticList10_T *sl); void f_list2str(typval_T *argvars, typval_T *rettv); *** ../vim-8.2.0148/src/proto/message.pro 2019-12-12 12:55:27.000000000 +0100 --- src/proto/message.pro 2020-01-26 15:30:08.621766594 +0100 *************** *** 13,18 **** --- 13,19 ---- void iemsg(char *s); void internal_error(char *where); void emsg_invreg(int name); + void emsg_namelen(char *msg, char_u *name, int len); char *msg_trunc_attr(char *s, int force, int attr); char_u *msg_may_trunc(int force, char_u *s); int delete_first_msg(void); *** ../vim-8.2.0148/src/proto/scriptfile.pro 2019-12-29 23:04:20.294639884 +0100 --- src/proto/scriptfile.pro 2020-01-26 15:30:08.621766594 +0100 *************** *** 8,14 **** int do_in_path(char_u *path, char_u *name, int flags, void (*callback)(char_u *fname, void *ck), void *cookie); int do_in_runtimepath(char_u *name, int flags, void (*callback)(char_u *fname, void *ck), void *cookie); int source_runtime(char_u *name, int flags); ! int source_in_path(char_u *path, char_u *name, int flags); void add_pack_start_dirs(void); void load_start_packages(void); void ex_packloadall(exarg_T *eap); --- 8,14 ---- int do_in_path(char_u *path, char_u *name, int flags, void (*callback)(char_u *fname, void *ck), void *cookie); int do_in_runtimepath(char_u *name, int flags, void (*callback)(char_u *fname, void *ck), void *cookie); int source_runtime(char_u *name, int flags); ! int source_in_path(char_u *path, char_u *name, int flags, int *ret_sid); void add_pack_start_dirs(void); void load_start_packages(void); void ex_packloadall(exarg_T *eap); *************** *** 21,27 **** linenr_T *source_breakpoint(void *cookie); int *source_dbg_tick(void *cookie); int source_level(void *cookie); ! int do_source(char_u *fname, int check_other, int is_vimrc); void ex_scriptnames(exarg_T *eap); void scriptnames_slash_adjust(void); char_u *get_scriptname(scid_T id); --- 21,27 ---- linenr_T *source_breakpoint(void *cookie); int *source_dbg_tick(void *cookie); int source_level(void *cookie); ! int do_source(char_u *fname, int check_other, int is_vimrc, int *ret_sid); void ex_scriptnames(exarg_T *eap); void scriptnames_slash_adjust(void); char_u *get_scriptname(scid_T id); *** ../vim-8.2.0148/src/proto/userfunc.pro 2019-12-12 12:55:36.000000000 +0100 --- src/proto/userfunc.pro 2020-01-26 15:30:08.621766594 +0100 *************** *** 1,26 **** /* userfunc.c */ void func_init(void); hashtab_T *func_tbl_get(void); int get_lambda_tv(char_u **arg, typval_T *rettv, int evaluate); 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); ! ufunc_T *find_func(char_u *name); void save_funccal(funccal_entry_T *entry); void restore_funccal(void); funccall_T *get_current_funccal(void); void free_all_functions(void); int func_call(char_u *name, typval_T *args, partial_T *partial, dict_T *selfdict, typval_T *rettv); int get_callback_depth(void); int call_callback(callback_T *callback, int len, typval_T *rettv, int argcount, typval_T *argvars); int call_func(char_u *funcname, int len, typval_T *rettv, int argcount_in, typval_T *argvars_in, funcexe_T *funcexe); char_u *trans_function_name(char_u **pp, int skip, int flags, funcdict_T *fdp, partial_T **partial); void ex_function(exarg_T *eap); int eval_fname_script(char_u *p); int translated_function_exists(char_u *name); int function_exists(char_u *name, int no_deref); char_u *get_expanded_name(char_u *name, int check); char_u *get_user_func_name(expand_T *xp, int idx); void ex_delfunction(exarg_T *eap); void func_unref(char_u *name); void func_ptr_unref(ufunc_T *fp); --- 1,32 ---- /* userfunc.c */ void func_init(void); 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); int get_lambda_tv(char_u **arg, typval_T *rettv, int evaluate); 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); ! ufunc_T *find_func(char_u *name, cctx_T *cctx); ! int call_user_func_check(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rettv, funcexe_T *funcexe, dict_T *selfdict); void save_funccal(funccal_entry_T *entry); void restore_funccal(void); funccall_T *get_current_funccal(void); void free_all_functions(void); + int builtin_function(char_u *name, int len); int func_call(char_u *name, typval_T *args, partial_T *partial, dict_T *selfdict, typval_T *rettv); int get_callback_depth(void); int call_callback(callback_T *callback, int len, typval_T *rettv, int argcount, typval_T *argvars); + void user_func_error(int error, char_u *name); int call_func(char_u *funcname, int len, typval_T *rettv, int argcount_in, typval_T *argvars_in, funcexe_T *funcexe); char_u *trans_function_name(char_u **pp, int skip, int flags, funcdict_T *fdp, partial_T **partial); void ex_function(exarg_T *eap); int eval_fname_script(char_u *p); int translated_function_exists(char_u *name); + int has_varargs(ufunc_T *ufunc); int function_exists(char_u *name, int no_deref); char_u *get_expanded_name(char_u *name, int check); char_u *get_user_func_name(expand_T *xp, int idx); + void clean_script_functions(int sid); void ex_delfunction(exarg_T *eap); void func_unref(char_u *name); void func_ptr_unref(ufunc_T *fp); *** ../vim-8.2.0148/src/proto/vim9compile.pro 2020-01-26 15:45:56.681682435 +0100 --- src/proto/vim9compile.pro 2020-01-26 15:30:08.621766594 +0100 *************** *** 0 **** --- 1,14 ---- + /* vim9compile.c */ + char_u *skip_type(char_u *start); + type_T *parse_type(char_u **arg, garray_T *type_list); + char *vartype_name(vartype_T type); + char *type_name(type_T *type, char **tofree); + int get_script_item_idx(int sid, char_u *name, int check_writable); + imported_T *find_imported(char_u *name, cctx_T *cctx); + char_u *to_name_end(char_u *arg); + char_u *to_name_const_end(char_u *arg); + int assignment_len(char_u *p, int *heredoc); + void compile_def_function(ufunc_T *ufunc, int set_return_type); + void delete_def_function(ufunc_T *ufunc); + void free_def_functions(void); + /* vim: set ft=c : */ *** ../vim-8.2.0148/src/proto/vim9execute.pro 2020-01-26 15:45:56.685682416 +0100 --- src/proto/vim9execute.pro 2020-01-26 15:30:08.621766594 +0100 *************** *** 0 **** --- 1,6 ---- + /* vim9execute.c */ + int call_def_function(ufunc_T *ufunc, int argc, typval_T *argv, typval_T *rettv); + void ex_disassemble(exarg_T *eap); + int tv2bool(typval_T *tv); + int check_not_string(typval_T *tv); + /* vim: set ft=c : */ *** ../vim-8.2.0148/src/proto/vim9script.pro 2020-01-26 15:45:56.689682397 +0100 --- src/proto/vim9script.pro 2020-01-26 15:30:08.621766594 +0100 *************** *** 0 **** --- 1,8 ---- + /* vim9script.c */ + int in_vim9script(void); + void ex_vim9script(exarg_T *eap); + void ex_export(exarg_T *eap); + void free_imports(int sid); + void ex_import(exarg_T *eap); + char_u *handle_import(char_u *arg_start, garray_T *gap, int sid); + /* vim: set ft=c : */ *** ../vim-8.2.0148/src/scriptfile.c 2020-01-19 20:18:04.310312788 +0100 --- src/scriptfile.c 2020-01-26 15:30:08.621766594 +0100 *************** *** 182,190 **** } static void ! source_callback(char_u *fname, void *cookie UNUSED) { ! (void)do_source(fname, FALSE, DOSO_NONE); } /* --- 182,190 ---- } static void ! source_callback(char_u *fname, void *cookie) { ! (void)do_source(fname, FALSE, DOSO_NONE, cookie); } /* *************** *** 399,414 **** int source_runtime(char_u *name, int flags) { ! return source_in_path(p_rtp, name, flags); } /* * Just like source_runtime(), but use "path" instead of 'runtimepath'. */ int ! source_in_path(char_u *path, char_u *name, int flags) { ! return do_in_path_and_pp(path, name, flags, source_callback, NULL); } --- 399,414 ---- int source_runtime(char_u *name, int flags) { ! return source_in_path(p_rtp, name, flags, NULL); } /* * Just like source_runtime(), but use "path" instead of 'runtimepath'. */ int ! source_in_path(char_u *path, char_u *name, int flags, int *ret_sid) { ! return do_in_path_and_pp(path, name, flags, source_callback, ret_sid); } *************** *** 427,433 **** if (gen_expand_wildcards(1, &pat, &num_files, &files, EW_FILE) == OK) { for (i = 0; i < num_files; ++i) ! (void)do_source(files[i], FALSE, DOSO_NONE); FreeWild(num_files, files); } } --- 427,433 ---- if (gen_expand_wildcards(1, &pat, &num_files, &files, EW_FILE) == OK) { for (i = 0; i < num_files; ++i) ! (void)do_source(files[i], FALSE, DOSO_NONE, NULL); FreeWild(num_files, files); } } *************** *** 930,936 **** ); // ":source" read ex commands ! else if (do_source(fname, FALSE, DOSO_NONE) == FAIL) semsg(_(e_notopen), fname); } --- 930,936 ---- ); // ":source" read ex commands ! else if (do_source(fname, FALSE, DOSO_NONE, NULL) == FAIL) semsg(_(e_notopen), fname); } *************** *** 1063,1078 **** /* * do_source: Read the file "fname" and execute its lines as EX commands. * * This function may be called recursively! * ! * return FAIL if file could not be opened, OK otherwise */ int do_source( char_u *fname, int check_other, // check for .vimrc and _vimrc ! int is_vimrc) // DOSO_ value { struct source_cookie cookie; char_u *p; --- 1063,1082 ---- /* * do_source: Read the file "fname" and execute its lines as EX commands. + * When "ret_sid" is not NULL and we loaded the script before, don't load it + * again. * * This function may be called recursively! * ! * Return FAIL if file could not be opened, OK otherwise. ! * If a scriptitem_T was found or created "*ret_sid" is set to the SID. */ int do_source( char_u *fname, int check_other, // check for .vimrc and _vimrc ! int is_vimrc, // DOSO_ value ! int *ret_sid UNUSED) { struct source_cookie cookie; char_u *p; *************** *** 1085,1090 **** --- 1089,1095 ---- static int last_current_SID_seq = 0; funccal_entry_T funccalp_entry; int save_debug_break_level = debug_break_level; + int sid; scriptitem_T *si = NULL; # ifdef UNIX stat_T st; *************** *** 1114,1119 **** --- 1119,1155 ---- goto theend; } + #ifdef FEAT_EVAL + // See if we loaded this script before. + # ifdef UNIX + stat_ok = (mch_stat((char *)fname_exp, &st) >= 0); + # endif + for (sid = script_items.ga_len; sid > 0; --sid) + { + si = &SCRIPT_ITEM(sid); + if (si->sn_name != NULL) + { + # ifdef UNIX + // Compare dev/ino when possible, it catches symbolic links. Also + // compare file names, the inode may change when the file was + // edited or it may be re-used for another script (esp. in tests). + if ((stat_ok && si->sn_dev_valid) + && (si->sn_dev != st.st_dev || si->sn_ino != st.st_ino)) + continue; + # endif + if (fnamecmp(si->sn_name, fname_exp) == 0) + // Found it! + break; + } + } + if (sid > 0 && ret_sid != NULL) + { + // Already loaded and no need to load again, return here. + *ret_sid = sid; + return OK; + } + #endif + // Apply SourceCmd autocommands, they should get the file and source it. if (has_autocmd(EVENT_SOURCECMD, fname_exp, NULL) && apply_autocmds(EVENT_SOURCECMD, fname_exp, fname_exp, *************** *** 1239,1271 **** current_sctx.sc_version = 1; // default script version // Check if this script was sourced before to finds its SID. - // If it's new, generate a new SID. // Always use a new sequence number. current_sctx.sc_seq = ++last_current_SID_seq; ! # ifdef UNIX ! stat_ok = (mch_stat((char *)fname_exp, &st) >= 0); ! # endif ! for (current_sctx.sc_sid = script_items.ga_len; current_sctx.sc_sid > 0; ! --current_sctx.sc_sid) { ! si = &SCRIPT_ITEM(current_sctx.sc_sid); ! if (si->sn_name != NULL) ! { ! # ifdef UNIX ! // Compare dev/ino when possible, it catches symbolic links. Also ! // compare file names, the inode may change when the file was ! // edited or it may be re-used for another script (esp. in tests). ! if ((stat_ok && si->sn_dev_valid) ! && (si->sn_dev != st.st_dev || si->sn_ino != st.st_ino)) ! continue; ! # endif ! if (fnamecmp(si->sn_name, fname_exp) == 0) ! // Found it! ! break; ! } } ! if (current_sctx.sc_sid == 0) { current_sctx.sc_sid = ++last_current_SID; if (ga_grow(&script_items, (int)(current_sctx.sc_sid - script_items.ga_len)) == FAIL) --- 1275,1306 ---- current_sctx.sc_version = 1; // default script version // Check if this script was sourced before to finds its SID. // Always use a new sequence number. current_sctx.sc_seq = ++last_current_SID_seq; ! if (sid > 0) { ! hashtab_T *ht; ! hashitem_T *hi; ! dictitem_T *di; ! int todo; ! ! // loading the same script again ! si->sn_had_command = FALSE; ! current_sctx.sc_sid = sid; ! ! ht = &SCRIPT_VARS(sid); ! todo = (int)ht->ht_used; ! for (hi = ht->ht_array; todo > 0; ++hi) ! if (!HASHITEM_EMPTY(hi)) ! { ! --todo; ! di = HI2DI(hi); ! di->di_flags |= DI_FLAGS_RELOAD; ! } } ! else { + // It's new, generate a new SID. current_sctx.sc_sid = ++last_current_SID; if (ga_grow(&script_items, (int)(current_sctx.sc_sid - script_items.ga_len)) == FAIL) *************** *** 1273,1285 **** while (script_items.ga_len < current_sctx.sc_sid) { ++script_items.ga_len; ! SCRIPT_ITEM(script_items.ga_len).sn_name = NULL; ! SCRIPT_ITEM(script_items.ga_len).sn_version = 1; // Allocate the local script variables to use for this script. new_script_vars(script_items.ga_len); # ifdef FEAT_PROFILE ! SCRIPT_ITEM(script_items.ga_len).sn_prof_on = FALSE; # endif } si = &SCRIPT_ITEM(current_sctx.sc_sid); --- 1308,1324 ---- while (script_items.ga_len < current_sctx.sc_sid) { ++script_items.ga_len; ! si = &SCRIPT_ITEM(script_items.ga_len); ! si->sn_name = NULL; ! si->sn_version = 1; // Allocate the local script variables to use for this script. new_script_vars(script_items.ga_len); + ga_init2(&si->sn_var_vals, sizeof(typval_T), 10); + ga_init2(&si->sn_imports, sizeof(imported_T), 10); + ga_init2(&si->sn_type_list, sizeof(type_T), 10); # ifdef FEAT_PROFILE ! si->sn_prof_on = FALSE; # endif } si = &SCRIPT_ITEM(current_sctx.sc_sid); *************** *** 1295,1300 **** --- 1334,1341 ---- else si->sn_dev_valid = FALSE; # endif + if (ret_sid != NULL) + *ret_sid = current_sctx.sc_sid; } # ifdef FEAT_PROFILE *************** *** 1392,1397 **** --- 1433,1447 ---- #ifdef FEAT_EVAL almosttheend: + // Get "si" again, "script_items" may have been reallocated. + si = &SCRIPT_ITEM(current_sctx.sc_sid); + if (si->sn_save_cpo != NULL) + { + free_string_option(p_cpo); + p_cpo = si->sn_save_cpo; + si->sn_save_cpo = NULL; + } + current_sctx = save_current_sctx; restore_funccal(); # ifdef FEAT_PROFILE *************** *** 1488,1494 **** --- 1538,1546 ---- { // the variables themselves are cleared in evalvars_clear() vim_free(SCRIPT_ITEM(i).sn_vars); + vim_free(SCRIPT_ITEM(i).sn_name); + free_string_option(SCRIPT_ITEM(i).sn_save_cpo); # ifdef FEAT_PROFILE ga_clear(&SCRIPT_ITEM(i).sn_prl_ga); # endif *************** *** 1789,1794 **** --- 1841,1851 ---- emsg(_("E984: :scriptversion used outside of a sourced file")); return; } + if (current_sctx.sc_version == SCRIPT_VERSION_VIM9) + { + emsg(_("E1040: Cannot use :scriptversion after :vim9script")); + return; + } nr = getdigits(&eap->arg); if (nr == 0 || *eap->arg != NUL) *** ../vim-8.2.0148/src/session.c 2019-09-29 20:52:14.000000000 +0200 --- src/session.c 2020-01-26 15:30:08.621766594 +0100 *************** *** 980,986 **** fname = get_view_file(*eap->arg); if (fname != NULL) { ! do_source(fname, FALSE, DOSO_NONE); vim_free(fname); } } --- 980,986 ---- fname = get_view_file(*eap->arg); if (fname != NULL) { ! do_source(fname, FALSE, DOSO_NONE, NULL); vim_free(fname); } } *** ../vim-8.2.0148/src/structs.h 2020-01-12 17:42:52.410413873 +0100 --- src/structs.h 2020-01-26 15:30:08.621766594 +0100 *************** *** 67,72 **** --- 67,75 ---- typedef struct VimMenu vimmenu_T; #endif + // value for sc_version in a Vim9 script file + #define SCRIPT_VERSION_VIM9 999999 + /* * SCript ConteXt (SCTX): identifies a script line. * When sourcing a script "sc_lnum" is zero, "sourcing_lnum" is the current *************** *** 1298,1327 **** int cb_free_name; // cb_name was allocated } callback_T; typedef struct jobvar_S job_T; typedef struct readq_S readq_T; typedef struct writeq_S writeq_T; typedef struct jsonq_S jsonq_T; typedef struct cbq_S cbq_T; typedef struct channel_S channel_T; typedef enum { ! VAR_UNKNOWN = 0, ! VAR_NUMBER, // "v_number" is used ! VAR_STRING, // "v_string" is used ! VAR_FUNC, // "v_string" is function name ! VAR_PARTIAL, // "v_partial" is used ! VAR_LIST, // "v_list" is used ! VAR_DICT, // "v_dict" is used ! VAR_FLOAT, // "v_float" is used ! VAR_BOOL, // "v_number" is VVAL_FALSE or VVAL_TRUE ! VAR_SPECIAL, // "v_number" is VVAL_NONE or VVAL_NULL ! VAR_JOB, // "v_job" is used ! VAR_CHANNEL, // "v_channel" is used ! VAR_BLOB, // "v_blob" is used } vartype_T; /* * Structure to hold an internal variable without a name. */ --- 1301,1343 ---- int cb_free_name; // cb_name was allocated } callback_T; + typedef struct dfunc_S dfunc_T; // :def function + typedef struct jobvar_S job_T; typedef struct readq_S readq_T; typedef struct writeq_S writeq_T; typedef struct jsonq_S jsonq_T; typedef struct cbq_S cbq_T; typedef struct channel_S channel_T; + typedef struct cctx_S cctx_T; typedef enum { ! VAR_UNKNOWN = 0, // not set, also used for "any" type ! VAR_VOID, // no value ! VAR_BOOL, // "v_number" is used: VVAL_TRUE or VVAL_FALSE ! VAR_SPECIAL, // "v_number" is used: VVAL_NULL or VVAL_NONE ! VAR_NUMBER, // "v_number" is used ! VAR_FLOAT, // "v_float" is used ! VAR_STRING, // "v_string" is used ! VAR_BLOB, // "v_blob" is used ! VAR_FUNC, // "v_string" is function name ! VAR_PARTIAL, // "v_partial" is used ! VAR_LIST, // "v_list" is used ! VAR_DICT, // "v_dict" is used ! VAR_JOB, // "v_job" is used ! VAR_CHANNEL, // "v_channel" is used } vartype_T; + // A type specification. + typedef struct type_S type_T; + struct type_S { + vartype_T tt_type; + short tt_argcount; // for func, partial, -1 for unknown + type_T *tt_member; // for list, dict, func return type + type_T *tt_args; // func arguments + }; + /* * Structure to hold an internal variable without a name. */ *************** *** 1380,1398 **** /* * Structure to hold info about a list. * Order of members is optimized to reduce padding. */ struct listvar_S { listitem_T *lv_first; // first item, NULL if none - listitem_T *lv_last; // last item, NULL if none listwatch_T *lv_watch; // first watcher, NULL if none ! listitem_T *lv_idx_item; // when not NULL item at index "lv_idx" list_T *lv_copylist; // copied list used by deepcopy() list_T *lv_used_next; // next list in used lists list list_T *lv_used_prev; // previous list in used lists list int lv_refcount; // reference count int lv_len; // number of items ! int lv_idx; // cached index of an item int lv_copyID; // ID used by deepcopy() char lv_lock; // zero, VAR_LOCKED, VAR_FIXED }; --- 1396,1429 ---- /* * Structure to hold info about a list. * Order of members is optimized to reduce padding. + * When created by range() it will at first have special value: + * lv_first == &range_list_item; + * and use lv_start, lv_end, lv_stride. */ struct listvar_S { listitem_T *lv_first; // first item, NULL if none listwatch_T *lv_watch; // first watcher, NULL if none ! union { ! struct { // used for non-materialized range list: ! // "lv_first" is &range_list_item ! varnumber_T lv_start; ! varnumber_T lv_end; ! int lv_stride; ! }; ! struct { // used for materialized list ! listitem_T *lv_last; // last item, NULL if none ! listitem_T *lv_idx_item; // when not NULL item at index "lv_idx" ! int lv_idx; // cached index of an item ! }; ! }; list_T *lv_copylist; // copied list used by deepcopy() list_T *lv_used_next; // next list in used lists list list_T *lv_used_prev; // previous list in used lists list int lv_refcount; // reference count int lv_len; // number of items ! int lv_with_items; // number of items following this struct that ! // should not be freed int lv_copyID; // ID used by deepcopy() char lv_lock; // zero, VAR_LOCKED, VAR_FIXED }; *************** *** 1413,1419 **** struct dictitem_S { typval_T di_tv; // type and value of the variable ! char_u di_flags; // flags (only used for variable) char_u di_key[1]; // key (actually longer!) }; typedef struct dictitem_S dictitem_T; --- 1444,1450 ---- struct dictitem_S { typval_T di_tv; // type and value of the variable ! char_u di_flags; // DI_FLAGS_ flags (only used for variable) char_u di_key[1]; // key (actually longer!) }; typedef struct dictitem_S dictitem_T; *************** *** 1426,1441 **** struct dictitem16_S { typval_T di_tv; // type and value of the variable ! char_u di_flags; // flags (only used for variable) char_u di_key[DICTITEM16_KEY_LEN + 1]; // key }; typedef struct dictitem16_S dictitem16_T; ! #define DI_FLAGS_RO 1 // "di_flags" value: read-only variable ! #define DI_FLAGS_RO_SBX 2 // "di_flags" value: read-only in the sandbox ! #define DI_FLAGS_FIX 4 // "di_flags" value: fixed: no :unlet or remove() ! #define DI_FLAGS_LOCK 8 // "di_flags" value: locked variable ! #define DI_FLAGS_ALLOC 16 // "di_flags" value: separately allocated /* * Structure to hold info about a Dictionary. --- 1457,1474 ---- struct dictitem16_S { typval_T di_tv; // type and value of the variable ! char_u di_flags; // DI_FLAGS_ flags (only used for variable) char_u di_key[DICTITEM16_KEY_LEN + 1]; // key }; typedef struct dictitem16_S dictitem16_T; ! // Flags for "di_flags" ! #define DI_FLAGS_RO 0x01 // read-only variable ! #define DI_FLAGS_RO_SBX 0x02 // read-only in the sandbox ! #define DI_FLAGS_FIX 0x04 // fixed: no :unlet or remove() ! #define DI_FLAGS_LOCK 0x08 // locked variable ! #define DI_FLAGS_ALLOC 0x10 // separately allocated ! #define DI_FLAGS_RELOAD 0x20 // set when script sourced again /* * Structure to hold info about a Dictionary. *************** *** 1470,1481 **** */ typedef struct { ! int uf_varargs; // variable nr of arguments ! int uf_flags; int uf_calls; // nr of active calls int uf_cleared; // func_clear() was already called garray_T uf_args; // arguments garray_T uf_def_args; // default argument expressions garray_T uf_lines; // function lines # ifdef FEAT_PROFILE int uf_profiling; // TRUE when func is being profiled --- 1503,1523 ---- */ typedef struct { ! int uf_varargs; // variable nr of arguments (old style) ! int uf_flags; // FC_ flags int uf_calls; // nr of active calls int uf_cleared; // func_clear() was already called + int uf_dfunc_idx; // >= 0 for :def function only garray_T uf_args; // arguments garray_T uf_def_args; // default argument expressions + + // for :def (for :function uf_ret_type is NULL) + type_T **uf_arg_types; // argument types (count == uf_args.ga_len) + type_T *uf_ret_type; // return type + garray_T uf_type_list; // types used in arg and return types + char_u *uf_va_name; // name from "...name" or NULL + type_T *uf_va_type; // type from "...name: type" or NULL + garray_T uf_lines; // function lines # ifdef FEAT_PROFILE int uf_profiling; // TRUE when func is being profiled *************** *** 1578,1594 **** } scriptvar_T; /* * Growarray to store info about already sourced scripts. * For Unix also store the dev/ino, so that we don't have to stat() each * script when going through the list. */ typedef struct { scriptvar_T *sn_vars; // stores s: variables for this script ! char_u *sn_name; int sn_version; // :scriptversion # ifdef UNIX int sn_dev_valid; --- 1620,1669 ---- } scriptvar_T; /* + * Entry for "sn_var_vals". Used for script-local variables. + */ + typedef struct { + char_u *sv_name; // points into "sn_vars" di_key + typval_T *sv_tv; // points into "sn_vars" di_tv + type_T *sv_type; + int sv_const; + int sv_export; // "export let var = val" + } svar_T; + + typedef struct { + char_u *imp_name; // name imported as (allocated) + int imp_sid; // script ID of "from" + + // for "import * as Name", "imp_name" is "Name" + int imp_all; + + // for variable + type_T *imp_type; + int imp_var_vals_idx; // index in sn_var_vals of "from" + + // for function + char_u *imp_funcname; // user func name (NOT allocated) + } imported_T; + + /* * Growarray to store info about already sourced scripts. * For Unix also store the dev/ino, so that we don't have to stat() each * script when going through the list. */ typedef struct { + char_u *sn_name; + scriptvar_T *sn_vars; // stores s: variables for this script + garray_T sn_var_vals; // same variables as a list of svar_T ! garray_T sn_imports; // imported items, imported_T ! ! garray_T sn_type_list; // keeps types used by variables int sn_version; // :scriptversion + int sn_had_command; // TRUE if any command was executed + char_u *sn_save_cpo; // 'cpo' value when :vim9script found # ifdef UNIX int sn_dev_valid; *************** *** 3691,3696 **** --- 3766,3777 ---- EXPR_NOMATCH, // !~ EXPR_IS, // is EXPR_ISNOT, // isnot + // used with ISN_OPNR + EXPR_ADD, // + + EXPR_SUB, // - + EXPR_MULT, // * + EXPR_DIV, // / + EXPR_REM, // % } exptype_T; /* *************** *** 3804,3809 **** --- 3885,3892 ---- typedef struct lval_S { char_u *ll_name; // start of variable name (can be NULL) + char_u *ll_name_end; // end of variable name (can be NULL) + type_T *ll_type; // type of variable (can be NULL) char_u *ll_exp_name; // NULL or expanded name in allocated memory. typval_T *ll_tv; // Typeval of item being used. If "newkey" // isn't NULL it's the Dict to which to add *** ../vim-8.2.0148/src/syntax.c 2019-12-05 21:23:36.000000000 +0100 --- src/syntax.c 2020-01-26 15:30:08.621766594 +0100 *************** *** 4736,4742 **** current_syn_inc_tag = ++running_syn_inc_tag; prev_toplvl_grp = curwin->w_s->b_syn_topgrp; curwin->w_s->b_syn_topgrp = sgl_id; ! if (source ? do_source(eap->arg, FALSE, DOSO_NONE) == FAIL : source_runtime(eap->arg, DIP_ALL) == FAIL) semsg(_(e_notopen), eap->arg); curwin->w_s->b_syn_topgrp = prev_toplvl_grp; --- 4736,4742 ---- current_syn_inc_tag = ++running_syn_inc_tag; prev_toplvl_grp = curwin->w_s->b_syn_topgrp; curwin->w_s->b_syn_topgrp = sgl_id; ! if (source ? do_source(eap->arg, FALSE, DOSO_NONE, NULL) == FAIL : source_runtime(eap->arg, DIP_ALL) == FAIL) semsg(_(e_notopen), eap->arg); curwin->w_s->b_syn_topgrp = prev_toplvl_grp; *** ../vim-8.2.0148/src/testdir/Make_all.mak 2019-12-25 13:59:47.396893396 +0100 --- src/testdir/Make_all.mak 2020-01-26 15:30:08.621766594 +0100 *************** *** 90,99 **** test_digraph \ test_display \ test_edit \ test_erasebackword \ test_escaped_glob \ test_eval_stuff \ - test_environ \ test_ex_equal \ test_ex_undo \ test_ex_z \ --- 90,99 ---- test_digraph \ test_display \ test_edit \ + test_environ \ test_erasebackword \ test_escaped_glob \ test_eval_stuff \ test_ex_equal \ test_ex_undo \ test_ex_z \ *************** *** 268,273 **** --- 268,275 ---- test_utf8 \ test_utf8_comparisons \ test_vartabs \ + test_vim9_expr \ + test_vim9_script \ test_viminfo \ test_vimscript \ test_virtualedit \ *************** *** 435,440 **** --- 437,444 ---- test_user_func.res \ test_usercommands.res \ test_vartabs.res \ + test_vim9_expr.res \ + test_vim9_script.res \ test_viminfo.res \ test_vimscript.res \ test_visual.res \ *** ../vim-8.2.0148/src/testdir/test_vim9_expr.vim 2020-01-26 15:45:56.717682263 +0100 --- src/testdir/test_vim9_expr.vim 2020-01-26 15:30:08.621766594 +0100 *************** *** 0 **** --- 1,734 ---- + " Tests for Vim9 script expressions + + source check.vim + + " Check that "line" inside ":def" results in an "error" message. + func CheckDefFailure(line, error) + call writefile(['def! Func()', a:line, 'enddef'], 'Xdef') + call assert_fails('so Xdef', a:error, a:line) + call delete('Xdef') + endfunc + + func CheckDefFailureList(lines, error) + call writefile(['def! Func()'] + a:lines + ['enddef'], 'Xdef') + call assert_fails('so Xdef', a:error, string(a:lines)) + call delete('Xdef') + endfunc + + " test cond ? expr : expr + def Test_expr1() + assert_equal('one', true ? 'one' : 'two') + assert_equal('one', 1 ? 'one' : 'two') + assert_equal('one', 0.1 ? 'one' : 'two') + assert_equal('one', 'x' ? 'one' : 'two') + " assert_equal('one', 0z1234 ? 'one' : 'two') + assert_equal('one', [0] ? 'one' : 'two') + " assert_equal('one', #{x: 0} ? 'one' : 'two') + let var = 1 + assert_equal('one', var ? 'one' : 'two') + + assert_equal('two', false ? 'one' : 'two') + assert_equal('two', 0 ? 'one' : 'two') + assert_equal('two', 0.0 ? 'one' : 'two') + assert_equal('two', '' ? 'one' : 'two') + " assert_equal('one', 0z ? 'one' : 'two') + assert_equal('two', [] ? 'one' : 'two') + " assert_equal('two', {} ? 'one' : 'two') + var = 0 + assert_equal('two', var ? 'one' : 'two') + enddef + + func Test_expr1_fails() + call CheckDefFailure("let x = 1 ? 'one'", "Missing ':' after '?'") + + let msg = "white space required before and after '?'" + call CheckDefFailure("let x = 1? 'one' : 'two'", msg) + call CheckDefFailure("let x = 1 ?'one' : 'two'", msg) + call CheckDefFailure("let x = 1?'one' : 'two'", msg) + + let msg = "white space required before and after ':'" + call CheckDefFailure("let x = 1 ? 'one': 'two'", msg) + call CheckDefFailure("let x = 1 ? 'one' :'two'", msg) + call CheckDefFailure("let x = 1 ? 'one':'two'", msg) + endfunc + + " TODO: define inside test function + def Record(val: any): any + g:vals->add(val) + return val + enddef + + " test || + def Test_expr2() + assert_equal(2, 2 || 0) + assert_equal(7, 0 || 0 || 7) + assert_equal(0, 0 || 0) + assert_equal('', 0 || '') + + g:vals = [] + assert_equal(3, Record(3) || Record(1)) + assert_equal([3], g:vals) + + g:vals = [] + assert_equal(5, Record(0) || Record(5)) + assert_equal([0, 5], g:vals) + + g:vals = [] + assert_equal(4, Record(0) || Record(4) || Record(0)) + assert_equal([0, 4], g:vals) + + g:vals = [] + assert_equal(0, Record([]) || Record('') || Record(0)) + assert_equal([[], '', 0], g:vals) + enddef + + func Test_expr2_fails() + let msg = "white space required before and after '||'" + call CheckDefFailure("let x = 1||2", msg) + call CheckDefFailure("let x = 1 ||2", msg) + call CheckDefFailure("let x = 1|| 2", msg) + endfunc + + " test && + def Test_expr3() + assert_equal(0, 2 && 0) + assert_equal(0, 0 && 0 && 7) + assert_equal(7, 2 && 3 && 7) + assert_equal(0, 0 && 0) + assert_equal(0, 0 && '') + assert_equal('', 8 && '') + + g:vals = [] + assert_equal(1, Record(3) && Record(1)) + assert_equal([3, 1], g:vals) + + g:vals = [] + assert_equal(0, Record(0) && Record(5)) + assert_equal([0], g:vals) + + g:vals = [] + assert_equal(0, Record(0) && Record(4) && Record(0)) + assert_equal([0], g:vals) + + g:vals = [] + assert_equal(0, Record(8) && Record(4) && Record(0)) + assert_equal([8, 4, 0], g:vals) + + g:vals = [] + assert_equal(0, Record([1]) && Record('z') && Record(0)) + assert_equal([[1], 'z', 0], g:vals) + enddef + + func Test_expr3_fails() + let msg = "white space required before and after '&&'" + call CheckDefFailure("let x = 1&&2", msg) + call CheckDefFailure("let x = 1 &&2", msg) + call CheckDefFailure("let x = 1&& 2", msg) + endfunc + + let atrue = v:true + let afalse = v:false + let anone = v:none + let anull = v:null + let anint = 10 + let alsoint = 4 + if has('float') + let afloat = 0.1 + endif + let astring = 'asdf' + let ablob = 0z01ab + let alist = [2, 3, 4] + let adict = #{aaa: 2, bbb: 8} + + " test == comperator + def Test_expr4_equal() + assert_equal(true, true == true) + assert_equal(false, true == false) + assert_equal(true, true == g:atrue) + assert_equal(false, g:atrue == false) + + assert_equal(true, v:none == v:none) + assert_equal(false, v:none == v:null) + assert_equal(true, g:anone == v:none) + assert_equal(false, v:none == g:anull) + + assert_equal(false, 2 == 0) + assert_equal(true, 61 == 61) + assert_equal(true, g:anint == 10) + assert_equal(false, 61 == g:anint) + + if has('float') + assert_equal(true, 0.3 == 0.3) + assert_equal(false, 0.4 == 0.3) + assert_equal(true, 0.1 == g:afloat) + assert_equal(false, g:afloat == 0.3) + + assert_equal(true, 3.0 == 3) + assert_equal(true, 3 == 3.0) + assert_equal(false, 3.1 == 3) + assert_equal(false, 3 == 3.1) + endif + + assert_equal(true, 'abc' == 'abc') + assert_equal(false, 'xyz' == 'abc') + assert_equal(true, g:astring == 'asdf') + assert_equal(false, 'xyz' == g:astring) + + assert_equal(false, 'abc' == 'ABC') + set ignorecase + assert_equal(false, 'abc' == 'ABC') + set noignorecase + + assert_equal(true, 0z3f == 0z3f) + assert_equal(false, 0z3f == 0z4f) + assert_equal(true, g:ablob == 0z01ab) + assert_equal(false, 0z3f == g:ablob) + + assert_equal(true, [1, 2, 3] == [1, 2, 3]) + assert_equal(false, [1, 2, 3] == [2, 3, 1]) + assert_equal(true, [2, 3, 4] == g:alist) + assert_equal(false, g:alist == [2, 3, 1]) + assert_equal(false, [1, 2, 3] == []) + assert_equal(false, [1, 2, 3] == ['1', '2', '3']) + + assert_equal(true, #{one: 1, two: 2} == #{one: 1, two: 2}) + assert_equal(false, #{one: 1, two: 2} == #{one: 2, two: 2}) + assert_equal(false, #{one: 1, two: 2} == #{two: 2}) + assert_equal(false, #{one: 1, two: 2} == #{}) + assert_equal(true, g:adict == #{bbb: 8, aaa: 2}) + assert_equal(false, #{ccc: 9, aaa: 2} == g:adict) + + assert_equal(true, function('Test_expr4_equal') == function('Test_expr4_equal')) + assert_equal(false, function('Test_expr4_equal') == function('Test_expr4_is')) + + assert_equal(true, function('Test_expr4_equal', [123]) == function('Test_expr4_equal', [123])) + assert_equal(false, function('Test_expr4_equal', [123]) == function('Test_expr4_is', [123])) + assert_equal(false, function('Test_expr4_equal', [123]) == function('Test_expr4_equal', [999])) + enddef + + " test != comperator + def Test_expr4_notequal() + assert_equal(false, true != true) + assert_equal(true, true != false) + assert_equal(false, true != g:atrue) + assert_equal(true, g:atrue != false) + + assert_equal(false, v:none != v:none) + assert_equal(true, v:none != v:null) + assert_equal(false, g:anone != v:none) + assert_equal(true, v:none != g:anull) + + assert_equal(true, 2 != 0) + assert_equal(false, 55 != 55) + assert_equal(false, g:anint != 10) + assert_equal(true, 61 != g:anint) + + if has('float') + assert_equal(false, 0.3 != 0.3) + assert_equal(true, 0.4 != 0.3) + assert_equal(false, 0.1 != g:afloat) + assert_equal(true, g:afloat != 0.3) + + assert_equal(false, 3.0 != 3) + assert_equal(false, 3 != 3.0) + assert_equal(true, 3.1 != 3) + assert_equal(true, 3 != 3.1) + endif + + assert_equal(false, 'abc' != 'abc') + assert_equal(true, 'xyz' != 'abc') + assert_equal(false, g:astring != 'asdf') + assert_equal(true, 'xyz' != g:astring) + + assert_equal(true, 'abc' != 'ABC') + set ignorecase + assert_equal(true, 'abc' != 'ABC') + set noignorecase + + assert_equal(false, 0z3f != 0z3f) + assert_equal(true, 0z3f != 0z4f) + assert_equal(false, g:ablob != 0z01ab) + assert_equal(true, 0z3f != g:ablob) + + assert_equal(false, [1, 2, 3] != [1, 2, 3]) + assert_equal(true, [1, 2, 3] != [2, 3, 1]) + assert_equal(false, [2, 3, 4] != g:alist) + assert_equal(true, g:alist != [2, 3, 1]) + assert_equal(true, [1, 2, 3] != []) + assert_equal(true, [1, 2, 3] != ['1', '2', '3']) + + assert_equal(false, #{one: 1, two: 2} != #{one: 1, two: 2}) + assert_equal(true, #{one: 1, two: 2} != #{one: 2, two: 2}) + assert_equal(true, #{one: 1, two: 2} != #{two: 2}) + assert_equal(true, #{one: 1, two: 2} != #{}) + assert_equal(false, g:adict != #{bbb: 8, aaa: 2}) + assert_equal(true, #{ccc: 9, aaa: 2} != g:adict) + + assert_equal(false, function('Test_expr4_equal') != function('Test_expr4_equal')) + assert_equal(true, function('Test_expr4_equal') != function('Test_expr4_is')) + + assert_equal(false, function('Test_expr4_equal', [123]) != function('Test_expr4_equal', [123])) + assert_equal(true, function('Test_expr4_equal', [123]) != function('Test_expr4_is', [123])) + assert_equal(true, function('Test_expr4_equal', [123]) != function('Test_expr4_equal', [999])) + enddef + + " test > comperator + def Test_expr4_greater() + assert_equal(true, 2 > 0) + assert_equal(true, 2 > 1) + assert_equal(false, 2 > 2) + assert_equal(false, 2 > 3) + enddef + + " test >= comperator + def Test_expr4_greaterequal() + assert_equal(true, 2 >= 0) + assert_equal(true, 2 >= 2) + assert_equal(false, 2 >= 3) + enddef + + " test < comperator + def Test_expr4_smaller() + assert_equal(false, 2 < 0) + assert_equal(false, 2 < 2) + assert_equal(true, 2 < 3) + enddef + + " test <= comperator + def Test_expr4_smallerequal() + assert_equal(false, 2 <= 0) + assert_equal(false, 2 <= 1) + assert_equal(true, 2 <= 2) + assert_equal(true, 2 <= 3) + enddef + + " test =~ comperator + def Test_expr4_match() + assert_equal(false, '2' =~ '0') + assert_equal(true, '2' =~ '[0-9]') + enddef + + " test !~ comperator + def Test_expr4_nomatch() + assert_equal(true, '2' !~ '0') + assert_equal(false, '2' !~ '[0-9]') + enddef + + " test is comperator + def Test_expr4_is() + let mylist = [2] + assert_equal(false, mylist is [2]) + let other = mylist + assert_equal(true, mylist is other) + enddef + + " test isnot comperator + def Test_expr4_isnot() + let mylist = [2] + assert_equal(true, '2' isnot '0') + assert_equal(true, mylist isnot [2]) + let other = mylist + assert_equal(false, mylist isnot other) + enddef + + def RetVoid() + let x = 1 + enddef + + func Test_expr4_fails() + let msg = "white space required before and after '>'" + call CheckDefFailure("let x = 1>2", msg) + call CheckDefFailure("let x = 1 >2", msg) + call CheckDefFailure("let x = 1> 2", msg) + + let msg = "white space required before and after '=='" + call CheckDefFailure("let x = 1==2", msg) + call CheckDefFailure("let x = 1 ==2", msg) + call CheckDefFailure("let x = 1== 2", msg) + + let msg = "white space required before and after 'is'" + call CheckDefFailure("let x = '1'is'2'", msg) + call CheckDefFailure("let x = '1' is'2'", msg) + call CheckDefFailure("let x = '1'is '2'", msg) + + let msg = "white space required before and after 'isnot'" + call CheckDefFailure("let x = '1'isnot'2'", msg) + call CheckDefFailure("let x = '1' isnot'2'", msg) + call CheckDefFailure("let x = '1'isnot '2'", msg) + + call CheckDefFailure("let x = 1 is# 2", 'E15:') + call CheckDefFailure("let x = 1 is? 2", 'E15:') + call CheckDefFailure("let x = 1 isnot# 2", 'E15:') + call CheckDefFailure("let x = 1 isnot? 2", 'E15:') + + call CheckDefFailure("let x = 1 == '2'", 'Cannot compare number with string') + call CheckDefFailure("let x = '1' == 2", 'Cannot compare string with number') + call CheckDefFailure("let x = 1 == RetVoid()", 'Cannot use void value') + call CheckDefFailure("let x = RetVoid() == 1", 'Cannot compare void with number') + + call CheckDefFailure("let x = true > false", 'Cannot compare bool with bool') + call CheckDefFailure("let x = true >= false", 'Cannot compare bool with bool') + call CheckDefFailure("let x = true < false", 'Cannot compare bool with bool') + call CheckDefFailure("let x = true <= false", 'Cannot compare bool with bool') + call CheckDefFailure("let x = true =~ false", 'Cannot compare bool with bool') + call CheckDefFailure("let x = true !~ false", 'Cannot compare bool with bool') + call CheckDefFailure("let x = true is false", 'Cannot use "is" with bool') + call CheckDefFailure("let x = true isnot false", 'Cannot use "isnot" with bool') + + call CheckDefFailure("let x = v:none is v:null", 'Cannot use "is" with special') + call CheckDefFailure("let x = v:none isnot v:null", 'Cannot use "isnot" with special') + call CheckDefFailure("let x = 123 is 123", 'Cannot use "is" with number') + call CheckDefFailure("let x = 123 isnot 123", 'Cannot use "isnot" with number') + if has('float') + call CheckDefFailure("let x = 1.3 is 1.3", 'Cannot use "is" with float') + call CheckDefFailure("let x = 1.3 isnot 1.3", 'Cannot use "isnot" with float') + endif + + call CheckDefFailure("let x = 0za1 > 0z34", 'Cannot compare blob with blob') + call CheckDefFailure("let x = 0za1 >= 0z34", 'Cannot compare blob with blob') + call CheckDefFailure("let x = 0za1 < 0z34", 'Cannot compare blob with blob') + call CheckDefFailure("let x = 0za1 <= 0z34", 'Cannot compare blob with blob') + call CheckDefFailure("let x = 0za1 =~ 0z34", 'Cannot compare blob with blob') + call CheckDefFailure("let x = 0za1 !~ 0z34", 'Cannot compare blob with blob') + + call CheckDefFailure("let x = [13] > [88]", 'Cannot compare list with list') + call CheckDefFailure("let x = [13] >= [88]", 'Cannot compare list with list') + call CheckDefFailure("let x = [13] < [88]", 'Cannot compare list with list') + call CheckDefFailure("let x = [13] <= [88]", 'Cannot compare list with list') + call CheckDefFailure("let x = [13] =~ [88]", 'Cannot compare list with list') + call CheckDefFailure("let x = [13] !~ [88]", 'Cannot compare list with list') + endfunc + + " test addition, subtraction, concatenation + def Test_expr5() + assert_equal(66, 60 + 6) + assert_equal(70, 60 + g:anint) + assert_equal(9, g:alsoint + 5) + assert_equal(14, g:alsoint + g:anint) + + assert_equal(54, 60 - 6) + assert_equal(50, 60 - g:anint) + assert_equal(-1, g:alsoint - 5) + assert_equal(-6, g:alsoint - g:anint) + + assert_equal('hello', 'hel' .. 'lo') + assert_equal('hello 123', 'hello ' .. 123) + assert_equal('123 hello', 123 .. ' hello') + assert_equal('123456', 123 .. 456) + enddef + + def Test_expr5_float() + CheckFeature float + assert_equal(66.0, 60.0 + 6.0) + assert_equal(66.0, 60.0 + 6) + assert_equal(66.0, 60 + 6.0) + assert_equal(5.1, g:afloat + 5) + assert_equal(8.1, 8 + g:afloat) + assert_equal(10.1, g:anint + g:afloat) + assert_equal(10.1, g:afloat + g:anint) + + assert_equal(54.0, 60.0 - 6.0) + assert_equal(54.0, 60.0 - 6) + assert_equal(54.0, 60 - 6.0) + assert_equal(-4.9, g:afloat - 5) + assert_equal(7.9, 8 - g:afloat) + assert_equal(9.9, g:anint - g:afloat) + assert_equal(-9.9, g:afloat - g:anint) + enddef + + func Test_expr5_fails() + let msg = "white space required before and after '+'" + call CheckDefFailure("let x = 1+2", msg) + call CheckDefFailure("let x = 1 +2", msg) + call CheckDefFailure("let x = 1+ 2", msg) + + let msg = "white space required before and after '-'" + call CheckDefFailure("let x = 1-2", msg) + call CheckDefFailure("let x = 1 -2", msg) + call CheckDefFailure("let x = 1- 2", msg) + + let msg = "white space required before and after '..'" + call CheckDefFailure("let x = '1'..'2'", msg) + call CheckDefFailure("let x = '1' ..'2'", msg) + call CheckDefFailure("let x = '1'.. '2'", msg) + endfunc + + " test multiply, divide, modulo + def Test_expr6() + assert_equal(36, 6 * 6) + assert_equal(24, 6 * g:alsoint) + assert_equal(24, g:alsoint * 6) + assert_equal(40, g:anint * g:alsoint) + + assert_equal(10, 60 / 6) + assert_equal(6, 60 / g:anint) + assert_equal(1, g:anint / 6) + assert_equal(2, g:anint / g:alsoint) + + assert_equal(5, 11 % 6) + assert_equal(4, g:anint % 6) + assert_equal(3, 13 % g:anint) + assert_equal(2, g:anint % g:alsoint) + + assert_equal(4, 6 * 4 / 6) + enddef + + def Test_expr6_float() + CheckFeature float + + assert_equal(36.0, 6.0 * 6) + assert_equal(36.0, 6 * 6.0) + assert_equal(36.0, 6.0 * 6.0) + assert_equal(1.0, g:afloat * g:anint) + + assert_equal(10.0, 60 / 6.0) + assert_equal(10.0, 60.0 / 6) + assert_equal(10.0, 60.0 / 6.0) + assert_equal(0.01, g:afloat / g:anint) + + assert_equal(4.0, 6.0 * 4 / 6) + assert_equal(4.0, 6 * 4.0 / 6) + assert_equal(4.0, 6 * 4 / 6.0) + assert_equal(4.0, 6.0 * 4.0 / 6) + assert_equal(4.0, 6 * 4.0 / 6.0) + assert_equal(4.0, 6.0 * 4 / 6.0) + assert_equal(4.0, 6.0 * 4.0 / 6.0) + + assert_equal(4.0, 6.0 * 4.0 / 6.0) + enddef + + func Test_expr6_fails() + let msg = "white space required before and after '*'" + call CheckDefFailure("let x = 1*2", msg) + call CheckDefFailure("let x = 1 *2", msg) + call CheckDefFailure("let x = 1* 2", msg) + + let msg = "white space required before and after '/'" + call CheckDefFailure("let x = 1/2", msg) + call CheckDefFailure("let x = 1 /2", msg) + call CheckDefFailure("let x = 1/ 2", msg) + + let msg = "white space required before and after '%'" + call CheckDefFailure("let x = 1%2", msg) + call CheckDefFailure("let x = 1 %2", msg) + call CheckDefFailure("let x = 1% 2", msg) + + call CheckDefFailure("let x = '1' * '2'", 'E1036:') + call CheckDefFailure("let x = '1' / '2'", 'E1036:') + call CheckDefFailure("let x = '1' % '2'", 'E1035:') + + call CheckDefFailure("let x = 0z01 * 0z12", 'E1036:') + call CheckDefFailure("let x = 0z01 / 0z12", 'E1036:') + call CheckDefFailure("let x = 0z01 % 0z12", 'E1035:') + + call CheckDefFailure("let x = [1] * [2]", 'E1036:') + call CheckDefFailure("let x = [1] / [2]", 'E1036:') + call CheckDefFailure("let x = [1] % [2]", 'E1035:') + + call CheckDefFailure("let x = #{one: 1} * #{two: 2}", 'E1036:') + call CheckDefFailure("let x = #{one: 1} / #{two: 2}", 'E1036:') + call CheckDefFailure("let x = #{one: 1} % #{two: 2}", 'E1035:') + + endfunc + + func Test_expr6_float_fails() + CheckFeature float + call CheckDefFailure("let x = 1.0 % 2", 'E1035:') + endfunc + + " define here to use old style parsing + if has('float') + let g:float_zero = 0.0 + let g:float_neg = -9.8 + let g:float_big = 9.9e99 + endif + let g:blob_empty = 0z + let g:blob_one = 0z01 + let g:blob_long = 0z0102.0304 + + let g:string_empty = '' + let g:string_short = 'x' + let g:string_long = 'abcdefghijklm' + let g:string_special = "ab\ncd\ref\ekk" + + let g:special_true = v:true + let g:special_false = v:false + let g:special_null = v:null + let g:special_none = v:none + + let g:list_empty = [] + let g:list_mixed = [1, 'b', v:false] + + let g:dict_empty = {} + let g:dict_one = #{one: 1} + + let $TESTVAR = 'testvar' + + let @a = 'register a' + + " test low level expression + def Test_expr7_number() + " number constant + assert_equal(0, 0) + assert_equal(654, 0654) + + assert_equal(6, 0x6) + assert_equal(15, 0xf) + assert_equal(255, 0xff) + enddef + + def Test_expr7_float() + " float constant + if has('float') + assert_equal(g:float_zero, .0) + assert_equal(g:float_zero, 0.0) + assert_equal(g:float_neg, -9.8) + assert_equal(g:float_big, 9.9e99) + endif + enddef + + def Test_expr7_blob() + " blob constant + assert_equal(g:blob_empty, 0z) + assert_equal(g:blob_one, 0z01) + assert_equal(g:blob_long, 0z0102.0304) + enddef + + def Test_expr7_string() + " string constant + assert_equal(g:string_empty, '') + assert_equal(g:string_empty, "") + assert_equal(g:string_short, 'x') + assert_equal(g:string_short, "x") + assert_equal(g:string_long, 'abcdefghijklm') + assert_equal(g:string_long, "abcdefghijklm") + assert_equal(g:string_special, "ab\ncd\ref\ekk") + enddef + + def Test_expr7_special() + " special constant + assert_equal(g:special_true, true) + assert_equal(g:special_false, false) + assert_equal(g:special_null, v:null) + assert_equal(g:special_none, v:none) + enddef + + def Test_expr7_list() + " list + assert_equal(g:list_empty, []) + assert_equal(g:list_empty, [ ]) + assert_equal(g:list_mixed, [1, 'b', false]) + enddef + + def Test_expr7_lambda() + " lambda + let La = { -> 'result'} + assert_equal('result', La()) + assert_equal([1, 3, 5], [1, 2, 3]->map({key, val -> key + val})) + enddef + + def Test_expr7_dict() + " dictionary + assert_equal(g:dict_empty, {}) + assert_equal(g:dict_empty, { }) + assert_equal(g:dict_one, {'one': 1}) + let key = 'one' + let val = 1 + assert_equal(g:dict_one, {key: val}) + enddef + + def Test_expr7_option() + " option + set ts=11 + assert_equal(11, &ts) + set ts=8 + set grepprg=some\ text + assert_equal('some text', &grepprg) + set grepprg& + enddef + + def Test_expr7_environment() + " environment variable + assert_equal('testvar', $TESTVAR) + assert_equal('', $ASDF_ASD_XXX) + enddef + + def Test_expr7_register() + " register + assert_equal('register a', @a) + enddef + + def Test_expr7_parens() + " (expr) + assert_equal(4, (6 * 4) / 6) + assert_equal(0, 6 * ( 4 / 6 )) + + assert_equal(6, +6) + assert_equal(-6, -6) + assert_equal(6, --6) + assert_equal(6, -+-6) + assert_equal(-6, ---6) + enddef + + def Test_expr7_not() + assert_equal(true, !'') + assert_equal(true, ![]) + assert_equal(false, !'asdf') + assert_equal(false, ![2]) + assert_equal(true, !!'asdf') + assert_equal(true, !![2]) + enddef + + func Test_expr7_fails() + call CheckDefFailure("let x = (12", "E110:") + + call CheckDefFailure("let x = -'xx'", "E1030:") + call CheckDefFailure("let x = +'xx'", "E1030:") + + call CheckDefFailure("let x = @", "E1002:") + call CheckDefFailure("let x = @<", "E354:") + endfunc + + let g:Funcrefs = [function('add')] + + func CallMe(arg) + return a:arg + endfunc + + def Test_expr7_trailing() + " user function call + assert_equal(123, CallMe(123)) + assert_equal('nothing', CallMe('nothing')) + + " partial call + let Part = function('CallMe') + assert_equal('yes', Part('yes')) + + " funcref call, using list index + let l = [] + g:Funcrefs[0](l, 2) + assert_equal([2], l) + + " method call + l = [2, 5, 6] + l->map({k, v -> k + v}) + assert_equal([2, 6, 8], l) + + " lambda method call + l = [2, 5] + l->{l -> add(l, 8)}() + assert_equal([2, 5, 8], l) + + " dict member + let d = #{key: 123} + assert_equal(123, d.key) + enddef + + func Test_expr7_trailing_fails() + call CheckDefFailureList(['let l = [2]', 'l->{l -> add(l, 8)}'], 'E107') + endfunc + + func Test_expr_fails() + call CheckDefFailure("let x = '1'is2", 'E488:') + call CheckDefFailure("let x = '1'isnot2", 'E488:') + endfunc *** ../vim-8.2.0148/src/testdir/test_vim9_script.vim 2020-01-26 15:45:56.721682244 +0100 --- src/testdir/test_vim9_script.vim 2020-01-26 15:30:08.621766594 +0100 *************** *** 0 **** --- 1,359 ---- + " Test various aspects of the Vim9 script language. + + " Check that "lines" inside ":def" results in an "error" message. + func CheckDefFailure(lines, error) + call writefile(['def! Func()'] + a:lines + ['enddef'], 'Xdef') + call assert_fails('so Xdef', a:error, a:lines) + call delete('Xdef') + endfunc + + func CheckScriptFailure(lines, error) + call writefile(a:lines, 'Xdef') + call assert_fails('so Xdef', a:error, a:lines) + call delete('Xdef') + endfunc + + def Test_syntax() + let var = 234 + let other: list = ['asdf'] + enddef + + func Test_def_basic() + def SomeFunc(): string + return 'yes' + enddef + call assert_equal('yes', SomeFunc()) + endfunc + + def Test_assignment() + let bool1: bool = true + assert_equal(v:true, bool1) + let bool2: bool = false + assert_equal(v:false, bool2) + + let list1: list = ['sdf', 'asdf'] + let list2: list = [1, 2, 3] + + " TODO: does not work yet + " let listS: list = [] + " let listN: list = [] + + let dict1: dict = #{key: 'value'} + let dict2: dict = #{one: 1, two: 2} + enddef + + func Test_assignment_failure() + call CheckDefFailure(['let var=234'], 'E1004:') + call CheckDefFailure(['let var =234'], 'E1004:') + call CheckDefFailure(['let var= 234'], 'E1004:') + + call CheckDefFailure(['let true = 1'], 'E1034:') + call CheckDefFailure(['let false = 1'], 'E1034:') + + call CheckDefFailure(['let var: list = [123]'], 'expected list but got list') + call CheckDefFailure(['let var: list = ["xx"]'], 'expected list but got list') + + call CheckDefFailure(['let var: dict = #{key: 123}'], 'expected dict but got dict') + call CheckDefFailure(['let var: dict = #{key: "xx"}'], 'expected dict but got dict') + + call CheckDefFailure(['let var = feedkeys("0")'], 'E1031:') + call CheckDefFailure(['let var: number = feedkeys("0")'], 'expected number but got void') + endfunc + + func Test_const() + call CheckDefFailure(['const var = 234', 'var = 99'], 'E1018:') + call CheckDefFailure(['const one = 234', 'let one = 99'], 'E1017:') + call CheckDefFailure(['const two'], 'E1021:') + endfunc + + def Test_block() + let outer = 1 + { + let inner = 2 + assert_equal(1, outer) + assert_equal(2, inner) + } + assert_equal(1, outer) + enddef + + func Test_block_failure() + call CheckDefFailure(['{', 'let inner = 1', '}', 'echo inner'], 'E1001:') + endfunc + + def ReturnString(): string + return 'string' + enddef + + def ReturnNumber(): number + return 123 + enddef + + def Test_return_string() + assert_equal('string', ReturnString()) + assert_equal(123, ReturnNumber()) + enddef + + func Increment() + let g:counter += 1 + endfunc + + def Test_call_ufunc_count() + g:counter = 1 + Increment() + Increment() + Increment() + " works with and without :call + assert_equal(4, g:counter) + call assert_equal(4, g:counter) + unlet g:counter + enddef + + def MyVarargs(arg: string, ...rest: list): string + let res = arg + for s in rest + res ..= ',' .. s + endfor + return res + enddef + + def Test_call_varargs() + assert_equal('one', MyVarargs('one')) + assert_equal('one,two', MyVarargs('one', 'two')) + assert_equal('one,two,three', MyVarargs('one', 'two', 'three')) + enddef + + def Test_return_type_wrong() + " TODO: why is ! needed for Mac and FreeBSD? + CheckScriptFailure(['def! Func(): number', 'return "a"', 'enddef'], 'expected number but got string') + CheckScriptFailure(['def! Func(): string', 'return 1', 'enddef'], 'expected string but got number') + CheckScriptFailure(['def! Func(): void', 'return "a"', 'enddef'], 'expected void but got string') + CheckScriptFailure(['def! Func()', 'return "a"', 'enddef'], 'expected void but got string') + enddef + + def Test_try_catch() + let l = [] + try + add(l, '1') + throw 'wrong' + add(l, '2') + catch + add(l, v:exception) + finally + add(l, '3') + endtry + assert_equal(['1', 'wrong', '3'], l) + enddef + + let s:export_script_lines =<< trim END + vim9script + let name: string = 'bob' + def Concat(arg: string): string + return name .. arg + enddef + let g:result = Concat('bie') + let g:localname = name + + export const CONST = 1234 + export let exported = 9876 + export def Exported(): string + return 'Exported' + enddef + END + + def Test_vim9script() + let import_script_lines =<< trim END + vim9script + import {exported, Exported} from './Xexport.vim' + g:imported = exported + g:imported_func = Exported() + END + + writefile(import_script_lines, 'Ximport.vim') + writefile(s:export_script_lines, 'Xexport.vim') + + source Ximport.vim + + assert_equal('bobbie', g:result) + assert_equal('bob', g:localname) + assert_equal(9876, g:imported) + assert_equal('Exported', g:imported_func) + assert_false(exists('g:name')) + + unlet g:result + unlet g:localname + unlet g:imported + unlet g:imported_func + delete('Ximport.vim') + delete('Xexport.vim') + + CheckScriptFailure(['scriptversion 2', 'vim9script'], 'E1039:') + CheckScriptFailure(['vim9script', 'scriptversion 2'], 'E1040:') + enddef + + def Test_vim9script_call() + let lines =<< trim END + vim9script + let var = '' + def MyFunc(arg: string) + var = arg + enddef + MyFunc('foobar') + assert_equal('foobar', var) + + let str = 'barfoo' + str->MyFunc() + assert_equal('barfoo', var) + + let g:value = 'value' + g:value->MyFunc() + assert_equal('value', var) + + let listvar = [] + def ListFunc(arg: list) + listvar = arg + enddef + [1, 2, 3]->ListFunc() + assert_equal([1, 2, 3], listvar) + + let dictvar = {} + def DictFunc(arg: dict) + dictvar = arg + enddef + {'a': 1, 'b': 2}->DictFunc() + assert_equal(#{a: 1, b: 2}, dictvar) + #{a: 3, b: 4}->DictFunc() + assert_equal(#{a: 3, b: 4}, dictvar) + END + writefile(lines, 'Xcall.vim') + source Xcall.vim + delete('Xcall.vim') + enddef + + def Test_vim9script_call_fail_decl() + let lines =<< trim END + vim9script + let var = '' + def MyFunc(arg: string) + let var = 123 + enddef + END + writefile(lines, 'Xcall_decl.vim') + assert_fails('source Xcall_decl.vim', 'E1054:') + delete('Xcall_decl.vim') + enddef + + def Test_vim9script_call_fail_const() + let lines =<< trim END + vim9script + const var = '' + def MyFunc(arg: string) + var = 'asdf' + enddef + END + writefile(lines, 'Xcall_const.vim') + assert_fails('source Xcall_const.vim', 'E46:') + delete('Xcall_const.vim') + enddef + + def Test_vim9script_reload() + let lines =<< trim END + vim9script + const var = '' + let valone = 1234 + def MyFunc(arg: string) + valone = 5678 + enddef + END + let morelines =<< trim END + let valtwo = 222 + export def GetValtwo(): number + return valtwo + enddef + END + writefile(lines + morelines, 'Xreload.vim') + source Xreload.vim + source Xreload.vim + source Xreload.vim + + let testlines =<< trim END + vim9script + def TheFunc() + import GetValtwo from './Xreload.vim' + assert_equal(222, GetValtwo()) + enddef + TheFunc() + END + writefile(testlines, 'Ximport.vim') + source Ximport.vim + + " test that when not using "morelines" valtwo is still defined + " need to source Xreload.vim again, import doesn't reload a script + writefile(lines, 'Xreload.vim') + source Xreload.vim + source Ximport.vim + + " cannot declare a var twice + lines =<< trim END + vim9script + let valone = 1234 + let valone = 5678 + END + writefile(lines, 'Xreload.vim') + assert_fails('source Xreload.vim', 'E1041:') + + delete('Xreload.vim') + delete('Ximport.vim') + enddef + + def Test_import_absolute() + let import_lines = [ + \ 'vim9script', + \ 'import exported from "' .. escape(getcwd(), '\') .. '/Xexport_abs.vim"', + \ 'g:imported_abs = exported', + \ ] + writefile(import_lines, 'Ximport_abs.vim') + writefile(s:export_script_lines, 'Xexport_abs.vim') + + source Ximport_abs.vim + + assert_equal(9876, g:imported_abs) + unlet g:imported_abs + + delete('Ximport_abs.vim') + delete('Xexport_abs.vim') + enddef + + def Test_import_rtp() + let import_lines = [ + \ 'vim9script', + \ 'import exported from "Xexport_rtp.vim"', + \ 'g:imported_rtp = exported', + \ ] + writefile(import_lines, 'Ximport_rtp.vim') + mkdir('import') + writefile(s:export_script_lines, 'import/Xexport_rtp.vim') + + let save_rtp = &rtp + &rtp = getcwd() + source Ximport_rtp.vim + &rtp = save_rtp + + assert_equal(9876, g:imported_rtp) + unlet g:imported_rtp + + delete('Ximport_rtp.vim') + delete('import/Xexport_rtp.vim') + delete('import', 'd') + enddef + + def Test_fixed_size_list() + " will be allocated as one piece of memory, check that changes work + let l = [1, 2, 3, 4] + l->remove(0) + l->add(5) + l->insert(99, 1) + call assert_equal([2, 99, 3, 4, 5], l) + enddef + + + " vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker *** ../vim-8.2.0148/src/testing.c 2020-01-11 16:05:19.594287610 +0100 --- src/testing.c 2020-01-26 15:30:08.621766594 +0100 *************** *** 758,766 **** switch (argvars[0].v_type) { case VAR_UNKNOWN: case VAR_NUMBER: - case VAR_FLOAT: case VAR_BOOL: case VAR_SPECIAL: case VAR_STRING: break; --- 758,767 ---- switch (argvars[0].v_type) { case VAR_UNKNOWN: + case VAR_VOID: case VAR_NUMBER: case VAR_BOOL: + case VAR_FLOAT: case VAR_SPECIAL: case VAR_STRING: break; *************** *** 781,787 **** { ufunc_T *fp; ! fp = find_func(argvars[0].vval.v_string); if (fp != NULL) retval = fp->uf_refcount; } --- 782,788 ---- { ufunc_T *fp; ! fp = find_func(argvars[0].vval.v_string, NULL); if (fp != NULL) retval = fp->uf_refcount; } *** ../vim-8.2.0148/src/userfunc.c 2020-01-07 20:59:30.528926519 +0100 --- src/userfunc.c 2020-01-26 15:40:07.891241019 +0100 *************** *** 22,29 **** #define FC_DELETED 0x10 // :delfunction used while uf_refcount > 0 #define FC_REMOVED 0x20 // function redefined while uf_refcount > 0 #define FC_SANDBOX 0x40 // function defined in the sandbox ! ! #define FUNCARG(fp, j) ((char_u **)(fp->uf_args.ga_data))[j] /* * All user-defined functions are found in this hashtable. --- 22,29 ---- #define FC_DELETED 0x10 // :delfunction used while uf_refcount > 0 #define FC_REMOVED 0x20 // function redefined while uf_refcount > 0 #define FC_SANDBOX 0x40 // function defined in the sandbox ! #define FC_DEAD 0x80 // function kept only for reference to dfunc ! #define FC_EXPORT 0x100 // "export def Func()" /* * All user-defined functions are found in this hashtable. *************** *** 63,75 **** } /* * Get function arguments. */ ! static int get_function_args( char_u **argp, char_u endchar, garray_T *newargs, int *varargs, garray_T *default_args, int skip) --- 63,146 ---- } /* + * Get one function argument and an optional type: "arg: type". + * Return a pointer to after the type. + * When something is wrong return "arg". + */ + static char_u * + one_function_arg(char_u *arg, garray_T *newargs, garray_T *argtypes, int skip) + { + char_u *p = arg; + + while (ASCII_ISALNUM(*p) || *p == '_') + ++p; + if (arg == p || isdigit(*arg) + || (p - arg == 9 && STRNCMP(arg, "firstline", 9) == 0) + || (p - arg == 8 && STRNCMP(arg, "lastline", 8) == 0)) + { + if (!skip) + semsg(_("E125: Illegal argument: %s"), arg); + return arg; + } + if (newargs != NULL && ga_grow(newargs, 1) == FAIL) + return arg; + if (newargs != NULL) + { + char_u *arg_copy; + int c; + int i; + + c = *p; + *p = NUL; + arg_copy = vim_strsave(arg); + if (arg_copy == NULL) + { + *p = c; + return arg; + } + + // Check for duplicate argument name. + for (i = 0; i < newargs->ga_len; ++i) + if (STRCMP(((char_u **)(newargs->ga_data))[i], arg_copy) == 0) + { + semsg(_("E853: Duplicate argument name: %s"), arg_copy); + vim_free(arg_copy); + return arg; + } + ((char_u **)(newargs->ga_data))[newargs->ga_len] = arg_copy; + newargs->ga_len++; + + *p = c; + } + + // get any type from "arg: type" + if (argtypes != NULL && ga_grow(argtypes, 1) == OK) + { + char_u *type = NULL; + + if (*p == ':') + { + type = skipwhite(p + 1); + p = skip_type(type); + type = vim_strnsave(type, p - type); + } + else if (*skipwhite(p) == ':') + emsg(_("E1059: No white space allowed before :")); + ((char_u **)argtypes->ga_data)[argtypes->ga_len++] = type; + } + + return p; + } + + /* * Get function arguments. */ ! int get_function_args( char_u **argp, char_u endchar, garray_T *newargs, + garray_T *argtypes, // NULL unless using :def int *varargs, garray_T *default_args, int skip) *************** *** 78,89 **** char_u *arg = *argp; char_u *p = arg; int c; - int i; int any_default = FALSE; char_u *expr; if (newargs != NULL) ga_init2(newargs, (int)sizeof(char_u *), 3); if (default_args != NULL) ga_init2(default_args, (int)sizeof(char_u *), 3); --- 149,161 ---- char_u *arg = *argp; char_u *p = arg; int c; int any_default = FALSE; char_u *expr; if (newargs != NULL) ga_init2(newargs, (int)sizeof(char_u *), 3); + if (argtypes != NULL) + ga_init2(argtypes, (int)sizeof(char_u *), 3); if (default_args != NULL) ga_init2(default_args, (int)sizeof(char_u *), 3); *************** *** 101,146 **** *varargs = TRUE; p += 3; mustend = TRUE; } else { arg = p; ! while (ASCII_ISALNUM(*p) || *p == '_') ! ++p; ! if (arg == p || isdigit(*arg) ! || (p - arg == 9 && STRNCMP(arg, "firstline", 9) == 0) ! || (p - arg == 8 && STRNCMP(arg, "lastline", 8) == 0)) ! { ! if (!skip) ! semsg(_("E125: Illegal argument: %s"), arg); break; - } - if (newargs != NULL && ga_grow(newargs, 1) == FAIL) - goto err_ret; - if (newargs != NULL) - { - c = *p; - *p = NUL; - arg = vim_strsave(arg); - if (arg == NULL) - { - *p = c; - goto err_ret; - } - // Check for duplicate argument name. - for (i = 0; i < newargs->ga_len; ++i) - if (STRCMP(((char_u **)(newargs->ga_data))[i], arg) == 0) - { - semsg(_("E853: Duplicate argument name: %s"), arg); - vim_free(arg); - goto err_ret; - } - ((char_u **)(newargs->ga_data))[newargs->ga_len] = arg; - newargs->ga_len++; - - *p = c; - } if (*skipwhite(p) == '=' && default_args != NULL) { typval_T rettv; --- 173,201 ---- *varargs = TRUE; p += 3; mustend = TRUE; + + if (argtypes != NULL) + { + // ...name: list + if (!ASCII_ISALPHA(*p)) + { + emsg(_("E1055: Missing name after ...")); + break; + } + + arg = p; + p = one_function_arg(p, newargs, argtypes, skip); + if (p == arg) + break; + } } else { arg = p; ! p = one_function_arg(p, newargs, argtypes, skip); ! if (p == arg) break; if (*skipwhite(p) == '=' && default_args != NULL) { typval_T rettv; *************** *** 266,272 **** ga_init(&newlines); // First, check if this is a lambda expression. "->" must exist. ! ret = get_function_args(&start, '-', NULL, NULL, NULL, TRUE); if (ret == FAIL || *start != '>') return NOTDONE; --- 321,327 ---- ga_init(&newlines); // First, check if this is a lambda expression. "->" must exist. ! ret = get_function_args(&start, '-', NULL, NULL, NULL, NULL, TRUE); if (ret == FAIL || *start != '>') return NOTDONE; *************** *** 276,282 **** else pnewargs = NULL; *arg = skipwhite(*arg + 1); ! ret = get_function_args(arg, '-', pnewargs, &varargs, NULL, FALSE); if (ret == FAIL || **arg != '>') goto errret; --- 331,338 ---- else pnewargs = NULL; *arg = skipwhite(*arg + 1); ! // TODO: argument types ! ret = get_function_args(arg, '-', pnewargs, NULL, &varargs, NULL, FALSE); if (ret == FAIL || **arg != '>') goto errret; *************** *** 307,312 **** --- 363,369 ---- fp = alloc_clear(offsetof(ufunc_T, uf_name) + STRLEN(name) + 1); if (fp == NULL) goto errret; + fp->uf_dfunc_idx = -1; pt = ALLOC_CLEAR_ONE(partial_T); if (pt == NULL) goto errret; *************** *** 345,350 **** --- 402,408 ---- #endif if (sandbox) flags |= FC_SANDBOX; + // can be called with more args than uf_args.ga_len fp->uf_varargs = TRUE; fp->uf_flags = flags; fp->uf_calls = 0; *************** *** 581,597 **** } /* * Find a function by name, return pointer to it in ufuncs. * Return NULL for unknown function. */ ! ufunc_T * ! find_func(char_u *name) { hashitem_T *hi; hi = hash_find(&func_hashtab, name); if (!HASHITEM_EMPTY(hi)) return HI2UF(hi); return NULL; } --- 639,711 ---- } /* + * Find a function "name" in script "sid". + */ + static ufunc_T * + find_func_with_sid(char_u *name, int sid) + { + hashitem_T *hi; + char_u buffer[200]; + + buffer[0] = K_SPECIAL; + buffer[1] = KS_EXTRA; + buffer[2] = (int)KE_SNR; + vim_snprintf((char *)buffer + 3, sizeof(buffer) - 3, "%ld_%s", + (long)sid, name); + hi = hash_find(&func_hashtab, buffer); + if (!HASHITEM_EMPTY(hi)) + return HI2UF(hi); + + return NULL; + } + + /* * Find a function by name, return pointer to it in ufuncs. * Return NULL for unknown function. */ ! static ufunc_T * ! find_func_even_dead(char_u *name, cctx_T *cctx) { hashitem_T *hi; + ufunc_T *func; + imported_T *imported; + + if (in_vim9script()) + { + // Find script-local function before global one. + func = find_func_with_sid(name, current_sctx.sc_sid); + if (func != NULL) + return func; + + // Find imported funcion before global one. + imported = find_imported(name, cctx); + if (imported != NULL && imported->imp_funcname != NULL) + { + hi = hash_find(&func_hashtab, imported->imp_funcname); + if (!HASHITEM_EMPTY(hi)) + return HI2UF(hi); + } + } hi = hash_find(&func_hashtab, name); if (!HASHITEM_EMPTY(hi)) return HI2UF(hi); + + return NULL; + } + + /* + * Find a function by name, return pointer to it in ufuncs. + * "cctx" is passed in a :def function to find imported functions. + * Return NULL for unknown or dead function. + */ + ufunc_T * + find_func(char_u *name, cctx_T *cctx) + { + ufunc_T *fp = find_func_even_dead(name, cctx); + + if (fp != NULL && (fp->uf_flags & FC_DEAD) == 0) + return fp; return NULL; } *************** *** 761,766 **** --- 875,1001 ---- } } } + /* + * Unreference "fc": decrement the reference count and free it when it + * becomes zero. "fp" is detached from "fc". + * When "force" is TRUE we are exiting. + */ + static void + funccal_unref(funccall_T *fc, ufunc_T *fp, int force) + { + funccall_T **pfc; + int i; + + if (fc == NULL) + return; + + if (--fc->fc_refcount <= 0 && (force || ( + fc->l_varlist.lv_refcount == DO_NOT_FREE_CNT + && fc->l_vars.dv_refcount == DO_NOT_FREE_CNT + && fc->l_avars.dv_refcount == DO_NOT_FREE_CNT))) + for (pfc = &previous_funccal; *pfc != NULL; pfc = &(*pfc)->caller) + { + if (fc == *pfc) + { + *pfc = fc->caller; + free_funccal_contents(fc); + return; + } + } + for (i = 0; i < fc->fc_funcs.ga_len; ++i) + if (((ufunc_T **)(fc->fc_funcs.ga_data))[i] == fp) + ((ufunc_T **)(fc->fc_funcs.ga_data))[i] = NULL; + } + + /* + * Remove the function from the function hashtable. If the function was + * deleted while it still has references this was already done. + * Return TRUE if the entry was deleted, FALSE if it wasn't found. + */ + static int + func_remove(ufunc_T *fp) + { + hashitem_T *hi; + + // Return if it was already virtually deleted. + if (fp->uf_flags & FC_DEAD) + return FALSE; + + hi = hash_find(&func_hashtab, UF2HIKEY(fp)); + if (!HASHITEM_EMPTY(hi)) + { + // When there is a def-function index do not actually remove the + // function, so we can find the index when defining the function again. + if (fp->uf_dfunc_idx >= 0) + fp->uf_flags |= FC_DEAD; + else + hash_remove(&func_hashtab, hi); + return TRUE; + } + return FALSE; + } + + static void + func_clear_items(ufunc_T *fp) + { + ga_clear_strings(&(fp->uf_args)); + ga_clear_strings(&(fp->uf_def_args)); + ga_clear_strings(&(fp->uf_lines)); + VIM_CLEAR(fp->uf_name_exp); + VIM_CLEAR(fp->uf_arg_types); + ga_clear(&fp->uf_type_list); + #ifdef FEAT_PROFILE + VIM_CLEAR(fp->uf_tml_count); + VIM_CLEAR(fp->uf_tml_total); + VIM_CLEAR(fp->uf_tml_self); + #endif + } + + /* + * Free all things that a function contains. Does not free the function + * itself, use func_free() for that. + * When "force" is TRUE we are exiting. + */ + static void + func_clear(ufunc_T *fp, int force) + { + if (fp->uf_cleared) + return; + fp->uf_cleared = TRUE; + + // clear this function + func_clear_items(fp); + funccal_unref(fp->uf_scoped, fp, force); + delete_def_function(fp); + } + + /* + * Free a function and remove it from the list of functions. Does not free + * what a function contains, call func_clear() first. + */ + static void + func_free(ufunc_T *fp) + { + // Only remove it when not done already, otherwise we would remove a newer + // version of the function with the same name. + if ((fp->uf_flags & (FC_DELETED | FC_REMOVED)) == 0) + func_remove(fp); + + if ((fp->uf_flags & FC_DEAD) == 0) + vim_free(fp); + } + + /* + * Free all things that a function contains and free the function itself. + * When "force" is TRUE we are exiting. + */ + static void + func_clear_free(ufunc_T *fp, int force) + { + func_clear(fp, force); + func_free(fp); + } + /* * Call a user function. *************** *** 822,827 **** --- 1057,1076 ---- ga_init2(&fc->fc_funcs, sizeof(ufunc_T *), 1); func_ptr_ref(fp); + if (fp->uf_dfunc_idx >= 0) + { + estack_push_ufunc(ETYPE_UFUNC, fp, 1); + + // Execute the compiled function. + call_def_function(fp, argcount, argvars, rettv); + --depth; + current_funccal = fc->caller; + + estack_pop(); + free_funccal(fc); + return; + } + if (STRNCMP(fp->uf_name, "", 8) == 0) islambda = TRUE; *************** *** 1146,1255 **** } /* ! * Unreference "fc": decrement the reference count and free it when it ! * becomes zero. "fp" is detached from "fc". ! * When "force" is TRUE we are exiting. */ ! static void ! funccal_unref(funccall_T *fc, ufunc_T *fp, int force) ! { ! funccall_T **pfc; ! int i; ! ! if (fc == NULL) ! return; ! if (--fc->fc_refcount <= 0 && (force || ( ! fc->l_varlist.lv_refcount == DO_NOT_FREE_CNT ! && fc->l_vars.dv_refcount == DO_NOT_FREE_CNT ! && fc->l_avars.dv_refcount == DO_NOT_FREE_CNT))) ! for (pfc = &previous_funccal; *pfc != NULL; pfc = &(*pfc)->caller) { ! if (fc == *pfc) ! { ! *pfc = fc->caller; ! free_funccal_contents(fc); ! return; ! } } ! for (i = 0; i < fc->fc_funcs.ga_len; ++i) ! if (((ufunc_T **)(fc->fc_funcs.ga_data))[i] == fp) ! ((ufunc_T **)(fc->fc_funcs.ga_data))[i] = NULL; ! } ! ! /* ! * Remove the function from the function hashtable. If the function was ! * deleted while it still has references this was already done. ! * Return TRUE if the entry was deleted, FALSE if it wasn't found. ! */ ! static int ! func_remove(ufunc_T *fp) ! { ! hashitem_T *hi = hash_find(&func_hashtab, UF2HIKEY(fp)); ! ! if (!HASHITEM_EMPTY(hi)) ! { ! hash_remove(&func_hashtab, hi); ! return TRUE; } ! return FALSE; ! } ! ! static void ! func_clear_items(ufunc_T *fp) ! { ! ga_clear_strings(&(fp->uf_args)); ! ga_clear_strings(&(fp->uf_def_args)); ! ga_clear_strings(&(fp->uf_lines)); ! VIM_CLEAR(fp->uf_name_exp); ! #ifdef FEAT_PROFILE ! VIM_CLEAR(fp->uf_tml_count); ! VIM_CLEAR(fp->uf_tml_total); ! VIM_CLEAR(fp->uf_tml_self); ! #endif ! } ! ! /* ! * Free all things that a function contains. Does not free the function ! * itself, use func_free() for that. ! * When "force" is TRUE we are exiting. ! */ ! static void ! func_clear(ufunc_T *fp, int force) ! { ! if (fp->uf_cleared) ! return; ! fp->uf_cleared = TRUE; ! ! // clear this function ! func_clear_items(fp); ! funccal_unref(fp->uf_scoped, fp, force); ! } ! ! /* ! * Free a function and remove it from the list of functions. Does not free ! * what a function contains, call func_clear() first. ! */ ! static void ! func_free(ufunc_T *fp) ! { ! // only remove it when not done already, otherwise we would remove a newer ! // version of the function ! if ((fp->uf_flags & (FC_DELETED | FC_REMOVED)) == 0) ! func_remove(fp); ! ! vim_free(fp); ! } ! ! /* ! * Free all things that a function contains and free the function itself. ! * When "force" is TRUE we are exiting. ! */ ! static void ! func_clear_free(ufunc_T *fp, int force) ! { ! func_clear(fp, force); ! func_free(fp); } /* --- 1395,1451 ---- } /* ! * Call a user function after checking the arguments. */ ! int ! call_user_func_check( ! ufunc_T *fp, ! int argcount, ! typval_T *argvars, ! typval_T *rettv, ! funcexe_T *funcexe, ! dict_T *selfdict) ! { ! int error; ! int regular_args = fp->uf_args.ga_len; ! ! if (fp->uf_flags & FC_RANGE && funcexe->doesrange != NULL) ! *funcexe->doesrange = TRUE; ! if (argcount < regular_args - fp->uf_def_args.ga_len) ! error = FCERR_TOOFEW; ! else if (!has_varargs(fp) && argcount > regular_args) ! error = FCERR_TOOMANY; ! else if ((fp->uf_flags & FC_DICT) && selfdict == NULL) ! error = FCERR_DICT; ! else ! { ! int did_save_redo = FALSE; ! save_redo_T save_redo; ! /* ! * Call the user function. ! * Save and restore search patterns, script variables and ! * redo buffer. ! */ ! save_search_patterns(); ! if (!ins_compl_active()) { ! saveRedobuff(&save_redo); ! did_save_redo = TRUE; } ! ++fp->uf_calls; ! call_user_func(fp, argcount, argvars, rettv, ! funcexe->firstline, funcexe->lastline, ! (fp->uf_flags & FC_DICT) ? selfdict : NULL); ! if (--fp->uf_calls <= 0 && fp->uf_refcount <= 0) ! // Function was unreferenced while being used, free it now. ! func_clear_free(fp, FALSE); ! if (did_save_redo) ! restoreRedobuff(&save_redo); ! restore_search_patterns(); ! error = FCERR_NONE; } ! return error; } /* *************** *** 1327,1335 **** for (hi = func_hashtab.ht_array; todo > 0; ++hi) if (!HASHITEM_EMPTY(hi)) { // Only free functions that are not refcounted, those are // supposed to be freed when no longer referenced. - fp = HI2UF(hi); if (func_name_refcount(fp->uf_name)) ++skipped; else --- 1523,1535 ---- for (hi = func_hashtab.ht_array; todo > 0; ++hi) if (!HASHITEM_EMPTY(hi)) { + // clear the def function index now + fp = HI2UF(hi); + fp->uf_flags &= ~FC_DEAD; + fp->uf_dfunc_idx = -1; + // Only free functions that are not refcounted, those are // supposed to be freed when no longer referenced. if (func_name_refcount(fp->uf_name)) ++skipped; else *************** *** 1371,1376 **** --- 1571,1578 ---- } if (skipped == 0) hash_clear(&func_hashtab); + + free_def_functions(); } #endif *************** *** 1379,1385 **** * lower case letter and doesn't contain AUTOLOAD_CHAR. * "len" is the length of "name", or -1 for NUL terminated. */ ! static int builtin_function(char_u *name, int len) { char_u *p; --- 1581,1587 ---- * lower case letter and doesn't contain AUTOLOAD_CHAR. * "len" is the length of "name", or -1 for NUL terminated. */ ! int builtin_function(char_u *name, int len) { char_u *p; *************** *** 1469,1474 **** --- 1671,1713 ---- } /* + * Give an error message for the result of a function. + * Nothing if "error" is FCERR_NONE. + */ + void + user_func_error(int error, char_u *name) + { + switch (error) + { + case FCERR_UNKNOWN: + emsg_funcname(e_unknownfunc, name); + break; + case FCERR_NOTMETHOD: + emsg_funcname( + N_("E276: Cannot use function as a method: %s"), name); + break; + case FCERR_DELETED: + emsg_funcname(N_(e_func_deleted), name); + break; + case FCERR_TOOMANY: + emsg_funcname((char *)e_toomanyarg, name); + break; + case FCERR_TOOFEW: + emsg_funcname((char *)e_toofewarg, name); + break; + case FCERR_SCRIPT: + emsg_funcname( + N_("E120: Using not in a script context: %s"), name); + break; + case FCERR_DICT: + emsg_funcname( + N_("E725: Calling dict function without Dictionary: %s"), + name); + break; + } + } + + /* * Call a function with its resolved parameters * * Return FAIL when the function can't be called, OK otherwise. *************** *** 1561,1567 **** if (partial != NULL && partial->pt_func != NULL) fp = partial->pt_func; else ! fp = find_func(rfname); // Trigger FuncUndefined event, may load the function. if (fp == NULL --- 1800,1806 ---- if (partial != NULL && partial->pt_func != NULL) fp = partial->pt_func; else ! fp = find_func(rfname, NULL); // Trigger FuncUndefined event, may load the function. if (fp == NULL *************** *** 1570,1582 **** && !aborting()) { // executed an autocommand, search for the function again ! fp = find_func(rfname); } // Try loading a package. if (fp == NULL && script_autoload(rfname, TRUE) && !aborting()) { // loaded a package, search for the function again ! fp = find_func(rfname); } if (fp != NULL && (fp->uf_flags & FC_DELETED)) --- 1809,1821 ---- && !aborting()) { // executed an autocommand, search for the function again ! fp = find_func(rfname, NULL); } // Try loading a package. if (fp == NULL && script_autoload(rfname, TRUE) && !aborting()) { // loaded a package, search for the function again ! fp = find_func(rfname, NULL); } if (fp != NULL && (fp->uf_flags & FC_DELETED)) *************** *** 1598,1640 **** argv_base = 1; } ! if (fp->uf_flags & FC_RANGE && funcexe->doesrange != NULL) ! *funcexe->doesrange = TRUE; ! if (argcount < fp->uf_args.ga_len - fp->uf_def_args.ga_len) ! error = FCERR_TOOFEW; ! else if (!fp->uf_varargs && argcount > fp->uf_args.ga_len) ! error = FCERR_TOOMANY; ! else if ((fp->uf_flags & FC_DICT) && selfdict == NULL) ! error = FCERR_DICT; ! else ! { ! int did_save_redo = FALSE; ! save_redo_T save_redo; ! ! /* ! * Call the user function. ! * Save and restore search patterns, script variables and ! * redo buffer. ! */ ! save_search_patterns(); ! if (!ins_compl_active()) ! { ! saveRedobuff(&save_redo); ! did_save_redo = TRUE; ! } ! ++fp->uf_calls; ! call_user_func(fp, argcount, argvars, rettv, ! funcexe->firstline, funcexe->lastline, ! (fp->uf_flags & FC_DICT) ? selfdict : NULL); ! if (--fp->uf_calls <= 0 && fp->uf_refcount <= 0) ! // Function was unreferenced while being used, free it ! // now. ! func_clear_free(fp, FALSE); ! if (did_save_redo) ! restoreRedobuff(&save_redo); ! restore_search_patterns(); ! error = FCERR_NONE; ! } } } else if (funcexe->basetv != NULL) --- 1837,1844 ---- argv_base = 1; } ! error = call_user_func_check(fp, argcount, argvars, rettv, ! funcexe, selfdict); } } else if (funcexe->basetv != NULL) *************** *** 1675,1712 **** */ if (!aborting()) { ! switch (error) ! { ! case FCERR_UNKNOWN: ! emsg_funcname(N_("E117: Unknown function: %s"), name); ! break; ! case FCERR_NOTMETHOD: ! emsg_funcname( ! N_("E276: Cannot use function as a method: %s"), ! name); ! break; ! case FCERR_DELETED: ! emsg_funcname(N_("E933: Function was deleted: %s"), name); ! break; ! case FCERR_TOOMANY: ! emsg_funcname((char *)e_toomanyarg, name); ! break; ! case FCERR_TOOFEW: ! emsg_funcname( ! N_("E119: Not enough arguments for function: %s"), ! name); ! break; ! case FCERR_SCRIPT: ! emsg_funcname( ! N_("E120: Using not in a script context: %s"), ! name); ! break; ! case FCERR_DICT: ! emsg_funcname( ! N_("E725: Calling dict function without Dictionary: %s"), ! name); ! break; ! } } // clear the copies made from the partial --- 1879,1885 ---- */ if (!aborting()) { ! user_func_error(error, name); } // clear the copies made from the partial *************** *** 1719,1724 **** --- 1892,1903 ---- return ret; } + static char_u * + printable_func_name(ufunc_T *fp) + { + return fp->uf_name_exp != NULL ? fp->uf_name_exp : fp->uf_name; + } + /* * List the head of the function: "name(arg1, arg2)". */ *************** *** 1731,1746 **** if (indent) msg_puts(" "); msg_puts("function "); ! if (fp->uf_name_exp != NULL) ! msg_puts((char *)fp->uf_name_exp); ! else ! msg_puts((char *)fp->uf_name); msg_putchar('('); for (j = 0; j < fp->uf_args.ga_len; ++j) { if (j) msg_puts(", "); msg_puts((char *)FUNCARG(fp, j)); if (j >= fp->uf_args.ga_len - fp->uf_def_args.ga_len) { msg_puts(" = "); --- 1910,1930 ---- if (indent) msg_puts(" "); msg_puts("function "); ! msg_puts((char *)printable_func_name(fp)); msg_putchar('('); for (j = 0; j < fp->uf_args.ga_len; ++j) { if (j) msg_puts(", "); msg_puts((char *)FUNCARG(fp, j)); + if (fp->uf_arg_types != NULL) + { + char *tofree; + + msg_puts(": "); + msg_puts(type_name(fp->uf_arg_types[j], &tofree)); + vim_free(tofree); + } if (j >= fp->uf_args.ga_len - fp->uf_def_args.ga_len) { msg_puts(" = "); *************** *** 1754,1759 **** --- 1938,1958 ---- msg_puts(", "); msg_puts("..."); } + if (fp->uf_va_name != NULL) + { + if (j) + msg_puts(", "); + msg_puts("..."); + msg_puts((char *)fp->uf_va_name); + if (fp->uf_va_type) + { + char *tofree; + + msg_puts(": "); + msg_puts(type_name(fp->uf_va_type, &tofree)); + vim_free(tofree); + } + } msg_putchar(')'); if (fp->uf_flags & FC_ABORT) msg_puts(" abort"); *************** *** 1793,1799 **** --- 1992,2000 ---- int lead; char_u sid_buf[20]; int len; + int extra = 0; lval_T lv; + int vim9script; if (fdp != NULL) vim_memset(fdp, 0, sizeof(funcdict_T)); *************** *** 1934,1939 **** --- 2135,2144 ---- len = (int)(end - lv.ll_name); } + // In Vim9 script a user function is script-local by default. + vim9script = ASCII_ISUPPER(*start) + && current_sctx.sc_version == SCRIPT_VERSION_VIM9; + /* * Copy the function name to allocated memory. * Accept name() inside a script, translate into 123_name(). *************** *** 1941,1960 **** */ if (skip) lead = 0; // do nothing ! else if (lead > 0) { ! lead = 3; ! if ((lv.ll_exp_name != NULL && eval_fname_sid(lv.ll_exp_name)) || eval_fname_sid(*pp)) { ! // It's "s:" or "" if (current_sctx.sc_sid <= 0) { emsg(_(e_usingsid)); goto theend; } sprintf((char *)sid_buf, "%ld_", (long)current_sctx.sc_sid); ! lead += (int)STRLEN(sid_buf); } } else if (!(flags & TFN_INT) && builtin_function(lv.ll_name, len)) --- 2146,2170 ---- */ if (skip) lead = 0; // do nothing ! else if (lead > 0 || vim9script) { ! if (!vim9script) ! lead = 3; ! if (vim9script || (lv.ll_exp_name != NULL ! && eval_fname_sid(lv.ll_exp_name)) || eval_fname_sid(*pp)) { ! // It's script-local, "s:" or "" if (current_sctx.sc_sid <= 0) { emsg(_(e_usingsid)); goto theend; } sprintf((char *)sid_buf, "%ld_", (long)current_sctx.sc_sid); ! if (vim9script) ! extra = 3 + (int)STRLEN(sid_buf); ! else ! lead += (int)STRLEN(sid_buf); } } else if (!(flags & TFN_INT) && builtin_function(lv.ll_name, len)) *************** *** 1974,1992 **** } } ! name = alloc(len + lead + 1); if (name != NULL) { ! if (lead > 0) { name[0] = K_SPECIAL; name[1] = KS_EXTRA; name[2] = (int)KE_SNR; ! if (lead > 3) // If it's "" STRCPY(name + 3, sid_buf); } ! mch_memmove(name + lead, lv.ll_name, (size_t)len); ! name[lead + len] = NUL; } *pp = end; --- 2184,2202 ---- } } ! name = alloc(len + lead + extra + 1); if (name != NULL) { ! if (lead > 0 || vim9script) { name[0] = K_SPECIAL; name[1] = KS_EXTRA; name[2] = (int)KE_SNR; ! if (vim9script || lead > 3) // If it's "" STRCPY(name + 3, sid_buf); } ! mch_memmove(name + lead + extra, lv.ll_name, (size_t)len); ! name[lead + extra + len] = NUL; } *pp = end; *************** *** 2012,2030 **** char_u *arg; char_u *line_arg = NULL; garray_T newargs; garray_T default_args; garray_T newlines; int varargs = FALSE; int flags = 0; ufunc_T *fp; int overwrite = FALSE; int indent; int nesting; dictitem_T *v; funcdict_T fudi; static int func_nr = 0; // number for nameless function int paren; - hashtab_T *ht; int todo; hashitem_T *hi; int do_concat = TRUE; --- 2222,2243 ---- char_u *arg; char_u *line_arg = NULL; garray_T newargs; + garray_T argtypes; garray_T default_args; garray_T newlines; int varargs = FALSE; int flags = 0; + char_u *ret_type = NULL; ufunc_T *fp; int overwrite = FALSE; int indent; int nesting; + #define MAX_FUNC_NESTING 50 + char nesting_def[MAX_FUNC_NESTING]; dictitem_T *v; funcdict_T fudi; static int func_nr = 0; // number for nameless function int paren; int todo; hashitem_T *hi; int do_concat = TRUE; *************** *** 2048,2054 **** { --todo; fp = HI2UF(hi); ! if (message_filtered(fp->uf_name)) continue; if (!func_name_refcount(fp->uf_name)) list_func_head(fp, FALSE); --- 2261,2268 ---- { --todo; fp = HI2UF(hi); ! if ((fp->uf_flags & FC_DEAD) ! || message_filtered(fp->uf_name)) continue; if (!func_name_refcount(fp->uf_name)) list_func_head(fp, FALSE); *************** *** 2084,2091 **** { --todo; fp = HI2UF(hi); ! if (!isdigit(*fp->uf_name) ! && vim_regexec(®match, fp->uf_name, 0)) list_func_head(fp, FALSE); } } --- 2298,2306 ---- { --todo; fp = HI2UF(hi); ! if ((fp->uf_flags & FC_DEAD) == 0 ! && !isdigit(*fp->uf_name) ! && vim_regexec(®match, fp->uf_name, 0)) list_func_head(fp, FALSE); } } *************** *** 2098,2103 **** --- 2313,2322 ---- return; } + ga_init(&newargs); + ga_init(&argtypes); + ga_init(&default_args); + /* * Get the function name. There are these situations: * func normal function name *************** *** 2155,2161 **** *p = NUL; if (!eap->skip && !got_int) { ! fp = find_func(name); if (fp != NULL) { list_func_head(fp, TRUE); --- 2374,2380 ---- *p = NUL; if (!eap->skip && !got_int) { ! fp = find_func(name, NULL); if (fp != NULL) { list_func_head(fp, TRUE); *************** *** 2176,2182 **** if (!got_int) { msg_putchar('\n'); ! msg_puts(" endfunction"); } } else --- 2395,2404 ---- if (!got_int) { msg_putchar('\n'); ! if (fp->uf_dfunc_idx >= 0) ! msg_puts(" enddef"); ! else ! msg_puts(" endfunction"); } } else *************** *** 2231,2273 **** emsg(_("E862: Cannot use g: here")); } ! if (get_function_args(&p, ')', &newargs, &varargs, ! &default_args, eap->skip) == FAIL) goto errret_2; ! // find extra arguments "range", "dict", "abort" and "closure" ! for (;;) { ! p = skipwhite(p); ! if (STRNCMP(p, "range", 5) == 0) { ! flags |= FC_RANGE; ! p += 5; ! } ! else if (STRNCMP(p, "dict", 4) == 0) ! { ! flags |= FC_DICT; ! p += 4; ! } ! else if (STRNCMP(p, "abort", 5) == 0) ! { ! flags |= FC_ABORT; ! p += 5; } ! else if (STRNCMP(p, "closure", 7) == 0) { ! flags |= FC_CLOSURE; ! p += 7; ! if (current_funccal == NULL) { ! emsg_funcname(N_("E932: Closure function should not be at top level: %s"), ! name == NULL ? (char_u *)"" : name); ! goto erret; } } - else - break; - } // When there is a line break use what follows for the function body. // Makes 'exe "func Test()\n...\nendfunc"' work. --- 2453,2510 ---- emsg(_("E862: Cannot use g: here")); } ! if (get_function_args(&p, ')', &newargs, ! eap->cmdidx == CMD_def ? &argtypes : NULL, ! &varargs, &default_args, eap->skip) == FAIL) goto errret_2; ! if (eap->cmdidx == CMD_def) { ! // find the return type: :def Func(): type ! if (*p == ':') { ! ret_type = skipwhite(p + 1); ! p = skip_type(ret_type); ! if (p > ret_type) ! p = skipwhite(p); ! else ! semsg(_("E1056: expected a type: %s"), ret_type); } ! } ! else ! // find extra arguments "range", "dict", "abort" and "closure" ! for (;;) { ! p = skipwhite(p); ! if (STRNCMP(p, "range", 5) == 0) { ! flags |= FC_RANGE; ! p += 5; ! } ! else if (STRNCMP(p, "dict", 4) == 0) ! { ! flags |= FC_DICT; ! p += 4; } + else if (STRNCMP(p, "abort", 5) == 0) + { + flags |= FC_ABORT; + p += 5; + } + else if (STRNCMP(p, "closure", 7) == 0) + { + flags |= FC_CLOSURE; + p += 7; + if (current_funccal == NULL) + { + emsg_funcname(N_("E932: Closure function should not be at top level: %s"), + name == NULL ? (char_u *)"" : name); + goto erret; + } + } + else + break; } // When there is a line break use what follows for the function body. // Makes 'exe "func Test()\n...\nendfunc"' work. *************** *** 2277,2283 **** emsg(_(e_trailing)); /* ! * Read the body of the function, until ":endfunction" is found. */ if (KeyTyped) { --- 2514,2521 ---- emsg(_(e_trailing)); /* ! * Read the body of the function, until "}", ":endfunction" or ":enddef" is ! * found. */ if (KeyTyped) { *************** *** 2288,2294 **** { if (fudi.fd_dict != NULL && fudi.fd_newkey == NULL) emsg(_(e_funcdict)); ! else if (name != NULL && find_func(name) != NULL) emsg_funcname(e_funcexts, name); } --- 2526,2532 ---- { if (fudi.fd_dict != NULL && fudi.fd_newkey == NULL) emsg(_(e_funcdict)); ! else if (name != NULL && find_func(name, NULL) != NULL) emsg_funcname(e_funcexts, name); } *************** *** 2304,2309 **** --- 2542,2548 ---- indent = 2; nesting = 0; + nesting_def[nesting] = (eap->cmdidx == CMD_def); for (;;) { if (KeyTyped) *************** *** 2339,2345 **** lines_left = Rows - 1; if (theline == NULL) { ! emsg(_("E126: Missing :endfunction")); goto erret; } --- 2578,2587 ---- lines_left = Rows - 1; if (theline == NULL) { ! if (eap->cmdidx == CMD_def) ! emsg(_("E1057: Missing :enddef")); ! else ! emsg(_("E126: Missing :endfunction")); goto erret; } *************** *** 2352,2358 **** if (skip_until != NULL) { ! // Don't check for ":endfunc" between // * ":append" and "." // * ":python < 0) ! give_warning2( ! (char_u *)_("W22: Text found after :endfunction: %s"), p, TRUE); if (nextcmd != NULL) { --- 2636,2644 ---- else if (line_arg != NULL && *skipwhite(line_arg) != NUL) nextcmd = line_arg; else if (*p != NUL && *p != '"' && p_verbose > 0) ! give_warning2(eap->cmdidx == CMD_def ! ? (char_u *)_("W1001: Text found after :enddef: %s") ! : (char_u *)_("W22: Text found after :endfunction: %s"), p, TRUE); if (nextcmd != NULL) { *************** *** 2414,2420 **** // Increase indent inside "if", "while", "for" and "try", decrease // at "end". ! if (indent > 2 && STRNCMP(p, "end", 3) == 0) indent -= 2; else if (STRNCMP(p, "if", 2) == 0 || STRNCMP(p, "wh", 2) == 0 --- 2658,2664 ---- // Increase indent inside "if", "while", "for" and "try", decrease // at "end". ! if (indent > 2 && (*p == '}' || STRNCMP(p, "end", 3) == 0)) indent -= 2; else if (STRNCMP(p, "if", 2) == 0 || STRNCMP(p, "wh", 2) == 0 *************** *** 2423,2429 **** indent += 2; // Check for defining a function inside this function. ! if (checkforcmd(&p, "function", 2)) { if (*p == '!') p = skipwhite(p + 1); --- 2667,2674 ---- indent += 2; // Check for defining a function inside this function. ! c = *p; ! if (checkforcmd(&p, "function", 2) || checkforcmd(&p, "def", 3)) { if (*p == '!') p = skipwhite(p + 1); *************** *** 2431,2438 **** vim_free(trans_function_name(&p, TRUE, 0, NULL, NULL)); if (*skipwhite(p) == '(') { ! ++nesting; ! indent += 2; } } --- 2676,2689 ---- vim_free(trans_function_name(&p, TRUE, 0, NULL, NULL)); if (*skipwhite(p) == '(') { ! if (nesting == MAX_FUNC_NESTING - 1) ! emsg(_("E1058: function nesting too deep")); ! else ! { ! ++nesting; ! nesting_def[nesting] = (c == 'd'); ! indent += 2; ! } } } *************** *** 2537,2542 **** --- 2788,2795 ---- */ if (fudi.fd_dict == NULL) { + hashtab_T *ht; + v = find_var(name, &ht, FALSE); if (v != NULL && v->di_tv.v_type == VAR_FUNC) { *************** *** 2545,2556 **** goto erret; } ! fp = find_func(name); if (fp != NULL) { // Function can be replaced with "function!" and when sourcing the // same script again, but only once. ! if (!eap->forceit && (fp->uf_script_ctx.sc_sid != current_sctx.sc_sid || fp->uf_script_ctx.sc_seq == current_sctx.sc_seq)) { --- 2798,2811 ---- goto erret; } ! fp = find_func_even_dead(name, NULL); if (fp != NULL) { + int dead = fp->uf_flags & FC_DEAD; + // Function can be replaced with "function!" and when sourcing the // same script again, but only once. ! if (!dead && !eap->forceit && (fp->uf_script_ctx.sc_sid != current_sctx.sc_sid || fp->uf_script_ctx.sc_seq == current_sctx.sc_seq)) { *************** *** 2582,2587 **** --- 2837,2843 ---- fp->uf_name_exp = NULL; func_clear_items(fp); fp->uf_name_exp = exp_name; + fp->uf_flags &= ~FC_DEAD; #ifdef FEAT_PROFILE fp->uf_profiling = FALSE; fp->uf_prof_initialized = FALSE; *************** *** 2651,2656 **** --- 2907,2913 ---- fp = alloc_clear(offsetof(ufunc_T, uf_name) + STRLEN(name) + 1); if (fp == NULL) goto erret; + fp->uf_dfunc_idx = -1; if (fudi.fd_dict != NULL) { *************** *** 2696,2701 **** --- 2953,3013 ---- } fp->uf_args = newargs; fp->uf_def_args = default_args; + fp->uf_ret_type = &t_any; + + if (eap->cmdidx == CMD_def) + { + // parse the argument types + ga_init2(&fp->uf_type_list, sizeof(type_T), 5); + + if (argtypes.ga_len > 0) + { + // When "varargs" is set the last name/type goes into uf_va_name + // and uf_va_type. + int len = argtypes.ga_len - (varargs ? 1 : 0); + + fp->uf_arg_types = ALLOC_CLEAR_MULT(type_T *, len); + if (fp->uf_arg_types != NULL) + { + int i; + + for (i = 0; i < len; ++ i) + { + p = ((char_u **)argtypes.ga_data)[i]; + if (p == NULL) + // todo: get type from default value + fp->uf_arg_types[i] = &t_any; + else + fp->uf_arg_types[i] = parse_type(&p, &fp->uf_type_list); + } + } + if (varargs) + { + // Move the last argument "...name: type" to uf_va_name and + // uf_va_type. + fp->uf_va_name = ((char_u **)fp->uf_args.ga_data) + [fp->uf_args.ga_len - 1]; + --fp->uf_args.ga_len; + p = ((char_u **)argtypes.ga_data)[len]; + if (p == NULL) + // todo: get type from default value + fp->uf_va_type = &t_any; + else + fp->uf_va_type = parse_type(&p, &fp->uf_type_list); + } + varargs = FALSE; + } + + // parse the return type, if any + if (ret_type == NULL) + fp->uf_ret_type = &t_void; + else + { + p = ret_type; + fp->uf_ret_type = parse_type(&p, &fp->uf_type_list); + } + } + fp->uf_lines = newlines; if ((flags & FC_CLOSURE) != 0) { *************** *** 2716,2725 **** --- 3028,3049 ---- fp->uf_calls = 0; fp->uf_script_ctx = current_sctx; fp->uf_script_ctx.sc_lnum += sourcing_lnum_top; + if (is_export) + { + fp->uf_flags |= FC_EXPORT; + // let ex_export() know the export worked. + is_export = FALSE; + } + + // ":def Func()" needs to be compiled + if (eap->cmdidx == CMD_def) + compile_def_function(fp, FALSE); + goto ret_free; erret: ga_clear_strings(&newargs); + ga_clear_strings(&argtypes); ga_clear_strings(&default_args); errret_2: ga_clear_strings(&newlines); *************** *** 2755,2761 **** { if (builtin_function(name, -1)) return has_internal_func(name); ! return find_func(name) != NULL; } /* --- 3079,3095 ---- { if (builtin_function(name, -1)) return has_internal_func(name); ! return find_func(name, NULL) != NULL; ! } ! ! /* ! * Return TRUE when "ufunc" has old-style "..." varargs ! * or named varargs "...name: type". ! */ ! int ! has_varargs(ufunc_T *ufunc) ! { ! return ufunc->uf_varargs || ufunc->uf_va_name != NULL; } /* *************** *** 2826,2834 **** ++hi; fp = HI2UF(hi); ! if ((fp->uf_flags & FC_DICT) || STRNCMP(fp->uf_name, "", 8) == 0) ! return (char_u *)""; // don't show dict and lambda functions if (STRLEN(fp->uf_name) + 4 >= IOSIZE) return fp->uf_name; // prevents overflow --- 3160,3169 ---- ++hi; fp = HI2UF(hi); ! // don't show dead, dict and lambda functions ! if ((fp->uf_flags & FC_DEAD) || (fp->uf_flags & FC_DICT) || STRNCMP(fp->uf_name, "", 8) == 0) ! return (char_u *)""; if (STRLEN(fp->uf_name) + 4 >= IOSIZE) return fp->uf_name; // prevents overflow *************** *** 2837,2843 **** if (xp->xp_context != EXPAND_USER_FUNC) { STRCAT(IObuff, "("); ! if (!fp->uf_varargs && fp->uf_args.ga_len == 0) STRCAT(IObuff, ")"); } return IObuff; --- 3172,3178 ---- if (xp->xp_context != EXPAND_USER_FUNC) { STRCAT(IObuff, "("); ! if (!has_varargs(fp) && fp->uf_args.ga_len == 0) STRCAT(IObuff, ")"); } return IObuff; *************** *** 2876,2882 **** *p = NUL; if (!eap->skip) ! fp = find_func(name); vim_free(name); if (!eap->skip) --- 3211,3217 ---- *p = NUL; if (!eap->skip) ! fp = find_func(name, NULL); vim_free(name); if (!eap->skip) *************** *** 2931,2937 **** if (name == NULL || !func_name_refcount(name)) return; ! fp = find_func(name); if (fp == NULL && isdigit(*name)) { #ifdef EXITFREE --- 3266,3272 ---- if (name == NULL || !func_name_refcount(name)) return; ! fp = find_func(name, NULL); if (fp == NULL && isdigit(*name)) { #ifdef EXITFREE *************** *** 2974,2980 **** if (name == NULL || !func_name_refcount(name)) return; ! fp = find_func(name); if (fp != NULL) ++fp->uf_refcount; else if (isdigit(*name)) --- 3309,3315 ---- if (name == NULL || !func_name_refcount(name)) return; ! fp = find_func(name, NULL); if (fp != NULL) ++fp->uf_refcount; else if (isdigit(*name)) *************** *** 3119,3125 **** if (*startarg != '(') { ! semsg(_(e_missingparen), eap->arg); goto end; } --- 3454,3460 ---- if (*startarg != '(') { ! semsg(_(e_missing_paren), eap->arg); goto end; } *************** *** 3444,3450 **** : rettv->vval.v_partial->pt_name; // Translate "s:func" to the stored function name. fname = fname_trans_sid(fname, fname_buf, &tofree, &error); ! fp = find_func(fname); vim_free(tofree); } --- 3779,3785 ---- : rettv->vval.v_partial->pt_name; // Translate "s:func" to the stored function name. fname = fname_trans_sid(fname, fname_buf, &tofree, &error); ! fp = find_func(fname, NULL); vim_free(tofree); } *************** *** 3610,3616 **** hashtab_T * get_funccal_local_ht() { ! if (current_funccal == NULL) return NULL; return &get_funccal()->l_vars.dv_hashtab; } --- 3945,3951 ---- hashtab_T * get_funccal_local_ht() { ! if (current_funccal == NULL || current_funccal->l_vars.dv_refcount == 0) return NULL; return &get_funccal()->l_vars.dv_hashtab; } *************** *** 3622,3628 **** dictitem_T * get_funccal_local_var() { ! if (current_funccal == NULL) return NULL; return &get_funccal()->l_vars_var; } --- 3957,3963 ---- dictitem_T * get_funccal_local_var() { ! if (current_funccal == NULL || current_funccal->l_vars.dv_refcount == 0) return NULL; return &get_funccal()->l_vars_var; } *************** *** 3634,3640 **** hashtab_T * get_funccal_args_ht() { ! if (current_funccal == NULL) return NULL; return &get_funccal()->l_avars.dv_hashtab; } --- 3969,3975 ---- hashtab_T * get_funccal_args_ht() { ! if (current_funccal == NULL || current_funccal->l_vars.dv_refcount == 0) return NULL; return &get_funccal()->l_avars.dv_hashtab; } *************** *** 3646,3652 **** dictitem_T * get_funccal_args_var() { ! if (current_funccal == NULL) return NULL; return &get_funccal()->l_avars_var; } --- 3981,3987 ---- dictitem_T * get_funccal_args_var() { ! if (current_funccal == NULL || current_funccal->l_vars.dv_refcount == 0) return NULL; return &get_funccal()->l_avars_var; } *************** *** 3657,3663 **** void list_func_vars(int *first) { ! if (current_funccal != NULL) list_hashtable_vars(¤t_funccal->l_vars.dv_hashtab, "l:", FALSE, first); } --- 3992,3998 ---- void list_func_vars(int *first) { ! if (current_funccal != NULL && current_funccal->l_vars.dv_refcount > 0) list_hashtable_vars(¤t_funccal->l_vars.dv_hashtab, "l:", FALSE, first); } *************** *** 3866,3872 **** if (fp_in == NULL) { fname = fname_trans_sid(name, fname_buf, &tofree, &error); ! fp = find_func(fname); } if (fp != NULL) { --- 4201,4207 ---- if (fp_in == NULL) { fname = fname_trans_sid(name, fname_buf, &tofree, &error); ! fp = find_func(fname, NULL); } if (fp != NULL) { *** ../vim-8.2.0148/src/vim.h 2020-01-23 16:51:58.566062088 +0100 --- src/vim.h 2020-01-26 15:30:08.625766579 +0100 *************** *** 2151,2156 **** --- 2151,2160 ---- USEPOPUP_HIDDEN // use info popup initially hidden } use_popup_T; + // Flags for assignment functions. + #define LET_IS_CONST 1 // ":const" + #define LET_NO_COMMAND 2 // "var = expr" without ":let" or ":const" + #include "ex_cmds.h" // Ex command defines #include "spell.h" // spell checking stuff *** ../vim-8.2.0148/src/vim9.h 2020-01-26 15:45:56.741682147 +0100 --- src/vim9.h 2020-01-26 15:30:08.625766579 +0100 *************** *** 0 **** --- 1,252 ---- + /* vi:set ts=8 sts=4 sw=4 noet: + * + * VIM - Vi IMproved by Bram Moolenaar + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + * See README.txt for an overview of the Vim source code. + */ + + /* + * vim9.h: types and globals used for Vim9 script. + */ + + typedef enum { + ISN_EXEC, // execute Ex command line isn_arg.string + ISN_ECHO, // echo isn_arg.number items on top of stack + + // get and set variables + ISN_LOAD, // push local variable isn_arg.number + ISN_LOADV, // push v: variable isn_arg.number + ISN_LOADSCRIPT, // push script-local variable isn_arg.script. + ISN_LOADS, // push s: variable isn_arg.string + ISN_LOADG, // push g: variable isn_arg.string + ISN_LOADOPT, // push option isn_arg.string + ISN_LOADENV, // push environment variable isn_arg.string + ISN_LOADREG, // push register isn_arg.number + + ISN_STORE, // pop into local variable isn_arg.number + ISN_STOREG, // pop into global variable isn_arg.string + ISN_STORESCRIPT, // pop into scirpt variable isn_arg.script + ISN_STOREOPT, // pop into option isn_arg.string + // ISN_STOREOTHER, // pop into other script variable isn_arg.other. + + ISN_STORENR, // store number into local variable isn_arg.storenr.str_idx + + // constants + ISN_PUSHNR, // push number isn_arg.number + ISN_PUSHBOOL, // push bool value isn_arg.number + ISN_PUSHSPEC, // push special value isn_arg.number + ISN_PUSHF, // push float isn_arg.fnumber + ISN_PUSHS, // push string isn_arg.string + ISN_PUSHBLOB, // push blob isn_arg.blob + ISN_NEWLIST, // push list from stack items, size is isn_arg.number + ISN_NEWDICT, // push dict from stack items, size is isn_arg.number + + // function call + ISN_BCALL, // call builtin function isn_arg.bfunc + ISN_DCALL, // call def function isn_arg.dfunc + ISN_UCALL, // call user function or funcref/partial isn_arg.ufunc + ISN_PCALL, // call partial, use isn_arg.pfunc + ISN_RETURN, // return, result is on top of stack + ISN_FUNCREF, // push a function ref to dfunc isn_arg.number + + // expression operations + ISN_JUMP, // jump if condition is matched isn_arg.jump + + // loop + ISN_FOR, // get next item from a list, uses isn_arg.forloop + + ISN_TRY, // add entry to ec_trystack, uses isn_arg.try + ISN_THROW, // pop value of stack, store in v:exception + ISN_PUSHEXC, // push v:exception + ISN_CATCH, // drop v:exception + ISN_ENDTRY, // take entry off from ec_trystack + + // moreexpression operations + ISN_ADDLIST, + ISN_ADDBLOB, + + // operation with two arguments; isn_arg.op.op_type is exptype_T + ISN_OPNR, + ISN_OPFLOAT, + ISN_OPANY, + + // comparative operations; isn_arg.op.op_type is exptype_T, op_ic used + ISN_COMPAREBOOL, + ISN_COMPARESPECIAL, + ISN_COMPARENR, + ISN_COMPAREFLOAT, + ISN_COMPARESTRING, + ISN_COMPAREBLOB, + ISN_COMPARELIST, + ISN_COMPAREDICT, + ISN_COMPAREFUNC, + ISN_COMPAREPARTIAL, + ISN_COMPAREANY, + + // expression operations + ISN_CONCAT, + ISN_INDEX, // [expr] list index + ISN_MEMBER, // dict.member using isn_arg.string + ISN_2BOOL, // convert value to bool, invert if isn_arg.number != 0 + ISN_2STRING, // convert value to string at isn_arg.number on stack + ISN_NEGATENR, // apply "-" to number + + ISN_CHECKNR, // check value can be used as a number + ISN_CHECKTYPE, // check value type is isn_arg.type.tc_type + + ISN_DROP // pop stack and discard value + } isntype_T; + + + // arguments to ISN_BCALL + typedef struct { + int cbf_idx; // index in "global_functions" + int cbf_argcount; // number of arguments on top of stack + } cbfunc_T; + + // arguments to ISN_DCALL + typedef struct { + int cdf_idx; // index in "def_functions" for ISN_DCALL + int cdf_argcount; // number of arguments on top of stack + } cdfunc_T; + + // arguments to ISN_PCALL + typedef struct { + int cpf_top; // when TRUE partial is above the arguments + int cpf_argcount; // number of arguments on top of stack + } cpfunc_T; + + // arguments to ISN_UCALL and ISN_XCALL + typedef struct { + char_u *cuf_name; + int cuf_argcount; // number of arguments on top of stack + } cufunc_T; + + typedef enum { + JUMP_ALWAYS, + JUMP_IF_TRUE, // pop and jump if true + JUMP_IF_FALSE, // pop and jump if false + JUMP_AND_KEEP_IF_TRUE, // jump if top of stack is true, drop if not + JUMP_AND_KEEP_IF_FALSE, // jump if top of stack is false, drop if not + } jumpwhen_T; + + // arguments to ISN_JUMP + typedef struct { + jumpwhen_T jump_when; + int jump_where; // position to jump to + } jump_T; + + // arguments to ISN_FOR + typedef struct { + int for_idx; // loop variable index + int for_end; // position to jump to after done + } forloop_T; + + // arguments to ISN_TRY + typedef struct { + int try_catch; // position to jump to on throw + int try_finally; // position to jump to for return + } try_T; + + // arguments to ISN_ECHO + typedef struct { + int echo_with_white; // :echo instead of :echon + int echo_count; // number of expressions + } echo_T; + + // arguments to ISN_OPNR, ISN_OPFLOAT, etc. + typedef struct { + exptype_T op_type; + int op_ic; // TRUE with '#', FALSE with '?', else MAYBE + } opexpr_T; + + // arguments to ISN_CHECKTYPE + typedef struct { + vartype_T ct_type; + int ct_off; // offset in stack, -1 is bottom + } checktype_T; + + // arguments to ISN_STORENR + typedef struct { + int str_idx; + varnumber_T str_val; + } storenr_T; + + // arguments to ISN_STOREOPT + typedef struct { + char_u *so_name; + int so_flags; + } storeopt_T; + + // arguments to ISN_LOADS + typedef struct { + char_u *ls_name; // variable name + int ls_sid; // script ID + } loads_T; + + // arguments to ISN_LOADSCRIPT + typedef struct { + int script_sid; // script ID + int script_idx; // index in sn_var_vals + } script_T; + + /* + * Instruction + */ + typedef struct { + isntype_T isn_type; + int isn_lnum; + union { + char_u *string; + varnumber_T number; + blob_T *blob; + #ifdef FEAT_FLOAT + float_T fnumber; + #endif + jump_T jump; + forloop_T forloop; + try_T try; + cbfunc_T bfunc; + cdfunc_T dfunc; + cpfunc_T pfunc; + cufunc_T ufunc; + echo_T echo; + opexpr_T op; + checktype_T type; + storenr_T storenr; + storeopt_T storeopt; + loads_T loads; + script_T script; + } isn_arg; + } isn_T; + + /* + * Info about a function defined with :def. Used in "def_functions". + */ + struct dfunc_S { + ufunc_T *df_ufunc; // struct containing most stuff + int df_idx; // index in def_functions + int df_deleted; // if TRUE function was deleted + + garray_T df_def_args_isn; // default argument instructions + isn_T *df_instr; // function body to be executed + int df_instr_count; + + int df_varcount; // number of local variables + }; + + // Number of entries used by stack frame for a function call. + #define STACK_FRAME_SIZE 3 + + + #ifdef DEFINE_VIM9_GLOBALS + // Functions defined with :def are stored in this growarray. + // They are never removed, so that they can be found by index. + // Deleted functions have the df_deleted flag set. + garray_T def_functions = {0, 0, sizeof(dfunc_T), 50, NULL}; + #else + extern garray_T def_functions; + #endif + *** ../vim-8.2.0148/src/vim9compile.c 2020-01-26 15:45:56.745682128 +0100 --- src/vim9compile.c 2020-01-26 15:30:08.625766579 +0100 *************** *** 0 **** --- 1,4612 ---- + /* vi:set ts=8 sts=4 sw=4 noet: + * + * VIM - Vi IMproved by Bram Moolenaar + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + * See README.txt for an overview of the Vim source code. + */ + + /* + * vim9compile.c: :def and dealing with instructions + */ + + #define USING_FLOAT_STUFF + #include "vim.h" + + #if defined(FEAT_EVAL) || defined(PROTO) + + #ifdef VMS + # include + #endif + + #define DEFINE_VIM9_GLOBALS + #include "vim9.h" + + /* + * Chain of jump instructions where the end label needs to be set. + */ + typedef struct endlabel_S endlabel_T; + struct endlabel_S { + endlabel_T *el_next; // chain end_label locations + int el_end_label; // instruction idx where to set end + }; + + /* + * info specific for the scope of :if / elseif / else + */ + typedef struct { + int is_if_label; // instruction idx at IF or ELSEIF + endlabel_T *is_end_label; // instructions to set end label + } ifscope_T; + + /* + * info specific for the scope of :while + */ + typedef struct { + int ws_top_label; // instruction idx at WHILE + endlabel_T *ws_end_label; // instructions to set end + } whilescope_T; + + /* + * info specific for the scope of :for + */ + typedef struct { + int fs_top_label; // instruction idx at FOR + endlabel_T *fs_end_label; // break instructions + } forscope_T; + + /* + * info specific for the scope of :try + */ + typedef struct { + int ts_try_label; // instruction idx at TRY + endlabel_T *ts_end_label; // jump to :finally or :endtry + int ts_catch_label; // instruction idx of last CATCH + int ts_caught_all; // "catch" without argument encountered + } tryscope_T; + + typedef enum { + NO_SCOPE, + IF_SCOPE, + WHILE_SCOPE, + FOR_SCOPE, + TRY_SCOPE, + BLOCK_SCOPE + } scopetype_T; + + /* + * Info for one scope, pointed to by "ctx_scope". + */ + typedef struct scope_S scope_T; + struct scope_S { + scope_T *se_outer; // scope containing this one + scopetype_T se_type; + int se_local_count; // ctx_locals.ga_len before scope + union { + ifscope_T se_if; + whilescope_T se_while; + forscope_T se_for; + tryscope_T se_try; + }; + }; + + /* + * Entry for "ctx_locals". Used for arguments and local variables. + */ + typedef struct { + char_u *lv_name; + type_T *lv_type; + int lv_const; // when TRUE cannot be assigned to + int lv_arg; // when TRUE this is an argument + } lvar_T; + + /* + * Context for compiling lines of Vim script. + * Stores info about the local variables and condition stack. + */ + struct cctx_S { + ufunc_T *ctx_ufunc; // current function + int ctx_lnum; // line number in current function + garray_T ctx_instr; // generated instructions + + garray_T ctx_locals; // currently visible local variables + int ctx_max_local; // maximum number of locals at one time + + garray_T ctx_imports; // imported items + + scope_T *ctx_scope; // current scope, NULL at toplevel + + garray_T ctx_type_stack; // type of each item on the stack + garray_T *ctx_type_list; // space for adding types + }; + + static char e_var_notfound[] = N_("E1001: variable not found: %s"); + static char e_syntax_at[] = N_("E1002: Syntax error at %s"); + + static int compile_expr1(char_u **arg, cctx_T *cctx); + static int compile_expr2(char_u **arg, cctx_T *cctx); + static int compile_expr3(char_u **arg, cctx_T *cctx); + + /* + * Lookup variable "name" in the local scope and return the index. + */ + static int + lookup_local(char_u *name, size_t len, cctx_T *cctx) + { + int idx; + + if (len <= 0) + return -1; + for (idx = 0; idx < cctx->ctx_locals.ga_len; ++idx) + { + lvar_T *lvar = ((lvar_T *)cctx->ctx_locals.ga_data) + idx; + + if (STRNCMP(name, lvar->lv_name, len) == 0 + && STRLEN(lvar->lv_name) == len) + return idx; + } + return -1; + } + + /* + * Lookup an argument in the current function. + * Returns the argument index or -1 if not found. + */ + static int + lookup_arg(char_u *name, size_t len, cctx_T *cctx) + { + int idx; + + if (len <= 0) + return -1; + for (idx = 0; idx < cctx->ctx_ufunc->uf_args.ga_len; ++idx) + { + char_u *arg = FUNCARG(cctx->ctx_ufunc, idx); + + if (STRNCMP(name, arg, len) == 0 && STRLEN(arg) == len) + return idx; + } + return -1; + } + + /* + * Lookup a vararg argument in the current function. + * Returns TRUE if there is a match. + */ + static int + lookup_vararg(char_u *name, size_t len, cctx_T *cctx) + { + char_u *va_name = cctx->ctx_ufunc->uf_va_name; + + return len > 0 && va_name != NULL + && STRNCMP(name, va_name, len) == 0 && STRLEN(va_name) == len; + } + + /* + * Lookup a variable in the current script. + * Returns OK or FAIL. + */ + static int + lookup_script(char_u *name, size_t len) + { + int cc; + hashtab_T *ht = &SCRIPT_VARS(current_sctx.sc_sid); + dictitem_T *di; + + cc = name[len]; + name[len] = NUL; + di = find_var_in_ht(ht, 0, name, TRUE); + name[len] = cc; + return di == NULL ? FAIL: OK; + } + + static type_T * + get_list_type(type_T *member_type, garray_T *type_list) + { + type_T *type; + + // recognize commonly used types + if (member_type->tt_type == VAR_UNKNOWN) + return &t_list_any; + if (member_type->tt_type == VAR_NUMBER) + return &t_list_number; + if (member_type->tt_type == VAR_STRING) + return &t_list_string; + + // Not a common type, create a new entry. + if (ga_grow(type_list, 1) == FAIL) + return FAIL; + type = ((type_T *)type_list->ga_data) + type_list->ga_len; + ++type_list->ga_len; + type->tt_type = VAR_LIST; + type->tt_member = member_type; + return type; + } + + static type_T * + get_dict_type(type_T *member_type, garray_T *type_list) + { + type_T *type; + + // recognize commonly used types + if (member_type->tt_type == VAR_UNKNOWN) + return &t_dict_any; + if (member_type->tt_type == VAR_NUMBER) + return &t_dict_number; + if (member_type->tt_type == VAR_STRING) + return &t_dict_string; + + // Not a common type, create a new entry. + if (ga_grow(type_list, 1) == FAIL) + return FAIL; + type = ((type_T *)type_list->ga_data) + type_list->ga_len; + ++type_list->ga_len; + type->tt_type = VAR_DICT; + type->tt_member = member_type; + return type; + } + + ///////////////////////////////////////////////////////////////////// + // Following generate_ functions expect the caller to call ga_grow(). + + /* + * Generate an instruction without arguments. + * Returns a pointer to the new instruction, NULL if failed. + */ + static isn_T * + generate_instr(cctx_T *cctx, isntype_T isn_type) + { + garray_T *instr = &cctx->ctx_instr; + isn_T *isn; + + if (ga_grow(instr, 1) == FAIL) + return NULL; + isn = ((isn_T *)instr->ga_data) + instr->ga_len; + isn->isn_type = isn_type; + isn->isn_lnum = cctx->ctx_lnum + 1; + ++instr->ga_len; + + return isn; + } + + /* + * Generate an instruction without arguments. + * "drop" will be removed from the stack. + * Returns a pointer to the new instruction, NULL if failed. + */ + static isn_T * + generate_instr_drop(cctx_T *cctx, isntype_T isn_type, int drop) + { + garray_T *stack = &cctx->ctx_type_stack; + + stack->ga_len -= drop; + return generate_instr(cctx, isn_type); + } + + /* + * Generate instruction "isn_type" and put "type" on the type stack. + */ + static isn_T * + generate_instr_type(cctx_T *cctx, isntype_T isn_type, type_T *type) + { + isn_T *isn; + garray_T *stack = &cctx->ctx_type_stack; + + if ((isn = generate_instr(cctx, isn_type)) == NULL) + return NULL; + + if (ga_grow(stack, 1) == FAIL) + return NULL; + ((type_T **)stack->ga_data)[stack->ga_len] = type; + ++stack->ga_len; + + return isn; + } + + /* + * If type at "offset" isn't already VAR_STRING then generate ISN_2STRING. + */ + static int + may_generate_2STRING(int offset, cctx_T *cctx) + { + isn_T *isn; + garray_T *stack = &cctx->ctx_type_stack; + type_T **type = ((type_T **)stack->ga_data) + stack->ga_len + offset; + + if ((*type)->tt_type == VAR_STRING) + return OK; + *type = &t_string; + + if ((isn = generate_instr(cctx, ISN_2STRING)) == NULL) + return FAIL; + isn->isn_arg.number = offset; + + return OK; + } + + static int + check_number_or_float(vartype_T type1, vartype_T type2, char_u *op) + { + if (!((type1 == VAR_NUMBER || type1 == VAR_FLOAT || type1 == VAR_UNKNOWN) + && (type2 == VAR_NUMBER || type2 == VAR_FLOAT + || type2 == VAR_UNKNOWN))) + { + if (*op == '+') + semsg(_("E1035: wrong argument type for +")); + else + semsg(_("E1036: %c requires number or float arguments"), *op); + return FAIL; + } + return OK; + } + + /* + * Generate an instruction with two arguments. The instruction depends on the + * type of the arguments. + */ + static int + generate_two_op(cctx_T *cctx, char_u *op) + { + garray_T *stack = &cctx->ctx_type_stack; + type_T *type1; + type_T *type2; + vartype_T vartype; + isn_T *isn; + + // Get the known type of the two items on the stack. If they are matching + // use a type-specific instruction. Otherwise fall back to runtime type + // checking. + type1 = ((type_T **)stack->ga_data)[stack->ga_len - 2]; + type2 = ((type_T **)stack->ga_data)[stack->ga_len - 1]; + vartype = VAR_UNKNOWN; + if (type1->tt_type == type2->tt_type + && (type1->tt_type == VAR_NUMBER + || type1->tt_type == VAR_LIST + #ifdef FEAT_FLOAT + || type1->tt_type == VAR_FLOAT + #endif + || type1->tt_type == VAR_BLOB)) + vartype = type1->tt_type; + + switch (*op) + { + case '+': if (vartype != VAR_LIST && vartype != VAR_BLOB + && check_number_or_float( + type1->tt_type, type2->tt_type, op) == FAIL) + return FAIL; + isn = generate_instr_drop(cctx, + vartype == VAR_NUMBER ? ISN_OPNR + : vartype == VAR_LIST ? ISN_ADDLIST + : vartype == VAR_BLOB ? ISN_ADDBLOB + #ifdef FEAT_FLOAT + : vartype == VAR_FLOAT ? ISN_OPFLOAT + #endif + : ISN_OPANY, 1); + if (isn != NULL) + isn->isn_arg.op.op_type = EXPR_ADD; + break; + + case '-': + case '*': + case '/': if (check_number_or_float(type1->tt_type, type2->tt_type, + op) == FAIL) + return FAIL; + if (vartype == VAR_NUMBER) + isn = generate_instr_drop(cctx, ISN_OPNR, 1); + #ifdef FEAT_FLOAT + else if (vartype == VAR_FLOAT) + isn = generate_instr_drop(cctx, ISN_OPFLOAT, 1); + #endif + else + isn = generate_instr_drop(cctx, ISN_OPANY, 1); + if (isn != NULL) + isn->isn_arg.op.op_type = *op == '*' + ? EXPR_MULT : *op == '/'? EXPR_DIV : EXPR_SUB; + break; + + case '%': if ((type1->tt_type != VAR_UNKNOWN + && type1->tt_type != VAR_NUMBER) + || (type2->tt_type != VAR_UNKNOWN + && type2->tt_type != VAR_NUMBER)) + { + emsg(_("E1035: % requires number arguments")); + return FAIL; + } + isn = generate_instr_drop(cctx, + vartype == VAR_NUMBER ? ISN_OPNR : ISN_OPANY, 1); + if (isn != NULL) + isn->isn_arg.op.op_type = EXPR_REM; + break; + } + + // correct type of result + if (vartype == VAR_UNKNOWN) + { + type_T *type = &t_any; + + #ifdef FEAT_FLOAT + // float+number and number+float results in float + if ((type1->tt_type == VAR_NUMBER || type1->tt_type == VAR_FLOAT) + && (type2->tt_type == VAR_NUMBER || type2->tt_type == VAR_FLOAT)) + type = &t_float; + #endif + ((type_T **)stack->ga_data)[stack->ga_len - 1] = type; + } + + return OK; + } + + /* + * Generate an ISN_COMPARE* instruction with a boolean result. + */ + static int + generate_COMPARE(cctx_T *cctx, exptype_T exptype, int ic) + { + isntype_T isntype = ISN_DROP; + isn_T *isn; + garray_T *stack = &cctx->ctx_type_stack; + vartype_T type1; + vartype_T type2; + + // Get the known type of the two items on the stack. If they are matching + // use a type-specific instruction. Otherwise fall back to runtime type + // checking. + type1 = ((type_T **)stack->ga_data)[stack->ga_len - 2]->tt_type; + type2 = ((type_T **)stack->ga_data)[stack->ga_len - 1]->tt_type; + if (type1 == type2) + { + switch (type1) + { + case VAR_BOOL: isntype = ISN_COMPAREBOOL; break; + case VAR_SPECIAL: isntype = ISN_COMPARESPECIAL; break; + case VAR_NUMBER: isntype = ISN_COMPARENR; break; + case VAR_FLOAT: isntype = ISN_COMPAREFLOAT; break; + case VAR_STRING: isntype = ISN_COMPARESTRING; break; + case VAR_BLOB: isntype = ISN_COMPAREBLOB; break; + case VAR_LIST: isntype = ISN_COMPARELIST; break; + case VAR_DICT: isntype = ISN_COMPAREDICT; break; + case VAR_FUNC: isntype = ISN_COMPAREFUNC; break; + case VAR_PARTIAL: isntype = ISN_COMPAREPARTIAL; break; + default: isntype = ISN_COMPAREANY; break; + } + } + else if (type1 == VAR_UNKNOWN || type2 == VAR_UNKNOWN + || ((type1 == VAR_NUMBER || type1 == VAR_FLOAT) + && (type2 == VAR_NUMBER || type2 ==VAR_FLOAT))) + isntype = ISN_COMPAREANY; + + if ((exptype == EXPR_IS || exptype == EXPR_ISNOT) + && (isntype == ISN_COMPAREBOOL + || isntype == ISN_COMPARESPECIAL + || isntype == ISN_COMPARENR + || isntype == ISN_COMPAREFLOAT)) + { + semsg(_("E1037: Cannot use \"%s\" with %s"), + exptype == EXPR_IS ? "is" : "isnot" , vartype_name(type1)); + return FAIL; + } + if (isntype == ISN_DROP + || ((exptype != EXPR_EQUAL && exptype != EXPR_NEQUAL + && (type1 == VAR_BOOL || type1 == VAR_SPECIAL + || type2 == VAR_BOOL || type2 == VAR_SPECIAL))) + || ((exptype != EXPR_EQUAL && exptype != EXPR_NEQUAL + && exptype != EXPR_IS && exptype != EXPR_ISNOT + && (type1 == VAR_BLOB || type2 == VAR_BLOB + || type1 == VAR_LIST || type2 == VAR_LIST)))) + { + semsg(_("E1037: Cannot compare %s with %s"), + vartype_name(type1), vartype_name(type2)); + return FAIL; + } + + if ((isn = generate_instr(cctx, isntype)) == NULL) + return FAIL; + isn->isn_arg.op.op_type = exptype; + isn->isn_arg.op.op_ic = ic; + + // takes two arguments, puts one bool back + if (stack->ga_len >= 2) + { + --stack->ga_len; + ((type_T **)stack->ga_data)[stack->ga_len - 1] = &t_bool; + } + + return OK; + } + + /* + * Generate an ISN_2BOOL instruction. + */ + static int + generate_2BOOL(cctx_T *cctx, int invert) + { + isn_T *isn; + garray_T *stack = &cctx->ctx_type_stack; + + if ((isn = generate_instr(cctx, ISN_2BOOL)) == NULL) + return FAIL; + isn->isn_arg.number = invert; + + // type becomes bool + ((type_T **)stack->ga_data)[stack->ga_len - 1] = &t_bool; + + return OK; + } + + static int + generate_TYPECHECK(cctx_T *cctx, type_T *vartype, int offset) + { + isn_T *isn; + garray_T *stack = &cctx->ctx_type_stack; + + if ((isn = generate_instr(cctx, ISN_CHECKTYPE)) == NULL) + return FAIL; + isn->isn_arg.type.ct_type = vartype->tt_type; // TODO: whole type + isn->isn_arg.type.ct_off = offset; + + // type becomes vartype + ((type_T **)stack->ga_data)[stack->ga_len - 1] = vartype; + + return OK; + } + + /* + * Generate an ISN_PUSHNR instruction. + */ + static int + generate_PUSHNR(cctx_T *cctx, varnumber_T number) + { + isn_T *isn; + + if ((isn = generate_instr_type(cctx, ISN_PUSHNR, &t_number)) == NULL) + return FAIL; + isn->isn_arg.number = number; + + return OK; + } + + /* + * Generate an ISN_PUSHBOOL instruction. + */ + static int + generate_PUSHBOOL(cctx_T *cctx, varnumber_T number) + { + isn_T *isn; + + if ((isn = generate_instr_type(cctx, ISN_PUSHBOOL, &t_bool)) == NULL) + return FAIL; + isn->isn_arg.number = number; + + return OK; + } + + /* + * Generate an ISN_PUSHSPEC instruction. + */ + static int + generate_PUSHSPEC(cctx_T *cctx, varnumber_T number) + { + isn_T *isn; + + if ((isn = generate_instr_type(cctx, ISN_PUSHSPEC, &t_special)) == NULL) + return FAIL; + isn->isn_arg.number = number; + + return OK; + } + + #ifdef FEAT_FLOAT + /* + * Generate an ISN_PUSHF instruction. + */ + static int + generate_PUSHF(cctx_T *cctx, float_T fnumber) + { + isn_T *isn; + + if ((isn = generate_instr_type(cctx, ISN_PUSHF, &t_float)) == NULL) + return FAIL; + isn->isn_arg.fnumber = fnumber; + + return OK; + } + #endif + + /* + * Generate an ISN_PUSHS instruction. + * Consumes "str". + */ + static int + generate_PUSHS(cctx_T *cctx, char_u *str) + { + isn_T *isn; + + if ((isn = generate_instr_type(cctx, ISN_PUSHS, &t_string)) == NULL) + return FAIL; + isn->isn_arg.string = str; + + return OK; + } + + /* + * Generate an ISN_PUSHBLOB instruction. + * Consumes "blob". + */ + static int + generate_PUSHBLOB(cctx_T *cctx, blob_T *blob) + { + isn_T *isn; + + if ((isn = generate_instr_type(cctx, ISN_PUSHBLOB, &t_blob)) == NULL) + return FAIL; + isn->isn_arg.blob = blob; + + return OK; + } + + /* + * Generate an ISN_STORE instruction. + */ + static int + generate_STORE(cctx_T *cctx, isntype_T isn_type, int idx, char_u *name) + { + isn_T *isn; + + if ((isn = generate_instr_drop(cctx, isn_type, 1)) == NULL) + return FAIL; + if (name != NULL) + isn->isn_arg.string = vim_strsave(name); + else + isn->isn_arg.number = idx; + + return OK; + } + + /* + * Generate an ISN_STORENR instruction (short for ISN_PUSHNR + ISN_STORE) + */ + static int + generate_STORENR(cctx_T *cctx, int idx, varnumber_T value) + { + isn_T *isn; + + if ((isn = generate_instr(cctx, ISN_STORENR)) == NULL) + return FAIL; + isn->isn_arg.storenr.str_idx = idx; + isn->isn_arg.storenr.str_val = value; + + return OK; + } + + /* + * Generate an ISN_STOREOPT instruction + */ + static int + generate_STOREOPT(cctx_T *cctx, char_u *name, int opt_flags) + { + isn_T *isn; + + if ((isn = generate_instr(cctx, ISN_STOREOPT)) == NULL) + return FAIL; + isn->isn_arg.storeopt.so_name = vim_strsave(name); + isn->isn_arg.storeopt.so_flags = opt_flags; + + return OK; + } + + /* + * Generate an ISN_LOAD or similar instruction. + */ + static int + generate_LOAD( + cctx_T *cctx, + isntype_T isn_type, + int idx, + char_u *name, + type_T *type) + { + isn_T *isn; + + if ((isn = generate_instr_type(cctx, isn_type, type)) == NULL) + return FAIL; + if (name != NULL) + isn->isn_arg.string = vim_strsave(name); + else + isn->isn_arg.number = idx; + + return OK; + } + + /* + * Generate an ISN_LOADS instruction. + */ + static int + generate_LOADS( + cctx_T *cctx, + char_u *name, + int sid) + { + isn_T *isn; + + if ((isn = generate_instr_type(cctx, ISN_LOADS, &t_any)) == NULL) + return FAIL; + isn->isn_arg.loads.ls_name = vim_strsave(name); + isn->isn_arg.loads.ls_sid = sid; + + return OK; + } + + /* + * Generate an ISN_LOADSCRIPT or ISN_STORESCRIPT instruction. + */ + static int + generate_SCRIPT( + cctx_T *cctx, + isntype_T isn_type, + int sid, + int idx, + type_T *type) + { + isn_T *isn; + + if (isn_type == ISN_LOADSCRIPT) + isn = generate_instr_type(cctx, isn_type, type); + else + isn = generate_instr_drop(cctx, isn_type, 1); + if (isn == NULL) + return FAIL; + isn->isn_arg.script.script_sid = sid; + isn->isn_arg.script.script_idx = idx; + return OK; + } + + /* + * Generate an ISN_NEWLIST instruction. + */ + static int + generate_NEWLIST(cctx_T *cctx, int count) + { + isn_T *isn; + garray_T *stack = &cctx->ctx_type_stack; + garray_T *type_list = cctx->ctx_type_list; + type_T *type; + type_T *member; + + if ((isn = generate_instr(cctx, ISN_NEWLIST)) == NULL) + return FAIL; + isn->isn_arg.number = count; + + // drop the value types + stack->ga_len -= count; + + // use the first value type for the list member type + if (count > 0) + member = ((type_T **)stack->ga_data)[stack->ga_len]; + else + member = &t_any; + type = get_list_type(member, type_list); + + // add the list type to the type stack + if (ga_grow(stack, 1) == FAIL) + return FAIL; + ((type_T **)stack->ga_data)[stack->ga_len] = type; + ++stack->ga_len; + + return OK; + } + + /* + * Generate an ISN_NEWDICT instruction. + */ + static int + generate_NEWDICT(cctx_T *cctx, int count) + { + isn_T *isn; + garray_T *stack = &cctx->ctx_type_stack; + garray_T *type_list = cctx->ctx_type_list; + type_T *type; + type_T *member; + + if ((isn = generate_instr(cctx, ISN_NEWDICT)) == NULL) + return FAIL; + isn->isn_arg.number = count; + + // drop the key and value types + stack->ga_len -= 2 * count; + + // use the first value type for the list member type + if (count > 0) + member = ((type_T **)stack->ga_data)[stack->ga_len + 1]; + else + member = &t_any; + type = get_dict_type(member, type_list); + + // add the dict type to the type stack + if (ga_grow(stack, 1) == FAIL) + return FAIL; + ((type_T **)stack->ga_data)[stack->ga_len] = type; + ++stack->ga_len; + + return OK; + } + + /* + * Generate an ISN_FUNCREF instruction. + */ + static int + generate_FUNCREF(cctx_T *cctx, int dfunc_idx) + { + isn_T *isn; + garray_T *stack = &cctx->ctx_type_stack; + + if ((isn = generate_instr(cctx, ISN_FUNCREF)) == NULL) + return FAIL; + isn->isn_arg.number = dfunc_idx; + + if (ga_grow(stack, 1) == FAIL) + return FAIL; + ((type_T **)stack->ga_data)[stack->ga_len] = &t_partial_any; + // TODO: argument and return types + ++stack->ga_len; + + return OK; + } + + /* + * Generate an ISN_JUMP instruction. + */ + static int + generate_JUMP(cctx_T *cctx, jumpwhen_T when, int where) + { + isn_T *isn; + garray_T *stack = &cctx->ctx_type_stack; + + if ((isn = generate_instr(cctx, ISN_JUMP)) == NULL) + return FAIL; + isn->isn_arg.jump.jump_when = when; + isn->isn_arg.jump.jump_where = where; + + if (when != JUMP_ALWAYS && stack->ga_len > 0) + --stack->ga_len; + + return OK; + } + + static int + generate_FOR(cctx_T *cctx, int loop_idx) + { + isn_T *isn; + garray_T *stack = &cctx->ctx_type_stack; + + if ((isn = generate_instr(cctx, ISN_FOR)) == NULL) + return FAIL; + isn->isn_arg.forloop.for_idx = loop_idx; + + if (ga_grow(stack, 1) == FAIL) + return FAIL; + // type doesn't matter, will be stored next + ((type_T **)stack->ga_data)[stack->ga_len] = &t_any; + ++stack->ga_len; + + return OK; + } + + /* + * Generate an ISN_BCALL instruction. + * Return FAIL if the number of arguments is wrong. + */ + static int + generate_BCALL(cctx_T *cctx, int func_idx, int argcount) + { + isn_T *isn; + garray_T *stack = &cctx->ctx_type_stack; + + if (check_internal_func(func_idx, argcount) == FAIL) + return FAIL; + + if ((isn = generate_instr(cctx, ISN_BCALL)) == NULL) + return FAIL; + isn->isn_arg.bfunc.cbf_idx = func_idx; + isn->isn_arg.bfunc.cbf_argcount = argcount; + + stack->ga_len -= argcount; // drop the arguments + if (ga_grow(stack, 1) == FAIL) + return FAIL; + ((type_T **)stack->ga_data)[stack->ga_len] = + internal_func_ret_type(func_idx, argcount); + ++stack->ga_len; // add return value + + return OK; + } + + /* + * Generate an ISN_DCALL or ISN_UCALL instruction. + * Return FAIL if the number of arguments is wrong. + */ + static int + generate_CALL(cctx_T *cctx, ufunc_T *ufunc, int argcount) + { + isn_T *isn; + garray_T *stack = &cctx->ctx_type_stack; + int regular_args = ufunc->uf_args.ga_len; + + if (argcount > regular_args && !has_varargs(ufunc)) + { + semsg(_(e_toomanyarg), ufunc->uf_name); + return FAIL; + } + if (argcount < regular_args - ufunc->uf_def_args.ga_len) + { + semsg(_(e_toofewarg), ufunc->uf_name); + return FAIL; + } + + // Turn varargs into a list. + if (ufunc->uf_va_name != NULL) + { + int count = argcount - regular_args; + + // TODO: add default values for optional arguments? + generate_NEWLIST(cctx, count < 0 ? 0 : count); + argcount = regular_args + 1; + } + + if ((isn = generate_instr(cctx, + ufunc->uf_dfunc_idx >= 0 ? ISN_DCALL : ISN_UCALL)) == NULL) + return FAIL; + if (ufunc->uf_dfunc_idx >= 0) + { + isn->isn_arg.dfunc.cdf_idx = ufunc->uf_dfunc_idx; + isn->isn_arg.dfunc.cdf_argcount = argcount; + } + else + { + // A user function may be deleted and redefined later, can't use the + // ufunc pointer, need to look it up again at runtime. + isn->isn_arg.ufunc.cuf_name = vim_strsave(ufunc->uf_name); + isn->isn_arg.ufunc.cuf_argcount = argcount; + } + + stack->ga_len -= argcount; // drop the arguments + if (ga_grow(stack, 1) == FAIL) + return FAIL; + // add return value + ((type_T **)stack->ga_data)[stack->ga_len] = ufunc->uf_ret_type; + ++stack->ga_len; + + return OK; + } + + /* + * Generate an ISN_UCALL instruction when the function isn't defined yet. + */ + static int + generate_UCALL(cctx_T *cctx, char_u *name, int argcount) + { + isn_T *isn; + garray_T *stack = &cctx->ctx_type_stack; + + if ((isn = generate_instr(cctx, ISN_UCALL)) == NULL) + return FAIL; + isn->isn_arg.ufunc.cuf_name = vim_strsave(name); + isn->isn_arg.ufunc.cuf_argcount = argcount; + + stack->ga_len -= argcount; // drop the arguments + + // drop the funcref/partial, get back the return value + ((type_T **)stack->ga_data)[stack->ga_len - 1] = &t_any; + + return OK; + } + + /* + * Generate an ISN_PCALL instruction. + */ + static int + generate_PCALL(cctx_T *cctx, int argcount, int at_top) + { + isn_T *isn; + garray_T *stack = &cctx->ctx_type_stack; + + if ((isn = generate_instr(cctx, ISN_PCALL)) == NULL) + return FAIL; + isn->isn_arg.pfunc.cpf_top = at_top; + isn->isn_arg.pfunc.cpf_argcount = argcount; + + stack->ga_len -= argcount; // drop the arguments + + // drop the funcref/partial, get back the return value + ((type_T **)stack->ga_data)[stack->ga_len - 1] = &t_any; + + return OK; + } + + /* + * Generate an ISN_MEMBER instruction. + */ + static int + generate_MEMBER(cctx_T *cctx, char_u *name, size_t len) + { + isn_T *isn; + garray_T *stack = &cctx->ctx_type_stack; + type_T *type; + + if ((isn = generate_instr(cctx, ISN_MEMBER)) == NULL) + return FAIL; + isn->isn_arg.string = vim_strnsave(name, (int)len); + + // change dict type to dict member type + type = ((type_T **)stack->ga_data)[stack->ga_len - 1]; + ((type_T **)stack->ga_data)[stack->ga_len - 1] = type->tt_member; + + return OK; + } + + /* + * Generate an ISN_ECHO instruction. + */ + static int + generate_ECHO(cctx_T *cctx, int with_white, int count) + { + isn_T *isn; + + if ((isn = generate_instr_drop(cctx, ISN_ECHO, count)) == NULL) + return FAIL; + isn->isn_arg.echo.echo_with_white = with_white; + isn->isn_arg.echo.echo_count = count; + + return OK; + } + + static int + generate_EXEC(cctx_T *cctx, char_u *line) + { + isn_T *isn; + + if ((isn = generate_instr(cctx, ISN_EXEC)) == NULL) + return FAIL; + isn->isn_arg.string = vim_strsave(line); + return OK; + } + + static char e_white_both[] = + N_("E1004: white space required before and after '%s'"); + + /* + * Reserve space for a local variable. + * Return the index or -1 if it failed. + */ + static int + reserve_local(cctx_T *cctx, char_u *name, size_t len, int isConst, type_T *type) + { + int idx; + lvar_T *lvar; + + if (lookup_arg(name, len, cctx) >= 0 || lookup_vararg(name, len, cctx)) + { + emsg_namelen(_("E1006: %s is used as an argument"), name, (int)len); + return -1; + } + + if (ga_grow(&cctx->ctx_locals, 1) == FAIL) + return -1; + idx = cctx->ctx_locals.ga_len; + if (cctx->ctx_max_local < idx + 1) + cctx->ctx_max_local = idx + 1; + ++cctx->ctx_locals.ga_len; + + lvar = ((lvar_T *)cctx->ctx_locals.ga_data) + idx; + lvar->lv_name = vim_strnsave(name, (int)(len == 0 ? STRLEN(name) : len)); + lvar->lv_const = isConst; + lvar->lv_type = type; + + return idx; + } + + /* + * Skip over a type definition and return a pointer to just after it. + */ + char_u * + skip_type(char_u *start) + { + char_u *p = start; + + while (ASCII_ISALNUM(*p) || *p == '_') + ++p; + + // Skip over ""; this is permissive about white space. + if (*skipwhite(p) == '<') + { + p = skipwhite(p); + p = skip_type(skipwhite(p + 1)); + p = skipwhite(p); + if (*p == '>') + ++p; + } + return p; + } + + /* + * Parse the member type: "" and return "type" with the member set. + * Use "type_list" if a new type needs to be added. + * Returns NULL in case of failure. + */ + static type_T * + parse_type_member(char_u **arg, type_T *type, garray_T *type_list) + { + type_T *member_type; + + if (**arg != '<') + { + if (*skipwhite(*arg) == '<') + emsg(_("E1007: No white space allowed before <")); + else + emsg(_("E1008: Missing ")); + return NULL; + } + *arg = skipwhite(*arg + 1); + + member_type = parse_type(arg, type_list); + if (member_type == NULL) + return NULL; + + *arg = skipwhite(*arg); + if (**arg != '>') + { + emsg(_("E1009: Missing > after type")); + return NULL; + } + ++*arg; + + if (type->tt_type == VAR_LIST) + return get_list_type(member_type, type_list); + return get_dict_type(member_type, type_list); + } + + /* + * Parse a type at "arg" and advance over it. + * Return NULL for failure. + */ + type_T * + parse_type(char_u **arg, garray_T *type_list) + { + char_u *p = *arg; + size_t len; + + // skip over the first word + while (ASCII_ISALNUM(*p) || *p == '_') + ++p; + len = p - *arg; + + switch (**arg) + { + case 'a': + if (len == 3 && STRNCMP(*arg, "any", len) == 0) + { + *arg += len; + return &t_any; + } + break; + case 'b': + if (len == 4 && STRNCMP(*arg, "bool", len) == 0) + { + *arg += len; + return &t_bool; + } + if (len == 4 && STRNCMP(*arg, "blob", len) == 0) + { + *arg += len; + return &t_blob; + } + break; + case 'c': + if (len == 7 && STRNCMP(*arg, "channel", len) == 0) + { + *arg += len; + return &t_channel; + } + break; + case 'd': + if (len == 4 && STRNCMP(*arg, "dict", len) == 0) + { + *arg += len; + return parse_type_member(arg, &t_dict_any, type_list); + } + break; + case 'f': + if (len == 5 && STRNCMP(*arg, "float", len) == 0) + { + *arg += len; + return &t_float; + } + if (len == 4 && STRNCMP(*arg, "func", len) == 0) + { + *arg += len; + // TODO: arguments and return type + return &t_func_any; + } + break; + case 'j': + if (len == 3 && STRNCMP(*arg, "job", len) == 0) + { + *arg += len; + return &t_job; + } + break; + case 'l': + if (len == 4 && STRNCMP(*arg, "list", len) == 0) + { + *arg += len; + return parse_type_member(arg, &t_list_any, type_list); + } + break; + case 'n': + if (len == 6 && STRNCMP(*arg, "number", len) == 0) + { + *arg += len; + return &t_number; + } + break; + case 'p': + if (len == 4 && STRNCMP(*arg, "partial", len) == 0) + { + *arg += len; + // TODO: arguments and return type + return &t_partial_any; + } + break; + case 's': + if (len == 6 && STRNCMP(*arg, "string", len) == 0) + { + *arg += len; + return &t_string; + } + break; + case 'v': + if (len == 4 && STRNCMP(*arg, "void", len) == 0) + { + *arg += len; + return &t_void; + } + break; + } + + semsg(_("E1010: Type not recognized: %s"), *arg); + return &t_any; + } + + /* + * Check if "type1" and "type2" are exactly the same. + */ + static int + equal_type(type_T *type1, type_T *type2) + { + if (type1->tt_type != type2->tt_type) + return FALSE; + switch (type1->tt_type) + { + case VAR_VOID: + case VAR_UNKNOWN: + case VAR_SPECIAL: + case VAR_BOOL: + case VAR_NUMBER: + case VAR_FLOAT: + case VAR_STRING: + case VAR_BLOB: + case VAR_JOB: + case VAR_CHANNEL: + return TRUE; // not composite is always OK + case VAR_LIST: + case VAR_DICT: + return equal_type(type1->tt_member, type2->tt_member); + case VAR_FUNC: + case VAR_PARTIAL: + // TODO; check argument types. + return equal_type(type1->tt_member, type2->tt_member) + && type1->tt_argcount == type2->tt_argcount; + } + return TRUE; + } + + /* + * Find the common type of "type1" and "type2" and put it in "dest". + * "type2" and "dest" may be the same. + */ + static void + common_type(type_T *type1, type_T *type2, type_T *dest) + { + if (equal_type(type1, type2)) + { + if (dest != type2) + *dest = *type2; + return; + } + + if (type1->tt_type == type2->tt_type) + { + dest->tt_type = type1->tt_type; + if (type1->tt_type == VAR_LIST || type2->tt_type == VAR_DICT) + { + common_type(type1->tt_member, type2->tt_member, dest->tt_member); + return; + } + // TODO: VAR_FUNC and VAR_PARTIAL + } + + dest->tt_type = VAR_UNKNOWN; // "any" + } + + char * + vartype_name(vartype_T type) + { + switch (type) + { + case VAR_VOID: return "void"; + case VAR_UNKNOWN: return "any"; + case VAR_SPECIAL: return "special"; + case VAR_BOOL: return "bool"; + case VAR_NUMBER: return "number"; + case VAR_FLOAT: return "float"; + case VAR_STRING: return "string"; + case VAR_BLOB: return "blob"; + case VAR_JOB: return "job"; + case VAR_CHANNEL: return "channel"; + case VAR_LIST: return "list"; + case VAR_DICT: return "dict"; + case VAR_FUNC: return "function"; + case VAR_PARTIAL: return "partial"; + } + return "???"; + } + + /* + * Return the name of a type. + * The result may be in allocated memory, in which case "tofree" is set. + */ + char * + type_name(type_T *type, char **tofree) + { + char *name = vartype_name(type->tt_type); + + *tofree = NULL; + if (type->tt_type == VAR_LIST || type->tt_type == VAR_DICT) + { + char *member_free; + char *member_name = type_name(type->tt_member, &member_free); + size_t len; + + len = STRLEN(name) + STRLEN(member_name) + 3; + *tofree = alloc(len); + if (*tofree != NULL) + { + vim_snprintf(*tofree, len, "%s<%s>", name, member_name); + vim_free(member_free); + return *tofree; + } + } + // TODO: function and partial argument types + + return name; + } + + /* + * Find "name" in script-local items of script "sid". + * Returns the index in "sn_var_vals" if found. + * If found but not in "sn_var_vals" returns -1. + * If not found returns -2. + */ + int + get_script_item_idx(int sid, char_u *name, int check_writable) + { + hashtab_T *ht; + dictitem_T *di; + scriptitem_T *si = &SCRIPT_ITEM(sid); + int idx; + + // First look the name up in the hashtable. + if (sid <= 0 || sid > script_items.ga_len) + return -1; + ht = &SCRIPT_VARS(sid); + di = find_var_in_ht(ht, 0, name, TRUE); + if (di == NULL) + return -2; + + // Now find the svar_T index in sn_var_vals. + for (idx = 0; idx < si->sn_var_vals.ga_len; ++idx) + { + svar_T *sv = ((svar_T *)si->sn_var_vals.ga_data) + idx; + + if (sv->sv_tv == &di->di_tv) + { + if (check_writable && sv->sv_const) + semsg(_(e_readonlyvar), name); + return idx; + } + } + return -1; + } + + /* + * Find "name" in imported items of the current script/ + */ + imported_T * + find_imported(char_u *name, cctx_T *cctx) + { + scriptitem_T *si = &SCRIPT_ITEM(current_sctx.sc_sid); + int idx; + + if (cctx != NULL) + for (idx = 0; idx < cctx->ctx_imports.ga_len; ++idx) + { + imported_T *import = ((imported_T *)cctx->ctx_imports.ga_data) + + idx; + + if (STRCMP(name, import->imp_name) == 0) + return import; + } + + for (idx = 0; idx < si->sn_imports.ga_len; ++idx) + { + imported_T *import = ((imported_T *)si->sn_imports.ga_data) + idx; + + if (STRCMP(name, import->imp_name) == 0) + return import; + } + return NULL; + } + + /* + * Generate an instruction to load script-local variable "name". + */ + static int + compile_load_scriptvar(cctx_T *cctx, char_u *name) + { + scriptitem_T *si = &SCRIPT_ITEM(current_sctx.sc_sid); + int idx = get_script_item_idx(current_sctx.sc_sid, name, FALSE); + imported_T *import; + + if (idx == -1) + { + // variable exists but is not in sn_var_vals: old style script. + return generate_LOADS(cctx, name, current_sctx.sc_sid); + } + if (idx >= 0) + { + svar_T *sv = ((svar_T *)si->sn_var_vals.ga_data) + idx; + + generate_SCRIPT(cctx, ISN_LOADSCRIPT, + current_sctx.sc_sid, idx, sv->sv_type); + return OK; + } + + import = find_imported(name, cctx); + if (import != NULL) + { + // TODO: check this is a variable, not a function + generate_SCRIPT(cctx, ISN_LOADSCRIPT, + import->imp_sid, + import->imp_var_vals_idx, + import->imp_type); + return OK; + } + + semsg(_("E1050: Item not found: %s"), name); + return FAIL; + } + + /* + * Compile a variable name into a load instruction. + * "end" points to just after the name. + * When "error" is FALSE do not give an error when not found. + */ + static int + compile_load(char_u **arg, char_u *end, cctx_T *cctx, int error) + { + type_T *type; + char_u *name; + int res = FAIL; + + if (*(*arg + 1) == ':') + { + // load namespaced variable + name = vim_strnsave(*arg + 2, end - (*arg + 2)); + if (name == NULL) + return FAIL; + + if (**arg == 'v') + { + // load v:var + int vidx = find_vim_var(name); + + if (vidx < 0) + { + if (error) + semsg(_(e_var_notfound), name); + goto theend; + } + + // TODO: get actual type + res = generate_LOAD(cctx, ISN_LOADV, vidx, NULL, &t_any); + } + else if (**arg == 'g') + { + // Global variables can be defined later, thus we don't check if it + // exists, give error at runtime. + res = generate_LOAD(cctx, ISN_LOADG, 0, name, &t_any); + } + else if (**arg == 's') + { + res = compile_load_scriptvar(cctx, name); + } + else + { + semsg("Namespace not supported yet: %s", **arg); + goto theend; + } + } + else + { + size_t len = end - *arg; + int idx; + int gen_load = FALSE; + + name = vim_strnsave(*arg, end - *arg); + if (name == NULL) + return FAIL; + + idx = lookup_arg(*arg, len, cctx); + if (idx >= 0) + { + if (cctx->ctx_ufunc->uf_arg_types != NULL) + type = cctx->ctx_ufunc->uf_arg_types[idx]; + else + type = &t_any; + + // Arguments are located above the frame pointer. + idx -= cctx->ctx_ufunc->uf_args.ga_len + STACK_FRAME_SIZE; + if (cctx->ctx_ufunc->uf_va_name != NULL) + --idx; + gen_load = TRUE; + } + else if (lookup_vararg(*arg, len, cctx)) + { + // varargs is always the last argument + idx = -STACK_FRAME_SIZE - 1; + type = cctx->ctx_ufunc->uf_va_type; + gen_load = TRUE; + } + else + { + idx = lookup_local(*arg, len, cctx); + if (idx >= 0) + { + type = (((lvar_T *)cctx->ctx_locals.ga_data) + idx)->lv_type; + gen_load = TRUE; + } + else + { + if ((len == 4 && STRNCMP("true", *arg, 4) == 0) + || (len == 5 && STRNCMP("false", *arg, 5) == 0)) + res = generate_PUSHBOOL(cctx, **arg == 't' + ? VVAL_TRUE : VVAL_FALSE); + else + res = compile_load_scriptvar(cctx, name); + } + } + if (gen_load) + res = generate_LOAD(cctx, ISN_LOAD, idx, NULL, type); + } + + *arg = end; + + theend: + if (res == FAIL && error) + semsg(_(e_var_notfound), name); + vim_free(name); + return res; + } + + /* + * Compile the argument expressions. + * "arg" points to just after the "(" and is advanced to after the ")" + */ + static int + compile_arguments(char_u **arg, cctx_T *cctx, int *argcount) + { + char_u *p = *arg; + + while (*p != NUL && *p != ')') + { + if (compile_expr1(&p, cctx) == FAIL) + return FAIL; + ++*argcount; + if (*p == ',') + p = skipwhite(p + 1); + } + if (*p != ')') + { + emsg(_(e_missing_close)); + return FAIL; + } + *arg = p + 1; + return OK; + } + + /* + * Compile a function call: name(arg1, arg2) + * "arg" points to "name", "arg + varlen" to the "(". + * "argcount_init" is 1 for "value->method()" + * Instructions: + * EVAL arg1 + * EVAL arg2 + * BCALL / DCALL / UCALL + */ + static int + compile_call(char_u **arg, size_t varlen, cctx_T *cctx, int argcount_init) + { + char_u *name = *arg; + char_u *p = *arg + varlen + 1; + int argcount = argcount_init; + char_u namebuf[100]; + ufunc_T *ufunc; + + if (varlen >= sizeof(namebuf)) + { + semsg(_("E1011: name too long: %s"), name); + return FAIL; + } + vim_strncpy(namebuf, name, varlen); + + *arg = skipwhite(*arg + varlen + 1); + if (compile_arguments(arg, cctx, &argcount) == FAIL) + return FAIL; + + if (ASCII_ISLOWER(*name)) + { + int idx; + + // builtin function + idx = find_internal_func(namebuf); + if (idx >= 0) + return generate_BCALL(cctx, idx, argcount); + semsg(_(e_unknownfunc), namebuf); + } + + // User defined function or variable must start with upper case. + if (!ASCII_ISUPPER(*name)) + { + semsg(_("E1012: Invalid function name: %s"), namebuf); + return FAIL; + } + + // If we can find the function by name generate the right call. + ufunc = find_func(namebuf, cctx); + if (ufunc != NULL) + return generate_CALL(cctx, ufunc, argcount); + + // If the name is a variable, load it and use PCALL. + p = namebuf; + if (compile_load(&p, namebuf + varlen, cctx, FALSE) == OK) + return generate_PCALL(cctx, argcount, FALSE); + + // The function may be defined only later. Need to figure out at runtime. + return generate_UCALL(cctx, namebuf, argcount); + } + + // like NAMESPACE_CHAR but with 'a' and 'l'. + #define VIM9_NAMESPACE_CHAR (char_u *)"bgstvw" + + /* + * Find the end of a variable or function name. Unlike find_name_end() this + * does not recognize magic braces. + * Return a pointer to just after the name. Equal to "arg" if there is no + * valid name. + */ + char_u * + to_name_end(char_u *arg) + { + char_u *p; + + // Quick check for valid starting character. + if (!eval_isnamec1(*arg)) + return arg; + + for (p = arg + 1; *p != NUL && eval_isnamec(*p); MB_PTR_ADV(p)) + // Include a namespace such as "s:var" and "v:var". But "n:" is not + // and can be used in slice "[n:]". + if (*p == ':' && (p != arg + 1 + || vim_strchr(VIM9_NAMESPACE_CHAR, *arg) == NULL)) + break; + return p; + } + + /* + * Like to_name_end() but also skip over a list or dict constant. + */ + char_u * + to_name_const_end(char_u *arg) + { + char_u *p = to_name_end(arg); + typval_T rettv; + + if (p == arg && *arg == '[') + { + + // Can be "[1, 2, 3]->Func()". + if (get_list_tv(&p, &rettv, FALSE, FALSE) == FAIL) + p = arg; + } + else if (p == arg && *arg == '#' && arg[1] == '{') + { + ++p; + if (eval_dict(&p, &rettv, FALSE, TRUE) == FAIL) + p = arg; + } + else if (p == arg && *arg == '{') + { + int ret = get_lambda_tv(&p, &rettv, FALSE); + + if (ret == NOTDONE) + ret = eval_dict(&p, &rettv, FALSE, FALSE); + if (ret != OK) + p = arg; + } + + return p; + } + + static void + type_mismatch(type_T *expected, type_T *actual) + { + char *tofree1, *tofree2; + + semsg(_("E1013: type mismatch, expected %s but got %s"), + type_name(expected, &tofree1), type_name(actual, &tofree2)); + vim_free(tofree1); + vim_free(tofree2); + } + + /* + * Check if the expected and actual types match. + */ + static int + check_type(type_T *expected, type_T *actual, int give_msg) + { + if (expected->tt_type != VAR_UNKNOWN) + { + if (expected->tt_type != actual->tt_type) + { + if (give_msg) + type_mismatch(expected, actual); + return FAIL; + } + if (expected->tt_type == VAR_DICT || expected->tt_type == VAR_LIST) + { + int ret = check_type(expected->tt_member, actual->tt_member, + FALSE); + if (ret == FAIL && give_msg) + type_mismatch(expected, actual); + return ret; + } + } + return OK; + } + + /* + * Check that + * - "actual" is "expected" type or + * - "actual" is a type that can be "expected" type: add a runtime check; or + * - return FAIL. + */ + static int + need_type(type_T *actual, type_T *expected, int offset, cctx_T *cctx) + { + if (equal_type(actual, expected) || expected->tt_type == VAR_UNKNOWN) + return OK; + if (actual->tt_type != VAR_UNKNOWN) + { + type_mismatch(expected, actual); + return FAIL; + } + generate_TYPECHECK(cctx, expected, offset); + return OK; + } + + /* + * parse a list: [expr, expr] + * "*arg" points to the '['. + */ + static int + compile_list(char_u **arg, cctx_T *cctx) + { + char_u *p = skipwhite(*arg + 1); + int count = 0; + + while (*p != ']') + { + if (*p == NUL) + return FAIL; + if (compile_expr1(&p, cctx) == FAIL) + break; + ++count; + if (*p == ',') + ++p; + p = skipwhite(p); + } + *arg = p + 1; + + generate_NEWLIST(cctx, count); + return OK; + } + + /* + * parse a lambda: {arg, arg -> expr} + * "*arg" points to the '{'. + */ + static int + compile_lambda(char_u **arg, cctx_T *cctx) + { + garray_T *instr = &cctx->ctx_instr; + typval_T rettv; + ufunc_T *ufunc; + + // Get the funcref in "rettv". + if (get_lambda_tv(arg, &rettv, TRUE) == FAIL) + return FAIL; + ufunc = rettv.vval.v_partial->pt_func; + + // The function will have one line: "return {expr}". + // Compile it into instructions. + compile_def_function(ufunc, TRUE); + + if (ufunc->uf_dfunc_idx >= 0) + { + if (ga_grow(instr, 1) == FAIL) + return FAIL; + generate_FUNCREF(cctx, ufunc->uf_dfunc_idx); + return OK; + } + return FAIL; + } + + /* + * Compile a lamda call: expr->{lambda}(args) + * "arg" points to the "{". + */ + static int + compile_lambda_call(char_u **arg, cctx_T *cctx) + { + ufunc_T *ufunc; + typval_T rettv; + int argcount = 1; + int ret = FAIL; + + // Get the funcref in "rettv". + if (get_lambda_tv(arg, &rettv, TRUE) == FAIL) + return FAIL; + + if (**arg != '(') + { + if (*skipwhite(*arg) == '(') + semsg(_(e_nowhitespace)); + else + semsg(_(e_missing_paren), "lambda"); + clear_tv(&rettv); + return FAIL; + } + + // The function will have one line: "return {expr}". + // Compile it into instructions. + ufunc = rettv.vval.v_partial->pt_func; + ++ufunc->uf_refcount; + compile_def_function(ufunc, TRUE); + + // compile the arguments + *arg = skipwhite(*arg + 1); + if (compile_arguments(arg, cctx, &argcount) == OK) + // call the compiled function + ret = generate_CALL(cctx, ufunc, argcount); + + clear_tv(&rettv); + return ret; + } + + /* + * parse a dict: {'key': val} or #{key: val} + * "*arg" points to the '{'. + */ + static int + compile_dict(char_u **arg, cctx_T *cctx, int literal) + { + garray_T *instr = &cctx->ctx_instr; + int count = 0; + dict_T *d = dict_alloc(); + dictitem_T *item; + + if (d == NULL) + return FAIL; + *arg = skipwhite(*arg + 1); + while (**arg != '}' && **arg != NUL) + { + char_u *key = NULL; + + if (literal) + { + char_u *p = to_name_end(*arg); + + if (p == *arg) + { + semsg(_("E1014: Invalid key: %s"), *arg); + return FAIL; + } + key = vim_strnsave(*arg, p - *arg); + if (generate_PUSHS(cctx, key) == FAIL) + return FAIL; + *arg = p; + } + else + { + isn_T *isn; + + if (compile_expr1(arg, cctx) == FAIL) + return FAIL; + // TODO: check type is string + isn = ((isn_T *)instr->ga_data) + instr->ga_len - 1; + if (isn->isn_type == ISN_PUSHS) + key = isn->isn_arg.string; + } + + // Check for duplicate keys, if using string keys. + if (key != NULL) + { + item = dict_find(d, key, -1); + if (item != NULL) + { + semsg(_(e_duplicate_key), key); + goto failret; + } + item = dictitem_alloc(key); + if (item != NULL) + { + item->di_tv.v_type = VAR_UNKNOWN; + item->di_tv.v_lock = 0; + if (dict_add(d, item) == FAIL) + dictitem_free(item); + } + } + + *arg = skipwhite(*arg); + if (**arg != ':') + { + semsg(_(e_missing_dict_colon), *arg); + return FAIL; + } + + *arg = skipwhite(*arg + 1); + if (compile_expr1(arg, cctx) == FAIL) + return FAIL; + ++count; + + if (**arg == '}') + break; + if (**arg != ',') + { + semsg(_(e_missing_dict_comma), *arg); + goto failret; + } + *arg = skipwhite(*arg + 1); + } + + if (**arg != '}') + { + semsg(_(e_missing_dict_end), *arg); + goto failret; + } + *arg = *arg + 1; + + dict_unref(d); + return generate_NEWDICT(cctx, count); + + failret: + dict_unref(d); + return FAIL; + } + + /* + * Compile "&option". + */ + static int + compile_get_option(char_u **arg, cctx_T *cctx) + { + typval_T rettv; + char_u *start = *arg; + int ret; + + // parse the option and get the current value to get the type. + rettv.v_type = VAR_UNKNOWN; + ret = get_option_tv(arg, &rettv, TRUE); + if (ret == OK) + { + // include the '&' in the name, get_option_tv() expects it. + char_u *name = vim_strnsave(start, *arg - start); + type_T *type = rettv.v_type == VAR_NUMBER ? &t_number : &t_string; + + ret = generate_LOAD(cctx, ISN_LOADOPT, 0, name, type); + vim_free(name); + } + clear_tv(&rettv); + + return ret; + } + + /* + * Compile "$VAR". + */ + static int + compile_get_env(char_u **arg, cctx_T *cctx) + { + char_u *start = *arg; + int len; + int ret; + char_u *name; + + start = *arg; + ++*arg; + len = get_env_len(arg); + if (len == 0) + { + semsg(_(e_syntax_at), start - 1); + return FAIL; + } + + // include the '$' in the name, get_env_tv() expects it. + name = vim_strnsave(start, len + 1); + ret = generate_LOAD(cctx, ISN_LOADENV, 0, name, &t_string); + vim_free(name); + return ret; + } + + /* + * Compile "@r". + */ + static int + compile_get_register(char_u **arg, cctx_T *cctx) + { + int ret; + + ++*arg; + if (**arg == NUL) + { + semsg(_(e_syntax_at), *arg - 1); + return FAIL; + } + if (!valid_yank_reg(**arg, TRUE)) + { + emsg_invreg(**arg); + return FAIL; + } + ret = generate_LOAD(cctx, ISN_LOADREG, **arg, NULL, &t_string); + ++*arg; + return ret; + } + + /* + * Apply leading '!', '-' and '+' to constant "rettv". + */ + static int + apply_leader(typval_T *rettv, char_u *start, char_u *end) + { + char_u *p = end; + + // this works from end to start + while (p > start) + { + --p; + if (*p == '-' || *p == '+') + { + // only '-' has an effect, for '+' we only check the type + #ifdef FEAT_FLOAT + if (rettv->v_type == VAR_FLOAT) + { + if (*p == '-') + rettv->vval.v_float = -rettv->vval.v_float; + } + else + #endif + { + varnumber_T val; + int error = FALSE; + + // tv_get_number_chk() accepts a string, but we don't want that + // here + if (check_not_string(rettv) == FAIL) + return FAIL; + val = tv_get_number_chk(rettv, &error); + clear_tv(rettv); + if (error) + return FAIL; + if (*p == '-') + val = -val; + rettv->v_type = VAR_NUMBER; + rettv->vval.v_number = val; + } + } + else + { + int v = tv2bool(rettv); + + // '!' is permissive in the type. + clear_tv(rettv); + rettv->v_type = VAR_BOOL; + rettv->vval.v_number = v ? VVAL_FALSE : VVAL_TRUE; + } + } + return OK; + } + + /* + * Recognize v: variables that are constants and set "rettv". + */ + static void + get_vim_constant(char_u **arg, typval_T *rettv) + { + if (STRNCMP(*arg, "v:true", 6) == 0) + { + rettv->v_type = VAR_BOOL; + rettv->vval.v_number = VVAL_TRUE; + *arg += 6; + } + else if (STRNCMP(*arg, "v:false", 7) == 0) + { + rettv->v_type = VAR_BOOL; + rettv->vval.v_number = VVAL_FALSE; + *arg += 7; + } + else if (STRNCMP(*arg, "v:null", 6) == 0) + { + rettv->v_type = VAR_SPECIAL; + rettv->vval.v_number = VVAL_NULL; + *arg += 6; + } + else if (STRNCMP(*arg, "v:none", 6) == 0) + { + rettv->v_type = VAR_SPECIAL; + rettv->vval.v_number = VVAL_NONE; + *arg += 6; + } + } + + /* + * Compile code to apply '-', '+' and '!'. + */ + static int + compile_leader(cctx_T *cctx, char_u *start, char_u *end) + { + char_u *p = end; + + // this works from end to start + while (p > start) + { + --p; + if (*p == '-' || *p == '+') + { + int negate = *p == '-'; + isn_T *isn; + + // TODO: check type + while (p > start && (p[-1] == '-' || p[-1] == '+')) + { + --p; + if (*p == '-') + negate = !negate; + } + // only '-' has an effect, for '+' we only check the type + if (negate) + isn = generate_instr(cctx, ISN_NEGATENR); + else + isn = generate_instr(cctx, ISN_CHECKNR); + if (isn == NULL) + return FAIL; + } + else + { + int invert = TRUE; + + while (p > start && p[-1] == '!') + { + --p; + invert = !invert; + } + if (generate_2BOOL(cctx, invert) == FAIL) + return FAIL; + } + } + return OK; + } + + /* + * Compile whatever comes after "name" or "name()". + */ + static int + compile_subscript( + char_u **arg, + cctx_T *cctx, + char_u **start_leader, + char_u *end_leader) + { + for (;;) + { + if (**arg == '(') + { + int argcount = 0; + + // funcref(arg) + *arg = skipwhite(*arg + 1); + if (compile_arguments(arg, cctx, &argcount) == FAIL) + return FAIL; + if (generate_PCALL(cctx, argcount, TRUE) == FAIL) + return FAIL; + } + else if (**arg == '-' && (*arg)[1] == '>') + { + char_u *p; + + // something->method() + // Apply the '!', '-' and '+' first: + // -1.0->func() works like (-1.0)->func() + if (compile_leader(cctx, *start_leader, end_leader) == FAIL) + return FAIL; + *start_leader = end_leader; // don't apply again later + + *arg = skipwhite(*arg + 2); + if (**arg == '{') + { + // lambda call: list->{lambda} + if (compile_lambda_call(arg, cctx) == FAIL) + return FAIL; + } + else + { + // method call: list->method() + for (p = *arg; eval_isnamec1(*p); ++p) + ; + if (*p != '(') + { + semsg(_(e_missing_paren), arg); + return FAIL; + } + // TODO: base value may not be the first argument + if (compile_call(arg, p - *arg, cctx, 1) == FAIL) + return FAIL; + } + } + else if (**arg == '[') + { + // list index: list[123] + // TODO: more arguments + // TODO: dict member dict['name'] + *arg = skipwhite(*arg + 1); + if (compile_expr1(arg, cctx) == FAIL) + return FAIL; + + if (**arg != ']') + { + emsg(_(e_missbrac)); + return FAIL; + } + *arg = skipwhite(*arg + 1); + + if (generate_instr_drop(cctx, ISN_INDEX, 1) == FAIL) + return FAIL; + } + else if (**arg == '.' && (*arg)[1] != '.') + { + char_u *p; + + ++*arg; + p = *arg; + // dictionary member: dict.name + if (eval_isnamec1(*p)) + while (eval_isnamec(*p)) + MB_PTR_ADV(p); + if (p == *arg) + { + semsg(_(e_syntax_at), *arg); + return FAIL; + } + // TODO: check type is dict + if (generate_MEMBER(cctx, *arg, p - *arg) == FAIL) + return FAIL; + *arg = p; + } + else + break; + } + + // TODO - see handle_subscript(): + // Turn "dict.Func" into a partial for "Func" bound to "dict". + // Don't do this when "Func" is already a partial that was bound + // explicitly (pt_auto is FALSE). + + return OK; + } + + /* + * Compile an expression at "*p" and add instructions to "instr". + * "p" is advanced until after the expression, skipping white space. + * + * This is the equivalent of eval1(), eval2(), etc. + */ + + /* + * number number constant + * 0zFFFFFFFF Blob constant + * "string" string constant + * 'string' literal string constant + * &option-name option value + * @r register contents + * identifier variable value + * function() function call + * $VAR environment variable + * (expression) nested expression + * [expr, expr] List + * {key: val, key: val} Dictionary + * #{key: val, key: val} Dictionary with literal keys + * + * Also handle: + * ! in front logical NOT + * - in front unary minus + * + in front unary plus (ignored) + * trailing (arg) funcref/partial call + * trailing [] subscript in String or List + * trailing .name entry in Dictionary + * trailing ->name() method call + */ + static int + compile_expr7(char_u **arg, cctx_T *cctx) + { + typval_T rettv; + char_u *start_leader, *end_leader; + int ret = OK; + + /* + * Skip '!', '-' and '+' characters. They are handled later. + */ + start_leader = *arg; + while (**arg == '!' || **arg == '-' || **arg == '+') + *arg = skipwhite(*arg + 1); + end_leader = *arg; + + rettv.v_type = VAR_UNKNOWN; + switch (**arg) + { + /* + * Number constant. + */ + case '0': // also for blob starting with 0z + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '.': if (get_number_tv(arg, &rettv, TRUE, FALSE) == FAIL) + return FAIL; + break; + + /* + * String constant: "string". + */ + case '"': if (get_string_tv(arg, &rettv, TRUE) == FAIL) + return FAIL; + break; + + /* + * Literal string constant: 'str''ing'. + */ + case '\'': if (get_lit_string_tv(arg, &rettv, TRUE) == FAIL) + return FAIL; + break; + + /* + * Constant Vim variable. + */ + case 'v': get_vim_constant(arg, &rettv); + ret = NOTDONE; + break; + + /* + * List: [expr, expr] + */ + case '[': ret = compile_list(arg, cctx); + break; + + /* + * Dictionary: #{key: val, key: val} + */ + case '#': if ((*arg)[1] == '{') + { + ++*arg; + ret = compile_dict(arg, cctx, TRUE); + } + else + ret = NOTDONE; + break; + + /* + * Lambda: {arg, arg -> expr} + * Dictionary: {'key': val, 'key': val} + */ + case '{': { + char_u *start = skipwhite(*arg + 1); + + // Find out what comes after the arguments. + ret = get_function_args(&start, '-', NULL, + NULL, NULL, NULL, TRUE); + if (ret != FAIL && *start == '>') + ret = compile_lambda(arg, cctx); + else + ret = compile_dict(arg, cctx, FALSE); + } + break; + + /* + * Option value: &name + */ + case '&': ret = compile_get_option(arg, cctx); + break; + + /* + * Environment variable: $VAR. + */ + case '$': ret = compile_get_env(arg, cctx); + break; + + /* + * Register contents: @r. + */ + case '@': ret = compile_get_register(arg, cctx); + break; + /* + * nested expression: (expression). + */ + case '(': *arg = skipwhite(*arg + 1); + ret = compile_expr1(arg, cctx); // recursive! + *arg = skipwhite(*arg); + if (**arg == ')') + ++*arg; + else if (ret == OK) + { + emsg(_(e_missing_close)); + ret = FAIL; + } + break; + + default: ret = NOTDONE; + break; + } + if (ret == FAIL) + return FAIL; + + if (rettv.v_type != VAR_UNKNOWN) + { + // apply the '!', '-' and '+' before the constant + if (apply_leader(&rettv, start_leader, end_leader) == FAIL) + { + clear_tv(&rettv); + return FAIL; + } + start_leader = end_leader; // don't apply again below + + // push constant + switch (rettv.v_type) + { + case VAR_BOOL: + generate_PUSHBOOL(cctx, rettv.vval.v_number); + break; + case VAR_SPECIAL: + generate_PUSHSPEC(cctx, rettv.vval.v_number); + break; + case VAR_NUMBER: + generate_PUSHNR(cctx, rettv.vval.v_number); + break; + #ifdef FEAT_FLOAT + case VAR_FLOAT: + generate_PUSHF(cctx, rettv.vval.v_float); + break; + #endif + case VAR_BLOB: + generate_PUSHBLOB(cctx, rettv.vval.v_blob); + rettv.vval.v_blob = NULL; + break; + case VAR_STRING: + generate_PUSHS(cctx, rettv.vval.v_string); + rettv.vval.v_string = NULL; + break; + default: + iemsg("constant type missing"); + return FAIL; + } + } + else if (ret == NOTDONE) + { + char_u *p; + int r; + + if (!eval_isnamec1(**arg)) + { + semsg(_("E1015: Name expected: %s"), *arg); + return FAIL; + } + + // "name" or "name()" + p = to_name_end(*arg); + if (*p == '(') + r = compile_call(arg, p - *arg, cctx, 0); + else + r = compile_load(arg, p, cctx, TRUE); + if (r == FAIL) + return FAIL; + } + + if (compile_subscript(arg, cctx, &start_leader, end_leader) == FAIL) + return FAIL; + + // Now deal with prefixed '-', '+' and '!', if not done already. + return compile_leader(cctx, start_leader, end_leader); + } + + /* + * * number multiplication + * / number division + * % number modulo + */ + static int + compile_expr6(char_u **arg, cctx_T *cctx) + { + char_u *op; + + // get the first variable + if (compile_expr7(arg, cctx) == FAIL) + return FAIL; + + /* + * Repeat computing, until no "*", "/" or "%" is following. + */ + for (;;) + { + op = skipwhite(*arg); + if (*op != '*' && *op != '/' && *op != '%') + break; + if (!VIM_ISWHITE(**arg) || !VIM_ISWHITE(op[1])) + { + char_u buf[3]; + + vim_strncpy(buf, op, 1); + semsg(_(e_white_both), buf); + } + *arg = skipwhite(op + 1); + + // get the second variable + if (compile_expr7(arg, cctx) == FAIL) + return FAIL; + + generate_two_op(cctx, op); + } + + return OK; + } + + /* + * + number addition + * - number subtraction + * .. string concatenation + */ + static int + compile_expr5(char_u **arg, cctx_T *cctx) + { + char_u *op; + int oplen; + + // get the first variable + if (compile_expr6(arg, cctx) == FAIL) + return FAIL; + + /* + * Repeat computing, until no "+", "-" or ".." is following. + */ + for (;;) + { + op = skipwhite(*arg); + if (*op != '+' && *op != '-' && !(*op == '.' && (*(*arg + 1) == '.'))) + break; + oplen = (*op == '.' ? 2 : 1); + + if (!VIM_ISWHITE(**arg) || !VIM_ISWHITE(op[oplen])) + { + char_u buf[3]; + + vim_strncpy(buf, op, oplen); + semsg(_(e_white_both), buf); + } + + *arg = skipwhite(op + oplen); + + // get the second variable + if (compile_expr6(arg, cctx) == FAIL) + return FAIL; + + if (*op == '.') + { + if (may_generate_2STRING(-2, cctx) == FAIL + || may_generate_2STRING(-1, cctx) == FAIL) + return FAIL; + generate_instr_drop(cctx, ISN_CONCAT, 1); + } + else + generate_two_op(cctx, op); + } + + return OK; + } + + /* + * expr5a == expr5b + * expr5a =~ expr5b + * expr5a != expr5b + * expr5a !~ expr5b + * expr5a > expr5b + * expr5a >= expr5b + * expr5a < expr5b + * expr5a <= expr5b + * expr5a is expr5b + * expr5a isnot expr5b + * + * Produces instructions: + * EVAL expr5a Push result of "expr5a" + * EVAL expr5b Push result of "expr5b" + * COMPARE one of the compare instructions + */ + static int + compile_expr4(char_u **arg, cctx_T *cctx) + { + exptype_T type = EXPR_UNKNOWN; + char_u *p; + int len = 2; + int i; + int type_is = FALSE; + + // get the first variable + if (compile_expr5(arg, cctx) == FAIL) + return FAIL; + + p = skipwhite(*arg); + switch (p[0]) + { + case '=': if (p[1] == '=') + type = EXPR_EQUAL; + else if (p[1] == '~') + type = EXPR_MATCH; + break; + case '!': if (p[1] == '=') + type = EXPR_NEQUAL; + else if (p[1] == '~') + type = EXPR_NOMATCH; + break; + case '>': if (p[1] != '=') + { + type = EXPR_GREATER; + len = 1; + } + else + type = EXPR_GEQUAL; + break; + case '<': if (p[1] != '=') + { + type = EXPR_SMALLER; + len = 1; + } + else + type = EXPR_SEQUAL; + break; + case 'i': if (p[1] == 's') + { + // "is" and "isnot"; but not a prefix of a name + if (p[2] == 'n' && p[3] == 'o' && p[4] == 't') + len = 5; + i = p[len]; + if (!isalnum(i) && i != '_') + { + type = len == 2 ? EXPR_IS : EXPR_ISNOT; + type_is = TRUE; + } + } + break; + } + + /* + * If there is a comparative operator, use it. + */ + if (type != EXPR_UNKNOWN) + { + int ic = FALSE; // Default: do not ignore case + + if (type_is && (p[len] == '?' || p[len] == '#')) + { + semsg(_(e_invexpr2), *arg); + return FAIL; + } + // extra question mark appended: ignore case + if (p[len] == '?') + { + ic = TRUE; + ++len; + } + // extra '#' appended: match case (ignored) + else if (p[len] == '#') + ++len; + // nothing appended: match case + + if (!VIM_ISWHITE(**arg) || !VIM_ISWHITE(p[len])) + { + char_u buf[7]; + + vim_strncpy(buf, p, len); + semsg(_(e_white_both), buf); + } + + // get the second variable + *arg = skipwhite(p + len); + if (compile_expr5(arg, cctx) == FAIL) + return FAIL; + + generate_COMPARE(cctx, type, ic); + } + + return OK; + } + + /* + * Compile || or &&. + */ + static int + compile_and_or(char_u **arg, cctx_T *cctx, char *op) + { + char_u *p = skipwhite(*arg); + int opchar = *op; + + if (p[0] == opchar && p[1] == opchar) + { + garray_T *instr = &cctx->ctx_instr; + garray_T end_ga; + + /* + * Repeat until there is no following "||" or "&&" + */ + ga_init2(&end_ga, sizeof(int), 10); + while (p[0] == opchar && p[1] == opchar) + { + if (!VIM_ISWHITE(**arg) || !VIM_ISWHITE(p[2])) + semsg(_(e_white_both), op); + + if (ga_grow(&end_ga, 1) == FAIL) + { + ga_clear(&end_ga); + return FAIL; + } + *(((int *)end_ga.ga_data) + end_ga.ga_len) = instr->ga_len; + ++end_ga.ga_len; + generate_JUMP(cctx, opchar == '|' + ? JUMP_AND_KEEP_IF_TRUE : JUMP_AND_KEEP_IF_FALSE, 0); + + // eval the next expression + *arg = skipwhite(p + 2); + if ((opchar == '|' ? compile_expr3(arg, cctx) + : compile_expr4(arg, cctx)) == FAIL) + { + ga_clear(&end_ga); + return FAIL; + } + p = skipwhite(*arg); + } + + // Fill in the end label in all jumps. + while (end_ga.ga_len > 0) + { + isn_T *isn; + + --end_ga.ga_len; + isn = ((isn_T *)instr->ga_data) + + *(((int *)end_ga.ga_data) + end_ga.ga_len); + isn->isn_arg.jump.jump_where = instr->ga_len; + } + ga_clear(&end_ga); + } + + return OK; + } + + /* + * expr4a && expr4a && expr4a logical AND + * + * Produces instructions: + * EVAL expr4a Push result of "expr4a" + * JUMP_AND_KEEP_IF_FALSE end + * EVAL expr4b Push result of "expr4b" + * JUMP_AND_KEEP_IF_FALSE end + * EVAL expr4c Push result of "expr4c" + * end: + */ + static int + compile_expr3(char_u **arg, cctx_T *cctx) + { + // get the first variable + if (compile_expr4(arg, cctx) == FAIL) + return FAIL; + + // || and && work almost the same + return compile_and_or(arg, cctx, "&&"); + } + + /* + * expr3a || expr3b || expr3c logical OR + * + * Produces instructions: + * EVAL expr3a Push result of "expr3a" + * JUMP_AND_KEEP_IF_TRUE end + * EVAL expr3b Push result of "expr3b" + * JUMP_AND_KEEP_IF_TRUE end + * EVAL expr3c Push result of "expr3c" + * end: + */ + static int + compile_expr2(char_u **arg, cctx_T *cctx) + { + // eval the first expression + if (compile_expr3(arg, cctx) == FAIL) + return FAIL; + + // || and && work almost the same + return compile_and_or(arg, cctx, "||"); + } + + /* + * Toplevel expression: expr2 ? expr1a : expr1b + * + * Produces instructions: + * EVAL expr2 Push result of "expr" + * JUMP_IF_FALSE alt jump if false + * EVAL expr1a + * JUMP_ALWAYS end + * alt: EVAL expr1b + * end: + */ + static int + compile_expr1(char_u **arg, cctx_T *cctx) + { + char_u *p; + + // evaluate the first expression + if (compile_expr2(arg, cctx) == FAIL) + return FAIL; + + p = skipwhite(*arg); + if (*p == '?') + { + garray_T *instr = &cctx->ctx_instr; + garray_T *stack = &cctx->ctx_type_stack; + int alt_idx = instr->ga_len; + int end_idx; + isn_T *isn; + type_T *type1; + type_T *type2; + + if (!VIM_ISWHITE(**arg) || !VIM_ISWHITE(p[1])) + semsg(_(e_white_both), "?"); + + generate_JUMP(cctx, JUMP_IF_FALSE, 0); + + // evaluate the second expression; any type is accepted + *arg = skipwhite(p + 1); + compile_expr1(arg, cctx); + + // remember the type and drop it + --stack->ga_len; + type1 = ((type_T **)stack->ga_data)[stack->ga_len]; + + end_idx = instr->ga_len; + generate_JUMP(cctx, JUMP_ALWAYS, 0); + + // jump here from JUMP_IF_FALSE + isn = ((isn_T *)instr->ga_data) + alt_idx; + isn->isn_arg.jump.jump_where = instr->ga_len; + + // Check for the ":". + p = skipwhite(*arg); + if (*p != ':') + { + emsg(_(e_missing_colon)); + return FAIL; + } + if (!VIM_ISWHITE(**arg) || !VIM_ISWHITE(p[1])) + semsg(_(e_white_both), ":"); + + // evaluate the third expression + *arg = skipwhite(p + 1); + compile_expr1(arg, cctx); + + // If the types differ, the result has a more generic type. + type2 = ((type_T **)stack->ga_data)[stack->ga_len - 1]; + common_type(type1, type2, type2); + + // jump here from JUMP_ALWAYS + isn = ((isn_T *)instr->ga_data) + end_idx; + isn->isn_arg.jump.jump_where = instr->ga_len; + } + return OK; + } + + /* + * compile "return [expr]" + */ + static char_u * + compile_return(char_u *arg, int set_return_type, cctx_T *cctx) + { + char_u *p = arg; + garray_T *stack = &cctx->ctx_type_stack; + type_T *stack_type; + + if (*p != NUL && *p != '|' && *p != '\n') + { + // compile return argument into instructions + if (compile_expr1(&p, cctx) == FAIL) + return NULL; + + stack_type = ((type_T **)stack->ga_data)[stack->ga_len - 1]; + if (set_return_type) + cctx->ctx_ufunc->uf_ret_type = stack_type; + else if (need_type(stack_type, cctx->ctx_ufunc->uf_ret_type, -1, cctx) + == FAIL) + return NULL; + } + else + { + if (set_return_type) + cctx->ctx_ufunc->uf_ret_type = &t_void; + else if (cctx->ctx_ufunc->uf_ret_type->tt_type != VAR_VOID) + { + emsg(_("E1003: Missing return value")); + return NULL; + } + + // No argument, return zero. + generate_PUSHNR(cctx, 0); + } + + if (generate_instr(cctx, ISN_RETURN) == NULL) + return NULL; + + // "return val | endif" is possible + return skipwhite(p); + } + + /* + * Return the length of an assignment operator, or zero if there isn't one. + */ + int + assignment_len(char_u *p, int *heredoc) + { + if (*p == '=') + { + if (p[1] == '<' && p[2] == '<') + { + *heredoc = TRUE; + return 3; + } + return 1; + } + if (vim_strchr((char_u *)"+-*/%", *p) != NULL && p[1] == '=') + return 2; + if (STRNCMP(p, "..=", 3) == 0) + return 3; + return 0; + } + + // words that cannot be used as a variable + static char *reserved[] = { + "true", + "false", + NULL + }; + + /* + * Get a line for "=<<". + * Return a pointer to the line in allocated memory. + * Return NULL for end-of-file or some error. + */ + static char_u * + heredoc_getline( + int c UNUSED, + void *cookie, + int indent UNUSED, + int do_concat UNUSED) + { + cctx_T *cctx = (cctx_T *)cookie; + + if (cctx->ctx_lnum == cctx->ctx_ufunc->uf_lines.ga_len) + NULL; + ++cctx->ctx_lnum; + return vim_strsave(((char_u **)cctx->ctx_ufunc->uf_lines.ga_data) + [cctx->ctx_lnum]); + } + + /* + * compile "let var [= expr]", "const var = expr" and "var = expr" + * "arg" points to "var". + */ + static char_u * + compile_assignment(char_u *arg, exarg_T *eap, cmdidx_T cmdidx, cctx_T *cctx) + { + char_u *p; + char_u *ret = NULL; + int var_count = 0; + int semicolon = 0; + size_t varlen; + garray_T *instr = &cctx->ctx_instr; + int idx = -1; + char_u *op; + int option = FALSE; + int opt_type; + int opt_flags = 0; + int global = FALSE; + int script = FALSE; + int oplen = 0; + int heredoc = FALSE; + type_T *type; + lvar_T *lvar; + char_u *name; + char_u *sp; + int has_type = FALSE; + int is_decl = cmdidx == CMD_let || cmdidx == CMD_const; + int instr_count = -1; + + p = skip_var_list(arg, FALSE, &var_count, &semicolon); + if (p == NULL) + return NULL; + if (var_count > 0) + { + // TODO: let [var, var] = list + emsg("Cannot handle a list yet"); + return NULL; + } + + varlen = p - arg; + name = vim_strnsave(arg, (int)varlen); + if (name == NULL) + return NULL; + + if (*arg == '&') + { + int cc; + long numval; + char_u *stringval = NULL; + + option = TRUE; + if (cmdidx == CMD_const) + { + emsg(_(e_const_option)); + return NULL; + } + if (is_decl) + { + semsg(_("E1052: Cannot declare an option: %s"), arg); + goto theend; + } + p = arg; + p = find_option_end(&p, &opt_flags); + if (p == NULL) + { + emsg(_(e_letunexp)); + return NULL; + } + cc = *p; + *p = NUL; + opt_type = get_option_value(arg + 1, &numval, &stringval, opt_flags); + *p = cc; + if (opt_type == -3) + { + semsg(_(e_unknown_option), *arg); + return NULL; + } + if (opt_type == -2 || opt_type == 0) + type = &t_string; + else + type = &t_number; // both number and boolean option + } + else if (STRNCMP(arg, "g:", 2) == 0) + { + global = TRUE; + if (is_decl) + { + semsg(_("E1016: Cannot declare a global variable: %s"), name); + goto theend; + } + } + else + { + for (idx = 0; reserved[idx] != NULL; ++idx) + if (STRCMP(reserved[idx], name) == 0) + { + semsg(_("E1034: Cannot use reserved name %s"), name); + goto theend; + } + + idx = lookup_local(arg, varlen, cctx); + if (idx >= 0) + { + if (is_decl) + { + semsg(_("E1017: Variable already declared: %s"), name); + goto theend; + } + else + { + lvar = ((lvar_T *)cctx->ctx_locals.ga_data) + idx; + if (lvar->lv_const) + { + semsg(_("E1018: Cannot assign to a constant: %s"), name); + goto theend; + } + } + } + else if (lookup_script(arg, varlen) == OK) + { + script = TRUE; + if (is_decl) + { + semsg(_("E1054: Variable already declared in the script: %s"), + name); + goto theend; + } + } + } + + if (!option) + { + if (is_decl && *p == ':') + { + // parse optional type: "let var: type = expr" + p = skipwhite(p + 1); + type = parse_type(&p, cctx->ctx_type_list); + if (type == NULL) + goto theend; + has_type = TRUE; + } + else if (idx < 0) + { + // global and new local default to "any" type + type = &t_any; + } + else + { + lvar = ((lvar_T *)cctx->ctx_locals.ga_data) + idx; + type = lvar->lv_type; + } + } + + sp = p; + p = skipwhite(p); + op = p; + oplen = assignment_len(p, &heredoc); + if (oplen > 0 && (!VIM_ISWHITE(*sp) || !VIM_ISWHITE(op[oplen]))) + { + char_u buf[4]; + + vim_strncpy(buf, op, oplen); + semsg(_(e_white_both), buf); + } + + if (oplen == 3 && !heredoc && !global && type->tt_type != VAR_STRING + && type->tt_type != VAR_UNKNOWN) + { + emsg("E1019: Can only concatenate to string"); + goto theend; + } + + // +=, /=, etc. require an existing variable + if (idx < 0 && !global && !option) + { + if (oplen > 1 && !heredoc) + { + semsg(_("E1020: cannot use an operator on a new variable: %s"), + name); + goto theend; + } + + // new local variable + idx = reserve_local(cctx, arg, varlen, cmdidx == CMD_const, type); + if (idx < 0) + goto theend; + } + + if (heredoc) + { + list_T *l; + listitem_T *li; + + // [let] varname =<< [trim] {end} + eap->getline = heredoc_getline; + eap->cookie = cctx; + l = heredoc_get(eap, op + 3); + + // Push each line and the create the list. + for (li = l->lv_first; li != NULL; li = li->li_next) + { + generate_PUSHS(cctx, li->li_tv.vval.v_string); + li->li_tv.vval.v_string = NULL; + } + generate_NEWLIST(cctx, l->lv_len); + type = &t_list_string; + list_free(l); + p += STRLEN(p); + } + else if (oplen > 0) + { + // for "+=", "*=", "..=" etc. first load the current value + if (*op != '=') + { + if (option) + generate_LOAD(cctx, ISN_LOADOPT, 0, name + 1, type); + else if (global) + generate_LOAD(cctx, ISN_LOADG, 0, name + 2, type); + else + generate_LOAD(cctx, ISN_LOAD, idx, NULL, type); + } + + // compile the expression + instr_count = instr->ga_len; + p = skipwhite(p + oplen); + if (compile_expr1(&p, cctx) == FAIL) + goto theend; + + if (idx >= 0 && (is_decl || !has_type)) + { + garray_T *stack = &cctx->ctx_type_stack; + type_T *stacktype = + ((type_T **)stack->ga_data)[stack->ga_len - 1]; + + lvar = ((lvar_T *)cctx->ctx_locals.ga_data) + idx; + if (!has_type) + { + if (stacktype->tt_type == VAR_VOID) + { + emsg(_("E1031: Cannot use void value")); + goto theend; + } + else + lvar->lv_type = stacktype; + } + else + if (check_type(lvar->lv_type, stacktype, TRUE) == FAIL) + goto theend; + } + } + else if (cmdidx == CMD_const) + { + emsg(_("E1021: const requires a value")); + goto theend; + } + else if (!has_type || option) + { + emsg(_("E1022: type or initialization required")); + goto theend; + } + else + { + // variables are always initialized + // TODO: support more types + if (ga_grow(instr, 1) == FAIL) + goto theend; + if (type->tt_type == VAR_STRING) + generate_PUSHS(cctx, vim_strsave((char_u *)"")); + else + generate_PUSHNR(cctx, 0); + } + + if (oplen > 0 && *op != '=') + { + type_T *expected = &t_number; + garray_T *stack = &cctx->ctx_type_stack; + type_T *stacktype; + + // TODO: if type is known use float or any operation + + if (*op == '.') + expected = &t_string; + stacktype = ((type_T **)stack->ga_data)[stack->ga_len - 1]; + if (need_type(stacktype, expected, -1, cctx) == FAIL) + goto theend; + + if (*op == '.') + generate_instr_drop(cctx, ISN_CONCAT, 1); + else + { + isn_T *isn = generate_instr_drop(cctx, ISN_OPNR, 1); + + if (isn == NULL) + goto theend; + switch (*op) + { + case '+': isn->isn_arg.op.op_type = EXPR_ADD; break; + case '-': isn->isn_arg.op.op_type = EXPR_SUB; break; + case '*': isn->isn_arg.op.op_type = EXPR_MULT; break; + case '/': isn->isn_arg.op.op_type = EXPR_DIV; break; + case '%': isn->isn_arg.op.op_type = EXPR_REM; break; + } + } + } + + if (option) + generate_STOREOPT(cctx, name + 1, opt_flags); + else if (global) + generate_STORE(cctx, ISN_STOREG, 0, name + 2); + else if (script) + { + idx = get_script_item_idx(current_sctx.sc_sid, name, TRUE); + // TODO: specific type + generate_SCRIPT(cctx, ISN_STORESCRIPT, + current_sctx.sc_sid, idx, &t_any); + } + else + { + isn_T *isn = ((isn_T *)instr->ga_data) + instr->ga_len - 1; + + // optimization: turn "var = 123" from ISN_PUSHNR + ISN_STORE into + // ISN_STORENR + if (instr->ga_len == instr_count + 1 && isn->isn_type == ISN_PUSHNR) + { + varnumber_T val = isn->isn_arg.number; + garray_T *stack = &cctx->ctx_type_stack; + + isn->isn_type = ISN_STORENR; + isn->isn_arg.storenr.str_idx = idx; + isn->isn_arg.storenr.str_val = val; + if (stack->ga_len > 0) + --stack->ga_len; + } + else + generate_STORE(cctx, ISN_STORE, idx, NULL); + } + ret = p; + + theend: + vim_free(name); + return ret; + } + + /* + * Compile an :import command. + */ + static char_u * + compile_import(char_u *arg, cctx_T *cctx) + { + return handle_import(arg, &cctx->ctx_imports, 0); + } + + /* + * generate a jump to the ":endif"/":endfor"/":endwhile"/":finally"/":endtry". + */ + static int + compile_jump_to_end(endlabel_T **el, jumpwhen_T when, cctx_T *cctx) + { + garray_T *instr = &cctx->ctx_instr; + endlabel_T *endlabel = ALLOC_CLEAR_ONE(endlabel_T); + + if (endlabel == NULL) + return FAIL; + endlabel->el_next = *el; + *el = endlabel; + endlabel->el_end_label = instr->ga_len; + + generate_JUMP(cctx, when, 0); + return OK; + } + + static void + compile_fill_jump_to_end(endlabel_T **el, cctx_T *cctx) + { + garray_T *instr = &cctx->ctx_instr; + + while (*el != NULL) + { + endlabel_T *cur = (*el); + isn_T *isn; + + isn = ((isn_T *)instr->ga_data) + cur->el_end_label; + isn->isn_arg.jump.jump_where = instr->ga_len; + *el = cur->el_next; + vim_free(cur); + } + } + + /* + * Create a new scope and set up the generic items. + */ + static scope_T * + new_scope(cctx_T *cctx, scopetype_T type) + { + scope_T *scope = ALLOC_CLEAR_ONE(scope_T); + + if (scope == NULL) + return NULL; + scope->se_outer = cctx->ctx_scope; + cctx->ctx_scope = scope; + scope->se_type = type; + scope->se_local_count = cctx->ctx_locals.ga_len; + return scope; + } + + /* + * compile "if expr" + * + * "if expr" Produces instructions: + * EVAL expr Push result of "expr" + * JUMP_IF_FALSE end + * ... body ... + * end: + * + * "if expr | else" Produces instructions: + * EVAL expr Push result of "expr" + * JUMP_IF_FALSE else + * ... body ... + * JUMP_ALWAYS end + * else: + * ... body ... + * end: + * + * "if expr1 | elseif expr2 | else" Produces instructions: + * EVAL expr Push result of "expr" + * JUMP_IF_FALSE elseif + * ... body ... + * JUMP_ALWAYS end + * elseif: + * EVAL expr Push result of "expr" + * JUMP_IF_FALSE else + * ... body ... + * JUMP_ALWAYS end + * else: + * ... body ... + * end: + */ + static char_u * + compile_if(char_u *arg, cctx_T *cctx) + { + char_u *p = arg; + garray_T *instr = &cctx->ctx_instr; + scope_T *scope; + + // compile "expr" + if (compile_expr1(&p, cctx) == FAIL) + return NULL; + + scope = new_scope(cctx, IF_SCOPE); + if (scope == NULL) + return NULL; + + // "where" is set when ":elseif", "else" or ":endif" is found + scope->se_if.is_if_label = instr->ga_len; + generate_JUMP(cctx, JUMP_IF_FALSE, 0); + + return p; + } + + static char_u * + compile_elseif(char_u *arg, cctx_T *cctx) + { + char_u *p = arg; + garray_T *instr = &cctx->ctx_instr; + isn_T *isn; + scope_T *scope = cctx->ctx_scope; + + if (scope == NULL || scope->se_type != IF_SCOPE) + { + emsg(_(e_elseif_without_if)); + return NULL; + } + cctx->ctx_locals.ga_len = scope->se_local_count; + + // jump from previous block to the end + if (compile_jump_to_end(&scope->se_if.is_end_label, + JUMP_ALWAYS, cctx) == FAIL) + return NULL; + + // previous "if" or "elseif" jumps here + isn = ((isn_T *)instr->ga_data) + scope->se_if.is_if_label; + isn->isn_arg.jump.jump_where = instr->ga_len; + + // compile "expr" + if (compile_expr1(&p, cctx) == FAIL) + return NULL; + + // "where" is set when ":elseif", "else" or ":endif" is found + scope->se_if.is_if_label = instr->ga_len; + generate_JUMP(cctx, JUMP_IF_FALSE, 0); + + return p; + } + + static char_u * + compile_else(char_u *arg, cctx_T *cctx) + { + char_u *p = arg; + garray_T *instr = &cctx->ctx_instr; + isn_T *isn; + scope_T *scope = cctx->ctx_scope; + + if (scope == NULL || scope->se_type != IF_SCOPE) + { + emsg(_(e_else_without_if)); + return NULL; + } + cctx->ctx_locals.ga_len = scope->se_local_count; + + // jump from previous block to the end + if (compile_jump_to_end(&scope->se_if.is_end_label, + JUMP_ALWAYS, cctx) == FAIL) + return NULL; + + // previous "if" or "elseif" jumps here + isn = ((isn_T *)instr->ga_data) + scope->se_if.is_if_label; + isn->isn_arg.jump.jump_where = instr->ga_len; + + return p; + } + + static char_u * + compile_endif(char_u *arg, cctx_T *cctx) + { + scope_T *scope = cctx->ctx_scope; + ifscope_T *ifscope; + garray_T *instr = &cctx->ctx_instr; + isn_T *isn; + + if (scope == NULL || scope->se_type != IF_SCOPE) + { + emsg(_(e_endif_without_if)); + return NULL; + } + ifscope = &scope->se_if; + cctx->ctx_scope = scope->se_outer; + cctx->ctx_locals.ga_len = scope->se_local_count; + + // previous "if" or "elseif" jumps here + isn = ((isn_T *)instr->ga_data) + scope->se_if.is_if_label; + isn->isn_arg.jump.jump_where = instr->ga_len; + + // Fill in the "end" label in jumps at the end of the blocks. + compile_fill_jump_to_end(&ifscope->is_end_label, cctx); + + vim_free(scope); + return arg; + } + + /* + * compile "for var in expr" + * + * Produces instructions: + * PUSHNR -1 + * STORE loop-idx Set index to -1 + * EVAL expr Push result of "expr" + * top: FOR loop-idx, end Increment index, use list on bottom of stack + * - if beyond end, jump to "end" + * - otherwise get item from list and push it + * STORE var Store item in "var" + * ... body ... + * JUMP top Jump back to repeat + * end: DROP Drop the result of "expr" + * + */ + static char_u * + compile_for(char_u *arg, cctx_T *cctx) + { + char_u *p; + size_t varlen; + garray_T *instr = &cctx->ctx_instr; + garray_T *stack = &cctx->ctx_type_stack; + scope_T *scope; + int loop_idx; // index of loop iteration variable + int var_idx; // index of "var" + type_T *vartype; + + // TODO: list of variables: "for [key, value] in dict" + // parse "var" + for (p = arg; eval_isnamec1(*p); ++p) + ; + varlen = p - arg; + var_idx = lookup_local(arg, varlen, cctx); + if (var_idx >= 0) + { + semsg(_("E1023: variable already defined: %s"), arg); + return NULL; + } + + // consume "in" + p = skipwhite(p); + if (STRNCMP(p, "in", 2) != 0 || !VIM_ISWHITE(p[2])) + { + emsg(_(e_missing_in)); + return NULL; + } + p = skipwhite(p + 2); + + + scope = new_scope(cctx, FOR_SCOPE); + if (scope == NULL) + return NULL; + + // Reserve a variable to store the loop iteration counter. + loop_idx = reserve_local(cctx, (char_u *)"", 0, FALSE, &t_number); + if (loop_idx < 0) + return NULL; + + // Reserve a variable to store "var" + var_idx = reserve_local(cctx, arg, varlen, FALSE, &t_any); + if (var_idx < 0) + return NULL; + + generate_STORENR(cctx, loop_idx, -1); + + // compile "expr", it remains on the stack until "endfor" + arg = p; + if (compile_expr1(&arg, cctx) == FAIL) + return NULL; + + // now we know the type of "var" + vartype = ((type_T **)stack->ga_data)[stack->ga_len - 1]; + if (vartype->tt_type != VAR_LIST) + { + emsg(_("E1024: need a List to iterate over")); + return NULL; + } + if (vartype->tt_member->tt_type != VAR_UNKNOWN) + { + lvar_T *lvar = ((lvar_T *)cctx->ctx_locals.ga_data) + var_idx; + + lvar->lv_type = vartype->tt_member; + } + + // "for_end" is set when ":endfor" is found + scope->se_for.fs_top_label = instr->ga_len; + + generate_FOR(cctx, loop_idx); + generate_STORE(cctx, ISN_STORE, var_idx, NULL); + + return arg; + } + + /* + * compile "endfor" + */ + static char_u * + compile_endfor(char_u *arg, cctx_T *cctx) + { + garray_T *instr = &cctx->ctx_instr; + scope_T *scope = cctx->ctx_scope; + forscope_T *forscope; + isn_T *isn; + + if (scope == NULL || scope->se_type != FOR_SCOPE) + { + emsg(_(e_for)); + return NULL; + } + forscope = &scope->se_for; + cctx->ctx_scope = scope->se_outer; + cctx->ctx_locals.ga_len = scope->se_local_count; + + // At end of ":for" scope jump back to the FOR instruction. + generate_JUMP(cctx, JUMP_ALWAYS, forscope->fs_top_label); + + // Fill in the "end" label in the FOR statement so it can jump here + isn = ((isn_T *)instr->ga_data) + forscope->fs_top_label; + isn->isn_arg.forloop.for_end = instr->ga_len; + + // Fill in the "end" label any BREAK statements + compile_fill_jump_to_end(&forscope->fs_end_label, cctx); + + // Below the ":for" scope drop the "expr" list from the stack. + if (generate_instr_drop(cctx, ISN_DROP, 1) == NULL) + return NULL; + + vim_free(scope); + + return arg; + } + + /* + * compile "while expr" + * + * Produces instructions: + * top: EVAL expr Push result of "expr" + * JUMP_IF_FALSE end jump if false + * ... body ... + * JUMP top Jump back to repeat + * end: + * + */ + static char_u * + compile_while(char_u *arg, cctx_T *cctx) + { + char_u *p = arg; + garray_T *instr = &cctx->ctx_instr; + scope_T *scope; + + scope = new_scope(cctx, WHILE_SCOPE); + if (scope == NULL) + return NULL; + + scope->se_while.ws_top_label = instr->ga_len; + + // compile "expr" + if (compile_expr1(&p, cctx) == FAIL) + return NULL; + + // "while_end" is set when ":endwhile" is found + if (compile_jump_to_end(&scope->se_while.ws_end_label, + JUMP_IF_FALSE, cctx) == FAIL) + return FAIL; + + return p; + } + + /* + * compile "endwhile" + */ + static char_u * + compile_endwhile(char_u *arg, cctx_T *cctx) + { + scope_T *scope = cctx->ctx_scope; + + if (scope == NULL || scope->se_type != WHILE_SCOPE) + { + emsg(_(e_while)); + return NULL; + } + cctx->ctx_scope = scope->se_outer; + cctx->ctx_locals.ga_len = scope->se_local_count; + + // At end of ":for" scope jump back to the FOR instruction. + generate_JUMP(cctx, JUMP_ALWAYS, scope->se_while.ws_top_label); + + // Fill in the "end" label in the WHILE statement so it can jump here. + // And in any jumps for ":break" + compile_fill_jump_to_end(&scope->se_while.ws_end_label, cctx); + + vim_free(scope); + + return arg; + } + + /* + * compile "continue" + */ + static char_u * + compile_continue(char_u *arg, cctx_T *cctx) + { + scope_T *scope = cctx->ctx_scope; + + for (;;) + { + if (scope == NULL) + { + emsg(_(e_continue)); + return NULL; + } + if (scope->se_type == FOR_SCOPE || scope->se_type == WHILE_SCOPE) + break; + scope = scope->se_outer; + } + + // Jump back to the FOR or WHILE instruction. + generate_JUMP(cctx, JUMP_ALWAYS, + scope->se_type == FOR_SCOPE ? scope->se_for.fs_top_label + : scope->se_while.ws_top_label); + return arg; + } + + /* + * compile "break" + */ + static char_u * + compile_break(char_u *arg, cctx_T *cctx) + { + scope_T *scope = cctx->ctx_scope; + endlabel_T **el; + + for (;;) + { + if (scope == NULL) + { + emsg(_(e_break)); + return NULL; + } + if (scope->se_type == FOR_SCOPE || scope->se_type == WHILE_SCOPE) + break; + scope = scope->se_outer; + } + + // Jump to the end of the FOR or WHILE loop. + if (scope->se_type == FOR_SCOPE) + el = &scope->se_for.fs_end_label; + else + el = &scope->se_while.ws_end_label; + if (compile_jump_to_end(el, JUMP_ALWAYS, cctx) == FAIL) + return FAIL; + + return arg; + } + + /* + * compile "{" start of block + */ + static char_u * + compile_block(char_u *arg, cctx_T *cctx) + { + if (new_scope(cctx, BLOCK_SCOPE) == NULL) + return NULL; + return skipwhite(arg + 1); + } + + /* + * compile end of block: drop one scope + */ + static void + compile_endblock(cctx_T *cctx) + { + scope_T *scope = cctx->ctx_scope; + + cctx->ctx_scope = scope->se_outer; + cctx->ctx_locals.ga_len = scope->se_local_count; + vim_free(scope); + } + + /* + * compile "try" + * Creates a new scope for the try-endtry, pointing to the first catch and + * finally. + * Creates another scope for the "try" block itself. + * TRY instruction sets up exception handling at runtime. + * + * "try" + * TRY -> catch1, -> finally push trystack entry + * ... try block + * "throw {exception}" + * EVAL {exception} + * THROW create exception + * ... try block + * " catch {expr}" + * JUMP -> finally + * catch1: PUSH exeception + * EVAL {expr} + * MATCH + * JUMP nomatch -> catch2 + * CATCH remove exception + * ... catch block + * " catch" + * JUMP -> finally + * catch2: CATCH remove exception + * ... catch block + * " finally" + * finally: + * ... finally block + * " endtry" + * ENDTRY pop trystack entry, may rethrow + */ + static char_u * + compile_try(char_u *arg, cctx_T *cctx) + { + garray_T *instr = &cctx->ctx_instr; + scope_T *try_scope; + scope_T *scope; + + // scope that holds the jumps that go to catch/finally/endtry + try_scope = new_scope(cctx, TRY_SCOPE); + if (try_scope == NULL) + return NULL; + + // "catch" is set when the first ":catch" is found. + // "finally" is set when ":finally" or ":endtry" is found + try_scope->se_try.ts_try_label = instr->ga_len; + if (generate_instr(cctx, ISN_TRY) == NULL) + return NULL; + + // scope for the try block itself + scope = new_scope(cctx, BLOCK_SCOPE); + if (scope == NULL) + return NULL; + + return arg; + } + + /* + * compile "catch {expr}" + */ + static char_u * + compile_catch(char_u *arg, cctx_T *cctx UNUSED) + { + scope_T *scope = cctx->ctx_scope; + garray_T *instr = &cctx->ctx_instr; + char_u *p; + isn_T *isn; + + // end block scope from :try or :catch + if (scope != NULL && scope->se_type == BLOCK_SCOPE) + compile_endblock(cctx); + scope = cctx->ctx_scope; + + // Error if not in a :try scope + if (scope == NULL || scope->se_type != TRY_SCOPE) + { + emsg(_(e_catch)); + return NULL; + } + + if (scope->se_try.ts_caught_all) + { + emsg(_("E1033: catch unreachable after catch-all")); + return NULL; + } + + // Jump from end of previous block to :finally or :endtry + if (compile_jump_to_end(&scope->se_try.ts_end_label, + JUMP_ALWAYS, cctx) == FAIL) + return NULL; + + // End :try or :catch scope: set value in ISN_TRY instruction + isn = ((isn_T *)instr->ga_data) + scope->se_try.ts_try_label; + if (isn->isn_arg.try.try_catch == 0) + isn->isn_arg.try.try_catch = instr->ga_len; + if (scope->se_try.ts_catch_label != 0) + { + // Previous catch without match jumps here + isn = ((isn_T *)instr->ga_data) + scope->se_try.ts_catch_label; + isn->isn_arg.jump.jump_where = instr->ga_len; + } + + p = skipwhite(arg); + if (ends_excmd(*p)) + { + scope->se_try.ts_caught_all = TRUE; + scope->se_try.ts_catch_label = 0; + } + else + { + // Push v:exception, push {expr} and MATCH + generate_instr_type(cctx, ISN_PUSHEXC, &t_string); + + if (compile_expr1(&p, cctx) == FAIL) + return NULL; + + // TODO: check for strings? + if (generate_COMPARE(cctx, EXPR_MATCH, FALSE) == FAIL) + return NULL; + + scope->se_try.ts_catch_label = instr->ga_len; + if (generate_JUMP(cctx, JUMP_IF_FALSE, 0) == FAIL) + return NULL; + } + + if (generate_instr(cctx, ISN_CATCH) == NULL) + return NULL; + + if (new_scope(cctx, BLOCK_SCOPE) == NULL) + return NULL; + return p; + } + + static char_u * + compile_finally(char_u *arg, cctx_T *cctx) + { + scope_T *scope = cctx->ctx_scope; + garray_T *instr = &cctx->ctx_instr; + isn_T *isn; + + // end block scope from :try or :catch + if (scope != NULL && scope->se_type == BLOCK_SCOPE) + compile_endblock(cctx); + scope = cctx->ctx_scope; + + // Error if not in a :try scope + if (scope == NULL || scope->se_type != TRY_SCOPE) + { + emsg(_(e_finally)); + return NULL; + } + + // End :catch or :finally scope: set value in ISN_TRY instruction + isn = ((isn_T *)instr->ga_data) + scope->se_try.ts_try_label; + if (isn->isn_arg.try.try_finally != 0) + { + emsg(_(e_finally_dup)); + return NULL; + } + + // Fill in the "end" label in jumps at the end of the blocks. + compile_fill_jump_to_end(&scope->se_try.ts_end_label, cctx); + + if (scope->se_try.ts_catch_label != 0) + { + // Previous catch without match jumps here + isn = ((isn_T *)instr->ga_data) + scope->se_try.ts_catch_label; + isn->isn_arg.jump.jump_where = instr->ga_len; + } + + isn->isn_arg.try.try_finally = instr->ga_len; + // TODO: set index in ts_finally_label jumps + + return arg; + } + + static char_u * + compile_endtry(char_u *arg, cctx_T *cctx) + { + scope_T *scope = cctx->ctx_scope; + garray_T *instr = &cctx->ctx_instr; + isn_T *isn; + + // end block scope from :catch or :finally + if (scope != NULL && scope->se_type == BLOCK_SCOPE) + compile_endblock(cctx); + scope = cctx->ctx_scope; + + // Error if not in a :try scope + if (scope == NULL || scope->se_type != TRY_SCOPE) + { + if (scope == NULL) + emsg(_(e_no_endtry)); + else if (scope->se_type == WHILE_SCOPE) + emsg(_(e_endwhile)); + if (scope->se_type == FOR_SCOPE) + emsg(_(e_endfor)); + else + emsg(_(e_endif)); + return NULL; + } + + isn = ((isn_T *)instr->ga_data) + scope->se_try.ts_try_label; + if (isn->isn_arg.try.try_catch == 0 && isn->isn_arg.try.try_finally == 0) + { + emsg(_("E1032: missing :catch or :finally")); + return NULL; + } + + // Fill in the "end" label in jumps at the end of the blocks, if not done + // by ":finally". + compile_fill_jump_to_end(&scope->se_try.ts_end_label, cctx); + + // End :catch or :finally scope: set value in ISN_TRY instruction + if (isn->isn_arg.try.try_finally == 0) + isn->isn_arg.try.try_finally = instr->ga_len; + compile_endblock(cctx); + + if (generate_instr(cctx, ISN_ENDTRY) == NULL) + return NULL; + return arg; + } + + /* + * compile "throw {expr}" + */ + static char_u * + compile_throw(char_u *arg, cctx_T *cctx UNUSED) + { + char_u *p = skipwhite(arg); + + if (ends_excmd(*p)) + { + emsg(_(e_argreq)); + return NULL; + } + if (compile_expr1(&p, cctx) == FAIL) + return NULL; + if (may_generate_2STRING(-1, cctx) == FAIL) + return NULL; + if (generate_instr_drop(cctx, ISN_THROW, 1) == NULL) + return NULL; + + return p; + } + + /* + * compile "echo expr" + */ + static char_u * + compile_echo(char_u *arg, int with_white, cctx_T *cctx) + { + char_u *p = arg; + int count = 0; + + // for () + { + if (compile_expr1(&p, cctx) == FAIL) + return NULL; + ++count; + } + + generate_ECHO(cctx, with_white, count); + + return p; + } + + /* + * After ex_function() has collected all the function lines: parse and compile + * the lines into instructions. + * Adds the function to "def_functions". + * When "set_return_type" is set then set ufunc->uf_ret_type to the type of the + * return statement (used for lambda). + */ + void + compile_def_function(ufunc_T *ufunc, int set_return_type) + { + dfunc_T *dfunc; + char_u *line = NULL; + char_u *p; + exarg_T ea; + char *errormsg = NULL; // error message + int had_return = FALSE; + cctx_T cctx; + garray_T *instr; + int called_emsg_before = called_emsg; + int ret = FAIL; + sctx_T save_current_sctx = current_sctx; + + if (ufunc->uf_dfunc_idx >= 0) + { + // redefining a function that was compiled before + dfunc = ((dfunc_T *)def_functions.ga_data) + ufunc->uf_dfunc_idx; + dfunc->df_deleted = FALSE; + } + else + { + // Add the function to "def_functions". + if (ga_grow(&def_functions, 1) == FAIL) + return; + dfunc = ((dfunc_T *)def_functions.ga_data) + def_functions.ga_len; + vim_memset(dfunc, 0, sizeof(dfunc_T)); + dfunc->df_idx = def_functions.ga_len; + ufunc->uf_dfunc_idx = dfunc->df_idx; + dfunc->df_ufunc = ufunc; + ++def_functions.ga_len; + } + + vim_memset(&cctx, 0, sizeof(cctx)); + cctx.ctx_ufunc = ufunc; + cctx.ctx_lnum = -1; + ga_init2(&cctx.ctx_locals, sizeof(lvar_T), 10); + ga_init2(&cctx.ctx_type_stack, sizeof(type_T *), 50); + ga_init2(&cctx.ctx_imports, sizeof(imported_T), 10); + cctx.ctx_type_list = &ufunc->uf_type_list; + ga_init2(&cctx.ctx_instr, sizeof(isn_T), 50); + instr = &cctx.ctx_instr; + + // Most modern script version. + current_sctx.sc_version = SCRIPT_VERSION_VIM9; + + for (;;) + { + if (line != NULL && *line == '|') + // the line continues after a '|' + ++line; + else if (line != NULL && *line != NUL) + { + semsg(_("E488: Trailing characters: %s"), line); + goto erret; + } + else + { + do + { + ++cctx.ctx_lnum; + if (cctx.ctx_lnum == ufunc->uf_lines.ga_len) + break; + line = ((char_u **)ufunc->uf_lines.ga_data)[cctx.ctx_lnum]; + } while (line == NULL); + if (cctx.ctx_lnum == ufunc->uf_lines.ga_len) + break; + SOURCING_LNUM = ufunc->uf_script_ctx.sc_lnum + cctx.ctx_lnum + 1; + } + + had_return = FALSE; + vim_memset(&ea, 0, sizeof(ea)); + ea.cmdlinep = &line; + ea.cmd = skipwhite(line); + + // "}" ends a block scope + if (*ea.cmd == '}') + { + scopetype_T stype = cctx.ctx_scope == NULL + ? NO_SCOPE : cctx.ctx_scope->se_type; + + if (stype == BLOCK_SCOPE) + { + compile_endblock(&cctx); + line = ea.cmd; + } + else + { + emsg("E1025: using } outside of a block scope"); + goto erret; + } + if (line != NULL) + line = skipwhite(ea.cmd + 1); + continue; + } + + // "{" starts a block scope + if (*ea.cmd == '{') + { + line = compile_block(ea.cmd, &cctx); + continue; + } + + /* + * COMMAND MODIFIERS + */ + if (parse_command_modifiers(&ea, &errormsg, FALSE) == FAIL) + { + if (errormsg != NULL) + goto erret; + // empty line or comment + line = (char_u *)""; + continue; + } + + // Skip ":call" to get to the function name. + if (checkforcmd(&ea.cmd, "call", 3)) + ea.cmd = skipwhite(ea.cmd); + + // Assuming the command starts with a variable or function name, find + // what follows. Also "&opt = value". + p = (*ea.cmd == '&') ? ea.cmd + 1 : ea.cmd; + p = to_name_end(p); + if (p > ea.cmd && *p != NUL) + { + int oplen; + int heredoc; + + // "funcname(" is always a function call. + // "varname[]" is an expression. + // "g:varname" is an expression. + // "varname->expr" is an expression. + if (*p == '(' + || *p == '[' + || ((p - ea.cmd) > 2 && ea.cmd[1] == ':') + || (*p == '-' && p[1] == '>')) + { + // TODO + } + + oplen = assignment_len(skipwhite(p), &heredoc); + if (oplen > 0) + { + // Recognize an assignment if we recognize the variable name: + // "g:var = expr" + // "var = expr" where "var" is a local var name. + // "&opt = expr" + if (*ea.cmd == '&' + || ((p - ea.cmd) > 2 && ea.cmd[1] == ':') + || lookup_local(ea.cmd, p - ea.cmd, &cctx) >= 0 + || lookup_script(ea.cmd, p - ea.cmd) == OK) + { + line = compile_assignment(ea.cmd, &ea, CMD_SIZE, &cctx); + if (line == NULL) + goto erret; + continue; + } + } + } + + /* + * COMMAND after range + */ + ea.cmd = skip_range(ea.cmd, NULL); + p = find_ex_command(&ea, NULL, lookup_local, &cctx); + + if (p == ea.cmd && ea.cmdidx != CMD_SIZE) + { + // Expression or function call. + if (ea.cmdidx == CMD_eval) + { + p = ea.cmd; + if (compile_expr1(&p, &cctx) == FAIL) + goto erret; + + // drop the return value + generate_instr_drop(&cctx, ISN_DROP, 1); + line = p; + continue; + } + if (ea.cmdidx == CMD_let) + { + line = compile_assignment(ea.cmd, &ea, CMD_SIZE, &cctx); + if (line == NULL) + goto erret; + continue; + } + iemsg("Command from find_ex_command() not handled"); + goto erret; + } + + p = skipwhite(p); + + switch (ea.cmdidx) + { + case CMD_def: + case CMD_function: + // TODO: Nested function + emsg("Nested function not implemented yet"); + goto erret; + + case CMD_return: + line = compile_return(p, set_return_type, &cctx); + had_return = TRUE; + break; + + case CMD_let: + case CMD_const: + line = compile_assignment(p, &ea, ea.cmdidx, &cctx); + break; + + case CMD_import: + line = compile_import(p, &cctx); + break; + + case CMD_if: + line = compile_if(p, &cctx); + break; + case CMD_elseif: + line = compile_elseif(p, &cctx); + break; + case CMD_else: + line = compile_else(p, &cctx); + break; + case CMD_endif: + line = compile_endif(p, &cctx); + break; + + case CMD_while: + line = compile_while(p, &cctx); + break; + case CMD_endwhile: + line = compile_endwhile(p, &cctx); + break; + + case CMD_for: + line = compile_for(p, &cctx); + break; + case CMD_endfor: + line = compile_endfor(p, &cctx); + break; + case CMD_continue: + line = compile_continue(p, &cctx); + break; + case CMD_break: + line = compile_break(p, &cctx); + break; + + case CMD_try: + line = compile_try(p, &cctx); + break; + case CMD_catch: + line = compile_catch(p, &cctx); + break; + case CMD_finally: + line = compile_finally(p, &cctx); + break; + case CMD_endtry: + line = compile_endtry(p, &cctx); + break; + case CMD_throw: + line = compile_throw(p, &cctx); + break; + + case CMD_echo: + line = compile_echo(p, TRUE, &cctx); + break; + case CMD_echon: + line = compile_echo(p, FALSE, &cctx); + break; + + default: + // Not recognized, execute with do_cmdline_cmd(). + generate_EXEC(&cctx, line); + line = (char_u *)""; + break; + } + if (line == NULL) + goto erret; + + if (cctx.ctx_type_stack.ga_len < 0) + { + iemsg("Type stack underflow"); + goto erret; + } + } + + if (cctx.ctx_scope != NULL) + { + if (cctx.ctx_scope->se_type == IF_SCOPE) + emsg(_(e_endif)); + else if (cctx.ctx_scope->se_type == WHILE_SCOPE) + emsg(_(e_endwhile)); + else if (cctx.ctx_scope->se_type == FOR_SCOPE) + emsg(_(e_endfor)); + else + emsg(_("E1026: Missing }")); + goto erret; + } + + if (!had_return) + { + if (ufunc->uf_ret_type->tt_type != VAR_VOID) + { + emsg(_("E1027: Missing return statement")); + goto erret; + } + + // Return zero if there is no return at the end. + generate_PUSHNR(&cctx, 0); + generate_instr(&cctx, ISN_RETURN); + } + + dfunc->df_instr = instr->ga_data; + dfunc->df_instr_count = instr->ga_len; + dfunc->df_varcount = cctx.ctx_max_local; + + ret = OK; + + erret: + if (ret == FAIL) + { + ga_clear(instr); + ufunc->uf_dfunc_idx = -1; + --def_functions.ga_len; + if (errormsg != NULL) + emsg(errormsg); + else if (called_emsg == called_emsg_before) + emsg("E1028: compile_def_function failed"); + + // don't execute this function body + ufunc->uf_lines.ga_len = 0; + } + + current_sctx = save_current_sctx; + ga_clear(&cctx.ctx_type_stack); + ga_clear(&cctx.ctx_locals); + } + + /* + * Delete an instruction, free what it contains. + */ + static void + delete_instr(isn_T *isn) + { + switch (isn->isn_type) + { + case ISN_EXEC: + case ISN_LOADENV: + case ISN_LOADG: + case ISN_LOADOPT: + case ISN_MEMBER: + case ISN_PUSHEXC: + case ISN_PUSHS: + case ISN_STOREG: + vim_free(isn->isn_arg.string); + break; + + case ISN_LOADS: + vim_free(isn->isn_arg.loads.ls_name); + break; + + case ISN_STOREOPT: + vim_free(isn->isn_arg.storeopt.so_name); + break; + + case ISN_PUSHBLOB: // push blob isn_arg.blob + blob_unref(isn->isn_arg.blob); + break; + + case ISN_UCALL: + vim_free(isn->isn_arg.ufunc.cuf_name); + break; + + case ISN_2BOOL: + case ISN_2STRING: + case ISN_ADDBLOB: + case ISN_ADDLIST: + case ISN_BCALL: + case ISN_CATCH: + case ISN_CHECKNR: + case ISN_CHECKTYPE: + case ISN_COMPAREANY: + case ISN_COMPAREBLOB: + case ISN_COMPAREBOOL: + case ISN_COMPAREDICT: + case ISN_COMPAREFLOAT: + case ISN_COMPAREFUNC: + case ISN_COMPARELIST: + case ISN_COMPARENR: + case ISN_COMPAREPARTIAL: + case ISN_COMPARESPECIAL: + case ISN_COMPARESTRING: + case ISN_CONCAT: + case ISN_DCALL: + case ISN_DROP: + case ISN_ECHO: + case ISN_ENDTRY: + case ISN_FOR: + case ISN_FUNCREF: + case ISN_INDEX: + case ISN_JUMP: + case ISN_LOAD: + case ISN_LOADSCRIPT: + case ISN_LOADREG: + case ISN_LOADV: + case ISN_NEGATENR: + case ISN_NEWDICT: + case ISN_NEWLIST: + case ISN_OPNR: + case ISN_OPFLOAT: + case ISN_OPANY: + case ISN_PCALL: + case ISN_PUSHF: + case ISN_PUSHNR: + case ISN_PUSHBOOL: + case ISN_PUSHSPEC: + case ISN_RETURN: + case ISN_STORE: + case ISN_STORENR: + case ISN_STORESCRIPT: + case ISN_THROW: + case ISN_TRY: + // nothing allocated + break; + } + } + + /* + * When a user function is deleted, delete any associated def function. + */ + void + delete_def_function(ufunc_T *ufunc) + { + int idx; + + if (ufunc->uf_dfunc_idx >= 0) + { + dfunc_T *dfunc = ((dfunc_T *)def_functions.ga_data) + + ufunc->uf_dfunc_idx; + ga_clear(&dfunc->df_def_args_isn); + + for (idx = 0; idx < dfunc->df_instr_count; ++idx) + delete_instr(dfunc->df_instr + idx); + VIM_CLEAR(dfunc->df_instr); + + dfunc->df_deleted = TRUE; + } + } + + #if defined(EXITFREE) || defined(PROTO) + void + free_def_functions(void) + { + vim_free(def_functions.ga_data); + } + #endif + + + #endif // FEAT_EVAL *** ../vim-8.2.0148/src/vim9execute.c 2020-01-26 15:45:56.753682089 +0100 --- src/vim9execute.c 2020-01-26 15:30:08.625766579 +0100 *************** *** 0 **** --- 1,1934 ---- + /* vi:set ts=8 sts=4 sw=4 noet: + * + * VIM - Vi IMproved by Bram Moolenaar + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + * See README.txt for an overview of the Vim source code. + */ + + /* + * vim9execute.c: execute Vim9 script instructions + */ + + #define USING_FLOAT_STUFF + #include "vim.h" + + #if defined(FEAT_EVAL) || defined(PROTO) + + #ifdef VMS + # include + #endif + + #include "vim9.h" + + // Structure put on ec_trystack when ISN_TRY is encountered. + typedef struct { + int tcd_frame; // ec_frame when ISN_TRY was encountered + int tcd_catch_idx; // instruction of the first catch + int tcd_finally_idx; // instruction of the finally block + int tcd_caught; // catch block entered + int tcd_return; // when TRUE return from end of :finally + } trycmd_T; + + + // A stack is used to store: + // - arguments passed to a :def function + // - info about the calling function, to use when returning + // - local variables + // - temporary values + // + // In detail (FP == Frame Pointer): + // arg1 first argument from caller (if present) + // arg2 second argument from caller (if present) + // extra_arg1 any missing optional argument default value + // FP -> cur_func calling function + // current previous instruction pointer + // frame_ptr previous Frame Pointer + // var1 space for local variable + // var2 space for local variable + // .... fixed space for max. number of local variables + // temp temporary values + // .... flexible space for temporary values (can grow big) + + /* + * Execution context. + */ + typedef struct { + garray_T ec_stack; // stack of typval_T values + int ec_frame; // index in ec_stack: context of ec_dfunc_idx + + garray_T ec_trystack; // stack of trycmd_T values + int ec_in_catch; // when TRUE in catch or finally block + + int ec_dfunc_idx; // current function index + isn_T *ec_instr; // array with instructions + int ec_iidx; // index in ec_instr: instruction to execute + } ectx_T; + + // Get pointer to item relative to the bottom of the stack, -1 is the last one. + #define STACK_TV_BOT(idx) (((typval_T *)ectx->ec_stack.ga_data) + ectx->ec_stack.ga_len + idx) + + /* + * Return the number of arguments, including any vararg. + */ + static int + ufunc_argcount(ufunc_T *ufunc) + { + return ufunc->uf_args.ga_len + (ufunc->uf_va_name != NULL ? 1 : 0); + } + + /* + * Call compiled function "cdf_idx" from compiled code. + * + * Stack has: + * - current arguments (already there) + * - omitted optional argument (default values) added here + * - stack frame: + * - pointer to calling function + * - Index of next instruction in calling function + * - previous frame pointer + * - reserved space for local variables + */ + static int + call_dfunc(int cdf_idx, int argcount, ectx_T *ectx) + { + dfunc_T *dfunc = ((dfunc_T *)def_functions.ga_data) + cdf_idx; + ufunc_T *ufunc = dfunc->df_ufunc; + int optcount = ufunc_argcount(ufunc) - argcount; + int idx; + + if (dfunc->df_deleted) + { + emsg_funcname(e_func_deleted, ufunc->uf_name); + return FAIL; + } + + if (ga_grow(&ectx->ec_stack, optcount + 3 + dfunc->df_varcount) == FAIL) + return FAIL; + + // TODO: Put omitted argument default values on the stack. + if (optcount > 0) + { + emsg("optional arguments not implemented yet"); + return FAIL; + } + if (optcount < 0) + { + emsg("argument count wrong?"); + return FAIL; + } + // for (idx = argcount - dfunc->df_minarg; + // idx < dfunc->df_maxarg; ++idx) + // { + // copy_tv(&dfunc->df_defarg[idx], STACK_TV_BOT(0)); + // ++ectx->ec_stack.ga_len; + // } + + // Store current execution state in stack frame for ISN_RETURN. + // TODO: If the actual number of arguments doesn't match what the called + // function expects things go bad. + STACK_TV_BOT(0)->vval.v_number = ectx->ec_dfunc_idx; + STACK_TV_BOT(1)->vval.v_number = ectx->ec_iidx; + STACK_TV_BOT(2)->vval.v_number = ectx->ec_frame; + ectx->ec_frame = ectx->ec_stack.ga_len; + + // Initialize local variables + for (idx = 0; idx < dfunc->df_varcount; ++idx) + STACK_TV_BOT(STACK_FRAME_SIZE + idx)->v_type = VAR_UNKNOWN; + ectx->ec_stack.ga_len += STACK_FRAME_SIZE + dfunc->df_varcount; + + // Set execution state to the start of the called function. + ectx->ec_dfunc_idx = cdf_idx; + ectx->ec_instr = dfunc->df_instr; + estack_push_ufunc(ETYPE_UFUNC, dfunc->df_ufunc, 1); + ectx->ec_iidx = 0; + + return OK; + } + + // Get pointer to item in the stack. + #define STACK_TV(idx) (((typval_T *)ectx->ec_stack.ga_data) + idx) + + /* + * Return from the current function. + */ + static void + func_return(ectx_T *ectx) + { + int ret_idx = ectx->ec_stack.ga_len - 1; + int idx; + dfunc_T *dfunc; + + // execution context goes one level up + estack_pop(); + + // Clear the local variables and temporary values, but not + // the return value. + for (idx = ectx->ec_frame + STACK_FRAME_SIZE; + idx < ectx->ec_stack.ga_len - 1; ++idx) + clear_tv(STACK_TV(idx)); + dfunc = ((dfunc_T *)def_functions.ga_data) + ectx->ec_dfunc_idx; + ectx->ec_stack.ga_len = ectx->ec_frame + - ufunc_argcount(dfunc->df_ufunc) + 1; + ectx->ec_dfunc_idx = STACK_TV(ectx->ec_frame)->vval.v_number; + ectx->ec_iidx = STACK_TV(ectx->ec_frame + 1)->vval.v_number; + ectx->ec_frame = STACK_TV(ectx->ec_frame + 2)->vval.v_number; + *STACK_TV_BOT(-1) = *STACK_TV(ret_idx); + dfunc = ((dfunc_T *)def_functions.ga_data) + ectx->ec_dfunc_idx; + ectx->ec_instr = dfunc->df_instr; + } + + #undef STACK_TV + + /* + * Prepare arguments and rettv for calling a builtin or user function. + */ + static int + call_prepare(int argcount, typval_T *argvars, ectx_T *ectx) + { + int idx; + typval_T *tv; + + // Move arguments from bottom of the stack to argvars[] and add terminator. + for (idx = 0; idx < argcount; ++idx) + argvars[idx] = *STACK_TV_BOT(idx - argcount); + argvars[argcount].v_type = VAR_UNKNOWN; + + // Result replaces the arguments on the stack. + if (argcount > 0) + ectx->ec_stack.ga_len -= argcount - 1; + else if (ga_grow(&ectx->ec_stack, 1) == FAIL) + return FAIL; + else + ++ectx->ec_stack.ga_len; + + // Default return value is zero. + tv = STACK_TV_BOT(-1); + tv->v_type = VAR_NUMBER; + tv->vval.v_number = 0; + + return OK; + } + + /* + * Call a builtin function by index. + */ + static int + call_bfunc(int func_idx, int argcount, ectx_T *ectx) + { + typval_T argvars[MAX_FUNC_ARGS]; + int idx; + + if (call_prepare(argcount, argvars, ectx) == FAIL) + return FAIL; + + // Call the builtin function. + call_internal_func_by_idx(func_idx, argvars, STACK_TV_BOT(-1)); + + // Clear the arguments. + for (idx = 0; idx < argcount; ++idx) + clear_tv(&argvars[idx]); + return OK; + } + + /* + * Execute a user defined function. + */ + static int + call_ufunc(ufunc_T *ufunc, int argcount, ectx_T *ectx) + { + typval_T argvars[MAX_FUNC_ARGS]; + funcexe_T funcexe; + int error; + int idx; + + if (ufunc->uf_dfunc_idx >= 0) + // The function has been compiled, can call it quickly. + return call_dfunc(ufunc->uf_dfunc_idx, argcount, ectx); + + if (call_prepare(argcount, argvars, ectx) == FAIL) + return FAIL; + vim_memset(&funcexe, 0, sizeof(funcexe)); + funcexe.evaluate = TRUE; + + // Call the user function. Result goes in last position on the stack. + // TODO: add selfdict if there is one + error = call_user_func_check(ufunc, argcount, argvars, + STACK_TV_BOT(-1), &funcexe, NULL); + + // Clear the arguments. + for (idx = 0; idx < argcount; ++idx) + clear_tv(&argvars[idx]); + + if (error != FCERR_NONE) + { + user_func_error(error, ufunc->uf_name); + return FAIL; + } + return OK; + } + + /* + * Execute a function by "name". + * This can be a builtin function or a user function. + * Returns FAIL if not found without an error message. + */ + static int + call_by_name(char_u *name, int argcount, ectx_T *ectx) + { + ufunc_T *ufunc; + + if (builtin_function(name, -1)) + { + int func_idx = find_internal_func(name); + + if (func_idx < 0) + return FAIL; + if (check_internal_func(func_idx, argcount) == FAIL) + return FAIL; + return call_bfunc(func_idx, argcount, ectx); + } + + ufunc = find_func(name, NULL); + if (ufunc != NULL) + return call_ufunc(ufunc, argcount, ectx); + + return FAIL; + } + + static int + call_partial(typval_T *tv, int argcount, ectx_T *ectx) + { + char_u *name; + int called_emsg_before = called_emsg; + + if (tv->v_type == VAR_PARTIAL) + { + partial_T *pt = tv->vval.v_partial; + + if (pt->pt_func != NULL) + return call_ufunc(pt->pt_func, argcount, ectx); + name = pt->pt_name; + } + else + name = tv->vval.v_string; + if (call_by_name(name, argcount, ectx) == FAIL) + { + if (called_emsg == called_emsg_before) + semsg(_(e_unknownfunc), name); + return FAIL; + } + return OK; + } + + /* + * Execute a function by "name". + * This can be a builtin function, user function or a funcref. + */ + static int + call_eval_func(char_u *name, int argcount, ectx_T *ectx) + { + int called_emsg_before = called_emsg; + + if (call_by_name(name, argcount, ectx) == FAIL + && called_emsg == called_emsg_before) + { + // "name" may be a variable that is a funcref or partial + // if find variable + // call_partial() + // else + // semsg(_(e_unknownfunc), name); + emsg("call_eval_func(partial) not implemented yet"); + return FAIL; + } + return OK; + } + + /* + * Call a "def" function from old Vim script. + * Return OK or FAIL. + */ + int + call_def_function( + ufunc_T *ufunc, + int argc, // nr of arguments + typval_T *argv, // arguments + typval_T *rettv) // return value + { + ectx_T ectx; // execution context + int initial_frame_ptr; + typval_T *tv; + int idx; + int ret = FAIL; + dfunc_T *dfunc; + + // Get pointer to item in the stack. + #define STACK_TV(idx) (((typval_T *)ectx.ec_stack.ga_data) + idx) + + // Get pointer to item at the bottom of the stack, -1 is the bottom. + #undef STACK_TV_BOT + #define STACK_TV_BOT(idx) (((typval_T *)ectx.ec_stack.ga_data) + ectx.ec_stack.ga_len + idx) + + // Get pointer to local variable on the stack. + #define STACK_TV_VAR(idx) (((typval_T *)ectx.ec_stack.ga_data) + ectx.ec_frame + STACK_FRAME_SIZE + idx) + + vim_memset(&ectx, 0, sizeof(ectx)); + ga_init2(&ectx.ec_stack, sizeof(typval_T), 500); + if (ga_grow(&ectx.ec_stack, 20) == FAIL) + goto failed; + ectx.ec_dfunc_idx = ufunc->uf_dfunc_idx; + + ga_init2(&ectx.ec_trystack, sizeof(trycmd_T), 10); + + // Put arguments on the stack. + for (idx = 0; idx < argc; ++idx) + { + copy_tv(&argv[idx], STACK_TV_BOT(0)); + ++ectx.ec_stack.ga_len; + } + + // Frame pointer points to just after arguments. + ectx.ec_frame = ectx.ec_stack.ga_len; + initial_frame_ptr = ectx.ec_frame; + + // dummy frame entries + for (idx = 0; idx < STACK_FRAME_SIZE; ++idx) + { + STACK_TV(ectx.ec_stack.ga_len)->v_type = VAR_UNKNOWN; + ++ectx.ec_stack.ga_len; + } + + // Reserve space for local variables. + dfunc = ((dfunc_T *)def_functions.ga_data) + ufunc->uf_dfunc_idx; + for (idx = 0; idx < dfunc->df_varcount; ++idx) + STACK_TV_VAR(idx)->v_type = VAR_UNKNOWN; + ectx.ec_stack.ga_len += dfunc->df_varcount; + + ectx.ec_instr = dfunc->df_instr; + ectx.ec_iidx = 0; + for (;;) + { + isn_T *iptr; + trycmd_T *trycmd = NULL; + + if (did_throw && !ectx.ec_in_catch) + { + garray_T *trystack = &ectx.ec_trystack; + + // An exception jumps to the first catch, finally, or returns from + // the current function. + if (trystack->ga_len > 0) + trycmd = ((trycmd_T *)trystack->ga_data) + trystack->ga_len - 1; + if (trycmd != NULL && trycmd->tcd_frame == ectx.ec_frame) + { + // jump to ":catch" or ":finally" + ectx.ec_in_catch = TRUE; + ectx.ec_iidx = trycmd->tcd_catch_idx; + } + else + { + // not inside try or need to return from current functions. + if (ectx.ec_frame == initial_frame_ptr) + { + // At the toplevel we are done. Push a dummy return value. + if (ga_grow(&ectx.ec_stack, 1) == FAIL) + goto failed; + tv = STACK_TV_BOT(0); + tv->v_type = VAR_NUMBER; + tv->vval.v_number = 0; + ++ectx.ec_stack.ga_len; + goto done; + } + + func_return(&ectx); + } + continue; + } + + iptr = &ectx.ec_instr[ectx.ec_iidx++]; + switch (iptr->isn_type) + { + // execute Ex command line + case ISN_EXEC: + do_cmdline_cmd(iptr->isn_arg.string); + break; + + // execute :echo {string} ... + case ISN_ECHO: + { + int count = iptr->isn_arg.echo.echo_count; + int atstart = TRUE; + int needclr = TRUE; + + for (idx = 0; idx < count; ++idx) + { + tv = STACK_TV_BOT(idx - count); + echo_one(tv, iptr->isn_arg.echo.echo_with_white, + &atstart, &needclr); + clear_tv(tv); + } + ectx.ec_stack.ga_len -= count; + } + break; + + // load local variable or argument + case ISN_LOAD: + if (ga_grow(&ectx.ec_stack, 1) == FAIL) + goto failed; + copy_tv(STACK_TV_VAR(iptr->isn_arg.number), STACK_TV_BOT(0)); + ++ectx.ec_stack.ga_len; + break; + + // load v: variable + case ISN_LOADV: + if (ga_grow(&ectx.ec_stack, 1) == FAIL) + goto failed; + copy_tv(get_vim_var_tv(iptr->isn_arg.number), STACK_TV_BOT(0)); + ++ectx.ec_stack.ga_len; + break; + + // load s: variable in vim9script + case ISN_LOADSCRIPT: + { + scriptitem_T *si = + &SCRIPT_ITEM(iptr->isn_arg.script.script_sid); + svar_T *sv; + + sv = ((svar_T *)si->sn_var_vals.ga_data) + + iptr->isn_arg.script.script_idx; + if (ga_grow(&ectx.ec_stack, 1) == FAIL) + goto failed; + copy_tv(sv->sv_tv, STACK_TV_BOT(0)); + ++ectx.ec_stack.ga_len; + } + break; + + // load s: variable in old script + case ISN_LOADS: + { + hashtab_T *ht = &SCRIPT_VARS(iptr->isn_arg.loads.ls_sid); + char_u *name = iptr->isn_arg.loads.ls_name; + dictitem_T *di = find_var_in_ht(ht, 0, name, TRUE); + if (di == NULL) + { + semsg(_("E121: Undefined variable: s:%s"), name); + goto failed; + } + else + { + if (ga_grow(&ectx.ec_stack, 1) == FAIL) + goto failed; + copy_tv(&di->di_tv, STACK_TV_BOT(0)); + ++ectx.ec_stack.ga_len; + } + } + break; + + // load g: variable + case ISN_LOADG: + { + dictitem_T *di; + + di = find_var_in_ht(get_globvar_ht(), 0, + iptr->isn_arg.string, TRUE); + if (di == NULL) + { + semsg(_("E121: Undefined variable: g:%s"), + iptr->isn_arg.string); + goto failed; + } + else + { + if (ga_grow(&ectx.ec_stack, 1) == FAIL) + goto failed; + copy_tv(&di->di_tv, STACK_TV_BOT(0)); + ++ectx.ec_stack.ga_len; + } + } + break; + + // load &option + case ISN_LOADOPT: + { + typval_T optval; + char_u *name = iptr->isn_arg.string; + + if (ga_grow(&ectx.ec_stack, 1) == FAIL) + goto failed; + get_option_tv(&name, &optval, TRUE); + *STACK_TV_BOT(0) = optval; + ++ectx.ec_stack.ga_len; + } + break; + + // load $ENV + case ISN_LOADENV: + { + typval_T optval; + char_u *name = iptr->isn_arg.string; + + if (ga_grow(&ectx.ec_stack, 1) == FAIL) + goto failed; + get_env_tv(&name, &optval, TRUE); + *STACK_TV_BOT(0) = optval; + ++ectx.ec_stack.ga_len; + } + break; + + // load @register + case ISN_LOADREG: + if (ga_grow(&ectx.ec_stack, 1) == FAIL) + goto failed; + tv = STACK_TV_BOT(0); + tv->v_type = VAR_STRING; + tv->vval.v_string = get_reg_contents( + iptr->isn_arg.number, GREG_EXPR_SRC); + ++ectx.ec_stack.ga_len; + break; + + // store local variable + case ISN_STORE: + --ectx.ec_stack.ga_len; + tv = STACK_TV_VAR(iptr->isn_arg.number); + clear_tv(tv); + *tv = *STACK_TV_BOT(0); + break; + + // store script-local variable + case ISN_STORESCRIPT: + { + scriptitem_T *si = &SCRIPT_ITEM( + iptr->isn_arg.script.script_sid); + svar_T *sv = ((svar_T *)si->sn_var_vals.ga_data) + + iptr->isn_arg.script.script_idx; + + --ectx.ec_stack.ga_len; + clear_tv(sv->sv_tv); + *sv->sv_tv = *STACK_TV_BOT(0); + } + break; + + // store option + case ISN_STOREOPT: + { + long n = 0; + char_u *s = NULL; + char *msg; + + --ectx.ec_stack.ga_len; + tv = STACK_TV_BOT(0); + if (tv->v_type == VAR_STRING) + s = tv->vval.v_string; + else if (tv->v_type == VAR_NUMBER) + n = tv->vval.v_number; + else + { + emsg(_("E1051: Expected string or number")); + goto failed; + } + msg = set_option_value(iptr->isn_arg.storeopt.so_name, + n, s, iptr->isn_arg.storeopt.so_flags); + if (msg != NULL) + { + emsg(_(msg)); + goto failed; + } + clear_tv(tv); + } + break; + + // store g: variable + case ISN_STOREG: + { + dictitem_T *di; + + --ectx.ec_stack.ga_len; + di = find_var_in_ht(get_globvar_ht(), 0, + iptr->isn_arg.string, TRUE); + if (di == NULL) + { + funccal_entry_T entry; + + save_funccal(&entry); + set_var_const(iptr->isn_arg.string, NULL, + STACK_TV_BOT(0), FALSE, 0); + restore_funccal(); + } + else + { + clear_tv(&di->di_tv); + di->di_tv = *STACK_TV_BOT(0); + } + } + break; + + // store number in local variable + case ISN_STORENR: + tv = STACK_TV_VAR(iptr->isn_arg.storenr.str_idx); + clear_tv(tv); + tv->v_type = VAR_NUMBER; + tv->vval.v_number = iptr->isn_arg.storenr.str_val; + break; + + // push constant + case ISN_PUSHNR: + case ISN_PUSHBOOL: + case ISN_PUSHSPEC: + case ISN_PUSHF: + case ISN_PUSHS: + case ISN_PUSHBLOB: + if (ga_grow(&ectx.ec_stack, 1) == FAIL) + goto failed; + tv = STACK_TV_BOT(0); + ++ectx.ec_stack.ga_len; + switch (iptr->isn_type) + { + case ISN_PUSHNR: + tv->v_type = VAR_NUMBER; + tv->vval.v_number = iptr->isn_arg.number; + break; + case ISN_PUSHBOOL: + tv->v_type = VAR_BOOL; + tv->vval.v_number = iptr->isn_arg.number; + break; + case ISN_PUSHSPEC: + tv->v_type = VAR_SPECIAL; + tv->vval.v_number = iptr->isn_arg.number; + break; + #ifdef FEAT_FLOAT + case ISN_PUSHF: + tv->v_type = VAR_FLOAT; + tv->vval.v_float = iptr->isn_arg.fnumber; + break; + #endif + case ISN_PUSHBLOB: + blob_copy(iptr->isn_arg.blob, tv); + break; + default: + tv->v_type = VAR_STRING; + tv->vval.v_string = vim_strsave(iptr->isn_arg.string); + } + break; + + // create a list from items on the stack; uses a single allocation + // for the list header and the items + case ISN_NEWLIST: + { + int count = iptr->isn_arg.number; + list_T *list = list_alloc_with_items(count); + + if (list == NULL) + goto failed; + for (idx = 0; idx < count; ++idx) + list_set_item(list, idx, STACK_TV_BOT(idx - count)); + + if (count > 0) + ectx.ec_stack.ga_len -= count - 1; + else if (ga_grow(&ectx.ec_stack, 1) == FAIL) + goto failed; + else + ++ectx.ec_stack.ga_len; + tv = STACK_TV_BOT(-1); + tv->v_type = VAR_LIST; + tv->vval.v_list = list; + ++list->lv_refcount; + } + break; + + // create a dict from items on the stack + case ISN_NEWDICT: + { + int count = iptr->isn_arg.number; + dict_T *dict = dict_alloc(); + dictitem_T *item; + + if (dict == NULL) + goto failed; + for (idx = 0; idx < count; ++idx) + { + // check key type is VAR_STRING + tv = STACK_TV_BOT(2 * (idx - count)); + item = dictitem_alloc(tv->vval.v_string); + clear_tv(tv); + if (item == NULL) + goto failed; + item->di_tv = *STACK_TV_BOT(2 * (idx - count) + 1); + item->di_tv.v_lock = 0; + if (dict_add(dict, item) == FAIL) + goto failed; + } + + if (count > 0) + ectx.ec_stack.ga_len -= 2 * count - 1; + else if (ga_grow(&ectx.ec_stack, 1) == FAIL) + goto failed; + else + ++ectx.ec_stack.ga_len; + tv = STACK_TV_BOT(-1); + tv->v_type = VAR_DICT; + tv->vval.v_dict = dict; + ++dict->dv_refcount; + } + break; + + // call a :def function + case ISN_DCALL: + if (call_dfunc(iptr->isn_arg.dfunc.cdf_idx, + iptr->isn_arg.dfunc.cdf_argcount, + &ectx) == FAIL) + goto failed; + break; + + // call a builtin function + case ISN_BCALL: + SOURCING_LNUM = iptr->isn_lnum; + if (call_bfunc(iptr->isn_arg.bfunc.cbf_idx, + iptr->isn_arg.bfunc.cbf_argcount, + &ectx) == FAIL) + goto failed; + break; + + // call a funcref or partial + case ISN_PCALL: + { + cpfunc_T *pfunc = &iptr->isn_arg.pfunc; + int r; + typval_T partial; + + SOURCING_LNUM = iptr->isn_lnum; + if (pfunc->cpf_top) + { + // funcref is above the arguments + tv = STACK_TV_BOT(-pfunc->cpf_argcount - 1); + } + else + { + // Get the funcref from the stack. + --ectx.ec_stack.ga_len; + partial = *STACK_TV_BOT(0); + tv = &partial; + } + r = call_partial(tv, pfunc->cpf_argcount, &ectx); + if (tv == &partial) + clear_tv(&partial); + if (r == FAIL) + goto failed; + + if (pfunc->cpf_top) + { + // Get the funcref from the stack, overwrite with the + // return value. + clear_tv(tv); + --ectx.ec_stack.ga_len; + *STACK_TV_BOT(-1) = *STACK_TV_BOT(0); + } + } + break; + + // call a user defined function or funcref/partial + case ISN_UCALL: + { + cufunc_T *cufunc = &iptr->isn_arg.ufunc; + + SOURCING_LNUM = iptr->isn_lnum; + if (call_eval_func(cufunc->cuf_name, + cufunc->cuf_argcount, &ectx) == FAIL) + goto failed; + } + break; + + // return from a :def function call + case ISN_RETURN: + { + if (trycmd != NULL && trycmd->tcd_frame == ectx.ec_frame + && trycmd->tcd_finally_idx != 0) + { + // jump to ":finally" + ectx.ec_iidx = trycmd->tcd_finally_idx; + trycmd->tcd_return = TRUE; + } + else + { + // Restore previous function. If the frame pointer + // is zero then there is none and we are done. + if (ectx.ec_frame == initial_frame_ptr) + goto done; + + func_return(&ectx); + } + } + break; + + // push a function reference to a compiled function + case ISN_FUNCREF: + { + partial_T *pt = NULL; + + pt = ALLOC_CLEAR_ONE(partial_T); + if (pt == NULL) + goto failed; + dfunc = ((dfunc_T *)def_functions.ga_data) + + iptr->isn_arg.number; + pt->pt_func = dfunc->df_ufunc; + pt->pt_refcount = 1; + ++dfunc->df_ufunc->uf_refcount; + + if (ga_grow(&ectx.ec_stack, 1) == FAIL) + goto failed; + tv = STACK_TV_BOT(0); + ++ectx.ec_stack.ga_len; + tv->vval.v_partial = pt; + tv->v_type = VAR_PARTIAL; + } + break; + + // jump if a condition is met + case ISN_JUMP: + { + jumpwhen_T when = iptr->isn_arg.jump.jump_when; + int jump = TRUE; + + if (when != JUMP_ALWAYS) + { + tv = STACK_TV_BOT(-1); + jump = tv2bool(tv); + if (when == JUMP_IF_FALSE + || when == JUMP_AND_KEEP_IF_FALSE) + jump = !jump; + if (when == JUMP_IF_FALSE || when == JUMP_IF_TRUE + || !jump) + { + // drop the value from the stack + clear_tv(tv); + --ectx.ec_stack.ga_len; + } + } + if (jump) + ectx.ec_iidx = iptr->isn_arg.jump.jump_where; + } + break; + + // top of a for loop + case ISN_FOR: + { + list_T *list = STACK_TV_BOT(-1)->vval.v_list; + typval_T *idxtv = + STACK_TV_VAR(iptr->isn_arg.forloop.for_idx); + + // push the next item from the list + if (ga_grow(&ectx.ec_stack, 1) == FAIL) + goto failed; + if (++idxtv->vval.v_number >= list->lv_len) + // past the end of the list, jump to "endfor" + ectx.ec_iidx = iptr->isn_arg.forloop.for_end; + else if (list->lv_first == &range_list_item) + { + // non-materialized range() list + tv = STACK_TV_BOT(0); + tv->v_type = VAR_NUMBER; + tv->vval.v_number = list_find_nr( + list, idxtv->vval.v_number, NULL); + ++ectx.ec_stack.ga_len; + } + else + { + listitem_T *li = list_find(list, idxtv->vval.v_number); + + if (li == NULL) + goto failed; + copy_tv(&li->li_tv, STACK_TV_BOT(0)); + ++ectx.ec_stack.ga_len; + } + } + break; + + // start of ":try" block + case ISN_TRY: + { + if (ga_grow(&ectx.ec_trystack, 1) == FAIL) + goto failed; + trycmd = ((trycmd_T *)ectx.ec_trystack.ga_data) + + ectx.ec_trystack.ga_len; + ++ectx.ec_trystack.ga_len; + ++trylevel; + trycmd->tcd_frame = ectx.ec_frame; + trycmd->tcd_catch_idx = iptr->isn_arg.try.try_catch; + trycmd->tcd_finally_idx = iptr->isn_arg.try.try_finally; + } + break; + + case ISN_PUSHEXC: + if (current_exception == NULL) + { + iemsg("Evaluating catch while current_exception is NULL"); + goto failed; + } + if (ga_grow(&ectx.ec_stack, 1) == FAIL) + goto failed; + tv = STACK_TV_BOT(0); + ++ectx.ec_stack.ga_len; + tv->v_type = VAR_STRING; + tv->vval.v_string = vim_strsave( + (char_u *)current_exception->value); + break; + + case ISN_CATCH: + { + garray_T *trystack = &ectx.ec_trystack; + + if (trystack->ga_len > 0) + { + trycmd = ((trycmd_T *)trystack->ga_data) + + trystack->ga_len - 1; + trycmd->tcd_caught = TRUE; + } + did_emsg = got_int = did_throw = FALSE; + catch_exception(current_exception); + } + break; + + // end of ":try" block + case ISN_ENDTRY: + { + garray_T *trystack = &ectx.ec_trystack; + + if (trystack->ga_len > 0) + { + --trystack->ga_len; + --trylevel; + trycmd = ((trycmd_T *)trystack->ga_data) + + trystack->ga_len; + if (trycmd->tcd_caught) + { + // discard the exception + if (caught_stack == current_exception) + caught_stack = caught_stack->caught; + discard_current_exception(); + } + + if (trycmd->tcd_return) + { + // Restore previous function. If the frame pointer + // is zero then there is none and we are done. + if (ectx.ec_frame == initial_frame_ptr) + goto done; + + func_return(&ectx); + } + } + } + break; + + case ISN_THROW: + --ectx.ec_stack.ga_len; + tv = STACK_TV_BOT(0); + if (throw_exception(tv->vval.v_string, ET_USER, NULL) == FAIL) + { + vim_free(tv->vval.v_string); + goto failed; + } + did_throw = TRUE; + break; + + // compare with special values + case ISN_COMPAREBOOL: + case ISN_COMPARESPECIAL: + { + typval_T *tv1 = STACK_TV_BOT(-2); + typval_T *tv2 = STACK_TV_BOT(-1); + varnumber_T arg1 = tv1->vval.v_number; + varnumber_T arg2 = tv2->vval.v_number; + int res; + + switch (iptr->isn_arg.op.op_type) + { + case EXPR_EQUAL: res = arg1 == arg2; break; + case EXPR_NEQUAL: res = arg1 != arg2; break; + default: res = 0; break; + } + + --ectx.ec_stack.ga_len; + tv1->v_type = VAR_BOOL; + tv1->vval.v_number = res ? VVAL_TRUE : VVAL_FALSE; + } + break; + + // Operation with two number arguments + case ISN_OPNR: + case ISN_COMPARENR: + { + typval_T *tv1 = STACK_TV_BOT(-2); + typval_T *tv2 = STACK_TV_BOT(-1); + varnumber_T arg1 = tv1->vval.v_number; + varnumber_T arg2 = tv2->vval.v_number; + varnumber_T res; + + switch (iptr->isn_arg.op.op_type) + { + case EXPR_MULT: res = arg1 * arg2; break; + case EXPR_DIV: res = arg1 / arg2; break; + case EXPR_REM: res = arg1 % arg2; break; + case EXPR_SUB: res = arg1 - arg2; break; + case EXPR_ADD: res = arg1 + arg2; break; + + case EXPR_EQUAL: res = arg1 == arg2; break; + case EXPR_NEQUAL: res = arg1 != arg2; break; + case EXPR_GREATER: res = arg1 > arg2; break; + case EXPR_GEQUAL: res = arg1 >= arg2; break; + case EXPR_SMALLER: res = arg1 < arg2; break; + case EXPR_SEQUAL: res = arg1 <= arg2; break; + default: res = 0; break; + } + + --ectx.ec_stack.ga_len; + if (iptr->isn_type == ISN_COMPARENR) + { + tv1->v_type = VAR_BOOL; + tv1->vval.v_number = res ? VVAL_TRUE : VVAL_FALSE; + } + else + tv1->vval.v_number = res; + } + break; + + // Computation with two float arguments + case ISN_OPFLOAT: + case ISN_COMPAREFLOAT: + { + typval_T *tv1 = STACK_TV_BOT(-2); + typval_T *tv2 = STACK_TV_BOT(-1); + float_T arg1 = tv1->vval.v_float; + float_T arg2 = tv2->vval.v_float; + float_T res = 0; + int cmp = FALSE; + + switch (iptr->isn_arg.op.op_type) + { + case EXPR_MULT: res = arg1 * arg2; break; + case EXPR_DIV: res = arg1 / arg2; break; + case EXPR_SUB: res = arg1 - arg2; break; + case EXPR_ADD: res = arg1 + arg2; break; + + case EXPR_EQUAL: cmp = arg1 == arg2; break; + case EXPR_NEQUAL: cmp = arg1 != arg2; break; + case EXPR_GREATER: cmp = arg1 > arg2; break; + case EXPR_GEQUAL: cmp = arg1 >= arg2; break; + case EXPR_SMALLER: cmp = arg1 < arg2; break; + case EXPR_SEQUAL: cmp = arg1 <= arg2; break; + default: cmp = 0; break; + } + --ectx.ec_stack.ga_len; + if (iptr->isn_type == ISN_COMPAREFLOAT) + { + tv1->v_type = VAR_BOOL; + tv1->vval.v_number = cmp ? VVAL_TRUE : VVAL_FALSE; + } + else + tv1->vval.v_float = res; + } + break; + + case ISN_COMPARELIST: + { + typval_T *tv1 = STACK_TV_BOT(-2); + typval_T *tv2 = STACK_TV_BOT(-1); + list_T *arg1 = tv1->vval.v_list; + list_T *arg2 = tv2->vval.v_list; + int cmp = FALSE; + int ic = iptr->isn_arg.op.op_ic; + + switch (iptr->isn_arg.op.op_type) + { + case EXPR_EQUAL: cmp = + list_equal(arg1, arg2, ic, FALSE); break; + case EXPR_NEQUAL: cmp = + !list_equal(arg1, arg2, ic, FALSE); break; + case EXPR_IS: cmp = arg1 == arg2; break; + case EXPR_ISNOT: cmp = arg1 != arg2; break; + default: cmp = 0; break; + } + --ectx.ec_stack.ga_len; + clear_tv(tv1); + clear_tv(tv2); + tv1->v_type = VAR_BOOL; + tv1->vval.v_number = cmp ? VVAL_TRUE : VVAL_FALSE; + } + break; + + case ISN_COMPAREBLOB: + { + typval_T *tv1 = STACK_TV_BOT(-2); + typval_T *tv2 = STACK_TV_BOT(-1); + blob_T *arg1 = tv1->vval.v_blob; + blob_T *arg2 = tv2->vval.v_blob; + int cmp = FALSE; + + switch (iptr->isn_arg.op.op_type) + { + case EXPR_EQUAL: cmp = blob_equal(arg1, arg2); break; + case EXPR_NEQUAL: cmp = !blob_equal(arg1, arg2); break; + case EXPR_IS: cmp = arg1 == arg2; break; + case EXPR_ISNOT: cmp = arg1 != arg2; break; + default: cmp = 0; break; + } + --ectx.ec_stack.ga_len; + clear_tv(tv1); + clear_tv(tv2); + tv1->v_type = VAR_BOOL; + tv1->vval.v_number = cmp ? VVAL_TRUE : VVAL_FALSE; + } + break; + + // TODO: handle separately + case ISN_COMPARESTRING: + case ISN_COMPAREDICT: + case ISN_COMPAREFUNC: + case ISN_COMPAREPARTIAL: + case ISN_COMPAREANY: + { + typval_T *tv1 = STACK_TV_BOT(-2); + typval_T *tv2 = STACK_TV_BOT(-1); + exptype_T exptype = iptr->isn_arg.op.op_type; + int ic = iptr->isn_arg.op.op_ic; + + typval_compare(tv1, tv2, exptype, ic); + clear_tv(tv2); + tv1->v_type = VAR_BOOL; + tv1->vval.v_number = tv1->vval.v_number + ? VVAL_TRUE : VVAL_FALSE; + --ectx.ec_stack.ga_len; + } + break; + + case ISN_ADDLIST: + case ISN_ADDBLOB: + { + typval_T *tv1 = STACK_TV_BOT(-2); + typval_T *tv2 = STACK_TV_BOT(-1); + + if (iptr->isn_type == ISN_ADDLIST) + eval_addlist(tv1, tv2); + else + eval_addblob(tv1, tv2); + clear_tv(tv2); + --ectx.ec_stack.ga_len; + } + break; + + // Computation with two arguments of unknown type + case ISN_OPANY: + { + typval_T *tv1 = STACK_TV_BOT(-2); + typval_T *tv2 = STACK_TV_BOT(-1); + varnumber_T n1, n2; + #ifdef FEAT_FLOAT + float_T f1 = 0, f2 = 0; + #endif + int error = FALSE; + + if (iptr->isn_arg.op.op_type == EXPR_ADD) + { + if (tv1->v_type == VAR_LIST && tv2->v_type == VAR_LIST) + { + eval_addlist(tv1, tv2); + clear_tv(tv2); + --ectx.ec_stack.ga_len; + break; + } + else if (tv1->v_type == VAR_BLOB + && tv2->v_type == VAR_BLOB) + { + eval_addblob(tv1, tv2); + clear_tv(tv2); + --ectx.ec_stack.ga_len; + break; + } + } + #ifdef FEAT_FLOAT + if (tv1->v_type == VAR_FLOAT) + { + f1 = tv1->vval.v_float; + n1 = 0; + } + else + #endif + { + n1 = tv_get_number_chk(tv1, &error); + if (error) + goto failed; + #ifdef FEAT_FLOAT + if (tv2->v_type == VAR_FLOAT) + f1 = n1; + #endif + } + #ifdef FEAT_FLOAT + if (tv2->v_type == VAR_FLOAT) + { + f2 = tv2->vval.v_float; + n2 = 0; + } + else + #endif + { + n2 = tv_get_number_chk(tv2, &error); + if (error) + goto failed; + #ifdef FEAT_FLOAT + if (tv1->v_type == VAR_FLOAT) + f2 = n2; + #endif + } + #ifdef FEAT_FLOAT + // if there is a float on either side the result is a float + if (tv1->v_type == VAR_FLOAT || tv2->v_type == VAR_FLOAT) + { + switch (iptr->isn_arg.op.op_type) + { + case EXPR_MULT: f1 = f1 * f2; break; + case EXPR_DIV: f1 = f1 / f2; break; + case EXPR_SUB: f1 = f1 - f2; break; + case EXPR_ADD: f1 = f1 + f2; break; + default: emsg(_(e_modulus)); goto failed; + } + clear_tv(tv1); + clear_tv(tv2); + tv1->v_type = VAR_FLOAT; + tv1->vval.v_float = f1; + --ectx.ec_stack.ga_len; + } + else + #endif + { + switch (iptr->isn_arg.op.op_type) + { + case EXPR_MULT: n1 = n1 * n2; break; + case EXPR_DIV: n1 = num_divide(n1, n2); break; + case EXPR_SUB: n1 = n1 - n2; break; + case EXPR_ADD: n1 = n1 + n2; break; + default: n1 = num_modulus(n1, n2); break; + } + clear_tv(tv1); + clear_tv(tv2); + tv1->v_type = VAR_NUMBER; + tv1->vval.v_number = n1; + --ectx.ec_stack.ga_len; + } + } + break; + + case ISN_CONCAT: + { + char_u *str1 = STACK_TV_BOT(-2)->vval.v_string; + char_u *str2 = STACK_TV_BOT(-1)->vval.v_string; + char_u *res; + + res = concat_str(str1, str2); + clear_tv(STACK_TV_BOT(-2)); + clear_tv(STACK_TV_BOT(-1)); + --ectx.ec_stack.ga_len; + STACK_TV_BOT(-1)->vval.v_string = res; + } + break; + + case ISN_INDEX: + { + list_T *list; + varnumber_T n; + listitem_T *li; + + // list index: list is at stack-2, index at stack-1 + tv = STACK_TV_BOT(-2); + if (tv->v_type != VAR_LIST) + { + emsg(_(e_listreq)); + goto failed; + } + list = tv->vval.v_list; + + tv = STACK_TV_BOT(-1); + if (tv->v_type != VAR_NUMBER) + { + emsg(_(e_number_exp)); + goto failed; + } + n = tv->vval.v_number; + clear_tv(tv); + if ((li = list_find(list, n)) == NULL) + { + semsg(_(e_listidx), n); + goto failed; + } + --ectx.ec_stack.ga_len; + clear_tv(STACK_TV_BOT(-1)); + copy_tv(&li->li_tv, STACK_TV_BOT(-1)); + } + break; + + // dict member with string key + case ISN_MEMBER: + { + dict_T *dict; + dictitem_T *di; + + tv = STACK_TV_BOT(-1); + if (tv->v_type != VAR_DICT || tv->vval.v_dict == NULL) + { + emsg(_(e_dictreq)); + goto failed; + } + dict = tv->vval.v_dict; + + if ((di = dict_find(dict, iptr->isn_arg.string, -1)) + == NULL) + { + semsg(_(e_dictkey), iptr->isn_arg.string); + goto failed; + } + clear_tv(tv); + copy_tv(&di->di_tv, tv); + } + break; + + case ISN_NEGATENR: + tv = STACK_TV_BOT(-1); + tv->vval.v_number = -tv->vval.v_number; + break; + + case ISN_CHECKNR: + { + int error = FALSE; + + tv = STACK_TV_BOT(-1); + if (check_not_string(tv) == FAIL) + { + --ectx.ec_stack.ga_len; + goto failed; + } + (void)tv_get_number_chk(tv, &error); + if (error) + goto failed; + } + break; + + case ISN_CHECKTYPE: + { + checktype_T *ct = &iptr->isn_arg.type; + + tv = STACK_TV_BOT(ct->ct_off); + if (tv->v_type != ct->ct_type) + { + semsg(_("E1029: Expected %s but got %s"), + vartype_name(ct->ct_type), + vartype_name(tv->v_type)); + goto failed; + } + } + break; + + case ISN_2BOOL: + { + int n; + + tv = STACK_TV_BOT(-1); + n = tv2bool(tv); + if (iptr->isn_arg.number) // invert + n = !n; + clear_tv(tv); + tv->v_type = VAR_BOOL; + tv->vval.v_number = n ? VVAL_TRUE : VVAL_FALSE; + } + break; + + case ISN_2STRING: + { + char_u *str; + + tv = STACK_TV_BOT(iptr->isn_arg.number); + if (tv->v_type != VAR_STRING) + { + str = typval_tostring(tv); + clear_tv(tv); + tv->v_type = VAR_STRING; + tv->vval.v_string = str; + } + } + break; + + case ISN_DROP: + --ectx.ec_stack.ga_len; + clear_tv(STACK_TV_BOT(0)); + break; + } + } + + done: + // function finished, get result from the stack. + tv = STACK_TV_BOT(-1); + *rettv = *tv; + tv->v_type = VAR_UNKNOWN; + ret = OK; + + failed: + for (idx = 0; idx < ectx.ec_stack.ga_len; ++idx) + clear_tv(STACK_TV(idx)); + vim_free(ectx.ec_stack.ga_data); + return ret; + } + + #define DISASSEMBLE 1 + + /* + * ":dissassemble". + */ + void + ex_disassemble(exarg_T *eap) + { + #ifdef DISASSEMBLE + ufunc_T *ufunc = find_func(eap->arg, NULL); + dfunc_T *dfunc; + isn_T *instr; + int current; + int line_idx = 0; + int prev_current = 0; + + if (ufunc == NULL) + { + semsg("Cannot find function %s", eap->arg); + return; + } + if (ufunc->uf_dfunc_idx < 0) + { + semsg("Function %s is not compiled", eap->arg); + return; + } + if (ufunc->uf_name_exp != NULL) + msg((char *)ufunc->uf_name_exp); + else + msg((char *)ufunc->uf_name); + + dfunc = ((dfunc_T *)def_functions.ga_data) + ufunc->uf_dfunc_idx; + instr = dfunc->df_instr; + for (current = 0; current < dfunc->df_instr_count; ++current) + { + isn_T *iptr = &instr[current]; + + while (line_idx < iptr->isn_lnum && line_idx < ufunc->uf_lines.ga_len) + { + if (current > prev_current) + { + msg_puts("\n\n"); + prev_current = current; + } + msg(((char **)ufunc->uf_lines.ga_data)[line_idx++]); + } + + switch (iptr->isn_type) + { + case ISN_EXEC: + smsg("%4d EXEC %s", current, iptr->isn_arg.string); + break; + case ISN_ECHO: + { + echo_T *echo = &iptr->isn_arg.echo; + + smsg("%4d %s %d", current, + echo->echo_with_white ? "ECHO" : "ECHON", + echo->echo_count); + } + break; + case ISN_LOAD: + if (iptr->isn_arg.number < 0) + smsg("%4d LOAD arg[%lld]", current, + iptr->isn_arg.number + STACK_FRAME_SIZE); + else + smsg("%4d LOAD $%lld", current, iptr->isn_arg.number); + break; + case ISN_LOADV: + smsg("%4d LOADV v:%s", current, + get_vim_var_name(iptr->isn_arg.number)); + break; + case ISN_LOADSCRIPT: + { + scriptitem_T *si = + &SCRIPT_ITEM(iptr->isn_arg.script.script_sid); + svar_T *sv = ((svar_T *)si->sn_var_vals.ga_data) + + iptr->isn_arg.script.script_idx; + + smsg("%4d LOADSCRIPT %s from %s", current, + sv->sv_name, si->sn_name); + } + break; + case ISN_LOADS: + { + scriptitem_T *si = &SCRIPT_ITEM(iptr->isn_arg.loads.ls_sid); + + smsg("%4d LOADS s:%s from %s", current, + iptr->isn_arg.string, si->sn_name); + } + break; + case ISN_LOADG: + smsg("%4d LOADG g:%s", current, iptr->isn_arg.string); + break; + case ISN_LOADOPT: + smsg("%4d LOADOPT %s", current, iptr->isn_arg.string); + break; + case ISN_LOADENV: + smsg("%4d LOADENV %s", current, iptr->isn_arg.string); + break; + case ISN_LOADREG: + smsg("%4d LOADREG @%c", current, iptr->isn_arg.number); + break; + + case ISN_STORE: + smsg("%4d STORE $%lld", current, iptr->isn_arg.number); + break; + case ISN_STOREG: + smsg("%4d STOREG g:%s", current, iptr->isn_arg.string); + break; + case ISN_STORESCRIPT: + { + scriptitem_T *si = + &SCRIPT_ITEM(iptr->isn_arg.script.script_sid); + svar_T *sv = ((svar_T *)si->sn_var_vals.ga_data) + + iptr->isn_arg.script.script_idx; + + smsg("%4d STORESCRIPT %s in %s", current, + sv->sv_name, si->sn_name); + } + break; + case ISN_STOREOPT: + smsg("%4d STOREOPT &%s", current, + iptr->isn_arg.storeopt.so_name); + break; + + case ISN_STORENR: + smsg("%4d STORE %lld in $%d", current, + iptr->isn_arg.storenr.str_val, + iptr->isn_arg.storenr.str_idx); + break; + + // constants + case ISN_PUSHNR: + smsg("%4d PUSHNR %lld", current, iptr->isn_arg.number); + break; + case ISN_PUSHBOOL: + case ISN_PUSHSPEC: + smsg("%4d PUSH %s", current, + get_var_special_name(iptr->isn_arg.number)); + break; + case ISN_PUSHF: + smsg("%4d PUSHF %g", current, iptr->isn_arg.fnumber); + break; + case ISN_PUSHS: + smsg("%4d PUSHS \"%s\"", current, iptr->isn_arg.string); + break; + case ISN_PUSHBLOB: + { + char_u *r; + char_u numbuf[NUMBUFLEN]; + char_u *tofree; + + r = blob2string(iptr->isn_arg.blob, &tofree, numbuf); + smsg("%4d PUSHBLOB \"%s\"", current, r); + vim_free(tofree); + } + break; + case ISN_PUSHEXC: + smsg("%4d PUSH v:exception", current); + break; + case ISN_NEWLIST: + smsg("%4d NEWLIST size %lld", current, iptr->isn_arg.number); + break; + case ISN_NEWDICT: + smsg("%4d NEWDICT size %lld", current, iptr->isn_arg.number); + break; + + // function call + case ISN_BCALL: + { + cbfunc_T *cbfunc = &iptr->isn_arg.bfunc; + + smsg("%4d BCALL %s(argc %d)", current, + internal_func_name(cbfunc->cbf_idx), + cbfunc->cbf_argcount); + } + break; + case ISN_DCALL: + { + cdfunc_T *cdfunc = &iptr->isn_arg.dfunc; + dfunc_T *df = ((dfunc_T *)def_functions.ga_data) + + cdfunc->cdf_idx; + + smsg("%4d DCALL %s(argc %d)", current, + df->df_ufunc->uf_name_exp != NULL + ? df->df_ufunc->uf_name_exp + : df->df_ufunc->uf_name, cdfunc->cdf_argcount); + } + break; + case ISN_UCALL: + { + cufunc_T *cufunc = &iptr->isn_arg.ufunc; + + smsg("%4d UCALL %s(argc %d)", current, + cufunc->cuf_name, cufunc->cuf_argcount); + } + break; + case ISN_PCALL: + { + cpfunc_T *cpfunc = &iptr->isn_arg.pfunc; + + smsg("%4d PCALL%s (argc %d)", current, + cpfunc->cpf_top ? " top" : "", cpfunc->cpf_argcount); + } + break; + case ISN_RETURN: + smsg("%4d RETURN", current); + break; + case ISN_FUNCREF: + { + dfunc_T *df = ((dfunc_T *)def_functions.ga_data) + + iptr->isn_arg.number; + + smsg("%4d FUNCREF %s", current, df->df_ufunc->uf_name); + } + break; + + case ISN_JUMP: + { + char *when = "?"; + + switch (iptr->isn_arg.jump.jump_when) + { + case JUMP_ALWAYS: + when = "JUMP"; + break; + case JUMP_IF_TRUE: + when = "JUMP_IF_TRUE"; + break; + case JUMP_AND_KEEP_IF_TRUE: + when = "JUMP_AND_KEEP_IF_TRUE"; + break; + case JUMP_IF_FALSE: + when = "JUMP_IF_FALSE"; + break; + case JUMP_AND_KEEP_IF_FALSE: + when = "JUMP_AND_KEEP_IF_FALSE"; + break; + } + smsg("%4d %s -> %lld", current, when, + iptr->isn_arg.jump.jump_where); + } + break; + + case ISN_FOR: + { + forloop_T *forloop = &iptr->isn_arg.forloop; + + smsg("%4d FOR $%d -> %d", current, + forloop->for_idx, forloop->for_end); + } + break; + + case ISN_TRY: + { + try_T *try = &iptr->isn_arg.try; + + smsg("%4d TRY catch -> %d, finally -> %d", current, + try->try_catch, try->try_finally); + } + break; + case ISN_CATCH: + // TODO + smsg("%4d CATCH", current); + break; + case ISN_ENDTRY: + smsg("%4d ENDTRY", current); + break; + case ISN_THROW: + smsg("%4d THROW", current); + break; + + // expression operations on number + case ISN_OPNR: + case ISN_OPFLOAT: + case ISN_OPANY: + { + char *what; + char *ins; + + switch (iptr->isn_arg.op.op_type) + { + case EXPR_MULT: what = "*"; break; + case EXPR_DIV: what = "/"; break; + case EXPR_REM: what = "%"; break; + case EXPR_SUB: what = "-"; break; + case EXPR_ADD: what = "+"; break; + default: what = "???"; break; + } + switch (iptr->isn_type) + { + case ISN_OPNR: ins = "OPNR"; break; + case ISN_OPFLOAT: ins = "OPFLOAT"; break; + case ISN_OPANY: ins = "OPANY"; break; + default: ins = "???"; break; + } + smsg("%4d %s %s", current, ins, what); + } + break; + + case ISN_COMPAREBOOL: + case ISN_COMPARESPECIAL: + case ISN_COMPARENR: + case ISN_COMPAREFLOAT: + case ISN_COMPARESTRING: + case ISN_COMPAREBLOB: + case ISN_COMPARELIST: + case ISN_COMPAREDICT: + case ISN_COMPAREFUNC: + case ISN_COMPAREPARTIAL: + case ISN_COMPAREANY: + { + char *p; + char buf[10]; + char *type; + + switch (iptr->isn_arg.op.op_type) + { + case EXPR_EQUAL: p = "=="; break; + case EXPR_NEQUAL: p = "!="; break; + case EXPR_GREATER: p = ">"; break; + case EXPR_GEQUAL: p = ">="; break; + case EXPR_SMALLER: p = "<"; break; + case EXPR_SEQUAL: p = "<="; break; + case EXPR_MATCH: p = "=~"; break; + case EXPR_IS: p = "is"; break; + case EXPR_ISNOT: p = "isnot"; break; + case EXPR_NOMATCH: p = "!~"; break; + default: p = "???"; break; + } + STRCPY(buf, p); + if (iptr->isn_arg.op.op_ic == TRUE) + strcat(buf, "?"); + switch(iptr->isn_type) + { + case ISN_COMPAREBOOL: type = "COMPAREBOOL"; break; + case ISN_COMPARESPECIAL: + type = "COMPARESPECIAL"; break; + case ISN_COMPARENR: type = "COMPARENR"; break; + case ISN_COMPAREFLOAT: type = "COMPAREFLOAT"; break; + case ISN_COMPARESTRING: + type = "COMPARESTRING"; break; + case ISN_COMPAREBLOB: type = "COMPAREBLOB"; break; + case ISN_COMPARELIST: type = "COMPARELIST"; break; + case ISN_COMPAREDICT: type = "COMPAREDICT"; break; + case ISN_COMPAREFUNC: type = "COMPAREFUNC"; break; + case ISN_COMPAREPARTIAL: + type = "COMPAREPARTIAL"; break; + case ISN_COMPAREANY: type = "COMPAREANY"; break; + default: type = "???"; break; + } + + smsg("%4d %s %s", current, type, buf); + } + break; + + case ISN_ADDLIST: smsg("%4d ADDLIST", current); break; + case ISN_ADDBLOB: smsg("%4d ADDBLOB", current); break; + + // expression operations + case ISN_CONCAT: smsg("%4d CONCAT", current); break; + case ISN_INDEX: smsg("%4d INDEX", current); break; + case ISN_MEMBER: smsg("%4d MEMBER %s", current, + iptr->isn_arg.string); break; + case ISN_NEGATENR: smsg("%4d NEGATENR", current); break; + + case ISN_CHECKNR: smsg("%4d CHECKNR", current); break; + case ISN_CHECKTYPE: smsg("%4d CHECKTYPE %s stack[%d]", current, + vartype_name(iptr->isn_arg.type.ct_type), + iptr->isn_arg.type.ct_off); + break; + case ISN_2BOOL: if (iptr->isn_arg.number) + smsg("%4d INVERT (!val)", current); + else + smsg("%4d 2BOOL (!!val)", current); + break; + case ISN_2STRING: smsg("%4d 2STRING stack[%d]", current, + iptr->isn_arg.number); + break; + + case ISN_DROP: smsg("%4d DROP", current); break; + } + } + #endif + } + + /* + * Return TRUE when "tv" is not falsey: non-zero, non-empty string, non-empty + * list, etc. Mostly like what JavaScript does, except that empty list and + * empty dictionary are FALSE. + */ + int + tv2bool(typval_T *tv) + { + switch (tv->v_type) + { + case VAR_NUMBER: + return tv->vval.v_number != 0; + case VAR_FLOAT: + #ifdef FEAT_FLOAT + return tv->vval.v_float != 0.0; + #else + break; + #endif + case VAR_PARTIAL: + return tv->vval.v_partial != NULL; + case VAR_FUNC: + case VAR_STRING: + return tv->vval.v_string != NULL && *tv->vval.v_string != NUL; + case VAR_LIST: + return tv->vval.v_list != NULL && tv->vval.v_list->lv_len > 0; + case VAR_DICT: + return tv->vval.v_dict != NULL + && tv->vval.v_dict->dv_hashtab.ht_used > 0; + case VAR_BOOL: + case VAR_SPECIAL: + return tv->vval.v_number == VVAL_TRUE ? TRUE : FALSE; + case VAR_JOB: + #ifdef FEAT_JOB_CHANNEL + return tv->vval.v_job != NULL; + #else + break; + #endif + case VAR_CHANNEL: + #ifdef FEAT_JOB_CHANNEL + return tv->vval.v_channel != NULL; + #else + break; + #endif + case VAR_BLOB: + return tv->vval.v_blob != NULL && tv->vval.v_blob->bv_ga.ga_len > 0; + case VAR_UNKNOWN: + case VAR_VOID: + break; + } + return FALSE; + } + + /* + * If "tv" is a string give an error and return FAIL. + */ + int + check_not_string(typval_T *tv) + { + if (tv->v_type == VAR_STRING) + { + emsg(_("E1030: Using a String as a Number")); + clear_tv(tv); + return FAIL; + } + return OK; + } + + + #endif // FEAT_EVAL *** ../vim-8.2.0148/src/vim9script.c 2020-01-26 15:45:56.757682069 +0100 --- src/vim9script.c 2020-01-26 15:30:08.625766579 +0100 *************** *** 0 **** --- 1,405 ---- + /* vi:set ts=8 sts=4 sw=4 noet: + * + * VIM - Vi IMproved by Bram Moolenaar + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + * See README.txt for an overview of the Vim source code. + */ + + /* + * vim9script.c: :vim9script, :import, :export and friends + */ + + #include "vim.h" + + #if defined(FEAT_EVAL) || defined(PROTO) + + #include "vim9.h" + + static char e_needs_vim9[] = N_("E1042: import/export can only be used in vim9script"); + + int + in_vim9script(void) + { + // TODO: go up the stack? + return current_sctx.sc_version == SCRIPT_VERSION_VIM9; + } + + /* + * ":vim9script". + */ + void + ex_vim9script(exarg_T *eap) + { + scriptitem_T *si = &SCRIPT_ITEM(current_sctx.sc_sid); + + if (!getline_equal(eap->getline, eap->cookie, getsourceline)) + { + emsg(_("E1038: vim9script can only be used in a script")); + return; + } + if (si->sn_had_command) + { + emsg(_("E1039: vim9script must be the first command in a script")); + return; + } + current_sctx.sc_version = SCRIPT_VERSION_VIM9; + si->sn_version = SCRIPT_VERSION_VIM9; + si->sn_had_command = TRUE; + + if (STRCMP(p_cpo, CPO_VIM) != 0) + { + si->sn_save_cpo = p_cpo; + p_cpo = vim_strsave((char_u *)CPO_VIM); + } + } + + /* + * ":export let Name: type" + * ":export const Name: type" + * ":export def Name(..." + * ":export class Name ..." + * + * ":export {Name, ...}" + */ + void + ex_export(exarg_T *eap UNUSED) + { + if (current_sctx.sc_version != SCRIPT_VERSION_VIM9) + { + emsg(_(e_needs_vim9)); + return; + } + + eap->cmd = eap->arg; + (void)find_ex_command(eap, NULL, lookup_scriptvar, NULL); + switch (eap->cmdidx) + { + case CMD_let: + case CMD_const: + case CMD_def: + // case CMD_class: + is_export = TRUE; + do_cmdline(eap->cmd, eap->getline, eap->cookie, + DOCMD_VERBOSE + DOCMD_NOWAIT); + + // The command will reset "is_export" when exporting an item. + if (is_export) + { + emsg(_("E1044: export with invalid argument")); + is_export = FALSE; + } + break; + default: + emsg(_("E1043: Invalid command after :export")); + break; + } + } + + /* + * Add a new imported item entry to the current script. + */ + static imported_T * + new_imported(garray_T *gap) + { + if (ga_grow(gap, 1) == OK) + return ((imported_T *)gap->ga_data + gap->ga_len++); + return NULL; + } + + /* + * Free all imported items in script "sid". + */ + void + free_imports(int sid) + { + scriptitem_T *si = &SCRIPT_ITEM(sid); + int idx; + + for (idx = 0; idx < si->sn_imports.ga_len; ++idx) + { + imported_T *imp = ((imported_T *)si->sn_imports.ga_data + idx); + + vim_free(imp->imp_name); + } + ga_clear(&si->sn_imports); + } + + /* + * ":import Item from 'filename'" + * ":import Item as Alias from 'filename'" + * ":import {Item} from 'filename'". + * ":import {Item as Alias} from 'filename'" + * ":import {Item, Item} from 'filename'" + * ":import {Item, Item as Alias} from 'filename'" + * + * ":import * as Name from 'filename'" + */ + void + ex_import(exarg_T *eap) + { + if (current_sctx.sc_version != SCRIPT_VERSION_VIM9) + emsg(_(e_needs_vim9)); + else + { + char_u *cmd_end = handle_import(eap->arg, NULL, current_sctx.sc_sid); + + if (cmd_end != NULL) + eap->nextcmd = check_nextcmd(cmd_end); + } + } + + /* + * Handle an ":import" command and add the resulting imported_T to "gap", when + * not NULL, or script "import_sid" sn_imports. + * Returns a pointer to after the command or NULL in case of failure + */ + char_u * + handle_import(char_u *arg_start, garray_T *gap, int import_sid) + { + char_u *arg = arg_start; + char_u *cmd_end; + char_u *as_ptr = NULL; + char_u *from_ptr; + int as_len = 0; + int ret = FAIL; + typval_T tv; + int sid = -1; + int res; + + if (*arg == '{') + { + // skip over {item} list + while (*arg != NUL && *arg != '}') + ++arg; + if (*arg == '}') + arg = skipwhite(arg + 1); + } + else + { + if (*arg == '*') + arg = skipwhite(arg + 1); + else + { + while (eval_isnamec1(*arg)) + ++arg; + arg = skipwhite(arg); + } + if (STRNCMP("as", arg, 2) == 0 && VIM_ISWHITE(arg[2])) + { + // skip over "as Name " + arg = skipwhite(arg + 2); + as_ptr = arg; + while (eval_isnamec1(*arg)) + ++arg; + as_len = (int)(arg - as_ptr); + arg = skipwhite(arg); + } + else if (*arg_start == '*') + { + emsg(_("E1045: Missing \"as\" after *")); + return NULL; + } + } + if (STRNCMP("from", arg, 4) != 0 || !VIM_ISWHITE(arg[4])) + { + emsg(_("E1045: Missing \"from\"")); + return NULL; + } + from_ptr = arg; + arg = skipwhite(arg + 4); + tv.v_type = VAR_UNKNOWN; + // TODO: should we accept any expression? + if (*arg == '\'') + ret = get_lit_string_tv(&arg, &tv, TRUE); + else if (*arg == '"') + ret = get_string_tv(&arg, &tv, TRUE); + if (ret == FAIL || tv.vval.v_string == NULL || *tv.vval.v_string == NUL) + { + emsg(_("E1045: Invalid string after \"from\"")); + return NULL; + } + cmd_end = arg; + + // find script tv.vval.v_string + if (*tv.vval.v_string == '.') + { + size_t len; + scriptitem_T *si = &SCRIPT_ITEM(current_sctx.sc_sid); + char_u *tail = gettail(si->sn_name); + char_u *from_name; + + // Relative to current script: "./name.vim", "../../name.vim". + len = STRLEN(si->sn_name) - STRLEN(tail) + STRLEN(tv.vval.v_string) + 2; + from_name = alloc((int)len); + if (from_name == NULL) + { + clear_tv(&tv); + return NULL; + } + vim_strncpy(from_name, si->sn_name, tail - si->sn_name); + add_pathsep(from_name); + STRCAT(from_name, tv.vval.v_string); + simplify_filename(from_name); + + res = do_source(from_name, FALSE, DOSO_NONE, &sid); + vim_free(from_name); + } + else if (mch_isFullName(tv.vval.v_string)) + { + // Absolute path: "/tmp/name.vim" + res = do_source(tv.vval.v_string, FALSE, DOSO_NONE, &sid); + } + else + { + size_t len = 7 + STRLEN(tv.vval.v_string) + 1; + char_u *from_name; + + // Find file in "import" subdirs in 'runtimepath'. + from_name = alloc((int)len); + if (from_name == NULL) + { + clear_tv(&tv); + return NULL; + } + vim_snprintf((char *)from_name, len, "import/%s", tv.vval.v_string); + res = source_in_path(p_rtp, from_name, DIP_NOAFTER, &sid); + vim_free(from_name); + } + + if (res == FAIL || sid <= 0) + { + semsg(_("E1053: Could not import \"%s\""), tv.vval.v_string); + clear_tv(&tv); + return NULL; + } + clear_tv(&tv); + + if (*arg_start == '*') + { + imported_T *imported = new_imported(gap != NULL ? gap + : &SCRIPT_ITEM(import_sid).sn_imports); + + if (imported == NULL) + return NULL; + imported->imp_name = vim_strnsave(as_ptr, as_len); + imported->imp_sid = sid; + imported->imp_all = TRUE; + } + else + { + scriptitem_T *script = &SCRIPT_ITEM(sid); + + arg = arg_start; + if (*arg == '{') + arg = skipwhite(arg + 1); + for (;;) + { + char_u *name = arg; + int name_len; + int cc; + int idx; + svar_T *sv; + imported_T *imported; + ufunc_T *ufunc; + + // isolate one name + while (eval_isnamec1(*arg)) + ++arg; + name_len = (int)(arg - name); + + // find name in "script" + // TODO: also find script-local user function + cc = *arg; + *arg = NUL; + idx = get_script_item_idx(sid, name, FALSE); + if (idx >= 0) + { + sv = ((svar_T *)script->sn_var_vals.ga_data) + idx; + if (!sv->sv_export) + { + semsg(_("E1049: Item not exported in script: %s"), name); + *arg = cc; + return NULL; + } + } + else + { + char_u buffer[200]; + char_u *funcname; + + // it could be a user function. + if (STRLEN(name) < sizeof(buffer) - 10) + funcname = buffer; + else + { + funcname = alloc(STRLEN(name) + 10); + if (funcname == NULL) + { + *arg = cc; + return NULL; + } + } + funcname[0] = K_SPECIAL; + funcname[1] = KS_EXTRA; + funcname[2] = (int)KE_SNR; + sprintf((char *)funcname + 3, "%ld_%s", (long)sid, name); + ufunc = find_func(funcname, NULL); + if (funcname != buffer) + vim_free(funcname); + + if (ufunc == NULL) + { + semsg(_("E1048: Item not found in script: %s"), name); + *arg = cc; + return NULL; + } + } + + imported = new_imported(gap != NULL ? gap + : &SCRIPT_ITEM(import_sid).sn_imports); + if (imported == NULL) + return NULL; + + *arg = cc; + arg = skipwhite(arg); + + // TODO: check for "as" following + // imported->imp_name = vim_strnsave(as_ptr, as_len); + imported->imp_name = vim_strnsave(name, name_len); + imported->imp_sid = sid; + if (idx >= 0) + { + imported->imp_type = sv->sv_type; + imported->imp_var_vals_idx = idx; + } + else + imported->imp_funcname = ufunc->uf_name; + + arg = skipwhite(arg); + if (*arg_start != '{') + break; + if (*arg == '}') + { + arg = skipwhite(arg + 1); + break; + } + + if (*arg != ',') + { + emsg(_("E1046: Missing comma in import")); + return NULL; + } + arg = skipwhite(arg + 1); + } + if (arg != from_ptr) + { + emsg(_("E1047: syntax error in import")); + return NULL; + } + } + return cmd_end; + } + + #endif // FEAT_EVAL *** ../vim-8.2.0148/src/viminfo.c 2020-01-11 16:05:19.594287610 +0100 --- src/viminfo.c 2020-01-26 15:30:08.625766579 +0100 *************** *** 1327,1332 **** --- 1327,1333 ---- case VAR_SPECIAL: s = "XPL"; break; case VAR_UNKNOWN: + case VAR_VOID: case VAR_FUNC: case VAR_PARTIAL: case VAR_JOB: *** ../vim-8.2.0148/src/version.c 2020-01-24 20:21:15.389142318 +0100 --- src/version.c 2020-01-26 15:44:57.241971357 +0100 *************** *** 744,745 **** --- 744,747 ---- { /* Add new patch number below this line */ + /**/ + 149, /**/ -- If the Universe is constantly expanding, why can't I ever find a parking space? /// 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 ///