]> git.ipfire.org Git - thirdparty/vim.git/commitdiff
patch 8.2.0149: maintaining a Vim9 branch separately is more work v8.2.0149
authorBram Moolenaar <Bram@vim.org>
Sun, 26 Jan 2020 14:56:19 +0000 (15:56 +0100)
committerBram Moolenaar <Bram@vim.org>
Sun, 26 Jan 2020 14:56:19 +0000 (15:56 +0100)
Problem:    Maintaining a Vim9 branch separately is more work.
Solution:   Merge the Vim9 script changes.

65 files changed:
README.md
README_VIM9.md [new file with mode: 0644]
runtime/doc/Makefile
runtime/doc/eval.txt
runtime/doc/options.txt
runtime/doc/vim9.txt [new file with mode: 0644]
runtime/ftplugin/vim.vim
runtime/indent/vim.vim
runtime/syntax/vim.vim
src/Make_cyg_ming.mak
src/Make_mvc.mak
src/Makefile
src/blob.c
src/channel.c
src/dict.c
src/eval.c
src/evalbuffer.c
src/evalfunc.c
src/evalvars.c
src/ex_cmdidxs.h
src/ex_cmds.h
src/ex_docmd.c
src/ex_eval.c
src/filepath.c
src/globals.h
src/gui.c
src/if_lua.c
src/if_py_both.h
src/insexpand.c
src/json.c
src/list.c
src/macros.h
src/main.c
src/message.c
src/misc1.c
src/proto.h
src/proto/blob.pro
src/proto/eval.pro
src/proto/evalfunc.pro
src/proto/evalvars.pro
src/proto/ex_docmd.pro
src/proto/ex_eval.pro
src/proto/list.pro
src/proto/message.pro
src/proto/scriptfile.pro
src/proto/userfunc.pro
src/proto/vim9compile.pro [new file with mode: 0644]
src/proto/vim9execute.pro [new file with mode: 0644]
src/proto/vim9script.pro [new file with mode: 0644]
src/scriptfile.c
src/session.c
src/structs.h
src/syntax.c
src/testdir/Make_all.mak
src/testdir/test_vim9_expr.vim [new file with mode: 0644]
src/testdir/test_vim9_script.vim [new file with mode: 0644]
src/testing.c
src/userfunc.c
src/version.c
src/vim.h
src/vim9.h [new file with mode: 0644]
src/vim9compile.c [new file with mode: 0644]
src/vim9execute.c [new file with mode: 0644]
src/vim9script.c [new file with mode: 0644]
src/viminfo.c

index c87a26558bf86a9f3f81f4b281259a8a5019f100..4e3d8433c363a2294377d198031ced22bd6d8a96 100644 (file)
--- 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 (file)
index 0000000..b77d013
--- /dev/null
@@ -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!
+```
index 514a01237f507e116acd042daa273ead4a09f229..65dc8436c6ae8b208d76c4067e101d5feb70dd85 100644 (file)
@@ -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
index b580f2fabfb6ef1d448e3ea7153ac81024464570..071f250807533c884ea169ca731673cfe98bedae 100644 (file)
@@ -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
index 876ddf3588a48cf6c7e09d5df61afaae781d550f..847290e29fbfe2f75055f948dec083c8436ba4bb 100644 (file)
@@ -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 (file)
index 0000000..c6c674d
--- /dev/null
@@ -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:
index c1cd8bb62cc1085339a4202cfe9ccd9c8ae9a7d3..751a033045a885cbce5fb0cd4064d99736e018a6 100644 (file)
@@ -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
index db27f197146f3b8a993a017ba88da03931d005e9..b0c0a3916a40d47fb1a00509a94d6af3b8180671 100644 (file)
@@ -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
 
index b7988178ba65e7072e651691da600ee82be9ee5b..5289f723e994b2a38fdbed668cb0015fa6137982 100644 (file)
@@ -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
index f89d8dd3663ef8ede3a61af62b91b13cf81921a1..639f3e5dcbcfbcb4eefa69ca3370bf707e5d2bde 100644 (file)
@@ -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
index 87b716952d6d5fd550e8f5607d116a62f93a9256..3e8eae168f2fb8dbe20d9fc26dafa0fb3bb05799 100644 (file)
@@ -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) \
index 08a32775439285ed5f4b6c1785031865ac69c257..912098f8b3bf5fa3bf436c4ec5f5c4dac814b0bb 100644 (file)
@@ -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 \
index 2a7ec3a73ab64a23919a4649ab3d916ec7cdff72..f105170cfbde971005dacb8743b3d19c0b42bb68 100644 (file)
@@ -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;
        }
index 3aec7c800839ee7b7cb76d7af484d7d0470e071c..5f03068734e7beb12424fe33f8acb7db576864ec 100644 (file)
@@ -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)
index f3f3521672fda987dac7fd175c4d8be3a5a3db88..2ff4ae37f782a4a3665867620042a5cdb94ec2b8 100644 (file)
@@ -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);
index fe6dee1f1b355d1dde9a2f4ad40c1f33bef790ed..72f932498bd69d791370eccc2aa8901971fefd9e 100644 (file)
 # 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)
@@ -3412,11 +3355,121 @@ get_option_tv(
     return ret;
 }
 
+/*
+ * 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);
index 09db525e28154995d0f48e60583ae3865f948d62..d44fb10180fa3e741639101e629ce99df7a83c49 100644 (file)
@@ -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;
index d248f4bf429bdd4e80cc6a42cdec279c5d34f66b..5365227612550cc4a10575c610730208603ce924 100644 (file)
@@ -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;
index d359c7f44cc332cc5deaf3bd7fcbdc6c4ad37f13..ebb30dd22e2677cfe3c15f2be52fe1bf3d5f4da6 100644 (file)
@@ -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:
@@ -1900,6 +1915,22 @@ get_vimvar_dict(void)
     return &vimvardict;
 }
 
+/*
+ * 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".
  */
@@ -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)
@@ -2365,6 +2416,58 @@ find_var_in_ht(
     return HI2DI(hi);
 }
 
+/*
+ * 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.
@@ -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);
        }
index c43e1f2d072083ba0fcfd3db2a4fa627fd85ba8b..790cbf4b59911774368c49773a43da6903f13b28 100644 (file)
@@ -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;
index 53e49d519cc6462d60c4675ccebceb4f55a120d6..10b9ed72338d46615fc0cf3b5578e5fb852c3812 100644 (file)
@@ -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),
index 193cfcfdffab49b0849bf090fadc15324319ac80..0e6b8dc89b87c8bbbb817219e8d0d03fd8ba213e 100644 (file)
@@ -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.
index 382e99e0d2b96aea8c4a750c05e30f4d86fbdc4f..70b52a3699e62182fd52bcf8341ee5d2b41e42f6 100644 (file)
@@ -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
     {
        /*
index ef5edae2349f338e7d8375e6a37e8ac5ccf38c79..04026a0986acf32bca7d55fc73cf2abc16c53445 100644 (file)
@@ -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;
index 34430d3e0faff36cc3432707f12451bcc84d08fb..6a73bc135f6a115c83bb93acd1da34c7461ff7b8 100644 (file)
@@ -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
index 1249fab22096156e75cf679ed14de72a1f409264..eec89d5aa265b0ba1a81e7c475a6878069942568 100644 (file)
--- 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;
index b80b6c9e26c5c9d69c1094d3f30d33be4e43189f..1eb7a77a143aa5556446d5a053640f94671f332a 100644 (file)
@@ -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
     {
index af4b98dd06f50d87604f1c799f6479bdc62e6776..45bfeec593f067ef99bd932f89c65e60e9e281ae 100644 (file)
@@ -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);
index 54f8cb35122c8376b6b339b203c213039b7dd856..454e2e3f9c85b8aff69df623d1b089b217aabc16 100644 (file)
@@ -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
index 64ef93fabb4731a848d77640a47dc7371882af82..6d7b1939e0ea5535874ce43be7e7b42a616e9973 100644 (file)
@@ -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;
     }
index 68be0345238111ac7d21491a5080bb284463805e..4071a7d7fe3a15161da45c7d4caf42df36b367a8 100644 (file)
@@ -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;
 }
 
@@ -100,6 +104,59 @@ list_alloc_id(alloc_id_T id UNUSED)
     return (list_alloc());
 }
 
+/*
+ * 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
@@ -484,6 +582,22 @@ list_append_tv(list_T *l, typval_T *tv)
     return OK;
 }
 
+/*
+ * 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))
index 455705ca4f581a1bdadc401e0a1424a6dfbc0ac9..6631d4d9ba0ea4d604c9d1b64ec0c44e84b42a40 100644 (file)
 # 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.
index 117c2072e633a2297ee425813c647fe270452625..68a419eaa6c7eb227b90b9430a356ad626191abd 100644 (file)
@@ -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
index 7a6e346096e4162e198ff3d1a87723234e995a6b..935fd8c447e97c6f471da68d9aadd90a3183154c 100644 (file)
@@ -846,6 +846,17 @@ emsg_invreg(int name)
     semsg(_("E354: Invalid register name: '%s'"), transchar(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.
index fb75e19308d2c365758355230179917f9f1f4462..85ab727d323774a47b2e1de046f2c47ec8b52ee8 100644 (file)
@@ -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;
 }
index e8af1e27c350a3ae0b3d3790672d89be22e20695..7836c528e55ed7b9b4a837a99f64cce0765a3a3a 100644 (file)
@@ -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
index 706a83ea061a8fc26e5424294ac77f6a24d20a51..3bc6625456ecdd4c805b4308601e94d6343a576f 100644 (file)
@@ -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);
index b08607922c42b79d036a7b28f1f87dce636d577e..14b5f261db68a3c4ee1b796170c9483202ad7365 100644 (file)
@@ -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);
index 064f12f8d3376c662be2537735af4749c73725e9..59ff35c69480243ff93325ab4e63f0df9faa3c0c 100644 (file)
@@ -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);
index bb8b5a1c65fefff1e7df5bd972e73759e0c2c246..1d557f528405576e267c9762ac4701f8b61ba271 100644 (file)
@@ -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);
index f5942f5c1a97597ad37293f324e6d0bf711729b4..af70e2dba0fe80599dac2bcbc0348dd85d872d7c 100644 (file)
@@ -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);
index bd68eeaba764892d423782dce565eaa1117d111c..929f47987f35f7104f03c22617611c85443f4e7e 100644 (file)
@@ -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);
index ae8a7328e879b4ed70134f8b244d1bf7b9b374b2..d60463d2ddaa379d715633dcef5789690e0134be 100644 (file)
@@ -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);
index a34ca3dda4df3fffa9a7b9f9e0e69eddca264270..5c9ca6cb11abfb3a3ba2d6341d612200b55745ab 100644 (file)
@@ -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);
index 7972e849a9a803c6336f165b02f5853d6b50f373..111e855a74577eb9adb2036904f4d20c22b19974 100644 (file)
@@ -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);
index 06f41e41acf2158a1a9f4f0cba54db5c698af422..74bcabd8cefdaac97d7a2e329899f85b781ba353 100644 (file)
@@ -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 (file)
index 0000000..4f06e9d
--- /dev/null
@@ -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 (file)
index 0000000..4f7262d
--- /dev/null
@@ -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 (file)
index 0000000..29fa269
--- /dev/null
@@ -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 : */
index 78a80d02bcffb41162320d712cec049041c7cd0d..120c64351e94af5b2d80b89869dca9e2bdb53124 100644 (file)
@@ -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)
index 418fcba5778d472dbac23cc0c8496b377c748d2c..5bebb05bbabddaaeafd14ba181229b049da85281 100644 (file)
@@ -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);
     }
 }
index 7e1508eb9c486ac17b92ca0c4fd616550ca7bae5..201a89bceacc7e877d5c0df7f468f261cc14a668 100644 (file)
@@ -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
@@ -1577,6 +1619,32 @@ typedef struct
     dict_T     sv_dict;
 } 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
@@ -1584,11 +1652,18 @@ typedef struct
  */
 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
index e99eb8669a4b137048f3826e384faba92baaf52f..d16c22010477cf04f4a45b2b266579ce68ddffd3 100644 (file)
@@ -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;
index 2cccc8e90ad9a46a4a4f98f297600ddca0690bb2..98ea494dbc6bcf6f6a1216dca5adb8d1829cad27 100644 (file)
@@ -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 (file)
index 0000000..ea79c04
--- /dev/null
@@ -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 (file)
index 0000000..ce1ab34
--- /dev/null
@@ -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
index ac9beea435805a69b729946947d0573bf455dd79..3ab9dcdc900a2f961114b79c9e11eacfdbd629d9 100644 (file)
@@ -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;
            }
index 29e0fac050e85a4dbf1385616205cb49fd628f30..808ed968a43744a9ffc4fe48529d554fe2572078 100644 (file)
@@ -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.
@@ -62,14 +62,85 @@ func_tbl_get(void)
     return &func_hashtab;
 }
 
+/*
+ * 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;
@@ -580,18 +638,74 @@ fname_trans_sid(char_u *name, char_u *fname_buf, char_u **tofree, int *error)
     return fname;
 }
 
+/*
+ * 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;
@@ -1468,6 +1670,43 @@ call_callback(
     return ret;
 }
 
+/*
+ * 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
  *
@@ -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)
     {
index 6dfc66df804ddd1008d4ccda162aae1e95ba8fa4..3b4a9599e14706768565a2801f574e70773f6d69 100644 (file)
@@ -742,6 +742,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    149,
 /**/
     148,
 /**/
index 7ad55aa2499057fcc5ce4f7c4a28317046787b5d..9402c4ecf970b636c0d51942d9ce3727b8c13c36 100644 (file)
--- 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 (file)
index 0000000..97941ca
--- /dev/null
@@ -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 (file)
index 0000000..9f273c5
--- /dev/null
@@ -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 (file)
index 0000000..10da178
--- /dev/null
@@ -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 (file)
index 0000000..4596ce3
--- /dev/null
@@ -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
index 24cd4f7133f8670fd526bcad12a2640dd32e4177..b2b7ab28b0d54d2ceee2843851d1599dd5549435 100644 (file)
@@ -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: