Nasal Variables
| The FlightGear forum has a subforum related to: Nasal Scripting |
| Nasal scripting |
|---|
| Nasal internals |
|---|
| Memory Management (GC) |
A variable is a combination of a name/namespace (e.g. foo in the local namespace or setprop in the global namespace) and a value (from simple scalars, like 1 or "hello!", to more complex data types, like hashes and vectors). Here we will explain some of how the various types of variables work.
Introduction
Nasal scripts should make use of the var keyword when declaring variables. The var keyword makes a variable guaranteed to be local. Nasal natively provides support for scalars (numbers, strings), lists (arrays, vectors) and hashes (objects or dictionaries); more complex data structures (such as trees) can be built using vectors or hashes.
var w = 100; # w is a local numerical variable
var x = "hello"; # x is a local string variable
var y = []; # y is a local vector (array)
var z = {}; # z is a local hash (dictionary or table) - also used for OOPNasal supports a nil value for use as a null pointer equivalent:
var foo = nil;Also, note that Nasal symbols are case-sensitive, these are all different variables:
var show = func(what) { print(what, "\n") };
var abc = 1; # these are all different symbols
var ABC = 2; # different from abc
var aBc = 3; # different from abc and ABC
show(abc);
show(ABC);
show(aBc);Please note that functions assigned to variables are no exception. If you write code without using var on variables, then you risk (often hard to debug) breakage at a later time because you may be overwriting symbols in another namespace.
Functions bound to variables should use the var keyword as well:
var hello = func {
print("hello\n");
};Advanced uses of variables
Nasal also supports Multi-assignment expressions. You can assign more than one variable (or lvalue) at a time by putting them in a parenthesized list:
(var a, var b) = (1, 2);
var (a, b) = (1, 2); # Shorthand for (var a, var b)
(var a, v[0], obj.field) = (1, 2, 3); # Any assignable lvalue works
var color = [1, 1, 0.5];
var (r, g, b) = color; # works with runtime vectors too
# var a = 1, b = 2; # this is invalidVectors and hashes
vectors and hashes can be understood as containers for your data; in the case of vectors, you end up with a container that has sequentially-numbered places for each element - whereas a hash can also used named keys for its values. Storing happens in a key/value fashion. Data look-up is usually done via square brackets:
# create an empty vector
var myVector = [];
# append a value to it
append(myVector, 10);
# get out the first element (starting at 0)
var firstElement = myVector[0];Equally, a hash is easy to set up to serve as container for your values:
var myHash = {};
myHash.field = 10;
var myField = myHash.field;Vectors
Elements in a vector are sequentially numbered, in essence each element has a numeric index:
var my_vector = ['A', 'B', 'C'];This initializes a vector with three elements: A, B and C. To access each element of the vector, you would need to use the element's numerical index:
print(my_vector[0]); # prints A
print(my_vector[1]); # prints B
print(my_vector[2]); # prints C
print(size(my_vector)); # prints 3As can be seen, indexing starts at zero.
Vector slicing
Vectors (lists or arrays) can be created from others using an ordered list of indexes and ranges. This is usually called vector slicing. For example:
var v1 = ["a", "b", "c", "d", "e"];
var v2 = v1[3, 2]; # == ["d", "c"];
var v3 = v1[1:3]; # i.e. range from 1 to 3: ["b", "c", "d"];
var v4 = v1[1:]; # no value means "to the end": ["b", "c", "d", "e"]
var i = 2;
var v5 = v1[i]; # runtime expressions are fine: ["c"]
var v6 = v1[-2, -1]; # negative indexes are relative to end: ["d", "e"]The range values can be computed at runtime (e.g. i=1; v5=v1[i:]). Negative indices work the same way they do with the vector functions (-1 is the last element, -2 is 2nd to last, etc...).
Example: making a range function
var range = func(start, end, step = 1) { # one-past-end, actually
var result = []; # gets filled like [0, 1, 2, 3, ... ]
for (var i = start; step > 0 ? i < end: i > end; i += step) {
append(result, i);
}
return result;
};
debug.dump(range(0, 3)); # [0, 1, 2]
debug.dump(range(0, 4, 2)); # [0, 2]
debug.dump(range(3, 0, -1)); # [3, 2, 1]std.Vector
Since FlightGear 3.3, you can manipulate vectors using an object-oriented API by using std.Vector.
Hashes
Compared to vectors, hashes don't use square brackets but curly braces instead:
var my_hash = {};Hashes may not just have numerical indexes, but also symbolic indexes as lookup keys:
var my_hash = { first: 'A', second: 'B', third: 'C' };This will create a hash (imagine it like a storage container for a bunch of related variables) and initialize it with three values (A, B and C) which are assigned to three different lookup keys: first, second, third.
In other words, you can access each element in the hash by using its lookup key:
var my_hash = { first: 'A', second: 'B', third: 'C' };
print(my_hash.first); # will print A
print(my_hash.second); # will print B
print(my_hash.third); # will print CInsert new pairs (or change existing):
var hash = {};
hash.field1 = 1;
hash['field2'] = 2;
hash["field3"] = 3;Example: dump all keys
var dump_keys = func(hash) {
if (typeof(hash) != "hash") die("dump_keys(): Error! Argument is not a hash.");
foreach(var key; keys(hash)){
print(key, ": ", typeof(hash[key]));
}
}Nasal variables vs. the property tree
With FlightGear's built-in property tree and Nasal's support for it, there are two obvious, and two somewhat competing, ways for storing scalar data: native Nasal variables and FlightGear properties, both of which can be easily accessed and managed from Nasal.
The advantage to native Nasal-space data is that it's fast and simple. If the only thing that will care about the value is your script, they are good choices.
The property tree is an inter-subsystem communication thing. This is what you want if you want to share data with the C++ world (for example, YASim <control-output> tags write to properties – they don't understand Nasal), or read in via configuration files.
Also, native Nasal data structures are usually far faster than their equivalent in property tree space. This is because there are several layers of indirection in retrieving a property tree value.
In general, this means that you shouldn't make overly excessive use of the property tree for storing state that isn't otherwise relevant to FlightGear or any of its subsystems. Doing that would in fact have adverse effects on the performance of your code. In general, you should favor Nasal variables and data structures and should only make use of properties to interface with the rest of FlightGear, or to easily provide debugging information at run time.