Skip to content

Instantly share code, notes, and snippets.

@crimeraaa
Last active January 3, 2024 14:33
Show Gist options
  • Save crimeraaa/1c00c85216dcae0b48d8218e63afb426 to your computer and use it in GitHub Desktop.
Save crimeraaa/1c00c85216dcae0b48d8218e63afb426 to your computer and use it in GitHub Desktop.
Lua + Programming Primer

1—Introduction

Hey there! What you're reading here is intended to be a "programming primer", that is it's meant to get you up to speed on basic concepts related to programming/coding/software development/whatever the hell you wanna call it.

1.1—Target Audience

This is aimed at absolute beginners who have had no prior programming experience. If you do have prior experience then great! This can be a refresher for you. For those more experienced/much smarter than I am, feel free to yell at me because I genuinely want this to be informative.

I've created this in the context of wanting to learn how to create mods for the game Don't Starve Together by Klei Entertainment. So I'll be mainly using the Lua programming language to explain stuff. I do quite like Lua since it's a rather simple but powerful language!

Since Klei themselves uses (a modified version of) the Lua 5.1.4 interpreter, I'll be also using that.

Before anything else, let's make sure we're all on the same page.

1.2—What the hell is programming?

Programming, coding, software development, these are all different terms (with their own unique meanings!) that have at least one thing in common: they're concerned with problem solving.

At the most general terms, that's all it is! Solving problems, specificially, with the computer. Generally speaking computers are very good at doing exactly what you tell them to. This is both a blessing and a curse: you have to be very particular with what you tell it to do when you code!

This means a couple of things.

  1. Functions and variables are usually case-sensitive.
  2. Mathemetical expressions like 0 < x < 14 can't (usually) be used as-is!
  3. Some errors are not immediately obvious to the program due to how a particular language is defined.
  4. The syntax (the grammar of a programming language) can be difficult to make sense of at first glance especially if this is your first time learning about all this.

1.3—Recommendations:

1.3.1—Programs

  1. Lua 5.1 Interpreter
  2. Any text editor! I'd recommend Notepad++ if you're on Windows.

TODO: Add specific instructions prolly

1.3.2—Resources

  1. CS50x by Harvard University

    A lot of my current understanding of computer science in general comes from this course! It does take quite a while to do and is very daunting at times, but I've learned so much even though I haven't finished it yet.

    Much of my explanations are adapted from CS50x 2023.

    Specifically, I frequently use Week 0 to Week 5 as personal reference.

  2. Programming in Lua (First Edition) (Roberto Ierusalimschy, December 2003)

    Because we're going to be using the Lua programming langauge, of course we'll be using their book as reference! Do note this version of the book was written for Lua version 5.0, and we're using 5.1.4.

    There are some differences between the two verisons, but generally you should be able to refer to this freely available online book anytime with no worry.


2—Your First Lua Program

For now, we won't be firing up DST. I want you to learn the most basic things first because DST has a lot of things that may be overwhelming to keep track of!

So we'll just be using your text editor, installed Lua interpreter, and the command line.

2.1—Hi mom!

  1. Using your favorite text editor, create a new file called hello.lua and save it wherever your like.

    For example, I'm on Windows 10. Let's say I've saved this file to C:\Users\crimeraaa\Documents\hello.lua.

    Here's the contents of the file should look like:

    print("Hi mom!")

    Be sure to type everything exactly as shown! We'll break down what this does in a bit.

  2. Open a terminal, e.g. open Command Prompt for Windows.

    NOTE: For everybody who isn't on Windows (MacOS, Linux, and more), replace backslashes \, with forward slashes /.

    On Linux/Unix-like systems, your user profile folder is usually going to be something like /home/crimeraaa, which you can shorten to just ~/.

    On MacOS, quick searching shows me that your user profile folder should be like /Users/crimeraaa. Using ~/, as Linux does, is also valid.

    Here's what my Windows Command Prompt looks like upon opening:

    C:\Users\crimeraaa>
    

    C:\User\crimeraaa is the so-called "current working directory". A directory is just a folder, so this means currently we're in my user folder!

    The > symbol is just to indicate the after here is where you can type commands.

    Other shells may use a different symbol. For example, Bash uses $ by default. Z shell (zsh) uses %.

  3. Let's now try the "change directory" command, cd.

    Don't press any other key just yet! First, press the spacebar. Then type the full location of wherever you saved hello.lua

    C:\Users\crimeraaa> cd C:\Users\crimeraaa\Documents
    

    NOTE: If your folder path has any spaces in it you'll probably need to wrap it in double quotes. like "C:\Users\crimeraaa\Path with spaces".

    Then press the <Enter> key. On Windows' Command Prompt, my terminal would look like this afterwards:

    C:\Users\crimeraaa\Documents>
    

    It's very important to change the text C:\Users\crimeraaa\Documents to the folder that you yourself saved hello.lua in!

  4. Now, type the command lua hello.lua.

    C:\Users\crimeraaa\Documents> lua hello.lua
    

    Then press the <Enter> key. The output should look something like this:

    C:\Users\crimeraaa\Documents> lua hello.lua
    Hi mom!
    C:\Users\crimeraaa\Documents>
    
  5. If everything went all, congratulations! You've just written your first lines of code!

2.2—Troubleshooting

2.2.1—lua is not recognized...

Although this message can vary across different terminals (e.g. Command Prompt has a different error message than PowerShell, which has a different error message than Bash, etc.) the idea remains the same: your terminal can't find the lua program!

Here's a few suggestions:

  • Did you install the Lua 5.1 interpreter yet?
  • Did you add the installation folder to your PATH enviroment variable?
  • Have you verified the program runs correctly?
  • Did you mistype anything?

TODO: explain how to set environment variables in Windows, for now please just search on your own, sorry!

2.2.2—attempt to call global...

This means that you probably mistyped a few things, here's some hints:

  • Is the word print is all lowercase? Remember, many programming languages are case-sensitive! print is treated differently from Print and PRINT.
  • Did you enclose the "Hi mom!" part in double quotes and parentheses? e.g. you should have print("Hi mom!), not print("Hi mom!" and not print(Hi mom!)

Side note: Lua actually allows us to omit the parentheses if there's only 1 argument.

As in: print "Hi mom!" is valid, but for our purposes we'll always enclose function calls in parentheses for consistency.


3—Breakdown

NOTE: Only read this section once you have the same output as me!

You just wrote the classic "Hello World" program, albeit with a slightly different message.

First off, we called a function named print. Now what on earth does calling a function mean?

3.1—Functions

Functions are sort of like sets of instructions. They're a way for us to tell the program how to do particular things.

The print function is a set of instructions, builf for us by the creators of Lua, that lets you write some output to your terminal. They've went through the trouble of figuring that out, so we don't have to!

How, though, does it know what to write? Well, you have to tell it what to do!

3.1.1—Calling

In order to use functions, we call them. Notice how we placed parentheses right after print? This is how call call functions. In many programming languages, the parentheses mean "hey, can you actually execute the set of instructions this function contains?"

So again, we called the print function like so:

print("Hi mom!")

But what's the "Hi mom!" message for? I said above, "you have to tell it (the print function, that is) what to do!". I was being very literal!

Functions, being sets of instructions, can be told what to do with so-called arguments, also known as parameters. For the rest of this document I'll refer to them as parameters, just so we're all on the same page.

We'll tackle later on how to make sense of other functions, how to create your own, and other things. However let's not get ahead of ourselves as that'll require understand other topics first!

3.1.2—Parameters

Many functions want to work with some data you provide for them. That's all parameters, are!

For the print function, when we called it, we passed the message "Hi mom!" to it. This tells the function that "hey, I have this message that I want you to print out".

So in our case, the message "Hi mom!" was the parameter that we passed to the print function for it to write out to the console.

3.2—Running

Let's look at the series of commands we used to run the file:

C:\Users\crimeraaa\Documents> lua hello.lua
Hi mom!
C:\Users\crimeraaa\Documents>

The command lua hello.lua is composed of two parts:

  1. lua: This is the Lua interpreter itself. Since this is the first command, it means lua is the program we want to run.
  2. hello.lua: This is file we pass to the Lua interpreter for it to, well, interpret. This is known as a command-line argument.

Because the Lua interpreter is a command-line program, it can only be run from the terminal, like an instance of a Windows Command Prompt. These programs don't provide a lot in visuals but they more than make up for it in sheer utility.

In this case, the Lua interpreter lets us pass Lua scripts we wrote. It then interprets the file. Specifically, through a complex process it "decodes" or "translates" what you wrote into a language your computer can run: binary. If you're familiar to .exe files, those are binary files! Specifically they are executable files, which you can, well, execute or run.

Actually, the Lua interpreter itself is written in the C programming language. So the interpreter you have is just an elaborate C program plus a linked library!

Specifically, if you learn how to compile it from source, compiling Lua via its Makefile create the Lua executable and a dynamic link library or a shared library file.

The dynamic link library is where much of the functionality is contained. Other C projects can link to it, with compiler flags, to make use of the Lua programming language, which is called embedding Lua.


4—Variables

Printing out a message to the console is nice, but what else can we do? Well let's learn about variables!

Variables are just one core building block of any programming language. They're basically pieces of data that we keep around, and maybe change them up as we go, but we may also choose to avoid changing them.

In Lua, we can declare a variable by typing out an identifier (a name!) and assigning a value to it.

Variable declaration is always in the format name = value.

Such names should start off with an alphabetical or an underscore character only. Starting with a number, e.g. 1x = 4 is not allowed, but placing a number after a first character, e.g. x1 = 4, is allowed.

Any other characters that are not alphabetical, numerical or underscore are not allowed. You cannot have spaces in a variable name. The only special character allowed is an underscore, meaning everything else like periods, question marks, exclamation marks, are all invalid.

For example, here are valid variable names:

x = 4
pi = 3.14
g = -9.81
msg = "Hi mom!"
x1 = 14.3
initialPrice = 1400
starting_rate = -0.00067
__warning = "Do not touch!"

These are invalid variable names:

1x = 4
x 2 = 4
hi! = "Hi mom!"
hello?there = "Hello there!"

4.1—Try it yourself!

Let's create a file called variables.lua. Following the steps back in the Your First Lua Program section, create that file anywhere you like but do remember the full path!

Write out these contents:

x = 1
print(x)
print(x + 1)
print(x > 14)

msg = "Hi mom!"
print(msg)

In the above code snippet I declare a variable named x, which contains the number value 1, and print it out.

After I've declared it, I can use the name x anywhere in my file!

Side note: Double dashes, --, are used as comments in Lua.

Comments are like little notes you can make for yourself or others. They don't (and shouldn't!) affect what the program does because most programming languages ignore them when running. Instead, they're for us humans!

