Advanced Vector Translator: documentation (page 4)

[Back] [Contents]

operation priority

Like many other programming languages, in AVT operations take priority. Parenthesis allow you to force change operation priority.

Priority Operation
1. new operations new
2. Unary postfix [] () . ++ --
3. Unary prefix ~ ! + - ++ -- @@@@ #### ^^^^ ++++ ---- @@..@@ ##..## ^^..^^ ++..++ --..-- (type)
4. Multiplication and division * / % // %% **** ***^ ***| //// **..** **..*^ **..*|
5. Addition and subtraction + - ++++ +++| +++# ---- ---| ---# ++..++ ++..+| ++..+# --..-- --..-| --..-#
6. Shift >> >>> << >>>> >>>>> <<<< >>..>> >>..>>> <<..<<
7. Relational > >= < <= >>|| >=|| <<|| <=|| >>|..| >=|..| <<|..| <=|..|
8. Comparison == != ==|| !=|| ==|..| !=|..|
9. Bitwise AND &
10. Bitwise XOR ^
11. Bitwise OR |
12. Logical AND &&
13. Logical OR ||
14. Conditional ?:
15. Assignment = *= /= %= //= %%= ****= ***^= ***|= ////= **..**= **..*^= **..*|= += -= ++++= +++|= +++#= ----= ---|= ---#= ++..++= ++..+|= ++..+#= --..--= --..-|= --..-#= >>= >>>= <<= >>>>= >>>>>= <<<<= >>..>>= >>..>>>= <<..<<= >>||= >=||= <<||= <=||= >>|..|= >=|..|= <<|..|= <=|..|= ==||= !=||= ==|..|= !=|..|= &= ^= |=

control flow operators

The variable declaration starts with type of variable followed by an identifier, optional initialization and a semicolon. Examples:

int i;
int j = 7;

It is recommended that a variable be initialized immediately, because AVT doesn’t check whether value was assigned to a variable.

The dispose operator removes from heap an object created by the new operation: a structure or an array. After dispose keyword followed by an expression refers to a structure or array descriptor, then a semicolon. Examples of creating and deleting objects:

Window wnd = new Window {
    left = 100,
    top = 100,
    width = 640,
    height = 480
};
int[][] iarray = new int[][] {
    new int[] {
        5, -3, 0
    },
    new int[10]
};
dispose iarray[0];
dispose iarray[1];
dispose iarray;
dispose wnd;

The with operator allows you to use structure fields as local variables. After with keyword followed by separated by comma expressions of «reference to a structure» type in parenthesis, then a control flow operator or block in curly brackets. Usage example:

Window wnd;
with(wnd = new Window)
{
    left = 100;
    top = 100;
    height = (width = 640) - 160;
}

The standard if operator allows you to create branch by condition. After if keyword followed by an expression of boolean type in parenthesis, and a control flow operator or  block in curly brackets. Then, if necessary, else keyword can be written followed by another control flow operator or block in curly brackets. Examples:

if(negative)
{
    value = -value;
}
if(order < 0)
{
    buf[len++] = '-';
    order = -order;
} else
{
    buf[len++] = '+';
}

The standard switch operator allows you to create branch by expression of int type (short type in 16-bit programmes). After switch keyword an expression of int type (or short type) in parenthesis is written followed by a block in curly brackets inside which are control flow operators some of which are labelled with a case keyword, int constant expression and a colon, or with default keyword and a colon. In this case, the first control flow operator must be labelled. For each switch only one default is allowed. Example:

public xvector setElementx(xvector value,
        int index, float element)
{
    switch(index)
    {
    case 0:
        return new xvector { element, value[1],
                value[2], value[3] };
    case 1:
        return new xvector { value[0], element,
                value[2], value[3] };
    case 2:
        return new xvector { value[0], value[1],
                element, value[3] };
    case 3:
        return new xvector { value[0], value[1],
                value[2], element };
    default:
        throw CompoundIndexOutOfBoundsException;
    }
}

The for operator allows you to create loops with precondition. After for keyword, in parenthesis, separated by semicolon, is written:

  • initialization (expressions and variable declarations are separated by comma);
  • repeat condition (has boolean type);
  • step (expressions are separated by comma).

After parenthesis followed by control flow operator or block in curly brackets.

The initialization of for loop in AVT differs from other programming languages in that it is possible to declare local variables of different types. Example:

byte[] palette = new byte[3 * 0x40];
for(short j = 0, byte i = 0; i < 0x40; i++)
{
    palette[j++] = i;
    palette[j++] = i;
    palette[j++] = i;
}

The standard while operator is the same for operator, but without initialization and step. Here’s similar example, but using the while operator:

byte[] palette = new byte[3 * 0x40];
{
    short j = 0;
    byte i = 0;
    while(i < 0x40)
    {
        palette[j++] = i;
        palette[j++] = i;
        palette[j++] = i;
        i++;
    }
}

The standard do operator allows you to create loops with postcondition. After do keyword followed by control flow operator or block in curly brackets, while keyword, repeat condition of boolean type in parenthesis and semicolon. Here’s similar, but not identical, example of do operator usage:

