learnxinyminutes-docs/luau.md
2025-01-30 13:21:34 +08:00

15 KiB

name: Luau contributors: - ["ratplier", "http://github.com/ratplier"] filename: learnLuau.luau

Luau builds upon Lua 5.1, enhancing performance and adding features while maintaining broad compatibility.
This example explores core concepts and highlights distinctions between Luau and standard Lua 5.1.

-- Single line comment
--[[
	Two sets of brackets create a multi-line comment
]]

--[=[
	Adding equal signs between the brackets allows for nested multi-line comments.
]=]

--[==[
	The above is matched in equal signs to the below, so it will not cause an error.
	This allows for commenting out large blocks of code that contain multi-line comments.
]==]


-- Variables:

local a = 0
local x, y, z = 0, 0, 0  -- Multiple assignments
local z, x, y = x, y, z -- Variables are evaluated before assignment

local number = 0       -- Integer
local decimal = 220.022  -- Floating-point number
local hexadecimal = 0xAA -- Hexadecimal number


-- Arithmetic Operations:

number = number + 1
number = number - 1
number = number * 2
number = number / 2
number = number ^ 2
number = number % 2

-- Compound Assignment Operators:
-- Used as syntax sugar
-- works with all math operations
-- special exceptions are listed below

number += 1
number -= 1
number *= 1
number /= 1
number //= 1 -- Floor division (rounded down)
number %= 1 -- Modulus (or remainder)
number ^= 1 -- Exponent

-- Strings:


local helloString = "Hello"
local worldString = "World!"
local multiLineString = [[
This is a multi-line string.
    It can span multiple lines.
]]
local interpolatedString = `var(number) -> {20}` -- var(number) -> 20

local concatenatedString = helloString .. worldString -- String concatenation
concatenatedString ..= multiLineString -- Concatenation compound operator

-- Lua (and Luau) use automatic garbage collection.  Setting a variable to nil makes it
-- eligible for GC sooner, but isn't strictly necessary.  It's mainly beneficial for
-- releasing large objects quickly or preventing accidental access to stale values.  Values
-- are also GC'd when a scope (e.g., do-end block, function) ends.

-- More details: https://www.lua.org/wshop18/Ierusalimschy.pdf

multiLineString = nil -- No longer needed; allows quicker GC.


-- Booleans and Logical Operators:

local booleanValue = true
booleanValue = not booleanValue -- Flip boolean to false

local andResult = true and false -- Logical AND
local orResult = true or false  -- Logical OR
local chainedResult = (true and false) or (true or not true)  -- Chained expressions



-- Tables (Arrays and Dictionaries):

local emptyTable = {}

local myArray = { "index 1", "index 2", "index 3", [4] = "index 4" } -- Array
local dictionary = { key1 = 20, key2 = 30, key3 = 40 }          -- Dictionary

local preAllocatedArray = table.create(20)        -- Efficiently pre-allocate an array
local preFilledArray = table.create(20, "hello") -- Create and fill an array

emptyTable["a"] = 20  -- Add to table (dictionary style)
emptyTable.b = 10       -- Alternative syntax
emptyTable[1] = 30    -- Add to table (array style)

print(emptyTable[1])     -- Access array element
print(emptyTable["a"])  -- Access dictionary element
print(dictionary.key1)   -- Access dictionary element


-- Table Library Functions:

local packedTable = table.pack(1, 2, 3)   -- Pack values into an array
local anotherTable = table.create(3)

local val1, val2, val3 = table.unpack(packedTable) -- Unpack array into variables

table.insert(myArray, "hello")           -- Insert at the end
table.insert(myArray, 1, "world")      -- Insert at index 1

table.remove(myArray, 1)                -- Remove element at index 1

local clonedTable = table.clone(myArray)      -- Shallow copy

local arrayLength = table.maxn(myArray)   -- Get array length (for numerically indexed tables)


table.sort(myArray, function(a, b)  -- Sort with custom comparator
	return a > b
end)

table.clear(emptyTable)              -- Remove all elements

table.move(myArray, 2, 3, 1, anotherTable) -- Copy a section of array to another table

local concatenatedValues = table.concat(myArray, " ") -- Join array elements into a string

local readOnlyTable = table.freeze(myArray) -- Create a read-only version of the table (Luau Specific)





-- Functions:

local function add(a, b)
	return a + b
end

local sum = add(2, 3)


local function average(...) -- Variadic function (accepts any number of arguments)
	local args = table.pack(...)
	local total = 0
	for _, n in ipairs(args) do
		total += n
	end
	return total / #args
end

local avg = average(10, 20, 30, 40)


-- Control Flow:

do
	local myLocal = 20     -- Local scope
	myGlobal = 10        -- Global scope
	local myNilValue   -- Declaring a nil variable (Luau allows this locally not globally)
	myNilValue = 20
end

local counter = 0
while counter < 50 do
	counter -= 1
	counter += 2
end


while false do  -- Never executes
	print("This will not print")
end


if counter > 40 then
	print(counter)
end


local loopCount = 0
repeat
	loopCount += 1
until loopCount > 20


