Nasal library

From FlightGear wiki
Revision as of 11:13, 6 November 2015 by Red Leader (talk | contribs) (→‎Extension functions: Move Positioned Object Queries to Navdata cache)
Jump to navigation Jump to search


This page documents the global library functions of FlightGear's built-in scripting language, Nasal. This includes core library functions, which were included in Nasal before its integration into FlightGear, and extension functions, which have been subsequently added, and are specifically designed for FlightGear. The main relevant folders in Git are:

Tip  Copy & paste the examples into your Nasal Console and execute them to see what they do.

Core library functions

This is the list of the basic core library functions. Most of these functions were part of the original Nasal library (before its integration in to FlightGear), while some have been added or changed over time. See also:

append()

append(vector, element[, element[, ...]]);

Source

This function appends, or adds, the given element(s) to the end of the vector given in the first argument. Returns the vector operated on.

vector
The vector to which the arguments will be appended.
element
An element to be added to the vector.

Examples

var vector = [1, 2, 3]; # Initialize the vector
append(vector, 4); # Append the number 4 to the end of the vector
debug.dump(vector); # Print the contents of the vector
var vector = [1, 2, 3]; # Initialize the vector
append(vector, 4, 5, 6); # Append the numbers 4, 5, and 6 to the end of the vector
debug.dump(vector); # Print the contents of the vector

bind()

bind(function, locals[, outer_scope]);

Source

This creates a new function object. A function in Nasal is three things: the actual code, a hash/namespace of local variables available to the function namespace, and the closure object of that namespace. These correspond to the three arguments respectively.

function
Function to evaluate.
locals
Hash containing values that will become the namespace (first closure) for the function.
outer_scope
Optional function which is bound to the next closure. This can be bound to yet another, making a linked list.

Example

# This is a namespace/hash with a single member, named "key", which is initialized to 12 
var Namespace = {
    key: 12
};

# This is different namespace/hash containing a function
# dividing a variable "key" (which is unavailable/nil in this namespace) by 2
var AnotherNamespace = {
    ret: func {
        key /= 2;
    }
};

# To see that key is not available, try to call AnotherNamespace.ret() first
call(AnotherNamespace.ret, [], nil, nil, var errors = []);
if(size(errors)){
    print("Key could not be divided/resolved!");
    debug.printerror(errors);
}

# Associate the AnotherNamespace.ret() function with the first namespace
# so that "key" is now available
var function = bind(AnotherNamespace.ret, Namespace);

# Invoke the new function
function();

# Print out the value of Namespace.key
# It was changed to 6 from 12 by AnotherNamespace.ret()
print(Namespace.key);

call()

