UnrealScript Operators

From Oldunreal-Wiki
Jump to navigation Jump to search

One of the lesser known features of UnrealScript is the ability for programmers to define their own operators. The Unrealscript Reference barely touches on the subject, merely saying it's possible, but doesn't go into any detail. It doesn't even provide the syntax for creating them. However, this document will go into detail on how unrealscript operators work, as well as providing some interesting examples. All of the information in this document I've found out myself, so I'm sure some of it may not be totally accurate.

Declaring a basic operator

The syntax for declaring a basic operator is as follows:


final operator([Precedence]) [Return Type] [Name] ( [Parameter1], [Parameter2] ){}

Let's break that down.

Name

The name/identifier of the operator. This can be:

  • A string identifier, like a normal function.
  • A symbol, like +,-,*,/,#,:,etc
  • A group of 2 symbols. For some reason the compiler limits you to 18 different symbol groups. They are: <<, >>, !=, <=, >=, ++, --, +=, -=, *=, /=, &&, ||, ^^, ==, **, ~=, @=
  • The compiler also allows a special 3 symbol operator, >>>.

Return Type

Just like any normal unrealscript function, an operator can have a return type. Works exactly the same.Naturally, if you want the operator to do anything, you're going to have to return the result of the operation between parameter 1 and 2.

Parameters

Almost the same as unrealscript functions with one catch, a standard operator expects exactly 2 parameters. The first parameter will be the one on the left side of the operator, the second one will be the right side.

Precedence

This allows you to specify the operator's precedence, or how tightly it will bind. The lower the number, the tighter the operator will bind. When you use a complex expression like A = 4 * 5 + 6, Unrealscript will 'group' the operators by precedence. 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 Symbol 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
>>>
Double Right Shift
24
<
Less Than
24
>
Greater Than
24
<=
Less Or Equal
24
>=
Greater Or Equal
24
==
Equality
24
~=
Case Insensitive Equality
26
!=
Not Equal
28
&
Bitwise And
28
|
Bitwise Or
28
^
Bitwise Xor
30
&&
And
30
Or
32
^^
Xor

Basic Example

Here's a simple sample operator to demonstrate what we've covered so far. This 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 precedence for this operator 18, so the addition will bind tighter in this case.

Advanced operators

Preoperators / Postoperators

These are another form of operator that you can implement, an example of which would be ++ or --. Syntax for declaring a pre/postoperator is similar to that of regular operators:


final [postoperator|preoperator] [Return Type] [Name] ( [Parameter] ){}

Everything is the same, except you change 'operator' to preoperator or postoperator. Also, pre/postoperators only take one parameter - in the case of a preoperator that would be the variable right after the operator, and in the case of a postoperator, it'll be the variable right before the operator. For example, ++i would pass i into the appropriate preoperator, while i++ would pass it into a postoperator. While you don't have to do this, a preoperator should return the data as it was before it was modified, and a postoperator should do the inverse, as that's the whole point :-).

Out Params

You can use the 'out' keyword, just as you can in normal unrealscript functions, before any parameter to an operator. This, as you might expect, allows you to modify the actual variables passed to the operator. Also, if your operator has an out parameter, it can be used as an expression all by itself. For example, let's take an int postoperator '#' that divides the integer it's invoked on by 10, but doesn't modify the original integer:


final postoperator int # ( int A );{  return A / 10;}

i = 100;

b = i#; // b = 10, i = 100 (i is NOT affected)

i#; // Wrong.. can't do this unless we declare the operator with 'out int A'

Now, let's try the same thing, but using out.


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

Static operators

One thing I've failed to mention thus far, is you can make most operators 'static', as long they don't access any non-static member data, or call any non-static functions.


static final postoperator int # ( out int A );{  return A /= 10;}

This does the exact same thing that the previous example does, but has some performance benefits, as the operator isn't re-instantiated every time it is executed.

Examples

String multiplication

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(22) string * ( coerce string A, int B ){  local string Build;  local int i;

  for(i=0;i<B;i++)    Build = Build$A;  return Build;}

Vector operators

Some simple vector operators, since most operators that you could want for vector have already been defined.