Open your terminal, cd to the path you saved it in, and run the command lua variables.lua. If you need a refresher, go here. Otherwise here's a summary of the steps:

NOTE: The following are specific to Windows Command Prompt. Although cd is generally the same across shells, it's good to know your particular shell.

  1. Open your terminal if one isn't already open.

    C:\Users\crimeraaa>
    
  2. Go to the path where variables.lua was saved.

    C:\Users\crimeraaa> cd C:\Users\crimeraaa\Documents
    C:\Users\crimeraaa\Documents>
    
  3. Run lua variables.lua. Check your output to see if it's the same as mine!

    C:\Users\crimeraaa\Documents> lua variables.lua
    1
    2
    false
    Hi mom!
    C:\Users\crimeraaa\Documents>
    

4.2—Declaration

Declaration is the act of telling your program "hey, this is a piece of data I want you to know exists, and you should keep it around!". Variables are useful because we can assign values to them.

For example, let's say I want to do some calculations with the mathematical constant pi:

print(3.14)
print(3.14 / 2)
print(3.14 * 2)

What I wrote above does work, but it required me to repeatedly type the same value. If I wanted to change the calculations from pi to say, the acceleration of gravity, I'd have to change all occurences of the number 3.14!

Here's what my changed code could look like:

print(-9.81)
print(-9.81 / 2)
print(-9.81 * 2)

For this simple example, yes changing only 3 occurences of a value isn't much to whine about. But as you develop more complex projects, wouldn't it be nice to just change one thing and the effect would cascade?

That's where variables come in:

x = 3.14
print(x)
print(x / 2)
print(x * 2)

Now, instead of constantly typing out 3.14 over and over again, I can just use the variable name x to refer to it!

