7. Advanced Concepts
This chapter includes advanced concepts in GSL which were left out from the former chapters to not confuse new users. You can write mighty scripts without using these concepts, they don't add aditional functionality, but add some more type safety, reduce coding effort and beautify your code. As you might know, all structs in GSL are extended from the struct type "struct" which contains no members at all. However, you can also extend a struct from other ones(like inheritance in object oriented languages). A struct contains all members of all structs it is extended from, plus the ones that are declared in its own definition. An extended struct can be used everwhere, where one of its parents can be used ([ref=typecast]implicit upcast[]). Note that if two of the struct's parents have equally named members of the struct itself redeclares a member that is already in one of its ancestors, you will get an error, telling you which members are double and thus one of both has to removed. You can test if the content of a variable is derived from (or even equal to) a struct by using the instanceof operator. The syntax for deriving structs from other ones is the following: typedef TYPENAME struct extends PARENT1 , PARENT2, ...{
MEMBERDEFINITIONS
}
The only new part is the "extends PARENT1 , PARENT2, ..." behind the word struct telling which parents this struct is derived from. Definition examples: Defining Point3D as a child of Point2D typedef Point2D struct{
int x;
int y;
}
typedef Point3D struct extends Point2D{
int z;
}
Point3D now has the members x,y,z.
Wrong: typedef Machine struct{
int price;
...
}
typedef Car struct{
int price;
...
}
typedef Porsche struct extends Machine, Car{
...
}
Will produce an error, since Porsche is extended from Machine and Car and both contain a member named "price".
Usage examples: Using the above definitions of Point2D/Point3D: Point2D myPoint = Point3D(); //Possible, since Point3D is an ancestor of Point3D (implicit upcast)
if(myPoint instanceof Point3D){ //True, since this instance of Point2D is actually of type Point3D
Point3D p = (Point3D)myPoint; //Explicit downcast
p.z = 100; //Possible, since now we are using a "Point3D"
}
[subcaption ref]Extending other datatypes[/subcaption] In GSL you can not only extend structs, but ALL data types. However this doesn't add new functionality, it just adds some typesafety. Like for structs, a derived type can always be cast implicitly to its ancestors. If you derive for example something from the type int, then you can use variables of that type everywhere, where you can use a variable of type int (implicit upcasting). However, if you declare functions that take a variable of your new defined type, than you have to cast ints to that type explicitly when using them as parameter (explicit downcasting). The syntax from extending from an arbitrary type is the following: typedef TYPENAME extends PARENT;
This creates the type <TYPENAME> which is a child of the type <PARENT>.
Example: We derive from int, to create a typesafe sleep function: typedef Milliseconds extends int; //The definition
void typesafeSleep(Milliseconds m){ //The typesafe sleep function, takes only milliseconds
sleep(m); //No casting is needed here, sleep takes an int which is an ancestor of Milliseconds (implicit upcast)
}
Milliseconds m = 1000;
typesafeSleep(m); //Possible, types match
int i = 1000;
typesafeSleep(i); //ERROR: int can't be cast implicitly to Millisec
typesafeSleep((Millisec)i); //Possible with explicit cast (explicit downcasting)
[subcaption ref]Using types that were defined in another file[/subcaption] If you want to define data types in one file and use them in another one which is executed at the same evaluation run, then you could get problems: If the file where they are defined is included in the file where they re used, you will get a Parse Error, even if the include statement was before the first usage. But why? Very simple: In contrast to C/C++ where include is a preprocessor directive and is executed before any compilation, in GSL include is a normal function, which is evaluated AFTER parsing the file. Since at parse time, types have to be well-known to the parser, it will throw an error, if you use external types like this. There are 4 ways to go around that problem: 1) Define and use them in the same file. However most of the time this is dissatisfying, since you want to use types in many files or just keep the structure you want. 2) Let the defining file include the using after the definition, not vice versa. Then, the types will be known when the using file is included. However this is still dissatisfying sometimes when many files should use one type. 3) Include the definition file in another evaluation run. By default GSL does three evaluation runs: [list] [*]One for the basic system calls, which only executes the file essentials.gsl in the script/autoexec folder. [*]One for all other autoexec tasks, which executes the autoexec.gsl in the script/autoexec folder. This file is a good place to put your includes to defined data types. [*]The actual run for the desired script file. One run is done completely (i.e. parsing AND evaluation) before the next one is parsed. So if you define your data types for example in the autoexec run, they will be available in the desired script file even without reincluding the file there. 4) Use the "typedef extern" syntax to declare them in the using file. The typedef extern statement: Syntax: typedef TYPE1,TYPE2,... extern;
The typedef extern statement tells the parser that the declared types (TYPE1, TYPE2...) are defined somewhere else and may be used as types. Now you won't get parse errors when using this types. But don't forget to include the file where they are defined before you use them the first time, or you will get an unknown type error!Typerestricted arrays are normal arrays which may only contain values of some predefined types, not every type. This adds additional typesafety. An example would be a function that takes an array of ints, adds them all and returns the result. If one entry is no error, this function would fail, so we need to ensure that the taken array only contains ints. We could do this by hand by just checking every entry for "instanceof int". However GSL offers a more intuitive and simple way to do it: Typerestricted arrays. Syntax: array<TYPE1,TYPE2,...>
This syntax restricts the array to the Types <TYPE1>, <TYPE2> ...
Usage of this expression:
A Namespace is a useful construct in GSL, syntax fist: namespace VARNAME{
arbitrary code
}
<VARNAME> has to be either an array or a struct variable.
Note that namespaces may be nested in other ones.
The semantics is the following:
The GSL Nameresolver resolves variable names in the following order, if a name isn't found in one of this spaces, than the next one is checked:
Examples: Usage for arrays: array a = array();
a["foo"] = "bar";
namespace a{
echo(foo); //Will echo "bar", since the nameresolver uses the key "foo" from a.
}
Usage for structs: typedef Point2D struct{
int x;
int y;
}
Point2D p = Point2D();
p.x = 15;
namespace p{
echo(x + ""); //Will echo 16, since the nameresolver uses the member "x" from p.
}
Nested namespaces: array a = array();
a["foo"] = "bar in array a";
a["foo2"] = "bar2 in array a";
array b = array();
b["foo"] = "bar in array b";
namespace a{
namespace b{
echo(foo); //Will echo "bar in array b", since the nameresolver will look in b before a, since it is the innermost namespace
echo(foo2); //Will echo "bar2 in array a", since the nameresolver cannot find the key "foo2" in a, so it checks b for it
string foo = "local bar";
global string foo2 = "global bar";
echo(foo); //Will echo "local bar", since the nameresolver checks local variables before namespaces
echo(foo2); //Will echo "global bar", since the nameresolver checks global variables before namespaces
}
} The keyword this can be used inside namespaces. It evaluates to the innermost namespace, i.e. to an array or struct. Example: array a = array();
namespace a{
this.foo = "bar"; //sets a["foo"] to bar
this["foo"] = "bar"; //same as above, just using array syntax instead of struct syntax
echo(foo); //will echo bar
echo(this.foo); //the same as above
}
This is useful for assigning new variables to the namespace or checking which type the namespace is. If you try to use this outside a namespace you will get an error. If you use "return;" while beeing in no function, this will exit this evaluation run. Additional, you can just stop the execution of this evaluation run and upcoming ones by just throwing an error using the fail system call. However none of both is very ellegant, maybe a keyword for exiting the script will be introduced in upcoming versions... In GSL there exists an own namespace for external JAVA injected variables. It is accessed by using @VARNAME
It contains external variables, like command line arguments. You cannot declare new variables in it, you can just read variables from it to check the command line parameters or other stuff. By default, there are four external variables:
The application itself can define other external variables. GMSI for example defines, the paths which are set in the GMSI.ini as external variables:
Example: Outputting some information: echo("GSL is used by the application \"" + @appName + \"", version: " + @appVersion + "\n");
echo("GSL version is " + @scriptVersion + "\n");
echo("Number of command line arguments passed to this script: " + size(@args)); There is a syntax for the simple use of arrays as stacks. The syntax is (like in PHP): arrayname[]
where <arrayname> is an array variable or a function returning an array.
This can also be used to store data in an array easily when you don't care about at which index it is stored.
When this expression is used on the left side of an assignment (lValue) it will push the value on the right (rValue) onto the stack, i.e. find the smallest integer key in this array that is greater than every other int key in the array (or 0 if no int key exists yet in this array) and assign the value to it. arrayname[] = valueToBePushedOntoTheStack;
When the expression is used in another context it will return the top of the stack without removing it (also called peek). echo(arrayname[] + ""); //echoes the top of the stack
When the expression is used in an [ref=unset]unset expression[/unset] it will remove the top element from the stack and return it (also called pop). unset(arrayname[]); //Removes the top element from the stack
echo("" + unset(arrayname[])); //Removes and echoes the top element from the stack
If pop or peek is used while the array has no int keys, they will return null (and pop won't remove an element since there is none).
Example: array a = array(); //Create an array which we will use as a stack
a[] = "a"; //push "a"
a[] = "b"; //push "b"
a[] = "c"; //push "c"
echo(a + ""); //Will output array([0] => "a" [1] => "b" [2] => "c")
echo(a[]); //Will output "a"
unset(a[]); //Remove a
echoln(a[]); //Will output "b"
unset(a[]); //Remove b
echoln(a[]); //Will output "c"
unset(a[]); //Remove c
echoln(a[]); //Will output "null");
unset(a[]); //Will remove nothing, since the stack is empty When you want to create multidimensional arrays you would have to use, something like that: array a = array(); //create the first dimension
a[1] = array(); //create another dimension at the index 1
a[1][6] =...; //Now we can use this dimension
This sucks however, because you will have to check if an entry already exists. If not, then create a new array for the entry you want to use. If you need even more dimensions, you will have even more work checking for and creating arrays.
So GSL offers you "Array Index Auto Creation". So if a multidimensional syntax is used as lValue (on the left side of an assignment) all missing arrays will be created, so you can use such a syntax: array a = array();
a[1][2][3] = "my 3d dimension entry";
The arrays at the index 1 and the array in the array at index 2 don't exit. So GSL creates them automatically when using such a syntax.GSL supports pointers. However these are just symbolic references to the variables, no real memory addresses, so you cannot use pointer arithmetics on GSL pointers. Everything else is possible like known from C. A pointer is defined like that: VARTYPE* POINTERNAME;
This defines a pointer to a variable of type <VARTYPE> which is named <POINTERNAME>.
You can then use the reference operator & to get a reference to a variable of type <VARTYPE> and use the indirection operator * to retrieve the value of the variable to which <POINTERNAME> points.
Example: int i = 5;
int* point = &i; //point now holds a reference to the variable i
*point = 255; //The variable onto which point points (i in this case) is set to 255
echo("" + i); //will echo 255 since it was set to this value using the pointer The EXPRESSION.type operator evaluates the expression and then stands for the type of the expression. It can be used in:
Examples: use in typecasts: var a = "f";
int i = 35;
var x = (a.type)i; //Casts i to string, since a contains an instance of type string
use in constructors: var a = array();
var x = a.type(); //Creates a new array, since a.type evaluates to the type array
use in instanceof expressions: var a = array();
var x = struct();
bool b = (a instanceof x.type) //False, since x.type evaluates to struct and a is not an instance of struct The EXPRESSION.strtotype operator evaluates the expression, casts its value to string and uses this value as a type. It can be used everywhere where you can also use the .type operator Example: var x = "array".strtotype(); //Creates a new array, since a.type evaluates to the type array
var y = ("string".strtotype)234;//Casts the int 234 to string This operator is somehow the opposite of the typetostr operator. It can be used in two contexts: It takes a type and returns a string containg the type's name: Example: string s = int.typetostr; //s contains now "int";
or it can take an expression. It evaluates the expression, gets its value's type and returns a string with this type's name: Example: string s = "sdfb".typetostr; //s contains now "string" since "sdfb" is of type string; This operator allows you to call a function which name is handed as a string. Syntax: EXPRESSION.call(PARAMLIST);
<EXPRESSION> is an expression which is evaluated and which has to return a value of type string. This string is then used as function name and called with the parameters specified in <PARAMLIST>, which is used like in normal function calls. Example: string s = "echo";
s.call("foobar"); //Calls the function echo with the paramter "foobar". Same as echo("foobar");
This can be used to store functions in arrays, by just storing their name or passing function names as parameters to other functions. |