NVSEScript

From GECK
Jump to: navigation, search

THIS PAGE IS WIP - The new script compiler has not been released yet.

Introduction

As of xNVSE 7.0 -- a new script compiler has been introduced. This new compiler fixes many shortcomings of the old script compiler, and aims to be easier to understand, write, and maintain for the xNVSE team.

It is still recommended to understand the vanilla scripting language first (obscript), which you can read about here, as many of the same concepts will apply to this new language, just with a more familiar syntax on top.

It is worth noting that while this is a new language / compiler - scripts written in this new language are fully compatible with those written in obscript. An nvsescript script can reference variables / call udfs defined in obscript, and vice-versa.

Benefits over obscript

No more 'eval'

Every expression is now an NVSE Expression, so eval begone!

Removed lambda limit

In obscript, due to parser limitations, lambda functions were limited to a maximum of 512 characters. This limitation has been removed in the new compiler and lambdas can be thousands of characters long (if you so wish).

It is recommended to break a long lambda out into a UDF if it is getting complex as this will help with readability / maintainability of your scripts.

Clear / consistent syntax

With the removal of let/eval, and the introduction of () for calls and semicolons at the end of statements, the syntax can be a lot less ambiguous and easier to read at a glance.

You can get a better understanding below in the syntax section.

Better error messages

Another nice thing to come out of a more modern compiler, error messages. The new compiler should be able to report (mostly) accurate error messages for any mistakes you might have made, and should include line numbers and other helpful text where possible.

This is also a big one for scriptrunner users, as the compiler should function identically during runtime and still produce good error messages.

Easier maintainability

This one doesn't apply as much to users, but on the development side, this language is infinitely easier to maintain and extend going forward, as it reuses none of the vanilla code and handles everything in one context, unlike the vanilla + nvse compilation process.

Syntax

Semicolons

To make the language more clear, as well as consistent with other programming languages (as well as easier to parse), semicolons are now required at the end of most statements / expressions.

int i = 10;
int j = 20;

print($(i * j));
name MyScript;

Beginning a script

NVSEScript scripts begin with a 'name' statement, similar to 'scn' in obscript. This is because we needed some way to tell the geck/game that we are compiling a script with the new compiler instead of the old.

name MyScript;

Comments

Similar to other syntax features, comments in NVSEScript follow the traditional commenting style used in C-based programming languages.

Single line comments

Initiated with //

This line is not commented out
// This line is commented out
This line is not commented out

Multi line comments

Initiated with /*, need to be closed with */ These can NOT be nested.

/*
All
of this is commented
out
*/

Function calls

Function calls, like most other things, now have a more traditional syntax, where you put parenthesis after the command name with the arguments inside the parenthesis, separated by commas.

This new syntax removes a lot of ambiguity in the parsing phase, as there were many bugs induced by the old style of command parsing (and these bugs were relied on for many different scripts, so it could not easily be fixed.)

Example

Before:

print GetName player
player.AddItem Caps001 10

After:

print(GetName(player));
player.AddItem(Caps001, 10);

Event blocks

See: Begin

Event blocks are declared with the following syntax:

GameMode {
    // Code here
}

These blocks can only be defined in the root level scope of the script (cannot be nested in other block types or function blocks)

Conditional event blocks

Some event blocks, such as OnEquip, take an optional argument.

OnEquip takes an actor as a filter, where the code inside of the block will only run if OnEquip is fired for that specified actor.

Arguments for such blocks can be specified like this:

OnEquip::Player {
    // Code here
}
MenuMode::1014 {
    // Code here
}

Variables

Variable declarations in NVSEScript are very similar to obscript, with the exception that NVSEScript allows you to declare/define multiple different variables of the same type in one line.

int x = 10;

ref myRef = player;

double x,y,z;
player.GetPosEx(x,y,z);

double i, j = 10, k;

Variable types

  • Array- Equivalent to array_var in obscript
  • String - Equivalent to string_var in obscript
  • Ref
  • Float
  • Double - An alias for float
  • Int

For loops

Standard for loops use the following basic structure.

for (<initializer>; <condition>; <increment>) {
    // Code
}
  • The <initializer> block will run once, before the first loop iteration, and can be used to set up something like a counter / index.
  • The <condition> block will run at the end of each loop iteration, after executing the <increment> block, and must evaluate to a boolean value. If it evaluates to true, the loop will continue, and the loop body will be executed again. If it evaluates to false, the loop will terminate and code execution will continue after the loops closing brace.

Example of loop that prints values from 0 to 9:

for (int i = 0; i < 10; i++) {
    print($i);
}

Note: the value 10 is not printed here, because the condition expression is 'i < 10', this means that when 'i' gets incremented to 10 after the previous loop iteration, it will fail the condition expression check, and the loop will terminate. If 10 is desired to be printed the condition expression can be changed to 'i <= 10'.

For-each loops

For-each loops can be used to iterate over Arrays

Basic syntax:

for (array aIter in <array>) {
    print($*aIter);
}

Where 'aIter' here holds the current element, as an array. This is because the source array may be either a map or an array, and the values can be of any type.

If your array only holds one type of value, you can utilize the new destructure syntax to avoid the dereference (*) operator for accessing the value.

array myArr = [1,2,3];
for ([int elem] in myArr) {
    print($elem);
}

Note the square brackets around 'int elem' in the loop initializer, this is required to tell NVSE that you want to try and automatically extract the value into 'elem'.

This destructure syntax also works for map-arrays, though it gets a bit tricky.

// If only a single variable is provided to the for loop, it will reference the 'key' of the current element
for ([string key] in myMap) {

}

// You can provide variables for both the key and value, and as long as the types are valid they NVSE will extract them both for you. 
for ([string key, int value] in myMap) {

}

// Finally, if you want to just iterate over the values in a map and do not care about the key, you can use a '_' in place of the variable declaration to signify that you don't care about it.
// This way is more efficient, as long as you do not need the key, as it will not create a useless variable inside your script that the game will need to keep track of.
for ([_, int value] in myMap) {

}

While loops

While loops use the following syntax:

while (<condition>) {
    // Code
}

Before each iteration, NVSE will evaluate the <condition> expression (which must evaluate to a boolean value), and execute the loop body if the condition is true. This will repeat while condition returns true.

Example of loop that prints values from 0 to 9:

int i = 0;
while (i < 10) {
    print($i);
    i++;
}

Array/Map literals

As an alternative to Ar_Construct, Ar_List, and Ar_Map, NVSEScript supports array / map literals.

Array literal:

Array literals use the following basic structure:

[value1, value2, ...]

Example:

array myArray = [1,2,3];

Note the use of square brackets, as these denote an array instead of a map.

Map Literal:

Map literals use the following basic structure:

{pair1, pair2, ...}

Example:

array myMap = {1::"Hello", 2::"World};

Map literals use curly braces, and must contain pairs. The compiler will also try to validate that all of the keys have the same type.

Lambdas

See: Lambda

User Defined Functions (UDFs)

See: User Defined Function

See Also