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.