The three virtues of a programmer: Laziness, Impatience, and Hubris. – Larry Wall
Legacy:Scripting Operators
Contents
Using Operators[edit]
Operators vs. Functions[edit]
In maths, the difference is simply one of notation. The following mean the same, but the first is written as an operator, the second and third as functions:
4 + 5 +( 4 , 5 ) plus( 4 , 5 )
In UnrealScript there is a similar distinction. Both functions and operators are represented by some sort of symbol or group of symbols, but operator's parameters are either side of the name instead of enclosed in brackets:
4 + 5 ; 4 plus 5 ; plus( 4 , 5 ) ;
In UnrealScript, operators are limited to having one or two parameters. UnrealScript's built-in operators are defined in the Object class.
Binary operators[edit]
Most operators are binary – they take two parameters, one on each side. Defining an operator is very similar to defining a function:
static final operator(<priority>) <type> <opname> (<param1>, <param2>) {}
Note that a return value is required for operators, even if they have 'out' parameters.
Operators need to be declared as final (or the compiler will start bitching), but not necessarily as static – if you don't declare an operator as static, you can access object variables in its code. (Unlike non-static functions you can use a non-static operator in a static function; any object variables you access there yield their default properties in that case.) A function declared as static may execute faster, however note that although it may be static it cannot be referenced from outside of the class it was defined in and its subclasses. The following, for instance, is not valid:
ValueA class'MathFunctions'.static.+ ValueB
The number in parenthesis after the keyword "operator" gives the evaluation priority; the lower this value, the earlier a term with this operator will be evaluated when a complete expression is executed. For example, the exponentiation operator's precedence is 12, multiplication is 16, and addition is 20. Consider the expression:
A = B / 2 MyOperator 5;
If MyOperator has a precedence of 10, it'll bind tighter than division (16), so the expression will be evaluated as:
A = B / (2 MyOperator 5);
On the other hand, if MyOperator has a precedence of 20, it would evaluate as:
A = (B / 2) MyOperator 5;
The valid range for precedence values is 0-255. Here's a list of the existing values used in the built in operators:
Precedence | Operator | Operation |
12 | ** |
Exponentiation |
16 | * |
Multiplication |
16 | / |
Division |
16 | Dot |
Dot Product |
16 | Cross |
Cross Product |
18 | % |
Modulus |
20 | + |
Addition |
20 | - |
Subtraction |
22 | << |
Left Shift |
22 | >> |
Right Shift |
22 | >>> |
Unsigned Right Shift |
24 | < |
Less Than |
24 | > |
Greater Than |
24 | <= |
Less or Equal |
24 | >= |
Greater or Equal |
24 | ---- |
Equality |
24 | ~= |
Approximate Equality |
26 | != |
Not Equal |
28 | & |
Bitwise And |
28 | {| |
|Bitwise Or |- | style="text-align: right" |28 | style="text-align: center" |^
|Bitwise Xor |- | style="text-align: right" |30 | style="text-align: center" |&&
|And |- | style="text-align: right" |30 | style="text-align: center" |{|
|- | |}
|Or |- | style="text-align: right" |32 | style="text-align: center" |^^
|Xor |}
(Also see Operators.)
Type is what type the operator will return, if any. This also works the same as a function.
Opname is the name of the operator, similar to the name of a function. However, the name of an operator is more flexible. You can use any of the following as an operator name:
The name/identifier of the operator. This can be:
- A string identifier, like a normal function.
- A symbol, like +,-,*,/,#,:,etc.
- A group of two symbols. The compiler limits you to the symbol groups already recognized by the parser: <<, >>, !=, <=, >=, ++, –, +=, -=, *=, /=, &&, ||, ^^, ==, **, ~=, @=
- The compiler also allows a special three-symbol operator, >>>.
The parameters of the operator work the same way as a function, but you must have two and only two parameters for a binary function. The first parameter is the value on the left side of the operator when it is used. The second parameter is the value on the right side of the operator when it is used. Like a function, you can use the out keyword if you want an operator to change the value of one of the values inputted to it.
The code for your operator goes between the curly braces, the same way as a function. An operator is largely a function that is merely called using a different syntax, as as such can do most things that a function can. Use return as you would in a function to return the result of the operator, if any.
The following example is an alternative to %, the built-in modulo operator. This is an extremely handy one to have, as the built-in % operator disagrees with the mathematical modulo function for negative numbers.
static final operator(18) float mod (float A, float B) { if( A % B >= 0 ) return A % B ; else return ( A % B ) + B ; }
Note that the operator does not need to be represented by a single special character; it can also be a valid symbol name. With the above example, the new syntax would be:
local float a, b, c; c = a mod b;
Here's another sample operator that will calculate the average of the left and right side:
final operator(18) int : ( int A, int B ) { return (A + B) / 2; } Middle = 10 : 2; // Middle = 6 Middle = 10 + 2 : 4; // Middle = (10 + 2) : 4 = 8
I made the priority for this operator 18, so the addition will be evaluated before the : operator in this case.
Unary operators[edit]
Unary operators are similar, just use "preoperator" or "postoperator" as the keyword. A preoperator and a postoperator has only one parameter, and is prepended or appended to the value to be processed, respectively. An example of a postoperator is ++ or –. In addition, there is no priority for unary operators, they will always be evaluated first. I am not certain what the result is if a preoperator and a postoperator are called on the same value simultaneously. An example of a unary operator, boolean negation is defined by this line in the Object script:
native(129) static final preoperator bool ! (bool A);
Examples of Operators[edit]
There are many useful examples on the following pages:
- Useful Maths functions – number functions, trig functions and vector operators
- Color Operators
- Dynamic Array implementation (hack for UT)
- AssociativeArray
More Examples of Operators are provided below.
Using the Out Keyword[edit]
final postoperator int # ( out int A ); { return A /= 10; // Notice how A is modified here } i = 100; b = i#; // b = 10, i = 10 (i IS affected, because of the out keyword) i#; // i = 1
String Multiplication[edit]
Here's a simple operator that will take a string, and 'multiply' it by an integer, returning the string repeated that many times:
static final operator(28) string * ( coerce string String, int nRepetitions ) { local string Result; while(nRepetitions > 0) { if ((nRepetitions & 1) == 1) Result = Result $ String; String = String $ String; nRepetitions = nRepetitions >> 1; } return Result; }
Actor Destruction[edit]
static final postoperator Actor DIEDIEDIE ( out Actor NearlyDead ) { NearlyDead.Destroy(); return NearlyDead; } SomeActor DIEDIEDIE; // It is now dead.
Implementing a crude Logf[edit]
This is a simple formatted function that writes to the log file. You use the : operator to group together a bunch of strings and floats (or integers), and then pass the resulting struct into a function that willll parse the format and grab the needed strings/numbers out of the struct.
class TestLogf extends CommandLet; // This will hold all of our parameters struct AnythingGlob { var string StringGlob[20]; var int StringNum; var float NumberGlob[20]; var int NumberNum; var int num; }; // Add a string to a glob final operator(98) AnythingGlob : ( anythingglob B, string A ) { B.StringGlob[B.StringNum++] = A; return B; } // Add a float to a glob final operator(98) AnythingGlob : ( anythingglob B, float A ) { B.NumberGlob[B.NumberNum++] = A; return B; } // Create the glob out of 2 strings final operator(99) AnythingGlob : ( string A, string B ) { local AnythingGlob C; C.StringGlob[C.StringNum++] = A; C.StringGlob[C.StringNum++] = B; return C; } // Create the glob out of 2 floats final operator(99) AnythingGlob : ( float A, float B ) { local AnythingGlob C; C.NumberGlob[C.NumberNum++] = A; C.NumberGlob[C.NumberNum++] = B; return C; } // Create the glob out of a string and a float final operator(99) AnythingGlob : ( string A, float B ) { local AnythingGlob C; C.StringGlob[C.StringNum++] = A; C.NumberGlob[C.NumberNum++] = B; return C; } // Create the glob out of a float and a string final operator(99) AnythingGlob : ( float B, string A ) { local AnythingGlob C; C.StringGlob[C.StringNum++] = A; C.NumberGlob[C.NumberNum++] = B; return C; } function int main( string parm ) { local AnythingGlob Test; Logf( "Testing... with a string '%s', an int '%i', and a float %f": "Test": 6: 6.4 ); Logf( "This commandlet was passed '%s'": parm ); Test = ( "Using prebuilt glob: %s %f %i %s": "Hello": 3.1415926: 4: "Bye" ); Logf( Test ); return 1; } // The actual Logf... an anythingglob of parameters function Logf( AnythingGlob params ) { local int i; local string Format,FinalS, Flag; local int sglbnum; local int fglbnum; Format = params.StringGlob[sglbnum++]; for (i=0;i<=Len(format);i++) { if (Mid(format, i, 1) == "%") { i++; Flag = Mid(format,i,1); if ( Flag == "s" ) FinalS = FinalS $ params.StringGlob[sglbnum++]; else if ( Flag == "i" ) FinalS = FinalS $ string(int(params.NumberGlob[fglbnum++])); else if ( Flag == "f" ) FinalS = FinalS $ string(params.NumberGlob[fglbnum++]); else { FinalS = FinalS $ "%"; i--; } } else FinalS = FinalS $ Mid(format, i, 1); } Log(FinalS); }
Related topics[edit]
- Main UnrealScript topic page
- Operators
Mychaeel: Very nice. :tup: But please use third- (or fourth-) level headings where they're due...
Tarquin: cool. :D Maybe split this into several pages of ready-made operators, to sit alongside Useful Maths functions
Foxpaw: This page had two separate explanations that were both complete so I merged them into one – I've tried to ensure no content was lost, but if anything was lost in the merger feel free to put it back. The original author was not credited on the original page, the second explanation was made by Yoda.
Foxpaw: It seems logical to assume that operators are "simulated" due to their nature - is this an accurate assumption?
Mychaeel: Usually operators are static, so the concept of simulated doesn't apply; however, if you make a non-static operator, it's your choice whether it is simulated (usable on clients) or not.