summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md2
-rw-r--r--README_VIM9.md344
-rw-r--r--runtime/doc/Makefile2
-rw-r--r--runtime/doc/eval.txt11
-rw-r--r--runtime/doc/options.txt1
-rw-r--r--runtime/doc/vim9.txt561
-rw-r--r--runtime/ftplugin/vim.vim2
-rw-r--r--runtime/indent/vim.vim8
-rw-r--r--runtime/syntax/vim.vim8
-rw-r--r--src/Make_cyg_ming.mak9
-rw-r--r--src/Make_mvc.mak12
-rw-r--r--src/Makefile72
-rw-r--r--src/blob.c8
-rw-r--r--src/channel.c5
-rw-r--r--src/dict.c8
-rw-r--r--src/eval.c498
-rw-r--r--src/evalbuffer.c15
-rw-r--r--src/evalfunc.c1109
-rw-r--r--src/evalvars.c320
-rw-r--r--src/ex_cmdidxs.h54
-rw-r--r--src/ex_cmds.h18
-rw-r--r--src/ex_docmd.c85
-rw-r--r--src/ex_eval.c23
-rw-r--r--src/filepath.c1
-rw-r--r--src/globals.h97
-rw-r--r--src/gui.c19
-rw-r--r--src/if_lua.c3
-rw-r--r--src/if_py_both.h9
-rw-r--r--src/insexpand.c4
-rw-r--r--src/json.c3
-rw-r--r--src/list.c179
-rw-r--r--src/macros.h4
-rw-r--r--src/main.c37
-rw-r--r--src/message.c11
-rw-r--r--src/misc1.c12
-rw-r--r--src/proto.h5
-rw-r--r--src/proto/blob.pro2
-rw-r--r--src/proto/eval.pro7
-rw-r--r--src/proto/evalfunc.pro6
-rw-r--r--src/proto/evalvars.pro11
-rw-r--r--src/proto/ex_docmd.pro1
-rw-r--r--src/proto/ex_eval.pro2
-rw-r--r--src/proto/list.pro8
-rw-r--r--src/proto/message.pro1
-rw-r--r--src/proto/scriptfile.pro4
-rw-r--r--src/proto/userfunc.pro8
-rw-r--r--src/proto/vim9compile.pro14
-rw-r--r--src/proto/vim9execute.pro6
-rw-r--r--src/proto/vim9script.pro8
-rw-r--r--src/scriptfile.c125
-rw-r--r--src/session.c2
-rw-r--r--src/structs.h135
-rw-r--r--src/syntax.c2
-rw-r--r--src/testdir/Make_all.mak6
-rw-r--r--src/testdir/test_vim9_expr.vim734
-rw-r--r--src/testdir/test_vim9_script.vim359
-rw-r--r--src/testing.c5
-rw-r--r--src/userfunc.c917
-rw-r--r--src/version.c2
-rw-r--r--src/vim.h4
-rw-r--r--src/vim9.h252
-rw-r--r--src/vim9compile.c4612
-rw-r--r--src/vim9execute.c1934
-rw-r--r--src/vim9script.c405
-rw-r--r--src/viminfo.c1
65 files changed, 11791 insertions, 1341 deletions
diff --git a/README.md b/README.md
index c87a26558..4e3d8433c 100644
--- a/README.md
+++ b/README.md
@@ -31,6 +31,8 @@ flavours of UNIX. Porting to other systems should not be very difficult.
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
diff --git a/README_VIM9.md b/README_VIM9.md
new file mode 100644
index 000000000..b77d013c0
--- /dev/null
+++ b/README_VIM9.md
@@ -0,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!
+```
diff --git a/runtime/doc/Makefile b/runtime/doc/Makefile
index 514a01237..65dc8436c 100644
--- a/runtime/doc/Makefile
+++ b/runtime/doc/Makefile
@@ -149,6 +149,7 @@ DOCS = \
version7.txt \
version8.txt \
vi_diff.txt \
+ vim9.txt \
visual.txt \
windows.txt \
workshop.txt
@@ -289,6 +290,7 @@ HTMLS = \
version8.html \
vi_diff.html \
vimindex.html \
+ vim9.html \
visual.html \
windows.html \
workshop.html
diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt
index b580f2fab..071f25080 100644
--- a/runtime/doc/eval.txt
+++ b/runtime/doc/eval.txt
@@ -12,6 +12,10 @@ Note: Expression evaluation can be disabled at compile time. If this has been
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,8 +2516,8 @@ haslocaldir([{winnr} [, {tabnr}]])
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
+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
@@ -10894,6 +10898,9 @@ New functions can be defined. These can be called just like builtin
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
diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt
index 876ddf358..847290e29 100644
--- a/runtime/doc/options.txt
+++ b/runtime/doc/options.txt
@@ -6221,6 +6221,7 @@ A jump table for the options with a short description can be found at |Q_op|.
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|
diff --git a/runtime/doc/vim9.txt b/runtime/doc/vim9.txt
new file mode 100644
index 000000000..c6c674da8
--- /dev/null
+++ b/runtime/doc/vim9.txt
@@ -0,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<type>)
+ 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<type>
+ dict<type>
+ (a: type, b: type): type
+ job
+ channel
+
+Not supported yet:
+ tuple<a: type, b: type, ...>
+
+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<type> 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<string>
+{not implemented yet}
+
+And classes and interfaces can be used as types: >
+ :class MyClass
+ :let mine: MyClass
+
+ :interface MyInterface
+ :let mine: MyInterface
+
+ :class MyTemplate<Targ>
+ :let mine: MyTemplate<number>
+ :let mine: MyTemplate<string>
+
+ :class MyInterface<Targ>
+ :let mine: MyInterface<number>
+ :let mine: MyInterface<string>
+{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(<f-args>)
+
+< 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:
diff --git a/runtime/ftplugin/vim.vim b/runtime/ftplugin/vim.vim
index c1cd8bb62..751a03304 100644
--- a/runtime/ftplugin/vim.vim
+++ b/runtime/ftplugin/vim.vim
@@ -84,8 +84,10 @@ if exists("loaded_matchit")
let b:match_ignorecase = 0
let b:match_words =
\ '\<fu\%[nction]\>:\<retu\%[rn]\>:\<endf\%[unction]\>,' .
+ \ '\<def\>:\<retu\%[rn]\>:\<enddef\>,' .
\ '\<\(wh\%[ile]\|for\)\>:\<brea\%[k]\>:\<con\%[tinue]\>:\<end\(w\%[hile]\|fo\%[r]\)\>,' .
\ '\<if\>:\<el\%[seif]\>:\<en\%[dif]\>,' .
+ \ '{:},' .
\ '\<try\>:\<cat\%[ch]\>:\<fina\%[lly]\>:\<endt\%[ry]\>,' .
\ '\<aug\%[roup]\s\+\%(END\>\)\@!\S:\<aug\%[roup]\s\+END\>,'
" Ignore syntax region commands and settings, any 'en*' would clobber
diff --git a/runtime/indent/vim.vim b/runtime/indent/vim.vim
index db27f1971..b0c0a3916 100644
--- a/runtime/indent/vim.vim
+++ b/runtime/indent/vim.vim
@@ -10,7 +10,7 @@ endif
let b:did_indent = 1
setlocal indentexpr=GetVimIndent()
-setlocal indentkeys+==end,=else,=cat,=fina,=END,0\\,0=\"\\\
+setlocal indentkeys+==end,=},=else,=cat,=fina,=END,0\\,0=\"\\\
let b:undo_indent = "setl indentkeys< indentexpr<"
@@ -92,7 +92,7 @@ function GetVimIndentIntern()
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]\)\>')
+ 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,8 +115,8 @@ function GetVimIndentIntern()
" 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]\)'
+ " :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
diff --git a/runtime/syntax/vim.vim b/runtime/syntax/vim.vim
index b7988178b..5289f723e 100644
--- a/runtime/syntax/vim.vim
+++ b/runtime/syntax/vim.vim
@@ -26,6 +26,7 @@ syn keyword vimCommand contained abo[veleft] argdo au bel[owright] bp[revious] b
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 "\<z[-+^.=]\=\>"
+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,16 +238,17 @@ endif
" =========
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 "\<fu\%[nction]!\=\s\+\%(<[sS][iI][dD]>\|[sSgGbBwWtTlL]:\)\=\%(\i\|[#.]\|{.\{-1,}}\)*\ze\s*(" contains=@vimFuncList nextgroup=vimFuncBody
+syn match vimFunction "\<\(fu\%[nction]\|def\)!\=\s\+\%(<[sS][iI][dD]>\|[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
+ 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]\>\)" contains=@vimFuncBodyList
+ 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<sid>\|\<s:"
syn keyword vimFuncKey contained fu[nction]
+syn keyword vimFuncKey contained def
syn match vimFuncBlank contained "\s\+"
syn keyword vimPattern contained start skip end
diff --git a/src/Make_cyg_ming.mak b/src/Make_cyg_ming.mak
index f89d8dd36..639f3e5dc 100644
--- a/src/Make_cyg_ming.mak
+++ b/src/Make_cyg_ming.mak
@@ -788,6 +788,9 @@ OBJ = \
$(OUTDIR)/usercmd.o \
$(OUTDIR)/userfunc.o \
$(OUTDIR)/version.o \
+ $(OUTDIR)/vim9compile.o \
+ $(OUTDIR)/vim9execute.o \
+ $(OUTDIR)/vim9script.o \
$(OUTDIR)/viminfo.o \
$(OUTDIR)/winclip.o \
$(OUTDIR)/window.o
@@ -1153,6 +1156,12 @@ $(OUTDIR)/netbeans.o: netbeans.c $(INCL) version.h
$(OUTDIR)/version.o: version.c $(INCL) version.h
+$(OUTDIR)/vim9compile.o: vim9compile.c $(INCL) version.h
+
+$(OUTDIR)/vim9execute.o: vim9execute.c $(INCL) version.h
+
+$(OUTDIR)/vim9script.o: vim9script.c $(INCL) version.h
+
$(OUTDIR)/viminfo.o: viminfo.c $(INCL) version.h
$(OUTDIR)/gui_dwrite.o: gui_dwrite.cpp gui_dwrite.h
diff --git a/src/Make_mvc.mak b/src/Make_mvc.mak
index 87b716952..3e8eae168 100644
--- a/src/Make_mvc.mak
+++ b/src/Make_mvc.mak
@@ -791,6 +791,9 @@ OBJ = \
$(OUTDIR)\undo.obj \
$(OUTDIR)\usercmd.obj \
$(OUTDIR)\userfunc.obj \
+ $(OUTDIR)\vim9compile.obj \
+ $(OUTDIR)\vim9execute.obj \
+ $(OUTDIR)\vim9script.obj \
$(OUTDIR)\viminfo.obj \
$(OUTDIR)\winclip.obj \
$(OUTDIR)\window.obj \
@@ -1726,6 +1729,12 @@ $(OUTDIR)/userfunc.obj: $(OUTDIR) userfunc.c $(INCL)
$(OUTDIR)/version.obj: $(OUTDIR) version.c $(INCL) version.h
+$(OUTDIR)/vim9compile.obj: $(OUTDIR) vim9compile.c $(INCL)
+
+$(OUTDIR)/vim9execute.obj: $(OUTDIR) vim9execute.c $(INCL)
+
+$(OUTDIR)/vim9script.obj: $(OUTDIR) vim9script.c $(INCL)
+
$(OUTDIR)/viminfo.obj: $(OUTDIR) viminfo.c $(INCL) version.h
$(OUTDIR)/window.obj: $(OUTDIR) window.c $(INCL)
@@ -1907,6 +1916,9 @@ proto.h: \
proto/undo.pro \
proto/usercmd.pro \
proto/userfunc.pro \
+ proto/vim9compile.pro \
+ proto/vim9execute.pro \
+ proto/vim9script.pro \
proto/viminfo.pro \
proto/window.pro \
$(SOUND_PRO) \
diff --git a/src/Makefile b/src/Makefile
index 08a327754..912098f8b 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -1623,6 +1623,7 @@ BASIC_SRC = \
main.c \
map.c \
mark.c \
+ mbyte.c \
memfile.c \
memline.c \
menu.c \
@@ -1631,7 +1632,6 @@ BASIC_SRC = \
misc2.c \
mouse.c \
move.c \
- mbyte.c \
normal.c \
ops.c \
option.c \
@@ -1645,8 +1645,8 @@ BASIC_SRC = \
quickfix.c \
regexp.c \
register.c \
- scriptfile.c \
screen.c \
+ scriptfile.c \
search.c \
session.c \
sha256.c \
@@ -1666,6 +1666,9 @@ BASIC_SRC = \
usercmd.c \
userfunc.c \
version.c \
+ vim9compile.c \
+ vim9execute.c \
+ vim9script.c \
viminfo.c \
window.c \
bufwrite.c \
@@ -1761,13 +1764,13 @@ OBJ_COMMON = \
objects/list.o \
objects/map.o \
objects/mark.o \
+ objects/mbyte.o \
objects/memline.o \
objects/menu.o \
objects/misc1.o \
objects/misc2.o \
objects/mouse.o \
objects/move.o \
- objects/mbyte.o \
objects/normal.o \
objects/ops.o \
objects/option.o \
@@ -1781,8 +1784,8 @@ OBJ_COMMON = \
objects/quickfix.o \
objects/regexp.o \
objects/register.o \
- objects/scriptfile.o \
objects/screen.o \
+ objects/scriptfile.o \
objects/search.o \
objects/session.o \
objects/sha256.o \
@@ -1802,6 +1805,9 @@ OBJ_COMMON = \
objects/usercmd.o \
objects/userfunc.o \
objects/version.o \
+ objects/vim9compile.o \
+ objects/vim9execute.o \
+ objects/vim9script.o \
objects/viminfo.o \
objects/window.o \
objects/bufwrite.o \
@@ -1873,9 +1879,12 @@ PRO_AUTO = \
arabic.pro \
arglist.pro \
autocmd.pro \
+ beval.pro \
blowfish.pro \
buffer.pro \
+ bufwrite.pro \
change.pro \
+ channel.pro \
charset.pro \
cindent.pro \
cmdexpand.pro \
@@ -1904,6 +1913,7 @@ PRO_AUTO = \
findfile.pro \
fold.pro \
getchar.pro \
+ gui_beval.pro \
hardcopy.pro \
hashtab.pro \
highlight.pro \
@@ -1930,6 +1940,7 @@ PRO_AUTO = \
misc2.pro \
mouse.pro \
move.pro \
+ netbeans.pro \
normal.pro \
ops.pro \
option.pro \
@@ -1943,8 +1954,8 @@ PRO_AUTO = \
quickfix.pro \
regexp.pro \
register.pro \
- scriptfile.pro \
screen.pro \
+ scriptfile.pro \
search.pro \
session.pro \
sha256.pro \
@@ -1965,13 +1976,11 @@ PRO_AUTO = \
usercmd.pro \
userfunc.pro \
version.pro \
+ vim9compile.pro \
+ vim9execute.pro \
+ vim9script.pro \
viminfo.pro \
window.pro \
- bufwrite.pro \
- beval.pro \
- gui_beval.pro \
- netbeans.pro \
- channel.pro \
$(ALL_GUI_PRO) \
$(TCL_PRO)
@@ -3079,6 +3088,9 @@ objects/blowfish.o: blowfish.c
objects/buffer.o: buffer.c
$(CCC) -o $@ buffer.c
+objects/bufwrite.o: bufwrite.c
+ $(CCC) -o $@ bufwrite.c
+
objects/change.o: change.c
$(CCC) -o $@ change.c
@@ -3433,15 +3445,21 @@ objects/usercmd.o: usercmd.c
objects/userfunc.o: userfunc.c
$(CCC) -o $@ userfunc.c
+objects/vim9compile.o: vim9compile.c
+ $(CCC) -o $@ vim9compile.c
+
+objects/vim9execute.o: vim9execute.c
+ $(CCC) -o $@ vim9execute.c
+
+objects/vim9script.o: vim9script.c
+ $(CCC) -o $@ vim9script.c
+
objects/viminfo.o: viminfo.c
$(CCC) -o $@ viminfo.c
objects/window.o: window.c
$(CCC) -o $@ window.c
-objects/bufwrite.o: bufwrite.c
- $(CCC) -o $@ bufwrite.c
-
objects/netbeans.o: netbeans.c
$(CCC) -o $@ netbeans.c
@@ -3787,6 +3805,10 @@ objects/mark.o: mark.c vim.h protodef.h auto/config.h feature.h os_unix.h \
auto/osdef.h ascii.h keymap.h term.h macros.h option.h beval.h \
proto/gui_beval.pro structs.h regexp.h gui.h alloc.h ex_cmds.h spell.h \
proto.h globals.h
+objects/mbyte.o: mbyte.c vim.h protodef.h auto/config.h feature.h os_unix.h \
+ auto/osdef.h ascii.h keymap.h term.h macros.h option.h beval.h \
+ proto/gui_beval.pro structs.h regexp.h gui.h alloc.h ex_cmds.h spell.h \
+ proto.h globals.h
objects/memfile.o: memfile.c vim.h protodef.h auto/config.h feature.h os_unix.h \
auto/osdef.h ascii.h keymap.h term.h macros.h option.h beval.h \
proto/gui_beval.pro structs.h regexp.h gui.h alloc.h ex_cmds.h spell.h \
@@ -3819,10 +3841,6 @@ objects/move.o: move.c vim.h protodef.h auto/config.h feature.h os_unix.h \
auto/osdef.h ascii.h keymap.h term.h macros.h option.h beval.h \
proto/gui_beval.pro structs.h regexp.h gui.h alloc.h ex_cmds.h spell.h \
proto.h globals.h
-objects/mbyte.o: mbyte.c vim.h protodef.h auto/config.h feature.h os_unix.h \
- auto/osdef.h ascii.h keymap.h term.h macros.h option.h beval.h \
- proto/gui_beval.pro structs.h regexp.h gui.h alloc.h ex_cmds.h spell.h \
- proto.h globals.h
objects/normal.o: normal.c vim.h protodef.h auto/config.h feature.h os_unix.h \
auto/osdef.h ascii.h keymap.h term.h macros.h option.h beval.h \
proto/gui_beval.pro structs.h regexp.h gui.h alloc.h ex_cmds.h spell.h \
@@ -3875,14 +3893,14 @@ objects/register.o: register.c vim.h protodef.h auto/config.h feature.h os_unix.
auto/osdef.h ascii.h keymap.h term.h macros.h option.h beval.h \
proto/gui_beval.pro structs.h regexp.h gui.h alloc.h ex_cmds.h spell.h \
proto.h globals.h
-objects/scriptfile.o: scriptfile.c vim.h protodef.h auto/config.h feature.h \
- os_unix.h auto/osdef.h ascii.h keymap.h term.h macros.h option.h beval.h \
- proto/gui_beval.pro structs.h regexp.h gui.h alloc.h ex_cmds.h spell.h \
- proto.h globals.h
objects/screen.o: screen.c vim.h protodef.h auto/config.h feature.h os_unix.h \
auto/osdef.h ascii.h keymap.h term.h macros.h option.h beval.h \
proto/gui_beval.pro structs.h regexp.h gui.h alloc.h ex_cmds.h spell.h \
proto.h globals.h
+objects/scriptfile.o: scriptfile.c vim.h protodef.h auto/config.h feature.h \
+ os_unix.h auto/osdef.h ascii.h keymap.h term.h macros.h option.h beval.h \
+ proto/gui_beval.pro structs.h regexp.h gui.h alloc.h ex_cmds.h spell.h \
+ proto.h globals.h
objects/search.o: search.c vim.h protodef.h auto/config.h feature.h os_unix.h \
auto/osdef.h ascii.h keymap.h term.h macros.h option.h beval.h \
proto/gui_beval.pro structs.h regexp.h gui.h alloc.h ex_cmds.h spell.h \
@@ -3961,6 +3979,18 @@ objects/version.o: version.c vim.h protodef.h auto/config.h feature.h os_unix.h
auto/osdef.h ascii.h keymap.h term.h macros.h option.h beval.h \
proto/gui_beval.pro structs.h regexp.h gui.h alloc.h ex_cmds.h spell.h \
proto.h globals.h version.h
+objects/vim9compile.o: vim9compile.c vim.h protodef.h auto/config.h feature.h \
+ os_unix.h auto/osdef.h ascii.h keymap.h term.h macros.h option.h beval.h \
+ proto/gui_beval.pro structs.h regexp.h gui.h alloc.h ex_cmds.h spell.h \
+ proto.h globals.h vim9.h
+objects/vim9execute.o: vim9execute.c vim.h protodef.h auto/config.h feature.h \
+ os_unix.h auto/osdef.h ascii.h keymap.h term.h macros.h option.h beval.h \
+ proto/gui_beval.pro structs.h regexp.h gui.h alloc.h ex_cmds.h spell.h \
+ proto.h globals.h vim9.h
+objects/vim9script.o: vim9script.c vim.h protodef.h auto/config.h feature.h \
+ os_unix.h auto/osdef.h ascii.h keymap.h term.h macros.h option.h beval.h \
+ proto/gui_beval.pro structs.h regexp.h gui.h alloc.h ex_cmds.h spell.h \
+ proto.h globals.h vim9.h
objects/viminfo.o: viminfo.c vim.h protodef.h auto/config.h feature.h os_unix.h \
auto/osdef.h ascii.h keymap.h term.h macros.h option.h beval.h \
proto/gui_beval.pro structs.h regexp.h gui.h alloc.h ex_cmds.h spell.h \
diff --git a/src/blob.c b/src/blob.c
index 2a7ec3a73..f105170cf 100644
--- a/src/blob.c
+++ b/src/blob.c
@@ -58,24 +58,24 @@ rettv_blob_set(typval_T *rettv, blob_T *b)
}
int
-blob_copy(typval_T *from, typval_T *to)
+blob_copy(blob_T *from, typval_T *to)
{
int ret = OK;
to->v_type = VAR_BLOB;
to->v_lock = 0;
- if (from->vval.v_blob == NULL)
+ if (from == 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;
+ int len = from->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);
+ vim_memsave(from->bv_ga.ga_data, len);
if (to->vval.v_blob->bv_ga.ga_data == NULL)
len = 0;
}
diff --git a/src/channel.c b/src/channel.c
index 3aec7c800..5f0306873 100644
--- a/src/channel.c
+++ b/src/channel.c
@@ -2263,7 +2263,10 @@ channel_get_json(
while (item != NULL)
{
list_T *l = item->jq_value->vval.v_list;
- typval_T *tv = &l->lv_first->li_tv;
+ 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)
diff --git a/src/dict.c b/src/dict.c
index f3f352167..2ff4ae37f 100644
--- a/src/dict.c
+++ b/src/dict.c
@@ -826,7 +826,7 @@ eval_dict(char_u **arg, typval_T *rettv, int evaluate, int literal)
if (**arg != ':')
{
- semsg(_("E720: Missing colon in Dictionary: %s"), *arg);
+ semsg(_(e_missing_dict_colon), *arg);
clear_tv(&tvkey);
goto failret;
}
@@ -853,7 +853,7 @@ eval_dict(char_u **arg, typval_T *rettv, int evaluate, int literal)
item = dict_find(d, key, -1);
if (item != NULL)
{
- semsg(_("E721: Duplicate key in Dictionary: \"%s\""), key);
+ semsg(_(e_duplicate_key), key);
clear_tv(&tvkey);
clear_tv(&tv);
goto failret;
@@ -873,7 +873,7 @@ eval_dict(char_u **arg, typval_T *rettv, int evaluate, int literal)
break;
if (**arg != ',')
{
- semsg(_("E722: Missing comma in Dictionary: %s"), *arg);
+ semsg(_(e_missing_dict_comma), *arg);
goto failret;
}
*arg = skipwhite(*arg + 1);
@@ -881,7 +881,7 @@ eval_dict(char_u **arg, typval_T *rettv, int evaluate, int literal)
if (**arg != '}')
{
- semsg(_("E723: Missing end of Dictionary '}': %s"), *arg);
+ semsg(_(e_missing_dict_end), *arg);
failret:
if (d != NULL)
dict_free(d);
diff --git a/src/eval.c b/src/eval.c
index fe6dee1f1..72f932498 100644
--- a/src/eval.c
+++ b/src/eval.c
@@ -20,12 +20,10 @@
# include <float.h>
#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"
@@ -60,10 +58,7 @@ static int eval6(char_u **arg, typval_T *rettv, int evaluate, int want_string);
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);
@@ -222,6 +217,11 @@ eval1_emsg(char_u **arg, typval_T *rettv, int evaluate)
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,14 +243,22 @@ eval_expr_typval(typval_T *expr, typval_T *argv, int argc, typval_T *rettv)
{
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;
+ 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,6 +660,7 @@ get_lval(
// 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,8 +687,20 @@ get_lval(
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,6 +1023,7 @@ get_lval(
}
clear_tv(&var1);
+ lp->ll_name_end = p;
return p;
}
@@ -1027,7 +1049,7 @@ set_var_lval(
char_u *endp,
typval_T *rettv,
int copy,
- int is_const, // Disallow to modify existing variable for :const
+ int flags, // LET_IS_CONST and/or LET_NO_COMMAND
char_u *op)
{
int cc;
@@ -1093,7 +1115,7 @@ set_var_lval(
{
typval_T tv;
- if (is_const)
+ if (flags & LET_IS_CONST)
{
emsg(_(e_cannot_mod));
*endp = cc;
@@ -1114,7 +1136,7 @@ set_var_lval(
}
}
else
- set_var_const(lp->ll_name, rettv, copy, is_const);
+ set_var_const(lp->ll_name, lp->ll_type, rettv, copy, flags);
*endp = cc;
}
else if (var_check_lock(lp->ll_newkey == NULL
@@ -1126,7 +1148,7 @@ set_var_lval(
listitem_T *ll_li = lp->ll_li;
int ll_n1 = lp->ll_n1;
- if (is_const)
+ if (flags & LET_IS_CONST)
{
emsg(_("E996: Cannot lock a range"));
return;
@@ -1185,7 +1207,7 @@ set_var_lval(
/*
* Assign to a List or Dictionary item.
*/
- if (is_const)
+ if (flags & LET_IS_CONST)
{
emsg(_("E996: Cannot lock a list or dict"));
return;
@@ -1250,6 +1272,7 @@ tv_op(typval_T *tv1, typval_T *tv2, char_u *op)
switch (tv1->v_type)
{
case VAR_UNKNOWN:
+ case VAR_VOID:
case VAR_DICT:
case VAR_FUNC:
case VAR_PARTIAL:
@@ -1392,14 +1415,14 @@ eval_for_line(
if (fi == NULL)
return NULL;
- expr = skip_var_list(arg, &fi->fi_varcount, &fi->fi_semicolon);
+ 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(_("E690: Missing \"in\" after :for"));
+ emsg(_(e_missing_in));
return fi;
}
@@ -1420,6 +1443,9 @@ eval_for_line(
}
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,7 +1462,7 @@ eval_for_line(
// Make a copy, so that the iteration still works when the
// blob is changed.
- blob_copy(&tv, &btv);
+ blob_copy(tv.vval.v_blob, &btv);
fi->fi_blob = btv.vval.v_blob;
}
clear_tv(&tv);
@@ -1478,7 +1504,7 @@ next_for_item(void *fi_void, char_u *arg)
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;
+ fi->fi_varcount, 0, NULL) == OK;
}
item = fi->fi_lw.lw_item;
@@ -1488,7 +1514,7 @@ next_for_item(void *fi_void, char_u *arg)
{
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);
+ fi->fi_varcount, 0, NULL) == OK);
}
return result;
}
@@ -1814,7 +1840,7 @@ eval1(char_u **arg, typval_T *rettv, int evaluate)
*/
if ((*arg)[0] != ':')
{
- emsg(_("E109: Missing ':' after '?'"));
+ emsg(_(e_missing_colon));
if (evaluate && result)
clear_tv(rettv);
return FAIL;
@@ -2089,6 +2115,43 @@ eval4(char_u **arg, typval_T *rettv, int evaluate)
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,7 +2168,6 @@ eval4(char_u **arg, typval_T *rettv, int evaluate)
eval5(char_u **arg, typval_T *rettv, int evaluate)
{
typval_T var2;
- typval_T var3;
int op;
varnumber_T n1, n2;
#ifdef FEAT_FLOAT
@@ -2189,36 +2251,12 @@ eval5(char_u **arg, typval_T *rettv, int evaluate)
}
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);
- }
- }
+ eval_addblob(rettv, &var2);
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);
+ if (eval_addlist(rettv, &var2) == FAIL)
return FAIL;
- }
- clear_tv(rettv);
- *rettv = var3;
}
else
{
@@ -2424,7 +2462,7 @@ eval6(
}
else
{
- emsg(_("E804: Cannot use '%' with Float"));
+ emsg(_(e_modulus));
return FAIL;
}
rettv->v_type = VAR_FLOAT;
@@ -2462,6 +2500,7 @@ eval6(
* $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,9 +2522,8 @@ eval7(
char_u **arg,
typval_T *rettv,
int evaluate,
- int want_string UNUSED) // after "." operator
+ int want_string) // after "." operator
{
- varnumber_T n;
int len;
char_u *s;
char_u *start_leader, *end_leader;
@@ -2532,105 +2570,8 @@ eval7(
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;
- }
- }
+ case '.': ret = get_number_tv(arg, rettv, evaluate, want_string);
break;
- }
/*
* String constant: "string".
@@ -2647,7 +2588,7 @@ eval7(
/*
* List: [expr, expr]
*/
- case '[': ret = get_list_tv(arg, rettv, evaluate);
+ case '[': ret = get_list_tv(arg, rettv, evaluate, TRUE);
break;
/*
@@ -2706,7 +2647,7 @@ eval7(
++*arg;
else if (ret == OK)
{
- emsg(_("E110: Missing ')'"));
+ emsg(_(e_missing_close));
clear_tv(rettv);
ret = FAIL;
}
@@ -2907,7 +2848,7 @@ eval_lambda(
if (*skipwhite(*arg) == '(')
semsg(_(e_nowhitespace));
else
- semsg(_(e_missingparen), "lambda");
+ semsg(_(e_missing_paren), "lambda");
}
clear_tv(rettv);
ret = FAIL;
@@ -2961,7 +2902,7 @@ eval_method(
if (**arg != '(')
{
if (verbose)
- semsg(_(e_missingparen), name);
+ semsg(_(e_missing_paren), name);
ret = FAIL;
}
else if (VIM_ISWHITE((*arg)[-1]))
@@ -3024,6 +2965,7 @@ eval_index(
emsg(_("E909: Cannot index a special variable"));
return FAIL;
case VAR_UNKNOWN:
+ case VAR_VOID:
if (evaluate)
return FAIL;
// FALLTHROUGH
@@ -3129,6 +3071,7 @@ eval_index(
switch (rettv->v_type)
{
case VAR_UNKNOWN:
+ case VAR_VOID:
case VAR_FUNC:
case VAR_PARTIAL:
case VAR_FLOAT:
@@ -3377,7 +3320,7 @@ get_option_tv(
if (opt_type == -3) // invalid name
{
if (rettv != NULL)
- semsg(_("E113: Unknown option: %s"), *arg);
+ semsg(_(e_unknown_option), *arg);
ret = FAIL;
}
else if (rettv != NULL)
@@ -3413,10 +3356,120 @@ get_option_tv(
}
/*
+ * 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.
*/
- static int
+ int
get_string_tv(char_u **arg, typval_T *rettv, int evaluate)
{
char_u *p;
@@ -3553,7 +3606,7 @@ get_string_tv(char_u **arg, typval_T *rettv, int evaluate)
* Allocate a variable for a 'str''ing' constant.
* Return OK or FAIL.
*/
- static int
+ int
get_lit_string_tv(char_u **arg, typval_T *rettv, int evaluate)
{
char_u *p;
@@ -3772,6 +3825,8 @@ tv_equal(
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,10 +3834,6 @@ tv_equal(
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;
@@ -3795,9 +3846,11 @@ tv_equal(
#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,6 +4564,7 @@ echo_string_core(
case VAR_NUMBER:
case VAR_UNKNOWN:
+ case VAR_VOID:
*tofree = NULL;
r = tv_get_string_buf(tv, numbuf);
break;
@@ -4668,7 +4722,7 @@ string2float(
* If the environment variable was not set, silently assume it is empty.
* Return FAIL if the name is invalid.
*/
- static int
+ int
get_env_tv(char_u **arg, typval_T *rettv, int evaluate)
{
char_u *string = NULL;
@@ -5363,6 +5417,7 @@ free_tv(typval_T *varp)
case VAR_NUMBER:
case VAR_FLOAT:
case VAR_UNKNOWN:
+ case VAR_VOID:
case VAR_BOOL:
case VAR_SPECIAL:
break;
@@ -5425,6 +5480,7 @@ clear_tv(typval_T *varp)
varp->vval.v_channel = NULL;
#endif
case VAR_UNKNOWN:
+ case VAR_VOID:
break;
}
varp->v_lock = 0;
@@ -5503,6 +5559,7 @@ tv_get_number_chk(typval_T *varp, int *denote)
emsg(_("E974: Using a Blob as a Number"));
break;
case VAR_UNKNOWN:
+ case VAR_VOID:
internal_error("tv_get_number(UNKNOWN)");
break;
}
@@ -5556,6 +5613,7 @@ tv_get_float(typval_T *varp)
emsg(_("E975: Using a Blob as a Float"));
break;
case VAR_UNKNOWN:
+ case VAR_VOID:
internal_error("tv_get_float(UNKNOWN)");
break;
}
@@ -5678,6 +5736,7 @@ tv_get_string_buf_chk(typval_T *varp, char_u *buf)
#endif
break;
case VAR_UNKNOWN:
+ case VAR_VOID:
emsg(_(e_inval_string));
break;
}
@@ -5826,6 +5885,7 @@ copy_tv(typval_T *from, typval_T *to)
}
break;
case VAR_UNKNOWN:
+ case VAR_VOID:
internal_error("copy_tv(UNKNOWN)");
break;
}
@@ -5885,7 +5945,7 @@ item_copy(
ret = FAIL;
break;
case VAR_BLOB:
- ret = blob_copy(from, to);
+ ret = blob_copy(from->vval.v_blob, to);
break;
case VAR_DICT:
to->v_type = VAR_DICT;
@@ -5904,6 +5964,7 @@ item_copy(
ret = FAIL;
break;
case VAR_UNKNOWN:
+ case VAR_VOID:
internal_error("item_copy(UNKNOWN)");
ret = FAIL;
}
@@ -5911,6 +5972,59 @@ item_copy(
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,11 +6035,9 @@ ex_echo(exarg_T *eap)
{
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;
@@ -5954,52 +6066,8 @@ ex_echo(exarg_T *eap)
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);
+ echo_one(&rettv, eap->cmdidx == CMD_echo, &atstart, &needclr);
- (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);
}
@@ -6369,7 +6437,7 @@ typval_compare(
case EXPR_SEQUAL: n1 = (f1 <= f2); break;
case EXPR_UNKNOWN:
case EXPR_MATCH:
- case EXPR_NOMATCH: break; // avoid gcc warning
+ default: break; // avoid gcc warning
}
}
#endif
@@ -6395,7 +6463,7 @@ typval_compare(
case EXPR_SEQUAL: n1 = (n1 <= n2); break;
case EXPR_UNKNOWN:
case EXPR_MATCH:
- case EXPR_NOMATCH: break; // avoid gcc warning
+ default: break; // avoid gcc warning
}
}
else
@@ -6425,7 +6493,7 @@ typval_compare(
n1 = !n1;
break;
- case EXPR_UNKNOWN: break; // avoid gcc warning
+ default: break; // avoid gcc warning
}
}
clear_tv(typ1);
diff --git a/src/evalbuffer.c b/src/evalbuffer.c
index 09db525e2..d44fb1018 100644
--- a/src/evalbuffer.c
+++ b/src/evalbuffer.c
@@ -176,6 +176,7 @@ set_buffer_lines(
if (lines->v_type == VAR_LIST)
{
l = lines->vval.v_list;
+ range_list_materialize(l);
li = l->lv_first;
}
else
@@ -689,10 +690,16 @@ get_buffer_lines(
{
char_u *p;
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = NULL;
- if (retlist && rettv_list_alloc(rettv) == FAIL)
- return;
+ 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;
diff --git a/src/evalfunc.c b/src/evalfunc.c
index d248f4bf4..536522761 100644
--- a/src/evalfunc.c
+++ b/src/evalfunc.c
@@ -290,6 +290,7 @@ typedef struct
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,580 +305,580 @@ typedef struct
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},
+ {"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, 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},
+ {"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, f_atan},
- {"atan2", 2, 2, FEARG_1, f_atan2},
+ {"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, f_balloon_gettext},
- {"balloon_show", 1, 1, FEARG_1, f_balloon_show},
+ {"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, f_balloon_split},
+ {"balloon_split", 1, 1, FEARG_1, &t_list_string, 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},
+ {"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, f_ceil},
+ {"ceil", 1, 1, FEARG_1, &t_float, 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},
+ {"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, f_cos},
- {"cosh", 1, 1, FEARG_1, f_cosh},
+ {"cos", 1, 1, FEARG_1, &t_float, f_cos},
+ {"cosh", 1, 1, FEARG_1, &t_float, 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},
+ {"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, 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},
+ {"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, 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},
+ {"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, 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},
+ {"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, f_isinf},
+ {"isinf", 1, 1, FEARG_1, &t_number, f_isinf},
#endif
- {"islocked", 1, 1, FEARG_1, f_islocked},
+ {"islocked", 1, 1, FEARG_1, &t_number, f_islocked},
#if defined(FEAT_FLOAT) && defined(HAVE_MATH_H)
- {"isnan", 1, 1, FEARG_1, f_isnan},
+ {"isnan", 1, 1, FEARG_1, &t_number, f_isnan},
#endif
- {"items", 1, 1, FEARG_1, f_items},
+ {"items", 1, 1, FEARG_1, &t_list_any, 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},
+ {"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, f_log},
- {"log10", 1, 1, FEARG_1, f_log10},
+ {"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, 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},
+ {"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, f_mzeval},
+ {"mzeval", 1, 1, FEARG_1, &t_any, 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},
+ {"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, f_perleval},
+ {"perleval", 1, 1, FEARG_1, &t_any, 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},
+ {"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, f_pow},
+ {"pow", 2, 2, FEARG_1, &t_float, f_pow},
#endif
- {"prevnonblank", 1, 1, FEARG_1, f_prevnonblank},
- {"printf", 1, 19, FEARG_2, f_printf},
+ {"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, f_prompt_setcallback},
- {"prompt_setinterrupt", 2, 2, FEARG_1, f_prompt_setinterrupt},
- {"prompt_setprompt", 2, 2, FEARG_1, f_prompt_setprompt},
+ {"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, 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},
+ {"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, f_py3eval},
+ {"py3eval", 1, 1, FEARG_1, &t_any, f_py3eval},
#endif
#ifdef FEAT_PYTHON
- {"pyeval", 1, 1, FEARG_1, f_pyeval},
+ {"pyeval", 1, 1, FEARG_1, &t_any, 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},
+ {"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, 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},
+ {"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, f_round},
+ {"round", 1, 1, FEARG_1, &t_float, 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},
+ {"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, f_sha256},
+ {"sha256", 1, 1, FEARG_1, &t_string, f_sha256},
#endif
- {"shellescape", 1, 2, FEARG_1, f_shellescape},
- {"shiftwidth", 0, 1, FEARG_1, f_shiftwidth},
+ {"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, 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},
+ {"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, f_sin},
- {"sinh", 1, 1, FEARG_1, f_sinh},
+ {"sin", 1, 1, FEARG_1, &t_float, f_sin},
+ {"sinh", 1, 1, FEARG_1, &t_float, f_sinh},
#endif
- {"sort", 1, 3, FEARG_1, f_sort},
+ {"sort", 1, 3, FEARG_1, &t_list_any, 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},
+ {"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, f_sqrt},
+ {"sqrt", 1, 1, FEARG_1, &t_float, f_sqrt},
#endif
- {"srand", 0, 1, FEARG_1, f_srand},
- {"state", 0, 1, FEARG_1, f_state},
+ {"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, f_str2float},
+ {"str2float", 1, 1, FEARG_1, &t_float, 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},
+ {"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, f_strftime},
+ {"strftime", 1, 2, FEARG_1, &t_string, 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},
+ {"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, 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},
+ {"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, f_tan},
- {"tanh", 1, 1, FEARG_1, f_tanh},
+ {"tan", 1, 1, FEARG_1, &t_float, f_tan},
+ {"tanh", 1, 1, FEARG_1, &t_float, f_tanh},
#endif
- {"tempname", 0, 0, 0, f_tempname},
+ {"tempname", 0, 0, 0, &t_string, 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},
+ {"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, f_term_getansicolors},
+ {"term_getansicolors", 1, 1, FEARG_1, &t_list_string, 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},
+ {"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, f_term_setansicolors},
+ {"term_setansicolors", 2, 2, FEARG_1, &t_void, 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},
+ {"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, f_test_null_channel},
+ {"test_null_channel", 0, 0, 0, &t_channel, f_test_null_channel},
#endif
- {"test_null_dict", 0, 0, 0, f_test_null_dict},
+ {"test_null_dict", 0, 0, 0, &t_dict_any, 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},
+ {"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, f_test_scrollbar},
+ {"test_scrollbar", 3, 3, FEARG_2, &t_void, f_test_scrollbar},
#endif
- {"test_setmouse", 2, 2, 0, f_test_setmouse},
- {"test_settime", 1, 1, FEARG_1, f_test_settime},
+ {"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, 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},
+ {"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, 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},
+ {"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,7 +936,7 @@ get_expr_name(expand_T *xp, int idx)
* Find internal function "name" in table "global_functions".
* Return index, or -1 if not found
*/
- static int
+ int
find_internal_func(char_u *name)
{
int first = 0;
@@ -966,6 +967,47 @@ has_internal_func(char_u *name)
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,6 +1029,15 @@ call_internal_func(
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,6 +1885,7 @@ f_empty(typval_T *argvars, typval_T *rettv)
break;
#endif
case VAR_UNKNOWN:
+ case VAR_VOID:
internal_error("f_empty(UNKNOWN)");
n = TRUE;
break;
@@ -2487,7 +2539,7 @@ common_function(typval_T *argvars, typval_T *rettv, int is_funcref)
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
+ ? find_func(trans_name, NULL) == NULL
: !translated_function_exists(trans_name)))
semsg(_("E700: Unknown function: %s"), s);
else
@@ -2623,7 +2675,7 @@ common_function(typval_T *argvars, typval_T *rettv, int is_funcref)
}
else if (is_funcref)
{
- pt->pt_func = find_func(trans_name);
+ pt->pt_func = find_func(trans_name, NULL);
func_ptr_ref(pt->pt_func);
vim_free(name);
}
@@ -4319,6 +4371,7 @@ f_len(typval_T *argvars, typval_T *rettv)
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,7 +5290,6 @@ f_range(typval_T *argvars, typval_T *rettv)
varnumber_T start;
varnumber_T end;
varnumber_T stride = 1;
- varnumber_T i;
int error = FALSE;
start = tv_get_number_chk(&argvars[0], &error);
@@ -5259,13 +5311,41 @@ f_range(typval_T *argvars, typval_T *rettv)
emsg(_("E726: Stride is zero"));
else if (stride > 0 ? end + 1 < start : end - 1 > start)
emsg(_("E727: Start past end"));
- else
+ else if (rettv_list_alloc(rettv) == OK)
{
- 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;
+ 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,6 +8436,7 @@ f_type(typval_T *argvars, typval_T *rettv)
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;
diff --git a/src/evalvars.c b/src/evalvars.c
index d359c7f44..ebb30dd22 100644
--- a/src/evalvars.c
+++ b/src/evalvars.c
@@ -15,8 +15,6 @@
#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
@@ -41,6 +39,8 @@ static hashtab_T compat_hashtab;
#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,17 +163,14 @@ static dict_T vimvardict; // Dictionary with v: variables
// 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 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 is_const, char_u *endchars, char_u *op);
+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,7 +541,7 @@ list_script_vars(int *first)
* indentation in the 'cmd' line) is stripped.
* Returns a List with {lines} or NULL.
*/
- static list_T *
+ list_T *
heredoc_get(exarg_T *eap, char_u *cmd)
{
char_u *theline;
@@ -669,6 +666,7 @@ heredoc_get(exarg_T *eap, char_u *cmd)
* ":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,8 +699,13 @@ ex_let_const(exarg_T *eap, int is_const)
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, &var_count, &semicolon);
+ argend = skip_var_list(arg, TRUE, &var_count, &semicolon);
if (argend == NULL)
return;
if (argend > arg && argend[-1] == '.') // for var.='str'
@@ -749,7 +752,7 @@ ex_let_const(exarg_T *eap, int is_const)
op[0] = '=';
op[1] = NUL;
(void)ex_let_vars(eap->arg, &rettv, FALSE, semicolon, var_count,
- is_const, op);
+ flags, op);
}
clear_tv(&rettv);
}
@@ -783,7 +786,7 @@ ex_let_const(exarg_T *eap, int is_const)
else if (i != FAIL)
{
(void)ex_let_vars(eap->arg, &rettv, FALSE, semicolon, var_count,
- is_const, op);
+ flags, op);
clear_tv(&rettv);
}
}
@@ -804,7 +807,7 @@ ex_let_vars(
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
+ int flags, // LET_IS_CONST and/or LET_NO_COMMAND
char_u *op)
{
char_u *arg = arg_start;
@@ -816,7 +819,7 @@ ex_let_vars(
if (*arg != '[')
{
// ":let var = expr" or ":for var in list"
- if (ex_let_one(arg, tv, copy, is_const, op, op) == NULL)
+ if (ex_let_one(arg, tv, copy, flags, op, op) == NULL)
return FAIL;
return OK;
}
@@ -844,8 +847,7 @@ ex_let_vars(
while (*arg != ']')
{
arg = skipwhite(arg + 1);
- arg = ex_let_one(arg, &item->li_tv, TRUE, is_const,
- (char_u *)",;]", op);
+ arg = ex_let_one(arg, &item->li_tv, TRUE, flags, (char_u *)",;]", op);
item = item->li_next;
if (arg == NULL)
return FAIL;
@@ -869,7 +871,7 @@ ex_let_vars(
ltv.vval.v_list = l;
l->lv_refcount = 1;
- arg = ex_let_one(skipwhite(arg + 1), &ltv, FALSE, is_const,
+ arg = ex_let_one(skipwhite(arg + 1), &ltv, FALSE, flags,
(char_u *)"]", op);
clear_tv(&ltv);
if (arg == NULL)
@@ -896,6 +898,7 @@ ex_let_vars(
char_u *
skip_var_list(
char_u *arg,
+ int include_type,
int *var_count,
int *semicolon)
{
@@ -908,7 +911,7 @@ skip_var_list(
for (;;)
{
p = skipwhite(p + 1); // skip whites after '[', ';' or ','
- s = skip_var_one(p);
+ s = skip_var_one(p, TRUE);
if (s == p)
{
semsg(_(e_invarg2), p);
@@ -937,20 +940,29 @@ skip_var_list(
return p + 1;
}
else
- return skip_var_one(arg);
+ 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)
+skip_var_one(char_u *arg, int include_type)
{
+ char_u *end;
+
if (*arg == '@' && arg[1] != NUL)
return arg + 2;
- return find_name_end(*arg == '$' || *arg == '&' ? arg + 1 : arg,
+ 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,7 +1153,7 @@ ex_let_one(
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
+ 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,7 +1168,7 @@ ex_let_one(
// ":let $VAR = expr": Set environment variable.
if (*arg == '$')
{
- if (is_const)
+ if (flags & LET_IS_CONST)
{
emsg(_("E996: Cannot lock an environment variable"));
return NULL;
@@ -1214,9 +1226,9 @@ ex_let_one(
// ":let &g:option = expr": Set global option value.
else if (*arg == '&')
{
- if (is_const)
+ if (flags & LET_IS_CONST)
{
- emsg(_("E996: Cannot lock an option"));
+ emsg(_(e_const_option));
return NULL;
}
// Find the end of the name.
@@ -1281,7 +1293,7 @@ ex_let_one(
// ":let @r = expr": Set register contents.
else if (*arg == '@')
{
- if (is_const)
+ if (flags & LET_IS_CONST)
{
emsg(_("E996: Cannot lock a register"));
return NULL;
@@ -1317,6 +1329,7 @@ ex_let_one(
}
// ":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,11 +1338,12 @@ ex_let_one(
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)
+ if (endchars != NULL && vim_strchr(endchars,
+ *skipwhite(lv.ll_name_end)) == NULL)
emsg(_(e_letunexp));
else
{
- set_var_lval(&lv, p, tv, copy, is_const, op);
+ set_var_lval(&lv, p, tv, copy, flags, op);
arg_end = p;
}
}
@@ -1657,12 +1671,13 @@ item_lock(typval_T *tv, int deep, int lock)
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_BOOL:
case VAR_SPECIAL:
case VAR_JOB:
case VAR_CHANNEL:
@@ -1901,6 +1916,22 @@ get_vimvar_dict(void)
}
/*
+ * 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,6 +1950,12 @@ set_vim_var_nr(int idx, varnumber_T val)
vimvars[idx].vv_nr = val;
}
+ char *
+get_vim_var_name(int idx)
+{
+ return vimvars[idx].vv_name;
+}
+
/*
* Get typval_T v: variable value.
*/
@@ -2245,6 +2282,20 @@ get_var_tv(
*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,6 +2417,58 @@ find_var_in_ht(
}
/*
+ * 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,9 +2498,18 @@ find_var_ht(char_u *name, char_u **varname)
}
ht = get_funccal_local_ht();
- if (ht == NULL)
- return &globvarht; // global variable
- return ht; // local variable
+ 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,14 +2526,19 @@ find_var_ht(char_u *name, char_u **varname)
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);
+ 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,7 +2734,7 @@ set_var(
typval_T *tv,
int copy) // make copy of value in "tv"
{
- set_var_const(name, tv, copy, FALSE);
+ set_var_const(name, NULL, tv, copy, 0);
}
/*
@@ -2628,13 +2745,15 @@ set_var(
void
set_var_const(
char_u *name,
+ type_T *type,
typval_T *tv,
int copy, // make copy of value in "tv"
- int is_const) // disallow to modify existing variable
+ int flags) // LET_IS_CONST and/or LET_NO_COMMAND
{
- dictitem_T *v;
+ 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,75 +2761,92 @@ set_var_const(
semsg(_(e_illvar), name);
return;
}
- v = find_var_in_ht(ht, 0, varname, TRUE);
+ 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 (v == NULL)
- v = find_var_in_scoped_ht(name, TRUE);
+ 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, v == NULL))
+ && var_check_func_name(name, di == NULL))
return;
- if (v != NULL)
+ if (di != NULL)
{
- if (is_const)
+ if ((di->di_flags & DI_FLAGS_RELOAD) == 0)
{
- emsg(_(e_cannot_mod));
- return;
+ 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
- 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
+ // Handle setting internal di: variables separately where needed to
// prevent changing the type.
if (ht == &vimvarht)
{
- if (v->di_tv.v_type == VAR_STRING)
+ if (di->di_tv.v_type == VAR_STRING)
{
- VIM_CLEAR(v->di_tv.vval.v_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 (v->di_tv.vval.v_string == NULL)
- v->di_tv.vval.v_string = vim_strsave(val);
+ 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.
- v->di_tv.vval.v_string = tv->vval.v_string;
+ di->di_tv.vval.v_string = tv->vval.v_string;
tv->vval.v_string = NULL;
}
return;
}
- else if (v->di_tv.v_type == VAR_NUMBER)
+ else if (di->di_tv.v_type == VAR_NUMBER)
{
- v->di_tv.vval.v_number = tv_get_number(tv);
+ di->di_tv.vval.v_number = tv_get_number(tv);
if (STRCMP(varname, "searchforward") == 0)
- set_search_direction(v->di_tv.vval.v_number ? '/' : '?');
+ set_search_direction(di->di_tv.vval.v_number ? '/' : '?');
#ifdef FEAT_SEARCH_EXTRA
else if (STRCMP(varname, "hlsearch") == 0)
{
- no_hlsearch = !v->di_tv.vval.v_number;
+ no_hlsearch = !di->di_tv.vval.v_number;
redraw_all_later(SOME_VALID);
}
#endif
return;
}
- else if (v->di_tv.v_type != tv->v_type)
+ else if (di->di_tv.v_type != tv->v_type)
{
semsg(_("E963: setting %s to value with wrong type"), name);
return;
}
}
- clear_tv(&v->di_tv);
+ clear_tv(&di->di_tv);
}
else // add a new variable
{
@@ -2725,31 +2861,53 @@ set_var_const(
if (!valid_varname(varname))
return;
- v = alloc(sizeof(dictitem_T) + STRLEN(varname));
- if (v == NULL)
+ di = alloc(sizeof(dictitem_T) + STRLEN(varname));
+ if (di == NULL)
return;
- STRCPY(v->di_key, varname);
- if (hash_add(ht, DI2HIKEY(v)) == FAIL)
+ STRCPY(di->di_key, varname);
+ if (hash_add(ht, DI2HIKEY(di)) == FAIL)
{
- vim_free(v);
+ vim_free(di);
return;
}
- v->di_flags = DI_FLAGS_ALLOC;
- if (is_const)
- v->di_flags |= DI_FLAGS_LOCK;
+ 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, &v->di_tv);
+ copy_tv(tv, &di->di_tv);
else
{
- v->di_tv = *tv;
- v->di_tv.v_lock = 0;
+ di->di_tv = *tv;
+ di->di_tv.v_lock = 0;
init_tv(tv);
}
- if (is_const)
- v->di_tv.v_lock |= VAR_LOCKED;
+ if (flags & LET_IS_CONST)
+ di->di_tv.v_lock |= VAR_LOCKED;
}
/*
@@ -3130,9 +3288,9 @@ var_redir_start(char_u *name, int append)
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 *)".");
+ set_var_lval(redir_lval, redir_endp, &tv, TRUE, 0, (char_u *)".");
else
- set_var_lval(redir_lval, redir_endp, &tv, TRUE, FALSE, (char_u *)"=");
+ set_var_lval(redir_lval, redir_endp, &tv, TRUE, 0, (char_u *)"=");
clear_lval(redir_lval);
err = did_emsg;
did_emsg |= save_emsg;
@@ -3205,7 +3363,7 @@ var_redir_stop(void)
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,
+ set_var_lval(redir_lval, redir_endp, &tv, FALSE, 0,
(char_u *)".");
clear_lval(redir_lval);
}
diff --git a/src/ex_cmdidxs.h b/src/ex_cmdidxs.h
index c43e1f2d0..790cbf4b5 100644
--- a/src/ex_cmdidxs.h
+++ b/src/ex_cmdidxs.h
@@ -9,28 +9,28 @@ static const unsigned short cmdidxs1[26] =
/* 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
+ /* 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,12 +44,12 @@ static const unsigned char cmdidxs2[26][26] =
/* 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 },
+ /* 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, 13, 0, 15, 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,11 +62,11 @@ static const unsigned char cmdidxs2[26][26] =
/* 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 },
+ /* 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 = 562;
+static const int command_count = 568;
diff --git a/src/ex_cmds.h b/src/ex_cmds.h
index 53e49d519..10b9ed723 100644
--- a/src/ex_cmds.h
+++ b/src/ex_cmds.h
@@ -442,6 +442,9 @@ EXCMD(CMD_debug, "debug", ex_debug,
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,6 +478,9 @@ EXCMD(CMD_diffthis, "diffthis", ex_diffthis,
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,6 +535,9 @@ EXCMD(CMD_emenu, "emenu", ex_emenu,
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,6 +565,9 @@ EXCMD(CMD_execute, "execute", ex_execute,
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,6 +691,9 @@ EXCMD(CMD_imapclear, "imapclear", ex_mapclear,
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,6 +1663,9 @@ EXCMD(CMD_vimgrep, "vimgrep", ex_vimgrep,
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),
diff --git a/src/ex_docmd.c b/src/ex_docmd.c
index 193cfcfdf..0e6b8dc89 100644
--- a/src/ex_docmd.c
+++ b/src/ex_docmd.c
@@ -27,7 +27,6 @@ static int if_level = 0; // depth in :if
#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
@@ -275,6 +274,7 @@ static void ex_tag_cmd(exarg_T *eap, char_u *name);
# 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,7 +300,10 @@ static void ex_tag_cmd(exarg_T *eap, char_u *name);
# 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,7 +1711,13 @@ do_one_cmd(
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
+ 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,7 +1885,7 @@ do_one_cmd(
#ifdef FEAT_EVAL
&& !aborting()
#endif
- ) ? find_command(&ea, NULL) : ea.cmd;
+ ) ? find_ex_command(&ea, NULL, NULL, NULL) : ea.cmd;
}
if (p == NULL)
@@ -2485,6 +2494,10 @@ do_one_cmd(
}
#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,15 +3121,64 @@ append_command(char_u *cmd)
* 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.
*/
- static char_u *
-find_command(exarg_T *eap, int *full UNUSED)
+ 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,8 +3211,17 @@ find_command(exarg_T *eap, int *full UNUSED)
++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,7 +3378,7 @@ cmd_exists(char_u *name)
// 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);
+ p = find_ex_command(&ea, &full, NULL, NULL);
if (p == NULL)
return 3;
if (vim_isdigit(*name) && ea.cmdidx != CMD_match)
@@ -8558,7 +8629,7 @@ ex_folddo(exarg_T *eap)
}
#endif
-#ifdef FEAT_QUICKFIX
+#if defined(FEAT_QUICKFIX) || defined(PROTO)
/*
* Returns TRUE if the supplied Ex cmdidx is for a location list command
* instead of a quickfix command.
diff --git a/src/ex_eval.c b/src/ex_eval.c
index 382e99e0d..70b52a369 100644
--- a/src/ex_eval.c
+++ b/src/ex_eval.c
@@ -15,7 +15,6 @@
#if defined(FEAT_EVAL) || defined(PROTO)
-static int throw_exception(void *, except_type_T, char_u *);
static char *get_end_emsg(cstack_T *cstack);
/*
@@ -498,7 +497,7 @@ get_exception_string(
* user or interrupt exception, or points to a message list in case of an
* error exception.
*/
- static int
+ int
throw_exception(void *value, except_type_T type, char_u *cmdname)
{
except_T *excp;
@@ -649,7 +648,7 @@ discard_current_exception(void)
/*
* Put an exception on the caught stack.
*/
- static void
+ void
catch_exception(except_T *excp)
{
excp->caught = caught_stack;
@@ -932,7 +931,7 @@ ex_endif(exarg_T *eap)
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");
+ eap->errmsg = N_(e_endif_without_if);
else
{
/*
@@ -976,10 +975,10 @@ ex_else(exarg_T *eap)
{
if (eap->cmdidx == CMD_else)
{
- eap->errmsg = N_("E581: :else without :if");
+ eap->errmsg = N_(e_else_without_if);
return;
}
- eap->errmsg = N_("E582: :elseif without :if");
+ eap->errmsg = N_(e_elseif_without_if);
skip = TRUE;
}
else if (cstack->cs_flags[cstack->cs_idx] & CSF_ELSE)
@@ -1152,7 +1151,7 @@ ex_continue(exarg_T *eap)
cstack_T *cstack = eap->cstack;
if (cstack->cs_looplevel <= 0 || cstack->cs_idx < 0)
- eap->errmsg = N_("E586: :continue without :while or :for");
+ eap->errmsg = N_(e_continue);
else
{
// Try to find the matching ":while". This might stop at a try
@@ -1190,7 +1189,7 @@ ex_break(exarg_T *eap)
cstack_T *cstack = eap->cstack;
if (cstack->cs_looplevel <= 0 || cstack->cs_idx < 0)
- eap->errmsg = N_("E587: :break without :while or :for");
+ eap->errmsg = N_(e_break);
else
{
// Inactivate conditionals until the matching ":while" or a try
@@ -1492,7 +1491,7 @@ ex_catch(exarg_T *eap)
if (cstack->cs_trylevel <= 0 || cstack->cs_idx < 0)
{
- eap->errmsg = N_("E603: :catch without :try");
+ eap->errmsg = e_catch;
give_up = TRUE;
}
else
@@ -1648,7 +1647,7 @@ ex_finally(exarg_T *eap)
cstack_T *cstack = eap->cstack;
if (cstack->cs_trylevel <= 0 || cstack->cs_idx < 0)
- eap->errmsg = N_("E606: :finally without :try");
+ eap->errmsg = e_finally;
else
{
if (!(cstack->cs_flags[cstack->cs_idx] & CSF_TRY))
@@ -1668,7 +1667,7 @@ ex_finally(exarg_T *eap)
if (cstack->cs_flags[idx] & CSF_FINALLY)
{
// Give up for a multiple ":finally" and ignore it.
- eap->errmsg = N_("E607: multiple :finally");
+ eap->errmsg = e_finally_dup;
return;
}
rewind_conditionals(cstack, idx, CSF_WHILE | CSF_FOR,
@@ -1777,7 +1776,7 @@ ex_endtry(exarg_T *eap)
cstack_T *cstack = eap->cstack;
if (cstack->cs_trylevel <= 0 || cstack->cs_idx < 0)
- eap->errmsg = N_("E602: :endtry without :try");
+ eap->errmsg = e_no_endtry;
else
{
/*
diff --git a/src/filepath.c b/src/filepath.c
index ef5edae23..04026a098 100644
--- a/src/filepath.c
+++ b/src/filepath.c
@@ -1892,6 +1892,7 @@ f_writefile(typval_T *argvars, typval_T *rettv)
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;
diff --git a/src/globals.h b/src/globals.h
index 34430d3e0..6a73bc135 100644
--- a/src/globals.h
+++ b/src/globals.h
@@ -286,8 +286,11 @@ EXTERN int debug_backtrace_level INIT(= 0); // breakpoint backtrace level
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]
+# 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,9 +362,6 @@ EXTERN int suppress_errthrow INIT(= FALSE);
*/
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
@@ -376,6 +376,39 @@ EXTERN int garbage_collect_at_exit INIT(= FALSE);
// 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,6 +1071,8 @@ EXTERN int ctrl_c_interrupts INIT(= TRUE); // CTRL-C sets got_int
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,9 +1500,13 @@ EXTERN char e_cmdwin[] INIT(= N_("E11: Invalid in command-line window; <CR> exec
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_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_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,7 +1595,7 @@ EXTERN char e_notmp[] INIT(= N_("E483: Can't get temp file name"));
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)
+#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,10 +1614,10 @@ EXTERN char e_prev_dir[] INIT(= N_("E459: Cannot go back to previous directory")
#ifdef FEAT_QUICKFIX
EXTERN char e_quickfix[] INIT(= N_("E42: No Errors"));
-EXTERN char e_loclist[] INIT(= N_("E776: No location list"));
+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_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,17 +1628,23 @@ EXTERN char e_readonlyvar[] INIT(= N_("E46: Cannot change read-only variable \"%
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_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_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,7 +1677,12 @@ EXTERN char e_write[] INIT(= N_("E80: Error while writing"));
EXTERN char e_zerocount[] INIT(= N_("E939: Positive count required"));
#ifdef FEAT_EVAL
EXTERN char e_usingsid[] INIT(= N_("E81: Using <SID> not in a script context"));
-EXTERN char e_missingparen[] INIT(= N_("E107: Missing parentheses: %s"));
+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,6 +1710,18 @@ EXTERN char e_menuothermode[] INIT(= N_("E328: Menu only exists in another mode"
#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,6 +1797,9 @@ EXTERN int *eval_lavars_used INIT(= NULL);
// 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
diff --git a/src/gui.c b/src/gui.c
index 1249fab22..eec89d5aa 100644
--- a/src/gui.c
+++ b/src/gui.c
@@ -519,7 +519,7 @@ gui_init(void)
if (vim_strchr(p_go, GO_NOSYSMENU) == NULL)
{
sys_menu = TRUE;
- do_source((char_u *)SYS_MENU_FILE, FALSE, DOSO_NONE);
+ do_source((char_u *)SYS_MENU_FILE, FALSE, DOSO_NONE, NULL);
sys_menu = FALSE;
}
#endif
@@ -540,8 +540,8 @@ gui_init(void)
{
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);
+ && do_source(use_gvimrc, FALSE, DOSO_NONE, NULL) != OK)
+ semsg(_("E230: Cannot read from \"%s\""), use_gvimrc, NULL);
}
else
{
@@ -549,7 +549,7 @@ gui_init(void)
* 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);
+ do_source((char_u *)SYS_GVIMRC_FILE, FALSE, DOSO_NONE, NULL);
#endif
/*
@@ -563,19 +563,20 @@ gui_init(void)
*/
if (process_env((char_u *)"GVIMINIT", FALSE) == FAIL
&& do_source((char_u *)USR_GVIMRC_FILE, TRUE,
- DOSO_GVIMRC) == FAIL
+ DOSO_GVIMRC, NULL) == FAIL
#ifdef USR_GVIMRC_FILE2
&& do_source((char_u *)USR_GVIMRC_FILE2, TRUE,
- DOSO_GVIMRC) == FAIL
+ DOSO_GVIMRC, NULL) == FAIL
#endif
#ifdef USR_GVIMRC_FILE3
&& do_source((char_u *)USR_GVIMRC_FILE3, TRUE,
- DOSO_GVIMRC) == FAIL
+ DOSO_GVIMRC, NULL) == FAIL
#endif
)
{
#ifdef USR_GVIMRC_FILE4
- (void)do_source((char_u *)USR_GVIMRC_FILE4, TRUE, DOSO_GVIMRC);
+ (void)do_source((char_u *)USR_GVIMRC_FILE4, TRUE,
+ DOSO_GVIMRC, NULL);
#endif
}
@@ -623,7 +624,7 @@ gui_init(void)
(char_u *)GVIMRC_FILE, FALSE, TRUE) != FPC_SAME
#endif
)
- do_source((char_u *)GVIMRC_FILE, TRUE, DOSO_GVIMRC);
+ do_source((char_u *)GVIMRC_FILE, TRUE, DOSO_GVIMRC, NULL);
if (secure == 2)
need_wait_return = TRUE;
diff --git a/src/if_lua.c b/src/if_lua.c
index b80b6c9e2..1eb7a77a1 100644
--- a/src/if_lua.c
+++ b/src/if_lua.c
@@ -841,8 +841,7 @@ luaV_list_newindex(lua_State *L)
if (lua_isnil(L, 3)) // remove?
{
vimlist_remove(l, li, li);
- clear_tv(&li->li_tv);
- vim_free(li);
+ listitem_free(l, li);
}
else
{
diff --git a/src/if_py_both.h b/src/if_py_both.h
index af4b98dd0..45bfeec59 100644
--- a/src/if_py_both.h
+++ b/src/if_py_both.h
@@ -785,6 +785,7 @@ VimToPython(typval_T *our_tv, int depth, PyObject *lookup_dict)
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,6 +2256,7 @@ ListNew(PyTypeObject *subtype, list_T *list)
return NULL;
self->list = list;
++list->lv_refcount;
+ range_list_materialize(list);
pyll_add((PyObject *)(self), &self->ref, &lastlist);
@@ -2302,7 +2304,7 @@ list_py_concat(list_T *l, PyObject *obj, PyObject *lookup_dict)
{
Py_DECREF(item);
Py_DECREF(iterator);
- listitem_free(li);
+ listitem_free(l, li);
return -1;
}
@@ -2662,7 +2664,7 @@ ListAssSlice(ListObject *self, Py_ssize_t first,
}
for (i = 0; i < numreplaced; i++)
- listitem_free(lis[i]);
+ listitem_free(l, lis[i]);
if (step == 1)
for (i = numreplaced; i < slicelen; i++)
listitem_remove(l, lis[i]);
@@ -2822,6 +2824,7 @@ ListIter(ListObject *self)
return NULL;
}
+ range_list_materialize(l);
list_add_watch(l, &lii->lw);
lii->lw.lw_item = l->lv_first;
lii->list = l;
@@ -3018,6 +3021,7 @@ FunctionConstructor(PyTypeObject *subtype, PyObject *args, PyObject *kwargs)
return NULL;
}
argslist = argstv.vval.v_list;
+ range_list_materialize(argslist);
argc = argslist->lv_len;
if (argc != 0)
@@ -6386,6 +6390,7 @@ ConvertToPyObject(typval_T *tv)
(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);
diff --git a/src/insexpand.c b/src/insexpand.c
index 54f8cb351..454e2e3f9 100644
--- a/src/insexpand.c
+++ b/src/insexpand.c
@@ -576,8 +576,8 @@ ins_compl_add(
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
+ 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
diff --git a/src/json.c b/src/json.c
index 64ef93fab..6d7b1939e 100644
--- a/src/json.c
+++ b/src/json.c
@@ -215,7 +215,7 @@ json_encode_item(garray_T *gap, typval_T *val, int copyID, int options)
case VAR_NUMBER:
vim_snprintf((char *)numbuf, NUMBUFLEN, "%lld",
- (long_long_T)val->vval.v_number);
+ (long_long_T)val->vval.v_number);
ga_concat(gap, numbuf);
break;
@@ -350,6 +350,7 @@ json_encode_item(garray_T *gap, typval_T *val, int copyID, int options)
break;
#endif
case VAR_UNKNOWN:
+ case VAR_VOID:
internal_error("json_encode_item()");
return FAIL;
}
diff --git a/src/list.c b/src/list.c
index 68be03452..4071a7d7f 100644
--- a/src/list.c
+++ b/src/list.c
@@ -65,6 +65,17 @@ list_fix_watch(list_T *l, listitem_T *item)
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,14 +87,7 @@ list_alloc(void)
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;
- }
+ list_init(l);
return l;
}
@@ -101,6 +105,59 @@ list_alloc_id(alloc_id_T id UNUSED)
}
/*
+ * 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,13 +220,14 @@ list_free_contents(list_T *l)
{
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);
- }
+ 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,13 +308,26 @@ listitem_alloc(void)
}
/*
- * Free a list item. Also clears the value. Does not notify watchers.
+ * 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(listitem_T *item)
+listitem_free(list_T *l, listitem_T *item)
{
clear_tv(&item->li_tv);
- vim_free(item);
+ list_free_item(l, item);
}
/*
@@ -266,7 +337,7 @@ listitem_free(listitem_T *item)
listitem_remove(list_T *l, listitem_T *item)
{
vimlist_remove(l, item, item);
- listitem_free(item);
+ listitem_free(l, item);
}
/*
@@ -299,6 +370,9 @@ list_equal(
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,6 +403,8 @@ list_find(list_T *l, long n)
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,6 +474,26 @@ list_find_nr(
{
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,6 +533,7 @@ list_idx_of_item(list_T *l, listitem_T *item)
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,6 +548,7 @@ list_idx_of_item(list_T *l, listitem_T *item)
void
list_append(list_T *l, listitem_T *item)
{
+ range_list_materialize(l);
if (l->lv_last == NULL)
{
// empty list
@@ -469,7 +567,7 @@ list_append(list_T *l, listitem_T *item)
}
/*
- * Append typval_T "tv" to the end of list "l".
+ * Append typval_T "tv" to the end of list "l". "tv" is copied.
* Return FAIL when out of memory.
*/
int
@@ -485,6 +583,22 @@ list_append_tv(list_T *l, typval_T *tv)
}
/*
+ * 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,6 +698,7 @@ list_insert_tv(list_T *l, typval_T *tv, listitem_T *item)
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,6 +733,9 @@ list_extend(list_T *l1, list_T *l2, listitem_T *bef)
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,6 +793,7 @@ list_copy(list_T *orig, int deep, int copyID)
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,6 +834,8 @@ vimlist_remove(list_T *l, listitem_T *item, listitem_T *item2)
{
listitem_T *ip;
+ range_list_materialize(l);
+
// notify watchers
for (ip = item; ip != NULL; ip = ip->li_next)
{
@@ -748,6 +869,7 @@ list2string(typval_T *tv, int copyID, int restore_copyID)
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,6 +907,7 @@ list_join_inner(
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,7 +1038,7 @@ f_join(typval_T *argvars, typval_T *rettv)
* Return OK or FAIL.
*/
int
-get_list_tv(char_u **arg, typval_T *rettv, int evaluate)
+get_list_tv(char_u **arg, typval_T *rettv, int evaluate, int do_error)
{
list_T *l = NULL;
typval_T tv;
@@ -950,7 +1073,8 @@ get_list_tv(char_u **arg, typval_T *rettv, int evaluate)
break;
if (**arg != ',')
{
- semsg(_("E696: Missing comma in List: %s"), *arg);
+ if (do_error)
+ semsg(_("E696: Missing comma in List: %s"), *arg);
goto failret;
}
*arg = skipwhite(*arg + 1);
@@ -958,7 +1082,8 @@ get_list_tv(char_u **arg, typval_T *rettv, int evaluate)
if (**arg != ']')
{
- semsg(_("E697: Missing end of List ']': %s"), *arg);
+ if (do_error)
+ semsg(_("E697: Missing end of List ']': %s"), *arg);
failret:
if (evaluate)
list_free(l);
@@ -983,6 +1108,7 @@ write_list(FILE *fd, list_T *list, int binary)
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,6 +1195,7 @@ f_list2str(typval_T *argvars, typval_T *rettv)
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,7 +1250,7 @@ list_remove(typval_T *argvars, typval_T *rettv, char_u *arg_errmsg)
// Remove one item, return its value.
vimlist_remove(l, item, item);
*rettv = item->li_tv;
- vim_free(item);
+ list_free_item(l, item);
}
else
{
@@ -1361,6 +1488,7 @@ do_sort_uniq(typval_T *argvars, typval_T *rettv, int sort)
TRUE))
goto theend;
rettv_list_set(rettv, l);
+ range_list_materialize(l);
len = list_len(l);
if (len <= 1)
@@ -1519,7 +1647,7 @@ do_sort_uniq(typval_T *argvars, typval_T *rettv, int sort)
else
l->lv_last = ptrs[i].item;
list_fix_watch(l, li);
- listitem_free(li);
+ listitem_free(l, li);
l->lv_len--;
}
}
@@ -1729,6 +1857,7 @@ filter_map(typval_T *argvars, typval_T *rettv, int map)
// 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))
diff --git a/src/macros.h b/src/macros.h
index 455705ca4..6631d4d9b 100644
--- a/src/macros.h
+++ b/src/macros.h
@@ -302,6 +302,10 @@
# 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.
diff --git a/src/main.c b/src/main.c
index 117c2072e..68a419eaa 100644
--- a/src/main.c
+++ b/src/main.c
@@ -482,7 +482,7 @@ vim_main2(void)
# else
(char_u *)"plugin/**/*.vim",
# endif
- DIP_ALL | DIP_NOAFTER);
+ DIP_ALL | DIP_NOAFTER, NULL);
TIME_MSG("loading plugins");
vim_free(rtp_copy);
@@ -3169,7 +3169,7 @@ source_startup_scripts(mparm_T *parmp)
*/
if (parmp->evim_mode)
{
- (void)do_source((char_u *)EVIM_FILE, FALSE, DOSO_NONE);
+ (void)do_source((char_u *)EVIM_FILE, FALSE, DOSO_NONE, NULL);
TIME_MSG("source evim file");
}
@@ -3180,7 +3180,7 @@ source_startup_scripts(mparm_T *parmp)
if (parmp->use_vimrc != NULL)
{
if (STRCMP(parmp->use_vimrc, "DEFAULTS") == 0)
- do_source((char_u *)VIM_DEFAULTS_FILE, FALSE, DOSO_NONE);
+ 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,7 +3191,7 @@ source_startup_scripts(mparm_T *parmp)
}
else
{
- if (do_source(parmp->use_vimrc, FALSE, DOSO_NONE) != OK)
+ if (do_source(parmp->use_vimrc, FALSE, DOSO_NONE, NULL) != OK)
semsg(_("E282: Cannot read from \"%s\""), parmp->use_vimrc);
}
}
@@ -3209,10 +3209,11 @@ source_startup_scripts(mparm_T *parmp)
* 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);
+ (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);
+ (void)do_source((char_u *)"$VIMRUNTIME/macmap.vim", FALSE,
+ DOSO_NONE, NULL);
#endif
/*
@@ -3227,28 +3228,31 @@ source_startup_scripts(mparm_T *parmp)
*/
if (process_env((char_u *)"VIMINIT", TRUE) != OK)
{
- if (do_source((char_u *)USR_VIMRC_FILE, TRUE, DOSO_VIMRC) == FAIL
+ 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) == FAIL
+ DOSO_VIMRC, NULL) == FAIL
#endif
#ifdef USR_VIMRC_FILE3
&& do_source((char_u *)USR_VIMRC_FILE3, TRUE,
- DOSO_VIMRC) == FAIL
+ DOSO_VIMRC, NULL) == FAIL
#endif
#ifdef USR_VIMRC_FILE4
&& do_source((char_u *)USR_VIMRC_FILE4, TRUE,
- DOSO_VIMRC) == FAIL
+ DOSO_VIMRC, NULL) == FAIL
#endif
&& process_env((char_u *)"EXINIT", FALSE) == FAIL
- && do_source((char_u *)USR_EXRC_FILE, FALSE, DOSO_NONE) == 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) == FAIL
+ && 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);
+ do_source((char_u *)VIM_DEFAULTS_FILE, FALSE, DOSO_NONE, NULL);
}
}
@@ -3285,7 +3289,7 @@ source_startup_scripts(mparm_T *parmp)
(char_u *)VIMRC_FILE, FALSE, TRUE) != FPC_SAME
#endif
)
- i = do_source((char_u *)VIMRC_FILE, TRUE, DOSO_VIMRC);
+ i = do_source((char_u *)VIMRC_FILE, TRUE, DOSO_VIMRC, NULL);
if (i == FAIL)
{
@@ -3303,7 +3307,8 @@ source_startup_scripts(mparm_T *parmp)
(char_u *)EXRC_FILE, FALSE, TRUE) != FPC_SAME
#endif
)
- (void)do_source((char_u *)EXRC_FILE, FALSE, DOSO_NONE);
+ (void)do_source((char_u *)EXRC_FILE, FALSE,
+ DOSO_NONE, NULL);
}
}
if (secure == 2)
@@ -3334,7 +3339,7 @@ main_start_gui(void)
#endif // NO_VIM_MAIN
/*
- * Get an environment variable, and execute it as Ex commands.
+ * Get an environment variable and execute it as Ex commands.
* Returns FAIL if the environment variable was not executed, OK otherwise.
*/
int
diff --git a/src/message.c b/src/message.c
index 7a6e34609..935fd8c44 100644
--- a/src/message.c
+++ b/src/message.c
@@ -847,6 +847,17 @@ emsg_invreg(int name)
}
/*
+ * 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()!
diff --git a/src/misc1.c b/src/misc1.c
index fb75e1930..85ab727d3 100644
--- a/src/misc1.c
+++ b/src/misc1.c
@@ -2067,13 +2067,17 @@ match_user(char_u *name)
concat_str(char_u *str1, char_u *str2)
{
char_u *dest;
- size_t l = STRLEN(str1);
+ size_t l = str1 == NULL ? 0 : STRLEN(str1);
- dest = alloc(l + STRLEN(str2) + 1L);
+ dest = alloc(l + (str2 == NULL ? 0 : STRLEN(str2)) + 1L);
if (dest != NULL)
{
- STRCPY(dest, str1);
- STRCPY(dest + l, str2);
+ if (str1 == NULL)
+ *dest = NUL;
+ else
+ STRCPY(dest, str1);
+ if (str2 != NULL)
+ STRCPY(dest + l, str2);
}
return dest;
}
diff --git a/src/proto.h b/src/proto.h
index e8af1e27c..7836c528e 100644
--- a/src/proto.h
+++ b/src/proto.h
@@ -226,6 +226,11 @@ void mbyte_im_set_active(int active_arg);
# 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
diff --git a/src/proto/blob.pro b/src/proto/blob.pro
index 706a83ea0..3bc662545 100644
--- a/src/proto/blob.pro
+++ b/src/proto/blob.pro
@@ -2,7 +2,7 @@
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);
+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);
diff --git a/src/proto/eval.pro b/src/proto/eval.pro
index b08607922..14b5f261d 100644
--- a/src/proto/eval.pro
+++ b/src/proto/eval.pro
@@ -27,7 +27,12 @@ void set_context_for_expression(expand_T *xp, char_u *arg, cmdidx_T cmdidx);
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,6 +48,7 @@ char_u *echo_string(typval_T *tv, char_u **tofree, char_u *numbuf, int copyID);
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,6 +72,7 @@ char_u *tv_get_string_chk(typval_T *varp);
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);
diff --git a/src/proto/evalfunc.pro b/src/proto/evalfunc.pro
index 064f12f8d..59ff35c69 100644
--- a/src/proto/evalfunc.pro
+++ b/src/proto/evalfunc.pro
@@ -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,6 +18,7 @@ win_T *get_optional_window(typval_T *argvars, int idx);
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);
diff --git a/src/proto/evalvars.pro b/src/proto/evalvars.pro
index bb8b5a1c6..1d557f528 100644
--- a/src/proto/evalvars.pro
+++ b/src/proto/evalvars.pro
@@ -13,10 +13,11 @@ list_T *eval_spell_expr(char_u *badword, char_u *expr);
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 is_const, char_u *op);
-char_u *skip_var_list(char_u *arg, int *var_count, int *semicolon);
+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,8 +28,10 @@ char *get_var_special_name(int nr);
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,6 +53,8 @@ int get_var_tv(char_u *name, int len, typval_T *rettv, dictitem_T **dip, int ver
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,7 +63,7 @@ void unref_var_dict(dict_T *dict);
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);
+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);
diff --git a/src/proto/ex_docmd.pro b/src/proto/ex_docmd.pro
index f5942f5c1..af70e2dba 100644
--- a/src/proto/ex_docmd.pro
+++ b/src/proto/ex_docmd.pro
@@ -7,6 +7,7 @@ void *getline_cookie(char_u *(*fgetline)(int, void *, int, int), void *cookie);
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);
diff --git a/src/proto/ex_eval.pro b/src/proto/ex_eval.pro
index bd68eeaba..929f47987 100644
--- a/src/proto/ex_eval.pro
+++ b/src/proto/ex_eval.pro
@@ -8,7 +8,9 @@ void free_global_msglist(void);
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);
diff --git a/src/proto/list.pro b/src/proto/list.pro
index ae8a7328e..d60463d2d 100644
--- a/src/proto/list.pro
+++ b/src/proto/list.pro
@@ -3,6 +3,8 @@ void list_add_watch(list_T *l, listwatch_T *lw);
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,7 +13,8 @@ int list_free_nonref(int copyID);
void list_free_items(int copyID);
void list_free(list_T *l);
listitem_T *listitem_alloc(void);
-void listitem_free(listitem_T *item);
+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,6 +24,7 @@ char_u *list_find_str(list_T *l, long idx);
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,7 +38,7 @@ void vimlist_remove(list_T *l, listitem_T *item, listitem_T *item2);
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 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);
diff --git a/src/proto/message.pro b/src/proto/message.pro
index a34ca3dda..5c9ca6cb1 100644
--- a/src/proto/message.pro
+++ b/src/proto/message.pro
@@ -13,6 +13,7 @@ int emsg(char *s);
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);
diff --git a/src/proto/scriptfile.pro b/src/proto/scriptfile.pro
index 7972e849a..111e855a7 100644
--- a/src/proto/scriptfile.pro
+++ b/src/proto/scriptfile.pro
@@ -8,7 +8,7 @@ void ex_runtime(exarg_T *eap);
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 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,7 +21,7 @@ void ex_options(exarg_T *eap);
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 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);
diff --git a/src/proto/userfunc.pro b/src/proto/userfunc.pro
index 06f41e41a..74bcabd8c 100644
--- a/src/proto/userfunc.pro
+++ b/src/proto/userfunc.pro
@@ -1,26 +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);
+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);
diff --git a/src/proto/vim9compile.pro b/src/proto/vim9compile.pro
new file mode 100644
index 000000000..4f06e9d9c
--- /dev/null
+++ b/src/proto/vim9compile.pro
@@ -0,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 : */
diff --git a/src/proto/vim9execute.pro b/src/proto/vim9execute.pro
new file mode 100644
index 000000000..4f7262d74
--- /dev/null
+++ b/src/proto/vim9execute.pro
@@ -0,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 : */
diff --git a/src/proto/vim9script.pro b/src/proto/vim9script.pro
new file mode 100644
index 000000000..29fa269db
--- /dev/null
+++ b/src/proto/vim9script.pro
@@ -0,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 : */
diff --git a/src/scriptfile.c b/src/scriptfile.c
index 78a80d02b..120c64351 100644
--- a/src/scriptfile.c
+++ b/src/scriptfile.c
@@ -182,9 +182,9 @@ ex_runtime(exarg_T *eap)
}
static void
-source_callback(char_u *fname, void *cookie UNUSED)
+source_callback(char_u *fname, void *cookie)
{
- (void)do_source(fname, FALSE, DOSO_NONE);
+ (void)do_source(fname, FALSE, DOSO_NONE, cookie);
}
/*
@@ -399,16 +399,16 @@ do_in_runtimepath(
int
source_runtime(char_u *name, int flags)
{
- return source_in_path(p_rtp, name, 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)
+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, NULL);
+ return do_in_path_and_pp(path, name, flags, source_callback, ret_sid);
}
@@ -427,7 +427,7 @@ source_all_matches(char_u *pat)
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);
+ (void)do_source(files[i], FALSE, DOSO_NONE, NULL);
FreeWild(num_files, files);
}
}
@@ -930,7 +930,7 @@ cmd_source(char_u *fname, exarg_T *eap)
);
// ":source" read ex commands
- else if (do_source(fname, FALSE, DOSO_NONE) == FAIL)
+ else if (do_source(fname, FALSE, DOSO_NONE, NULL) == FAIL)
semsg(_(e_notopen), fname);
}
@@ -1063,16 +1063,20 @@ fopen_noinh_readbin(char *filename)
/*
* 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
+ * 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 is_vimrc, // DOSO_ value
+ int *ret_sid UNUSED)
{
struct source_cookie cookie;
char_u *p;
@@ -1085,6 +1089,7 @@ do_source(
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,6 +1119,37 @@ do_source(
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,33 +1275,32 @@ do_source(
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)
+ if (sid > 0)
{
- 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;
- }
+ 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;
+ }
}
- if (current_sctx.sc_sid == 0)
+ 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,13 +1308,17 @@ do_source(
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;
+ 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
- SCRIPT_ITEM(script_items.ga_len).sn_prof_on = FALSE;
+ si->sn_prof_on = FALSE;
# endif
}
si = &SCRIPT_ITEM(current_sctx.sc_sid);
@@ -1295,6 +1334,8 @@ do_source(
else
si->sn_dev_valid = FALSE;
# endif
+ if (ret_sid != NULL)
+ *ret_sid = current_sctx.sc_sid;
}
# ifdef FEAT_PROFILE
@@ -1392,6 +1433,15 @@ do_source(
#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,7 +1538,9 @@ free_scriptnames(void)
{
// 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,6 +1841,11 @@ ex_scriptversion(exarg_T *eap UNUSED)
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)
diff --git a/src/session.c b/src/session.c
index 418fcba57..5bebb05bb 100644
--- a/src/session.c
+++ b/src/session.c
@@ -980,7 +980,7 @@ ex_loadview(exarg_T *eap)
fname = get_view_file(*eap->arg);
if (fname != NULL)
{
- do_source(fname, FALSE, DOSO_NONE);
+ do_source(fname, FALSE, DOSO_NONE, NULL);
vim_free(fname);
}
}
diff --git a/src/structs.h b/src/structs.h
index 7e1508eb9..201a89bce 100644
--- a/src/structs.h
+++ b/src/structs.h
@@ -67,6 +67,9 @@ typedef struct terminal_S term_T;
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,30 +1301,43 @@ typedef struct {
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,
- 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
+ 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,19 +1396,34 @@ struct listwatch_S
/*
* 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
- 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"
+ 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_idx; // cached index of an item
+ 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,7 +1444,7 @@ typedef struct {
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_flags; // DI_FLAGS_ flags (only used for variable)
char_u di_key[1]; // key (actually longer!)
};
typedef struct dictitem_S dictitem_T;
@@ -1426,16 +1457,18 @@ typedef struct dictitem_S dictitem_T;
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_flags; // 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
+// 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,12 +1503,21 @@ typedef struct funccall_S funccall_T;
*/
typedef struct
{
- int uf_varargs; // variable nr of arguments
- int uf_flags;
+ 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,17 +1620,50 @@ typedef struct
} 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
- char_u *sn_name;
+ 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,6 +3766,12 @@ typedef enum
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,6 +3885,8 @@ typedef struct
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
diff --git a/src/syntax.c b/src/syntax.c
index e99eb8669..d16c22010 100644
--- a/src/syntax.c
+++ b/src/syntax.c
@@ -4736,7 +4736,7 @@ syn_cmd_include(exarg_T *eap, int syncing UNUSED)
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
+ 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;
diff --git a/src/testdir/Make_all.mak b/src/testdir/Make_all.mak
index 2cccc8e90..98ea494db 100644
--- a/src/testdir/Make_all.mak
+++ b/src/testdir/Make_all.mak
@@ -90,10 +90,10 @@ NEW_TESTS = \
test_digraph \
test_display \
test_edit \
+ test_environ \
test_erasebackword \
test_escaped_glob \
test_eval_stuff \
- test_environ \
test_ex_equal \
test_ex_undo \
test_ex_z \
@@ -268,6 +268,8 @@ NEW_TESTS = \
test_utf8 \
test_utf8_comparisons \
test_vartabs \
+ test_vim9_expr \
+ test_vim9_script \
test_viminfo \
test_vimscript \
test_virtualedit \
@@ -435,6 +437,8 @@ NEW_TESTS_RES = \
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 \
diff --git a/src/testdir/test_vim9_expr.vim b/src/testdir/test_vim9_expr.vim
new file mode 100644
index 000000000..ea79c0457
--- /dev/null
+++ b/src/testdir/test_vim9_expr.vim
@@ -0,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
diff --git a/src/testdir/test_vim9_script.vim b/src/testdir/test_vim9_script.vim
new file mode 100644
index 000000000..ce1ab34d6
--- /dev/null
+++ b/src/testdir/test_vim9_script.vim
@@ -0,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<string> = ['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<string> = ['sdf', 'asdf']
+ let list2: list<number> = [1, 2, 3]
+
+ " TODO: does not work yet
+ " let listS: list<string> = []
+ " let listN: list<number> = []
+
+ let dict1: dict<string> = #{key: 'value'}
+ let dict2: dict<number> = #{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<string> = [123]'], 'expected list<string> but got list<number>')
+ call CheckDefFailure(['let var: list<number> = ["xx"]'], 'expected list<number> but got list<string>')
+
+ call CheckDefFailure(['let var: dict<string> = #{key: 123}'], 'expected dict<string> but got dict<number>')
+ call CheckDefFailure(['let var: dict<number> = #{key: "xx"}'], 'expected dict<number> but got dict<string>')
+
+ 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>): 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<number>)
+ listvar = arg
+ enddef
+ [1, 2, 3]->ListFunc()
+ assert_equal([1, 2, 3], listvar)
+
+ let dictvar = {}
+ def DictFunc(arg: dict<number>)
+ 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
diff --git a/src/testing.c b/src/testing.c
index ac9beea43..3ab9dcdc9 100644
--- a/src/testing.c
+++ b/src/testing.c
@@ -758,9 +758,10 @@ f_test_refcount(typval_T *argvars, typval_T *rettv)
switch (argvars[0].v_type)
{
case VAR_UNKNOWN:
+ case VAR_VOID:
case VAR_NUMBER:
- case VAR_FLOAT:
case VAR_BOOL:
+ case VAR_FLOAT:
case VAR_SPECIAL:
case VAR_STRING:
break;
@@ -781,7 +782,7 @@ f_test_refcount(typval_T *argvars, typval_T *rettv)
{
ufunc_T *fp;
- fp = find_func(argvars[0].vval.v_string);
+ fp = find_func(argvars[0].vval.v_string, NULL);
if (fp != NULL)
retval = fp->uf_refcount;
}
diff --git a/src/userfunc.c b/src/userfunc.c
index 29e0fac05..808ed968a 100644
--- a/src/userfunc.c
+++ b/src/userfunc.c
@@ -22,8 +22,8 @@
#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]
+#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,13 +63,84 @@ func_tbl_get(void)
}
/*
+ * 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.
*/
- static int
+ 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,12 +149,13 @@ get_function_args(
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 (argtypes != NULL)
+ ga_init2(argtypes, (int)sizeof(char_u *), 3);
if (default_args != NULL)
ga_init2(default_args, (int)sizeof(char_u *), 3);
@@ -101,46 +173,29 @@ get_function_args(
*varargs = TRUE;
p += 3;
mustend = TRUE;
+
+ if (argtypes != NULL)
+ {
+ // ...name: list<type>
+ 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;
- 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);
+ p = one_function_arg(p, newargs, argtypes, skip);
+ if (p == 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;
@@ -266,7 +321,7 @@ get_lambda_tv(char_u **arg, typval_T *rettv, int evaluate)
ga_init(&newlines);
// First, check if this is a lambda expression. "->" must exist.
- ret = get_function_args(&start, '-', NULL, NULL, NULL, TRUE);
+ ret = get_function_args(&start, '-', NULL, NULL, NULL, NULL, TRUE);
if (ret == FAIL || *start != '>')
return NOTDONE;
@@ -276,7 +331,8 @@ get_lambda_tv(char_u **arg, typval_T *rettv, int evaluate)
else
pnewargs = NULL;
*arg = skipwhite(*arg + 1);
- ret = get_function_args(arg, '-', pnewargs, &varargs, NULL, FALSE);
+ // TODO: argument types
+ ret = get_function_args(arg, '-', pnewargs, NULL, &varargs, NULL, FALSE);
if (ret == FAIL || **arg != '>')
goto errret;
@@ -307,6 +363,7 @@ get_lambda_tv(char_u **arg, typval_T *rettv, int evaluate)
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,6 +402,7 @@ get_lambda_tv(char_u **arg, typval_T *rettv, int evaluate)
#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,17 +639,73 @@ fname_trans_sid(char_u *name, char_u *fname_buf, char_u **tofree, int *error)
}
/*
+ * 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.
*/
- ufunc_T *
-find_func(char_u *name)
+ 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,6 +875,127 @@ cleanup_function_call(funccall_T *fc)
}
}
}
+/*
+ * 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,6 +1057,20 @@ call_user_func(
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, "<lambda>", 8) == 0)
islambda = TRUE;
@@ -1146,110 +1395,57 @@ call_user_func(
}
/*
- * 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.
+ * Call a user function after checking the arguments.
*/
- static void
-funccal_unref(funccall_T *fc, ufunc_T *fp, int force)
+ int
+call_user_func_check(
+ ufunc_T *fp,
+ int argcount,
+ typval_T *argvars,
+ typval_T *rettv,
+ funcexe_T *funcexe,
+ dict_T *selfdict)
{
- funccall_T **pfc;
- int i;
-
- if (fc == NULL)
- return;
+ 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;
- 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)
+ /*
+ * Call the user function.
+ * Save and restore search patterns, script variables and
+ * redo buffer.
+ */
+ save_search_patterns();
+ if (!ins_compl_active())
{
- if (fc == *pfc)
- {
- *pfc = fc->caller;
- free_funccal_contents(fc);
- return;
- }
+ saveRedobuff(&save_redo);
+ did_save_redo = TRUE;
}
- 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;
+ ++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 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);
+ return error;
}
/*
@@ -1327,9 +1523,13 @@ free_all_functions(void)
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.
- fp = HI2UF(hi);
if (func_name_refcount(fp->uf_name))
++skipped;
else
@@ -1371,6 +1571,8 @@ free_all_functions(void)
}
if (skipped == 0)
hash_clear(&func_hashtab);
+
+ free_def_functions();
}
#endif
@@ -1379,7 +1581,7 @@ free_all_functions(void)
* lower case letter and doesn't contain AUTOLOAD_CHAR.
* "len" is the length of "name", or -1 for NUL terminated.
*/
- static int
+ int
builtin_function(char_u *name, int len)
{
char_u *p;
@@ -1469,6 +1671,43 @@ call_callback(
}
/*
+ * 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 <SID> 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,7 +1800,7 @@ call_func(
if (partial != NULL && partial->pt_func != NULL)
fp = partial->pt_func;
else
- fp = find_func(rfname);
+ fp = find_func(rfname, NULL);
// Trigger FuncUndefined event, may load the function.
if (fp == NULL
@@ -1570,13 +1809,13 @@ call_func(
&& !aborting())
{
// executed an autocommand, search for the function again
- fp = find_func(rfname);
+ 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);
+ fp = find_func(rfname, NULL);
}
if (fp != NULL && (fp->uf_flags & FC_DELETED))
@@ -1598,43 +1837,8 @@ call_func(
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;
- }
+ error = call_user_func_check(fp, argcount, argvars, rettv,
+ funcexe, selfdict);
}
}
else if (funcexe->basetv != NULL)
@@ -1675,38 +1879,7 @@ theend:
*/
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 <SID> not in a script context: %s"),
- name);
- break;
- case FCERR_DICT:
- emsg_funcname(
- N_("E725: Calling dict function without Dictionary: %s"),
- name);
- break;
- }
+ user_func_error(error, name);
}
// clear the copies made from the partial
@@ -1719,6 +1892,12 @@ theend:
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,16 +1910,21 @@ list_func_head(ufunc_T *fp, int indent)
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_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,6 +1938,21 @@ list_func_head(ufunc_T *fp, int indent)
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,7 +1992,9 @@ trans_function_name(
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,6 +2135,10 @@ trans_function_name(
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 <SID>name() inside a script, translate into <SNR>123_name().
@@ -1941,20 +2146,25 @@ trans_function_name(
*/
if (skip)
lead = 0; // do nothing
- else if (lead > 0)
+ else if (lead > 0 || vim9script)
{
- lead = 3;
- if ((lv.ll_exp_name != NULL && eval_fname_sid(lv.ll_exp_name))
+ if (!vim9script)
+ lead = 3;
+ if (vim9script || (lv.ll_exp_name != NULL
+ && eval_fname_sid(lv.ll_exp_name))
|| eval_fname_sid(*pp))
{
- // It's "s:" or "<SID>"
+ // It's script-local, "s:" or "<SID>"
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);
+ 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,19 +2184,19 @@ trans_function_name(
}
}
- name = alloc(len + lead + 1);
+ name = alloc(len + lead + extra + 1);
if (name != NULL)
{
- if (lead > 0)
+ if (lead > 0 || vim9script)
{
name[0] = K_SPECIAL;
name[1] = KS_EXTRA;
name[2] = (int)KE_SNR;
- if (lead > 3) // If it's "<SID>"
+ if (vim9script || lead > 3) // If it's "<SID>"
STRCPY(name + 3, sid_buf);
}
- mch_memmove(name + lead, lv.ll_name, (size_t)len);
- name[lead + len] = NUL;
+ mch_memmove(name + lead + extra, lv.ll_name, (size_t)len);
+ name[lead + extra + len] = NUL;
}
*pp = end;
@@ -2012,19 +2222,22 @@ ex_function(exarg_T *eap)
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;
- hashtab_T *ht;
int todo;
hashitem_T *hi;
int do_concat = TRUE;
@@ -2048,7 +2261,8 @@ ex_function(exarg_T *eap)
{
--todo;
fp = HI2UF(hi);
- if (message_filtered(fp->uf_name))
+ 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,8 +2298,9 @@ ex_function(exarg_T *eap)
{
--todo;
fp = HI2UF(hi);
- if (!isdigit(*fp->uf_name)
- && vim_regexec(&regmatch, fp->uf_name, 0))
+ if ((fp->uf_flags & FC_DEAD) == 0
+ && !isdigit(*fp->uf_name)
+ && vim_regexec(&regmatch, fp->uf_name, 0))
list_func_head(fp, FALSE);
}
}
@@ -2098,6 +2313,10 @@ ex_function(exarg_T *eap)
return;
}
+ ga_init(&newargs);
+ ga_init(&argtypes);
+ ga_init(&default_args);
+
/*
* Get the function name. There are these situations:
* func normal function name
@@ -2155,7 +2374,7 @@ ex_function(exarg_T *eap)
*p = NUL;
if (!eap->skip && !got_int)
{
- fp = find_func(name);
+ fp = find_func(name, NULL);
if (fp != NULL)
{
list_func_head(fp, TRUE);
@@ -2176,7 +2395,10 @@ ex_function(exarg_T *eap)
if (!got_int)
{
msg_putchar('\n');
- msg_puts(" endfunction");
+ if (fp->uf_dfunc_idx >= 0)
+ msg_puts(" enddef");
+ else
+ msg_puts(" endfunction");
}
}
else
@@ -2231,43 +2453,58 @@ ex_function(exarg_T *eap)
emsg(_("E862: Cannot use g: here"));
}
- if (get_function_args(&p, ')', &newargs, &varargs,
- &default_args, eap->skip) == FAIL)
+ if (get_function_args(&p, ')', &newargs,
+ eap->cmdidx == CMD_def ? &argtypes : NULL,
+ &varargs, &default_args, eap->skip) == FAIL)
goto errret_2;
- // find extra arguments "range", "dict", "abort" and "closure"
- for (;;)
+ if (eap->cmdidx == CMD_def)
{
- p = skipwhite(p);
- if (STRNCMP(p, "range", 5) == 0)
+ // find the return type: :def Func(): type
+ if (*p == ':')
{
- 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;
+ 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 if (STRNCMP(p, "closure", 7) == 0)
+ }
+ else
+ // find extra arguments "range", "dict", "abort" and "closure"
+ for (;;)
{
- flags |= FC_CLOSURE;
- p += 7;
- if (current_funccal == NULL)
+ p = skipwhite(p);
+ if (STRNCMP(p, "range", 5) == 0)
{
- emsg_funcname(N_("E932: Closure function should not be at top level: %s"),
- name == NULL ? (char_u *)"" : name);
- goto erret;
+ 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;
}
- else
- break;
- }
// When there is a line break use what follows for the function body.
// Makes 'exe "func Test()\n...\nendfunc"' work.
@@ -2277,7 +2514,8 @@ ex_function(exarg_T *eap)
emsg(_(e_trailing));
/*
- * Read the body of the function, until ":endfunction" is found.
+ * Read the body of the function, until "}", ":endfunction" or ":enddef" is
+ * found.
*/
if (KeyTyped)
{
@@ -2288,7 +2526,7 @@ ex_function(exarg_T *eap)
{
if (fudi.fd_dict != NULL && fudi.fd_newkey == NULL)
emsg(_(e_funcdict));
- else if (name != NULL && find_func(name) != NULL)
+ else if (name != NULL && find_func(name, NULL) != NULL)
emsg_funcname(e_funcexts, name);
}
@@ -2304,6 +2542,7 @@ ex_function(exarg_T *eap)
indent = 2;
nesting = 0;
+ nesting_def[nesting] = (eap->cmdidx == CMD_def);
for (;;)
{
if (KeyTyped)
@@ -2339,7 +2578,10 @@ ex_function(exarg_T *eap)
lines_left = Rows - 1;
if (theline == NULL)
{
- emsg(_("E126: Missing :endfunction"));
+ if (eap->cmdidx == CMD_def)
+ emsg(_("E1057: Missing :enddef"));
+ else
+ emsg(_("E126: Missing :endfunction"));
goto erret;
}
@@ -2352,7 +2594,7 @@ ex_function(exarg_T *eap)
if (skip_until != NULL)
{
- // Don't check for ":endfunc" between
+ // Don't check for ":endfunc"/":enddef" between
// * ":append" and "."
// * ":python <<EOF" and "EOF"
// * ":let {var-name} =<< [trim] {marker}" and "{marker}"
@@ -2383,8 +2625,9 @@ ex_function(exarg_T *eap)
for (p = theline; VIM_ISWHITE(*p) || *p == ':'; ++p)
;
- // Check for "endfunction".
- if (checkforcmd(&p, "endfunction", 4) && nesting-- == 0)
+ // Check for "endfunction" or "enddef".
+ if (checkforcmd(&p, nesting_def[nesting]
+ ? "enddef" : "endfunction", 4) && nesting-- == 0)
{
char_u *nextcmd = NULL;
@@ -2393,8 +2636,9 @@ ex_function(exarg_T *eap)
else if (line_arg != NULL && *skipwhite(line_arg) != NUL)
nextcmd = line_arg;
else if (*p != NUL && *p != '"' && p_verbose > 0)
- give_warning2(
- (char_u *)_("W22: Text found after :endfunction: %s"),
+ 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,7 +2658,7 @@ ex_function(exarg_T *eap)
// Increase indent inside "if", "while", "for" and "try", decrease
// at "end".
- if (indent > 2 && STRNCMP(p, "end", 3) == 0)
+ if (indent > 2 && (*p == '}' || STRNCMP(p, "end", 3) == 0))
indent -= 2;
else if (STRNCMP(p, "if", 2) == 0
|| STRNCMP(p, "wh", 2) == 0
@@ -2423,7 +2667,8 @@ ex_function(exarg_T *eap)
indent += 2;
// Check for defining a function inside this function.
- if (checkforcmd(&p, "function", 2))
+ c = *p;
+ if (checkforcmd(&p, "function", 2) || checkforcmd(&p, "def", 3))
{
if (*p == '!')
p = skipwhite(p + 1);
@@ -2431,8 +2676,14 @@ ex_function(exarg_T *eap)
vim_free(trans_function_name(&p, TRUE, 0, NULL, NULL));
if (*skipwhite(p) == '(')
{
- ++nesting;
- indent += 2;
+ if (nesting == MAX_FUNC_NESTING - 1)
+ emsg(_("E1058: function nesting too deep"));
+ else
+ {
+ ++nesting;
+ nesting_def[nesting] = (c == 'd');
+ indent += 2;
+ }
}
}
@@ -2537,6 +2788,8 @@ ex_function(exarg_T *eap)
*/
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,12 +2798,14 @@ ex_function(exarg_T *eap)
goto erret;
}
- fp = find_func(name);
+ 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 (!eap->forceit
+ 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,6 +2837,7 @@ ex_function(exarg_T *eap)
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,6 +2907,7 @@ ex_function(exarg_T *eap)
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,6 +2953,61 @@ ex_function(exarg_T *eap)
}
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,10 +3028,22 @@ ex_function(exarg_T *eap)
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,7 +3079,17 @@ translated_function_exists(char_u *name)
{
if (builtin_function(name, -1))
return has_internal_func(name);
- return find_func(name) != NULL;
+ 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,9 +3160,10 @@ get_user_func_name(expand_T *xp, int idx)
++hi;
fp = HI2UF(hi);
- if ((fp->uf_flags & FC_DICT)
+ // don't show dead, dict and lambda functions
+ if ((fp->uf_flags & FC_DEAD) || (fp->uf_flags & FC_DICT)
|| STRNCMP(fp->uf_name, "<lambda>", 8) == 0)
- return (char_u *)""; // don't show dict and lambda functions
+ return (char_u *)"";
if (STRLEN(fp->uf_name) + 4 >= IOSIZE)
return fp->uf_name; // prevents overflow
@@ -2837,7 +3172,7 @@ get_user_func_name(expand_T *xp, int idx)
if (xp->xp_context != EXPAND_USER_FUNC)
{
STRCAT(IObuff, "(");
- if (!fp->uf_varargs && fp->uf_args.ga_len == 0)
+ if (!has_varargs(fp) && fp->uf_args.ga_len == 0)
STRCAT(IObuff, ")");
}
return IObuff;
@@ -2876,7 +3211,7 @@ ex_delfunction(exarg_T *eap)
*p = NUL;
if (!eap->skip)
- fp = find_func(name);
+ fp = find_func(name, NULL);
vim_free(name);
if (!eap->skip)
@@ -2931,7 +3266,7 @@ func_unref(char_u *name)
if (name == NULL || !func_name_refcount(name))
return;
- fp = find_func(name);
+ fp = find_func(name, NULL);
if (fp == NULL && isdigit(*name))
{
#ifdef EXITFREE
@@ -2974,7 +3309,7 @@ func_ref(char_u *name)
if (name == NULL || !func_name_refcount(name))
return;
- fp = find_func(name);
+ fp = find_func(name, NULL);
if (fp != NULL)
++fp->uf_refcount;
else if (isdigit(*name))
@@ -3119,7 +3454,7 @@ ex_call(exarg_T *eap)
if (*startarg != '(')
{
- semsg(_(e_missingparen), eap->arg);
+ semsg(_(e_missing_paren), eap->arg);
goto end;
}
@@ -3444,7 +3779,7 @@ make_partial(dict_T *selfdict_in, typval_T *rettv)
: 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);
+ fp = find_func(fname, NULL);
vim_free(tofree);
}
@@ -3610,7 +3945,7 @@ get_funccal(void)
hashtab_T *
get_funccal_local_ht()
{
- if (current_funccal == NULL)
+ if (current_funccal == NULL || current_funccal->l_vars.dv_refcount == 0)
return NULL;
return &get_funccal()->l_vars.dv_hashtab;
}
@@ -3622,7 +3957,7 @@ get_funccal_local_ht()
dictitem_T *
get_funccal_local_var()
{
- if (current_funccal == NULL)
+ if (current_funccal == NULL || current_funccal->l_vars.dv_refcount == 0)
return NULL;
return &get_funccal()->l_vars_var;
}
@@ -3634,7 +3969,7 @@ get_funccal_local_var()
hashtab_T *
get_funccal_args_ht()
{
- if (current_funccal == NULL)
+ if (current_funccal == NULL || current_funccal->l_vars.dv_refcount == 0)
return NULL;
return &get_funccal()->l_avars.dv_hashtab;
}
@@ -3646,7 +3981,7 @@ get_funccal_args_ht()
dictitem_T *
get_funccal_args_var()
{
- if (current_funccal == NULL)
+ if (current_funccal == NULL || current_funccal->l_vars.dv_refcount == 0)
return NULL;
return &get_funccal()->l_avars_var;
}
@@ -3657,7 +3992,7 @@ get_funccal_args_var()
void
list_func_vars(int *first)
{
- if (current_funccal != NULL)
+ if (current_funccal != NULL && current_funccal->l_vars.dv_refcount > 0)
list_hashtable_vars(&current_funccal->l_vars.dv_hashtab,
"l:", FALSE, first);
}
@@ -3866,7 +4201,7 @@ set_ref_in_func(char_u *name, ufunc_T *fp_in, int copyID)
if (fp_in == NULL)
{
fname = fname_trans_sid(name, fname_buf, &tofree, &error);
- fp = find_func(fname);
+ fp = find_func(fname, NULL);
}
if (fp != NULL)
{
diff --git a/src/version.c b/src/version.c
index 6dfc66df8..3b4a9599e 100644
--- a/src/version.c
+++ b/src/version.c
@@ -743,6 +743,8 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
+ 149,
+/**/
148,
/**/
147,
diff --git a/src/vim.h b/src/vim.h
index 7ad55aa24..9402c4ecf 100644
--- a/src/vim.h
+++ b/src/vim.h
@@ -2151,6 +2151,10 @@ typedef enum {
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
diff --git a/src/vim9.h b/src/vim9.h
new file mode 100644
index 000000000..97941cad2
--- /dev/null
+++ b/src/vim9.h
@@ -0,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
+
diff --git a/src/vim9compile.c b/src/vim9compile.c
new file mode 100644
index 000000000..9f273c579
--- /dev/null
+++ b/src/vim9compile.c
@@ -0,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 <float.h>
+#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 "<type>"; 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: "<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 <type>"));
+ 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
diff --git a/src/vim9execute.c b/src/vim9execute.c
new file mode 100644
index 000000000..10da178eb
--- /dev/null
+++ b/src/vim9execute.c
@@ -0,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 <float.h>
+#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
diff --git a/src/vim9script.c b/src/vim9script.c
new file mode 100644
index 000000000..4596ce35a
--- /dev/null
+++ b/src/vim9script.c
@@ -0,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
diff --git a/src/viminfo.c b/src/viminfo.c
index 24cd4f713..b2b7ab28b 100644
--- a/src/viminfo.c
+++ b/src/viminfo.c
@@ -1327,6 +1327,7 @@ write_viminfo_varlist(FILE *fp)
case VAR_SPECIAL: s = "XPL"; break;
case VAR_UNKNOWN:
+ case VAR_VOID:
case VAR_FUNC:
case VAR_PARTIAL:
case VAR_JOB: