Skip to content

Latest commit

 

History

History
1041 lines (887 loc) · 36.5 KB

File metadata and controls

1041 lines (887 loc) · 36.5 KB

Title: 0. Basic Syntax

Author: Caesar James LEE

Date: October 5, 2025

introduction

  • lua is a lightweight, embeddable scripting language known for its simplicity and efficiency
  • widely used in game development, embedded systems, and as a configuration language like Neovim

Hello World

    print("Hello World")

comments

  • single line comments
        --single line comments
  • multi-line comments
        --[[
            multi-line comments
        ]]

variables

declaration

    globalVariable = 12--default(avoid using globals)
    local localVariable = 21--local, recommended
    local x, y, z = 1, 2, 3, 4--x = 1, y = 2, z = 3, and 4 is thrown away
    local alpha, beta = 1--alpha = 1, beta = nil

types

  1. nil
        local nullVariable = nil
  2. boolean
        local booleanVariable, booleanVariable1 = false, true
  3. number
        local integerVariable = 12
        local floatingPointVariable = 21.3
        local scientificVariable = 1e12--1.0*10^12
        local hexadecimalVariable = 0xFF--256 in decimal
  4. string
        local stringVariable0 = 'Hello World'
        local stringVariable1 = "Hello World"
        local stringVariable2 = [[
            multi-line
            string
        ]]
  5. function
        local fibonacci = function(n)
            return n < 2 and 1 or fibonacci(n - 2) + fibonacci(n - 1)
        end
  6. table
    • lua's only data structure
    • can represent arrays, maps(value's type is any), objects, sets, etc
    • array-like(1-based index)
          local array = {0, 1, 2, 3, 4}--indices are 1, 2, 3, 4, 5
          print(array [1])--output 0(first element)
    • map-like
          local person = {
              firstName = "Caesar",
              age = 19,
              gender = "semiboy",
              major = "CS"
          }
          print(person.firstName)--output Caesar
          print(person ["age"])--output 19
    • operations
      1. array-like
            local array = {1, 2, 3, 4, 5}
        
            --insertion
            array [6] = 5--insert at index 6
            table.insert(array, 6)--append 6 to end
            table.insert(array, 2, 7)--insert 7 at index 2
        
            --removal
            table.remove(array)--remove last element
            table.remove(array, 1)--remove element at index 1
        
            --length
            print(#array)--print length of array
        
            --concatenation
            local array1 = {"a", "b", "c"}
            print(array.concat(array1, ", "))--array is {1, 7, 2, 3, 4, 5, 6, a, b, c}
        
            --interation
            for index, value in ipairs(array) do
                print("index: " .. index .. "\tvalue: " .. tostring(value) .. '\n')
            end
            --[[
                output:
                index: 1    value: 1
                index: 2    value: 7
                index: 3    value: 2
                index: 4    value: 3
                index: 5    value: 4
                index: 6    value: 5
                index: 7    value: 6
                index: 8    value: a
                index: 9    value: b
                index: 10    value: c
        
            ]]
        
            --sorting
            table.sort(array)--sort in ascending order
            table.sort(array, function(a, b) return a > b end)--sort in descending order
      2. struct-like
            local person = {
                firstName = "Caesar",
                age = 19
            }
        
            --insertion
            person.lastName = "LEE"
            person [middleName] = "James"
        
            --removal
            person.age = nil--remove by setting to nil(logical removal)
        
            --iteration
            for key, value in pairs(person) do
                print("key: " .. key .. "\tvalue: " .. tostring(value) .. '\n')
            end
        
            --[[
                output:
                key: firstName  value: Caesar
                key: age    value: nil
                key: lastName   value: LEE
                key: middleName value: James
            ]]
        
            person.sort--sort alphabetically
      3. mixed tables
            local mixedTable = {
                1, 2, 3,--array part
                name = "Caesar"
            }
        
            print(#mixedTable)--3, only support for array part
            print(mixedTable [name])--Caesar
            print(mixedTable [3])--3
      4. userdata
        • struct in C/C++

type checking

```lua
    print(type(12))--number
    print(type(21.3))--number
    print(type(true))--boolean
    print(type("Hello"))--string
    print(type('World'))--string
    print(type(nill))--nil
    print(type({1, 2, 3}))--table
```

operators

arithmetic operators

math meaning C++ statement lua statement
$A + B$ A + B A + B
$A - B$ A - B A - B
$A \times B$ A * B A * B
${A} ^ {B}$ std::pow(A, B) A ^ B
$A \div B$ (integer) (int)A / (int)B A // B
$A \div B$ (double)A / (double)B A / B
$A \bmod B$ A % B A % B
$-A$ -A -A

bitwise operators

operation C++ statement lua statement
AND A & B A & B
OR A | B A | B
XOR A ^ B A ~ B
NOT ~A ~A
Shift Left A << B A << B
Shift Right A >> B A >> B

relational operators

math meaning C++ statement lua statement
$A = B$ A == B A == B
$A \neq B$ A != B A ~= B
$A &lt; B$ A < B A < B
$A \leq B$ A <= B A <= B
$A &gt; B$ A > B A > B
$A \geq B$ A >= B A >= B

logical operators

math meaning C++ statement lua statement
$A \land B$ A && B A and B
$A \lor B$ A || B A or B
$\lnot A$ !A not A

string concatenation

C++ statement lua statement
A + B A .. B

tips

  1. only false and nil are false(0 and "" are truthy)
  2. null is nil

control structures

if-else

    local num = 3
    if num > 0 then
        print("positive")
    elseif num < 0 then
        print("negative")
    else
        print("zero")
    end

ternary

    local function parity (n)
        if type(n) == "number" then
            return (n % 2 == 0) and "even" or "odd"
        else
            return "undefined type"
        end
    end

    local num = 2
    print(num .. " is an " .. parity(num) .. " number")

for

    for i = 1, 12 do--range is [start, end], include both ends
        if(i == 3 or i == 6 or i == 9)
            --lua doesn't support continue
            --use if-else to simulate it
            --or use goto (lua 5.2+)
        else
            print("i is " .. i .. '\n')--1, 2, 4, 5, 7, 8, 10, 11, 12
        end
    end

    local sum = 0
    for i = 5, 1, -1 do
        if(i == 3) then
            break
        end
        if i == 5 then
            goto continue--use goto alternaitive for continue
        end
        sum = sum + i
        print("sum is " .. sum)--4
        ::continue::
    end

generic for

  • equvalent to for-each in C/C++
    local array = {1, 2, 3}
    for index, value in ipairs(array) do
        print(tostring(index) .. ": -> " .. value)
    end

    local person = {
        firstName = "Caesar",
        lastName = "LEE"
    }
    for key, value in pairs(person) do
        print(tostring(key) .. ": -> " .. value)
    end

while

    local i = 0
    while i < 50 do
        print("i is " .. i)--[0, 49]
        i = i + 1
    end

repeat-until

  • quivalent to do-while in C++
    local i = 10
    repeat
        print("i is " .. i)--10, 9, 8, 7, 6, 5, 4, 3, 2, 1
        i = i - 1
    until i == 0

functions

basic function

return statements using ternaries

    function fibonacci(n)
        return n < 2 and 1 or fibonacci(n - 2) + fibonacci(n - 1)
    end

nested function

    function add(x)
        return function (y)
            return x + y
        end
    end
    local add12 = add(12)
    print("sum of 12 and 21 is " .. add12(21) .. '\n')--output 33

multiple return values

    local functionmin SumAvgMax (...)
        local args = {...}
        local min, sum, max = args [1], args [1], args [1]

        for index, value in ipairs(args) do
            if index ~= 1 then
                min = value < min and value or min
                max = value > max and value or max
                sum = sum + value
            end
        end

        return min, sum, sum / #args, max
    end

    local min, sum, avg, max = minSumAvgMax(12, 21, 30)
    print("minimum:\t" .. min)--minimum:    12
    print("sum:\t" .. sum)--sum:    63
    print("average:\t" .. avg)--average:    21
    print("maximum:\t" .. max)--maximum:    30

default parameters

    local functioni greet(name, greeting)--put default parameter at the end
        greeting = greeting or "Hello"
        print(greeting .. ' ' .. name)
    end

    greet("Caesar", "Fuck")--Fuck Caesar
    greet("CJL")--Hello CJL

anonymous functions(lua's lambda version)

return statements using ternaries (lambda version)

    local fibonacci = function(n)
        return n < 2 and 1 or fibonaicci(n - 2) + fibonacci(n - 1)
    end

sum of multiple values

    print(
        "sum is: " ..
        (function(...)
            local args, sum = {...}, 0

            for _, value in ipairs(args) do--_ means index
                sum = sum + value
            end

            return sum
        end)(1, 2, 3)
    )

metatables && metamethods

  • metatables allow you to change the behavior of tables
  • metatable is just a regular table with special keys(metamethods)
  • like a class where defines some special methods and operator overloads in C++
metamethod meaning C++ simular code
__index called when accessing a non-existent key operator[]
__newindex called when assigning to a non-existent key operator[], setter, etc
__call allow calling the table as a function operator()
__tostring define string representation (used bytostring() and print()) operator<<, std::to_string()
__add + operator operator+
__sub - operator operator-
__mul * operator operator*
__div / operator operator/
__idiv // operator(lua 5.3+) operator/ for integer types
__mod % operator operator%
__pow ^ oprtator operator^
__unm unary - operator unary operator-
__band & bitwise AND operator(lua 5.3+) operator&
__bor | bitwise OR operator(lua 5.3+) operator|
__bxor ~ bitwise XOR operator(lua 5.3+) operoator^
__bnot ~ bitwise NOT operator(lua 5.3+) operator~
__shl << shift left operaotor(lua 5.3+) operator<< (bit shift)
__shr >> shift right operator(lua 5.3+) operator>> (bit shift)
__concat .. operator operator+ for std::string
__len # operator size() or length() methods
__eq == and ~= operators operaotor== and operaotor!=
__lt < operator operator<
__le <=, >= operators operator<= and operator>=
__ipairs ipairs() function(lua 5.2+) begin(), end() iterators
__pairs pairs() function(lua 5.2+) begin(), ned() iterators
__metatable protect metatable, returned by getmetatable() encapsulaition + access modifiers
__gc garbage collection destructor ~ClassName()
__mode control weak tables(k, v, kv) std::weak_ptr
  • example: complete 2D/3D vector implementation
    --vector class
    local Vector = {}--empty table
    Vector.__index = Vector

    --constructor
    function Vector.new(x, y, z)
        if type(x) ~= "number" or type(y) ~= "number" then
            print("x and y must be numbers")
            return nil
        end

        if z ~= nil and type(z) ~= "number" then
            print("z must be a number or nil")
            return nil
        end

        local self = setmetatable({}, Vector)--like this
        self.x = x
        self.y = y
        self.z = z
        return self
    end

    --dimensions
    function Vector:is2D()
        return self.z == nil
    end

    function Vector:is3D()
        return self.z ~= nil
    end

    function Vector:getDimension()
        return self.z == nil and 2 or 3
    end

    --addition
    function Vector.__add(a, b)
        if getmetatable(a) ~= Vector or getmetatable(b) ~= Vector then
            print("cannot add with non-vector(s)")
            return nil
        end

        if a:getDimension() ~= b:getDimension() then
            print("cannot add with different dimension vector(s)")
            return nil
        end

        if a:is2D() then
            return Vector.new(a.x + b.x, a.y + b.y)
        else
            return Vector.new(a.x + b.x, a.y + b.y, a.z + b.z)
        end
    end

    --subtaction
    function Vector.__sub(a, b)
        if getmetatable(a) ~= Vector or getmetatable(b) ~= Vector then
            print("cannot subtract with non-vector(s)")
            return nil
        end

        if a:getDimension() ~= b:getDimension() then
            print("cannot subtract with different dimension vectors")
            return nil
        end

        if a:is2D() then
            return Vector.new(a.x - b.x, a.y - b.y)
        else
            return Vector.new(a.x - b.x, a.y - b.y, a.z - b.z)
        end
    end

    --scalar multiplication or dot product
    function Vector.__mul(a, b)
        --Vector * scalar
        if getmetatable(a) == Vector and type(b) == "number" then
            if a:is2D() then
                return Vector.new(a.x * b, a.y * b)
            elss
                return Vector.new(a.x * b, a.y * b, a.z * b)
            end

        --scalar * vector
        elseif type(a) == "number" and getmetatable(b) == Vector then
            if a:is2D() then
                return Vector.new(a * b.x, a * b.y)
            else
                return Vector.new(a * b.x, a * b.y, a * b.z)
            end

        --Vector * Vector
        elseif getmetatable(a) == Vector and getmetatable(b) == Vector then
            if a:getDimension() ~= b:getDimension() then
                print("cannot calculate dot product between different dimension vectors")
                return nil
            end
            if a:is2D() then
                return a.x * b.x + a.y * b.y
            else
                return a.x * b.x + a.y * b.y + a.z * b.z
            end

        else
            print("invalid parameter type")
            return nil
        end
    end

    --scalar division
    function Vector.__div(a, b)
        if getmetatable(a) ~= Vector and type(b) ~= "number" then
            print("invalid parameter type")
            return nil

        elseif b == 0 then
            print("cannot divided by zero")
            return nil

        else
            if a:is2D() then
                return Vector.new(a.x / b, a.y / b)
            else
                return Vector.new(a.x / b, a.y / b, a.z / b)
            end
        end

    end

    --unary minus
    function Vector.__unm(v)
        if v:is2D() then
            return Vector.new(-v.x, -v.y)
        else
            return Vector.new(-v.x, -v.y, -v.z)
        end
    end

    --magnitude
    function Vector.__len(v)
        if v:is2D() then
            return math.sqrt(v.x ^ 2 + v.y ^ 2)
        else
            return math.sqrt(v.x ^ 2 + v.y ^ 2 + v.z ^ 2)
        end
    end

    --magnitude squared
    function Vector:magnitudeSquared()
        if self:is2D() then
            return self.x ^ 2 + self.y ^ 2
        else
            return self.x ^ 2 + self.y ^ 2 + self.z ^ 2
        end
    end

    --equality comparison
    function Vector.__eq(a, b)
        if getmetatable(a) ~= Vector or getmetatable(b) ~= Vector then
            return false
        end

        if a:getDimension() ~= b:getDimension() then
            return false
        end

        if a:is2D() then
            return a.x == b.x and a.y == b.y
        else
            return a.x == b.x and a.y == b.y and a.z == b.z
        end
    end

    --less than
    function Vector.__lt(a, b)
        if getmetatable(a) ~= Vector or getmetatable(b) ~= Vector then
            print("cannot compare with non-vector(s)")
            return false
        end
        return #a < #b
    end

    --less than or equal
    function Vector.__le(a, b)
        if getmetatable(a) ~= Vector or getmetatable(b) ~= Vector then
            print("cannot compare with non-vector(s)")
            return false
        end
        return #a <= #b
    end

    --string representation
    function Vector.__tostring(v)
        if v:is2D() then
            string.format("v = (%.3f, %.3f)", v.x, v.y)
        else
            string.format("v = (%.3f, %.3f, %.3f)", v.x, v.y, v.z)
        end
    end

    --normalize, convert to unit vector
    function Vector:normalize()
        if self == Vector.ZERO
            print("cannot normalize zero vector")
            return nil
        end
        if self:is2D() then
            return Vector.new(self.x / #self, self.y / #self)
        else
            return Vector.new(self.x / #self, self.y / #self, self.z / #self)
        end
    end

    --cross product (3D only)
    function Vector:cross(v)
        if getmetatable(v) ~= Vector then
            print("cannot calculate with non-vector")
            return nil
        elseif not self:is3D() or not v:is3D() then
            print("cannot calculate with non-3D vector(s)")
            return nil
        else
            return Vector.new(
                self.y * v.z - self.z * v.y,
                self.z * v.x - self.x * v.z,
                self.x * v.y - self.y * v.x
            )
        end
    end

    --distance
    function Vector:distance(v)
        if getmetatable(v) ~= Vector then
            print("cannot calculate with non-vector")
            return nil
        elseif self:getDimension() ~= v:getDimension() then
            print("cannot calculate between different dimension vectors")
            return nil
        end
        return #(self - v)
    end

    --angle between vectors in radians
    function Vector:angle(v)
        if getmetatable(v) ~= Vector then
            print("cannot calculate with non-vector")
            return nil
        elseif self:getDimension() ~= v:getDimension() then
            print("cannot calculate between different dimension vectors")
            return nil
        elseif self == Vector.ZERO or v == Vector.ZERO then
            print("cannot calculate with zero vector")
            return nil
        else
            return math.acos((self * v) / (#self * #v))
        end
    end

    --project
    function Vector:project(v)
        if getmetatable(v) ~= Vector then
            print("cannot calculate with non-vector")
            return nil
        elseif self:getDimension() ~= v:getDimension() then
            print("cannot calculate between different dimension vectors")
            return nil
        elseif v == Vector.ZERO then
            print("cannot project onto zero vector")
            return nil
        else
            return v * ((self * v) / v:magnitudeSquared())
        end
    end

    --reflect
    function Vector:reflect(v)
        if getmetatable(v) ~= Vector then
            print("cannot calculate with non-vector")
            return nil
        elseif self:getDimension() ~= v:getDimension() then
            print("cannot calculate between different dimension vectors")
            return nil
        else
            return self - v * (2 * (self * v))
        end
    end

    --linear interpolation
    function Vector:linearInterpolation(v, t)
        if getmetatable(v) ~= Vector then
            print("cannot calculate with non-vector")
            return nil
        elseif self:getDimension() ~= v:getDimension() then
            print("cannot calculate between different dimension vectors")
            return nil
        else
            return self * (1 - t) + v * t
        end
    end

    --clamp magnitude
    function Vector:clamp(maximumLength)
        if #self > maximumLength then
            return self:normalize() * maximumLength
        end
        return Vector.new(self.x, self.y, self.z)
    end

    --static zero vector
    Vector.ZERO = Vector.ew(0, 0, 0)

string manipulation

function description
#s or string.len(s) length of str
string.upper(s) convert to uppercase
string.lower(s) convert to lowercase
string.sub(s, i, j) substring from i to j(1-based, inclusive)
string.find(s, pattern, [start], [length]) return start and end positions of match
string.gsub(s, old, new, [n]) replace matches of old with new
string.format(format, ...) formatted string, like printf in C
string.rep(s, n) repeat s n times
string.reverse(s) reversed string
string.char(n) convert numbers to chars
string.byte(s, [i], [j]) return numeric code at index (support negative)
string.match(s, pattern, [start]) return captured substring(s)
string.gmatch(s, pattern) return iterator for all matches

pattern characters

character regular expression description
. . any
%a [A-Za-z] letters
%c [\x00-\x1F\x7F] control characters
%d \d or [0-9] digits
%l [a-z] lowercases
%u [A-Z] uppercases
%w \w or [A-Za-z0-9] alphanumeric
%s \s or [ \t\n\r] space/writespace
%p [!-/:-@[-`{-~] punctutation
%x [0-9A-Fa-f] hexadecimal digits
%z \0 or \x00 null character
+ + $[1, \infty)$ repetitions
* * $[0, \infty)$ repetitions
- *? 0/more repetitions
? ? 0/1 repetition
^ ^ start of string
$ $ end of string
[set] [set] character set
[^set] [^set] complement of character set
() () capture group
%n(n = [0, 9]) \n(n = [0, 9]) captured string
%% % %

file I/O

read (default file operation)

read entire file

    local file = io.open("fileName", "r")--specify which file should be read
    if file then--if the file exists
        --read the whole content
        local content = file:read("*all")--read all content of the file
        --*a is ok
        print(content)--print the content
        file:close()
    end

read line by line

    local file = io.open("fileName", "r")
    if file then
        for line in file:lines() do
            print(line)
        end
        file:close()
    end

read specific amounts

    local file = io.open("fileName", "r")
    if file then
        local line = file:read("*line")--read one line
        --*l is ok
        local number = file:read("*number")--read a number
        --*n is ok
        local chars = file:read(12)--read 12 characters
        file:close()
    end

use io.lines

    for line in io.lines("fileName") do--automatically close file
        print(line)
    end

write

overwrite

    local file = io.open("fileName", "w")
    if file then
        file:write("content\n")
        file:close()
    end

append

    local file = io.open("fileName", "a")
    if file then
        file:write("content\n")
        file:close()
    end

file modes

mode description
r read(default)
w write(create if needed)
a append
r+ read and write(file must exist)
w+ read and write(create if needed)
a+ read and append
rb read binary
wb write binary
ab append binary

date and time

functions description
os.date([format], [time]) formatted date/time string, default is Fri Oct 10 08:59:30 2025
os.time([table]) timestamp in second since epoch (January 1, 2025 12:00:00 am)
os.difftime(laterTime, earlierTime) difference between two timestamps in seconds
os.clock CPU time used by program in seconds

format specifiers

specifier description
%a abbreviated weekday name
%A full weekday name
%w weekday as number([0 = Sunday, 6 = Saturday])
%b abbreviated month name
%B full month name
%m month as number
%d day of month
%j day of year
%Y year with century(4-digit)
%y year without century(2-digit)
%x date representation (10/13/25)
%H hour in 24-hour format
%I hour in 12-hour format
%M minute
%S second
%p AM or PM
%X time representation (18:21:12)
%c date and time(10/13/25 18:21:12)
%Z timezone name
%% %

math

function description
math.pi $\pi$
math.huge represent $\infty$
math.abs(n) $\lvert n \rvert$
math.max(n, ...) maximum value of all arguments
math.min(n, ...) minimum value of all arguments
math.sqrt(n) $\sqrt {n}$
math.sin(n) $\sin(n)$
math.sinh(n) $\sinh(n)$
math.asin(n) $\arcsin(n)$
math.cos(n) $\cos(n)$
math.cosh(n) $\cosh(n)$
math.acos(n) $\arccos(n)$
math.tan(n) $\tan(n)$
math.tanh(n) $\tanh(n)$
math.atan(n) $\arctan(n)$
math.atan2(y, x) $\arctan(\frac {y} {x})$
math.deg(n) convert n radians to degree
math.rad(n) convert n degree to radians
math.floor(n) $\lfloor n \rfloor$
math.ceil(n) $\lceil n \rceil$
math.pow(x, y) ${x} ^ {y}$
math.exp(n) ${e} ^ {n}$
math.fmod(x, y) $x \mod y$
math.modf(n) return integral part and fractional part of n
math.frexp(x) $x = m \times {2} ^ {e}$, and return m and e, e.g. $12.5 = 0.781,25 \times {2} ^ {4}$
math.ldexp(m, e) inverse of frexp
math.log(n) $\log_{e}(n)$
math.log10(n) $\log_{10}(n)$
math.random([m [,n]]) pseudo-random in $[0, 1)$ or $[1, m]$ or $[m, n]$
math.randomseed(n) set seed for the pseudo-random generator

error throwing and handling

throw an error

    local function biologicalGender(n)
        if not n then
            return "Male"
        elseif n == 1 then
            return "Female"
        else
            error("invalid biological gender code (NOT MENTAL GENDER)")--throw an error
            error("invalid biological gender code: " .. n, 2)--add a code level: 2
            error({
                type = "InvalidParameter",
                functionName = "biologicalGender",
                parameterName = "n"
                message = "invalid biological gender code"
            })--throw an error object

handle an error

pcall protected call

    local success, result = pcall(throwErrorFunctionName, ...)--optional arguments
    --success is a boolean variable
    --result is a returned value or error message
    if success then
        print("Success:\t", result)--executed successfully
    else
        print("Faild:\t", result)--caught an error message

xpcall extended protected call

  • an enhanced version of pcall
  • allow you to define a custom error handler
    local success, result = xpcall(
        throwErrorFunctionName,--target function
        function(err)--error handler
            print("\n============ Error  Handler ============")
            print("Error Messgae: " .. err)
            print("Stack Trace: " .. debug.traceback())
            print("============ END ============")
        end,
        ...--optionally proiiivide parameter(s)
    )

assert

    local function divide(numerator, denominator)
        assert(denominator ~= 0, "vivision by zero")--throw an error if false
        return numerator / denominator
    end