And if I want to change it to the acceleration of gravity, I just need to change the value of x:

x = -9.81
print(x)
print(x / 2)
print(x * 2)

But what's all this = business? Why do we need it and what does it do?

4.3—Assignment

Declaration is only one half of the puzzle, because it's just the act of saying "hey, this variable exists!". As is, it's not very useful until said variable has a value!

For example, this is still valid in Lua:

-- both of these have the value `nil`
x
msg

Although we declared variables named x and msg, they don't contain any values! As a result Lua assigns them a special value called nil which basically means "this value is empty".

nil is special in that, although you can print it and stuff like that, you cannot do any calculations with it, for example:

-- same as just typing: x = nil
x 
print(x)    
print(x + 2) 

The first print statement will simply output nil. The second print statement will actually throw an error!

This is because nil can be used for all sorts of things. For example I like use it to signify errors in calculations (like dividing by 0), but what exactly it means depends on the context!

Although we haven't tackled declaring and defining your own functions yet, here's just one example of how I'd use nil:

function remainder(dividend, divisor)
    if divisor == 0 then
        return nil
    end -- This is where I'd put my code... if I had any!!!
end

If you want to dive deeper into it, Programming in Lua—2.1 says:

"Nil is a type with a single value, nil, whose main property is to be different from any other value. As we have seen, a global variable has a nil value by default, before a first assignment, and you can assign nil to a global variable to delete it. Lua uses nil as a kind of non-value, to represent the absence of a useful value."

So before anything else, remember this: nil isn't the number 0, the boolean false, the empty string "", or anything like that. It's a whole separate value, for lack of better word.

Variable assignment comes to the rescue! You can assign a value to a specific variable with the = symbol.

x = 3.14
msg = "Hi mom!"

You can also assign long after a declaration:

-- nil here, print will also output nil
x
print(x)

-- now assign value of 3.14, print will now output 3.14
x = 3.14
print(x)

And it's as simple as that! Of course, things can get more involved once scope and functions come into play. Another thing that messes up new Lua programmers in the fact that Lua lets you declare global variables on the fly literally anywhere. We'll tackle that in a bit.

Exercise

What are the variables here? What are the values of each?

pi = 3.14
r = 2
area_circle = pi * r^2
area_sphere = 4 * pi * r^2

Hint: * is for multiplication, ^ is for exponentiation/raising to the power of something.

Explanation

The above code was equivalent to:

-- Given the following:
-- pi = 3.14
-- r = 2

-- area_circle = pi   * r^2
--               pi   * r^2
--               3.14 * 2^2
--               3.14 * 4
area_circle = 12.56

-- area_sphere = 4 * pi   * r^2
--               4 * pi   * r^2
--               4 * 3.14 * 2^2
--               4 * 3.14 * 4
--               12.56    * 4
area_sphere = 50.24

Remember, we (for lack of better word) substituted the variables pi and r into the equations needed to get area_circle and area_sphere.

In reality, substituted isn't quite the correct word. Understand that requires understanding a bit about how your computer uses memory to both read and write data!

Perhaps I'll write another post dedicated to giving an overview of computer memory, but for now we can work with this simplification.

4.4—Concepts

In math (at least where I come from), it was taught to us that we should declare variables like this:

$$ \begin{align*} \text{let }x &= 4 \\ y &= 2x + 3 \\ \end{align*} $$

In line 1, we declared a variable called $x$ which contained the numerical value $4$.

In line 2, we have an equation $y=2x+3$, meaning the variable called $y$ is equivalent to the result of that equation.

By substituting $4$, which is the value of $x$, into the equation, we can run the calculation step by step to determine the value of the variable $y$:

$$ \begin{align*} y &= 2x + 3 \\ y &= 2(4) + 3 \\ y &= 8 + 3 \\ y &= 11 \\ \end{align*} $$

In Lua (and programming in general) the same idea applies! Let's try to translate the above equation into Lua:

x = 4
y = 2 * x + 3
print(y)

Note how we can't type y = 2x + 3 literally, because in many programming languages the star * symbol is the only way to multiply.

Exercise

Do you know why y = 2 * x + 3 gives us 11 and not 14?

Explanation

Many programming languages (but not all!) follow a specific kind of order of operations, also known as operator precendence. What this means that there's a specific set of rules you have to follow to get a consistent output.

