Precedence in ARMA 3 SQF
ianbanks |
One of the oddities of ARMA 3 scripting is the lack of any discussion or documentation
on precedence. Although the basic mathematical operators such as +
and / follow the regular rules—the ones you learn in school
—those rules doesn't explain what code such as the following will return:
1 min 3 * 2
Hopefully, however, this table will:
Lowest Precedence | 1 | || or |
| 2 | && and |
| 3 | == != > < >= <= >> |
| 4 | All other binary operators. |
| 5 | else |
| 6 | + - max min |
| 7 | * / % mod atan2 |
| 8 | ^ |
Highest Precedence | 9 | All unary operators. |
Unary operators—functions such as getPos that take a single argument on their
right hand side—and binary operators—those that take an argument on either side
like setPos—are the fundamental building blocks of the SQF language and
make up practically the entire language, including parts like if statements and for loops.
When ARMA evaluates code, it calculates the higher precedence operators first. If two adjacent
operators have the same precedence, or are the same operator, it calculates the result from left to right—which also
matches the conventional mathematical rules:
Expression | Result |
1 - 1 - 1 | -1 |
(1 - 1) - 1 | -1 |
1 - (1 - 1) | 1 |
[1, 0, 0] vectorDiff [1, 0, 0] vectorAdd [1, 0, 0] | [1, 0, 0] |
[1, 0, 0] vectorDiff ([1, 0, 0] vectorAdd [1, 0, 0]) | [-1, 0, 0] |
Learning and applying the detailed precedence rules of any language isn't always a good
idea. Often code is easier to read when extra parenthesis are used, even if you do know the rules
by heart (and more so for anyone else that hasn't studied them). With that in
mind, there are a few key rules worth remembering:
Unary operators always have the highest precedence. A unary operator on either the left or the right hand side of a binary operator will never require parenthesis around the name and argument. For example, the following are equivalent:
getPos _a vectorFromTo getPos _b
(getPos _a) vectorFromTo (getPos _b)
As are:
getPos leader _a vectorFromTo getPos leader _b
(getPos (leader _a)) vectorFromTo (getPos (leader _b))
- The logical operators || and && have the lowest precedence, which means when using them in the condition of—for example—if, while or waitUntil, parenthesis aren't required around each comparison:
if (_x < 1 || _y < 2) exitWith { }
- The || operator is—however—lower precedence than &&, as is the case in almost all computer languages.
The arithmetic operators have a higher precedence than practically all other binary operators, which is why parenthesis are required in:
([1] select 0) + 1
Without the parenthesis, the expression would be:
[1] select 0 + 1
Which with the precedence rules, is equivalent to this erroneous expression:
[1] select (0 + 1)
One final point is that precedence issues don't always just produce the wrong answer, but they also
frequently cause script compile errors. For example, the following will always cause an error:
str true || false
The reason for the error is that ARMA evaluates str true first, because str—like
all unary operators— has a higher precedence than the binary operator ||. This first calculation
step results in:
"true" || false
Which then throws an error, because the || operator only accepts boolean arguments.
Extra Credit
One entry in the precedence table that sticks out is else, which has a higher precedence than other binary functions—and in particular, then. Consider a typical if expression:
if (_condition) then { 1 } else { 2 }
The operators involved in this expression are:
if Boolean | IfType |
IfType then (Code | ElseType) | Any |
Code else Code | ElseType |
Without the higher precedence of else, evaluating from left to right, the above
expression would be equivalent to:
((if _condition) then { 1 }) else { 2 }
Assuming for now that _condition is true, the above would result in 1
being passed as the left hand side of the else, which isn't permitted.
What actually happens is that else is given a higher precedence than
the then operator. The two code blocks are passed in and are returned
as an ElseType object, which the then operator can
then selects from once the condition is evaluated.