---------------------------------- -- Journey to speed learn Lua -- -- Created by Jack Meng (exoad) -- ---------------------------------- --[[ Extra Materials: 1) https://www.lua.org/manual/5.1/manual.html 2) http://lua-users.org/wiki/TutorialDirectory This guide covers the expanse of most modern Lua dialects. Starting from 5.2 ]] ------------------------ --[[ SECTION BASICS ]]-- ------------------------ --[[ This is a comment, comments come in single line and multiline, all of which will be ignored during runtime. This is a multiline comment which starts with --[[ and ends with two "]" ]] -- This is a single line comment, start a line with "--" and the statements after that line will be counted as comments. print() --[[ The print() function is a primitive Input/Output function in Lua. By running it with print() no arguments, nothing is printed. By running it with arguments like print("Hello World") -> Hello World will be printed to the ]] --[[ Indentations and spacing are only enforced for specific codes and you will know when. For most of the time, indentation is not enforced while spacing between terms are enforced. My vocabulary: "raw-value" or "value" -> Literally raw values, like 1, 2, "3", "hello" -These are not variables -They can be return resultants from a function "variable" -> A raw value that has a name attached to it All reserved words & operators in Lua: and,break,do,else,elseif,and,false,for,function,if,in,local,nil,not,or,repeat,return,then true,until,while,+,-,*,/,%,^,#,==,~=,<=,>=,<,>,=,(,),{,},[,],;,:,,,.,..,... ]] ----------------------- --[[ SECTION TYPES ]]-- ----------------------- type() --[[ The type() function is primitive to get the type of the argument. By running it with an argument like type("Hello") it outputs "String" because "Hello" is a String type(0) -> Number type(true) -> Boolean type(nil) -> nil type("Tree") -> String ]] print(nil) print(type(nil)) --[[ nil represents the absence of type. Using print(type()) in conjunction allows you to print the type of said value to STDOUT Think of it as "null" in some other languages ]] print(1) print(3) print(1.3) print(-3) --[[ These are "Number" types. Numbers can be negative, floating point, positive, integer, aka number; ]] print("Hello world") print("Hi!") --[[ These are "String" types. Which can be enclosed with double quotes("") or single quotes('') ]] print(true) print(false) --[[ These are booleans, which represent two binary values "true" or "false" ]] --------------------------- --[[ SECTION VARIABLES ]] -- --------------------------- myVariable = nil --[[ Declaring variable follows the format of: variableName = typeValue -variableName is the literal variable's name a.) Can contain numbers, letters, and underscores b.) Cannot start with a number (0-9) -typeValue is the value to set the variable to a.) This value is a value associated to a type, for example "myVariable = 123" would mean myVariable holds a "Number" type with value 123 -The assignment operator "=" When you are unsure of the starting value, always declare the variable to "nil" - Variables may be reassigned for example: var_a = 1 var_b = 3 var_a = var_b <- Here var_a would have a value of 3 and var_b with a value of 3 ]] print(myVariable) type(myVariable) --[[ Using variables in functions is as simple as passing their name into a function ]] tonumber() --[[ This is a handy method for when you want to convert something to a Number type (String-Number or Number). If it can't it would return "nil" For example tonumber("HI") -> nil tonumber("32") -> 32 ]] tostring() --[[ This is a handy method to convert the argument to a String format. For example tostring(false) -> "false" tostring(nil) -> "nil" ]] --------------------------------- --[[ SECTION DATA STRUCTURES ]]-- --------------------------------- myTable = {} --[[ This is the only default data structure in Lua. This can be associated as an array or a table. When treated as a table, we use mostly non-numeric String based keys. When treated as an array, we use numeric based keys. [!] VERY IMPORTANT. REMEMBER THAT LUA TREATS THE FIRST INDEX OF A TABLE/ARRAY AS "1" NOT "0" There are no generic array types in Lua and thus we can mix both numeric and String based key values in Lua. They are declared with "{}" We can access and manipulate content within the structure using keys. Done so by following the format: 1| table[key] = value ^^^^^ Notice the usage of the "[]" square brackets ]] myTable["myIndex"] = 100 -- Key "myIndex" set to "100" in table "myTable" myTable[1] = 20 -- Key "1" set to "20" in table "myTable" --[[ To access data, we use the same format, except now we don't use the assignment operator "=" If no data is present with that key, Lua will return "nil". We can also delete values by just setting them to "nil" ]] print(myTable[1]) -- 20 -> Based on what we set above print(myTable["thisKeyExists?"]) -- nil -> We did not set anything for this key above myTable.myIndex = 30 --[[ This is the other method to select values, this is the same as: 1| myTable["myIndex"] ^^^^ Note this is not the same as myTable[myIndex] ]] myTable2 = {first = 1, second = 2} --[[ Initialization can be done so this way, which would be the same as: 1| myTable2 = {["first"] = 1, ["second"] = 2} ]] myArrayTable = {53, 34, 12} --[[ This can also be declared as 1| myArrayTable = {[1] = 53, [2] = 34, [3] = 12} ]] print(myArrayTable[1]) -- Would print "53" because the first element is 53 print(#myArrayTable) --[[ This gives us the length of all numerical keys (integer keys) in the array. The "#" operator for this operation: 1| print(#{["hi"] = 1, 32, 4}) -- 2 Because "hi" is not numeric and will not be counted ]] ----------------------------------------- --[[ SECTION ARITHMETICS & Operations]]-- ----------------------------------------- --[[ Basic Arithmetics can be used on Number types and between Number types but never between any other. Operators can be used between variables or raw values. Operators are: + -> For addition | 2+3 => 5 - -> For subtraction | 2-3 => -1 * -> For multiplication | 2*3 => 6 / -> For division | 2/3 ~> 0.67 ^ -> For exponent (power) [NOT BIT XOR] | 2^3 => 8 % -> For remainder (modulo) | 2%3 => 2 - -> For value Negation | -(2+3) => -5 THERE ARE NO POSTFIX OPERATORS IN LUA (i++, c--, ++a, ++b), thus you have to do (for maybe incrementation by 1): my_Var = my_Var + 1 Operator Precedence (highest [top] to lowest [bottom]) "^" "not" "*","/" "+","-" "<",">","<=","=>","==","~=" "and" "or" [!] Same operator precedence at the same level is executed based on their placement from left to right ]] print("Hello World" .. "Its me") --[[ String concat, or adding two strings together is accomplished using the ".." operator Using this operation you can also "coerce" other types to a "String", take for example: 1| var_Num = 3 2| print("My Number: " .. varNum) -- Effectively coerce "var_Num" to be a String via String concat ]] print("Hello World" + "its me") --[[ String concat can also be achieved via the "+" addition operator and also has the same effects as ".." However, remember that when both sides are numbers, the standard addition operation is performed. Note: print(1 .. 3) -> 13 print(1 + 3) -> 4 ]] ----------------------- --[[ SECTION SCOPE ]]-- ----------------------- --[[ Scope can be defined as something's prevalence or visibility somewhere. Hypothetical code: 1| var b 2| block 3| var a 4| end Variable "b" is alive from lines 1-4 while variable "a" is only alive within 2-3 (exclusive) In Lua, a variable without a scope modifier is referred to as a global variable. To avoid conflicts between naming we add the "local" scope modifier in front of a variable name: 1| local myVariable = nil ^^^^ Local variables only exist in their permitted block and nowhere else. [!] Every loop iteration and function call creates a new scope with new variables. [!] Generally you want to use "local" specifiers as much as possible in order to: 1. Make your code more readable 2. Create less trap variables that can lead to undesired behaviors. ]] local t = 3 t = t + 2 -- Here t is referenced and a global reference is not created. local function f() end local f2 f2 = function() end local f3 = function() end --[[ This section from lua-users.org: """ The difference between the last two examples is important: the local variable still doesn't exist to the right of the = that gives it the initial value. So if the contents of the function used f to get a reference to itself, it will correctly get the local variable in the first and second versions, but the third version will get the global f (which will be nil, if not a completely unrelated value set by some other code). """ ]] --[[ Closures are the effect that functions (local or not) can use locale variables outside; these functions are therefore referred to as "closures" -The function sees any changes to the variable outside ]] -------------------------------------- --[[SECTION CONTROLS & CONDITIONS ]]-- -------------------------------------- --[[ For any boolean expression Lua treats: nil, false -> FALSE Anything else -> TRUE Statements like variable assignment are not treated as valid boolean expressions ]] myBool = true myVar = 3 if myBool then -- Note for boolean expressions, we don't have to do "myBool == true", you can, but is less concise print("I AM HUNGRY") end --[[ The most basic conditional statement is the "if" which follows the following format: 1| if boolean-condition then -- If condition declaration 2| todo -- Statement to run on boolean condition for true 3| end -- Marks the end of this case -If "boolean-condition" is true, then the "todo" code runs In the above code snippet, since "myBool" is true, then the program will print "I AM HUNGRY" ]] if myBool then print("I am hungry") else print("I am not hungry") end --[[ When we want to introduce a "default" case, we add the else keyword for when the other conditions fail. There can only be one else statement per conditional statement [!] Note that the "else" statement does not utilize a "then" keyword ]] if myVar == 2 then print("2") elseif myVar >= 3 then print(">=3") else print("?") end --[[ We can add intermediate conditional cases using the "elseif [expr] then" expression which is just an "if" statement but renamed. Processing this conditional block goes from top to bottom. We could've also ended the entire conditional block at the "else-if" with an "end" and not include a default "else" case ]] if myVar == 2 then print("My var is 2!!") end --[[ Conditional operators are used to validate certain things. They are parsed as "boolean expressions" that express "booleans". Here are all of the conditional operators in Lua: X > Y -> X greater than Y X >= Y -> X greater than or equal to Y X < Y -> X smaller than Y X <= Y -> X smaller than or equal to Y X == Y -> X equals Y X ~= Y -> X does not equal Y [!] When used with strings, they are evaluated based on the string's alphabetical ordering [!] Type coercion does not work thus a statement like 1| print("1" == 1) ^^^ Returns false, and thus you have to use tonumber() or tostring() [!] When used with objects, they are not equal if: 1. The types are different 2. Refer to different objects ]] if not myVar == 3 then print("Not 2") elseif myBool and myVar == 2 then print("Both!") elseif not myBool or myVar == 2 then print("Maybe") else print("?") end --[[ Lua also provides conditional operators: not and or -"not" we simply evaluate -> "not X" expresses to "true" if X is "false" and vice versa -"and" we simply require both sides of the expression to evaluate to true before the statement is fully evaluated as "true" -"or" we simply require only one side of the expression to evaluate to true before the statement is fully evaluated as "true" [!] Note when we ever "not" a Number or String, Lua always returns "false" ]] if myVar and (not true or myVar == 3) then end --[[ Take this hypothetical piece of conditional statement. Since operations/operators have precedence, we can use "()" to group expression to make them be evaluated first, thus increasing their precedence artificially. This format also increases readability ]] myLuaElvis = 1+1==2 and "Yes" or "No" --[[ Implementation of a basic Ternary operation in Lua. Lua does not support Ternary or Elvis Operators for one line IF-ELSE ]] --[[LOOPS]]-- while myBool do print("Hello World") end --[[ This is a basic LOOP. A while loop uses a boolean condition to make the loop run. Only until the expression evaluates to false, does the loop exit. They follow this format: 1| while [bool-expr] do <- Loop declaration 2| todo <- Blocks of code to execute 3| end <- Signifies the end of the loop definition ^^^^ for [bool-expr], you can treat it like any expression you would use in IF-ELSEIF-ELSE statements. ]] repeat print("Hello world") until myBool --[[ Similar to a while-loop except that it runs until the condition after "until" becomes true Follows this format: 1| repeat <- Loop declaration 2| todo <- Blocks of code to execute 3| until [bool-expr] <- Signified the end condition [!] This loop is similar to a DO-WHILE loop in other languages in which it is guaranteed to run at least once and then check the condition after the first iteration. ]] for i = 1, 10, 1 do print(i) end --[[ This is a numerical-step for-loop, it follows a similar fashion in many other languages. It uses two formats: 1| for var = start, end, step do 2| todo <- Code to execute 3| end <- Loop End Declaration ^^^ Start -> the start counter End -> the end value to reach (if "var" is greater than end, the loop exits) Step -> How much to increment per iteration. This can also be negative, but will mean that the loop will end only if "var" is less than "End" 1| for var = start, end do 2| todo <- Code to execute 3| end <- Loop End Declaration ^^^ This modification assumes "step" to be "+1" In some languages like Java, C/C++, JavaScript, this kind of loop can be represented as: ----- Lua ----- | ----- Other Langs. ----- 1| for i=0,10 do | 1| for(var i=0;i<=10;i+=1){ 2| end | 2| } [!] Scoping does not allow the variable in a numerical step loop to be used outside of the loop block ]] myTable = {1,2,3,4} for key,val in ipairs(myTable) do print(key.." "..val) end --[[ This is an iterator based loop. Primarily used for looping through a data structure like that of a Table/Array. They follow this format: 1| for var1,var2,var3...varN in [itr-expr] do 2| todo <- Code to execute 3| end <- Loop End Declaration [itr-expr] represents a valid iterator function. Most commonly we use the "ipairs()" function to retrieve a key and value pair. ]] --[[ Control statements for loops are: break -> jump out of the innermost loop goto [expr] -> tell the program to go to this point in the program - Goto a expression can be marked with "::[expr-name]::" Like so: 1| while true do 2| if someVar > 100 then 3| goto outside 4| end 5| end 6| ::outside:: ]] myVarA = 1 while true do myVarA = myVarA + 1 if myVarA > 5 then break end end -- BREAK PATTERN DEMONSTRATION --------------------------- --[[ SECTION FUNCTIONS ]]-- --------------------------- function fun() print("Function!") end --[[ Functions declare snippets of code that can be called and reused later. Functions follow the format of: 1| function functionName(args) <- The function start 2| todo <- Stuffs to execute 3| end <- The end of the function declaration (similar to a conditional block) ends with "end" ^^^^^ The function start also contains "args" which can be left blank or provided with a required argument to take. This is called function input/argument. Within the stuffs to execute section, you can write code like any other part of the program we talked about before. However, if you use parameters (described below), those are treated as a variable ]] function add(a, b) return a + b end print(add(1, 2)) --[[ Functions can take input via adding variable names in between the parentheses. This now means add() requires two arguments in order to work The "return" statement is straightforward, it represents a possible result the function wants to return. However we cannot specify anything else after this "return" statement -If no "return" statement is found, the function will not return a resultant: function m(a,b) c = a+b end print(m(1,2)) -> Nothing or "" -This is similar in many other languages: Kotlin/Java, C/C++, JavaScript/TypeScript -The resultant should be treated as a "raw" value -The only exception to no other statements after "return" is for conditional blocks: ]] function conditionalReturns(a,b) if a + b > 10 then return a + b - 10 elseif a + b == 10 then return a + b + 10 end return a+b end --[[ Conditional returns can have their return statements wrapped up. Note the ending "return a+b". This is the "else" case. You can write an "else" case within the conditional block, but it is not necessary, since if all other cases fail, the program will jump out and use the last return. ]] fun() --[[ Similar to calling print() and type() calling any other function is as simple as: functionName(args?) args? -> If argument required ]] myFunction = function(a,b) return a * b end print(myFunction(a,b)) -- We can also declare functions to variables and call them like this --[[ Builtin functions are functions that come pre bundled with Lua. Previously the "type()" and "print()" were builtin functions. But there are more. "tonumber","tointeger" etc are also built in You can find them by starting with "math.", "string." There are a lot, so we will not mention all of them. Check them out here: http://lua-users.org/wiki/TutorialDirectory Examples: ]] string.len("Hello World") -- Gets the length of a provided string string.upper("yooooo") -- Makes all characters in a string to uppercase (abc -> ABC) string.lower("yoink") -- Makes all characters in a string to lowercase (ABC -> abc) math.min(2, 30000) -- Returns the smallest of the two math.max(2, 30000) -- Returns the largest of the two math.random(0, 100) -- Returns a pseudorandom number from [start, end) where "end" is excluded and "start" is included ----------------------------------------------- --[[ SECTION METAPROGRAMMING & METAMETHODS ]]-- ----------------------------------------------- --[[ Metamethods in Lua enables the programmer to overload certain functionalities in Lua objects. -Similar to the concept of operator overloading in a language like C++ Metables is a regular Table data structure that holds metamethods that can be called when specific events in Lua are called. Like for example addition ("+" -> __add) To make a metatable work, we can use the following exemplar: ]] local myObj = { attribute = ":)" } local myMetaTable = { __add = function(lhs, rhs) -- here lhs -> LeftHandSide & rhs -> RightHandSide return { value = lhs.attribute .. rhs.attribute } -- the desired operation for when the "+" or addition operation is performed on -- two objects of myObj end } setmetatable(myObj, myMetaTable) -- tell Lua we have a new metamethod we want to add --[[ Thus we can now do things like 1| local myObjFinal = myObj + myObj 2| print(myObjFinal) ]] --[[ You can find a list of most overloadable metamethods that you can use here: http://www.lua.org/manual/5.4/manual.html#2.4 ]] ------------------------- --[[ SECTION MODULES ]]-- ------------------------- --[[ [!] THIS IS TARGETED FOR 5.2 AND ABOVE Modules are the most basic way for Lua code to be bundled and used together. You declare in the following fashion: -- BEGIN: file testModule.lua -- 1| local testModule = {} -- Note you don't specifically have to name the module the same as the file name 2| function testModule.someFun() print("This is a test") end 3| return testModule -- END: file testModule.lua To use this module in some other file, we use the "require" keyword: -- BEGIN: file testMyModule.lua -- 1| tM = require 'testModule' 2| tM.someFun() -- prints "This is a test" Modules are cached in the global table "package.loaded". -this also provides us the ability to reload: 1| package.loaded.someModule = nil 2| m = require 'someModule' You can think of this as a JavaScript Module.exports: 1| const pkgCode = require("myCodeHere") Or you can think of it as a C++ Namespace, Java Import/Class/Module You can also selectively expose modules contents: 1) Declare certain functions as local 2) This format: 1| local function f1() ... end 2| local function f2() ... end 3| return { f1 = f1, f2 = f2 } 3) Combine 1 & 2: 1| local me = {} 2| local function f1() ... end 3| local function f2() ... end 4| me.f1 = f1 5| me.f2 = f2 6| return me There are some other ways: http://lua-users.org/wiki/ModulesTutorial (using Environment Variables) ]] ------------------------------------ --[[ SECTION OBJECT ORIENTATION ]]-- ------------------------------------ --[[ Even though Lua is not OOP and there are no Class constructs, we can imitate using metatables. We can achieve this by creating a table that represents a "class" and have metatable attributes like "__index" and then we can use modular functions to create instance methods We often use the "self" semi-keyword to refer to the current instance. When we create a function with the first argument as "self" Lua automatically has a feature where if you want access a member method within the instance, you can use the ":" operator to work on this instance without having to call: 1| myInstance.func(myInstance, args) Instead you can just do: 2| myInstance:func(args) For example: ]] local ExampleClass = {} ExampleClass.__index = ExampleClass -- if default lookup fails we daful to table lookups for method names function ExampleClass.new(init) -- a function equivalent to a constructor local self = setmetatable({}, ExampleClass) self.value = init return self end function ExampleClass.set_val(self, val) self.value = val end --Usage-- local exampleClass_E = ExampleClass.new(5) exampleClass_E:set_val(1) -- value is now 1 --[[ This seems quite similar to how some other languages do their OOP-ish modularity, like JavaScript, Python, etc.. ]]