call(func[, args[, me[, locals[, error]]]);

Source

Calls the given function with the given arguments and returns the result. This function is very useful as it allows much more control over function calls and catches any errors or die calls that would normally trigger run-time errors cancelling execution of the script otherwise.

func
Function to execute.
args
Vector containing arguments to give to the called function.
me
me reference for the function call (i.e., for method calls). If given, this will override any me value existing in the namespace (locals argument).
locals
A hash with key/value pairs that will be available to the called function, typically used as the namespace for the function to be called.
error
A vector to append errors to. If the called function generates an error, the error, place, and line will be written to this. These errors can be printed using debug.printerror() .

Examples

# prints "Called from call()"
call(func {
    print("Called from call()");
});
# prints "a = 1 : b = 2
call(func(a, b){
        print("a = ", a, " : b = ", b);
    },
    [1, 2]
);
var Hash = {
    new: func {
        var m = { parents: [Hash] };

        m.el1 = "string1";
        m.el2 = "string2";

        return m;
    }
};

# prints "me.el1 = string1", then "me.el2 = string2" on the next line
call(func(a, b){        
        print("me.el", a, " = ", me["el" ~ a]);      
        print("me.el", b, " = ", me["el" ~ b]);
    },
    [1, 2],
    Hash.new()
);
# prints the value of math.pi
call(func {
        print(pi);
    }, nil, nil, 
    math
);
call(func {
        print(math.ip); # math.ip doesn't exist
    }, nil, nil, nil,
    var errs = []
);
debug.printerror(errs); # The error is caught and printed using debug.printerror()

caller()

caller([level]);

Source

Returns a vector containing a record from the current call stack. The level numbering starts from the currently executing function (level 0). Level 1 (the default) is the caller of the current function, and so on. The result is a four-element vector containing a hash of local variables, the function object, the source, and the line number.

level
Optional integer specifying the stack level to return a result from. Defaults to 1 (the caller of the currently executing function.

Examples

var myFunction = func(a, b){
    debug.dump(caller(0)[0]); # prints a hash of local variables, including arguments a and b
    return 2 * 2;
};

print("2 x 2 = ", myFunction(2, 2));
var get_arg_value = func(){
    print("Argument to myFunc = ", caller(1)[0]['a']); # print the value of myFunc's single argument, using caller()
};

var myFunc = func(a){
    get_arg_value();
};

myFunc(3);

This is a real example taken from fgdata/Nasal/canvas/MapStructure.nas. Function r() (above the TODOs) returns a hash with the key/value pairs as per its arguments. For example, something like this is returned: { name: "<name>", vis: 1, zindex: nil }.

var MapStructure_selfTest = func() {
	var temp = {};
	temp.dlg = canvas.Window.new([600,400],"dialog");
	temp.canvas = temp.dlg.createCanvas().setColorBackground(1,1,1,0.5);
	temp.root = temp.canvas.createGroup();
	var TestMap = temp.root.createChild("map");
	TestMap.setController("Aircraft position");
	TestMap.setRange(25); # TODO: implement zooming/panning via mouse/wheel here, for lack of buttons :-/
	TestMap.setTranslation(
		temp.canvas.get("view[0]")/2,
		temp.canvas.get("view[1]")/2
	);
	var r = func(name,vis=1,zindex=nil) return caller(0)[0];
	# TODO: we'll need some z-indexing here, right now it's just random
	# TODO: use foreach/keys to show all layers in this case by traversing SymbolLayer.registry direclty ?
	# maybe encode implicit z-indexing for each lcontroller ctor call ? - i.e. preferred above/below order ?
	foreach(var type; [r('TFC',0),r('APT'),r('DME'),r('VOR'),r('NDB'),r('FIX',0),r('RTE'),r('WPT'),r('FLT'),r('WXR'),r('APS'), ] ) 
		TestMap.addLayer(factory: canvas.SymbolLayer, type_arg: type.name,
					visible: type.vis, priority: type.zindex,
		);
}; # MapStructure_selfTest

chr()

chr(code);

Source

Returns a character as per the single argument. Extended ASCII is supported (see http://www.asciitable.com/ for a list of supported characters), although this may vary between different systems. For a list of the most commonly used characters, see the ASCII printable code chart This is a link to a Wikipedia article (Dec column). The following table lists supported control characters, along with their equivalent control characters in Nasal strings.

Note  In Nasal, only strings enclosed with double-quotes ("string") supports control chracters. Strings in single quotes ('string') do not.
Code Name Equivalent to
10 Newline This is a link to a Wikipedia article \n
9 Horizontal tab This is a link to a Wikipedia article \t
13 Carriage return This is a link to a Wikipedia article \r
code
Integer character code for the desired glyph.

Examples

print("Code 65 = ", chr(65)); # prints "Code 65 = A"

This example displays all of the characters in a list, in the format Code n = >char<, n being the index, and char being the character.

for(var i = 0; i <= 255; i += 1){
    print("Code ", i, " = >", chr(i), "<");
}

closure()

closure(func[, level]);

Source

Returns the hash table containing the lexical namespace of the given function. The level numbering start with level 0 being the namespace of func.

func
Function to evaluate.
level
Optional integer specifying the scope level. Defaults to 0 (the namespace of func).

Example

var get_math_e = func {
    return e; # return the value of math.e
}

var myFunction = bind(get_math_e, math); # bind get_math_e to the math namespace, so that math.e is immediately available to get_math_e
debug.dump(closure(myFunction)); # print the namespace of get_math_e

print(myFunction());

cmp()

cmp(a, b);

Source

Compares two strings, returning -1 if a is less than b, 0 if they are identical and 1 if a is greater than b.

a
First string argument for comparison.
b
Second string argument for comparison.

Examples

print(cmp("1", "two")); # prints -1
print(cmp("string", "string")); # prints 0
print(cmp("one", "2")); # prints 1
print(cmp("string1", "string2")); # prints -1

compile()

compile(code[, filename]);

Source

Compiles the specified code string and returns a function object bound to the current lexical context. If there is an error, the function dies, with the argument to die being filename.

code
String containing Nasal code to be compiled.
filename
Optional string used for error messages/logging. Defaults to <compile>

Examples

var myCode = 'print("hello");';
var helloFunc = compile(myCode, "myCode");
helloFunc();

compile is very convenient to support Nasal loaded from other files. For instance, PropertyList XML files (such as GUI dialogs) may contain embedded Nasal sections that need to be parsed, processed and compiled. For an example of how to do this, save the below XML code as $FG_ROOT/gui/dialogs/test.xml.

<?xml version="1.0"?>

<PropertyList>

<nasal><![CDATA[
print("You have FlightGear v", getprop("/sim/version/flightgear"));
]]></nasal>

</PropertyList>

Now, start FlightGear and execute this code in the Nasal Console.

# Build the path
var FGRoot = getprop("/sim/fg-root");
var filename = "/gui/dialogs/test.xml";
var path = FGRoot ~ filename;

var blob = io.read_properties(path);
var script = blob.getValues().nasal; # Get the nasal string

# Compile the script.  We're passing the filename here for better runtime diagnostics 
var code = call(func {
    compile(script, filename);
}, nil, nil, var compilation_errors = []);

if(size(compilation_errors)){
    die("Error compiling code in: " ~ filename);
}

# Invoke the compiled script, equivalent to code(); 
# We're using call() here to detect errors:
call(code, [], nil, nil, var runtime_errors = []);

if(size(runtime_errors)){
    die("Error calling code compiled loaded from: " ~ filename);
}

contains()

contains(hash, key);

Source

Returns 1 (True) if the hash contains the specified key, or 0 (False) if not.

hash
The hash to search in.
key
The scalar to be searched for, contained as a key in the hash.

Examples

# Initialize a hash
var hash = {
    element: "value"
};
print(contains(hash, "element") ? "Yes" : "No"); # This will print "Yes"
# Initialize a hash
var hash = {
    element: "value"
};
print(contains(hash, "element2") ? "Yes" : "No"); # This will print "No"

delete()

delete(hash, key);

Source

Deletes the key from the hash if it exists. Operationally, this is identical to setting the hash value specified by the key to nil, but this variant potentially frees storage by deleting the reference to the key and by shrinking the hash. Returns the hash that has been operated on.

hash
The hash from which to delete the key.
key
The scalar to be deleted, contained as a key in the hash.

Example

# Initialize the hash
var hash = {
    element1: "value1",
    element2: "value2"
};
delete(hash, "element1"); # Delete element1
debug.dump(hash); # prints the hash, which is now minus element1

die()

die(error);

Source

Terminates execution and unwinds the stack. The place and the line will be added to the error. This invokes the same internal exception handler used for internal runtime errors. Use this to signal fatal errors, or to implement exception handling. The error thrown (including internal runtime errors) can be caught with call .

error
String describing the error.
Note This parameter is technically optional, but it is highly recommended to use it.

Example

print("Will print");
die("Don't go any further!"); 
print("Won't print"); # Will not be printed because die() stops the process

find()

find(needle, haystack);

Source

Finds and returns the index of the first occurrence of the string needle in the string haystack, or -1 if no such occurrence was found.

needle
String to search for.
haystack
String to search in.

Examples

print(find("c", "abcdef")); # prints 2
print(find("x", "abcdef")); # prints -1
print(find("cd", "abcdef")); # prints 2

ghosttype()

ghosttype(ghost);

Source

Returns a string containing either a descriptive name of a ghost (a raw C/C++ object), or a unique id (the pointer to the C/C++ naGhostType instance) if no name has been set. Ghost is an acronym that stands for Garbage-collected Handle to OutSide Thingy.

ghost
Ghost to return a description for.

Example

print(ghosttype(airportinfo())); # prints "airport"

id()

id(object);

Source

Returns a string containing information on the type and ID of the object provided in the single argument. The information is returned in the form of type:id, where type is the type of object, and id is the ID.

object
Can be either of a string, a vector, a hash, a code, a function, or a ghost.

Example

print(id("A")); # prints "str:000000001624A590"

int()

int(number);

Source

Returns the integer part of the numeric value of the single argument, or nil if none exists.

number
Number or string with just a number in it to return an integer from.

Examples

print(int(23)); # prints "23"
print(int(23.123)); # prints "23"
debug.dump(int("string")); # prints "nil"

keys()

keys(hash);

Source

Returns a vector containing the list of keys found in the single hash argument.

hash
The hash to return the keys from.

Example

# Initialize a hash
var hash = {
    element1: "value",
    element2: "value"
};
debug.dump(keys(hash)); # print the vector

left()

left(string, length);

Source — Version added: FG 2.12

Returns a substring of string, starting from the left.

string
String to return part of.
length
Integer specifying the length of the substring to return.

Example

print(left("string", 2)); # prints "st"

num()

num(number);

Source

Returns the numerical value of the single string argument, or nil if none exists.

number
String with just a number in it to return a number from.

Examples

print(num("23")); # prints "23"
print(num("23.123")); # prints "23.123"
debug.dump(num("string")); # prints "nil"

pop()

pop(vector);

Source

Removes and returns the last element of the single vector argument, or nil if the vector is empty.

vector
Vector to remove an element from.

Examples

var vector = [1, 2, 3];
pop(vector);
debug.dump(vector); # prints "[1, 2]"
var vector = [1, 2, 3];
debug.dump(pop(vector)); # prints "3"
var vector = [];
debug.dump(pop(vector)); # prints "nil"

right()

right(string, length);

Source — Version added: FG 2.12

Returns a substring of string, starting from the right.

string
String to return part of.
length
Integer specifying the length of the substring to return.

Example

print(right("string", 2)); # prints "ng"

setsize()

setsize(vector, size);

Source

Sets the size of a vector. The first argument specifies a vector, the second a number representing the desired size of that vector. If the vector is currently larger than the specified size, it is truncated. If it is smaller, it is padded with nil entries. Returns the vector operated upon.

vector
The vector to be operated on.
size
The desired size of the vector in number of entries.

Examples

var vector = [1, 2, 3]; # Initialize a vector
setsize(vector, 4);
debug.dump(vector); # print the vector
var vector = [1, 2, 3]; # Initialize a vector
setsize(vector, 2);
debug.dump(vector); # print the vector

size()

size(object);

Source

Returns the size of the single argument. For strings, this is the length in bytes. For vectors, this is the number of elements. For hashes, it is the number of key/value pairs. If the argument is nil or a number, this error will be thrown: object has no size().

object
Object to find the size of. Must be a string, a vector or a hash.

Examples

var string = "string";
print(size(string)); # prints "6"
var vector = [1, 2, 3];
print(size(vector)); # prints "3"
var hash = {
    element1: "value1",
    element2: "value2",
    element3: "value3"
};
print(size(hash)); # prints "3"

sort()

sort(vector, function);

Source

Returns a vector containing the elements in the input vector sorted in according to the rule given by function. Implemented with the ANSI C qsort() , sort() is stable. This means that if the rules in the first example are used, equal elements in the output vector will appear in the same relative order as they do in the input. It is run in a loop, so function is run several times.

vector
Input vector to sort.
function
Function according to which the elements will be sorted by. It should take two arguments and should return one of 1, 0, or -1.
Return value Meaning
less than 0 first argument should go before second argument
0 first argument equals second argument
greater than 0 first argument should go after second argument

Examples

This example sorts elements from greatest to smallest.

var sort_rules = func(a, b){
    if(a < b){
        return -1; # A should before b in the returned vector
    }elsif(a == b){
        return 0; # A is equivalent to b 
    }else{
        return 1; # A should after b in the returned vector
    }
}
debug.dump(sort([3, 2, 5, 6, 4, 1], sort_rules)); # prints "[1, 2, 3, 4, 5, 6]"
# Outputs the elements in reverse order (greatest to smallest)
var sort_rules = func(a, b){
    if(a < b){
        return 1; # -1 in the above example
    }elsif(a == b){
        return 0;
    }else{
        return -1; # 1 in the above example
    }
}
debug.dump(sort([3, 2, 5, 6, 4, 1], sort_rules)); # prints "[6, 5, 4, 3, 2, 1]"

split()

split(delimiter, string);

Source

Splits the input string into a vector of substrings bounded by occurrences of the delimiter substring.

delimiter
String that will split the substrings in the returned vector.
string
String to split up.

Examples

debug.dump(split("cd", "abcdef")); # prints "['ab', 'ef']"
debug.dump(split(".", "3.2.0")); # prints "[3, 2, 0]"
debug.dump(split("/", "path/to/file")); # prints "['path', 'to', 'file']"

sprintf()

sprintf(format[, arg[, arg, [...]]]);

Source

Creates and returns a string formatted using ANSI C vsnprintf() [1]. Below is a table of supported format specifiers.

%[flags][width][.precision]specifier
Flags
Flag Output
+ Forces to precede the result with a plus or minus sign (+ or -) even for positive numbers. By default, only negative numbers are preceded with a - sign.
space Prefixes non-signed numbers with a space.
- Left-align the output of this placeholder (the default is to right-align the output) when the width option is specified.
0 Use 0 instead of spaces to pad a field when the width option is specified.
# Used with o, x or X specifiers the value is preceded with 0, 0x or 0X respectively for values different than zero. Used with e, E and f, it forces the written output to contain a decimal point even if no digits would follow. By default, if no digits follow, no decimal point is written. Used with g or G the result is the same as with e or E but trailing zeros are not removed.
Width
Integer specifying the minimum number of characters to be returned.
Precision
Integer preceded by a dot specifying the number of decimal places to be written.
Specifiers
Specifier Output
d, i Signed decimal number.
% Percent (%) character.
c A single character assigned to a character code, the code given in an integer argument. See http://www.asciitable.com/ for a list of supported characters and their codes.
o Unsigned integer as an octal number.
u Unsigned decimal integer.
x, X Unsigned integer as a hexadecimal number. If x is used, any letters in the number are lowercase, while X gives uppercase.
e, E Double value in scientific notation (i.e., [-]ddd.ddde[+/-]ddd), with an exponent being denoted by e or E depending on whether an upper or lowercase is used respectively.
f Floating-point number, in fixed decimal notation, by default with 6 decimal places.
F Appears to be available[2], but doesn't work.
g, G Double in either normal or exponential notation, whichever is more appropriate for its magnitude. g uses lower-case letters, G uses upper-case letters. This type differs slightly from fixed-point notation in that insignificant zeroes to the right of the decimal point are not included. Also, the decimal point is not included on whole numbers.
format
String specifying the format. Can be used with or without a format specifiers. See below for examples.
arg
Argument specifying a value to replace a format placeholder (such as %d) in the format string. Not required if there are no format specifiers.

Examples

print(sprintf("%i", 54)); # prints "54"
print(sprintf("Pi = %+.10f", math.pi)); # prints "Pi = +3.1415926536"
print(sprintf("%6d", 23)); # prints "    23"
print(sprintf("%06d", 23)); # prints "000023"
var FGVer = getprop("/sim/version/flightgear");
print(sprintf("You have FlightGear v%s", FGVer)); # prints "You have FlightGear v<your version>"
print(sprintf("Hexadecimal 100000 = %X", 100000)); # prints "Hexadecimal 100000 = 186A0"
print(sprintf("Hexadecimal 100000 = %x", 100000)); # prints "Hexadecimal 100000 = 186a0"
print(sprintf("Code 65 is %c", 65)); # prints "Code 65 is A"
print(sprintf("%e", 54)); # prints "5.400000e+001"
print(sprintf("%E", 54)); # prints "5.400000E+001"
print(sprintf("%o", 54)); # prints "66"
print(sprintf("50%% of 100 is %i", 100 / 2)); # prints "50% of 100 is 50"

streq()

streq(a, b);

Source

Tests the string values of the two arguments for equality. This function is needed because the == operator (see Nasal Operators) tests for numeric equality first. If either or both the arguments are not strings, 0 (False) will be returned. Returns either 0 (False) or 1 (True).

Note  This function is rarely required in typical code.
a
First argument for testing equality.
b
Second argument for testing equality.

Examples

print(streq("0", "0")); # prints "1" (True)
print(0 == 0.0); # prints "1" (True)
print(streq("0", "0.0")); # prints "0" (False)

substr()

substr(string, start [, length]);

Source

Similar the subvec , but operates on strings. Computes and returns a substring. The first argument specifies a string, the second is the index of the start of a substring, the optional third argument specifies a length (the default is to return the rest of the string from the start).

string
String to return a substring from.
start
Integer specifying the start of a substring.
length
Optional argument specifying the length of the substring. Defaults to the end of the string.

Examples

print(substr("abcde", 1, 3)); # prints "bcd"
print(substr("abcde", 1)); # prints "bcde"
print(substr("abcde", 2, 1)); # prints "c"

subvec()

subvec(vector, start[, length]);

Source

Returns a sub-range of a vector. The first argument specifies a vector, the second a starting index, and the optional third argument indicates a length (the default is to the end of the vector).

vector
The vector to take the sub-vector from.
start
The starting point of the sub-vector within the given vector.
length
Optional argument specifying the length of the sub-vector, from the starting point.

Examples

var vector = [1, 2, 3];
debug.dump(subvec(vector, 0)); # prints "[1, 2, 3]"
var vector = [1, 2, 3];
debug.dump(subvec(vector, 1)); # prints "[2, 3]"
var vector = [1, 2, 3];
debug.dump(subvec(vector, 1, 1)); # prints "[2]"

typeof()

typeof(object);

Source

Returns a string indicating the whether the object is nil, a scalar (number or string), a vector, a hash, a function, or a ghost.

object
Object to return the type of.

Examples

var object = nil;
print(typeof(object)); # prints "nil"
var object = "Hello world!";
print(typeof(object)); # prints "scalar"
var object = math.pi;
print(typeof(object)); # prints "scalar"
var object = [1, 2, 3];
print(typeof(object)); # prints "vector"
var object = {};
print(typeof(object)); # prints "hash"
var object = func {};
print(typeof(object)); # prints "func"
var object = airportinfo();
print(typeof(object)); # prints "ghost"

Extension functions

The extension functions are global functions that have been added to Nasal since its integration into FlightGear. Unlike the core library functions, they are generally specifically designed to interact directly with FlightGear. Extension functions come from four source files:

abort()

abort();

Source

This function is a wrapper for the C++ abort() function. It simply aborts FlightGear with an error, which varies depending on the operating system. This function should not really be used; instead, please use the "exit" fgcommand, which will exit FlightGear more gracefully (see example below).

Examples

This example will immediately stop FlightGear with an error, such as "FlightGear has stopped working."

abort();

For exiting FlightGear in a better way, please use the following code.

fgcommand("exit");

abs()

abs(number);

Source

This simple function returns the absolute value of the provided number.

number
This argument is required and should be a number.

Examples

print(abs(1)); # prints "1"
print(abs(-1)); # prints "1"

addcommand()

addcommand(name, code);

Source — Version added: FG 2.12

This function enables the addition of a new custom fgcommand to FlightGear from within Nasal. An fgcommand created using this method can be used in exactly the same way as the built-in fgcommands. Also, an fgcommand created via this method will always return True or 1, like all other fgcommands.

name
This will become the name of the new fgcommand. Must be a string.
code
The code that will be executed when the fgcommand is run. Must be a function.

Examples

This example adds a new fgcommand and then runs it. Although it executes a simple print statement, any valid Nasal code can be used.

addcommand("myFGCmd", func {
    print("fgcommand 'myFGCmd' has been run.");
});
fgcommand("myFGCmd");

This example demonstrates how parameters are defined in a new fgcommand.

addcommand("myFGCmd", func(node){
    print(node.getNode("number").getValue()); # prints the value of "number," which is 12
});
fgcommand("myFGCmd", props.Node.new({"number": 12}));

airportinfo()

airportinfo();
airportinfo(type);
airportinfo(id);
airportinfo(lat, lon[, type]);

Source

Function for retrieval of airport, heliport, or seaport information. It returns a Nasal ghost; however, its structure is like that of a Nasal hash. The following information is returned:

  • lon: Longitude of the location.
  • lat: Latitude of the location.
  • has_metar: True or false depending whether the airport has a METAR code defined for it.
  • elevation: Elevation of the location in metres.
  • id: ICAO code of the airport (or ID of the seaport/heliport).
  • name: Name of the airport/heliport/seaport.
  • runways
    • <runway name>
      • stopway: Length of the runway's stopway (the area before the threshold) in metres. Will return 0 if there is none.
      • threshold: Length of the runway's displaced threshold This is a link to a Wikipedia article in metres. Will return 0 if there is none.
      • lat: Latitude of the runway.
      • lon: Longitude of the runway.
      • width: Width of the runway in metres.
      • heading: Heading of the runway.
      • length: Length of the runway in metres.

Information is extracted in the same way as accessing members of a Nasal hash. For example:

# prints to lengths of the runways of the nearest airport in feet and metres
var info = airportinfo();
print("-- Lengths of the runways at ", info.name, " (", info.id, ") --");
foreach(var rwy; keys(info.runways)){
    print(rwy, ": ", math.round(info.runways[rwy].length * M2FT), " ft (", info.runways[rwy].length, " m)");
}

Note that searches for locations that are a long way away (e.g., the nearest seaport to the middle of the Sahara) may cause FlightGear to pause for an amount of time.

id
The ICAO code of an airport to retrieve information about.
type
When this argument is used, the function will return the closest airport of a certain type. Can be one of "heliport," "seaport," or "airport" (default).
Note Running this function without any parameters is equivalent to this:
airportinfo("airport");

lat and lon
When these parameters are used, the function will return information on the nearest airport, heliport or seaport (depending on the type parameter) to those coordinates.

Examples

var info = airportinfo();
print("Nearest airport: ", info.name, " (", info.id, ")"); # prints the name and ICAO code of the nearest airport
var info = airportinfo("heliport");
print("Elevation of the nearest heliport: ", math.round(info.elevation * M2FT), " ft"); # prints the elevation and name of the nearest heliport
var info = airportinfo("KSQL");
print("-- Runways of ", info.name, " (", info.id, "): --");
foreach(var rwy; keys(info.runways)) {
    print(rwy); # prints the runways of KSQL
}
var info = airportinfo(37.81909385, -122.4722484);
print("Coordinates of the nearest airport: ", info.lat, ", ", info.lon); # print the name and ICAO of the nearest airport to the Golden Gate Bridge
var info = airportinfo(37.81909385, -122.4722484, "seaport");
print("Nearest seaport: ", info.name, " (", info.id, ")"); # print the name and ID of the nearest seaport to the Golden Gate Bridge

This example prints the all information from an airportinfo() call.

var info = airportinfo("KSFO");
print(info.name);
print(info.id);
print(info.lat);
print(info.lon);
print(info.has_metar);
print(info.elevation);
foreach(var rwy; keys(info.runways)){
    print("-- ", rwy, " --");
    print(info.runways[rwy].lat);
    print(info.runways[rwy].lon);
    print(info.runways[rwy].length);
    print(info.runways[rwy].width);
    print(info.runways[rwy].heading);
    print(info.runways[rwy].stopway);
    print(info.runways[rwy].threshold);
}

cmdarg()

_cmdarg()
cmdarg();

Part 1 | Part 2

cmdarg() returns the property root of certain types of XML files. These could be nodes in the Property Tree, or temporary and/or non-public nodes outside the Property tree.

It is used by Nasal scripts embedded in XML files. It returns a props.Node object (see fgdata/Nasal/props.nas), and you can use all of its methods on the returned value. cmdarg() should only be used in four types/places of XML files:

  • Bindings: This is needed so that the value of a joystick's axis can be accessed internally.
  • Dialogs: This will return the root of the dialog in the Property Tree. This is useful for dialogs that are created/modified procedurally (e.g. for populating/changing widgets while loading the dialog).
  • Embedded Canvases: The Nasal code behind Canvas windows embedded in PUI dialogs can use it to accessing the root directory of their Canvas.
  • Animation XML files: If the animation XML file is used in an AI/MP model, cmdarg() will return the root of the AI model in the /ai/models/ directory. Examples: /ai/models/aircraft[3]/, /ai/models/multiplayer[1]/

You should not use cmdarg() in places other than those stated above. Although it won't cause an error, it will return the value of the last legitimate cmdarg() call.

Also, you should not delay cmdarg() using maketimer , settimer or setlistener , because it will return an unrelated property.

Example

This example demonstrates the usage of cmdarg() in a binding. Save the below XML snippet as $FG_ROOT/gui/dialogs/cmdarg-demo.xml. Then run the Nasal snippet below in your Nasal Console. Upon clicking "Close," a message will be printed sowing the root of the binding in the Property Tree.

<?xml version="1.0" encoding="UTF-8"?>

<PropertyList>
  <name>cmdarg-demo</name>
  <layout>vbox</layout>

  <text>
    <label>Press "Close" to activate the demonstration (a message in the console).</label>
  </text>

  <button>
    <legend>Close</legend>
    <binding>
      <command>nasal</command>
      <script>print("Button binding root: '" ~ cmdarg().getPath() ~ "'");</script>
    </binding>
    <binding>
      <command>dialog-close</command>
    </binding>
  </button>

</PropertyList>
fgcommand("dialog-show", props.Node.new({"dialog-name": "cmdarg-demo"}});

This example demonstrates the usage of cmdarg() in Nasal code within dialogs. Open $FG_ROOT/gui/dialogs/cmdarg-demo.xml from the previous example, blank the file, paste the code below, and save it. Then run the same Nasal snippet as the previous example in your Nasal Console, and notice that the button is labelled "Close" instead of "Change me."

<?xml version="1.0" encoding="UTF-8"?>

<PropertyList>
  <name>cmdarg-demo</name>
  <layout>vbox</layout>

  <text>
    <label>Press "Close" to activate the demonstration (a message in the console).</label>
  </text>

  <button>
    <legend>Change me</legend>
    <binding>
      <command>dialog-close</command>
    </binding>
  </button>

  <nasal>
    <open><![CDATA[
      var self = cmdarg();
      self.getNode("button[0]/legend").setValue("Close");
    ]]></open>
  </nasal>
</PropertyList>

For an example of cmdarg() used with Canvas, please see Howto:Adding a canvas to a GUI dialog.

fgcommand()

Runs an internal "fgcommand", see Bindings for a full list of available items (since they are most often used in bindings for input devices). The first argument is the name of the command (e.g. "property-interpolate") and the second is a props.Node object or its ghost which is the "argument" to the fgcommand. (This ensures that fgcommands are universal since props.Node/SGPropertyNode objects can really be accessed from anywhere in FlightGear.) Each fgcommand returns 1 if it succeeded or 0 if it failed.

The profiling related fgcommands profiler-start and profiler-stop are documented at Built-in Profiler.

removecommand()

As you can guess, there's also a removecommand() function which will remove any command – even those implemented in C++! As such it can be very dangerous and remove core functionality, so use with caution.

print()

Concatenates an arbitrary number of arguments to one string, appends a new-line, and prints it to the terminal. Returns the number of printed characters.

print("Just", " a ", "test");

logprint()

Similar, but the first argument is reserved for number specifying the priority (i.e. matching a sgDebugPriority object: 1 for bulk, 2 for debug, 3 for info, and 4 for warn). Also see printlog() in globals.nas: it does essentially the same but with named levels ("bulk", etc.). (The latter relies on print(), however, and does not make use of the sophistication of sglog in dealing with source file and line number.)

getprop()

Returns the node value for a given path, or nil if the node doesn't exist or hasn't been initialized to a value yet.

getprop(<path> [, <path>, [...]]);

Several arguments will be added together to produce a path, with numeric arguments specifying indexes (as of FlightGear 3.1), so

getprop("canvas/by-index", "texture", 1, "name");

is the same as:

getprop("canvas/by-index/texture[1]/name");

Example:

print("The frame rate is ", getprop("/sim/frame-rate"), " FPS");
for (var i=0; i < 10; i += 1) {
    print("View ", i, "'s name is: ", getprop("/sim/view", i, "name"));
}

setprop()

Sets a property value for a given node path string. Returns 1 on success or 0 if the property could not be set (i.e. was read-only).

setprop(<path> [, <path>, [...]], <value>);

All arguments but the last are concatenated to a path string, like getprop() above. The last value is written to the respective node. If the node isn't writable, then an error message is printed to the console.

Examples:

setprop("/sim/current-view/view-number", 2);
setprop("/controls", "engines/engine[0]", "reverser", 1);

Erasing a property from the property tree: a property that has been created, for example through setprop() has to be erased using the props namespace helper, like this:

props.globals.getNode("foo/bar").remove(); 		# take out the complete node
props.globals.getNode("foo").removeChild("bar"); 	# take out a certain child node

interpolate()

Give the value from a value or a source node to a destination node in given time.

interpolate(<path>, <value>, <time> [, <value2>, <time2> [, ...]]);

Examples:

interpolate("controls/switches/nav-lights-pos", 1, 0.25); # After 25ms, nav-lights-pos = 1
interpolate("controls/gear/brake-left-pos", getprop("controls/gear/brake-left"), 1); # After 1s, brake-left-pos = brake-left

To interpolate with a constant speed, use the formula time = distance/speed.

# Apply the brakes at 20%/second
var p = "controls/gear/brake-left";
var current = getprop(p);
var dist = 1-current;
if (dist) interpolate(p, 1, dist/.2);

setlistener()

settimer()

CautionImproper use of the settimer() API may cause resource leaks.

This typically is caused by the low-level nature of such code, requiring manual tracking of listeners and timers and manual reset and re-init handling.

It is instead recommended that you use the maketimer() API. Alternatively a wrapping helper class can be used to handle low-level APIs, which is the recommended way to support multiple FlightGear versions.

Runs a function after a given simulation time (default) or real time in seconds.

settimer(<function>, <time> [, <realtime=0>]);

The first argument is a function object (ie, "func { ... }"). Note that this is different from a function call (ie, "func ( ... )"). If you don't understand what this means, just remember to always enclose the first argument in any call to settimer with the word "func" and braces "{ }", and it will always work.

The second argument is a delay time. After this amount of time the function will be executed. For instance, if you want to print the words "My result" in five seconds, use this code:

settimer ( func { print ( "My result"); }, 5);

Inside the braces of the func object you can put any valid Nasal code, including a function call. In fact, if you want to call a function with certain values as arguments, the way to do it is to turn it into a function object by enclosing it with a func{}, for example:

myarg1="My favorite string";
myarg2=432;
settimer ( func { myfunction ( myarg1, myarg2); }, 25);

The third argument is optional and defaults to 0, which lets the time argument be interpreted as "seconds simulation time". In this case the timer doesn't run when FlightGear is paused. For user interaction purposes (measuring key press time, displaying popups, etc.) one usually prefers real time.

# simulation time example
var copilot_annoyed = func { setprop("/sim/messages/copilot", "Stop it! Immediately!") }
settimer(copilot_annoyed, 10);
# real time example
var popdown = func ( tipArg ) { 
    fgcommand("dialog-close", tipArg); 
}

var selfStatusPopupTip = func (label, delay = 10, override = nil) {	
    var tmpl = props.Node.new({
            name : "PopTipSelf", modal : 0, layout : "hbox",
            y: 140,
            text : { label : label, padding : 6 }
    });
    if (override != nil) tmpl.setValues(override);

    popdown(tipArgSelf);
    fgcommand("dialog-new", tmpl);
    fgcommand("dialog-show", tipArgSelf);

    currTimerSelf += 1;
    var thisTimerSelf = currTimerSelf;

    # Final argument 1 is a flag to use "real" time, not simulated time
    settimer(func { if(currTimerSelf == thisTimerSelf) { popdown(tipArgSelf) } }, delay, 1);
}

More information about using the settimer function to create loops.

maketimer() (2.11+)

As of 2.11, there is a new API for making a timer that allows more control over what happens in a timer – as opposed to setting one and forgetting about it.

var timer = maketimer(<interval>, <function>)
var timer = maketimer(<interval>, <self>, <function>)
timer.start()
timer.stop()
timer.restart(<interval>)
timer.singleShot [read/write]
timer.isRunning [read-only]
# create timer with 1 second interval
var timer = maketimer(1.0, func { 
 print('timer called'); 
 }
);
# start the timer (with 1 second inverval)
timer.start();
# restart timer with 4 second interval
timer.restart(4);

# fire one single time in 6 seconds
timer.singleShot = 1;
timer.restart(6);
var Tooltip = {
  new: func
  {
    var m = {
      parents: [Tooltip]
    }
    m._hideTimer = maketimer(1.0, m, Tooltip._hideTimeout);
    m._hideTimer.singleShot = 1;

   return m;
  },
  run: func
  {
    if( !me._hideTimer.isRunning )
      me._hideTimer.start();
  }
  _hideTimeout: func
  {
    print('_hideTimeout');
  }
};

geodinfo()

Returns information about geodetic coordinates. Takes two arguments: lat, lon (in degree) and returns a vector with two entries, or nil if no information could be obtained because the terrain tile wasn't loaded. The first entry is the elevation (in meters) for the given point, and the second is a hash with information about the assigned material, or nil if there was no material information available, because there is, for instance, an untextured building at that spot or the scenery tile is not loaded.

var lat = getprop("/position/latitude-deg");
var lon = getprop("/position/longitude-deg");
var info = geodinfo(lat, lon);

if (info != nil) {
    print("the terrain under the aircraft is at elevation ", info[0], " m");
    if (info[1] != nil)
        print("and it is ", info[1].solid ? "solid ground" : "covered by water");
}

A full data set looks like this:

debug.dump(geodinfo(lat, lon));

# outputs
[ 106.9892101062052, { light_coverage : 0, bumpiness : 0.5999999999999999, load_resistance : 1e+30,
solid : 0,  names : [ "Lake", "Pond", "Reservoir", "Stream", "Canal" ], friction_factor : 1, 
rolling_friction : 1.5 } ]

Note that geodinfo is a *very* CPU intensive operation, particularly in FG 2.4.0 and earlier, so use sparingly (forum discussion here).


history() (3.1+)

Function to expose flight history as aircraft.history()

var hist = aircraft.history();

# get history of aircraft position/orientation collapsing
# nodes with a distance smaller than the given minimum
# edge legth
debug.dump( hist.pathForHistory(<minimum-edge-length-meter>) );


flightplan()

Function to retrieve the active flight-plan object, or load a flight plan from a file path.

Usage:

var fp = flightplan();
var fp = flightplan('/some/path/to/a/flightplan.xml');

In advance of converting the Map and NavDisplay to use the Canvas, James has improved the "flightplan()" extension function of the Nasal scripting interpreter to expose the full route-path vector for each flight plan leg as a vector on the leg.


var fp = flightplan();
for (var i=0; i<fp.getPlanSize(); i += 1)
{
  var leg = fp.getWP(i);
  debug.dump(leg.path());
}

systime()

Returns epoch time (time since 1970/01/01 00:00) in seconds as a floating point number with high resolution. This is useful for benchmarking purposes.

#benchmarking example:
var start = systime();
how_fast_am_I(123);
var end = systime();
print("took ", end - start, " seconds");

carttogeod()

Converts cartesian coordinates x/y/z to geodetic coordinates lat/lon/alt, which are returned as a vector. Units are degree and meter.

var geod = carttogeod(-2737504, -4264101, 3862172);
print("lat=", geod[0], " lon=", geod[1], " alt=", geod[2]);

# outputs
lat=37.49999782141546 lon=-122.6999914632327 alt=998.6042055172776

geodtocart()

Converts geodetic coordinates lat/lon/alt to cartesian coordinates x/y/z. Units are degree and meter.

var cart = geodtocart(37.5, -122.7, 1000); # lat/lon/alt(m)
print("x=", cart[0], " y=", cart[1], " z=", cart[2]);

# outputs
x=-2737504.667684828 y=-4264101.900993474 z=3862172.834656495

parsexml()

This function is an interface to the built-in Expat XML parser. It takes up to five arguments. The first is a mandatory absolute path to an XML file, the remaining four are optional callback functions, each of which can be nil (which is also the default value).

var ret = parsexml(<path> [, <start-elem> [, <end-elem> [, <data> [, <pi> ]]]]);

<start-elem>  ... called for every starting tag with two arguments: the tag name, and an attribute hash
<end-elem>    ... called for every ending tag with one argument: the tag name
<data>        ... called for every piece of data with one argument: the data string
<pi>          ... called for every "processing information" with two args: target and data string

<ret>         ... the return value is nil on error, and the <path> otherwise

Example:

var start = func(name, attr) {
    print("starting tag ", name);
    foreach (var a; keys(attr))
        print("\twith attribute ", a, "=", attr[a]);
}
var end = func(name) { print("ending tag ", name) }
var data = func(data) { print("data=", data) }
var pi = func(target, data) { print("processing instruction: target=", target, " data=", data) }
parsexml("/tmp/foo.xml", start, end, data, pi);

resolvepath()

SimGear features its own path resolving framework that takes a relative path and returns an absolute path, checking from base directories such as $FG_ROOT, $FG_HOME, $FG_AIRCRAFT, and the current aircraft directory (/sim/aircraft-dir). This function in Nasal takes a path string and returns the absolute path or an empty string if the path couldn't be resolved.

Example:

var guess_path = func(path...) {
    var path_concat = string.join(path, "/");
    var file_path = resolvepath(path_concat);
    if (file_path == "") die("Path not found: "~path_concat);
    return file_path;
}

HTTP module (2.99+)

Get remote data using the HTTP.

http.load()

Load resource identified by its URL into memory.

var request = http.load(<url>);
http.load("http://example.com/test.txt")
    .done(func(r) print("Got response: " ~ r.response));

http.save()

Save resource to a local file.

var request = http.save(<url>, <file_path>);
http.save("http://example.com/test.png", getprop('/sim/fg-home') ~ '/cache/test.png')
    .fail(func print("Download failed!"))
    .done(func(r) print("Finished request with status: " ~ r.status ~ " " ~ r.reason))
    .always(func print("Request complete (fail or success)"));

rand()

Return a random number as generated by sg_random.

srand()

Seed the random number generator based upon the current time. Returns 0.

md5() (3.1+)

Get the md5 hash of a string.

var hash = md5(<str>);
References