byte[] palette = new byte[3 * 0x40];
{
    short j = 0;
    byte i = 0;
    do
    {
        palette[j++] = i;
        palette[j++] = i;
        palette[j++] = i;
    } while(++i < 0x40);
}

All control flow operators and blocks in curly brackets can be marked with a label. This label is written as an arbitrary identifier and a colon.

The break operator allows you to exit the switch, for, while, do operators. Maybe with label, in this case, the exit will be made from control flow operator or block in curly brackets marked with a label whose identifier is specified after break keyword. Syntax:

break;

break <label>;

The continue operator allows you to begin a new iteration of for, while and do loop operator. Like break operator, maybe with label, however the for, while or do loop operator must be marked with this label. The syntax is similar to break operator:

continue;

continue <label>;

Any expression that lastly performs a variable modify or a function call is also a control flow operator.

A separate semicolon is an empty control flow operator. Usually it is replaced by empty block in curly brackets.

The standard return operator exits the current function with or without a value return. Recall that if a function has a return value, then it must have a return operator to value return. Syntax:

return; /* for void functions and entry point */

return <expression>;

The throw operator raises an exception. CPU looks call stack and find try…catch operator in nearest function which can be catch raised exception. Control is passed to first catch block which contains an exception handler. Syntax:

throw <exception identifier>;

The throw operator can be used in function that have a return value, in addition or instead return operator, if throw operator returns function.

The try…catch operator has following syntax:

try
{
    <operators>
}
catch(<exception identifier>)
{
    <operators>
}
catch(<exception identifier>)
{
    <operators>
}
<…>
catch(<exception identifier>)
{
    <operators>
}

The try…finally operator ensures that finally block code is executed when exiting try block. Has following syntax:

try
{
    <operators>
}
finally
{
    <operators>
}

using the assembler

This section describes features of using assembler in AVT programmes. Note that functions in assembler: you use the syntax of flat assembler, not AVT. Therefore, even comments have to start with semicolon. Integer numbers are best written in hexadecimal notation with dollar prefix «$». AVT produces position-independent code for 64-bit programmes, so lea instruction will in some cases be preferable to mov.

Function arguments. To access function argument, just put a period character and write its identifier, for example:

fld     tbyte[.x] ; load x argument
                  ; into st0 register

Programme elements. To access programme element (constants, global variables, exceptions and functions), you need to specify namespace in which the element is located, then put a period character and write its identifier, for example:

fld     Math.E ; load E constant
               ; from Math namespace
               ; into st0 register

Structure fields. Here you should also use fully qualified name, for example:

mov     eax, [rax+System.Array.length]
; read array length

Labels and local symbols. They should be declared with «period» prefix, and their identifiers should not coincide with identifiers of function arguments and other labels and local symbols.

Numeric names of registers in 64-bit programmes. You can use standard names of general-purpose registers together numeric names which are replaced by standard ones during assembling. There are numeric names (standard name indicated in brackets):

r0 (rax)    r0d (eax)    r0w (ax)    r0b (al)
r1 (rcx)    r1d (ecx)    r1w (cx)    r1b (cl)
r2 (rdx)    r2d (edx)    r2w (dx)    r2b (dl)
r3 (rbx)    r3d (ebx)    r3w (bx)    r3b (bl)
            r4d (esp)    r4w (sp)    r4b (spl)
            r5d (ebp)    r5w (bp)    r5b (bpl)
r6 (rsi)    r6d (esi)    r6w (si)    r6b (sil)
r7 (rdi)    r7d (edi)    r7w (di)    r7b (dil)

Assembler functions in 64-bit programmes. It is not recommended to call other functions from such functions because of absence necessary exception handlers in assembler functions. Raising exception in such functions is performed by following instructions:

; for functions with one or more arguments
lea     r1, [namespace.exception]
mov     r2, [rbp+$08] ; r2 = return address
dec     r2
leave
jmp     r15
; for functions without arguments
lea     r1, [namespace.exception]
mov     r2, [rsp] ; r2 = return address
dec     r2
jmp     r15

Naturally, the «namespace.exception» words should be replaced with fully qualified name of exception, for example, System.ArrayIndexOutOfBoundsException.

If function in assembler still calls another function or itself, then assembler keyword should be replaced with pureassembler, and body of the function should look like this:

{
        push    r15
        lea     r15, [.L.EH]
        enter   $0000, $00
        <your code>
        leave
        pop     r15
        ret     <argument size>
.L.EH:  emms
        leave
        pop     r15
        pop     r2
        dec     r2
        jmp     r15
}

Raising exception code now changes to following:

lea     r1, [namespace.exception]
jmp     .L.EH

The r15 register used to store address of current exception handler and should not be used in assembler functions. The rsp and rbp registers used to work with stack. The rest registers can be used freely.

How do I calculate function’s argument size? If the function has no arguments, then their size is zero. Otherwise, you need to sum sizes of all arguments, aligning them along 8-byte boundary. For example, you need to calculate argument size of such function:

public pureassembler int testFunction(int a, real b, ultra c, byte[] d)
Argument’s type Size Aligned size
int 4 8
real 10 16
ultra 16 16
byte[] 8 8
Total 48

Therefore, such function will be returned by ret $30 instruction.