// Extend vector by an unit lengthstatic final preoperator vector ++ ( out vector A ){  return A += Normal(A);}

// Shrink operator by an unit lengthstatic final preoperator vector -- ( out vector A ){  return A -= Normal(A);}

// Same thing, but postopstatic final postoperator vector ++ ( out vector A ){  local vector B;  B = A;  A += Normal(A);  return B;}

static final postoperator vector -- ( out vector A ){  local vector B;  B = A;  A += Normal(A);  return B;}

Color Operators

Due to the lack of any color operators, let's make a few:


// Lighten color by 1static final preoperator color ++ ( out color A ){  A.R++;  A.G++;  A.B++;  return A;}

// Darken color by 1static final preoperator color -- ( out color A ){  A.R--;  A.G--;  A.B--;  return A;}

// Postoperator versionstatic final postoperator color ++ ( out color A ){  local color Copy;  Copy = A;  A.R++;  A.G++;  A.B++;  return Copy;}

// Postoperator versionstatic final postoperator color -- ( out color A ){  local color Copy;  Copy = A;  A.R--;  A.G--;  A.B--;  return Copy;}

// Remember that averaging operator we just used? Now it's suddenly useful, so I've copied it into herefinal operator(18) int : ( int A, int B ){  return (A + B) / 2;}

// Interpolate 2 colorsstatic final operator(22) color Mix ( color A, color B ){  local Color Result;  Result.R = A.R : B.R;  Result.G = A.G : B.G;  Result.B = A.B : B.B;  return Result;}

// UT Provides a * operator for colors, but no /. Ramp a color by a floatstatic final operator(16) color / ( color A, float B ){  local Color Result;  Result.R = A.R / B;  Result.G = A.G / B;  Result.B = A.B / B;  return Result;}

// Same thing, but this one affects the colorstatic final operator(34) color /= ( out color A, float B ){  A = A / B;  return A;}

// UT Provides *, not *=, so let's implement itstatic final operator(34) color *= ( out color A, float B ){  A = A * B;  return A;}

// Add a byte value to each componentstatic final operator(20) color + ( color A, byte B ){  local Color Result;  Result.R = A.R + B;  Result.G = A.G + B;  Result.B = A.B + B;  return Result;}

// Subtract a byte value to each componentstatic final operator(20) color - ( color A, byte B ){  local Color Result;  Result.R = A.R - B;  Result.G = A.G - B;  Result.B = A.B - B;  return Result;}

// Out versions of the operatorsstatic final operator(34) color += ( out color A, byte B ){  A = A + B;  return A;}

static final operator(34) color -= ( out color A, byte B ){  A = A - B;  return A;}

// Out version of the operator UT providesstatic final operator(34) color += ( out color A, color B ){  A = A + B;  return A;}

static final operator(34) color -= ( out color A, color B ){  A = A - B;  return A;}

Actor Destruction

// We can't use static because we're calling Destroy(), a non-static functionfinal postoperator Actor DIEDIEDIE ( out Actor NearlyDead ){  NearlyDead.Destroy();  return NearlyDead;}

SomeActor DIEDIEDIE; // It is now dead.

Abuse

Dynamic array implementation

Always wanted to use those elusive array<>'s, but found the GetPropertyText/SetPropertyText interface to be a problem? This is where operators come in. Here I provide a simple interface to a dynamic array of integers (although this is easily adaptable to support various types). You set elements of a dynamic array with 'Array << (Index:Data)' (totally arbitrary syntax I made up), and access an element with 'Array<Index>'.


class TestDynArray extends CommandLet;

// Our struct to store the index/data pairs created using the : operatorstruct SetGroup{  var int Index;  var int NData;};

// The left side of the Array construct... the one that actually does something.final operator(50) int < ( DynArray A, int B ){  return A.Get( B );}

// The right side of the Array construct... does absolutely nothing, it's just there to make the syntax pretty (complete the brackets).final postoperator int > ( int A ){  return A;}

// Sets an element in a dynamic array, taking a index/data pair as the right side.final operator(50) DynArray << ( out DynArray A, SetGroup B ){  A.Set( B.Index, B.NData );  return A;}

