diff options
Diffstat (limited to 'mnv/runtime/doc/mnv9.txt')
| -rw-r--r-- | mnv/runtime/doc/mnv9.txt | 3689 |
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: |
