Lua Tutorial

Lua is the programming language used by Kristal. In this tutorial, we'll go over the basics of Lua, and how to use it.

Using Lua


For this tutorial, we are going to be testing using the official online Lua demo.

This is a great place to try the language out.

PART 1: The Basics


Lua is a very simple language. It is very easy to learn, and is very powerful.


Strings, And Your First Program

First, let's start with a very, very simple program:

print("Hello, world!")

Type this in the box, and press the "run" button. You'll see the output in the box below. Congratulations, you've made your first Lua program!

So, what's happening here? Well, let's break it down in parts.

  • print - This is a function name. Functions are chunks of code which do something. In this case, the print function prints out whatever you give it.
  • "Hello, world!" - This is a string. Strings are a sequence of characters. In this case, it's the text we want to print. Strings are denoted by quotation marks.
  • () - These are parentheses. They are used to call functions, and to input data into one. In this case, we gave print a string, being "Hello, world!".
  • "" - These are quotation marks. They are used to denote strings.

Strings are a very important part of Lua. They are used to store text. They can be used to store anything, from a single letter, to a whole book. They are very, very useful.


Numbers

print(5 + 10)

This will print out 15. This is because 5 + 10 is 15. Lua can do math!

And this brings us to another data type: numbers. Numbers are, well, numbers. They can be integers (whole numbers), or decimals (numbers with a decimal point). Lua can do math with numbers.

print(0.1 + 0.2)

This will print out 0.3.


Variables

Variables are another important part of Lua. Variables are used to store data. Let's try this:

local x = 5
print(x)

This will print out 5. This is because we stored the number 5 in the variable x, and then printed it out.

Let's try another example:

local x = 5
local y = 10
print(x + y)

This will print out 15. This is because we stored the number 5 in the variable x, and the number 10 in the variable y, and then printed out the sum of x and y.

Yes, it works with strings as well!

local x = "Hello!"
print(x)

This will print out Hello!. This is because we stored the string Hello! in the variable x, and then printed it out.

But what happens if we put nothing in them? Or, how we even do that? Well, that's where nil comes in:

local x
print(x) -- prints nil

-- OR --

local x = 10
print(x) -- prints 10
x = nil
print(x) -- prints nil

nil is the absence of a value.


Comments

Comments should be everywhere in your code. They are used to explain what your code does, and why it does it. They are also used to disable code.

print("Hello!")
-- print("Goodbye!")

This will ONLY print out Hello!. This is because the second line is commented out. This means that it is ignored by the interpreter.

Comments are denoted by --. Anything after -- on a line is ignored.

You can also make a comment span multiple lines:

--[[
This is a comment.
It spans multiple lines.
]]

Strings, Continued

Strings are actually a bit more complicated than what we've seen so far. For example, this is a valid string:

[[
This is a string.
It spans multiple lines.
]]

That looks a lot like multiline comments, right? This is a long string, which spans multiple lines. Instead of using ", we use [[ and ]] to denote a multiline string!

Additionally, you can use strings like 'this' instead of "this". They are the same thing.

But wait, if you have a string like "this", how do you put a quotation mark in it? Well, you can use \" to put a quotation mark in a string. For example:

print("\"Hello!\"")

This will print out "Hello!". This is because we used \" to put a quotation mark in the string.

What if we wanted to add two strings together? Well, we can use .. to do that:

print("Hello" .. " world!")

This will print out Hello world!. And of course, you can use variables in strings:

local x = "Hello"
print(x .. " world!")

This will print out Hello world! as well.


Booleans

Conditions are a very important part of programming. They are used to check if something is true or false. For example:

if 5 > 10 then
    print("5 is greater than 10!")
end

This will print nothing. This is because 5 > 10 is false. > is a comparison operator. It compares two values, and returns true or false. In this case, 5 > 10 is false, so the code inside the if statement is not run.

So where does the boolean type come in? It's simple. Everything between if <...> then is evaluated, and then the result is used.

So, doing 5 > 10 alone gives us false! We can even store this in a variable:

local x = 5 > 10 -- false
local x = false  -- false
local x = true   -- true

And then we can use it in an if statement:

local x = 5 > 10 -- false
if x then
    print("5 is greater than 10!")
end

And of course, this prints nothing!


Functions

Functions are used to store chunks of code, and to run them later. For example:

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

print(add(5, 10))

This prints 15, because we defined a function called add, which takes two arguments, a and b, and returns a + b. Then, we called the function with 5 and 10, and printed the result. Functions are very useful. They allow you to reuse code, and to make your code more readable.

There's a massive list of built-in functions, and yes, print is one of them! We've been using a function this entire time.

Functions, like any other data type (like strings, numbers, etc) are actually able to be stored in variables.

This is the exact same as the example above:

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

Using a function like this is called an anonymous function. This is because the function has no name. It is stored in the variable add.

You can even store named functions in variables as well:

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

local add2 = add

add2 is the exact same as add!


Tables

Tables store data. Think of them as a list of values. For example:

local x = {
    "One",
    "Two",
    "Three"
}

This creates a table with three items. The first item is "One", the second item is "Two", and the third item is "Three". We can access these items like this:

print(x[1])

Using [index] on a table will return the item at that index. In this case, it will print One, because the first item is "One".

We can also add items to a table:

table.insert(x, "Four")

This will add "Four" to the end of the table. Now, x[4] is "Four".

But be careful, because anything outside of the table's length will return nil:

print(x[5]) -- nil
print(x[0]) -- nil
print(x[500]) -- nil

You can get the length of a table using # before the variable:

print(#x) -- 4

Tables, Continued

Tables can also have keys. Keys are used to access items in a table.

local x = {
    one = "One",
    two = "Two",
    three = "Three"
}

To access these items, we use the key:

print(x["one"])

This'll print One, because the key one is "One".

We can also use the dot operator to access items:

print(x.one)

This is the exact same as x["one"]! You can even chain them:

local x = {
    one = {
        two = {
            three = "Three"
        }
    }
}
print(x.one.two.three)

This will print Three!


Loops

Loops are used to repeat code. For example:

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

This prints out every number from 1 to 10. This is because the for loop repeats the code inside of it 10 times. The variable i is used to keep track of how many times the loop has run. It starts at 1, and goes up to 10.

What if we want to skip every other number? Well, we can use the step argument:

for i = 1, 10, 2 do
    print(i)
end

See that 2? That's the step, being how much i increases every loop.

We can also use the while loop:

local i = 1
while i <= 10 do
    print(i)
    i = i + 1
end

while loops until the condition is false. It's like mixing for and if together! Be careful you don't accidentally make an infinite loop, though:

while true do
    print("Oh no!")
end

This function loops forever, because true is always, well, true!


Putting It All Together

Let's make a program which takes a table of numbers, and prints out the sum of all of them:

local function sum(x)
    local total = 0
    for i = 1, #x do
        total = total + x[i]
    end
    return total
end

local x = { 1, 2, 3, 4, 5 }

print(sum(x))

Let's break it down:

  • First, we make a function. This function takes a table of numbers, and returns the sum of all of them.
  • In that function, we make a variable called total, which is used to store the sum of all the numbers.
  • We use a for loop to loop through every number in the table.
  • We add the number to total.
  • We return total.
  • Outside of the function, we make a table of numbers.
  • We call the function with the table, and print the result.

And that's it for the basics! That's... quite a bit, right? Don't worry, it's not as complicated as it seems. Well, yet, anyway.


Scopes And Local Variables

We've been using the keyword local all over the place, what gives?

Well, local is used to make a variable local to a scope. A scope is a chunk of code. For example:

local x = 5

if true then
    local x = 10
    print(x)
end

print(x)

The first x is local to the entire program. The second x is local to the if statement. This means that the first x is not affected by the second x.

So, we get the following output:

10
5

Scopes can contain other scopes. For example:

local x = 5

if true then
    print(x) -- prints 5
    local y = 10
    print(y) -- prints 10
end

print(y) -- prints `nil`, because the variable doesn't exist in this scope!

That's it for the basics! You can now make simple programs, and you know how to use variables, functions, and tables. It's good that's all there is, right? ...right?

PART 2: The Intermediate


We've already gone over tables, and even had two sections of them in the last part. They can't have much more, right?

Well, here's an entire part dedicated to them. Oh boy.


Tables, Continued, Continued

You don't always have to loop through a table by using a for loop and an index. You can also use ipairs:

local x = {
    "One",
    "Two",
    "Three"
}

for index, value in ipairs(x) do
    print(index, value)
end

This gives us the following output:

1	One
2	Two
3	Three

Which is exactly what we wanted. ipairs is used to loop through a table, and get the index and value of each item.

If your table has keys, you can use pairs:

local x = {
    one = "One",
    two = "Two",
    three = "Three"
}

for key, value in pairs(x) do
    print(key, value)
end

Which gives us:

two     Two
three   Three
one     One

That's... interesting. It wasn't in order! That's because tables are not guaranteed to be in any sort of order.

Due to the nature of list-like tables, those'll always be in order. But tables with keys are not guaranteed to be in any sort of order.


Functions in Tables

Tables can store functions. For example:

local x = {
    add = function(a, b)
        return a + b
    end,
    subtract = function(a, b)
        return a - b
    end
}

Pretty simple, right? We just call them like this:

print(x.add(5, 10))
print(x.subtract(5, 10))

This prints out 15 and -5, respectively.


Class-Like Tables

Since there's no class system in Lua, tables themselves are used as classes. For example:

local x = {
    name = "Bob",
    age = 10,
    sayHello = function(self)
        print("Hello, my name is " .. self.name .. "!")
    end
}

x:sayHello()

This prints out Hello, my name is Bob!. But what's with the colon (:)? Well, that's used to pass the table as the first argument to the function. This is called syntactic sugar. It's just a shortcut for this:

x.sayHello(x)

This is the exact same as the example above. It's just a shortcut.


Varargs, Multiple Returns, and Destructuring

Functions can return multiple values. For example:

local function addAndSubtract(a, b)
    return a + b, a - b
end

local x, y = addAndSubtract(5, 10)
print(x, y)

This prints out 15 -5. This is because the function returns two values, a + b and a - b. We can store these values in two variables, x and y, and then print them out.

But what if we only want one of the values? Well, we can use _ to ignore the value:

local x, _ = addAndSubtract(5, 10)
print(x)

This prints out 15. This is because we ignored the second value, and only stored the first one.

(Technically, it's not ignored. It's stored in a variable called _, which is just a variable name. It's just standard practice to do this.)

Now, we come to varargs. Varargs are used to pass an unknown amount of arguments to a function. For example:

local function add(...)
    local total = 0
    for _, value in ipairs({...}) do
        total = total + value
    end
    return total
end

print(add(1, 2, 3, 4, 5))

This prints out 15. This is because we passed 5 arguments to the function, and it added them all together.

We can use ... in the following ways:

-- Pass in all of the arguments to another function

local function test(...)
    print(...)
end

-- Convert the multiple arguments into a table
local function test(...)
    local x = {...}
    print(x[1])
end

-- "Destructure" the arguments
local function test(...)
    local x, y, z = ...
    print(x, y, z)
end

Destructuring is getting the first few arguments, and ignoring the rest. We actually did destructuring earlier, did you notice?

local function addAndSubtract(a, b)
    return a + b, a - b
end

local x, y = addAndSubtract(5, 10)
print(x, y)

This is destructuring the return values of the function. We're getting the first two values, and ignoring the rest. In this case, there are only two values, so we're getting all of them.

PART 3: The Advanced


Metatables, metamethods, coroutines