Operators

Division

There are two division operators: / and //.
The / is the decimal division. The // is the integer division.
If in LSL we are dividing two integers, in Slua we use //, otherwise we use /.

// division (LSL)

integer total = 10;
integer people = 7;

llOwnerSay((string)(total / people));         // --> 1
llOwnerSay((string)((float)total / people));  // --> 1.428571
-- division (SLua)

local total = 10
local people = 7

ll.OwnerSay(tostring(total // people))  -- > 1
ll.OwnerSay(tostring(total / people))   -- > 1.4285714285714286

Exponentiation

In Lua, the ^ is the exponentiation operator.
We can use ^ instead of the function ll.Pow().

// exponentiation (LSL)

integer base = 2;
integer exp = 3;

llOwnerSay((string)llPow(base,exp));  // --> 8.000000
-- exponentiation (SLua)

local base = 2;
local exp = 3;

ll.OwnerSay(tostring(base ^ exp))  -- > 8

Not equal

The unequality operator is ~= instead of !=.

// not equal (LSL)

if (llGetColor(ALL_SIDES) != <1,1,1>) {
    llOwnerSay("it isn't white");
}
-- not equal (SLua)

if ll.GetColor(ALL_SIDES) ~= vector(1,1,1) then
    ll.OwnerSay("it isn't white")
end

Increment / decrement

In Lua ++ and – don’t exist.
We need to add or subtract one.

// increment / decrement (LSL)

integer total = 0;
integer count = 10;

total++;
count--;
-- increment / decrement (SLua)

local total = 0
local count = 10

total += 1
count -= 1

Concatenation

The concatenation operator is .. instead of +.

// concatenation (LSL)

string greet1 = "hello";
string greet2 = "world";

llOwnerSay(greet1 + " " + greet2);  // --> hello world
-- concatenation (SLua)

local greet1 = "hello"
local greet2 = "world"

ll.OwnerSay(greet1.." "..greet2)  -- > hello world

And / Or / Not

The logical operators are “and”, “or”, “not” instead of &&, ||, !.
In SLua “and” has higher precedence than “or”. In LSL, && and || have the same precendence.

// logical operators (LSL)

integer isDone = FALSE;
integer value = 50;


if ( value < 0 || value > 25 && !isDone ) {
    // do something
}
-- logical operators (SLua)

local isDone = false
local value = 50

  -- and has higher precedence than or
if (value < 0 or value > 25) and not isDone then
    -- do something
end

In LSL both operators are always evaluated.
In SLua sometimes only the left operator is evaluated:

  • with “and”, if the left operator is false, the result is false, and the right operator is not evaluated
  • with “or”, if the left operator is true, the result is true, and the right operator is not evaluated

We can do:

  • if people > 0 and total / people > 10 then
    • If people is 0 the right operator ( total / people ) is not evaluated, and we don’t divide by zero

We can use them as value selectors:

  • result = optionA and optionB
    • same as result = if optionA then optionB else option A
  • result = optionA or optionB
    • same as result = if optionA then optionA else option B

To set a default value in case that a variable is nil:

  • total = total or 0
  • except with boolean variables with a default value of true, in this case it must be:
    • total = total ~= nil or true

As a ternary operator:

  • text = count == 1 and "1 item" or count .. " items"
    • same as text = if count == 1 then "1 item" else count .. " items"

To call a function on a condition:

  • isReady and start()
    • same as if isReady then start() end

Bitwise operations

The bitwise operators &, |, ~, ^, «, » don’t exist in SLua.

We have the library bit32, with functions for bitwise operations:

  • & : bit32.band()
  • | : bit32.bor()
  • ~ : bit32.bnot()
  • ^ : bit32.bxor()
  • « : bit32.lshift()
  • >> : bit32.arshift()
    band, bor and bnot can take any quantity of operators.

For instance, in the event changed:

  • In LSL: if (change & (CHANGED_OWNER | CHANGED_INVENTORY )) {
  • In SLua: if bit32.band(change, bit32.bor(CHANGED_OWNER, CHANGED_INVENTORY)) ~= 0 then

Or better with bit32.btest() that does a bitwise-and, returning true if the resulting value is not 0, or false if it is 0.

  • In SLua: if bit32.btest(change, bit32.bor(CHANGED_OWNER, CHANGED_INVENTORY)) then

Another example, checking for -1:

  • In LSL: if (~llListFindList(myList, [item])) {
  • In SLua: if bit32.bnot(ll.ListFindList(myList, {item})) ~= 0 then

The library bit32 works with 32 bits and SLua numbers are 64 bits. The library uses the low 32 bits of the number and the return value is an unsigned number:

  • in LSL: integer val = 0; val = ~val; llOwnerSay((string)val); // --> -1
  • in SLua: local val = 0 val = bit32.bnot(val) print(val) -- > 4294967295

To get signed results we can use the SLua type integer, which is a 32-bit signed integer, and cast the result to number:

  • in SLua: local val = 0 val = tonumber(bit32.bnot(integer(val))) print(val) -- > -1
    when all the parameters are SLua integers the returned value is also an SLua integer.

The previous example, checking for -1, works because -1 is stored with all bits to 1, so no matter how many bits are used it is still -1.

Another example, to get a negative channel:

// LSL
integer gChannel = 0x80000000 | (integer)("0x" + (string)llGetKey());
llOwnerSay((string)gChannel);  // --> -1261093815
-- SLua
local gChannel = bit32.bor(0x80000000, integer("0x" .. tostring(ll.GetKey())))
print(gChannel)  -- > 3033873481

which is a way to get a channel number that can’t be used in LSL and neither used typing it in the viewer.

With all the parameters as SLua integers:

-- SLua
local gChannel = tonumber(bit32.bor(integer(0x80000000), integer("0x" .. tostring(ll.GetKey()))))
print(gChannel)  -- > -1261093815

Comparing string and uuid

In SLua, equality checks use strict equality: two variables are considered equal only if they have the same type and the same value.
Variables of different types are always different, no matter their contents.

In LSL, we can compare strings and keys as if they were the same type.

In LSL this (in an object with blank textures) works:

  • if ( llGetTexture(0) == TEXTURE_BLANK ) { -- true
    but in SLua:
  • if ll.GetTexture(0) == TEXTURE_BLANK then -- false

Because the LL functions that return a texture can return the name of the texture or its UUID, but they can only have one return type. So they always return a string. And LL constants that contain a uuid have type uuid.

In SLua a variable of type uuid and a variable of type string are always different, even if they have the same text.

In SLua it must be:

  • if uuid( ll.GetTexture(0) ) == TEXTURE_BLANK then -- false

Vectors

The SLua datatype vector internally uses a Luau datatype vector and inherits some functions from Luau. We can’t use the :method notation, we have to call them on vector:

  • vector.magnitude( myVec ) : returns a number, same as ll.VecMag().
  • vector.normalize( myVec ) : returns a vector, same as ll.VecNorm().
  • vector.dot( myVec1, myVec2 ) : the dot product, returns a number, same as myVec1 * myVec2 in LSL (but not in SLua).
  • vector.cross( myVec1, myVec2 ) : the cross product, returns a vector, same as myVec1 % myVec2 in LSL and SLua.
  • vector.angle( myVec1, myVec2 ) : returns a number, the angle between the two vectors.
myVec1 * myVec2

In LSL is the dot product, in SLua multiplies the components:

  • print( vector( 3, 4, 5) * vector ( 10, 10, 10 ) ) -- > < 30, 40, 50 >

In LSL this:

  • float dotProduct = myVec1 * myVec2; // LSL
    is this in SLua:
  • local dotProduct = vector.dot(myVec1, myVec2) -- SLua

SLua has also added the division, that divides the components, and doesn’t exist in LSL:

  • print( vector( 12, 6, 3) / vector ( 3, 2, 1 ) ) -- > < 4, 3, 3 >

Infinity and NaN

Lua has some “extreme” values in the datatype number.

Infinity

Lua has “inf” and “-inf” to represent positive and negative infinity, and the constant math.huge:

  • print( math.huge ) --> inf
  • print( -math.huge ) --> -inf

We can get an infinite number with math.huge:

local big = math.huge
print( big )  -->   inf

Or dividing by 0:

  • print( 1 / 0 ) --> inf
  • print( -1 / 0 ) --> -inf

Or casting from a string:

  • local big = tonumber( "inf" )

We test for infinity with:

  • if x == math.huge then print("x is infinity!") end
  • if y == -math.huge then print("y is negative infinity!") end

We can do operations with infinity, in the way that mathematical infinities work.

We can use math.huge for initial values instead of 999999999:

local minimum = math.huge
if quantity < minimum then
    minimum = quantity
end

Division by zero doesn’t crash the script in Lua. It gives “inf”, “-inf”, or “nan”.

NaN

“nan” stands for “Not a Number”. It’s a special value that represents an undefined or unrepresentable result in math operations.

We can get “nan” when math goes wrong in a way that doesn’t crash the script, but also doesn’t produce a meaningful number:

  • print(0/0) --> nan (undefined division)
  • print( math.huge - math.huge ) --> nan (infinity minus infinity)
  • print( math.sqrt( -1 ) ) --> nan (square root of a negative number)

Arithmetic operations including a “nan” always result in “nan”. If any operand is “nan”, the whole expression becomes “nan”.

“nan” propagates through math operations, because once a value is undefined, any further calculation remains undefined.

Comparing “nan” with any number by >, < or == always returns false.

“nan” is not considered greater, smaller, or equal to any number, it’s “outside” the usual numeric order.

It’s false even compared to itself:

x = 0 / 0
print( x == x )  -->  false

This is the only case that a variable is not equal to itself.

So we can check for “nan” with:

function isNaN(x)
    return x ~= x
end

In logical operations, “nan” behaves like any other non-boolean value. Lua treats all non-nil, non-false values as truthy, so “nan” counts as truthy.

“inf”, “-inf” and “nan” avoid crashes when trying impossible math operations.

We can check for these extreme numbers with:

if x ~= x or x == math.huge or x == -math.huge then
    -- do something with this extreme number
end

Integer division and modulo

LSL and SLua behaves different in the integer division and the modulo division when one, and only one, of the operands is negative.

Integer divison

The integer division in LSL:

  • llOwnerSay((string)(-7 / 4)); // --> -1
  • llOwnerSay((string)(7 / -4)); // --> -1

And in SLua:

  • print(-7 // 4) -- > -2
  • print(7 // -4) -- > -2

In LSL, integer division always truncates toward zero. This means that the result of dividing two integers is the whole number part of the result, with any fractional portion discarded, and the sign of the result follows the sign of the numerator.

In SLua, integer division using the // operator rounds down toward negative infinity. This is known as floor division. It always returns the largest integer less than or equal to the result of the division.

To simulate LSL behaviour we can use this:

function divLSL(a, b)
	return if a * b < 0 then math.ceil(a / b) else math.floor(a / b)
end
Modulo

The modulo division in LSL:

  • llOwnerSay((string)(-7 % 4)); // --> -3
  • llOwnerSay((string)(7 % -4)); // --> 3

And in SLua:

  • print(-7 % 4) -- > 1
  • print(7 % -4) -- > -1

In LSL, the modulo operator (%) returns the remainder after division, and its result always carries the same sign as the numerator. This is known as truncating remainder, and it matches the way LSL performs integer division, both operations are consistent in that they truncate toward zero.

In SLua, the % operator performs true modulo, which means the result always has the same sign as the divisor. Lua defines this operation mathematically as the difference between the dividend and the product of the divisor and the floor of the division result. This ensures that the result is always non-negative if the divisor is positive, and always non-positive if the divisor is negative.

To simulate LSL behaviour we can use this:

function modLSL(a, b)
	return a % b - if a * b < 0 and a % b ~= 0 then b else 0
end