Perhaps you've heard of $\text{PEMDAS}$, or $\text{BODMAS}$? It means that, the following operations that are higher up are evaluated (calculated!) before lower operations, and operations of the same priority or precedence are evaluated from left to right.

$$ \begin{align} &\text{Parentheses, Brackets, Braces} \\ &\text{Exponenents} \\ &\text{Multiplication, Division} \\ &\text{Addition Subtraction} \\ \end{align} $$

So in y = 2 * x + 3, we first calculate 2 * x, because multiplication is higher up than addition. In this case we get 8.

We then do y = 8 + 3 to get 11 because the last remaining operation is addition with no more higher-priority operations to do.

Lua generally follows the above pattern, along with some of its own language-specific operations. Programming in Lua—Chapter 3.5 has a good overview.

We'll tackle things like relational operators, boolean operators, and their associated expressions later on. But for now, just keep $\text{PEMDAS}$ in mind so we're all on the same page.

4.2.2—Scope

If you've read at least some Lua code before you may have stumbled across stuff like this:

local pi = 3.14
local r = 2
local area_circle = pi * r^2
local area_sphere = 4 * pi * r^2

What's local for? Why is it everywhere? Why do we even need it?

This is where the concept of variable scope comes into play. Imagine it like the "visibility" of ability of other files or other functions (depending on the context) to see this variable.

4.2.2—Example 1

function tocelsius(fahrenheit)
    -- also in farenheit
    local freezing_point = 32 

    -- Derived from 100 C / 180 F:
    -- https://www.cuemath.com/c-to-f-formula/
    local change_ratio = 5/9 

    print(freezing_point)
    print(change_ratio)

    return (fahrenheit - freezing_point) * change_ratio
end

tocelsius(95)

print(freezing_point)
print(change_ratio)

Again, we haven't tackled functions very much in depth yet, but I hope you can follow along. We have two so-called "local" variables in the tocelsius function: freezing_point and ratio_of_change.

This means that only the tocelsius function can "see" and "poke at" these variables. Notice the print calls? When they're inside the function, they'll print out the correct values. But outside the function, they'll just print nil!

Exercise

Why do you think the print calls output nil when they're outside the tocelsius function?

Explanation

Outside the tocelsius function, we don't have any variables named freezing_point and change_ratio! So trying to print variables with those names outside the function just results in Lua bascially telling us "hey, I don't see any such variables!"

4.2.3—Example 2

The oppposite of "local" scope is "global" scope. With "global" scope, any file and function can see this variable. Lua by default makes all declared variables global unless you use the local keyword.

Now try removing the occurences of local and add some print more statements:

function tocelsius(fahrenheit)
    -- When this function is called, these 2 are declared as globals!
    freezing_point = 32
    change_ratio = 5/9 

    print(freezing_point)
    print(change_ratio)

    return (fahrenheit - freezing_point) * change_ratio
end

print(freezing_point)
print(change_ratio)

tocelsius(95)

print(freezing_point)
print(change_ratio)

Exercise

What's the output of the print statements before calliing tocelsius? How about after? Why do you think the output is the way it is?

Explanation

Before the function call, freezing_point and change_ratio weren't declared anywhere by anybody, so Lua doesn't know they exist! Hence the output is nil.

However, after the function call, since we didn't use the local keyword, we creating global variables called freezing_point and change_ratio. Because they were decalred as global, anyone can see, use and even change them!

4.2.3—Example 3

local function circle_area(r)
    local pi = 3.14
    return pi * r^2
end

TODO: explain in depth about pi's "locality", usefulness of "local" functions

5—Function

5.1—Declaring

You can declare your own function with the function keyword, followed by a name, followed by parameter names enclosed by parentheses. After this you can define the function body, where the calculations and such occur. Finally, you close the function body with the end keyword.

It'll make more sense with examples, so let's look at a stripped down version of the previous tocelsius function:

function tocelsius(f) -- `f` stands for fahrenheit
    return (f - 32) * 5/9
end

Here we declared a function, named tocelsius. It takes 1 parameter than we simply called f and returns some calculation that represents the conversion from Fahrenheit to Celsius.

5.2—Return Values