repeat
	print("Hello (once)")
until true  -- Only runs once


local age = 20
if age >= 18 then
	print("Adult")
elseif age >= 13 then
	print("Teenager")
else
	print("Child")
end


-- For Loops:

for i = 1, 5 do
	print(i)
end


for k = 10, 1, -2 do
	print(k)
end


-- Generic For Loops (Iterators):

local dataTable = { a = 1, b = 2, c = 3 }

for key, value in pairs(dataTable) do  -- Key-value pairs
	print(key, value)
end


local numArray = { 10, 20, 30 }
for index, value in ipairs(numArray) do  -- Numerically indexed arrays
	print(index, value)
end


local mixedTable = { 10, 20, 30, a = 20 }
for index, value in next, mixedTable do -- For tables where the indexing type is unknown
	print(index, value)
end


-- Custom Iterator:
local function customIterator(t, i)
	i = i + 1
	local v = t[i]
	if v then
		return i, v
	end
end


local customTable = { 10, 20, 30 }
for index, value in customIterator, customTable, 0 do
	print(index, value)
end

-- Luau specific features:

-- Buffers

-- Buffers are used for storing and manipulating raw binary data.
-- They are often used when dealing with network communication, file I/O, or when precise control over memory layout is needed.
-- Unlike strings which are UTF-8 encoded, buffers store raw bytes without any interpretation.

-- Creating Buffers:

local initialSize = 30
local myBuffer = buffer.create(initialSize)  -- Create a buffer with an initial capacity of 20 bytes.
print("Initial buffer size:", buffer.len(myBuffer))  -- Output: 20

local myBuffer2 = buffer.create(5, 0xFF) -- create a buffer and prefill with the byte value 0xFF (255 in decimal)
print(
	"Initial buffer size:",
	buffer.len(myBuffer2),
	" first byte: ",
	buffer.readu8(myBuffer2, 0)
) -- Output: 5, 255

-- Writing Data to a Buffer:

buffer.writeu8(myBuffer, 0, 0x41)         -- Write the byte 0x41 ('A') at index 0 (as an unsigned 8-bit int)
buffer.writeu16(myBuffer, 1, 0x1234)       -- Write the 16-bit value 0x1234 at index 1 (as an unsigned 16-bit int, little-endian)
buffer.writeu32(myBuffer, 3, 0xABCDEF01)   -- Write the 32-bit value at index 3 (unsigned 32-bit int, little-endian)
buffer.writef32(myBuffer, 7, 3.14)  -- Write a 32-bit floating-point number (little-endian)
buffer.writef64(myBuffer, 11, 2.71828) -- Write a 64-bit floating-point number (little-endian)
buffer.writestring(myBuffer, 19, "Luau") -- Write a string at index 19

print("buffer after writing:", buffer.len(myBuffer)) -- output: 24


-- Reading Data from a Buffer:

local byte1 = buffer.readu8(myBuffer, 0)     -- Read the byte at index 0
local short1 = buffer.readu16(myBuffer, 1)  -- Read the 16-bit value at index 1
local int1 = buffer.readu32(myBuffer, 3)  -- Read the 32-bit value at index 3
local float1 = buffer.readf32(myBuffer, 7)     -- Read the 32-bit float
local double1 = buffer.readf64(myBuffer, 11)   -- Read the 64-bit double
local str = buffer.readstring(myBuffer, 19, 4) -- Read the string from beginning to char 4


print("Read bytes:", string.format("0x%X",byte1) )            -- Output: 0x41
print("Read short:", string.format("0x%X",short1) )           -- Output: 0x1234
print("Read int:", string.format("0x%X",int1) )            -- Output: 0xABCDEF01
print("Read float:", float1)                -- Output: 3.140000104904175
print("Read double:", double1)             -- Output: 2.7182800000000001
print("Read string:", str)  -- Output: Luau

-- Buffer Size and Capacity:
local currentSize = buffer.len(myBuffer)          -- Get the current size (used bytes)
print("Current buffer size:", currentSize) -- Output: 24

-- Other Operations:

buffer.fill(myBuffer, 0, 0x00)  -- Fill buffer with 0x00 starting from index 0
local newBuffer = buffer.create(currentSize) -- Create a buffer to copy to from another buffer

buffer.copy(myBuffer, 0, newBuffer, 5, 10) -- copy 10 bytes from myBuffer starting at 0 to newBuffer at index 5
print("Copied buffer:", buffer.readu8(newBuffer, 5)) -- read one of the bytes copied above


-- Error Handling:
local function tryRead(buf, index)
	local success, value = pcall(buffer.readu8, buf, index)
	if success then
		print("Read byte at", index, ":", string.format("0x%X",value))
	else
		print("Error at index", index, ":", value)
	end
end

tryRead(myBuffer, 31) -- this will error
tryRead(newBuffer, 6) -- this will be okay

-- Important Notes:

--   * Byte Order (Endianness): Luau buffers typically use little-endian byte order for multi-byte values (U16, U32, U64, F32, F64).
--   * Indexing: Buffer indices are 0-based. The first byte is at index 0.
--   * Error Handling: Attempting to read or write outside the bounds of a buffer can lead to errors, so handle these cases accordingly, consider `pcall` or manually handling it.
--   * Buffer usage is more memory efficient than string when dealing with large chunks of binary data.

-- Types

-- Types in Luau are used to define the kind of data a variable, function, or table can hold or return.
-- They improve code readability, prevent errors, and make development faster by enabling better tooling support.
-- Types can describe simple values like numbers or strings, as well as more complex structures like tables or functions.

-- Basic Types:
-- These are the most common types used in Luau.

local aNumber: number = 10          -- Represents numeric values (e.g., integers, floating-point numbers).
local aString: string = "Hello"     -- Represents text or character sequences.
local aBoolean: boolean = true      -- Represents true or false values.
local aNil: nil = nil               -- Represents the absence of a value (or "nothing").

-- Compound Types:
-- Used to represent collections of values.

local aTable: table = {1, 2, 3}     -- Can store arrays, dictionaries, or mixed data.
local aDictionary: { [string]: number } = { key1 = 10, key2 = 20 }
-- A table with string keys and number values.

-- Union and Optional Types:
-- Union types allow a variable to hold multiple possible types.
-- Optional types allow a variable to hold a specific type or nil.

local aUnion: string | number = 42  -- Can be a string or a number.
local anOptional: number? = nil     -- Can be a number or nil.

-- Defining Types:
-- You can predefine types to be used locally or in other modules

type vector3 = {
	x: number,
	y: number,
	z: number
}

local a: vector3 = {}
a.x = 20

-- Export a type for other modules to use
export type vector3 = vector3

-- Generic Types:
-- You can make types that take in types and return a new type

type array<T> = {T} -- basic array type
type dictionary<K, V> = {[K]: V}

type recursiveType<T> = {
	head: recursizeType<T>?,
	value: T,
}

-- Function Types:
-- Functions can also have types to define their inputs and outputs.

local function sub(x: number, y: number): number
	return x + y
end

-- You can also do it like this

local add: (number, number) -> number = function(x, y)
	return x + y
end

-- basic function type definition 
type callback<...I, ...O> = (I...) -> (O...)

local function addVec3(a: vector3, b, vector3): vector3
	return {
		a.x + b.x,
		a.y + b.y,
		a.z + b.z,
	}
end

local function myTypedFunction<A>(parameter: A): A
	print(A)
	return A
end



-- Metamethods and Metatables

-- Metatables allow you to customize the behavior of tables.  They provide a mechanism
-- to intercept operations like addition, indexing, function calls, etc.  Metamethods
-- are functions defined within the metatable that are triggered when these operations
-- are performed on a table associated with that metatable.


-- Example: Overloading the addition operator (+)

local mt = {}  -- Create a metatable
mt.__index = mt -- Make methods self-referencing

mt.__add = function(self, other)  -- Define the __add metamethod
    return self.value + other.value
end

local t1 = { value = 10 }
local t2 = { value = 20 }

setmetatable(t1, mt)  -- Assign the metatable
setmetatable(t2, mt)

local sum = t1 + t2  -- Calls the __add metamethod
print(sum) --> 30


-- Other common metamethods:
-- __index: For handling table lookups (when a key doesn't exist).
-- __newindex: For handling table assignments.
-- __call: For making tables callable like functions.
-- __len: For getting the length of a table.
-- __idiv: Integer division in Luau.
-- etc.  (See Lua/Luau documentation for a complete list)
-- at https://create.roblox.com/docs/luau/metatables




-- Native Code Generation (NCG)
-- Luau's NCG compiles Lua code directly into native machine code, leading to significant
-- performance improvements compared to interpreted Lua.  NCG leverages LLVM to
-- optimize the generated code further.  Not all platforms support NCG.




-- Code Optimization (Luau)

-- Luau provides various optimization techniques:
-- * Loop unrolling: Reduces loop overhead for small, fixed iterations.
-- * Constant folding: Evaluates constant expressions at compile time.
-- * Inlining: Replaces function calls with the function's body in some cases.
-- * Dead code elimination: Removes code that is never executed.
-- * String interning: Stores identical strings only once, saving memory.
-- * Efficient table implementation: Luau's table implementation is optimized for
--   common usage patterns.
-- * Type specialization (Luau):  When type information is available, Luau can generate
--   more efficient code tailored to specific types.

-- These optimizations, combined with NCG, contribute significantly to Luau's performance advantages over standard Lua 5.1.

-- End of Luau Showcase

Lua Libraries in Luau and Differences:

Most Lua 5.1 libraries are present in Luau (e.g., math, string, table, coroutine). However, there are some key differences:

  • io (restricted): Limited or no access to file system functions due to sandboxing.
  • os (mostly absent): Operating system specific functions are generally unavailable.
  • debug (limited): Debugging functionality may be curtailed.
  • package (modified): Luau's module system may have significant changes depending on the embedding platform (e.g., Roblox).

Luau Specific Libraries

  • bit32: Bitwise operations library similar to Lua 5.2's bit32
  • utf8: If supported, provides UTF-8 string handling functions