Nasal scripting language
- Please note that a considerable amount of resources has not yet been incorporated here, you can check these out by going to the discussion page, where we are collecting links to webpages and mailing list discussions/postings related to Nasal.
FlightGear offers a very powerful functional scripting language called Nasal, which supports reading and writing of internal FlightGear properties, accessing internal data via extension functions, creating GUI dialogs and much more.
Work in progress This article or section will be worked on in the upcoming hours or days. See history for the latest developments. |
The FlightGear forum has a subforum related to: Nasal Scripting |
Nasal scripting |
---|
Nasal internals |
---|
Memory Management (GC) |
Hello world
A simple hello world example in Nasal would be:
# hello.nas
print('Hello World!');
This will show the "Hello World" string during startup in the console window. The hash sign (#) just introduces comments (i.e. will be ignored by the interpreter).
Note: Script-specific symbols such as global variables (or functions) will be put into a scope (namespace) based on the script's name, scripts embedded via aircraft-set.xml files can separately specify a corresponding module name (see Howto: Make an aircraft for details).
Strings in Nasal can also use double quotes which support escaping:
# hello.nas
print("Hello\nWorld!");
Double quotes support typical escape sequences:
- \n Newline
- \t Horizontal Tab
- \v Vertical Tab
- \b Backspace
- \r Carriage Return
- \f Form feed
- \a Audible Alert (bell)
- \\ Backslash
- \? Question mark
- \' Single quote
- \" Double quote
For example, to print a new line, use:
print ("\n");
To print a quoted string, use:
print ("\"quoted string\"");
and so on.
Single quotes treat everything as literal except for embedded single quotes (including embedded whitespace like newlines).
Nasal strings are always arrays of bytes (never characters: see the utf8 library if you want character-based equivalents of substr() et. al.). They can be indexed just like in C (although note that there is no nul termination -- get the length with size()):
Editing code files
Note that there is currently no way to tell FlightGear to reload Nasal scripts from the global Nasal directory at runtime, so in order to see changes take effect, you will have to exit and restart FlightGear for the time being. Note that there are some workarounds available, see: reloading Nasal code without re-starting FlightGear.
Also, note that as of 05/2009, Nasal in FlightGear does not yet support any form of dependency resolution. In other words, there's no "import", "require" or "include" directive - this is also why most code in FlightGear is wrapped inside a _setlistener() call instead, which in turn waits for a FlightGear signal before executing the code (see below for details).
The FlightGear forum has a subforum related to: Nasal Scripting |
Nasal scripting |
---|
Nasal internals |
---|
Memory Management (GC) |
Storage: property tree vs. Nasal
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.
As of FG 2.4.0, retrieving a value from the property tree via getprop is about 50% slower than accessing a native Nasal variable, and accessing the value via node.getValue() is 10-20% slower yet. This is an insignificant amount of time if you are retrieving and storing a few individual values from the property tree, but adds up fast if you are storing or retrieving hashes or large amounts of data. (You can easily benchmark times on your own code using systime() or debug.benchmark.)
In addition, it is worth noting that the Nasal/FlightGear APIs cannot currently be considered to be thread safe, this mean that -at least for now- the explicit use of pure Nasal space variables is the only way to exploit possible parallelism in your code by making use of threads.
Using Hashs to map keys to functions
You can easily reduce the complexity of huge conditional (IF) statements, such as this one:
if (a==1) function_a();
else
if (a==2) function_b();
else
if (a==3) function_c();
else
if (a==4) function_d();
else
if (a==5) function_e();
.. just by using the variable as a key (index) into a hash, so that you can directly call the corresponding function:
var mapping = {1:function_a, 2:function_b, 3:function_c, 4:function_d,5:function_e};
mapping[a] ();
This initializes first a hash map of values and maps a function "pointer" to each value, so that accessing mapping[x] will return the function pointer for the key "x".
Next, you can actually call the function by appending a list of function arguments (empty parentheses for no args) to the hash lookup.
Using this technique, you can reduce the complexity of huge conditional blocks. For example, consider:
# weather_tile_management.nas 460 if (code == "altocumulus_sky"){weather_tiles.set_altocumulus_tile();} 461 else if (code == "broken_layers") {weather_tiles.set_broken_layers_tile();} 462 else if (code == "stratus") {weather_tiles.set_overcast_stratus_tile();} 463 else if (code == "cumulus_sky") {weather_tiles.set_fair_weather_tile();} 464 else if (code == "gliders_sky") {weather_tiles.set_gliders_sky_tile();} 465 else if (code == "blue_thermals") {weather_tiles.set_blue_thermals_tile();} 466 else if (code == "summer_rain") {weather_tiles.set_summer_rain_tile();} 467 else if (code == "high_pressure_core") {weather_tiles.set_high_pressure_core_tile();} 468 else if (code == "high_pressure") {weather_tiles.set_high_pressure_tile();} 469 else if (code == "high_pressure_border") {weather_tiles.set_high_pressure_border_tile();} 470 else if (code == "low_pressure_border") {weather_tiles.set_low_pressure_border_tile();} 471 else if (code == "low_pressure") {weather_tiles.set_low_pressure_tile();} 472 else if (code == "low_pressure_core") {weather_tiles.set_low_pressure_core_tile();} 473 else if (code == "cold_sector") {weather_tiles.set_cold_sector_tile();} 474 else if (code == "warm_sector") {weather_tiles.set_warm_sector_tile();} 475 else if (code == "tropical_weather") {weather_tiles.set_tropical_weather_tile();} 476 else if (code == "test") {weather_tiles.set_4_8_stratus_tile();} 477 else ...
While this is not a very complex or huge block of code, it is an excellent example for very good naming conventions used already, because the consistency of naming variables and functions can pay off easily here, with just some very small changes, you can already reduce the whole thing to a hash lookup like this:
weather_tiles["set_"~code~"_tile"](); # naming convention
This would dynamically concatenate a key consisting of "set_" + code + "_title" into the hash named weather_tiles, and then call the function that is returned from the hash lookup.
So for this to work you only need to enforce consistency when naming your functions (i.e. this would of course CURRENTLY fail when the variable code contains "test" because there is no such hash member (it's "4_8_stratus" instead).
The same applies to cumulus sky (fair weather), stratus/overcast stratus.
But these are very simple changes to do (just renaming these functions to match the existing conventions). When you do that, you can easily replace such huge IF statements and replace them with a single hash lookup and function call:
hash[key] (arguments...);
For example, consider:
var makeFuncString = func(c) return tolower("set_"~c~"_tile");
var isFunc = func(f) typeof(f)=='func';
var hasMethod = func(h,m) contains(h,m) and isFunc;
var callIfAvailable = func(hash, method, unavailable=func{} ) {
var c=hasMethod(hash,makeFuncString(m) ) or unavailable();
hash[makeFuncString(m)] ();
}
callIfAvailable( weather_tiles,code, func {die("key not found in hash or not a func");} );
Initializing data structures
There are some more possibilities to increase the density of your code, such as by removing redundant code or by generalizing and refactoring existing code so that it can be reused in different places (i.e. avoiding duplicate code):
For example see weather_tile_management.nas #1000 (create_neighbours function):
1008 x = -40000.0; y = 40000.0; 1009 setprop(lw~"tiles/tile[0]/latitude-deg",blat + get_lat(x,y,phi)); 1010 setprop(lw~"tiles/tile[0]/longitude-deg",blon + get_lon(x,y,phi)); 1011 setprop(lw~"tiles/tile[0]/generated-flag",0); 1012 setprop(lw~"tiles/tile[0]/tile-index",-1); 1013 setprop(lw~"tiles/tile[0]/code",""); 1014 setprop(lw~"tiles/tile[0]/timestamp-sec",weather_dynamics.time_lw); 1015 setprop(lw~"tiles/tile[0]/orientation-deg",alpha); 1016 1017 x = 0.0; y = 40000.0; 1018 setprop(lw~"tiles/tile[1]/latitude-deg",blat + get_lat(x,y,phi)); 1019 setprop(lw~"tiles/tile[1]/longitude-deg",blon + get_lon(x,y,phi)); 1020 setprop(lw~"tiles/tile[1]/generated-flag",0); 1021 setprop(lw~"tiles/tile[1]/tile-index",-1); 1022 setprop(lw~"tiles/tile[1]/code",""); 1023 setprop(lw~"tiles/tile[1]/timestamp-sec",weather_dynamics.time_lw); 1024 setprop(lw~"tiles/tile[1]/orientation-deg",alpha); 1025 1026 x = 40000.0; y = 40000.0; 1027 setprop(lw~"tiles/tile[2]/latitude-deg",blat + get_lat(x,y,phi)); 1028 setprop(lw~"tiles/tile[2]/longitude-deg",blon + get_lon(x,y,phi)); 1029 setprop(lw~"tiles/tile[2]/generated-flag",0); 1030 setprop(lw~"tiles/tile[2]/tile-index",-1); 1031 setprop(lw~"tiles/tile[2]/code",""); 1032 setprop(lw~"tiles/tile[2]/timestamp-sec",weather_dynamics.time_lw); 1033 setprop(lw~"tiles/tile[2]/orientation-deg",alpha); 1034 1035 x = -40000.0; y = 0.0; 1036 setprop(lw~"tiles/tile[3]/latitude-deg",blat + get_lat(x,y,phi)); 1037 setprop(lw~"tiles/tile[3]/longitude-deg",blon + get_lon(x,y,phi)); 1038 setprop(lw~"tiles/tile[3]/generated-flag",0); 1039 setprop(lw~"tiles/tile[3]/tile-index",-1); 1040 setprop(lw~"tiles/tile[3]/code",""); 1041 setprop(lw~"tiles/tile[3]/timestamp-sec",weather_dynamics.time_lw); 1042 setprop(lw~"tiles/tile[3]/orientation-deg",alpha); 1043 1044 # this is the current tile 1045 x = 0.0; y = 0.0; 1046 setprop(lw~"tiles/tile[4]/latitude-deg",blat + get_lat(x,y,phi)); 1047 setprop(lw~"tiles/tile[4]/longitude-deg",blon + get_lon(x,y,phi)); 1048 setprop(lw~"tiles/tile[4]/generated-flag",1); 1049 setprop(lw~"tiles/tile[4]/tile-index",1); 1050 setprop(lw~"tiles/tile[4]/code",""); 1051 setprop(lw~"tiles/tile[4]/timestamp-sec",weather_dynamics.time_lw); 1052 setprop(lw~"tiles/tile[4]/orientation-deg",getprop(lw~"tmp/tile-orientation-deg")); 1053 1054 1055 x = 40000.0; y = 0.0; 1056 setprop(lw~"tiles/tile[5]/latitude-deg",blat + get_lat(x,y,phi)); 1057 setprop(lw~"tiles/tile[5]/longitude-deg",blon + get_lon(x,y,phi)); 1058 setprop(lw~"tiles/tile[5]/generated-flag",0); 1059 setprop(lw~"tiles/tile[5]/tile-index",-1); 1060 setprop(lw~"tiles/tile[5]/code",""); 1061 setprop(lw~"tiles/tile[5]/timestamp-sec",weather_dynamics.time_lw); 1062 setprop(lw~"tiles/tile[5]/orientation-deg",alpha); 1063 1064 x = -40000.0; y = -40000.0; 1065 setprop(lw~"tiles/tile[6]/latitude-deg",blat + get_lat(x,y,phi)); 1066 setprop(lw~"tiles/tile[6]/longitude-deg",blon + get_lon(x,y,phi)); 1067 setprop(lw~"tiles/tile[6]/generated-flag",0); 1068 setprop(lw~"tiles/tile[6]/tile-index",-1); 1069 setprop(lw~"tiles/tile[6]/code",""); 1070 setprop(lw~"tiles/tile[6]/timestamp-sec",weather_dynamics.time_lw); 1071 setprop(lw~"tiles/tile[6]/orientation-deg",alpha); 1072 1073 x = 0.0; y = -40000.0; 1074 setprop(lw~"tiles/tile[7]/latitude-deg",blat + get_lat(x,y,phi)); 1075 setprop(lw~"tiles/tile[7]/longitude-deg",blon + get_lon(x,y,phi)); 1076 setprop(lw~"tiles/tile[7]/generated-flag",0); 1077 setprop(lw~"tiles/tile[7]/tile-index",-1); 1078 setprop(lw~"tiles/tile[7]/code",""); 1079 setprop(lw~"tiles/tile[7]/timestamp-sec",weather_dynamics.time_lw); 1080 setprop(lw~"tiles/tile[7]/orientation-deg",alpha); 1081 1082 x = 40000.0; y = -40000.0; 1083 setprop(lw~"tiles/tile[8]/latitude-deg",blat + get_lat(x,y,phi)); 1084 setprop(lw~"tiles/tile[8]/longitude-deg",blon + get_lon(x,y,phi)); 1085 setprop(lw~"tiles/tile[8]/generated-flag",0); 1086 setprop(lw~"tiles/tile[8]/tile-index",-1); 1087 setprop(lw~"tiles/tile[8]/code",""); 1088 setprop(lw~"tiles/tile[8]/timestamp-sec",weather_dynamics.time_lw); 1089 setprop(lw~"tiles/tile[8]/orientation-deg",alpha); 1090 }
At first glance, this seems like a fairly repetitive and redundant block of code, so it could probably be simplified easily:
var create_neighbours = func (blat, blon, alpha) { var phi = alpha * math.pi/180.0; calc_geo(blat); var index=0; var pos = [ [-40000.0,40000.0], [0.0, 40.000], [40000.0, 40000.0], [-40000, 0], [0,0], [40000,0], [-40000,-40000], [0,-40000], [40000,-40000] ]; foreach (var p;pos) { x=p[0]; y=p[1]; setprop(lw~"tiles/tile["~index~"]/latitude-deg",blat + get_lat(x,y,phi)); setprop(lw~"tiles/tile["~index~"]/longitude-deg",blon + get_lon(x,y,phi)); setprop(lw~"tiles/tile["~index~"]/generated-flag",0); setprop(lw~"tiles/tile["~index~"]/tile-index",-1); setprop(lw~"tiles/tile["~index~"]/code",""); setprop(lw~"tiles/tile["~index~"]/timestamp-sec",weather_dynamics.time_lw); setprop(lw~"tiles/tile["~index~"]/orientation-deg",alpha); index=index+1; } }
Built-in functions
sort(vector, function)
Creates a new vector containing the elements in the input vector sorted in ascending order according to the rule given by function, which takes two arguments (elements of the input vector) and should return less than zero, zero, or greater than zero if the first argument is, respectively, less than, equal to, or greater than the second argument. Despite being implemented with ANSI C qsort(), the sort is stable; "equal" elements in the output vector will appear in the same relative order as they do in the input.
Because you can define the sort function, sort allows you to create a list of keys sorting a hash by any criterion--by key, value, or (if, for instance the hash values are hashes themselves) any subvalue.
vec = [100,24,45]; sortvec = sort (vec, func (a,b) cmp (a,b)); debug.dump (sortvec); #output is [24,45,100]
Here is an example of how to output the contents of a hash in sorted order. Note that the function does not actually sort the hash but returns a list of the hash keys in sorted order.
var airport = { "LOXZ": "Zeltweg", "LOWI": "Innsbruck", "LOXL": "Linz Hoersching", # the last comma is optional }; var sortedkeys= sort (keys(airport), func (a,b) cmp (airport[a], airport[b])); foreach (var i; sortedkeys) print (i, ": ", airport[i]);
The output is:
LOWI: Innsbruck LOXL: Linz Hoersching LOXZ: Zeltweg
If the hash values are themselves hashes, sorting by any of the subvalues is possible. For example:
var airport = { "LOXZ": {city: "Zeltweg", altitude_m: 1300 }, "LOWI": {city: "Innsbruck", altitude_m: 2312 }, "LOXL": {city: "Linz Hoersching", altitude_m: 1932 }, }; #return a list of the hash keys sorted by altitude_m var sortedkeys= sort (keys(airport), func (a,b) airport[a].altitude_m - airport[b].altitude_m); foreach (var i; sortedkeys) print (i, ": ", airport[i].city, ", ", airport[i].altitude_m);
Note that sort will return errors, and in FG 2.4.0 may even stop working, if the sort function you provide returns errors. A common cause of this is if your sort vector contains both string and numeric values. The cmp function will return an error for numeric values, and arithmetic operations you may use to sort numeric values will return errors if performed on a string. The error in these cases is typically "function/method call on uncallable object".
Other useful built-in functions
Other basic built-in Nasal functions such as append, setsize, subvec, typeof, contains, delete, int, num, keys, pop, size, streq, cmp, substr, sprintf, find, split, rand, call, die, bind, math.sin, math.pi, math.exp, math.ln math.e, io.read, io.write, regex.exec, and others of that sort, are detailed in this external document.
Useful functions in the Nasal directory
Other functions are available in the Nasal files found in the Nasal directory of a FlightGear install. Simply open those Nasal files in text editor to see what is inside. Reference those functions by putting the filename in front of the function, method, variable, or object you wish to use. For instance, to use the method Coord.new() in the file geo.nas, you simply write:
geo.Coord.new()
Distance calculations
To calculate the distance between two points (in two different ways):
# mylat1, mylong1, mylat2, mylong2 are lat & long in degrees # myalt1 & myalt2 are altitude in meters var GeoCoord1 = geo.Coord.new(); GeoCoord1.set_latlon(mylat1, mylong1,myalt1); var GeoCoord2 = geo.Coord.new(); GeoCoord2.set_latlon(mylat2, mylong2, myalt2); var directDistance = GeoCoord1.direct_distance_to(GeoCoord2); var surfaceDistance = GeoCoord1.distance_to(GeoCoord2);
The results are distances in meters.
- distance_to - returns distance in meters along Earth curvature, ignoring altitudes; useful for map distance
- direct_distance_to - returns distance in meters direct; considers altitude, but cuts through Earth surface
Other useful geographical functions
Other useful geographical functions are found in geo.nas (in the $FG_ROOT/Nasal directory of a FlightGear installation). geo.nas also includes documentation/explanation of the functions available.
Related content
The FlightGear forum has a subforum related to: Nasal |
External links
References
|