Before anything else, let's tackle what exactly a return value is!

TODO: tired rn, will explain when I work on this again

5.3—Parameters

Earlier I discussed basic stuff about functions like calling them and passing arguments or parameters to them. In the Variables section I dicussed a bit about variable scope.

function tocelsius(f)
    return (f - 32) * 5/9
end

Just what is f doing here exactly? Is it a variable, or something else?

f is an argument or a parameter. Earlier I mentioned how these are passed when calling a function to give it data to work with, and this is no different!

When you call the tocelsius function, you need to give it something to convert! Just writing tocelsius() won't work because you gave it no value to work with.

5.4—Scope

Think of parameters as local variables to the function. The main difference is, they are "assigned a value" only when function is called.

Parameters follow the same naming restrictions as any variable: they must start with an alphabetical character, they can have numbers after that, but otherwise must only comprise of alphabetical, numerical and underscore characters.

As is, the f parameter doesn't have a set value. This is important because f, as a parameter, can take on literally any value! It all depends on what you give the function everytime you call it.

function tocelsius(f)
    return (f - 32) * 5/9
end

print(tocelsius(95))
print(tocelsius(32))
print(tocelsius(212))
print(tocelsius(180))
print(tocelsius(451))

Exercise

What are the values of the fahrenheit parameter for each of the function calls? What are the outputs of each call to tocelsius?

6—Types

There are several so-called types of variables in Lua.

This means that there are different kinds of values. Numbers are treated very differently from text, for example.

Do note that because Lua is dynamically-typed, this means that what type a variable has is inferred.

In other languages, like C, we'd have to specify the type for each and every variable and function, like this:

/* `int` = `integer`, meaning whole numbers only */
int x = 1; 

/* `float` = `floating-point number`, it approximates fractional values. */
float pi = 3.14; 
float g = -9.8;

/* In C, strings are really just "arrays of characters". */
char msg[] = "Hi mom!";

/* This is a function which takes a `float` and gives you back a `float`. */
float tocelsius(float f) {
    return (f - 32) * 5/9;
}

Fortunately for you, you're not programming in C. In Lua, things are much shorter:

x = 1 -- a simple integer
msg = "Hi mom!" -- a simple string
pi = 3.14 -- ratio of circle's circumference to its diameter
g = -9.81 -- acceleration of gravity (m/s)
c = 299792458 -- speed of light (m/s)

For now, these are the ones you'll be most concerned with:

5.1—string

A line of text which is always enclosed by double or single quotes. For consistency, I'll only use double quotes. This is also a habit of mine from the C programming language, because there we can only use strings when they're enclosed by double quotes.

Strings are useful because they can represent all kinds of stuff! From names, places, messages, they're even useful in stuff like cryptography.

Here's how you declare strings:

greeting = "Hi mom!"

-- https://en.wikipedia.org/wiki/Pangram
pangram = "the quick brown fox jumps over the lazy dog"

If you have a very long string, consider using multi-line strings which use a pair of double square brackets [[...]]:

rhyme = [[
She sells seashells by the seashore,
The shells she sells are seashells, I’m sure.
So if she sells seashells on the seashore,
Then I’m sure she sells seashore shells.
]]

There's a few quirks that you need to be aware of when using these.

TODO: explain this lol but so many things to deal with first!!!

5.2—number

TODO: Lua numbers are just double from C (usually)

x = 1 -- a simple integer (well, not really, floats *can* represent ints!)
pi = 3.14 -- the mathematical constant used in equations involving circles
g = -9.81 -- acceleration of gravity, negative to show it goes downwards!

5.3—boolean

TODO: very brief overview of boolean algebra, Lua's implementation quirks

5.4—table

TODO: explain arrays as mathematical sets, hashtables/dictionaries

-- an array
local set = {2, 3, 5, 7, 11, 13, 19, 23}

-- a hashtable (a.k.a associative array, key-value pairs)
local weekdays = {
    ["Monday"] = 1,
    ["Tuesday"] = 2,
    ["Wednesday"] = 3,
    ["Thursday"] = 4,
    ["Friday"] = 5
}

5.5—function

TODO: explain declaration, arbitrary parameter names, return values

5.6—nil

TODO: express the idea that nil means "nothing" or "nothing useful"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment