Skip to content

New compiler's end user cheat sheet

ivan-mogilko edited this page Sep 27, 2024 · 10 revisions

This is an end-user friendly reference of the new script compiler's features and script syntax changes. Based on the information from Features of the new Script Compiler.

Structs

Nested structs

Both regular and managed structs can now contain nested regular or managed structs:

struct Pair { 
    int X, Y; 
};

struct TwoPairs {
    Pair p1;
    Pair p2;
};

managed struct ManagedStruct {
    TwoPairs tp;
    ManagedStruct* partner;
};

IMPORTANT: Initially there have been an engine limitation that prevented managed structs from containing other managed structs (pointers to structs) or managed arrays. This limitation is now removed in the latest version of ags4 engine.

Auto Pointers

You may now omit * symbol when declaring pointers to managed structs. So, for instance, instead of

ManagedStruct* obj;

you can write simply

ManagedStruct obj;

and compiler will guess that it's a pointer, as managed structs may only be used as pointers in script.

Implied this in struct functions

You can now omit this when writing struct functions and want to reference this struct's member variable or function:

struct Warrior {
    int Health;
    bool Dead;
    import function LooseHealh(int hp);
};

function Warrior::LooseHealh(int hp) {
    Health -= hp;
    if (Health < 0) {
        Dead = true;
    }
}

Attributes

You can now have extender attributes, that is - custom attributes attached to existing types, including builtin types.

Declaration examples:

attribute int AttrEx1(this struct S);
readonly attribute float AttrEx2(this struct S);
attribute float StaticAttrEx(static struct S);

attribute float ArrayAttrEx1[](this struct S);
readonly attribute float ArrayAttrEx2[](this struct S);
attribute float StaticArrayAttrEx2[](static struct S);

In all cases, you are still obliged to define the respective get_ and set_ functions with body (geti_ and seti_ for array attributes).

Arrays

Multi-dimensional regular arrays

You can have multi-dimensional regular arrays:

You can declare or equivalently,. .

// Declare two-dimensional array of size 5x3
int a[5, 3];
int b[5][3];

You can have any positive number of dimensions, e.g. int a[2][3][5][7][11];

Accessing elements is done similarily to declaration, that is:

int a[5, 3];
int e11 = a[1, 1];
int e23 = a[2][3];

Dynamic array's Length

Dynamic arrays now have Length property that returns their length:

int arr[];

arr = new int[10];

int length = arr.Length;

Multi-dimensional dynamic arrays

Multi-dimensional dynamic arrays are also known as "jagged arrays". In these each element of all dimensions except last one is a pointer to another dynamic array, and elements of last dimension are values of the array's type. All sub-arrays must be created individually, but also may have a independent length.

Jagged arrays must be declared with all dimension sizes undefined, like:

int dyn_arr[][][]; // a three-dimensional dynamic array

First you must create the "parent" array, then you may create sub-arrays for each of its elements. Each nesting level the sub-array has one dimension less. When you create actual arrays, only the first dimension must have a fixed size, and following dimensions still have undefined size:

int dyn_arr[][][];
// create 3-dimensional array of size 10
dyn_arr = new int[10][][];
// for the 5th element of the first dimension,
// create a 2-dimensional array of size 20
dyn_arr[5] = new int[20][];
// for the 6th element of the first dimension,
// create a 2-dimensional array of size 25
dyn_arr[6] = new int[25][];
// for the 15th element of the 5th element,
// create a 1-dimensional array of size 40
dyn_arr[5][15] = new int[40];
// finally we may access the actual integer values
dyn_arr[5][15][35] = 100;

Mixed arrays

Currently supported are regular arrays of dynamic arrays. This is done by declaring array which have first dimensions of fixed size, and last of undefined size. The regular dimensions "end" at the last fixed-sized dimension.

Following is a regular array of 10 dynamic arrays of int:

int mixed_array[10][];

Following is a regular multi-dimensional array, which contains pointers to multi-dimensional dynamic arrays:

int overcomplicated_array[10][20][][];

Dynamic arrays of regular structs

Dynamic arrays may now be used to store regular structs too, in addition to managed pointers:

struct Pair { 
    int X, Y; 
};

Pair many_pairs[];

many_pairs = new Pair[10];
many_pairs[0].X = 50;
many_pairs[0].Y = 60;

Variable definitions

You can now define global variables right after the struct or enum declaration, for example:

struct Pair { 
    int X, Y; 
} pair1, pair2, pair3; // declares 3 global variables of type Pair
enum TrafficLightColour {
    red,
    amber,
    green,
} col1, col2, col3; // declares 3 global variables of type TrafficLightColour

Functions

Functions can now be defined in any order within same script. So you can reference a function first and define it afterwards. This makes recursive algorithms much easier to code.

int  Func1(int a) {
    return 2 * Func2(a);    // uses Func2()
}

int Func2(int a) {    // defines Func2() and is below its first use (this used to be illegal)
    // some code here
}

You may still do forward declarations of a function, that is declare a function with import keyword before it is first used.

Compile-time evaluations

Compiler can now evaluate integer or float expressions at compile time whenever result is actually constant and can be calculated at compile time. This allows to use expressions where syntax normally requires a constant. For example:

int arr[7 * 9 + 1]; // the size of array will be calculated by compiler

Keywords

Extended use of const

const keyword now may be used to define compile-time constants, that is values that always stay the same. Only variable types allowed to be used with const are those that may be calculated at compile-time, that is: int and float.

const int MainMenuRoom = 301; // main menu room number
const float PI = 3.14;

NOTE: there was also a const string type, and it is still supported, but that's a special case, and it defines a unmodifiable string pointer, which may point to any kind of strings: old-style string, new-style String, and char array (e.g. to char text[100];).

Extended use of readonly

While const defines a compile-time constant, readonly defines a runtime constant. This means that the actual value may be determined at runtime, and be different each time, but the variable that stores it cannot be modified. You may have readonly global variables, local variables, function parameters, and struct attributes.

Two most common uses of a readonly variable are function parameters and local variables. This works as a "failproof" method to ensure that they are only assigned, but are never changed within the function by programmer's mistake. For example:

function MyFunc(readonly int param) {
    readonly int game_speed = GetGameSpeed(); // notice you can assign, but not change later
    
    param = 10; // compiler will error
    game_speed = 100; // compiler will error
}

NOTE: readonly only tags the variable as not-writeable, but it does not prevent the engine or another script from modifying it behind the scenes. For example, you may import a variable as readonly in one script module and as not readonly in another.

readonly struct attributes have another meaning: that defines attributes that can only be read (have getters, but not setters). This is left unchanged since the older AGS script rules.

fallthrough in switch

In switch, the compiler tries to find out whether code execution falls through the end of a non-empty case into the next case. Such "fall through" is usually unintended (a break; is missing). So the compiler warns when it detects the situation. In order to shut up the warning and declare that the fall through is intended, you can code fallthrough; immediately in front of the next case.

Examples:

switch(inventory_item)
{
case iBlueCup: // no statements inside, falling through, but is fine
case iYellowCup:
    player.Say("I fill the cup with water.");
    break; // break present, all is good
case iVase:
    player.Say("I fill the vase with water.");
    // missing break, will yield a warning
case iBigTowel:
    player.Say("That's my favourite towel. Oh well...");
    fallthrough; // fallthrough keyword tells compiler that it's intended
case iSmallTowel:
    player.Say("The towel is now wet.");
    break;
default:
    player.Say("I think this is useless.");
}

Operators

Pre-Increment and pre-decrement operators

Previously compiler supported only post-increment and -decrement: x++ and x--; The new compiler also supports pre-increment and -decrement: ++x and --x;

The difference between these is that if this operation is used in another expression:

  • With post-increment/decrement: the old variable's value will be used in calculation, and variable changed afterwards.
  • With pre-increment/decrement: the variable will be changed and its new value will be used in calculation.

For example:

int x = 5;
int a = x++; // a = 5, x becomes 6
int b = ++x; // x becomes 7, b = 7

Bitwise negation

Bitwise negation is ~ operator, which converts each bit in the value to its opposite: 1 to 0 and 0 to 1.

Ternary conditional operator

The classic ? : ternary operator is now supported. Standard form: foo = (x > 5) ? 1 : 2; means that foo is set to 1 if (x > 5), to 2 otherwise. Can leave out the second part of a ternary so that "?" and ":" go right next to each other. For instance, int foo = bar ?: 15; This means that foo is normally set to bar, but if bar is 0 or null then foo is 15 instead. Particularly handy for specifying a default in case a pointer variable turns out to be null. (This is the same functionality as the ?? operator in C#.)

Expressions

Function calls as partial expressions

If the function returns a managed object, you can now directly access properties and functions of returned object in the same expression.

For example: Character.GetAtScreenXY(x, y).Walk(100, 200);

Also sequences can now be undefinitely long.

Concatenated string literals

String literals that are next to each other (separated only by whitespace) are treated as if they were one concatenated string literal.

player.Say("Hello" " world");  // same as "Hello world"