The SQX language extends the SQF language primarily by adding control structures that allow object oriented development. This means that SQX allows plain SQF programming, but also introduce support to organize the code into classes, inheritances and interfaces. SQX also introduces a small toolbox of some optionally available new commands and structures that may be helpful. The following language reference explains all ways that SQX differs from SQF.
Follow these steps to create a mission and a TypeSqf project with some pre written SQX files.
The #region command is used together with the #endregion to create a custom folding region in the editor. It is used as a preprocessor command (without semicolon), it does not affect the TypeSqf analyzer, and it does not have any compiler output.
#region [RegionName] Some code ... #endregion
Custom regions are also possible to use in SQF files. However, since SQF is not compiled by TypeSqf you need to write them in a line komment:
// #region [RegionName] Some code ... // #endregion
#region Global Mission Variables Abc_EnemyCount = 10; Abc_AlwaysRespawnAtBase = true; Abc_UseRevive = true; #endregionExample: A folding region in SQF:
// #region Global Mission Variables Abc_EnemyCount = 10; Abc_AlwaysRespawnAtBase = true; Abc_UseRevive = true; // #endregion
The variable _base is a predefined class field that is declared automatically in all overrided classes, and it is used to reference members of the base class of the current class.
[Arguments] call _base.[MethodName]; // Calling a method of the base class
While it is possible to use a property on the base class, by referencing it with the _base field like below, it is considered bad practice. The result will be exactly the same as referencing it with _self, but note that a public or protected property with the same name also references the very same value.
_base.[PropertyName]; // Bad practice _self.[PropertyName]; // Good practice
public class Building { protected virtual method BuildRoof() { }; protected virtual method BuildDoor() { }; // Builds a general building. public virtual method BuildAll() { call _self.BuildRoof; call _self.BuildDoor; }; }; public class House : Building { protected virtual method BuildWindows() { }; // Builds a house with windows. public override method BuildAll() { call _base.BuildAll; // Build roof and door call _self.BuildWindows // Extend the house with windows; }; };
The variable _self is a local variable that is declared automatically in all classes, and it is used to reference members of the current instance of the class.
_self.[PropertyName] = [Value]; // Setting a property value [Variable] = _self.[PropertyName]; // Using a property value [Arguments] call _self.[MethodName]; // Calling a method
public class Tower { public constructor { call _self.InitializeHeight; // Using self to call method }; private method InitializeHeight { // Using self to set property value _self.Height = 100; // Sending current instance as parameter to a global function [_self] call somePublicFunction; }; public property Scalar Height { get; private set; }; };
The as keyword is used to explicitly determine the type of a variable or an expression, and it may be used in a number of ways.
1. It may be used to set a variable's type in a declaration:
private "_var" as Group;
When a variable is declared in this way the TypeSqf analyzer will not allow it to be assigned a value of any other type than Group or 'Any'. The latter is a data type that is used when the actual type is unspecified or unknown.
2. It may be used to cast a value type, e.g. in an assignment. A variable assigned like below is allowed to be used as a 'Building' (if its type is not explicitly set). The 'as' keyword here tells the TypeSqf analyzer that _anotherVar actually is of type Building, in case its type is unknown.
_myVar = _anotherVar as Building;
3. Is may be used in a forEach declaration to state the type of the iteration variable _x. If the "as" is left out in this example, the analyzer do not know how that _x is of type House, and it will not approve the reference to the method NoOfDoors that is being used.
{ hint str _x.NoOfDoors; } forEach _myArray as House
public class Vehicle {}; public class Civilian { public constructor { _self.IsAlive = true; }; public property Boolean IsAlive { get; set; }; }; civs = [new Civilian, new Civilian, new Civilian]; // A single variable of type Civilian private "_civilian" as Civilian; // An array declaration private ["_age" as Scalar, "_name" as String]; // It may also be used in the parameter declaration of 'params': params ["_vehicle" as Vehicle]; // Or a params parameter with default value: params [["_vehicle" as Vehicle, classNull]]; // Or as a type specifyer in a count statement: myCount = { _x.IsAlive } count civs as Civilian; // Or as a type specifyer in a forEach statement: { if (_x.IsAlive) then { hint "he is alive!"; }; } forEach civs as Civilian;
The class keyword is used to define a class (an object).
A class may contain one constructor, fields, properties and methods. The outline of a class looks like this:
public class [Name] [: [Base Class,] Interfaces ] { [private [static] fields [ ... ]; ] [public constructor[(Parameters)] { ... }; ] [public|protected [static] property [Type] [Name] { get; [private|protected] set; }; ] [public|protected|private [virtual|override|static]] method [Type] [Name][(Parameters)] { ... }; ] };
A class can implement one base class and/or one or more interfaces.
A class may only be public.
Since version 0.67 the SQX language supports true inheritance and object oriented programming, basically meaning that it supports necessary basic functionality for classes to inherit each other, to use common resources, to use virtual and overridable methods, as well as controlling the access to members by using the private, protected and public keywords.
For the basic understanding of object oriented programming, se this Wikipedia page.
public class TinyClass {};Example: Working class with all inner symbols represented:
public class Person { private fields ["_mName" as String]; public constructor("_name" as String, "_age" as Scalar) { _mName = _name; _self.Age = _age; }; public property Scalar Age { get; private set; }; public method IncreaseAge("_ageDiff" as Scalar) { _self.Age = _self.Age + _ageDiff; }; };Example: Using the class Person:
private ["_person" as Person, "_name" as String, "_age" as Scalar]; _person = ["David", 25] new Person; // Create new instance [10] call _person.IncreaseAge; // Increase age by 10 _age = _person.Age; // Get age from personExample: Class inheritance
public class Meal { public virtual method String GetWeight() { return "450 grams"; }; }; public class Breakfast : Meal { public override method String GetWeight() { return "240 grams"; }; }; private ["_meal" as Meal]; _meal = new Breakfast; hint call _meal.GetWeight; // Will show "240 grams"
The classNull keyword is used to give a variable a null value. The value can then be tested with the 'isNull' command.
public class Person {}; private ["_person" as Person]; // Before this line, _person is undefined _person = classNull; if (isNull _person) then { hint "This person is null!"; };
The constructor keyword is used to define a constructor in a class. A class may have zero or one constructor defined.
public constructor[(Parameters)] { ...; };
The purpose of a constructor is to initialize a class instance upon create, i.e. when the 'new' statement is executed. It must not have a return value, since the return value of the 'new' statement is the new instance of the class. That means, always terminate the last statement in a constructor with a semicolon.
A constructor may only be public.
The classic way to specify the constructor's parameter (prior to TypeSqf 0.64) is to use the "params" script command on the first line of the constructor, like this:
public constructor { params ["_name" as String, ["_yearsInBusiness" as Scalar, 0]]; ... };In the example, the first parameter "_name" is required, and the second "_yearsInBusiness" is optional, see the official Arma 3 params documentation.
Since TypeSqf 0.64, however, the params variable content can instead be moved to the header (similar to other languages). The syntax for the parameters in the header is exactly like the syntax in the params list.:
public constructor("_name" as String, ["_yearsInBusiness" as Scalar, 0]) { ... };
Example: Defining and instantiating a class with a constructor:
public class Vehicle { public constructor { hint "New vehicle created!"; }; }; pubVar = new Vehicle; // "New vehicle created!" shown on screen.
Example: Defining and instantiating a class with constructor parameters:
public class Vehicle { public constructor("_className" as String) { hint "A new vehicle of type " + _className + " created!"; }; }; pubVar = ["B_Quadbike_01_F"] new Vehicle; // "New vehicle created!"
The enum keyword is used to define an enum type. An enum (i.e. enumeration) is a collection of enumerated values.
public enum [Name] { [Value1 [ = x]][, Value2 [= y]] ... };
The purpose of an enum is to translate enumerable constants to understandable concepts for the developer. It can in many ways be compared to a constant. Imagine you want to use the arrow keys in a mission, but instead of remembering some imagined and distinctive number value for them, you create the following enum:
public enum Keys { Left, Up, Right, Down };
Now you will only need to remember the name of the enum, and the enum also defines all possible and impossible values.
As an alternative syntax, and if you want to translate the enum value to a specific value, you may declare the type and give the values a number. Say you also somewhere need the ASCII values for the arrow keys:
public enum Keys { Left = 37, Up = 38, Right = 39, Down = 40 };
Note that the latter version with explicitly set values will also let you use the enum values in SQF contexts. The usages of an enum type compiles directly to its scalar value. If not set explicitly, the values will be an enumeration starting from 1.
namespace MyMission { public enum VehicleType { Land, Air, Sea }; public class Vehicle { public constructor("_type" as VehicleType) { _self.Type = _type; }; public property VehicleType Type { get; set; }; }; private ["_vehicle" as Vehicle]; _vehicle = [VehicleType.Land] new Vehicle; if (_vehicle.Type == VehicleType.Air) then { // Will be false hint "This is an air vehicle."; }; };
The fields keyword is used to define private fields in a class. A 'field' (or a class variable) is a private variable that can be accessed by all members (the constructor and all methods) of a class.
private fields ["[Name]" [as [Type]], ... ];
The requirement for naming of a field is the same as for naming a local variable. However, it may be good practice to name them so that they never interfere with local variables in the constructor or in the methods. The suggestion is to add an 'm' (as in member) to name the variables. E.g. the local variable
_myVarwould form the corresponding private field
_mMyVar
A field may only be private. If you need a public field, use a property.
public class Person { private fields ["_mName", "_mAge"]; public constructor("_name", "_age") { _mName = _name; _mAge = _age; }; }; // Creating an instance of class person person = ["John", 30] new Person;
Interface management in SQX is used to template different aspects (or views) of class objects. It can be seen as a way to declare different behaviors of objects. Classes implementing interfaces are then fully compatible with methods and functions that manages these behaviours.
The behaviour of interfaces are much similar to interfaces as we know them from languages such as Java and C#.
An interface declaration is similar to a class declaration, but much simpler. It may only contain properties and methods, and it only provides declarative headers and no bodies. All members are regarded as public. Thus the keywords public and private need not be used (and are not allowed). The outline of an interface looks like this:
public interface [Name] { [property [Type] [Name] { get; [set;] }; ] [method [Type] [Name](Parameters); ] };
While the name of an object (a class) should be a noun, the name of an interface should be an adjective. Furthermore, the name of an interface is also suggested to start with a capital 'I', to clearly distinguish them from classes.
Unlike methods, the parameter list of an interface method is not optional (since TypeSqf 0.64). The syntax of the parameter list is the exact same as for the script command params.
While a call to a method of a class known by the editor is compiled to an ordinary global function call, that is not the case with calls to methods of unknown classes (interfaces). Since the exact global function name is unknown to the editor in the case of interfaces, the calls are being compiled with help of meta data in runtime. So if high performance is needed due to frequent function calls, you do better perform the method call to the actual class rather than to an interface.
namespace Units { public interface ITeleportable { method TeleportToPos("_pos" as Array); }; public class Soldier : ITeleportable { public property Object UnitObj { get; set; }; public method TeleportToPos("_pos" as Array) { _self.UnitObj setPos _pos; }; }; // If you want to use this function and send in your own class, // then have the class implement the ITeleportable interface. fnc_PerformTeleport = { params ["_object" as ITeleportable, "_pos" as Array]; [_pos] call _object.TeleportToPos; }; };
The is operator performs a runtime check on whether a variable is of a certain type or not. The return type of the is operator is Boolean.
[Variable|Literal] is [Type]
The "is" keyword can be used to test classes, interfaces and native types.
public interface IDemolishable { ... }; public class Tower : IDemolishable { ... }; ... if (_myTower is Tower) then {}; // true if _myTower is not null if (_myTower is IDemolishable) then {}; // true if _myTower is not null if (_height is Scalar) then {}; // true if _height is a Scalar.
If a class variable is set to null (classNull) the is operator always returns false, even if the variable's type is set explicitly in declaration.
_myTower = classNull; _result = (_myTower is Tower); // false.
Note that only variables and literals can be checked. Not expressions.
namespace Vehicles { public class Car { public property Scalar Passengers { get; set; }; }; public class Truck { public property String Cargo { get; set; }; }; globalFnc = { params ["_vehicle"]; if (_vehicle is Car) then { hint str (_vehicle as Car).Passengers; } else { hint (_vehicle as Truck).Cargo; }; }; };
A method is a function that belongs to a class. It executes just as an ordinary function, but it also has access to the fields and properties of the class.
[public|protected|private [virtual|override|static]] method [Type] [Name][(Parameters)] { ... };
A method may be public, protected or private. If it is private, only methods within the class have access to it, if it is protected only methods with the inheritance hierarchy have access to it, and if it is public it may be accessed from outside of the class.
If the return type is left out, the method's return value type will be 'Any'. However, this may be subject for changes in the future due to ongoing discussions.
The classic way to specify the method's parameter (prior to TypeSqf 0.64) is to use the "params" script command on the first line of the method body, like this:
public method Scalar RateEmployee { params ["_name" as String, ["_yearsInBusiness" as Scalar, 0]]; ... };In the example, the first parameter "_name" is required, and the second "_yearsInBusiness" is optional, see the official Arma 3 params documentation.
Since TypeSqf 0.64, however, the params variable content can instead be moved to the header (similar to other languages). The syntax for the parameters in the header is exactly like the syntax in the params list.:
public method Scalar RateEmployee("_name" as String, ["_yearsInBusiness" as Scalar, 0]) { ... };
public class Signal { public constructor { call _self.SoundBeep; // OK! ["TUUUT!"] call _self.SoundCustom; // OK! }; public method SoundBeep { hint "BEEP!"; }; private method SoundCustom("_sound" as String) { hint _sound; }; }; private "_signal" as Signal; _signal = new Signal; call _signal.SoundBeep; // OK! ["TOOOT!"] call _signal.SoundCustom; // Not OK - method is private!
A namespace is used to keep class names unique. A common problem for developers is that objects and function names collide with other developer's work. A common and primitive form of namespace is to add a personal prefix to function names. In SQX this can better be solved with namespaces. A namespace is declared like this:
namespace [Name] { ... };
Consider the example class Helicopter below:
namespace LordPhilip.Vehicles { public class Helicopter { public method LandAtBase { ... }; }; };
The Helicopter class above can be referenced in three ways.
1. The usual scenario would be to add a using directive at the beginning of the file. This tells the TypeSqf analyzer that all classes in the specified namespace can be used without specifying the namespace path on each occurrence.
using LordPhilip.Vehicles; myChopper = new Helicopter;
2. Within the same namespace (or a namespace with the same name), only the class name needs to be specified (unless there is no conflict with a class in another used namespace.).
namespace LordPhilip.Vehicles { myChopper = new Helicopter; };
3. The class can always and in any context be referenced by specifying the full namespace path and class name. This is useful if there are conflicting class names in other used namespaces.
myChopper = new LordPhilip.Vehicles.Helicopter;
The namespace declarations do not need to be unique. If you want to add a class to an existing namespace, simply surround the class with a namespace declaration that has the desired name.
All classes and namespaces that are included in the project are considered known to the TypeSqf Editor. Note that there is no question about which files that are included by files. TypeSqf assumes that all classes that can be found in the project tree is possible to use anywhere. That might not be the case. The user needs to manage so that all classes have been included and executed before being referenced.
Namespaces are independent of file structures. However, it is recommended to have the namespace names organized as the folders and files in the project. E.g. the content in the file MyRevive/Start/ReviveOptions.sqx would be written in a namespace named MyRevive.Start.
NOTE: The only code affected by namespace is SQX classes, and not public variables and functions. In future releases also public variables and functions may be affected.
namespace LordPhilip.Vehicles { public class Helicopter { public method LandAtBase { hint "Landing at base."; }; }; public class PieceOfFreight {}; };Example: The file Scissorman/Air/Helicopter.sqx in the mission project:
namespace Scissorman.Air { public class Helicopter { public method AttackPosition("_position" as Array) { hint "Attacking position " + str _position; }; }; };Example: The file init.sqx that are using the classes above:
call compile preprocessFile "LordPhilip/Vehicles/Helicopter.sqx"; call compile preprocessFile "Scissorman/Air/Helicopter.sqx"; using LordPhilip.Vehicles; using Scissorman.Air; namespace TheMission { // The PieceOfFreight class is known because of the "using" above. private "_freight" as PieceOfFreight; // The two helicopters' names are conflicting, so here we need // to specify the full namespace path for each. private "_freightChopper" as LordPhilip.Vehicles.Helicopter; private "_attackChopper" as Scissorman.Air.Helicopter; _freightChopper = new LordPhilip.Vehicles.Helicopter; _attackChopper = new Scissorman.Air.Helicopter; call _freightChopper.LandAtBase; [getMarkerPos "attackMarker"] call _attackChopper.AttackPosition; };
The property is essentially a public variable, but it may have getters and setters of different kinds of access (public/private).
[public|protected [static]] property [Type] [Name] { get; [protected|private] set; };
The purpose of a property, in addition to storing a variable value, is to make class variables available from contexts outside of the class. In addition, it makes the syntax easier and more convenient than get and set methods.
A property may only be public or protected, but its setter may follow the accessability of the property (set;) or may be protected (protected set;) or private (private set;).
If the return type is left out, the property's value type will be 'Any'. However, this may be subject for changes in the future due to ongoing discussions.
public class Dictionary { public constructor { _self.Name = ""; // OK! _self.Count = 0; // OK! }; public property String Name { get; set; }; public property Scalar Count { get; private set; }; }; myDictionary = new Dictionary; myDictionary.Name = "From A to Z"; // OK! myDictionary.Count = 10; // Not OK - property has a private setter!
The return statement is used to break the execution in a named scope (a function or a method) and return a value to the caller, and is well known from languages such as C++, C# and Java (and a lot more).
return [Value|Expression];
The purpose of the return statement is to return values from functions and methods in a clear and convenient way. The use of return has the compiler define the scope name and then translates the return statement to script command 'breakOut'.
If the function or method is not supposed to return a value, then the return statement should simply be terminated with a semicolon.
public class Ship { public method Scalar GetShipAge { return 10; // Return a value to caller. }; public method StartEngine("_engineId" as Scalar) { { if (_mEngine == _engineId) then { // Set engine as started return; // Break execution and return Nothing. } } foreach _mEngines as String; }; };
The keyword "static" is used to create static fields, properties or methods in classes. A static member does not belong to a certain instance, but should be thought of as a member shared across all instances, or rather, a single instance declared directly on the class definition. A static method does not use any non static members (like instance fields, instance properties and instance methods) of the class, and can therefore be called without having to instantiate the class first.
[public|private] static method [Type] [Name] { ... };
A static method can be called from .SQF context by replacing the dots in its full name with underscores (see example).
A static method is just like an ordinary global function, but with one important difference - it belongs to a class. Technically a global function is just as good as a method, but with the object oriented approach you would want all methods to belong to an object, and in that way add context and meaning to the method name.
For example, a Math class may be used to bundle up methods that do different types of calculations, but these methods do not have anything in common except for that. And you do not want to write a lot of code to instantiate the class before use:
private ["_math" as Math]; _math = new Math; _roundedValue = [_value] call _math.Round;
So if the methods are static you can simply use them with a call like the following:
_roundedValue = [_value] call Math.Round;
namespace Mission { public class Tank { // Static method that blows up the provided tank. public static method BlowUpTank("_tank" as Tank) { hint "BOOM!"; }; }; myTank = new Tank; // Blow up tank from insie the namespace [myTank] call Tank.BlowUpTank; }; // Blow up tank from outside the namespace [myTank] call Mission.Tank.BlowUpTank;Example: Blowing up the tank from an editor trigger (or from any SQF context):
[myTank] call Mission_Tank_BlowUpTank;
The using keyword is used to specify the use of a bundle of classes, so that they can be referenced without the developer having to specify the full namespace path on every time.
using [Namespace];
The using directive is usually written at the top of a file, but beneath eventual includes. It is allowed to write them further down in the file, and in that case the using will not be valid above the using statement. However, a using directive is always valid for the rest of the file, and cannot be removed.
namespace Scissorman.Air { public class Helicopter { public method AttackPosition("_position" as Array) { hint "Attacking position " + str _position; }; }; };Example: The file init.sqx in the mission project:
call compile preprocessFile "Scissorman/Air/Helicopter.sqx"; using Scissorman.Air; // The Helicopter's full class name is "Scissorman.Air.Helicopter". // Since the using above, simply "Helicopter" will be enough. private ["_chopper" as Helicopter]; _chopper = new Helicopter; call _chopper.LandAtBase;
The var keyword is used for inline local variable assignement declarations, very similar to SQF's private.
Like for private the variable's type is read out from the assignement, but unlike private the variable's type is set permanently instead of temporary.
private _myNumber1 = 10; var _myNumber2 = 10; _myNumber1 = "Twelve"; // OK! - the type will change to String. _myNumber2 = "Twelve"; // Not OK! the variable's type is Scalar.
The virtual keyword tells the compiler (and the developer) that a method is possibly overrided on a sub class of the current class.
[public|protected] virtual method [ReturnType] [MethodName][(Parameters)];
When a method is overrided in the subclass (using the override keyword), the method on the sub class (i.e. the class that overrides) will be called instead of the original method on the base class. The logic in the original base class method can still be executed by calling it using the _base field that is a reference to the base class.
A call to a virtual method is compiled differently. A call to a non virtual method is compiled as a direct call to a global function, while a call to a virtual method requires a runtime compile trick that has the sub class' method called instead. This affects performance slightly, and may need to be considered when optimizing heavily iterated code.
Only methods marked as virtual are possible o override. A method that is not marked as virtual will still exist on a sub class, but its behavior cannot be altered.
public class Building { protected virtual method BuildRoof() { }; protected virtual method BuildDoor() { }; // Builds a general building. public virtual method BuildAll() { call _self.BuildRoof; call _self.BuildDoor; }; }; public class House : Building { protected virtual method BuildWindows() { }; // Builds a house with windows. public override method BuildAll() { call _base.BuildAll; // Build roof and door call _self.BuildWindows // Extend the house with windows; }; }; public class Garage : Building { protected override method BuildDoor() { // This door is built extra wide. }; // Builds a garage with extra wide door. public override method BuildAll() { call _base.BuildAll; // Build roof and extra wide door }; };