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, theprint
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 gaveprint
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.