// Creates a index/data pairfinal operator(23) SetGroup : ( int A, int B ){  local SetGroup C;  C.Index = A;  C.NData = B;  return C;}

// Just a test function to show that we can use all sorts of expressions within the index/data pairsfunction int TestFunc(){  return 10;}

function int main( string parm ){  local DynArray Arr;  local int i;  local setgroup g;

  // Instantiate a DynArray  Arr = new class'DynArray';

  // Set some elements  Arr << (5:78);  Arr << (1:30);  Arr << (TestFunc():69);

  // And log them  Log( Arr<5> @ Arr<1> @ Arr<TestFunc()> );  return 1;}

// Interface to dynamic arrays. Evil.class DynArray extends Object;

var array<int> Data;var int CacheData[1024];var int Num;var bool bCached;

// Parse the elements out of a string like (2,3,5,2,4,6,2), and store them in our cachefunction Recache(){  local int i;  local string Nightmare;  local int NextPos;  local int z;  Num = 0;  Nightmare = GetPropertyText("Data");  Nightmare = Right( NightMare, Len(NightMare)-1 );  Nightmare = Left( NightMare, Len(NightMare)-1 );  for(i = InStr( Nightmare, "," );i > 0;z++)  {    CacheData[Num++] = int( Left(Nightmare, i) );    Nightmare = Mid( Nightmare, i + 1, Len( Nightmare ) );    i = InStr( Nightmare, "," );    if ( i == -1 && Len(NightMare) > 0 )      CacheData[Num++] = int( Nightmare );  }  bCached = true;}

// Set an element by building a string like (3,3,5,9), and recache.function Set( int Index, int Data ){  local string Build;  local int i;  Recache();  CacheData[Index] = Data;  if ( Index > Num-1 )    Num = Index+1;  Build = Build $ "(";  for(i=0;i<Num;i++)  {    Build = Build $ CacheData[i];    if ( i != Num-1 )      Build = Build $ ",";  }  Build = Build $ ")";  SetPropertyText("Data", Build);  bCached = true;}

// Get a cached elementfunction int Get( int Index ){  if ( !bCached )    Recache();

  return CacheData[Index];}

Lazy struct filling

This example shows how to implement an easy method of populating a struct or an array.


class TestLazyStruct extends CommandLet;

struct StringGroup{  var string Strings[20];  var int Num;};

// We could use , for the separator, but unfortunatly that messes up function calls (although that// makes for some interesting possibilities...)final operator(50) StringGroup : ( StringGroup A, string B ){  A.Strings[A.Num++] = B;  return A;}

// Group the two initial stringsfinal operator(52) StringGroup : ( string A, string B ){  local StringGroup C;  C.Strings[C.Num++] = A;  C.Strings[C.Num++] = B;  return C;}

// Test itfunction int main( string parm ){  local StringGroup SomeStrings;  local int i;  // Fill the struct  SomeStrings = ("String1": "AnotherString": "A third string": "The next one is a variable": parm);  for(i=0;i<SomeStrings.Num;i++)    Log(SomeStrings.Strings[i]);  return 1;}

Implementing a crude Logf

And finally, we design a simple formatted log function, using a variation of the technique used above. You use the : operator to group together a bunch of strings and floats(or int's), and then pass the resulting struct into a function that'll parse the format and grab the needed strings/numbers out of the struct.


class TestLogf extends CommandLet;

// This will hold all of our parametersstruct AnythingGlob{  var string StringGlob[20];  var int StringNum;  var float NumberGlob[20];  var int NumberNum;  var int num;};

// Add a string to a globfinal operator(98) AnythingGlob : ( anythingglob B, string A ){  B.StringGlob[B.StringNum++] = A;  return B;}

// Add a float to a globfinal operator(98) AnythingGlob : ( anythingglob B, float A ){  B.NumberGlob[B.NumberNum++] = A;  return B;}

// Create the glob out of 2 stringsfinal 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 floatsfinal 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 floatfinal 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 stringfinal 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 parametersfunction 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);}