summaryrefslogtreecommitdiff
path: root/mnv/runtime/doc/mnv9.txt
diff options
context:
space:
mode:
Diffstat (limited to 'mnv/runtime/doc/mnv9.txt')
-rw-r--r--mnv/runtime/doc/mnv9.txt3689
1 files changed, 3689 insertions, 0 deletions
diff --git a/mnv/runtime/doc/mnv9.txt b/mnv/runtime/doc/mnv9.txt
new file mode 100644
index 0000000000..b5e267d7d6
--- /dev/null
+++ b/mnv/runtime/doc/mnv9.txt
@@ -0,0 +1,3689 @@
+*mnv9.txt* For MNV version 10.0. Last change: 2026 Feb 14
+
+
+ MNV REFERENCE MANUAL by Bram Moolenaar
+
+
+MNV9 script commands and expressions. *MNV9* *mnv9*
+
+Most expression help is in |eval.txt|. This file is about the new syntax and
+features in MNV9 script.
+
+
+
+1. What is MNV9 script? |MNV9-script|
+2. Differences |mnv9-differences|
+3. New style functions |fast-functions|
+4. Types |mnv9-types|
+5. Generic functions |generic-functions|
+6. Namespace, Import and Export |mnv9script|
+7. Classes and interfaces |mnv9-classes|
+8. Rationale |mnv9-rationale|
+
+
+------------------------------------------------------------------------------
+
+ NOTE: In this mnv9.txt help file, the MNV9 script code blocks beginning
+ with `mnv9script` (and individual lines starting with `mnv9cmd`) are
+ MNV9 script syntax highlighted. Also, they are sourceable, meaning
+ you can run them to see what they output. To source them, use
+ `:'<,'>source` (see |:source-range|), which is done by visually
+ selecting the line(s) with |V| and typing `:so`. For example, try it
+ on the following MNV9 script: >mnv9
+
+ mnv9script
+ echowindow "Welcome to MNV9 script!"
+<
+ There are also code examples that should not be sourced - they
+ explain concepts that don't require a sourceable example. Such code
+ blocks appear in generic code syntax highlighting, like this: >
+
+ def ThisFunction() # script-local
+ def g:ThatFunction() # global
+ export def Function() # for import and import autoload
+
+==============================================================================
+
+1. What is MNV9 script? *MNV9-script*
+
+MNV script has been growing over time, while preserving backwards
+compatibility. That means bad choices from the past often can't be changed
+and compatibility with Vi restricts possible solutions. Execution is quite
+slow, each line is parsed every time it is executed.
+
+The main goal of MNV9 script is to drastically improve performance. This is
+accomplished by compiling commands into instructions that can be efficiently
+executed. An increase in execution speed of 10 to 100 times can be expected.
+
+A secondary goal is to avoid MNV-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, making function arguments available in the "a:"
+dictionary adds quite a lot of overhead. In a MNV9 function this dictionary
+is not available. Other differences are more subtle, such as how errors are
+handled.
+
+MNV9 script syntax, semantics, and behavior apply in:
+- a function defined with the `:def` command
+- a script file where the first command is `mnv9script`
+- an autocommand defined in the context of the above
+- a command prefixed with the `mnv9cmd` command modifier
+
+When using `:function` in a MNV9 script file the legacy syntax is used, with
+the highest |scriptversion|. However, this can be confusing and is therefore
+discouraged.
+
+MNV9 script and legacy MNV script can be mixed. There is no requirement to
+rewrite old scripts, they keep working as before. You may want to use a few
+`:def` functions for code that needs to be fast.
+
+:mnv9[cmd] {cmd} *:mnv9* *:mnv9cmd*
+ Evaluate and execute {cmd} using MNV9 script syntax,
+ semantics, and behavior. Useful when typing a command,
+ in a `:function`, or a legacy MNV script.
+
+ The following short example shows how a legacy MNV script
+ command and a :mnv9cmd (so MNV9 script context) may appear
+ similar, though may differ not just syntactically, but also
+ semantically and behaviorally. >mnv
+
+ call popup_notification('entrée'[5:]
+ \ ->str2list()->string(), #{time: 7000})
+ mnv9cmd popup_notification('entrée'[5 :]
+ ->str2list()->string(), {time: 7000})
+<
+ Notes: 1) The reason for the different output is MNV9 script
+ uses character indexing whereas legacy MNV script
+ uses byte indexing - see |mnv9-string-index|.
+ 2) Syntax is different too. In MNV9 script:
+ - The space in "[5 :]" is mandatory (see
+ |mnv9-white-space|).
+ - Line continuation with "\" is not required.
+ - The "#" (to avoid putting quotes around dictionary
+ keys) is neither required nor allowed - see |#{}|.
+
+ *E1164*
+ `:mnv9cmd` cannot stand alone; it must be followed by a command.
+
+:leg[acy] {cmd} *:leg* *:legacy*
+ Evaluate and execute {cmd} using legacy MNV script syntax,
+ semantics, and behavior. It is only applicable in a MNV9
+ script or a `:def` function. Using an equivalent script to
+ the one, above (see its notes for why the output differs): >mnv9
+
+ mnv9script
+ # Legacy context - so, this creates a popup with [769, 101]
+ legacy call popup_notification('entrée'[5:]
+ \ ->str2list()->string(), #{time: 7000})
+ # MNV9 script context - so, this creates a pop up with [101]
+ popup_notification('entrée'[5 :]
+ ->str2list()->string(), {time: 7000})
+<
+ MNV9 script script-local variables may be used by prefixing
+ "s:", like in legacy MNV script. This example shows the
+ difference in syntax: "k" for the script-local variable in
+ MNV9 script, "s:k" in the legacy MNV script context. >mnv9
+
+ mnv9script
+ var k: string = "Okay"
+ echo k
+ legacy echo s:k
+< *E1189*
+ Using `:legacy` is not allowed in compiled MNV9 script
+ control flow contexts. For example: >mnv9
+
+ mnv9script
+ def F_1189()
+ if v:version == 900
+ # E1189: Cannot use :legacy with this command: endif
+ legacy endif
+ enddef
+ F_1189()
+< *E1234*
+ `:legacy` cannot stand alone; it must be followed by a command.
+
+
+==============================================================================
+
+2. Differences from legacy MNV script *mnv9-differences*
+
+Overview ~
+ *E1146*
+Brief summary of the differences you will most often encounter when using MNV9
+script and `:def` functions; details are below:
+- Comments start with #, not ": >
+ echo "hello" # comment
+- Using a backslash for line continuation is hardly ever needed: >
+ echo "hello "
+ .. yourName
+ .. ", how are you?"
+- White space is required in many places to improve readability,
+ see |mnv9-white-space|.
+- Assign values without `:let` *E1126* , declare variables with `:var`: >
+ var count = 0
+ count += 3
+- Constants can be declared with `:final` and `:const`: >
+ final matches = [] # add to the list later
+ const names = ['Betty', 'Peter'] # cannot be changed
+- `:final` cannot be used as an abbreviation of `:finally`.
+- Variables and functions are script-local by default.
+- Functions are declared with argument types and return type: >
+ def CallMe(count: number, message: string): bool
+- Call functions without `:call`: >
+ writefile(['done'], 'file.txt')
+- You cannot use old Ex commands:
+ `:Print`
+ `:append`
+ `:change`
+ `:d` directly followed by 'd' or 'p'.
+ `:insert`
+ `:k`
+ `:mode`
+ `:open`
+ `:s` with only flags
+ `:t`
+ `:xit`
+- Some commands, especially those used for flow control, cannot be shortened.
+ E.g., `:throw` cannot be written as `:th`. *mnv9-no-shorten*
+- You cannot use curly-braces names.
+- A range before a command must be prefixed with a colon: >
+ :%s/this/that
+- Executing a register with "@r" does not work, you can prepend a colon or use
+ `:exe`: >
+ :exe @a
+- Unless mentioned specifically, the highest |scriptversion| is used.
+- When defining an expression mapping, the expression will be evaluated in the
+ context of the script where it was defined.
+- When indexing a string the index is counted in characters, not bytes:
+ |mnv9-string-index|
+- Some possibly unexpected differences: |mnv9-gotchas|.
+
+
+Comments starting with # ~
+
+In legacy MNV script comments start with double quote. In MNV9 script
+comments start with #. >
+ # declarations
+ var count = 0 # number of occurrences
+
+The reason is that a double quote can also be the start of a string. In many
+places, especially halfway through an expression with a line break, it's hard
+to tell what the meaning is, since both a string and a comment can be followed
+by arbitrary text. To avoid confusion only # comments are recognized. This
+is the same as in shell scripts and Python programs.
+
+In Vi # is a command to list text with numbers. In MNV9 script you can use
+`:number` for that. >
+ :101 number
+
+To improve readability there must be a space between a command and the #
+that starts a comment: >
+ var name = value # comment
+ var name = value# error!
+< *E1170*
+Do not start a comment with #{, it looks like the legacy dictionary literal
+and produces an error where this might be confusing. #{{ or #{{{ are OK,
+these can be used to start a fold.
+
+When starting to read a script file MNV doesn't know it is |MNV9| script until
+the `mnv9script` command is found. Until that point you would need to use
+legacy comments: >
+ " legacy comment
+ mnv9script
+ # MNV9 comment
+
+That looks ugly, better put `mnv9script` in the very first line: >
+ mnv9script
+ # MNV9 comment
+
+In legacy MNV script # is also used for the alternate file name. In MNV9
+script you need to use %% instead. Instead of ## use %%% (stands for all
+arguments).
+
+
+MNV9 functions ~
+ *E1099*
+A function defined with `:def` is compiled. Execution is many times faster,
+often 10 to 100 times.
+
+Many errors are already found when compiling, before the function is executed.
+The syntax is strict, to enforce code that is easy to read and understand.
+
+Compilation is done when any of these is encountered:
+- the first time the function is called
+- when the `:defcompile` command is encountered in the script after the
+ function was defined
+- `:disassemble` is used for the function.
+- a function that is compiled calls the function or uses it as a function
+ reference (so that the argument and return types can be checked)
+ *E1091* *E1191*
+If compilation fails it is not tried again on the next call, instead this
+error is given: "E1091: Function is not compiled: {name}".
+Compilation will fail when encountering a user command that has not been
+created yet. In this case you can call `execute()` to invoke it at runtime. >
+ def MyFunc()
+ execute('DefinedLater')
+ enddef
+
+`:def` has no options like `:function` does: "range", "abort", "dict" or
+"closure". A `:def` function always aborts on an error (unless `:silent!` was
+used for the command or the error was caught a `:try` block), does not get a
+range passed, cannot be a "dict" function, and can always be a closure.
+ *mnv9-no-dict-function* *E1182*
+You can use a MNV9 Class (|MNV9-class|) instead of a "dict function".
+You can also pass the dictionary explicitly: >
+ def DictFunc(self: dict<any>, arg: string)
+ echo self[arg]
+ enddef
+ var ad = {item: 'value', func: DictFunc}
+ ad.func(ad, 'item')
+
+You can call a legacy dict function though: >
+ func Legacy() dict
+ echo self.value
+ endfunc
+ def CallLegacy()
+ var d = {func: Legacy, value: 'text'}
+ d.func()
+ enddef
+
+The argument types and return type need to be specified. The "any" type can
+be used, type checking will then be done at runtime, like with legacy
+functions.
+ *E1106*
+Arguments are accessed by name, without "a:", just like any other language.
+There is no "a:" dictionary or "a:000" list.
+ *mnv9-variable-arguments* *E1055* *E1160* *E1180*
+Variable arguments are defined as the last argument, with a name and have a
+list type, similar to TypeScript. For example, a list of numbers: >
+ def MyFunc(...itemlist: list<number>)
+ for item in itemlist
+ ...
+
+When a function argument is optional (it has a default value) passing `v:none`
+as the argument results in using the default value. This is useful when you
+want to specify a value for an argument that comes after an argument that
+should use its default value. Example: >
+ def MyFunc(one = 'one', last = 'last')
+ ...
+ enddef
+ MyFunc(v:none, 'LAST') # first argument uses default value 'one'
+<
+ *mnv9-ignored-argument* *E1181*
+The argument "_" (an underscore) can be used to ignore the argument. This is
+most useful in callbacks where you don't need it, but do need to give an
+argument to match the call. E.g. when using map() two arguments are passed,
+the key and the value, to ignore the key: >
+ map(numberList, (_, v) => v * 2)
+There is no error for using the "_" argument multiple times. No type needs to
+be given.
+
+
+Functions and variables are script-local by default ~
+ *mnv9-scopes*
+When using `:function` or `:def` to specify a new function at the script level
+in a MNV9 script, the function is local to the script. Like prefixing "s:" in
+legacy script. To define a global function or variable the "g:" prefix must
+be used. For functions in a script that is to be imported and in an autoload
+script "export" needs to be used for those to be used elsewhere. >
+ def ThisFunction() # script-local
+ def g:ThatFunction() # global
+ export def Function() # for import and import autoload
+< *E1075*
+When using `:function` or `:def` to specify a nested function inside a `:def`
+function and no namespace was given, this nested function is local to the code
+block it is defined in. It cannot be used in `function()` with a string
+argument, pass the function reference itself: >
+ def Outer()
+ def Inner()
+ echo 'inner'
+ enddef
+ var Fok = function(Inner) # OK
+ var Fbad = function('Inner') # does not work
+
+Detail: this is because "Inner" will actually become a function reference to a
+function with a generated name.
+
+It is not possible to define a script-local function in a function. You can
+define a local function and assign it to a script-local Funcref (it must have
+been declared at the script level). It is possible to define a global
+function by using the "g:" prefix.
+
+When referring to a function and no "s:" or "g:" prefix is used, MNV will
+search for the function:
+- in the function scope, in block scopes
+- in the script scope
+
+Imported functions are found with the prefix from the `:import` command.
+
+Since a script-local function reference can be used without "s:" the name must
+start with an upper case letter even when using the "s:" prefix. In legacy
+script "s:funcref" could be used, because it could not be referred to with
+"funcref". In MNV9 script it can, therefore "s:Funcref" must be used to avoid
+that the name interferes with builtin functions.
+ *mnv9-s-namespace* *E1268*
+The use of the "s:" prefix is not supported at the MNV9 script level. All
+functions and variables without a prefix are script-local.
+
+In :def functions the use of "s:" depends on the script: Script-local
+variables and functions in a legacy script do use "s:", while in a MNV9 script
+they do not use "s:". This matches what you see in the rest of the file.
+
+In legacy functions the use of "s:" for script items is required, as before.
+No matter if the script is MNV9 or legacy.
+
+In all cases the function must be defined before used. That is when it is
+called, when `:defcompile` causes it to be compiled, or when code that calls
+it is being compiled (to figure out the return type).
+
+The result is that functions and variables without a namespace can usually be
+found in the script, either defined there or imported. Global functions and
+variables could be defined anywhere (good luck finding out where! You can
+often see where it was last set using |:verbose|).
+ *E1102*
+Global functions can still be defined and deleted at nearly any time. In
+MNV9 script script-local functions are defined once when the script is sourced
+and cannot be deleted or replaced by itself (it can be by reloading the
+script).
+
+When compiling a function and a function call is encountered for a function
+that is not (yet) defined, the |FuncUndefined| autocommand is not triggered.
+You can use an autoload function if needed, or call a legacy function and have
+|FuncUndefined| triggered there.
+
+
+Reloading a MNV9 script clears functions and variables by default ~
+ *mnv9-reload* *E1149* *E1150*
+When loading a legacy MNV script a second time nothing is removed, the
+commands will replace existing variables and functions, create new ones, and
+leave removed things hanging around.
+
+When loading a MNV9 script a second time all existing script-local functions
+and variables are deleted, thus you start with a clean slate. This is useful
+if you are developing a plugin and want to try a new version. If you renamed
+something you don't have to worry about the old name still hanging around.
+
+If you do want to keep items, use: >
+ mnv9script noclear
+
+You want to use this in scripts that use a `finish` command to bail out at
+some point when loaded again. E.g. when a buffer local option is set to a
+function, the function does not need to be defined more than once: >
+ mnv9script noclear
+ setlocal completefunc=SomeFunc
+ if exists('*SomeFunc')
+ finish
+ endif
+ def SomeFunc()
+ ....
+
+
+Variable declarations with :var, :final and :const ~
+ *mnv9-declaration* *:var* *E1079*
+ *E1017* *E1020* *E1054* *E1087* *E1124*
+Local variables need to be declared with `:var`. Local constants need to be
+declared with `:final` or `:const`. We refer to both as "variables" in this
+section.
+
+Variables can be local to a script, function or code block: >
+ mnv9script
+ var script_var = 123
+ def SomeFunc()
+ var func_var = script_var
+ if cond
+ var 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
+ var inner = 5
+ else
+ var inner = 0
+ endif
+ echo inner # Error!
+
+The declaration must be done earlier: >
+ var inner: number
+ if cond
+ inner = 5
+ else
+ inner = 0
+ endif
+ echo inner
+
+Although this is shorter and faster for simple values: >
+ var inner = 0
+ if cond
+ inner = 5
+ endif
+ echo inner
+< *E1025* *E1128*
+To intentionally hide a variable from code that follows, a block can be
+used: >
+ {
+ var temp = 'temp'
+ ...
+ }
+ echo temp # Error!
+
+This is especially useful in a user command: >
+ command -range Rename {
+ var save = @a
+ @a = 'some expression'
+ echo 'do something with ' .. @a
+ @a = save
+ }
+
+And with autocommands: >
+ au BufWritePre *.go {
+ var save = winsaveview()
+ silent! exe ':%! some formatting command'
+ winrestview(save)
+ }
+
+Although using a :def function probably works better.
+
+ *E1022* *E1103* *E1130* *E1131* *E1133*
+ *E1134*
+Declaring a variable with a type but without an initializer will initialize to
+false (for bool), empty (for string, list, dict, etc.) or zero (for number,
+any, etc.). This matters especially when using the "any" type, the value will
+default to the number zero. For example, when declaring a list, items can be
+added: >
+ var myList: list<number>
+ myList->add(7)
+
+Initializing a variable to a null value, e.g. `null_list`, differs from not
+initializing the variable. This throws an error: >
+ var myList = null_list
+ myList->add(7) # E1130: Cannot add to null list
+
+< *E1016* *E1052* *E1066*
+In MNV9 script `:let` cannot be used. An existing variable is assigned to
+without any command. The same for global, window, tab, buffer and MNV
+variables, because they are not really declared. Those can also be deleted
+with `:unlet`.
+ *E1065*
+You cannot use `:va` to declare a variable, it must be written with the full
+name `:var`. Just to make sure it is easy to read.
+ *E1178*
+`:lockvar` does not work on local variables. Use `:const` and `:final`
+instead.
+
+The `exists()` and `exists_compiled()` functions do not work on local variables
+or arguments.
+ *E1006* *E1041* *E1167* *E1168* *E1213*
+Variables, functions and function arguments cannot shadow previously defined
+or imported variables and functions in the same script file.
+Variables may shadow Ex commands, rename the variable if needed.
+
+Global variables must be prefixed with "g:", also at the script level. >
+ mnv9script
+ var script_local = 'text'
+ g:global = 'value'
+ var Funcref = g:ThatFunction
+
+Global functions must be prefixed with "g:": >
+ mnv9script
+ def g:GlobalFunc(): string
+ return 'text'
+ enddef
+ echo g:GlobalFunc()
+The "g:" prefix is not needed for auto-load functions.
+
+ *mnv9-function-defined-later*
+Although global functions can be called without the "g:" prefix, they must
+exist when compiled. By adding the "g:" prefix the function can be defined
+later. Example: >
+ def CallPluginFunc()
+ if exists('g:loaded_plugin')
+ g:PluginFunc()
+ endif
+ enddef
+
+If you do it like this, you get an error at compile time that "PluginFunc"
+does not exist, even when "g:loaded_plugin" does not exist: >
+ def CallPluginFunc()
+ if exists('g:loaded_plugin')
+ PluginFunc() # Error - function not found
+ endif
+ enddef
+
+You can use exists_compiled() to avoid the error, but then the function would
+not be called, even when "g:loaded_plugin" is defined later: >
+ def CallPluginFunc()
+ if exists_compiled('g:loaded_plugin')
+ PluginFunc() # Function may never be called
+ endif
+ enddef
+
+Since `&opt = value` is now assigning a value to option "opt", ":&" cannot be
+used to repeat a `:substitute` command.
+ *mnv9-unpack-ignore*
+For an unpack assignment the underscore can be used to ignore a list item,
+similar to how a function argument can be ignored: >
+ [a, _, c] = theList
+To ignore any remaining items: >
+ [a, b; _] = longList
+< *E1163* *E1080*
+Declaring more than one variable at a time, using the unpack notation, is
+possible. Each variable can have a type or infer it from the value: >
+ var [v1: number, v2] = GetValues()
+Use this only when there is a list with values, declaring one variable per
+line is much easier to read and change later.
+
+
+Constants ~
+ *mnv9-const* *mnv9-final*
+How constants work varies between languages. Some consider a variable that
+can't be assigned another value a constant. JavaScript is an example. Others
+also make the value immutable, thus when a constant uses a list, the list
+cannot be changed. In MNV9 we can use both.
+ *E1021* *E1307*
+`:const` is used for making both the variable and the value a constant. Use
+this for composite structures that you want to make sure will not be modified.
+Example: >
+ const myList = [1, 2]
+ myList = [3, 4] # Error!
+ myList[0] = 9 # Error!
+ myList->add(3) # Error!
+< *:final* *E1125*
+`:final` is used for making only the variable a constant, the value can be
+changed. This is well known from Java. Example: >
+ final myList = [1, 2]
+ myList = [3, 4] # Error!
+ myList[0] = 9 # OK
+ myList->add(3) # OK
+
+It is common to write constants as ALL_CAPS, but you don't have to.
+
+The constant only applies to the value itself, not what it refers to. >
+ final females = ["Mary"]
+ const NAMES = [["John", "Peter"], females]
+ NAMES[0] = ["Jack"] # Error!
+ NAMES[0][0] = "Jack" # Error!
+ NAMES[1] = ["Emma"] # Error!
+ NAMES[1][0] = "Emma" # OK, now females[0] == "Emma"
+
+
+Omitting :call and :eval ~
+ *E1190*
+Functions can be called without `:call`: >
+ writefile(lines, 'file')
+Using `:call` is still possible, 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. For a function either "(" or "->" must
+be following, without a line break. Examples: >
+ myList->add(123)
+ g:myList->add(123)
+ [1, 2, 3]->Process()
+ {a: 1, b: 2}->Process()
+ "foobar"->Process()
+ ("foobar")->Process()
+ 'foobar'->Process()
+ ('foobar')->Process()
+
+In the rare case there is ambiguity between a function name and an Ex command,
+prepend ":" to make clear you want to use the Ex command. For example, there
+is both the `:substitute` command and the `substitute()` function. When the
+line starts with `substitute(` this will use the function. Prepend a colon to
+use the command instead: >
+ :substitute(pattern (replacement (
+
+If the expression starts with "!" this is interpreted as a shell command, not
+negation of a condition. Thus this is a shell command: >
+ !shellCommand->something
+Put the expression in parentheses to use the "!" for negation: >
+ (!expression)->Method()
+
+Note that while variables need to be defined before they can be used,
+functions can be called before being defined. This is required to allow
+for cyclic dependencies between functions. It is slightly less efficient,
+since the function has to be looked up by name. And a typo in the function
+name will only be found when the function is called.
+
+
+Omitting function() ~
+
+A user defined function can be used as a function reference in an expression
+without `function()`. The argument types and return type will then be checked.
+The function must already have been defined. >
+
+ var Funcref = MyFunction
+
+When using `function()` the resulting type is "func", a function with any
+number of arguments and any return type (including void). The function can be
+defined later if the argument is in quotes.
+
+
+Lambda using => instead of -> ~
+ *mnv9-lambda*
+In legacy script there can be confusion between using "->" for a method call
+and for a lambda. Also, when a "{" is found the parser needs to figure out if
+it is the start of a lambda or a dictionary, which is now more complicated
+because of the use of argument types.
+
+To avoid these problems MNV9 script uses a different syntax for a lambda,
+which is similar to JavaScript: >
+ var Lambda = (arg) => expression
+ var Lambda = (arg): type => expression
+< *E1157*
+No line break is allowed in the arguments of a lambda up to and including the
+"=>" (so that MNV can tell the difference between an expression in parentheses
+and lambda arguments). This is OK: >
+ filter(list, (k, v) =>
+ v > 0)
+This does not work: >
+ filter(list, (k, v)
+ => v > 0)
+This also does not work: >
+ filter(list, (k,
+ v) => v > 0)
+But you can use a backslash to concatenate the lines before parsing: >
+ filter(list, (k,
+ \ v)
+ \ => v > 0)
+< *mnv9-lambda-arguments* *E1172*
+In legacy script a lambda could be called with any number of extra arguments,
+there was no way to warn for not using them. In MNV9 script the number of
+arguments must match. If you do want to accept any arguments, or any further
+arguments, use "..._", which makes the function accept
+|mnv9-variable-arguments|. Example: >
+ var Callback = (..._) => 'anything'
+ echo Callback(1, 2, 3) # displays "anything"
+
+< *inline-function* *E1171*
+Additionally, a lambda can contain statements in {}: >
+ var Lambda = (arg) => {
+ g:was_called = 'yes'
+ return expression
+ }
+This can be useful for a timer, for example: >
+ var count = 0
+ var timer = timer_start(500, (_) => {
+ count += 1
+ echom 'Handler called ' .. count
+ }, {repeat: 3})
+
+The ending "}" must be at the start of a line. It can be followed by other
+characters, e.g.: >
+ var d = mapnew(dict, (k, v): string => {
+ return 'value'
+ })
+No command can follow the "{", only a comment can be used there.
+
+ *command-block* *E1026*
+The block can also be used for defining a user command. Inside the block MNV9
+syntax will be used.
+
+This is an example of using here-docs: >
+ com SomeCommand {
+ g:someVar =<< trim eval END
+ ccc
+ ddd
+ END
+ }
+
+If the statements include a dictionary, its closing bracket must not be
+written at the start of a line. Otherwise, it would be parsed as the end of
+the block. This does not work: >
+ command NewCommand {
+ g:mydict = {
+ 'key': 'value',
+ } # ERROR: will be recognized as the end of the block
+ }
+Put the '}' after the last item to avoid this: >
+ command NewCommand {
+ g:mydict = {
+ 'key': 'value' }
+ }
+
+Rationale: The "}" cannot be after a command because it would require parsing
+the commands to find it. For consistency with that no command can follow the
+"{". Unfortunately this means using "() => { command }" does not work, line
+breaks are always required.
+
+ *mnv9-curly*
+To avoid the "{" of a dictionary literal to be recognized as a statement block
+wrap it in parentheses: >
+ var Lambda = (arg) => ({key: 42})
+
+Also when confused with the start of a command block: >
+ ({
+ key: value
+ })->method()
+
+
+Automatic line continuation ~
+ *mnv9-line-continuation* *E1097*
+In many cases it is obvious that an expression continues on the next line. In
+those cases there is no need to prefix the line with a backslash (see
+|line-continuation|). For example, when a list spans multiple lines: >
+ var mylist = [
+ 'one',
+ 'two',
+ ]
+And when a dict spans multiple lines: >
+ var mydict = {
+ one: 1,
+ two: 2,
+ }
+With a function call: >
+ var result = Func(
+ arg1,
+ arg2
+ )
+
+For binary operators in expressions not in [], {} or () a line break is
+possible just before or after the operator. For example: >
+ var text = lead
+ .. middle
+ .. end
+ var total = start +
+ end -
+ correction
+ var result = positive
+ ? PosFunc(arg)
+ : NegFunc(arg)
+
+For a method call using "->" and a member using a dot, a line break is allowed
+before it: >
+ var result = GetBuilder()
+ ->BuilderSetWidth(333)
+ ->BuilderSetHeight(777)
+ ->BuilderBuild()
+ var result = MyDict
+ .member
+
+For commands that have an argument that is a list of commands, the | character
+at the start of the line indicates line continuation: >
+ autocmd BufNewFile *.match if condition
+ | echo 'match'
+ | endif
+
+Note that this means that in heredoc the first line cannot start with a bar: >
+ var lines =<< trim END
+ | this doesn't work
+ END
+Either use an empty line at the start or do not use heredoc. Or temporarily
+add the "C" flag to 'cpoptions': >
+ set cpo+=C
+ var lines =<< trim END
+ | this works
+ END
+ set cpo-=C
+If the heredoc is inside a function 'cpoptions' must be set before :def and
+restored after the :enddef.
+
+In places where line continuation with a backslash is still needed, such as
+splitting up a long Ex command, comments can start with '#\ ': >
+ syn region Text
+ \ start='foo'
+ #\ comment
+ \ end='bar'
+Like with legacy script '"\ ' is used. This is also needed when line
+continuation is used without a backslash and a line starts with a bar: >
+ au CursorHold * echom 'BEFORE bar'
+ #\ some comment
+ | echom 'AFTER bar'
+<
+ *E1050*
+To make it possible for the operator at the start of the line to be
+recognized, it is required to put a colon before a range. This example will
+add "start" and "print": >
+ var result = start
+ + print
+Like this: >
+ var result = start + print
+
+This will assign "start" and print a line: >
+ var result = start
+ :+ print
+
+After the range an Ex command must follow. Without the colon you can call a
+function without `:call`, but after a range you do need it: >
+ MyFunc()
+ :% call MyFunc()
+
+Note that the colon is not required for the |+cmd| argument: >
+ edit +6 fname
+
+It is also possible to split a function header over multiple lines, in between
+arguments: >
+ def MyFunc(
+ text: string,
+ separator = '-'
+ ): string
+
+Since a continuation line cannot be easily recognized the parsing of commands
+has been made stricter. E.g., because of the error in the first line, the
+second line is seen as a separate command: >
+ popup_create(some invalid expression, {
+ exit_cb: Func})
+Now "exit_cb: Func})" is actually a valid command: save any changes to the
+file "_cb: Func})" and exit. To avoid this kind of mistake in MNV9 script
+there must be white space between most command names and the argument.
+*E1144*
+
+However, the argument of a command that is a command won't be recognized. For
+example, after "windo echo expr" a line break inside "expr" will not be seen.
+
+
+Notes:
+- "enddef" cannot be used at the start of a continuation line, it ends the
+ current function.
+- No line break is allowed in the LHS of an assignment. Specifically when
+ unpacking a list |:let-unpack|. This is OK: >
+ [var1, var2] =
+ Func()
+< This does not work: >
+ [var1,
+ var2] =
+ Func()
+- No line break is allowed in between arguments of an `:echo`, `:execute` and
+ similar commands. This is OK: >
+ echo [1,
+ 2] [3,
+ 4]
+< This does not work: >
+ echo [1, 2]
+ [3, 4]
+- In some cases it is difficult for MNV to parse a command, especially when
+ commands are used as an argument to another command, such as `:windo`. In
+ those cases the line continuation with a backslash has to be used.
+
+
+White space ~
+ *mnv9-white-space* *E1004* *E1068* *E1069* *E1074* *E1127* *E1202*
+MNV9 script enforces proper use of white space. This is no longer allowed: >
+ var name=234 # Error!
+ var name= 234 # Error!
+ var name =234 # Error!
+There must be white space before and after the "=": >
+ var name = 234 # OK
+White space must also be put before the # that starts a comment after a
+command: >
+ var name = 234# Error!
+ var name = 234 # OK
+
+White space is required around most operators.
+
+White space is required in a sublist (list slice) around the ":", except at
+the start and end: >
+ otherlist = mylist[v : count] # v:count has a different meaning
+ otherlist = mylist[:] # make a copy of the List
+ otherlist = mylist[v :]
+ otherlist = mylist[: v]
+
+White space is not allowed:
+- Between a function name and the "(": >
+ Func (arg) # Error!
+ Func
+ \ (arg) # Error!
+ Func
+ (arg) # Error!
+ Func(arg) # OK
+ Func(
+ arg) # OK
+ Func(
+ arg # OK
+ )
+< *E1205*
+White space is not allowed in a `:set` command between the option name and a
+following "&", "!", "<", "=", "+=", "-=" or "^=".
+
+
+No curly braces expansion ~
+
+|curly-braces-names| cannot be used.
+
+
+Command modifiers are not ignored ~
+ *E1176*
+Using a command modifier for a command that does not use it gives an error.
+ *E1082*
+Also, using a command modifier without a following command is now an error.
+
+
+Dictionary literals ~
+ *mnv9-literal-dict* *E1014*
+Traditionally MNV has supported dictionary literals with a {} syntax: >
+ let dict = {'key': value}
+
+Later it became clear that using a simple text key is very common, thus
+literal dictionaries were introduced in a backwards compatible way: >
+ let dict = #{key: value}
+
+However, this #{} syntax is unlike any existing language. As it turns out
+that using a literal key is much more common than using an expression, and
+considering that JavaScript uses this syntax, using the {} form for dictionary
+literals is considered a much more useful syntax. In MNV9 script the {} form
+uses literal keys: >
+ var dict = {key: value}
+
+This works for alphanumeric characters, underscore and dash. If you want to
+use another character, use a single or double quoted string: >
+ var dict = {'key with space': value}
+ var dict = {"key\twith\ttabs": value}
+ var dict = {'': value} # empty key
+< *E1139*
+In case the key needs to be an expression, square brackets can be used, just
+like in JavaScript: >
+ var dict = {["key" .. nr]: value}
+
+The key type can be string, number, bool or float. Other types result in an
+error. Without using [] the value is used as a string, keeping leading zeros.
+An expression given with [] is evaluated and then converted to a string.
+Leading zeros will then be dropped: >
+ var dict = {000123: 'without', [000456]: 'with'}
+ echo dict
+ {'456': 'with', '000123': 'without'}
+A float only works inside [] because the dot is not accepted otherwise: >
+ var dict = {[00.013]: 'float'}
+ echo dict
+ {'0.013': 'float'}
+
+
+No :xit, :t, :k, :append, :change or :insert ~
+ *E1100*
+These commands are too easily confused with local variable names.
+Instead of `:x` or `:xit` you can use `:exit`.
+Instead of `:t` you can use `:copy`.
+Instead of `:k` you can use `:mark`.
+
+
+Comparators ~
+
+The 'ignorecase' option is not used for comparators that use strings.
+Thus "=~" works like "=~#".
+
+"is" and "isnot" (|expr-is| and |expr-isnot|) when used on strings now return
+false. In legacy script they just compare the strings, in |MNV9| script they
+check identity, and strings are copied when used, thus two strings are never
+the same (this might change someday if strings are not copied but reference
+counted).
+
+
+Abort after error ~
+
+In legacy script, when an error is encountered, MNV continues to execute
+following lines. This can lead to a long sequence of errors and need to type
+CTRL-C to stop it. In MNV9 script execution of commands stops at the first
+error. Example: >
+ mnv9script
+ var x = does-not-exist
+ echo 'not executed'
+
+
+For loop ~
+ *E1254*
+The loop variable must not be declared yet: >
+ var i = 1
+ for i in [1, 2, 3] # Error!
+
+It is possible to use a global variable though: >
+ g:i = 1
+ for g:i in [1, 2, 3]
+ echo g:i
+ endfor
+
+Legacy MNV script has some tricks to make a for loop over a list handle
+deleting items at the current or previous item. In MNV9 script it just uses
+the index, if items are deleted then items in the list will be skipped.
+Example legacy script: >
+ let l = [1, 2, 3, 4]
+ for i in l
+ echo i
+ call remove(l, index(l, i))
+ endfor
+Would echo:
+ 1
+ 2
+ 3
+ 4
+In compiled MNV9 script you get:
+ 1
+ 3
+Generally, you should not change the list that is iterated over. Make a copy
+first if needed.
+When looping over a list of lists, the nested lists can be changed. The loop
+variable is "final", it cannot be changed but what its value can be changed.
+ *E1306*
+The depth of loops, :for and :while loops added together, cannot exceed 10.
+
+
+Conditions and expressions ~
+ *mnv9-boolean*
+Conditions and expressions are mostly working like they do in other languages.
+Some values are different from legacy MNV script:
+ value legacy MNV script MNV9 script ~
+ 0 falsy falsy
+ 1 truthy truthy
+ 99 truthy Error!
+ "0" falsy Error!
+ "99" truthy Error!
+ "text" falsy Error!
+
+For the "??" operator and when using "!" then there is no error, every value
+is either falsy or truthy. This is mostly like JavaScript, except that an
+empty list and dict is falsy:
+
+ type truthy when ~
+ bool true, v:true or 1
+ number non-zero
+ float non-zero
+ string non-empty
+ blob non-empty
+ list non-empty (different from JavaScript)
+ tuple non-empty (different from JavaScript)
+ dictionary non-empty (different from JavaScript)
+ func when there is a function name
+ special true or v:true
+ job when not NULL
+ channel when not NULL
+ class not applicable
+ object when not NULL
+ enum not applicable
+ enum value always
+ typealias not applicable
+
+The boolean operators "||" and "&&" expect the values to be boolean, zero or
+one: >
+ 1 || false == true
+ 0 || 1 == true
+ 0 || false == false
+ 1 && true == true
+ 0 && 1 == false
+ 8 || 0 Error!
+ 'yes' && 0 Error!
+ [] || 99 Error!
+
+When using "!" for inverting, there is no error for using any type and the
+result is a boolean. "!!" can be used to turn any value into boolean: >
+ !'yes' == false
+ !![] == false
+ !![1, 2, 3] == true
+
+When using "`.."` for string concatenation arguments of simple types are
+always converted to string: >
+ 'hello ' .. 123 == 'hello 123'
+ 'hello ' .. v:true == 'hello true'
+
+Simple types are Number, Float, Special and Bool. For other types |string()|
+should be used.
+ *false* *true* *null* *null_blob* *null_channel*
+ *null_class* *null_dict* *null_function* *null_job*
+ *null_list* *null_object* *null_partial* *null_string*
+ *E1034*
+In MNV9 script one can use the following predefined values: >
+ true
+ false
+ null
+ null_blob
+ null_channel
+ null_class
+ null_dict
+ null_function
+ null_job
+ null_list
+ null_tuple
+ null_object
+ null_partial
+ null_string
+`true` is the same as `v:true`, `false` the same as `v:false`, `null` the same
+as `v:null`.
+
+While `null` has the type "special", the other "null_" values have the type
+indicated by their name. Quite often a null value is handled the same as an
+empty value, but not always. The values can be useful to clear a script-local
+variable, since they cannot be deleted with `:unlet`. E.g.: >
+ var theJob = job_start(...)
+ # let the job do its work
+ theJob = null_job
+
+The values can also be useful as the default value for an argument: >
+ def MyFunc(b: blob = null_blob)
+ # Note: compare against null, not null_blob,
+ # to distinguish the default value from an empty blob.
+ if b == null
+ # b argument was not given
+See |null-compare| for more information about testing against null.
+
+It is possible to compare `null` with any value, this will not give a type
+error. However, comparing `null` with a number, float or bool will always
+result in `false`. This is different from legacy script, where comparing
+`null` with zero or `false` would return `true`.
+ *mnv9-false-true*
+When converting a boolean to a string `false` and `true` are used, not
+`v:false` and `v:true` like in legacy script. `v:none` has no `none`
+replacement, it has no equivalent in other languages.
+ *mnv9-string-index*
+Indexing a string with [idx] or taking a slice with [idx : idx] uses character
+indexes instead of byte indexes. Composing characters are included.
+Example: >
+ echo 'bár'[1]
+In legacy script this results in the character 0xc3 (an illegal byte), in MNV9
+script this results in the string 'á'.
+A negative index is counting from the end, "[-1]" is the last character.
+To exclude the last character use |slice()|.
+To count composing characters separately use |strcharpart()|.
+If the index is out of range then an empty string results.
+
+In legacy script "++var" and "--var" would be silently accepted and have no
+effect. This is an error in MNV9 script.
+
+Numbers starting with zero are not considered to be octal, only numbers
+starting with "0o" are octal: "0o744". |scriptversion-4|
+
+
+What to watch out for ~
+ *mnv9-gotchas*
+MNV9 was designed to be closer to often used programming languages, but at the
+same time tries to support the legacy MNV commands. Some compromises had to
+be made. Here is a summary of what might be unexpected.
+
+Ex command ranges need to be prefixed with a colon. >
+ -> legacy MNV: shifts the previous line to the right
+ ->func() MNV9: method call in a continuation line
+ :-> MNV9: shifts the previous line to the right
+
+ %s/a/b legacy MNV: substitute on all lines
+ x = alongname
+ % another MNV9: modulo operator in a continuation line
+ :%s/a/b MNV9: substitute on all lines
+ 't legacy MNV: jump to mark t
+ 'text'->func() MNV9: method call
+ :'t MNV9: jump to mark t
+
+Some Ex commands can be confused with assignments in MNV9 script: >
+ g:name = value # assignment
+ :g:pattern:cmd # :global command
+
+To avoid confusion between a `:global` or `:substitute` command and an
+expression or assignment, a few separators cannot be used when these commands
+are abbreviated to a single character: ':', '-' and '.'. >
+ g:pattern:cmd # invalid command - ERROR
+ s:pattern:repl # invalid command - ERROR
+ g-pattern-cmd # invalid command - ERROR
+ s-pattern-repl # invalid command - ERROR
+ g.pattern.cmd # invalid command - ERROR
+ s.pattern.repl # invalid command - ERROR
+
+Also, there cannot be a space between the command and the separator: >
+ g /pattern/cmd # invalid command - ERROR
+ s /pattern/repl # invalid command - ERROR
+
+Functions defined with `:def` compile the whole function. Legacy functions
+can bail out, and the following lines are not parsed: >
+ func Maybe()
+ if !has('feature')
+ return
+ endif
+ use-feature
+ endfunc
+MNV9 functions are compiled as a whole: >
+ def Maybe()
+ if !has('feature')
+ return
+ endif
+ use-feature # May give a compilation error
+ enddef
+For a workaround, split it in two functions: >
+ func Maybe()
+ if has('feature')
+ call MaybeInner()
+ endif
+ endfunc
+ if has('feature')
+ def MaybeInner()
+ use-feature
+ enddef
+ endif
+Or put the unsupported code inside an `if` with a constant expression that
+evaluates to false: >
+ def Maybe()
+ if has('feature')
+ use-feature
+ endif
+ enddef
+The `exists_compiled()` function can also be used for this.
+ *mnv9-user-command*
+Another side effect of compiling a function is that the presence of a user
+command is checked at compile time. If the user command is defined later an
+error will result. This works: >
+ command -nargs=1 MyCommand echom <q-args>
+ def Works()
+ MyCommand 123
+ enddef
+This will give an error for "MyCommand" not being defined: >
+ def Works()
+ command -nargs=1 MyCommand echom <q-args>
+ MyCommand 123
+ enddef
+A workaround is to invoke the command indirectly with `:execute`: >
+ def Works()
+ command -nargs=1 MyCommand echom <q-args>
+ execute 'MyCommand 123'
+ enddef
+
+Note that for unrecognized commands there is no check for "|" and a following
+command. This will give an error for missing `endif`: >
+ def Maybe()
+ if has('feature') | use-feature | endif
+ enddef
+
+Other differences ~
+
+Patterns are used like 'magic' is set, unless explicitly overruled.
+The 'edcompatible' option value is not used.
+The 'gdefault' option value is not used.
+
+You may also find this wiki useful. It was written by an early adopter of
+MNV9 script: https://github.com/lacygoill/wiki/blob/master/mnv/mnv9.md
+
+ *:++* *:--*
+The ++ and -- commands have been added. They are very similar to adding or
+subtracting one: >
+ ++var
+ var += 1
+ --var
+ var -= 1
+
+Using ++var or --var in an expression is not supported yet.
+
+
+==============================================================================
+
+3. New style functions *fast-functions*
+
+ *: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`.
+ *E1073*
+ The {name} cannot be reused at the script-local level: >mnv9
+
+ mnv9script
+ def F_1073()
+ enddef
+ def F_1073() # E1073: Name already defined: <SNR>...
+ enddef
+< *E1011*
+ The {name} must be less than 100 bytes long.
+
+ *E1077*
+ {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. So, the
+ declaration must provide a type. Example: >mnv9
+
+ mnv9script
+ def F_1077(x): void
+ # E1077: Missing argument type for x
+ enddef
+<
+ For the second form, because the declaration does not
+ specify it, MNV infers the type. For both second and
+ third forms, a default {value} applies when the
+ caller omits it. Examples: >mnv9
+
+ mnv9script
+ def SecondForm(arg = "Hi"): void
+ echo $'2. arg is a "{arg->typename()}" type ' ..
+ $'and the default value of arg is "{arg}"'
+ enddef
+ SecondForm()
+ def ThirdForm(arg2: number = 9): void
+ echo $'3. default value of arg2 is {arg2}'
+ enddef
+ ThirdForm()
+< *E1123*
+ Arguments in a builtin function called in a `:def`
+ function must have commas between arguments: >mnv9
+
+ mnv9script
+ def F_1123(a: number, b: number): void
+ echo max(a b)
+ # E1123: Missing comma before argument: b)
+ enddef
+ F_1123(1, 2)
+< *E1003* *E1027* *E1096*
+ The type of value used with `:return` must match
+ {return-type}. When {return-type} is omitted or is
+ "void" the function is not allowed to return
+ anything. Examples: >mnv9
+
+ mnv9script
+ def F_1003(): bool
+ return # E1003: Missing return value
+ enddef
+ F_1003()
+< >mnv9
+ mnv9script
+ def F_1027(): bool
+ echo false # E1027: Missing return statement
+ enddef
+ F_1027()
+< >mnv9
+ mnv9script
+ def F_1096(): void
+ return false # E1096: Returning a value ...
+ enddef
+ F_1096()
+< *E1056* *E1059*
+ When ": {return-type}" is specified, {return-type}
+ cannot be omitted (leaving a hanging colon). The ": "
+ also cannot be preceded by white space. Examples: >mnv
+
+ def F_1056():
+ # E1056: Expected a type:
+ enddef
+ def F_1059() : bool
+ # E1059: No white space allowed before colon:...
+ enddef
+<
+ The function will be compiled into instructions when
+ called or when either `:defcompile` or `:disassemble` is
+ used. (For an example, see |:disassemble|.) Syntax
+ and type errors will be produced at that time.
+
+ *E1058*
+ It is possible to nest `:def` inside another `:def` or
+ `:function` only up to 49 levels deep. At 50 or more
+ levels, it is a |E1058| error.
+
+ *E1117*
+ [!] is allowed only in legacy MNV script because it
+ permits function redefinition (as with `:function`!).
+ In MNV9 script, ! is not allowed because script-local
+ functions cannot be deleted or redefined, though they
+ can be removed by reloading the script. Also, nested
+ functions cannot use ! for redefinition. Examples: >mnv
+
+ " Legacy MNV script :def! example
+ def! LegacyFunc()
+ echo "def! is allowed in a legacy MNV script"
+ enddef
+ call LegacyFunc()
+< >mnv9
+ mnv9script
+ def Func()
+ def! InnerFunc()
+ # E1117: Cannot use ! with nested :def
+ enddef
+ enddef
+ Func()
+< >mnv9
+ mnv9script
+ def! F_477(): void # E477: No ! allowed
+ enddef
+< >mnv9
+ mnv9script
+ def F_1084(): void
+ enddef
+ delfunction! F_1084
+ # E1084: Cannot delete MNV9 script function F_1084
+<
+ Note: The generic error *E1028* ("Compiling :def
+ function failed") indicates an undeterminable error
+ during compilation. If reproducible, it may be
+ reported at https://github.com/Project-Tick/Project-Tick/issues as
+ it could represent a gap in MNV's error reporting.
+
+ *:enddef* *E1057* *E1152* *E1173*
+:enddef End of a function defined with `:def`. It should be on
+ a line by itself. Examples: >mnv9
+
+ mnv9script
+ def MyFunc()
+ echo 'Do Something' | enddef
+ # E1057: Missing :enddef
+< >mnv9
+ mnv9script
+ def F_1173()
+ enddef echo 'X'
+ # E1173: Text found after enddef: echo 'X'
+< >mnv9
+ mnv9script
+ def F_1152()
+ function X()
+ enddef # E1152: Mismatched enddef
+ enddef
+<
+You may also find this wiki useful. It was written by an early adopter of
+MNV9 script: https://github.com/lacygoill/wiki/blob/master/mnv/mnv9.md
+
+If the script the `:def` function is defined in is MNV9 script, script-local
+variables must be accessed without using the "s:" prefix. They must be
+defined before the function is compiled and there is no way to avoid errors
+(e.g., by using |exists()|) to conditionally skip undeclared variables.
+For example: >mnv9
+
+ mnv9script
+ def MyMNV9def()
+ echo unus # Echoes 1
+ # echo s:unus # This would be E1268 (Cannot use s: in MNV9)
+ if exists('duo')
+ # echo duo # This would be E1001 (Variable not found: duo)
+ endif
+ enddef
+ var unus: number = 1
+ MyMNV9def() # MyMNV9def is compiled ("duo" does not exist yet)
+ var duo: number = 2
+<
+If the script the `:def` function is defined in is legacy MNV script,
+script-local variables may be accessed with or without the "s:" prefix.
+However, using "s:" may defer variable resolution to runtime, avoiding
+compilation errors for variables that may not exist yet, as this example
+explains: >mnv
+
+ " legacy MNV script
+ def! MyLegacyDef(): void
+ echo [unus, s:unus] # Echoes [1, 1]
+ # (If uncommented) First sourcing of 'echo s:duo' is E121 and
+ # causes a compilation error; subsequent sourcing echoes 2:
+ # echo s:duo
+ if exists("s:duo")
+ # First sourcing: skips echo; subsequent sourcing: echoes 2
+ echo s:duo
+ endif
+ if exists("duo")
+ # (If uncommented) First sourcing of 'echo duo' is E1001 and
+ # causes a compilation error; subsequent sourcing echoes 2:
+ # echo duo
+ endif
+ enddef
+ let s:unus = 1
+ call MyLegacyDef() " Calls MyLegacyDef() and compiles if not already
+ let s:duo = 2
+< *E1269*
+Script-local variables in a MNV9 script must be declared at the script
+level. They cannot be created in a `:def` function and may not be declared
+in a legacy function with the "s:" prefix. For example: >mnv9
+
+ mnv9script
+ function F_1269()
+ let s:i_wish = v:true
+ endfunction
+ F_1269()
+ # E1269: Cannot create a MNV9 script variable in a function: s:i_wish
+<
+ *:defc* *:defcompile*
+:defc[ompile] Compile functions and classes (|class-compile|)
+ defined in the current script that were not compiled
+ yet. This will report any errors found during
+ compilation.
+
+ Example: When the three lines (up to and including
+ `enddef`) are sourced, there is no error because the
+ MNV9 `:def` function is not compiled. However, if all
+ four lines are sourced, compilation fails: >mnv9
+
+ mnv9script
+ def F_1027(): string
+ enddef
+ defcompile F_1027 # E1027: Missing return statement
+
+:defc[ompile] MyClass Compile all methods in a class. (See |:disassemble|
+ for an example.)
+
+:defc[ompile] {func}
+:defc[ompile] debug {func}
+:defc[ompile] profile {func}
+ Compile function {func}, if needed. Use "debug" and
+ "profile" to specify the compilation mode.
+ This will report any errors found during compilation.
+ {func} can also be "ClassName.functionName" to
+ compile a function or method in a class.
+ {func} can also be "ClassName" to compile all
+ functions and methods in a class.
+
+ *:disa* *:disassemble*
+:disa[ssemble] {func} Show the instructions generated for {func}.
+ This is for debugging and testing.
+ If {func} is not found, error *E1061* occurs.
+ {func} can also be "ClassName.functionName" to
+ disassemble a function in a class.
+ The following example demonstrates using `:defcompile`
+ with a |class| and `:disassemble` with a
+ "ClassName.functionName" (positioning the cursor on
+ the last line of the visually sourced script): >mnv9
+
+ mnv9script
+ class Line
+ var lnum: number
+ def new(this.lnum)
+ enddef
+ def SetLnum()
+ cursor(this.lnum, 52)
+ enddef
+ endclass
+ defcompile Line
+ disassemble Line.SetLnum
+ var vlast: Line = Line.new(line("'>"))
+ vlast.SetLnum() # Cursor is positioned here->_
+
+:disa[ssemble] profile {func}
+ Like `:disassemble` but with the instructions used for
+ profiling.
+
+:disa[ssemble] debug {func}
+ Like `:disassemble` but with the instructions used for
+ debugging.
+
+ Note: For command line completion of {func}, script-local functions
+ are shown with their <SNR>. Depending on options, including
+ |wildmenumode()|, completion may work with "s:", "<S", or the function
+ name directly. (For example, in MNV started with |-u| NONE, ":disa s:"
+ and |c_CTRL-E| lists script-local function names.)
+
+
+Limitations ~
+
+Variables local to `:def` functions are not visible to string evaluation.
+The following example shows that the script-local constant "SCRIPT_LOCAL" is
+visible whereas the function-local constant "DEF_LOCAL" is not: >mnv9
+
+ mnv9script
+ const SCRIPT_LOCAL = ['A', 'script-local', 'list']
+ def MapList(scope: string): list<string>
+ const DEF_LOCAL: list<string> = ['A', 'def-local', 'list']
+ if scope == 'script local'
+ return [1]->map('SCRIPT_LOCAL[v:val]')
+ else
+ return [1]->map('DEF_LOCAL[v:val]')
+ endif
+ enddef
+ echo 'script local'->MapList() # Echoes ['script-local']
+ echo 'def local'->MapList() # E121: Undefined variable: DEF_LOCAL
+<
+The map argument is a string expression, which is evaluated without the
+function scope. Instead, in MNV9 script, use a lambda: >mnv9
+
+ mnv9script
+ def MapList(): list<string>
+ const DEF_LOCAL: list<string> = ['A', 'def-local', 'list']
+ return [1]->map((_, v) => DEF_LOCAL[v])
+ enddef
+ echo MapList() # Echoes ['def-local']
+<
+For commands that are not compiled, such as `:edit`, |backtick-expansion| can
+be used and it can use the local scope. Example: >mnv9
+
+ mnv9script
+ def EditNewBlah()
+ var fname: string = 'blah.txt'
+ split
+ edit `=fname`
+ enddef
+ EditNewBlah() # A new split is created as buffer 'blah.txt'
+<
+Closures defined in a loop can either share a variable or each have their own
+copy, depending on where the variable is declared. With a variable declared
+outside the loop, all closures reference the same shared variable.
+The following example demonstrates the consequences, with the "outloop"
+variable existing only once: >mnv9
+
+ mnv9script
+ var flist: list<func>
+ def ClosureEg(n: number): void
+ var outloop: number = 0 # outloop is declared outside the loop!
+ for i in range(n)
+ outloop = i
+ flist[i] = (): number => outloop # Closures ref the same var
+ endfor
+ echo range(n)->map((i, _) => flist[i]())
+ enddef
+ ClosureEg(4) # Echoes [3, 3, 3, 3]
+<
+All closures put in the list refer to the same instance, which, in the end,
+is 3.
+
+However, when the variable is declared inside the loop, each closure gets its
+own copy, as shown in this example: >mnv9
+
+ mnv9script
+ var flist: list<func>
+ def ClosureEg(n: number): void
+ for i in range(n)
+ var inloop: number = i # inloop is declared inside the loop
+ flist[i] = (): number => inloop # Closures ref each inloop
+ endfor
+ echo range(n)->map((i, _) => flist[i]())
+ enddef
+ ClosureEg(4) # Echoes [0, 1, 2, 3]
+
+Another way to have a separate context for each closure is to call a
+function to define it: >mnv9
+
+ mnv9script
+ def GetClosure(i: number): func
+ var infunc: number = i
+ return (): number => infunc
+ enddef
+ var flist: list<func>
+ def ClosureEg(n: number): void
+ for i in range(n)
+ flist[i] = GetClosure(i)
+ endfor
+ echo range(n)->map((i, _) => flist[i]())
+ enddef
+ ClosureEg(4) # Echoes [0, 1, 2, 3]
+< *E1271*
+A closure must be compiled in the context that it is defined in, so that
+variables in that context can be found. This mostly happens correctly,
+except when a function is marked for debugging with `:breakadd` after it was
+compiled. Make sure to define the breakpoint before compiling the outer
+function.
+ *E1248*
+In some situations, such as when a MNV9 closure which captures local variables
+is converted to a string and then executed, an error occurs. This happens
+because the string execution context cannot access the local variables from
+the original context where the closure was defined. For example: >mnv9
+
+ mnv9script
+ def F_1248(): void
+ var n: number
+ var F: func = () => {
+ n += 1
+ }
+ try
+ execute printf("call %s()", F)
+ catch
+ echo v:exception
+ endtry
+ enddef
+ F_1248() # MNV(call):E1248: Closure called from invalid context
+
+In MNV9 script, a loop variable is invalid after the loop is closed.
+For example, this timer will echo 0 to 2 on separate lines. However, if
+the variable "n" is used after the `:endfor`, that is an |E121| error: >mnv9
+
+ mnv9script
+ for n in range(3)
+ var nr: number = n
+ timer_start(1000 * n, (_) => {
+ echowindow nr
+ })
+ endfor
+ try
+ echowindow n
+ catch
+ echo v:exception
+ endtry
+<
+ Note: Using `:echowindow` is useful in a timer because messages go
+ into a popup and will not interfere with what the user is
+ doing when it triggers.
+
+
+Converting a :function to a :def~
+ *convert_legacy_function_to_mnv9*
+ *convert_:function_to_:def*
+There are many changes that need to be made to convert a `:function` to
+a `:def` function. The following are some of them:
+
+- Change `let` used to declare variables to one of `var`, `const`, or `final`,
+ and remove the "s:" from each |script-variable|.
+- Change `func` or `function` to `def`.
+- Change `endfunc` or `endfunction` to `enddef`.
+- Add the applicable type (or "any") to each function argument.
+- Remove "a:" from each |function-argument|.
+- Remove inapplicable options such as |:func-range|, |:func-abort|,
+ |:func-dict|, and |:func-closure|.
+- If the function returns something, add the return type. (Ideally, add
+ "void" if it does not return anything.)
+- Remove line continuation backslashes from places they are not required.
+- Remove `let` for assigning values to |g:|, |b:|, |w:|, |t:|, or |l:| variables.
+- Rewrite |lambda| expressions in MNV9 script syntax (see |mnv9-lambda|).
+- Change comments to start with # (preceded by white space) instead of ".
+- Insert white space in expressions where required (see |mnv9-white-space|).
+- Change "." used for string concatenation to " .. ". (Alternatively, use
+ an |interpolated-string|.)
+
+The following legacy MNV script and MNV9 script examples demonstrate all
+those differences. First, legacy MNV script: >mnv
+
+ let s:lnum=0
+ function Leg8(arg) abort
+ let l:pre=['Result',
+ \': ']
+ let b:arg=a:arg
+ let s:lnum+=2
+ let b:arg*=4
+ let l:result={pre->join(pre,'')}(l:pre)
+ return l:result.(b:arg+s:lnum)"no space before comment
+ endfunction
+ call Leg8(10)->popup_notification(#{time: 3000})" Pops up 'Result: 42'
+
+The equivalent in MNV9 script: >mnv9
+
+ mnv9script
+ var lnum: number
+ def MNV9(arg: number): string
+ final pre = ['Result',
+ ': ']
+ b:arg = arg
+ lnum += 2
+ b:arg *= 4
+ const RESULT: string = ((lpre) => join(lpre, ''))(pre)
+ return RESULT .. (b:arg + lnum) # space required before # comment
+ enddef
+ MNV9(10)->popup_notification({time: 3000}) # Pops up 'Result: 42'
+
+< Note: This example also demonstrates (outside the `:def` function):
+ - Removing "#" from the legacy |#{}| - see |mnv9-literal-dict|
+ - Omitting `:call` (allowed, though unnecessary in MNV9 script)
+
+
+Calling a :def function in an expr option ~
+ *expr-option-function*
+The value of a few options, such as 'foldexpr', is an expression that is
+evaluated to get a value. The evaluation can have quite a bit of overhead.
+One way to minimize the overhead, and also to keep the option value simple,
+is to define a compiled function and set the option to call it without
+arguments. For example: >mnv9
+
+ mnv9script
+ def MyFoldFunc(): string
+ # This matches start of line (^), followed by a digit, a full stop
+ # a space or tab, an uppercase character, with an empty next line
+ return getline(v:lnum) =~ '^[[:digit:]]\.[[:blank:]][[:upper:]]'
+ && getline(v:lnum + 1)->empty() ? '>1' : '1'
+ enddef
+ set foldexpr=MyFoldFunc()
+ set foldmethod=expr
+ norm! zM
+<
+ Warning: This script creates and applies folds at the "Heading 1" level of
+ this mnv9.txt help buffer. (You can use |zR|, in Normal mode, to
+ open all the folds after sourcing the script.)
+
+
+==============================================================================
+
+4. Types *mnv9-types*
+
+The following types, each shown with its corresponding internal |v:t_TYPE|
+variable, are supported:
+
+ number |v:t_number|
+ string |v:t_string|
+ func |v:t_func|
+ func: {type} |v:t_func|
+ func({type}, ...) |v:t_func|
+ func({type}, ...): {type} |v:t_func|
+ list<{type}> |v:t_list|
+ dict<{type}> |v:t_dict|
+ float |v:t_float|
+ bool |v:t_bool|
+ none |v:t_none|
+ job |v:t_job|
+ channel |v:t_channel|
+ blob |v:t_blob|
+ class |v:t_class|
+ object |v:t_object|
+ typealias |v:t_typealias|
+ enum |v:t_enum|
+ enumvalue |v:t_enumvalue|
+ tuple<{type}> |v:t_tuple|
+ tuple<{type}, {type}, ...> |v:t_tuple|
+ tuple<...list<{type}>> |v:t_tuple|
+ tuple<{type}, ...list<{type}>> |v:t_tuple|
+ void
+
+ *E1031* *E1186*
+These types can be used in declarations, though no simple value can have the
+"void" type. Trying to use a void as a value results in an error. Examples: >mnv9
+
+ mnv9script
+ def NoReturnValue(): void
+ enddef
+ try
+ const X: any = NoReturnValue()
+ catch
+ echo v:exception # E1031: Cannot use void value
+ try
+ echo NoReturnValue()
+ catch
+ echo v:exception # E1186: Expression does not result in a ...
+ endtry
+ endtry
+< *E1008* *E1009* *E1010* *E1012*
+Ill-formed declarations and mismatching types result in errors. The following
+are examples of errors E1008, E1009, E1010, and E1012: >mnv9
+
+ mnv9cmd var l: list
+ mnv9cmd var l: list<number
+ mnv9cmd var l: list<invalidtype>
+ mnv9cmd var l: list<number> = ['42']
+<
+There is no array type. Instead, use either a list or a tuple. Those types
+may also be literals (constants). In the following example, [5, 6] is a list
+literal and (7, ) a tuple literal. The echoed list is a list literal too: >mnv9
+
+ mnv9script
+ var l: list<number> = [1, 2]
+ var t: tuple<...list<number>> = (3, 4)
+ echo [l, t, [5, 6], (7, )]
+<
+ *tuple-type*
+A tuple type may be declared in the following ways:
+tuple<number> a tuple with a single item of type |Number|
+tuple<number, string> a tuple with two items of type |Number| and
+ |String|
+tuple<number, float, bool> a tuple with three items of type |Number|,
+ |Float| and |Boolean|
+tuple<...list<number>> a variadic tuple with zero or more items of
+ type |Number|
+tuple<number, ...list<string>> a tuple with an item of type |Number| followed
+ by zero or more items of type |String|
+
+Examples: >
+ var myTuple: tuple<number> = (20,)
+ var myTuple: tuple<number, string> = (30, 'mnv')
+ var myTuple: tuple<number, float, bool> = (40, 1.1, true)
+ var myTuple: tuple<...list<string>> = ('a', 'b', 'c')
+ var myTuple: tuple<number, ...list<string>> = (3, 'a', 'b', 'c')
+<
+ *variadic-tuple* *E1539*
+A variadic tuple has zero or more items of the same type. The type of a
+variadic tuple must end with a list type. Examples: >
+ var myTuple: tuple<...list<number>> = (1, 2, 3)
+ var myTuple: tuple<...list<string>> = ('a', 'b', 'c')
+ var myTuple: tuple<...list<bool>> = ()
+<
+ *mnv9-func-declaration* *E1005* *E1007*
+ *mnv9-partial-declaration*
+ *mnv9-func-type*
+A function (or partial) may be declared in the following ways:
+func any kind of function reference, no type
+ checking for arguments or return value
+func: void any number and type of arguments, no return
+ value
+func: {type} any number and type of arguments with specific
+ return type
+
+func() function with no argument, does not return a
+ value
+func(): void same
+func(): {type} function with no argument and return type
+
+func({type}) function with argument type, does not return
+ a value
+func({type}): {type} function with argument type and return type
+func(?{type}) function with type of optional argument, does
+ not return a value
+func(...list<{type}>) function with type of list for variable number
+ of arguments, does not return a value
+func({type}, ?{type}, ...list<{type}>): {type}
+ function with:
+ - type of mandatory argument
+ - type of optional argument
+ - type of list for variable number of
+ arguments
+ - return type
+
+If the return type is "void" the function does not return a value.
+
+The reference can also be a |Partial|, in which case it stores extra arguments
+and/or a dictionary, which are not visible to the caller. Since they are
+called in the same way, the declaration is the same. This interactive example
+prompts for a circle's radius and returns its area to two decimal places,
+using a partial: >mnv9
+
+ mnv9script
+ def CircleArea(pi: float, radius: float): float
+ return pi * radius->pow(2)
+ enddef
+ const AREA: func(float): float = CircleArea->function([3.14])
+ const RADIUS: float = "Enter a radius value: "->input()->str2float()
+ echo $"\nThe area of a circle with a radius of {RADIUS} is " ..
+ $"{AREA(RADIUS)} (π to two d.p.)"
+<
+ *mnv9-typealias-type*
+Custom types (|typealias|) can be defined with `:type`. They must start with
+a capital letter (which avoids name clashes with either current or future
+builtin types) similar to user functions. This example creates a list of
+perfect squares and reports on |type()| (14, a typealias) and the |typename()|: >mnv9
+
+ mnv9script
+ type Ln = list<number>
+ final perfect_squares: Ln = [1, 4, 9, 16, 25]
+ echo "Typename (Ln): " ..
+ $"type() is {Ln->type()} and typename() is {Ln->typename()}"
+<
+ *E1105*
+A typealias itself cannot be converted to a string: >mnv9
+
+ mnv9script
+ type Ln = list<number>
+ const FAILS: func = (): string => {
+ echo $"{Ln}" # E1105: Cannot convert typealias to string
+ }
+<
+ *mnv9-class-type* *mnv9-interface-type*
+ *mnv9-object-type*
+A |class|, |object|, and |interface| may all be used as types. The following
+interactive example prompts for a float value and returns the area of two
+different shapes. It also reports on the |type()| and |typename()| of the
+classes, objects, and interface: >mnv9
+
+ mnv9script
+ interface Shape
+ def InfoArea(): tuple<string, float>
+ endinterface
+ class Circle implements Shape
+ var radius: float
+ def InfoArea(): tuple<string, float>
+ return ('Circle (π × r²)', 3.141593 * this.radius->pow(2))
+ enddef
+ endclass
+ class Square implements Shape
+ var side: float
+ def InfoArea(): tuple<string, float>
+ return ('Square (s²)', this.side->pow(2))
+ enddef
+ endclass
+ const INPUT: float = "Enter a float value: "->input()->str2float()
+ echo "\nAreas of shapes:"
+ var myCircle: object<Circle> = Circle.new(INPUT)
+ var mySquare: object<Square> = Square.new(INPUT)
+ final shapes: list<Shape> = [myCircle, mySquare]
+ for shape in shapes
+ const [N: string, A: float] = shape.InfoArea()
+ echo $"\t- {N} has area of {A}"
+ endfor
+ echo "\n\t\ttype()\ttypename()\n\t\t------\t----------"
+ echo $"Circle\t\t{Circle->type()}\t{Circle->typename()}"
+ echo $"Square\t\t{Square->type()}\t{Square->typename()}"
+ echo $"Shape\t\t{Shape->type()}\t{Shape->typename()}"
+ echo $"MyCircle\t{myCircle->type()}\t{myCircle->typename()}"
+ echo $"MySquare\t{mySquare->type()}\t{mySquare->typename()}"
+ echo $"shapes\t\t{shapes->type()}\t{shapes->typename()}"
+<
+ *mnv9-enum-type* *mnv9-enumvalue-type*
+An |enum| may be used as a type (|v:t_enum|). Variables holding enum values
+have the enumvalue type (|v:t_enumvalue|) at runtime. The following
+interactive example prompts for a character and returns information about
+either a square or a rhombus. It also reports on the |type()| and |typename()|
+of the enum and enumvalue: >mnv9
+
+ mnv9script
+ enum Quad
+ Square('four', 'only'),
+ Rhombus('opposite', 'no')
+ var eq: string
+ var ra: string
+ def string(): string
+ return $"\nA {this.name} has " ..
+ $"{this.eq} equal sides and {this.ra} right angles\n\n"
+ enddef
+ endenum
+ echo "Rhombus (r) or Square (s)?"
+ var myQuad: Quad = getcharstr() =~ '\c^R' ? Quad.Rhombus : Quad.Square
+ echo myQuad.string() .. "\ttype()\ttypename()"
+ echo $"Quad \t{Quad->type()} \t{Quad->typename()}"
+ echo $"myQuad\t{myQuad->type()}\t{myQuad->typename()}"
+<
+ Notes: This script uses builtin method "string()" (|object-string()|).
+ The typename() of Quad and myQuad are the same ("enum<Quad>")
+ whereas the type() is distinguished (myQuad returns 16,
+ enumvalue, whereas Quad returns 15, enum).
+
+Variable types and type casting ~
+ *variable-types*
+Variables declared in MNV9 script or in a `:def` function have a type, either
+specified explicitly or inferred from the initialization.
+
+Global, buffer, window and tab page variables do not have a specific type.
+Consequently, their values may change at any time, possibly changing the type.
+Therefore, in compiled code, the "any" type is assumed.
+
+This can be a problem when stricter typing is desired, for example, when
+declaring a list: >
+ var l: list<number> = [1, b:two]
+Since MNV doesn't know the type of "b:two", the expression becomes list<any>.
+A runtime check verifies the list matches the declared type before assignment.
+
+ *type-casting*
+To get more specific type checking, use type casting. This checks the
+variable's type before building the list, rather than checking whether
+list items match the declared type. For example: >
+ var l: list<number> = [1, <number>b:two]
+<
+So, here the type cast checks whether "b:two" is a number and gives an error
+if it isn't.
+
+The difference is demonstrated in the following example. With funcref
+variable "NTC", MNV infers the expression type "[1, b:two]" as list<any>, then
+verifies whether it can be assigned to the list<number> return type. With
+funcref variable "TC", the type cast means MNV first checks whether "b:two" is
+a <number> type: >mnv9
+
+ mnv9script
+ b:two = '2'
+ const NTC: func = (): list<number> => {
+ return [1, b:two]
+ }
+ disassemble NTC # 3 CHECKTYPE list<number> stack [-1]
+ try
+ NTC()
+ catch
+ echo v:exception .. "\n\n" # expected list<number> but...
+ endtry
+ const TC: func = (): list<number> => {
+ return [1, <number>b:two]
+ }
+ disassemble TC # 2 CHECKTYPE number stack [-1]
+ try
+ TC()
+ catch
+ echo v:exception # expected number but got string
+ endtry
+<
+ Note: Notice how the error messages differ, showing when
+ type checking occurs.
+
+ *E1104*
+The syntax of a type cast is "<{type}>". An error occurs if either the
+opening "<" (|E121|) or closing ">" (E1104) is omitted. Also, white space
+is not allowed either after the "<" (|E15|) or before the ">" (|E1068|), which
+avoids ambiguity with smaller-than and greater-than operators.
+
+Although a type casting forces explicit type checking, it neither changes the
+value of, nor the type of, a variable. If you need to alter the type, use a
+function such as |string()| to convert to a string, or |str2nr()| to convert a
+string to a number.
+
+If type casting is applied to a chained expression, it must be compatible with
+the final result. Examples: >mnv9
+
+ mnv9script
+ # These type casts work
+ echo <list<any>>[3, 2, 1]->extend(['Go!'])
+ echo <string>[3, 2, 1]->extend(['Go!'])->string()
+ echo <tuple<...list<number>>>[3, 2, 1]->list2tuple()
+ # This type cast fails
+ echo <number>[3, 2, 1]->extend(['Go!'])->string()
+<
+ *E1272*
+If a type is used in a context where types are not expected you can get
+E1272. For example: >
+ :mnv9cmd echo islocked('x: string')
+< Note: This must be executed from MNV's command line, not sourced.
+
+ *E1363*
+If a type is incomplete, such as when an object's class is unknown, E1363
+results. For example: >mnv9
+
+ mnv9script
+ var E1363 = null_class.member # E1363: Incomplete type
+<
+Another null object-related error is |E1360|: >mnv9
+
+ mnv9script
+ var obj = null_object
+ var E1360 = obj.MyMethod() # E1360: Using a null object
+<
+
+Type inference ~
+ *type-inference*
+Declaring types explicitly provides many benefits, including targeted type
+checking and clearer error messages. Nonetheless, MNV often can infer types
+automatically when they are omitted. For example, each of these variables'
+types are inferred, with the |type()| and |typename()| echoed showing those
+inferred types: >mnv9
+
+ mnv9script
+ echo "\t type()\t typename()"
+ var b = true | echo $"{b} \t {b->type()} \t {b->typename()}"
+ var f = 4.2 | echo $"{f} \t {f->type()} \t {f->typename()}"
+ var l = [1, 2] | echo $"{l} \t {l->type()} \t {l->typename()}"
+ var n = 42 | echo $"{n} \t {n->type()} \t {n->typename()}"
+ var s = 'yes' | echo $"{s} \t {s->type()} \t {s->typename()}"
+ var t = (42, ) | echo $"{t} \t {t->type()} \t {t->typename()}"
+<
+The type of a list, tuple, or dictionary is inferred from the common type of
+its values. When the values are all the same type, that type is used.
+If there is a mix of types, the "any" type is used. In the following example,
+the echoed |typename()| for each literal demonstrates these points: >mnv9
+
+ mnv9script
+ echo [1, 2]->typename() # list<number>
+ echo [1, 'x']->typename() # list<any>
+ echo {ints: [1, 2], bools: [false]}->typename() # dict<list<any>>
+ echo (true, false)->typename() # tuple<bool, bool>
+<
+The common type of function references, when they do not all have the same
+number of arguments, is indicated with "(...)", meaning the number of
+arguments is unequal. This script demonstrates a "list<func(...): void>": >mnv9
+
+ mnv9script
+ def Foo(x: bool): void
+ enddef
+ def Bar(x: bool, y: bool): void
+ enddef
+ var funclist = [Foo, Bar]
+ echo funclist->typename()
+<
+Script-local variables in a MNV9 script are type checked. The type is
+also checked for variables declared in a legacy function. For example: >mnv9
+
+ mnv9script
+ var my_local = (1, 2)
+ function Legacy()
+ let b:legacy = [1, 2]
+ endfunction
+ Legacy()
+ echo $"{my_local} is type {my_local->type()} ({my_local->typename()})"
+ echo $"{b:legacy} is type {b:legacy->type()} ({b:legacy->typename()})"
+<
+ *E1013*
+When a type is declared for a List, Tuple, or Dictionary, the type is attached
+to it. Similarly, if a type is not declared, the type MNV infers is attached.
+In either case, if an expression attempts to change the type, E1013 results.
+This example has its type inferred and demonstrates E1013: >mnv9
+
+ mnv9script
+ var lb = [true, true] # Two bools, so MNV infers list<bool> type
+ echo lb->typename() # Echoes list<bool>
+ lb->extend([0]) # E1013 Argument 2: type mismatch, ...
+<
+If you want a permissive list, either explicitly use <any> or declare an
+empty list initially (or both, i.e., `list<any> = []`). Examples: >mnv9
+
+ mnv9script
+ final la: list<any> = []
+ echo la->extend(['two', 1])
+ final le = []
+ echo le->extend(la)
+<
+Similarly for a permissive dictionary: >mnv9
+
+ mnv9script
+ final da: dict<any> = {}
+ echo da->extend({2: 2, 1: 'One'})
+ final de = {}
+ echo de->extend(da)->string()
+<
+And, although tuples themselves are immutable, permissive tuple concatenation
+can be achieved with either "any" or an empty tuple: >mnv9
+
+ mnv9script
+ var t_any: tuple<...list<any>> = (3, '2')
+ t_any = t_any + (true, )
+ echo t_any
+ var t_dec_empty = ()
+ t_dec_empty = t_dec_empty + (3, '2', true)
+ echo t_dec_empty
+<
+If a list literal or dictionary literal is not bound to a variable, its type
+may change, as this example shows: >mnv9
+
+ mnv9script
+ echo [3, 2, 1]->typename() # list<number>
+ echo [3, 2, 1]->extend(['Zero'])->typename() # list<any>
+ echo {1: ['One']}->typename() # dict<list<string>>
+ echo {1: ['One']}->extend({2: [2]})->typename() # dict<list<any>>
+<
+
+Stricter type checking ~
+ *type-checking*
+In legacy MNV script, where a number was expected, a string would be
+automatically converted to a number. This was convenient for an actual number
+such as "123", but leads to unexpected problems (and no error message) if the
+string doesn't start with a number. Quite often this leads to hard-to-find
+bugs. For example, in legacy MNV script this echoes "1": >mnv
+
+ echo 123 == '123'
+<
+However, if an unintended space is included, "0" is echoed: >mnv
+
+ echo 123 == ' 123'
+<
+ *E1206*
+In MNV9 script this has been made stricter. In most places it works just as
+before if the value used matches the expected type. For example, in both
+legacy MNV script and MNV9 script trying to use anything other than a
+dictionary when it is required: >mnv
+
+ echo [8, 9]->keys()
+ mnv9cmd echo [8, 9]->keys() # E1206: Dictionary required
+<
+ *E1023* *E1024* *E1029* *E1030*
+ *E1174* *E1175* *E1210* *E1212*
+However, sometimes there will be an error in MNV9 script, which breaks
+backwards compatibility. The following examples illustrate various places
+this happens. The legacy MNV script behavior, which does not fail, is shown
+first. It is followed by the error that occurs if the same command is used
+in MNV9 script.
+
+ - Using a number (except 0 or 1) where a bool is expected: >mnv
+
+ echo v:version ? v:true : v:false
+ mnv9cmd echo v:version ? true : false # E1023: Using a Number as a...
+<
+ - Using a number where a string is expected: >mnv
+
+ echo filter([1, 2], 0)
+ mnv9cmd echo filter([1, 2], 0) # E1024: Using a Number as a String
+<
+ - Not using a number where a number is expected: >mnv
+
+ " In this example, MNV script treats v:false as 0
+ function Not1029()
+ let b:l = [42] | unlet b:l[v:false]
+ endfunction
+ call Not1029() | echo b:l
+< >mnv9
+ mnv9script
+ def E1029(): void
+ b:l = [42] | unlet b:l[false]
+ enddef
+ E1029() # E1029: Expected number but got bool
+<
+ - Using a string as a number: >mnv
+
+ let b:l = [42] | unlet b:l['#'] | echo b:l
+ mnv9cmd b:l = [42] | mnv9cmd unlet b:l['#'] # E1030: Using a string...
+<
+ - Not using a string where an argument requires a string: >mnv9
+
+ echo substitute('Hallo', 'a', 'e', v:true)
+ mnv9cmd echo substitute('Hallo', 'a', 'e', true) # E1174: String...
+<
+ - Using an empty string in an argument that requires a non-empty string: >mnv9
+
+ echo exepath('')
+ mnv9cmd echo exepath('') # E1175: Non-empty string required for arg...
+<
+ - Not using a number when it is required: >mnv
+
+ echo gettabinfo('a')
+ mnv9cmd echo gettabinfo('a') # E1210: Number required for argument 1
+<
+ - Not using a bool when it is required: >mnv
+
+ echo char2nr('¡', 2)
+ mnv9cmd echo char2nr('¡', 2) # E1212: Bool required for argument 2
+<
+ - Not using a number when a number is required (|E521|): >mnv
+
+ let &laststatus='2'
+ mnv9cmd &laststatus = '2'
+<
+ - Not using a string when a string is required (|E928|): >mnv
+
+ let &langmenu = 42
+ mnv9cmd &langmenu = 42 # E928: String required
+<
+ - Comparing a |Special| with 'is' fails in some instances (|E1037|, |E1072|): >mnv
+
+ " 1 is echoed because these are both true
+ echo v:null is v:null && v:none is v:none
+ " 0 is echoed because all these expressions are false
+ echo v:none is v:null || v:none is 8 || v:true is v:none
+ " All these are errors in MNV9 script
+ mnv9cmd echo v:null is v:null # E1037: Cannot use 'is' with special
+ mnv9cmd echo v:none is v:none # E1037: Cannot use 'is' with special
+ mnv9cmd echo v:none is v:null # E1037: Cannot use 'is' with special
+ mnv9cmd echo v:none is 8 # E1072: Cannot compare special with numb
+ mnv9cmd echo v:true is v:none # E1072: Cannot compare bool with special
+<
+ Note: Although the last two MNV9 script examples above error using
+ `v:none`, they return `false` using `null` (which is the same
+ as `v:null` - see |v:null|): >mnv9
+
+ mnv9script
+ echo null is 8 # false
+ echo true is null # false
+<
+ - Using a string where a bool is required (|E1135|): >mnv
+
+ echo '42' ? v:true : v:false
+ mnv9cmd echo '42' ? true : false # E1135: Using a String as a Bool
+<
+ - Using a bool as a number (|E1138|): >mnv
+
+ let &laststatus=v:true
+ mnv9cmd &laststatus = true
+<
+ - Not using a string where an argument requires a string (|E1174|) >mnv
+
+ echo substitute('Hallo', 'a', 'e', v:true)
+ mnv9cmd echo substitute('Hallo', 'a', 'e', true) # E1174: String...
+<
+One consequence is that the item type of a list or dict given to |map()| must
+not change when its type is either declared or inferred. For example, this
+gives an error in MNV9 script, whereas in legacy MNV script it is allowed: >mnv
+
+ " legacy MNV script changes s:mylist to ['item 0', 'item 1']
+ let s:mylist = [0, 1]
+ call map(s:mylist, {i -> $"item {i}"})
+ echo s:mylist
+< >mnv9
+ mnv9script
+ var mylist = [0, 1] # MNV infers mylist is list<number>
+ map(mylist, (i, _) => $"item {i}") # E1012: type mismatch...
+<
+The error occurs because `map()` tries to modify the list elements to strings,
+which conflicts with the declared type.
+
+Use |mapnew()| instead. It creates a new list, and MNV infers its type if it
+is not specified. Inferred and declared types are shown in this example: >mnv9
+
+ mnv9script
+ var mylist = [0, 1]
+ var infer = mylist->mapnew((i, _) => $"item {i}")
+ echo [infer, infer->typename()]
+ var declare: list<string> = mylist->mapnew((i, _) => $"item {i}")
+ echo [declare, declare->typename()]
+<
+The key concept here is, variables with declared or inferred types cannot
+have the types of the elements within their containers change. However, type
+"changes" are allowed for either:
+ - a container literal (not bound to a variable), or
+ - a container where |copy()| or |deepcopy()| is used in method chaining.
+Both are demonstrated in this example: >mnv9
+
+ mnv9script
+ # list literal
+ echo [1, 2]->map((_, v) => $"#{v}")
+ echo [1, 2]->map((_, v) => $"#{v}")->typename()
+ # deepcopy() in a method chain
+ var mylist = [1, 2]
+ echo mylist->deepcopy()->map((_, v) => $"#{v}")
+ echo mylist->deepcopy()->map((_, v) => $"#{v}")->typename()
+ echo mylist
+<
+The reasoning behind this is, when a type is either declared or inferred
+and the list is passed around and changed, the declaration/inference must
+always hold so that you can rely on the type to match the declared/inferred
+type. For either a list literal or a fully copied list, that type safety is
+not needed because the original list is unchanged (as "echo mylist" shows,
+above).
+
+If the item type was not declared or determined to be "<any>", it will not
+change, even if all items later become the same type. However, when `mapnew()`
+is used, inference means that the new list will reflect the type(s) present.
+For example: >mnv9
+
+ mnv9script
+ # list<any>
+ var mylist = [1, '2'] # mixed types, i.e., list<any>
+ echo (mylist, mylist->typename()) # ([1, '2'], 'list<any>')
+ mylist->map((_, v) => $"item {v}") # all items are now strings
+ echo (mylist, mylist->typename()) # both strings, but list<any>
+ # mapnew()
+ var newlist = mylist->mapnew((_, v) => v)
+ echo (newlist, newlist->typename()) # newlist is a list<string>
+<
+Using |extend()| and |extendnew()| is similar, i.e., a list literal may use
+the former, so, this is okay: >mnv9
+
+ mnv9cmd echo [1, 2]->extend(['3']) # [1, 2, 3]
+<
+whereas, this is not: >mnv9
+
+ mnv9script
+ var mylist: list<number> = [1, 2]
+ echo mylist->extend(['3']) # E1013: Argument 2: type mismatch
+<
+Using |extendnew()| is needed for extending an existing typed list, except
+where the extension matches the list's type (or it is "any"). For example,
+first extending with an element of the same type, then extending with a
+different type: >mnv9
+
+ mnv9script
+ var mylist: list<number> = [1, 2]
+ mylist->extend([3])
+ echo mylist->extendnew(['4']) # [1, 2, 3, '4']
+
+< *E1158*
+Using |flatten()| is not allowed in MNV9 script, because it is intended
+always to change the type. This even applies to a list literal
+(unlike |map()| and |extend()|). Instead, use |flattennew()|: >mnv9
+
+ mnv9cmd [1, [2, 3]]->flatten() # E1158: Cannot use flatten
+ mnv9cmd echo [1, [2, 3]]->flattennew() # [1, 2, 3]
+<
+Assigning to a funcref with specified arguments (see |mnv9-func-declaration|)
+involves strict type checking of the arguments. For example, this works: >mnv9
+
+ mnv9script
+ var F_name_age: func(string, number): string
+ F_name_age = (n: string, a: number): string => $"Name: {n}, Age: {a}"
+ echo F_name_age('Bob', 42)
+<
+whereas this fails with error |E1012| (type mismatch): >mnv9
+
+ mnv9script
+ var F_name_age: func(string, number): string
+ F_name_age = (n: string, a: string): string => $"Name: {n}, Age: {a}"
+<
+If there is a variable number of arguments they must have the same type, as in
+this example: >mnv9
+
+ mnv9script
+ var Fproduct: func(...list<number>): number
+ Fproduct = (...v: list<number>): number => reduce(v, (a, b) => a * b)
+ echo Fproduct(3, 2, 4) # Echoes 24
+<
+And <any> may be used to accommodate mixed types: >mnv9
+
+ mnv9script
+ var FlatSort: func(...list<any>): any
+ FlatSort = (...v: list<any>) => flattennew(v)->sort('n')
+ echo FlatSort(true, [[[5, 3], 2], 4]) # Echoes [true, 2, 3, 4, 5]
+<
+ Note: Using <any> in a lambda does not avoid type checking of the
+ funcref. It remains constrained by the declared funcref's
+ type and, as these examples show, a runtime or compiling error
+ occurs when the types mismatch: >mnv9
+
+ mnv9script
+ var FuncSN: func(string): number
+ FuncSN = (v: any): number => v->str2nr()
+ echo FuncSN('162')->nr2char() # Echoes ¢
+ echo FuncSN(162)->nr2char()) # E1013 (runtime error)
+< >mnv9
+ mnv9script
+ var FuncSN: func(string): number
+ FuncSN = (v: any): number => v->str2nr()
+ def FuncSNfail(): void
+ echo FuncSN('162')->nr2char() # No echo because ...
+ echo FuncSN(162)->nr2char() # Error while compiling
+ enddef
+ FuncSNfail()
+<
+When the funcref has no arguments specified, there is no type checking. This
+example shows FlexArgs has a string argument the first time and a list the
+following time: >mnv9
+
+ mnv9script
+ var FlexArgs: func: string
+ FlexArgs = (s: string): string => $"It's countdown time {s}..."
+ echo FlexArgs("everyone")
+ FlexArgs = (...values: list<string>): string => join(values, ', ')
+ echo FlexArgs('3', '2', '1', 'GO!')
+<
+ *E1211* *E1217* *E1218* *E1219* *E1220*
+ *E1221* *E1222* *E1223* *E1224* *E1225*
+ *E1226* *E1228* *E1235* *E1238* *E1251*
+ *E1253* *E1256* *E1297* *E1298* *E1301*
+ *E1528* *E1529* *E1530* *E1531* *E1534*
+Types are checked for most builtin functions to make it easier to spot
+mistakes. The following one-line |:mnv9| commands, calling builtin functions,
+demonstrate many of those type-checking errors: >mnv9
+
+ mnv9 9->list2blob() # E1211: List required for argume...
+ mnv9 9->ch_close() # E1217: Channel or Job required ...
+ mnv9 9->job_info() # E1218: Job required for argumen...
+ mnv9 [9]->cos() # E1219: Float or Number required...
+ mnv9 {}->remove([]) # E1220: String or Number require...
+ mnv9 null_channel->ch_evalraw(9) # E1221: String or Blob required ...
+ mnv9 9->col() # E1222: String or List required ...
+ mnv9 9->complete_add() # E1223: String or Dictionary req...
+ mnv9 setbufline(9, 9, {}) # E1224: String, Number or List r...
+ mnv9 9->count(9) # E1225: String, List, Tuple or D...
+ mnv9 9->add(9) # E1226: List or Blob required fo...
+ mnv9 9->remove(9) # E1228: List, Dictionary, or Blo...
+ mnv9 getcharstr('9') # E1235: Bool or number required ...
+ mnv9 9->blob2list() # E1238: Blob required for argume...
+ mnv9 9->filter(9) # E1251: List, Tuple, Dictionary,...
+ mnv9 9->reverse() # E1253: String, List, Tuple or B...
+ mnv9 9->call(9) # E1256: String or Function requi...
+ mnv9 null_dict->winrestview() # E1297: Non-NULL Dictionary requ...
+ mnv9 {}->prop_add_list(null_list) # E1298: Non-NULL List required f...
+ mnv9 {}->repeat(9) # E1301: String, Number, List, Tu...
+ mnv9 9->index(9) # E1528: List or Tuple or Blob re...
+ mnv9 9->join() # E1529: List or Tuple required f...
+ mnv9 9->max() # E1530: List or Tuple or Diction...
+ mnv9 9->get(9) # E1531: Argument of get() must b...
+ mnv9 9->tuple2list() # E1534: Tuple required for argum...
+<
+Reserved for future use: *E1227* *E1250* *E1252*
+ E1227: List or Dictionary required for argument %d
+ E1250: Argument of %s must be a List, String, Dictionary or Blob
+ E1252: String, List or Blob required for argument %d
+
+
+Categories of variables, defaults and null handling ~
+ *variable-categories* *null-variables*
+There are three categories of variables:
+ primitive number, float, boolean
+ container string, blob, list, tuple, dict
+ specialized function, job, channel, user-defined-object
+
+When declaring a variable without an initializer, an explicit type must be
+provided. Each category has different default initialization semantics.
+
+Primitives default to type-specific values. All primitives are empty but do
+not equal `null`: >mnv9
+
+ mnv9script
+ var n: number | echo [n, n->empty(), n == null] # [0, 1, false]
+ var f: float | echo [f, f->empty(), f == null] # [0.0, 1, false]
+ var b: bool | echo [b, b->empty(), b == null] # [false, 1, false]
+<
+Containers default to an empty container. Only an empty string equals `null`: >mnv9
+
+ mnv9script
+ var s: string | echo [s, s->empty(), s == null] # ['', 1, true]
+ var z: blob | echo [z, z->empty(), z == null] # [0z, 1, false]
+ var l: list<string> | echo [l, l->empty(), l == null] # [[], 1, false]
+ var t: tuple<any> | echo [t, t->empty(), t == null] # [(), 1, false]
+ var d: dict<number> | echo [d, d->empty(), d == null] # [{}, 1, false]
+<
+Specialized types default to equaling `null`: >mnv9
+
+ mnv9script
+ var F: func | echo [F, F == null] # [function(''), true]
+ var j: job | echo [j, j == null] # ['no process', true]
+ var c: channel | echo [c, c == null] # ['channel fail', true]
+ class Class
+ endclass
+ var o: Class | echo [o, o == null] # [object of [unknown], true]
+ enum Enum
+ endenum
+ var e: Enum | echo [e, e == null] # [object of [unknown], true]
+<
+ Note: See |empty()| for explanations of empty job, empty channel, and
+ empty object types.
+
+MNV does not have a familiar null value. Instead, it has various null_<type>
+predefined values including |null_string|, |null_list|, and |null_job|.
+Primitives do not have a null_<type>. Typical use cases for null_<type> are:
+ - to clear a variable and release its resources,
+ - as a default for a parameter in a function definition (for an example,
+ see |null_blob|), or
+ - assigned to a container or specialized variable to set it to null
+ for later comparison (for an example, see |null-compare|).
+
+For a specialized variable, like `job`, null_<type> is used to clear the
+resources. For example: >mnv9
+
+ mnv9script
+ var mydate: list<string>
+ def Date(channel: channel, msg: string): void
+ mydate->add(msg)
+ enddef
+ var myjob = job_start([&shell, &shellcmdflag, 'date'], {out_cb: Date})
+ echo [myjob, myjob->job_status()]
+ sleep 2
+ echo $"The date and time is {mydate->join('')}"
+ echo [myjob, myjob->job_status()]
+ myjob = null_job # Clear the variable; release the job's resources.
+ echo myjob
+<
+For a container variable, resources may also be cleared by assigning an
+empty container to the variable. For example: >mnv9
+
+ mnv9script
+ var perfect: list<number> = [1, 4]
+ perfect->extend([9, 16, 25])
+ perfect = []
+ echo perfect
+
+Using an empty container, rather than null_<type>, to clear a container
+variable may avoid null complications - see |null-anomalies|.
+
+The initialization semantics of container variables and specialized variables
+differ. For containers:
+ - An uninitialized container defaults to empty but does not equal `null`
+ (except for a uninitialized string).
+ - A container initialized to [], (), {}, "", or 0z is empty but does not
+ equal `null`.
+ - A container initialized as null_<type> defaults to empty and equals `null`.
+
+In the following example, the uninitialized list ("lu") and [] initialized
+list ("li") are equivalent and indistinguishable whereas "ln" is a null
+container, which is similar to, but not equivalent to, an empty container
+(see |null-anomalies|). >mnv9
+
+ mnv9script
+ # uninitialized: empty container, not null
+ var lu: list<any>
+ echo ['lu', $"empty={lu->empty()}", $"null={lu == null}"]
+ # initialized: empty container, not null
+ var li: list<any> = []
+ echo ['li', $"empty={li->empty()}", $"null={li == null}"]
+ # initialized: empty container, null
+ var ln: list<any> = null_list
+ echo ['ln', $"empty={ln->empty()}", $"null={ln == null}"]
+<
+Specialized variables default to equaling null. These job initializations
+are equivalent and indistinguishable: >mnv9
+
+ mnv9script
+ var j1: job
+ var j2: job = null_job
+ var j3 = null_job
+ echo (j1 == j2) == (j2 == j3) # true (equivalent, indistinguishable)
+<
+When a list, tuple, or dict is declared, if the item type is not specified
+it cannot be inferred. Consequently, the item type defaults to "any": >mnv9
+
+ mnv9script
+ var [t1, t2] = [(), null_tuple]
+ echo $'t1 is {t1->typename()} and t2 is {t2->typename()} too'
+<
+Tuples and functions (or partials) may be declared in various ways.
+See |tuple-type|, |variadic-tuple|, and |mnv9-func-declaration|.
+
+ *null-compare*
+For familiar null compare semantics, where an empty container is not equal to
+a null container, do not use null_<type> in a comparison. That is because,
+in MNV9 script, although null_<type> == `null`, comparing an:
+
+ - empty container to `null` is `false`, but
+ - empty container to null_<type> is `true`.
+
+So, compare against `null`, not null_<type>. For example: >mnv9
+
+ mnv9script
+ var bonds: dict<list<string>> = {g: ['007', '008'], o: ['007', '009']}
+ def Search(query: string): list<string>
+ return query == "\r" ? null_list : bonds->get(query, [])
+ enddef
+ echo "Goldfinger (g) or Octopussy (o)?: "
+ const C: string = getcharstr()
+ var result: list<string> = C->Search()
+ if result == null # <<< DO NOT USE null_list HERE!
+ echo "Error: Nothing was entered"
+ else
+ echo result->empty() ? $"No matches for '{C}'" : $"{result}"
+ endif
+<
+ NOTE: Using "result == null_list" instead of "result == null" would
+ fail to distinguish the error (nothing entered) and the valid
+ (nothing matched) result because [] == null_list whereas
+ [] != null.
+
+Conceptually, think of the null_<type> construct as a hybrid/bridge between
+the general `null` and typed `empty` containers, having properties of both.
+In the following section there are details about comparison results.
+
+ *null-details* *null-anomalies*
+This section describes issues about using null and null_<type>; included below
+are the enumerated results of null comparisons. In some cases, if familiar
+with mnv9 null semantics, the programmer may choose to use null_<type> in
+comparisons and/or other situations.
+
+Elsewhere in the documentation it says, "often a null value is handled the
+same as an empty value, but not always". For example, you cannot add to a
+null container: >mnv9
+
+ mnv9script
+ var le: list<any> = []
+ le->add('Okay') # le is now ['Okay']
+ var ln = null_list
+ ln->add("E1130") # E1130: Cannot add to null list
+<
+As explained in |null-compare|, there is a non-transitive relationship among
+`null`, null_<type> containers, and `empty`. To recap, for example: >mnv9
+
+ mnv9cmd echo (null_dict == {}, null_dict == null, {} != null)
+<
+The exception is an uninitialized string. It is equal to `null` (and is the
+same instance as `null_string`). The 'is' operator (|expr-is|) may be used to
+determine whether a string is uninitialized: >mnv9
+
+ mnv9script
+ var s: string
+ echo s == null_string # true
+ echo s is null_string # true (the same instance)
+ echo s == null # true (unexpected, perhaps)
+ echo s is null # false (not the same instance)
+<
+However, don't do the same for the other containers because, when evaluated
+against their applicable null_<type> with 'is', they return `false`: >mnv9
+
+ mnv9script
+ var d: dict<any>
+ echo d == null_dict # true
+ echo d is null_dict # false (not the same instance)
+ echo d == null # false (as expected)
+ echo d is null # false (not the same instance)
+<
+The key distinction here is an uninitialized string is implemented as
+`null_string`, while an uninitialized list, dict, tuple, or blob is
+implemented as an empty container ([], {}, (), and 0z respectively).
+So, those uninitialized types are equal to, but not the same instance as,
+their null_<type> counterparts, as this example shows: >mnv9
+
+ mnv9script
+ var t: tuple<any>
+ echo t == null_tuple # true
+ echo t is null_tuple # false
+
+However, a variable initialized to the null_<type> is equal not only to the
+null_<type>, it is also equal to null. For example: >mnv9
+
+ mnv9script
+ var t: tuple<any> = null_tuple
+ echo t == null_tuple # true
+ echo t is null_tuple # true
+ echo t == null # true
+<
+An uninitialized container variable is not equal to null, except for an
+uninitialized string, which is explained in an example, above. So, these
+all echo `true`: >mnv9
+
+ mnv9script
+ var b: blob | echo b != null
+ var d: dict<any> | echo d != null
+ var l: list<any> | echo l != null
+ var t: tuple<any> | echo t != null
+ var s: string | echo s == null
+
+An uninitialized specialized variable is equal to null so these all echo `true`: >mnv9
+
+ mnv9script
+ var c: channel | echo c == null
+ var F: func | echo F == null
+ var j: job | echo j == null
+ class Class
+ endclass
+ var nc: Class | echo nc == null
+ enum Enum
+ endenum
+ var ne: Enum | echo ne == null
+<
+ Note: the specialized variables, like job, default to null and
+ no specialized variable has a corresponding empty value.
+
+A container variable initialized to empty equals null_<type>, so these are all
+`true`: >mnv9
+
+ mnv9script
+ var s: string = "" | echo s == null_string
+ var b: blob = 0z | echo b == null_blob
+ var l: list<any> = [] | echo l == null_list
+ var t: tuple<any> = () | echo t == null_tuple
+ var d: dict<any> = {} | echo d == null_dict
+<
+However, a container variable initialized to empty does not equal null, so
+these are all `true`: >mnv9
+
+ mnv9script
+ var s: string = "" | echo s != null
+ var b: blob = 0z | echo b != null
+ var l: list<any> = [] | echo l != null
+ var t: tuple<any> = () | echo t != null
+ var d: dict<any> = {} | echo d != null
+<
+
+==============================================================================
+
+ *generic-functions*
+5. Generic functions
+
+A generic function allows using the same function with different type
+arguments, while retaining type checking for arguments and the return value.
+This provides type safety and code reusability.
+
+
+Declaration~
+ *generic-function-declaration*
+ *E1553* *E1554*
+The type variables for a generic function are declared as its type parameters
+within angle brackets ("<" and ">"), directly after the function name.
+Multiple type parameters are separated by commas: >
+
+ def[!] {funcname}<{type} [, {types}]>([arguments])[: {return-type}]
+ {function body}
+ enddef
+< *generic-function-example*
+These type parameters may then be used, like any other type, within the
+function signature and its body. The following example combines two lists
+into a list of tuples: >mnv9
+
+ mnv9script
+ def Zip<T, U>(first: list<T>, second: list<U>): list<tuple<T, U>>
+ const LEN: number = ([first->len(), second->len()])->min()
+ final result: list<tuple<T, U>> = []
+ for i in range(LEN)
+ result->add((first[i], second[i]))
+ endfor
+ return result
+ enddef
+ var n: list<number> = [61, 62, 63]
+ var s: list<string> = ['a', 'b', 'c']
+ echo $"Zip example #1: {Zip<number, string>(n, s)}"
+ echo $"Zip example #2: {Zip<string, number>(s, n)}"
+<
+ *type-variable-naming* *E1552*
+ *type-parameter-naming*
+As in the preceding example, the convention is to use a single capital letter
+for a name (e.g., T, U, A, etc.). Although they may comprise more than one
+letter, names must start with a capital letter. In this example, "Ok" is
+valid whereas "n" is not: >mnv9
+
+ mnv9script
+ def MyFail<Ok, n>(): void
+ enddef
+ # E1552: Type variable name must start with an uppercase letter: n...
+<
+ *E1558* *E1560*
+A function must be declared and used either as a generic function or as a
+regular function, but not both. The following MNV9 scripts demonstrate these
+errors: >mnv9
+
+ mnv9script
+ My1558<number>()
+ # E1558: Unknown generic function: My1558
+< >mnv9
+ mnv9script
+ def My1560(): void
+ enddef
+ My1560<string>()
+ # E1560: Not a generic function: My1560
+<
+ *E1561*
+Type parameter names must not clash with other identifiers: >mnv9
+
+ mnv9script
+ def My1561<D, E, D>(): D
+ enddef
+ # E1561: Duplicate type variable name: D
+
+ mnv9script
+ enum E
+ Yes, No
+ endenum
+ def My1041<E>(): E
+ enddef
+ # E0141: Redefining script item "E"
+<
+
+Calling a generic function~
+ *generic-function-call*
+To call a generic function, specify the concrete types in "<" and ">"
+between the function name and the argument list: >
+
+ MyFunc<number, string, list<number>>()
+<
+ NOTE: There are several working examples in this section, which may
+ be sourced, including |generic-function-example|.
+
+ *E1555* *E1556* *E1557* *E1559*
+The number of passed type arguments to the function must match the number
+of its declared type parameters. An empty type list is not allowed.
+Examples: >mnv9
+
+ mnv9script
+ def My1555<>(): void
+ enddef
+ # E1555: Empty type list specified for generic function ...
+< >mnv9
+ mnv9script
+ def My1556<T>(): void
+ enddef
+ My1556<bool, bool>()
+ # E1556: Too many types specified for generic function ...
+< >mnv9
+ mnv9script
+ def My1557<T, U>(): void
+ enddef
+ My1557<bool>()
+ # E1557: Not enough types specified for generic function ...
+< >mnv9
+ mnv9script
+ def My1559<T>(): T
+ enddef
+ My1559()
+ # MNV(eval):E1559: Type arguments missing for generic function ...
+<
+Any MNV9 type (|mnv9-types|) can be used as a concrete type in a generic
+function.
+
+Spaces are not allowed:
+- Between the function name and "<" (|E1068|)
+- Between ">" and the opening "(" (|E1068|), or
+- Within the "<" and ">", except where required after the comma separating
+ the types (|E1202|).
+
+A generic function can be exported and imported like a regular function.
+See |:export| and |:import|.
+
+A generic function can be defined inside another regular or generic function.
+Example: >mnv9
+ mnv9script
+ def Outer(): void
+ # Returns either the first item of a list or a default value
+ def FirstOrDefault<T, U>(lst: list<T>, default: U): any
+ return lst->len() > 0 ? lst[0] : default
+ enddef
+ echo FirstOrDefault<string, bool>(['B', 'C'], false) # echos B
+ echo FirstOrDefault<number, number>([], 42) # echos 42
+ enddef
+ Outer()
+<
+
+Using a type variable as a type argument ~
+
+A type variable may also be passed as a type argument. For example: >mnv9
+
+ mnv9script
+ # T is declared as a type parameter
+ # It is used for the 'value' parameter and the return type
+ def Id<T>(value: T): T
+ return value
+ enddef
+ # U is declared as a type parameter
+ # It is used for the 'value' parameter and the return type
+ def CallId<U>(value: U): U
+ # U is a type variable passed/used as a type argument
+ return Id<U>(value)
+ enddef
+ echo CallId<string>('I am') .. ' ' .. CallId<number>(42)
+
+This is useful for complex data structures like dictionaries of lists or,
+as in the following example, lists of dictionaries: >mnv9
+
+ mnv9script
+ def Flatten<T>(x: list<list<T>>): list<T>
+ final result: list<T> = []
+ for inner in x
+ result->extend(inner)
+ endfor
+ return result
+ enddef
+ const ENGLISH: list<dict<string>> = [{1: 'one'}, {2: 'two'}]
+ const MANDARIN: list<dict<string>> = [{1: '壹'}, {2: '贰'}]
+ const ARABIC_N: list<dict<number>> = [{1: 1}, {2: 2}]
+ echo Flatten<dict<string>>([ENGLISH, MANDARIN])
+ echo Flatten<dict<any>>([ENGLISH, ARABIC_N])
+<
+In "Flatten<T>", "T" is a declared type parameter. Everywhere else in
+the function, "T" is a type variable referencing that type parameter.
+
+
+Generic class method~
+
+A MNV9 class method can be a generic function: >mnv9
+
+ mnv9script
+ class Config
+ var settings: dict<any>
+ def Get<T>(key: string): T
+ return this.settings[key]
+ enddef
+ endclass
+ var c: Config = Config.new({timeout: 30, debug: true})
+ echo c.Get<number>('timeout')
+ echo c.Get<bool>('debug')
+<
+ *E1432* *E1433* *E1434*
+A generic class method in a base class can be overridden by a generic method
+in a child class. The number of type variables must match between both
+methods. A concrete class method cannot be overridden by a generic method,
+and vice versa.
+
+
+Generic function reference~
+
+A function reference (|Funcref|) can be a generic function. This allows for
+creating factories of functions that operate on specific types: >mnv9
+
+ mnv9script
+ # Match a specified character in a string or the decimal value of the
+ # character in a list. Note: '*' is decimal 42 (U+002A)
+ var c: string = "*"
+ var char_dec: tuple<string, string> = (c, c->char2nr()->string())
+ def Matcher<T>(pattern: string): func(T): bool
+ return (value: T): bool => match(value, pattern) >= 0
+ enddef
+ var StringMatch = Matcher<string>(char_dec[0])
+ echo "*+"->StringMatch() # true (has *)
+ echo ",-"->StringMatch() # false
+ var ListMatch = Matcher<list<number>>(char_dec[1])
+ echo [42, 43]->ListMatch() # true (has 42)
+ echo [44, 45]->ListMatch() # false
+<
+
+Compiling and Disassembling Generic functions~
+
+The |:defcompile| command can be used to compile a generic function with a
+specific list of concrete types: >
+
+ defcompile MyFunc<number, list<number>, dict<string>>
+<
+The |:disassemble| command can be used to list the instructions generated for
+a generic function: >
+
+ disassemble MyFunc<string, dict<string>>
+ disassemble MyFunc<number, list<blob>>
+<
+
+Limitations and Future Work~
+
+Currently, MNV does not support:
+ - Type inference for type variables: All types must be explicitly specified
+ when calling a generic function.
+ - Type constraints: It's not possible to restrict a type variable to a
+ specific class or interface (e.g., `T extends SomeInterface`).
+ - Default type arguments: Providing a default type for a type parameter
+ when not explicitly specified.
+
+==============================================================================
+
+6. Namespace, Import and Export
+ *mnv9script* *mnv9-export* *mnv9-import*
+
+A MNV9 script can be written to be imported. This means that some items are
+intentionally exported, made available to other scripts. When the exporting
+script is imported in another script, these exported items can then be used in
+that script. All the other items remain script-local in the exporting script
+and cannot be accessed by the importing script.
+
+This mechanism exists for writing a script that can be sourced (imported) by
+other scripts, while making sure these other scripts only have access to what
+you want them to. This also avoids using the global namespace, which has a
+risk of name collisions. For example when you have two plugins with similar
+functionality.
+
+You can cheat by using the global namespace explicitly. That should be done
+only for things that really are global.
+
+
+Namespace ~
+ *mnv9-namespace*
+To recognize a file that can be imported the `mnv9script` statement must
+appear as the first statement in the file (see |mnv9-mix| for an exception).
+It tells MNV to interpret the script in its own namespace, instead of the
+global namespace. If a file starts with: >
+ mnv9script
+ var myvar = 'yes'
+Then "myvar" will only exist in this file. While without `mnv9script` it would
+be available as `g:myvar` from any other script and function.
+ *E1101*
+The variables at the file level are very much like the script-local "s:"
+variables in legacy MNV script, but the "s:" is omitted. And they cannot be
+deleted.
+
+In MNV9 script the global "g:" namespace can still be used as before. And the
+"w:", "b:" and "t:" namespaces. These have in common that variables are not
+declared, have no specific type and they can be deleted. *E1304*
+
+A side effect of `:mnv9script` is that the 'cpoptions' option is set to the
+MNV default value, like with: >
+ :set cpo&mnv
+One of the effects is that |line-continuation| is always enabled.
+The original value of 'cpoptions' is restored at the end of the script, while
+flags added or removed in the script are also added to or removed from the
+original value to get the same effect. The order of flags may change.
+In the |mnvrc| file sourced on startup this does not happen.
+
+ *mnv9-mix*
+There is one way to use both legacy and MNV9 syntax in one script file: >mnv9
+
+ " _legacy MNV script_ comments here
+ if !has('mnv9script')
+ " _legacy MNV script_ comments/commands here
+ finish
+ endif
+ mnv9script
+ # _MNV9 script_ commands/commands from here onwards
+ echowindow $"has('mnv9script') == {has('mnv9script')}"
+<
+This allows for writing a script that takes advantage of the MNV9 script
+syntax if possible, and prevents the mnv9script command from throwing an
+error if used in a version of MNV without 'mnv9script'.
+
+Note that MNV9 syntax changed before MNV 9 so that scripts using the current
+syntax (such as "import from" instead of "import") might throw errors.
+To prevent these, a safer check may be |v:version| >= 900 instead (because
+"has('mnv9script')" will return `v:true` back to MNV 8.2 with patch 3965).
+Sometimes it is prudent to cut off even later. MNV9 script's feature set
+continues to grow so, for example, if tuples are used (introduced in MNV 9.1
+patch 1232), a better condition is: >mnv9
+
+ if !has('patch-9.1.1232')
+ echowindow $"Fail: MNV does not have patch 9.1.1232"
+ finish
+ endif
+ mnv9script
+ echowindow $"Pass: version {v:versionlong}. Continuing ..."
+<
+Whichever mnv-mix condition is used, it only works in one of two ways:
+ 1. The "if" statement evaluates to false, the commands up to `endif` are
+ skipped and `mnv9script` is then the first command actually executed.
+ 2. The "if" statement evaluates to true, the commands up to `endif` are
+ executed and `finish` bails out before reaching `mnv9script`.
+
+
+Export ~
+ *:export* *:exp*
+Exporting an item can be written as: >
+ export const EXPORTED_CONST = 1234
+ export var someValue = ...
+ export final someValue = ...
+ export const someValue = ...
+ export def MyFunc() ...
+ export class MyClass ...
+ export interface MyClass ...
+ export enum MyEnum ...
+< *E1043* *E1044*
+As this suggests, only constants, variables, `:def` functions, classes,
+interfaces and enums can be exported.
+
+ *E1042*
+`:export` can only be used in MNV9 script, at the script level.
+
+
+Import ~
+ *:import* *:imp* *E1094* *E1047* *E1262*
+ *E1048* *E1049* *E1053* *E1071* *E1088* *E1236*
+The exported items can be imported in another script. The import syntax has
+two forms. The simple form: >
+ import {filename}
+<
+Where {filename} is an expression that must evaluate to a string. In this
+form the filename should end in ".mnv" and the portion before ".mnv" will
+become the script local name of the namespace. For example: >
+ import "myscript.mnv"
+<
+This makes each exported item in "myscript.mnv" available as "myscript.item".
+ *:import-as* *E1257* *E1261*
+In case the name is long or ambiguous, this form can be used to specify
+another name: >
+ import {longfilename} as {name}
+<
+In this form {name} becomes a specific script local name for the imported
+namespace. Therefore {name} must consist of letters, digits and '_', like
+|internal-variables|. The {longfilename} expression must evaluate to any
+filename. For example: >
+ import "thatscript.mnv.v2" as that
+< *E1060* *E1258* *E1259* *E1260*
+Then you can use "that.item", etc. You are free to choose the name "that".
+Use something that will be recognized as referring to the imported script.
+Avoid command names, command modifiers and builtin function names, because the
+name will shadow them. It's better not to start the name with a capital
+letter, since it can then also shadow global user commands and functions.
+Also, you cannot use the name for something else in the script, such as a
+function or variable name.
+
+In case the dot in the name is undesired, a local reference can be made for a
+function: >
+ var LongFunc = that.LongFuncName
+
+This also works for constants: >
+ const MAXLEN = that.MAX_LEN_OF_NAME
+
+This does not work for variables, since the value would be copied once and
+when changing the variable the copy will change, not the original variable.
+You will need to use the full name, with the dot.
+
+`:import` can not be used in a function. Imported items are intended to exist
+at the script level and only imported once.
+
+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 rarely be 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.
+ Note that "after/import" is not used.
+
+If the name does not end in ".mnv" then the use of "as name" is required.
+
+Once a MNV9 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.
+
+It is not allowed to import the same script twice, also when using two
+different "as" names.
+
+When using the imported name the dot and the item name must be in the same
+line, there can be no line break: >
+ echo that.
+ name # Error!
+ echo that
+ .name # Error!
+< *import-map*
+When you've imported a function from one script into a MNV9 script you can
+refer to the imported function in a mapping by prefixing it with |<SID>|: >
+ noremap <silent> ,a :call <SID>name.Function()<CR>
+
+When the mapping is defined "<SID>name." will be replaced with <SNR> and the
+script ID of the imported script.
+An even simpler solution is using |<ScriptCmd>|: >
+ noremap ,a <ScriptCmd>name.Function()<CR>
+
+Note that this does not work for variables, only for functions.
+
+ *import-legacy* *legacy-import*
+`:import` can also be used in legacy MNV script. The imported namespace still
+becomes script-local, even when the "s:" prefix is not given. For example: >
+ import "myfile.mnv"
+ call s:myfile.MyFunc()
+
+And using the "as name" form: >
+ import "otherfile.mnv9script" as that
+ call s:that.OtherFunc()
+
+However, the namespace cannot be resolved on its own: >
+ import "that.mnv"
+ echo s:that
+ " ERROR: E1060: Expected dot after name: s:that
+<
+This also affects the use of |<SID>| in the legacy mapping context. Since
+|<SID>| is only a valid prefix for a function and NOT for a namespace, you
+cannot use it to scope a function in a script local namespace. Instead of
+prefixing the function with |<SID>| you should use |<ScriptCmd>|. For example:
+>
+ noremap ,a <ScriptCmd>:call s:that.OtherFunc()<CR>
+<
+ *:import-cycle*
+The `import` commands are executed when encountered. If script A imports
+script B, and B (directly or indirectly) imports A, this will be skipped over.
+At this point items in A after "import B" will not have been processed and
+defined yet. Therefore cyclic imports can exist and not result in an error
+directly, but may result in an error for items in A after "import B" not being
+defined. This does not apply to autoload imports, see the next section.
+
+
+Importing an autoload script ~
+ *mnv9-autoload* *import-autoload*
+For optimal startup speed, loading scripts should be postponed until they are
+actually needed. Using the autoload mechanism is recommended:
+ *E1264*
+ 1. In the plugin, define user commands, functions and/or mappings
+ referring to items imported from an autoload script. >
+
+ import autoload 'for/search.mnv'
+ command -nargs=1 SearchForStuff search.Stuff(<f-args>)
+
+< This goes in .../plugin/anyname.mnv. "anyname.mnv" can be freely
+ chosen. The "SearchForStuff" command is now available to the user.
+
+ The "autoload" argument to `:import` means that the script is not
+ loaded until one of the items is actually used. The script will be
+ found under the "autoload" directory in 'runtimepath' instead of the
+ "import" directory. Alternatively, either a relative or absolute
+ name can be used - see below.
+
+ 2. In the autoload script put the bulk of the code. >
+
+ mnv9script
+ export def Stuff(arg: string): void
+ ...
+
+< This goes in .../autoload/for/search.mnv.
+
+ Putting the "search.mnv" script under the "/autoload/for/" directory
+ has the effect that "for#search#" will be prefixed to every exported
+ item. The prefix is obtained from the file name, just as you would
+ add it manually in a legacy autoload script. Thus the exported
+ function can be found with "for#search#Stuff", but you would normally
+ use `import autoload` and not use the prefix (which has the side effect
+ of loading the autoload script when compiling a function that
+ encounters this name).
+
+ You can split up the functionality and import other scripts from the
+ autoload script as you like. This way you can share code between
+ plugins.
+
+Searching for the autoload script in all entries in 'runtimepath' can be a bit
+slow. If the plugin knows where the script is located, quite often a relative
+path can be used. This avoids the search and should be quite a bit faster.
+Another advantage is that the script name does not need to be unique. Also,
+an absolute path is possible. Examples: >
+ import autoload '../lib/implement.mnv'
+ import autoload MyScriptsDir .. '/lib/implement.mnv'
+
+For defining a mapping that uses the imported autoload script the special key
+|<ScriptCmd>| is useful. It allows for a command in a mapping to use the
+script context of where the mapping was defined.
+
+When compiling a `:def` function and a function in an autoload script is
+encountered, the script is not loaded until the `:def` function is called.
+This also means you get any errors only at runtime, since the argument and
+return types are not known yet. If you would use the name with '#' characters
+then the autoload script IS loaded.
+
+Be careful to not refer to an item in an autoload script that does trigger
+loading it unintentionally. For example, when setting an option that takes a
+function name, make sure to use a string, not a function reference: >
+ import autoload 'qftf.mnv'
+ &quickfixtextfunc = 'qftf.Func' # autoload script NOT loaded
+ &quickfixtextfunc = qftf.Func # autoload script IS loaded
+On the other hand, it can be useful to load the script early, at a time when
+any errors should be given.
+
+For testing the |test_override()| function can be used to have the
+`import autoload` load the script right away, so that the items and types can
+be checked without waiting for them to be actually used: >
+ test_override('autoload', 1)
+Reset it later with: >
+ test_override('autoload', 0)
+Or: >
+ test_override('ALL', 0)
+
+
+==============================================================================
+
+7. Classes and interfaces *mnv9-classes*
+
+In legacy MNV script, a Dictionary could be used as a kind-of object by adding
+members that are functions. However, this is quite inefficient and requires
+the writer to do the work of making sure all the objects have the right
+members. See |Dictionary-function|.
+
+In |MNV9| script you can have classes, objects, interfaces, and enums like
+in most popular object-oriented programming languages. Since this is a lot
+of functionality, it is located in a separate help file: |mnv9class.txt|.
+
+
+==============================================================================
+
+8. Rationale *mnv9-rationale*
+
+The :def command ~
+
+Plugin writers have asked for much faster MNV script. Investigations have
+shown that keeping the existing semantics of function 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 legacy style code from MNV9 style
+code.
+
+Using "def" to define a function comes from Python. Other languages use
+"function" which clashes with legacy MNV script.
+
+
+Type checking ~
+
+When compiling lines of MNV 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 runtime 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, since adding two numbers
+almost never fails.
+
+ NOTE: As a tangential point, the exception is integer overflow, where the
+ result exceeds the maximum integer value. For example, adding to a 64-bit
+ signed integer where the result is greater than 2^63: >mnv9
+
+ mnv9script
+ echo 9223372036854775807 + 1 # -9223372036854775808
+ echo 2->pow(63)->float2nr() + 1 # -9223372036854775808
+<
+The syntax for types, using <type> for compound types, is similar to Java.
+It is easy to understand and widely used. The type names are what were used
+in MNV before, with some additions such as "void" and "bool".
+
+
+Removing clutter and weirdness ~
+
+Once decided that `:def` functions have different syntax than legacy functions,
+we are free to add improvements to make the code more familiar for users who
+know popular programming languages. In other words: remove weird things that
+only MNV does.
+
+We can also remove clutter, mainly things that were done to make MNV script
+backwards compatible with the good old Vi commands.
+
+Examples:
+- Drop `:call` for calling a function and `:eval` for evaluating an
+ expression.
+- Drop using a leading backslash for line continuation, automatically figure
+ out where an expression ends.
+
+However, this does require that some things need to change:
+- Comments start with # instead of ", to avoid confusing them with strings.
+ This is good anyway, it is also used by several popular languages.
+- Ex command ranges need to be prefixed with a colon, to avoid confusion with
+ expressions (single quote can be a string or a mark, "/" can be divide or a
+ search command, etc.).
+
+Goal is to limit the differences. A good criteria is that when the old syntax
+is accidentally used you are very likely to get an error message.
+
+
+Syntax and semantics from popular languages ~
+
+Script writers have complained that the MNV script syntax is unexpectedly
+different from what they are used to. To reduce this complaint popular
+languages are used as an example. At the same time, we do not want to abandon
+the well-known parts of legacy MNV script.
+
+For many things TypeScript is followed. It's a recent language that is
+gaining popularity and has similarities with MNV script. It also has a
+mix of static typing (a variable always has a known value type) and dynamic
+typing (a variable can have different types, this changes at runtime). Since
+legacy MNV script is dynamically typed and a lot of existing functionality
+(esp. builtin functions) depends on that, while static typing allows for much
+faster execution, we need to have this mix in MNV9 script.
+
+There is no intention to completely match TypeScript syntax and semantics. We
+just want to take those parts that we can use for MNV and we expect MNV users
+will be happy with. TypeScript is a complex language with its own history,
+advantages and disadvantages. To get an idea of the disadvantages read the
+book: "JavaScript: The Good Parts". Or find the article "TypeScript: the good
+parts" and read the "Things to avoid" section.
+
+People familiar with other languages (Java, Python, etc.) will also find
+things in TypeScript that they do not like or do not understand. We'll try to
+avoid those things.
+
+Specific items from TypeScript we avoid:
+- Overloading "+", using it both for addition and string concatenation. This
+ goes against legacy MNV script and often leads to mistakes. For that reason
+ we will keep using ".." for string concatenation. Lua also uses ".." this
+ way. And it allows for conversion to string for more values.
+- TypeScript can use an expression like "99 || 'yes'" in a condition, but
+ cannot assign the value to a boolean. That is inconsistent and can be
+ annoying. MNV recognizes an expression with && or || and allows using the
+ result as a bool. The |falsy-operator| was added for the mechanism to use a
+ default value.
+- TypeScript considers an empty string as Falsy, but an empty list or dict as
+ Truthy. That is inconsistent. In MNV an empty list and dict are also
+ Falsy.
+- TypeScript has various "Readonly" types, which have limited usefulness,
+ since a type cast can remove the immutable nature. MNV locks the value,
+ which is more flexible, but is only checked at runtime.
+- TypeScript has a complicated "import" statement that does not match how the
+ MNV import mechanism works. A much simpler mechanism is used instead, which
+ matches that the imported script is only sourced once.
+
+
+Declarations ~
+
+Legacy MNV script uses `:let` for every assignment, while in MNV9 declarations
+are used. That is different, thus it's good to use a different command:
+`:var`. This is used in many languages. The semantics might be slightly
+different, but it's easily recognized as a declaration.
+
+Using `:const` for constants is common, but the semantics varies. Some
+languages only make the variable immutable, others also make the value
+immutable. Since "final" is well known from Java for only making the variable
+immutable we decided to use that. And then `:const` can be used for making
+both immutable. This was also used in legacy MNV script and the meaning is
+almost the same.
+
+What we end up with is very similar to Dart: >
+ :var name # mutable variable and value
+ :final name # immutable variable, mutable value
+ :const name # immutable variable and value
+
+Since legacy and MNV9 script will be mixed and global variables will be
+shared, optional type checking is desirable. Also, type inference will avoid
+the need for specifying the type in many cases. The TypeScript syntax fits
+best for adding types to declarations: >
+ var name: string # string type is specified
+ ...
+ name = 'John'
+ const greeting = 'hello' # string type is inferred
+
+This is how we put types in a declaration: >
+ var mylist: list<string>
+ final mylist: list<string> = ['foo']
+ def Func(arg1: number, arg2: string): bool
+
+Two alternatives were considered:
+ 1. Put the type before the name, like Dart: >
+ var list<string> mylist
+ final list<string> mylist = ['foo']
+ def Func(number arg1, string arg2) bool
+< 2. Put the type after the variable name, but do not use a colon, like Go: >
+ var mylist list<string>
+ final mylist list<string> = ['foo']
+ def Func(arg1 number, arg2 string) bool
+
+The first is more familiar for anyone used to C or Java. The second one
+doesn't really have an advantage over the first, so let's discard the second.
+
+Since we use type inference the type can be left out when it can be inferred
+from the value. This means that after `var` we don't know if a type or a name
+follows. That makes parsing harder, not only for MNV but also for humans.
+Also, it will not be allowed to use a variable name that could be a type name,
+using `var string string` is too confusing.
+
+The chosen syntax, using a colon to separate the name from the type, adds
+punctuation, but it actually makes it easier to recognize the parts of a
+declaration.
+
+
+Expressions ~
+
+Expression evaluation was already close to what other languages are doing.
+Some details are unexpected and can be improved. For example a boolean
+condition would accept a string, convert it to a number and check if the
+number is non-zero. This is unexpected and often leads to mistakes, since
+text not starting with a number would be converted to zero, which is
+considered false. Thus using a string for a condition would often not give an
+error and be considered false. That is confusing.
+
+In MNV9 type checking is stricter to avoid mistakes. Where a condition is
+used, e.g. with the `:if` command and the `||` operator, only boolean-like
+values are accepted:
+ true: `true`, `v:true`, `1`, `0 < 9`
+ false: `false`, `v:false`, `0`, `0 > 9`
+Note that the number zero is false and the number one is true. This is more
+permissive than most other languages. It was done because many builtin
+functions return these values, and changing that causes more problems than it
+solves. After using this for a while it turned out to work well.
+
+If you have any type of value and want to use it as a boolean, use the `!!`
+operator (see |expr-!|): >mnv9
+
+ mnv9script
+ # The following are all true:
+ echo [!!'text', !![1], !!{'x': 1}, !!1, !!1.1]
+ # And these are all false:
+ echo [!!'', !![], !!{}, !!0, !!0.0]
+<
+From a language like JavaScript we have this handy construct: >
+ GetName() || 'unknown'
+However, this conflicts with only allowing a boolean for a condition.
+Therefore the "??" operator was added: >
+ GetName() ?? 'unknown'
+Here you can explicitly express your intention to use the value as-is and not
+result in a boolean. This is called the |falsy-operator|.
+
+
+Import and Export ~
+
+A problem of legacy MNV 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. This defies the concept of a package that only
+exports selected items and keeps the rest local.
+
+In MNV9 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,
+ some of these are exported.
+- When importing a script the symbols that are imported are explicitly listed,
+ avoiding name conflicts and failures if functionality is added later.
+- The mechanism allows for writing a big, long script with a very clear API:
+ the exported functions, variables and classes.
+- 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, its items are cached and loading it again is
+ not needed.
+- The MNV-specific use of "s:" to make things script-local can be dropped.
+
+When sourcing a MNV9 script (from either a MNV9 script or legacy MNV script),
+only the items defined globally can be used, not the exported items.
+Alternatives considered:
+- All the exported items become available as script-local items. This makes
+ it uncontrollable what items get defined and likely soon leads to trouble.
+- Use the exported items and make them global. Disadvantage is that it's then
+ not possible to avoid name clashes in the global namespace.
+- Completely disallow sourcing a MNV9 script, require using `:import`. That
+ makes it difficult to use scripts for testing, or sourcing them from the
+ command line to try them out.
+Note that you CAN also use `:import` in legacy MNV script, see above.
+
+
+Compiling functions early ~
+
+Functions are compiled when called or when `:defcompile` is used. Why not
+compile them early, so that syntax and type errors are reported early?
+
+The functions can't be compiled right away when encountered, because there may
+be forward references to functions defined later. Consider defining functions
+A, B and C, where A calls B, B calls C, and C calls A again. It's impossible
+to reorder the functions to avoid forward references.
+
+An alternative would be to first scan through the file to locate items and
+figure out their type, so that forward references are found, and only then
+execute the script and compile the functions. This means the script has to be
+parsed twice, which is slower, and some conditions at the script level, such
+as checking if a feature is supported, are hard to use. An attempt was made
+to see if it works, but it turned out to be impossible to make work well.
+
+It would be possible to compile all the functions at the end of the script.
+The drawback is that if a function never gets called, the overhead of
+compiling it counts anyway. Since startup speed is very important, in most
+cases it's better to do it later and accept that syntax and type errors are
+only reported then. In case these errors should be found early, e.g. when
+testing, a `:defcompile` command at the end of the script will help out.
+
+
+Why not use an existing embedded language? ~
+
+MNV supports interfaces to Perl, Python, Lua, Tcl and a few others. But
+these interfaces have never become widely used, for various reasons. When
+MNV9 was designed a decision was made to make these interfaces lower priority
+and concentrate on MNV script.
+
+Still, plugin writers may find other languages more familiar, want to use
+existing libraries or see a performance benefit. We encourage plugin authors
+to write code in any language and run it as an external process, using jobs
+and channels. We can try to make this easier somehow.
+
+Using an external tool also has disadvantages. An alternative is to convert
+the tool into MNV 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 MNV9 script now includes support for
+classes, objects, interfaces, and enums, that is increasingly feasible.
+
+
+
+ mnv:tw=78:ts=8:noet:ft=help:norl: