Comprehensive Lua
Comprehensive Lua
Introduction to Lua
Note: Many of the examples given in this document are created from examples and code on the website Lua.org or on Lua-users.org or from the book Programming in Lua by Roberto Ierusalimschy (third Edition) Some of these programs have been changed so they are more understandable or use thoughts from all three sources as well as many other sources on the web. See the references in the document for the various sites that have contributed to the examples in the document.
History
- Development started in 1993
- Designed to be integrated with C and C++
- It is simple, extensible, portable, and efficient
- Used in embedded systems, mobile devices, games, etc.
Goals
- Simplicity - a small and simple language
- Automatic memory management (versus C and C++)
- Extensibility - through Lua code and external C code
- Efficiency - one of the fastest languages
- Portability - runs on almost all platforms
References and Tutorials
- Wikipedia on Lua
- The Lua website
- Lua Documentation
- Lua Tutorial at TutorialsPoint
- Lua.org tutorial
- Lua Tutorial at luatut
- Lua Tutorial Directory at lua-users.org
- Examples of Lua code at Gammon.com
- Main Directory at lua-users.org
Installing an IDE
- There are several Lua IDEs (Integrated Development Environments)
- Two of these are
- Lua Development Tools (based on Eclipse)
- ZeroBrane (Standalone IDE)
Installing Lua Development Tools
Eclipse Lua Development Tools Standalone Download
- Quick Instructions
1. Download the Lua Development Tools Standalone Product from the link above 2. Unzip the tool zip file into a folder called Eclipse Lua (so the files will have a folder) 3. Create a directory where everything related to Lua can be placed A common practice is to put this on the root of the C disk in Windows 4. Create a folder called workspace in the same folder where you placed Eclipse Lua Note that this is the author's preference and not a required methodology 5. On Windows go into Eclipse Lua and right click on the LuaDevelopmentTools.exe 6. Using "Send To" on windows put a shortcut to the executable on the desktop 7. To start the tools double click on the desktop icon on Windows 8. Choose the workspace you created when asked
Installing ZeroBrane
- Quick Instructions
1. Go to the Zerobrane website using the link given above 2. Go to the Download page given on the website 3. Choose whether you want to support Zerobrane or Choose the link "Take me to the download page this time" 4. Create a folder to hold the all files related to Zerobrane A common practice is to put this on the root of the C disk in Windows It should be a location that can be found later 5. Choose the download for the type of computer If you choose the Windows exe file put it in the folder created in the last step 6. Both the zip file and the exe file for windows give an extracted folder The exe file for windows sets some folder preferences so have it in the correct location before executing it Move the extracted zip file to the correct location if using the zip 7. Go into the ZeroBraneStudio folder once it is extracted (zip) or created (exe) 8. Using "Send To" on windows put a shortcut to the executable on the desktop 9. To start the tools double click on the desktop icon on Windows
LuaRocks
- The Lua package manager
- In a later section LuaRocks will be installed, and used
- LuaSockets will be used as an example of a LuaRock
- In a Lua and C program that will do networking
- LuaSockets will be used as an example of a LuaRock
References
Basic syntax and semantics
Identifiers
- Any string of letters, digits, and underscores
- Letters in ranges A-Z and a-z
- Cannot start with a number
- Avoid identifiers starting with an underscore followed by an uppercase letter
- reserved for special uses in Lua
- Do not use reserved words in the language i.e. for, while, if, else, etc.
- In general
- use meaningful names
- use camel case to allow the reading of meaningful names
Types
- The main types in the language are:
- Numbers - represent real numbers in the IEEE 754 standard
- Any operation with a result that gives an exact representation
- must always give that representation
- The IEEE 754 standard makes integers unnecessary
- Strings - a sequence of characters
- Strings are immutable values
- When modifying a string a new string is always created
- Boolean - have values false and true
- Conditionals consider false and nil to be false values
- Anything else is true
- nil
- nil is a non-value representing the absence of a useful value
- Functions - a function can be assigned to a variable
- Table - an associative array
- The type function gives the type of a variable
print (type("Hello")) -- gives string print (type(true)) -- gives boolean local a = 10 local b = type(a) -- gives number print (b) print (type(b)) -- gives string
More on Strings
Literal Strings
- Can use either single (') or double (") quotes
- The other type of quote can be used within the string
Escape Sequences
\a - bell \b - back space \f - form feed \n - newline \r - carriage return \t - horizontal tab \v - vertical tab \\ - backslash \" - double quote \' - single quote
Long Strings
-- a long string longString = [[This is a very long string that goes on and on]] print (longString) -- another long string anotherString = [===[This is another long string that has some double brackets [[]] in it]===] print (anotherString) print("This is a string\nthat goes on also")
Coercions
- There are automatic coercions between numbers and strings
Tables
- Tables are associative arrays
- Can be indexed with number or strings (common way of doing this)
- or with other values of the language except nil
- Can be indexed with number or strings (common way of doing this)
- Tables are the main data structures of the language
- The only primitive data structure
- They are dynamically allocated
- A program has references to them
- They are created by a constructor expression
a = {} -- creates a table and puts a reference in a a["earth"] = "imageEarth.jpg" -- put the filename of an image into a at key "earth" print(a["earth"]) -- prints the string "imageEarth.jpg" a[10] = "imageMars.jpg" print(a[10]) print("The earth image is " .. a["earth"]) -- use the alternate syntax for a["earth"] print("The earth image is also " .. a.earth) local image = "earth" print ("This also gives the earth image " .. a[image]) -- assigning functions to variables (be careful) printFunction = print printFunction("this is a string") a["printIt"] = print a.printIt("Printing using an index into a table reference")
Table Constructors
-- The simplest constructor a = {} -- an empty table print(a[1]) -- table indexed by integers b = {"Earth", "Mars", "Saturn", "Jupiter"} print (b[1]) -- table with string indices c = {x="Earth", y="Mars", z=10} print(c.x) print(c["x"]) print(c.z) -- nesting tables within tables d = {x="Piano", y={"Trumpet", z="Violin"}} print(d.x) print(d.y[1]) print(d.y.z)
References
Expressions and Statements
- Expressions are numeric, relational or string
- Each has a set of operators that are used in their creation
Arithmetic expressions operators
addition (+) subtraction (-) multiplication (*) division (/) Exponentiation (^) Modulo (%) Unary negation (-variable)
Relational operators
Less than (<) Greater than (>) Less than or equal (<=) Greater than or equal (>=) Equal (==) Not Equal (~=)
Logical Operators
and or not
Precedence Rules
- Arithmetic, relational, and logical operators
- follow the standard precedence rules
Assignment
- The equals sign is assignment
- Assigns a value to a memory location
variableName = value
- left-hand side is a reference to a memory location
- right-hand side evaluates to the value (can be a function reference)
String Concatenation
a = "This is a " .. "string concatenation"
Length of String and Table with integer indices
- The length operator is the hash (#)
-- string concatenation and length a = "this is a string " .. "concatenation" print(a) b = #a print("The string length is " .. b) -- length of a table with integer indices b = {"Earth", "Mars", "Saturn", "Jupiter"} print (#b)
Comments
- Similar to most programming languages
- Lua has single line and block comments
Single Line Comments
- starts with a double hyphen -- and runs to the end of the line
-- This and the following line are commented out -- if (a > b) then a = b end
Block Comments
- Starts with --[[
- runs until an ending ]]
--[[ if a < b then c = 0 d = a + b end ]]
Global Variables
- Variables that are not declared as local in a block
Declarations of variables is not required in Lua This means that misspelling etc. can cause errors in the program. The misspelling will be taken as a proper identifier with a value of nil. Various ways have been suggested to correct this problem.
Ways of detecting undeclared variables
Local Variables
- Created using the local statement
- scope is limited to the block in which they are declared
- Block scope
- Body of a control structure
- Body of a function
- Body of the chunk (the file)
- do end block
- Block scope
- Local scope overrides global scope
Basic control structures
- Control structures generally include
- Looping
- Conditional branches
The while and repeat loops
- A while loop and a repeat loop have three required parts
- A statement that initialize a loop variable
- A while or repeat condition that tests the loop variable eventually ending the loop
- A statement within the loop that changes the loop variable so that the loop eventually ends
- The while loop tests its conditon at its start and may never execute its body
- The repeat loop always executes once and tests its loop condition at its end
- The basic structure of the while is the following:
-- set up a loop variable -- while conditionTestingLoopVariable do -- body of the loop -- change the loop variable -- end -- an example of the while loop local i = 1 local a = {"Oak", "Pine", "Cherry", "Fir"} while a[i] do print (a[i]) i = i + 1 end
- The basic structure of the repeat loop is the following
- Note that in this case the loop always runs once
- Which in this case prints nil to the console
- a[5] is nil since it is beyond the initialized portion of the table
-- set up a loop variable -- repeat -- body of the loop -- change the loop variable -- until conditionTestingLoopVariable -- an example of the repeat loop local i = 5 local a = {"Oak", "Pine", "Cherry", "Fir"} repeat print (a[i]) i = i + 1 until a[i]
The for loop
- There are two types of for loops in Lua
- Numeric for loop - usually use integers as the loop variable
- Generic for loops - use iterators to traverse a data structure
- The basic structure of the numeric for loop is the following
- Note that this returns oak and cherry as the results
- Because of the step of 2 in the loop variable
- Note that this returns oak and cherry as the results
--for var = exp1, exp2, exp3 do -- body of the loop that does something --end -- exp1 is used to initialize the loop variable -- which is local to the for loop block -- exp2 is used to test against the value of the loop variable -- exp3 tells how much to step the loop variable after each loop -- An example of a for loop local a = {"Oak", "Pine", "Cherry", "Fir"} for i = 1, 4, 2 do print(a[i]) end
Generic for loop
- Loops using an iterator
- The generic for loop traverses all values return by an iterator for a data structure
- We will see more data structures to which this could apply in later sections
- The function pairs returns the index and value of each element of a table in order
local tableDays = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"} for k,v in pairs(tableDays) do print(k, v) end
Conditionals if then elseif else end
- Lua has the standard forms of if and else
- The syntax is not c or java like however
The form of the if statement is if conditional then -- your code goes here end The form of the if else statement is if conditional then -- your if code goes here else -- your else code goes here end
A simple example of all three statement types is given below
local number1 = 57 local number2 = 43 local number3 = number1 + number2 if number1 > number2 then print ("number1 is greater") end if number1 < number2 then print ("number2 is greater") else print ("number1 is greater") end if number3 < number1 then print ("number1 is greater than number3") elseif number3 < number2 then print ("number2 is greater than number3") else print ("number3 is greater than both of the other numbers") end
Using break return and goto
- The "break" statement is used exclusively to break out of a loop
- The "return" statement is used to return from a function
- It is not required if the function returns naturally
- It does not require a return value
- The "goto" statement jumps execution to a "label"
The syntax of the "goto" and "label" are given below
goto myLabel -- some code goes here ::myLabel:: -- some code goes here
Note: Misuse of the goto causes spaghetti code so that care must be taken in using it. Originally it was the common way of looping. Many languages have dropped the "goto" as a construct because of the many problems that it causes with the concept of structured code.
Using do end to delimit blocks
blocks for declarations of local variables can be delimited by do end
do local a = 9 some other code end
Exercise on Control Structures
- Hands on Exercise
Write a program using Eclipse and Lua that will do the following This is inspired by a project in which inappropriate words had to be removed from the text of user blog postings. We will just use Tables with words for both the text and the check words. This makes it simpler. Note: in this exercise use words such as "and", or "the" or "tree" as the check words so that others can view your work. Usually it is bad form to but actual swear words into programs. The case mentioned above being the exception. 1. Create two tables that are initialize with words use numeric indices note that this can be done using tableText = {"This", "is", "my", "table"} 2. Using a while loop compare the text word table against the check word table 3. If check words appear in the text words then remove them from the text words using table.remove 4. Keep the check words found in the text words in a new table 5. Use a generic for loop to print out the check words that matched 6. Use a for loop to concatenate the text works back into a string with spaces between the text words then print out the text Note that concatenation of words is a poor way of building text since concatenation causes new strings to be built on each operation. The alternative is to use table.concat(tableIdentifier)
Functions
- The main mechanism for abstraction
- Can carry out a task
- Can return values
- A Function has the form
function name (parameter list) code goes here ... return optional end
- parameters work as local variables
- With function block scope
Variadic Functions
- A variadic function receives a variable number of arguments
An example of a Variadic Function
local a = 7 b = 8 c = 9 d = 5 function add(...) local sum = 0; for i,v in ipairs{...} do sum = sum + v end return sum end print (add(a, b)) print (add(a, b, c, d))
Returning multiple results
- Functions can return multiple results
function multipleResults (list of parameters) code goes here ... return variable1 variable2 variable3 end
- The returned results are adjusted to match the call
-- set up some local variables local i = 5 local j = 4 local k = 8 -- the example function function example1(a, b, c) local var1 = a + 5 local var2 = b + 1 local var3 = c + 10 return var1, var2, var3 end -- call the function and return one value local result1 = example1(i, j, k) print ("result1 is " .. result1) -- call the function and return three values local result2, result3, result4 = example1(i,j,k) print ("result2 is " .. result2) print ("result3 is " .. result3) print ("result4 is " .. result4) result1, result2, result3, result4 = 21,22, example1(i, j, k) print ("* result1 is " .. result1) print ("* result2 is " .. result2) print ("* result3 is " .. result3) print ("* result4 is " .. result4) result1, result2, result3, result4 = example1(i, j, k), 21,22 print ("- result1 is " .. result1) print ("- result2 is " .. result2) print ("- result3 is " .. result3) print (result4) -- returns a nil value
Exercise on Functions
- Hands on Exercise
Using Eclipse do the following: 1. Get an operation using io.read() operations are add, sub, mul, div - Save the operation in a table 2. Get two numbers from the user using io.read() - Save the operands in a table 3. Create a function that will perform the operation - The first number will be the numerator for division - If division will divide by zero then return a nil - otherwise return the result 4. Save the result - If there was an error then give an error message (in case of div by 0) 5. print out the result and ask the user for another operation and one number - The result of the last operation will be the other number (the first) 6. Continue this looping until the user enters bye as the operation 7. Output all of the operations and operands in an understandable format to the console - Using print() - include the total 6. do some calls to exercise your function 7. Single step through the code using the Eclipse Debugger
Named Arguments
- Arguments are passed in Lua by position
- First argument goes to first parameter etc.
- Arguments can be given names by instead passing a table
- The table will have name value pairs
- Thus the arguments being in a table can have associated names
An example of passing both types of arguments is given below
local arguments = {planets = {"Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune"}, stars = {"Sun", "Rigel", "Altair"}} local celestialType = "planets" function printCelestialType (argTable, printType) local theType = nil if printType == "planets" then theType = "planets" else theType = "stars" end tableToPrint = argTable[theType] for index, value in pairs(tableToPrint) do print (value) end end printCelestialType(arguments, celestialType)
Closures
- A function enclosed within another function
- has access to all of the local variables of the enclosing function
Exercise on Closures
Trace the following code in a debugger
- 1. Determine how name and ages are used in each function
- 2. Discuss and explain to the group
local names = {'Carol', 'Jess', 'John', 'Daniel' } local ages = {Carol = 20, Jess = 14, John = 41, Daniel = 63} table.sort(names, function (name1, name2) return ages[name1] > ages[name2] -- ages is an upvalue here, can be seen end) for i = 1, #names do print(names[i]) end names = {'Barbara', 'Ben', 'Harry', 'Jen' } ages = {Barbara = 20, Ben = 14, Harry = 41, Jen = 63} function sortByAge (names, ages) -- references names and ages are local to sort table.sort(names,function (name1, name2) -- names and ages can be seen within sort return ages[name1] > ages[name2] -- ages can be seen within anonymous function end) end sortByAge(names, ages) print("\n\n\n") for i = 1, #names do print(names[i]) end names = {'Barbara', 'Ben', 'Harry', 'Jen' } ages = {Barbara = 20, Ben = 14, Harry = 41, Jen = 63} function sortByAge () -- call has two parameters neither of which are used function ageCompare(name1, name2) -- ages and names are upvalues here return ages[name1] > ages[name2] -- ages can be seen within ageCompare end table.sort(names,ageCompare) -- references names and ages can be seen within sortByAge end sortByAge(names, ages) -- passed in names and ages in the call but they are not used print("\n\n\n") for i = 1, #names do print(names[i]) end
- In the examples the ages variable within the function is called a non-local variable
- It is not global and it is not local
- This is sometimes referred to as an upvalue
- It can be seen because of the scoping rules of Lua
Closure definition - A closure is a function plus all that it needs to access non-local variables correctly
Trace through the following code using the debugger
- 1. Determine how the closures work
- 2. Explain and discuss
function counter () local i = 0; return function () i = i + 1 return i end end theCounter = counter() print(theCounter()) -- the function reference continues to refer to the same function print(theCounter()) -- and its local variables (including its parameters) -- iterators and closure function iterator(theList) local i = 0 return (function () i = i + 1; return theList[i] end) end local numbers = {"Earth","Mars","Saturn","Venus"} iter = iterator(numbers) -- get a reference to the function while true do local theItem = iter() -- use the function if theItem == nil then break end print (theItem) end
Tail Calls
- A tail call happens when a function makes a function call in its return
- No other work must be required after the function returns
- return theFunction(x, y, z) would be a proper tail call
- return theFunction(x, y, z) + 1 would not be a proper tail call
- No other work must be required after the function returns
- This can be used for recursion when it function calls itself in its return
- It can be used to return the result of another function
In computer science, currying is the technique of transforming a function taking multiple arguments into a function that takes a single argument (the first of the arguments to the original function) and returns a new function that takes the remainder of the arguments and returns the result.
- Reference Curried Lua
- By the use of currying a function with several parameters can be modified
- Into several functions with single parameters
The following is an example of currying to reduce the number of parameters per function
function test1 (x, y, z) -- add three number with three parameters return x + y + z end print (test1(5, 4, 3)) function f1 (x) -- add three numbers with one parameter per call, curried return function (y) return function (z) return x + y + z end end end f2 = f1(5) -- calling the curried functions f3 = f2(4) print (f3(3))
References
Exercise on Functions (Polynomial Evaluation)
Note that this exercise is taken from "Programming in Lua" by Roberto Ierusalimschy
- Hands on Exercise
A polynomial can be written as y = anxn + an-1xn-1 + ... + a0x0
Write a function that performs polynomial evaluation as a function with a curried tail call. A. This function will take one parameter that will be a table of the "a" coefficients B. A curried tail call is a call that returns a function C. Generally that function is written as an anonymous function as part of the return statement D. The returned function will take one parameter that will be the value of the variable "x" E. The polynomial evaluation will be in the anonymous function
Data Structures Using Tables
Tables in Lua
- Tables are associative arrays
- Basically Hash Tables
- Name value pairs
- What is a hash
- Why is a hash fast
- Tables are the only actual data structure in Lua
- All other structures are made using them
Arrays
- Arrays are sequential tables
- without nils at intermediate locations
- Arrays are made up indexing tables by integers
- Customarily arrays in Lua start at 1
- You can start them at any integer index
- The length hash (#) works with arrays
array1 = {}; for i=1, 10 do array1[i] = "value" .. i end for i=1, 10 do print(array1[i]) end print("\n\n\n") -- using a constructor to initialize and array array2 = {"value1", "value2", "value3", "value4",} for i=1, #array2 do print(array2[i]) end
Multi-Dimensional Arrays
- Multi-dimensional arrays are created with tables
- Basically they are arrays of arrays
array1 = {} for i=1, 4 do array1[i] = {} for j=1, 4 do array1[i][j] = "value ".. i .. " " .. j end end for i=1, 4 do for j=1, 4 do print(array1[i][j]) end end
Linked Lists
- Linked Lists are created with Tables
- Each element is an array with fields next and value
function addToHead (linkedList, itemValue) if linkedList == nil then local temp = {next="endOfListFlag", value = itemValue} return temp else local temp = linkedList linkedList = {next=temp, value = itemValue} return linkedList end end local linkedList = nil linkedList = addToHead(linkedList, "first element on list") linkedList = addToHead(linkedList, "Second element on list") linkedList = addToHead(linkedList, "Third element on list") linkedList = addToHead(linkedList, "Fourth element on list") local root = linkedList local done = false; while not done do print(linkedList.value) if(linkedList.next == "endOfListFlag") then done = true end linkedList = linkedList.next end
Stacks
- A stack is a LIFO structure (last in first out)
- Example code is below
stack = nil stack = {next=stack, value = "first element on list"} stack = {next=stack, value = "second element on list"} stack = {next=stack, value = "third element on list"} stack = {next=stack, value = "fourth element on list"} root = stack while stack do print(stack.value) stack = stack.next end
Exercise on Stacks
- Hands on Exercise
Using Eclipse do the following: 1. Write a function that will push items on to a stack 2. Write a function that will pop items off of a stack 3. Exercise your function in Eclipse to prove that it works 4. Include several calls to push items 5. Include calls to pop items and then print out their values 6. Single step through your code using the Eclipse debugger
Queues
- Queues are similar to stacks
- They are FIFO structures (First In First Out)
- The coding is similar to that for stacks or linked lists
Sets
Examples of sets are given in later sections
String Buffer
- Reading in long strings one line at a time from a file
- Should not use string concatenation operator (..)
- A new string is created during each operation
- Should not use string concatenation operator (..)
- Instead read the lines into a table
- Then use table.concat(tableName)
Graphs
- Graphs are basically the same as linked list with the exception
- They can have links to multiple other nodes
Exercise on Graphs (Work in teams)
1. Create a data structure that can represent a graph -- The graph nodes should each contain an identifier and some string content -- The string content could, for example, be city and state names with airline routes as links 2. Create a set of functions that can add an node to the graph 3. Create a function that can print out a single node and its links
Tables as Objects
Objects vs Values
- Object types in Lua have references rather than values
- Object types in Lua: functions, tables (also thread like structures and userdata)
The differences are shown in the following
- The Table data structure requires both memory and code to access its elements
-- constructor expression planets = {"Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune"} a = planets -- passes a reference to the table data structure print(a[1]) -- uses the syntax for accessing the data structure b = 5 -- initialize a memory location c = b -- move the data to a new memory location print(c) -- print the content of a memory location
Errors and Error handling
- Programs are going to have errors
- Therefore we must handle errors in appropriate ways
- Stop the program when there is a major error
- Handle errors when possible
Stop the program on major errors
- Errors can be thrown using the error function
- This immediately stops the program with an error
- A second parameter can be put on the error function
- It gives the level for the error
- Allows the error to be passed to the calling code
- Calling code must then handle the error
- Present code is level 1
- The calling code is level 2
- error('invalid input not a number', 2)
local a = "this is a string" if type(a) ~= 'number' then error('invalid input not a number') end
- The assert function can test a condition and throw an error
- When the condition returns false or nil
- The message of the assert is shown as the error message
- This immediately stops the program with an error
local a = "this is a string" assert(type(a) == number, "this is not a number")
Catch and handle errors when appropriate
- To catch an error the pcall function is used
- pcall calls its first argument in protected mode
- If there are no errors pcall returns true
- If there are errors pcall returns false
- To use pcall encapsulate the code
- Encapsulate the code in an anonymous function
- Becomes the parameter for pcall
- pcall expects to receive two return results
- A Boolean that tells whether the function ran successfully
- A msg giving the error if it did not run sucessfully
local ok, message = pcall(function () local a = 50 local theType = type(a) assert(theType == "number", "this is not a number") --fails the program immediately local a1 = "this is a string" if type(a1) ~= 'number' then error('invalid input not a number') --fails the program immediately end assert(type(a) == "number", "the input is not a number") end) if ok then print("Sucessful continue on with code here") else print ("Error " .. message) end
Give a stack trace with the error message
- To get a stack trace use the xpcall function
- Allows a second parameter that is an error handler
- In the error handler call debug.traceback(err)
variable1 = 50 variable2 = "this is a string" function someFunction() local a = variable1 local theType = type(a) assert(theType == "number", "this is not a number") --fails the program immediately local a1 = variable2 if type(a1) ~= 'number' then error('invalid input not a number') --fails the program immediately end assert(type(a) == "number", "the input is not a number") end function errorHandler(err) return debug.traceback(err) end local ok, message = xpcall(someFunction, errorHandler) if ok then print("Sucessful continue on with code here") else print ("Error " .. message) end
Iterators
- An iterator iterates over elements of a collection
- Can be represented as a function
- Each call to the function returns the next element in the collection
- An iterator can keep state
- What element is the next element to return
- or what was the last element returned
An Example of a Table List Iterator
-- iterators and closure function iterator(theList) local i = 0 return (function () i = i + 1; return theList[i] end) end local planets = {"Earth","Mars","Saturn","Venus"} iter = iterator(planets) -- get a reference to the function while true do local theItem = iter() -- use the function if theItem == nil then break end print (theItem) end
The generic for loop
An example of a for loop using an iterator
local planets = {"Earth","Mars","Saturn","Venus"} for item in iterator(planets) do print (item) end
- The generic for loop allows (see code below):
- The var-list to be a list of one or more variables names
- Separated by commas
- The exp-list to be a list of one or more expressions
- Separated by commas (usually this has one element)
- The var-list to be a list of one or more variables names
Syntax of the generic for loop
for <var-list> in <exp-list> <body> end
Stateless vs stateful iterators
- Stateful iterators have been given as examples in prior code segments
- Stateless iterators must:
- Pass the state out to the calling code in the return statement
- Usually the return statement passes multiple values
- Pass the state in from the calling method in the function arguments
- Pass the state out to the calling code in the return statement
Examples of this are ipairs and pairs
- ipairs iterates over the portion of a table without holes
- pairs iterates over the entire table (in any order)
local orchestra = {'violin', 'trumpet', 'clarinet', 'tuba', 'guitar', 'flute'} for i, instrument in ipairs(orchestra) do print ("Instrument" .. i .. " " .. instrument) end orchestra = {[1] = 'violin', [2] = 'trumpet', [6] = 'clarinet', [18] = 'tuba', [20] = 'guitar', [3] = 'flute'} for i, instrument in pairs(orchestra) do print ("Instrument" .. i .. " " .. instrument) end
Results are
- Note that the second set are not in order
Instrument1 violin Instrument2 trumpet Instrument3 clarinet Instrument4 tuba Instrument5 guitar Instrument6 flute Instrument1 violin Instrument20 guitar Instrument2 trumpet Instrument18 tuba Instrument3 flute Instrument6 clarinet
Behind the covers something like the following is doing the iteration
- From the Lua website Stateless Iterators
function iter (a, i) i = i + 1 local v = a[i] if v then return i, v end end function ipairs (a) return iter, a, 0 end
Exercise on Iterators
- Hands on Exercise
Using Eclipse do the following: 1. Using the code created before for pushing and popping a stack initialize a stack 2. Create an iterator for a stack using closure and a function that passes in a stack 3. Write a for loop that iterates through the stack and prints out its values
Coroutines
- A coroutine is similar to a thread in that:
- It has its own stack
- It is a line of execution
- It has its own local variables
- It has its own instruction pointer
- A coroutine shares global variables
- A set of coroutines in a program are collaborative:
- Only one is running at a time
- It suspends execution only when it explicitly requests to be suspended
Producer Consumer Example
This discussion follows the discussion on the Lua Website at url Pipes and Filters
- It gives a good example of cooperating coroutines
- Trace through the function by hand first
- Then put it into Eclipse and try it with the debugger
- Put breakpoints on coroutine.yield and coroutine.receive
--- producer consumer using coroutine resume/yield to synchonize --- and pass data between the producer and consumer function receive() local status, value = coroutine.resume(producer) -- starts when other function yields return value end function send(x) coroutine.yield(x) -- yields and send value to resume end producer = coroutine.create( -- create the producer that runs the anonymous function code function () while true do local x = io.read('*L') if x == nil then os.exit(true, true) end -- kills the program when true send(x) -- calls the send function which does a yield sending data end end) function consumer() while true do local x = receive() io.write("input was -- ", x, "\n") end end io.input("C:\\lua Programming\\producerFile.txt") consumer()
Metatables and metamethods
- Metatables are similar to operator overloading in C++
- Metatables can be assigned to Tables
- Using setmetatable(theTable, theMetatable)
- There is also a getmetatable(theTable) which gives the metatable reference
- Metatables contain function definitions which are called metamethods
- The metamethods can be used to manipulate tables
The set example is given below
- This is from Programming in Lua by Roberto Ierusalimschy
local mt = {} Set = {} function Set.new (l) -- 2nd version local set = {} setmetatable(set, mt) for _, v in ipairs(l) do set[v] = true end return set end function Set.union (a,b) local res = Set.new{} for k in pairs(a) do res[k] = true end for k in pairs(b) do res[k] = true end return res end function Set.intersection (a,b) local res = Set.new{} for k in pairs(a) do res[k] = b[k] end return res end function Set.tostring (set) local l = {} for e in pairs(set) do l[#l + 1] = e end return "{" .. table.concat(l, ", ") .. "}" end function Set.print (s) print(Set.tostring(s)) end local s1 = Set.new{10, 20, 30, 50} local s2 = Set.new{30, 1} print(getmetatable(s1)) print(getmetatable(s2)) mt.__add = Set.union mt.__mul = Set.intersection --mt.__tostring = Set.tostring s3 = s1 + s2 Set.print(s3) Set.print((s1 + s2)*s1) print((s1 + s2)*s1)
Arithmetic Metamethods
- Metamethods can be defined for:
- Addition (+) __add
- Subtraction (-) __sub
- Multipication (*) __mul
- Division (/) __div
- Modulo (%) __mod
- Exponentiation (^) __exp
- Negation (unary -) _unm
- Concatenation (..) __concat
- It might be that some of these operators make no sense in terms of the types involved
Relational Metamethods
- Metamethods can be defined for:
- Equal (==) __eq
- Less Than (<) __lt
- Less Than or equal (<=) __le
- For the other operators the operands are reversed
- For example greater than (a>b) is the same as (b<a)
- For a ~= b the expression not(a == b) is used
Library and Table Access Metamethods
The metatable __toString method
- It is common for libraries to define metatable and metamethods
- The print command looks for the __tostring metamethod
- The effect of putting the tostring method in the metatable can be seen:
- The print command looks for the __tostring metamethod
- uncomment the mt.__tostring = Set.tostring line in the example for sets
The __index and __newindex metamethods
- When an access to an index in a table returns a nil
- The interpreter looks for an __index metamethod
- If found the metamethod returns the result rather than nil
- __index can be set to a function, or a table
- If set to a table a lookup is done in the table for the key passed in
- If a function the function is called with the table and key as parameters
- To access a table without invoking __index Use rawget(t,i)
- __newindex is used for table updates
- if the index absent then the __newindex function is called if it exists
- rawset(t,i) is used to bypass the check
- If a table is assigned to __newindex then the assignment is made in this table
Modules and packages
- Modules are intended to:
- Allow groups to share code
- Prevent collisions between the identifiers in different Modules
- Lua has Policies for modules and packages
- The attempts is to define these using standard Lua structures
- Tables
- Functions
- Metatables
- Environments
- The attempts is to define these using standard Lua structures
Using Modules
- A module is some code that can be loaded through the "require" statement
- Can be a combination of Lua and C code
- The "require" statement returns a table
- The table acts as a namespace
- The table contains constants, functions, etc.
- The standard libraries are modules
An example of using the math library
local math = require "math" local table1 = {} for i = 1, 10 do table1[i] = math.random(1, 200) print(table1[i]) end
- require cannot pass arguments
- This restriction is because the structures used are a normal part of the language
- You can easily create functions in the module that will allow the passing of arguments
How are modules Loaded
- On the first require statement for a module
- Lua searches for the Lua file with the module name
- If it finds a Lua file it calls "loadfile" which gives a loader for the file
- If it cannot find a Lua file it looks for a C library with the name
- If a C file is found it calls package.loadlib which gives a loader for the file
- Require now calls the loader to load the module
- Require returns the return value from the loader and puts it into the package.loaded table
- Upon subsequent calls to require the package.loaded file has the reference to the module
Path Searching
- Lua uses a set of templates to search for modules rather than a set of paths
- Each template is a path with optional question marks (?)
- The templates in a path are separated by semicolons
- The question marks are replaced by the module name given to require
- The path for finding Lua files comes from variable package.path
- Package.path is loaded when Lua starts up from LUA_PATH
- LUA_PATH is in the environment variables of the operating system
- If this cannot be found Lua uses a default path
- Package.path is loaded when Lua starts up from LUA_PATH
- The path for finding C files comes from variable package.cpath
- package.cpath comes from environment variable LUA_CPATH
Creating Modules
- Modules can be created as tables containing functions etc.
A example that creates a new module
newModule = {} local masterTable = {} local i = 1 function newModule.addString(inputVariable) if type(inputVariable) ~= "string" then print("Error - input is not a string") else masterTable[i] = inputVariable i = i + 1 end end function newModule.printStrings() for j = 1, i-1 do print(masterTable[j]) end end return newModule
An example of loading and run the new module
local testModule = require "moduleSample" testModule.addString("This is the first string") testModule.printStrings()
Organizing modules into packages
- Lua allows modules to be organized into packages
- This is similar to the packages of Java
- Folders are created with Lua files in them
- The dot notation specifies the package hierarchy
- It also specifies the folder hierarchy
An example of the code in a module
newModule = {} local masterTable = {} local i = 1 function newModule.addString(inputVariable) if type(inputVariable) ~= "string" then print("Error - input is not a string") else masterTable[i] = inputVariable i = i + 1 end end function newModule.printStrings() for j = 1, i-1 do print(masterTable[j]) end end return newModule
An example of using Lua modules and packages
local testModule = require "moduleSample" local anotherModule = require "modules.sample" testModule.addString("This is the first string") testModule.printStrings() anotherModule.addString("This is the first string from the modules package") anotherModule.printStrings()
Below is an image of Eclipse showing packages and modules
Metatables through examples
A number of examples to walk through are given in this section
- Giving default values for tables
- Checking every read and write to a table with a proxy
- Making a table read only
- Memorizing results for reuse through memoization
Default Values for Tables
Problem Definition
Assume that it is necessary to have default values on a set of tables
- If the table does not have a value for a key then
- A default value is to be given. Note that in this case
- only one default per table is allowed
- A default value is to be given. Note that in this case
- If the table does not have a value for a key then
Problem Solution Discussion
1. Each read of a value for a key from the table will return a value or if the key does not exist in the table a nil. 2. A call to return an absent key can be intercepted by defining the __index metamethod. 3. The __index metamethod can then return a default value 4. The question becomes how to make the metatable efficient
Solution 1
--[[This is a function which will set a default for a table. Note that a new metatable is created for each table. The function creates only one default per table. Each use of this function creates a closure this can be expensive in terms of memory used (metatable and default) if there are many tables]] function setDefault(table, default) local metatable = {__index = function () return default end} -- a new closure, when __index is called, default is known setmetatable(table, metatable) end theTable = {x=10, y=20} print(theTable.x, theTable.z) setDefault(theTable, -1) print (theTable.x, theTable.z) setDefault(theTable, 0) print (theTable.x, theTable.z)
Solution 2
--[[Here we set default values for indices that are used that have no table entries by only using one metatable, saves memory. The function saves the default data in the table itself. It uses an oddly named variable, three underscores (___) as the variable name]] local metatable = {__index = function(t) return t.___ end} function setDefault(table, default) table.___ = default setmetatable(table, metatable) end theTable = {x=10, y=20} print(theTable.x, theTable.z) setDefault(theTable, -1) print (theTable.x, theTable.z)
Solution 3
--[[set default values for indices that are used that have no table entries by only using one metatable, saves memory. The function here saves the default data in the table itself. In this case it uses the name "key" as the variable name Note: to create key it is set up as a table and created using a local variable. Its value is then initialize with the default (which can be of any type) within the setDefault function. This is somewhat obscure and subtle, we end up with an index and value pair where the index is an empty table (called key) and the value is the default]] local key = {} local metatable = {__index = function(t) return t[key] end} function setDefault(table, default) table[key] = default setmetatable(table, metatable) end theTable = {x=10, y=20} print(theTable.x, theTable.z) setDefault(theTable, -1) print (theTable.x, theTable.z)
Exercise on Default Values
1. Run the code for metatablesDefaultRequire from the code samples 2. Determine how the code works 3. Design and create code that will allow multiple default values to be created per table. Presently only one default per table is allowed.
Proxies
Problem Definition
Assume that all accesses to a table are to be:
- Tracked
- Limited
- Modified
- Some of the above
Problem Solution Discussion
1. Some type of Proxy standing in front of the table should be present<br> 2. The proxy needs to call some function for every read or write to the table 3. One suggestion is to use the __index and __newindex metamethods. With an empty table that acts as the proxy This suggestion is used for the following code The solutions are taken with some modifications from Programming in Lua by Roberto Ierusalimschy
Solution 1
--[[In this code the task is to monitor all accesses to a table. To do this we create a proxy for the table. This is an empty table that has both __index and __newIndex metamethods. Each call is made to the proxy table which checks the calls and then passes them onto the real table.]] theTable = {} local _t = theTable theTable = {} local mt = { __index = function( table, key) print("*access to element " .. tostring(key)) return _t[key] end, __newindex = function(table, key, value) print("*update of element " .. tostring(key) .. " to " .. tostring(value)) _t[key] = value end } setmetatable(theTable, mt) theTable[2] = "hello" print(theTable[2])
Exercise on Proxies
1. Put the code for metatableProxyModule.lua and metatableProxyRequire.lua into Eclipse 2. Run the metatableProxyRequire.lua file 3. Determine how it works and how it differs from the code in the last section 4. Discuss with instructor and others in the class
Read Only Tables
Problem Definition
Construct a table that is read only. Users will not be allow to add new indices
Problem Solution Discussion
A read only table can be constructed using a proxy<br> 1. The proxy table will be empty 2. The __index metamethod will be assigned the original table. This will cause accesses (reads) to be looked up in the table that is in __index (this is the definition of __index it is either a function or a table) 3. The __newindex metamethod will be a function that returns an error This will cause a updates (writes) to be rejected. Since the proxy table is empty __newindex will be called for each write to the table and an error will be returned.
Solution
The code of the module for the read only proxy is the following
--[[Take a table and make a proxy for that table that will be empty and therefore call the __index and __newindex metamethods for every access. The _newindex metamethod will return an error so that no new indices can be made in the table. The _index method will look up the value in the table which has been assign to the __index metamethod]] function readOnly (t) local proxy = {} local mt = { __index = t, __newindex = function(table, key, value) print("attempt to update a read-only table, operation " .. key .. " = " .. value .. " not performed") end } setmetatable(proxy, mt) return proxy end
The code that exercises the module is the following
--[[This is the code to exercise the metatableReadOnly.metatableProxyModule code]] require "metatableReadOnly.metatableProxyModule" local days = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"} days = readOnly(days) print(days[1]) days[2] = "None" local planets = {planet1="Mercury", planet2="Venus", planet3="Earth", planet4="Mars", planet5="Jupiter", planet6="Saturn", planet7="Uranus", planet8="Neptune"} planets = readOnly(planets) print(planets.planet8) planets.planet8 = "Xergon"
Memoization
Memoization refers to memorizing values already used
An example of this is given below
- Note that the __mode will cause the table to be weak
- This is discussed in the section on Garbage Collection and Weak Tables
--[[This is a function and some code that shows the concept of memorization]] local metas = {} setmetatable(metas, {__mode = "kv"}) function setDefault(table, default) local mt = metas[default] if mt == nil then mt = {__index = function() return default end} metas[default] = mt --this is the line that memorizes end setmetatable(table, mt) end planets = {"Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune"} local default = "Not in our solar system" setDefault(planets, default) print(planets[3]) print(planets[10]) star = {"Sun"} setDefault(star, default) print(star[1]) print(star[2]) print(getmetatable(planets)) print(getmetatable(star)) print(metas[default])
Environments
- Global variables are kept in the Global Environment table
- The Global Environment is stored in variable _G
Examples of using _G
value = _G[varname] -- get the value of a global variable _G[varname] = value -- set the value of a global variable varname = value -- much easier way to set the value
Declaring Global Variables
- They do not need to be declared, declared when used
- Can cause errors (a typo will cause a global variable)
Setting a metatable on _G to give errors when globals are nil
setmetatable(_G, { __newindex = function (t, n, v) local w = debug.getinfo(2, "S").what if w ~= "main" and w ~= "C" then error ("attempt to write to undeclared variable " .. n, 2) end rawset(t, n, v) end, __index = function(_, n) error("attempt to read undeclared variable " .. n, 2) end }) -- a = 9 print(a)
- The problem with the above is that a new variable that is initialize to nil cannot be set
- Without using rawset(table, key, value)
- This metatable also effects the entire program including standard libraries
Relationship between global variables and environments
Free Names
- Free names (Free Variables) are identifiers that are not within the context of
- A local variable name
- A for loop control variable
- A function parameter
- In other words they are what we have been calling Global Variables
_ENV and _G
- _ENV is a local variable associated with a chunk (a lua file)
- _ENV is initialize with a reference to the global table _G as the chunk is loaded
- Each free variable in a chuck is referenced as _ENV.variableName
- Since _ENV is a reference to _G any free variable goes into _ENV and _G
- Therefore you can use _ENV within a file to limit the functions and globals that are seen
- Function names are just global identifiers for function references
View of _ENV after it has been limited
- Note that if _ENV is set to nil then references to existing free variables are lost
The _ENV table and the _G table
- _ENV is associated with a chunk (a file)
- _G is associated with the global environment of the program
- _G is built as the program is loaded
- _ENV in Lua 5.2 can be overridden by a local _ENV
a = 1 b = 2 local _ENV = {print = print, _G = _G} c = 5 d = 7 print(_G.a) print(_ENV.c)
Object-oriented Programming
- Tables can be used to create classes in Lua
- They can have name value pairs which act like variables in a class
- They can reference functions by named indices which act like methods
- They can reference themselves using the identifier "self" (a new concept)
The concept of self
Self refers to the present object (Tables are objects in Lua). When the colon (:) operator is used to call a function in the object/table self is passed into the function (now referred to as a method) as a hidden variable. Internally in the function self can now be used to refer to an instance of a table. This allow the functions to operate on different instances of tables giving the ability to have object oriented programming (OOP). Internally the functions will use self to refer to their variables and functions function Account:deposit(v) self.balance = self.balance + v --refers to a balance value within this table instance end
Classes
- Classes can be created using tables as described above
- They will have name value pairs, name function pairs, and use self
An example of a class for a bank account
Account = {balance=0} function Account:new (theObject) theObject = theObject or {} setmetatable(theObject, self) self.__index = self return theObject end function Account:deposit (theDeposit) self.balance = self.balance + theDeposit end function Account:withdraw(withdrawal) if withdrawal > self.balance then error ("insufficient funds") end self.balance = self.balance - withdrawal end function Account:print() print ("Account Balance is " .. self.balance) end local newAccount = Account:new() newAccount:deposit(100.00) newAccount:print() local myAccount = {} Account:new(myAccount) myAccount:deposit(50.00) myAccount:withdraw(25.00) myAccount:print() -- using the : passes in self as a hidden parameter --myAccount.print() -- this will not work because self is not present
Inheritance
- A new table can be built that inherits from the Account table
- The new table will have the same __index function in its metatable
- So it will inherits the functions and variables of Account
An example of inheritance Put this code and the prior code into files and try them They can also be found in the sample files for the course
local account = require "objectOriented.classAccount" SpecialAccount = Account:new() function SpecialAccount:withdraw(v) if v - self.balance >= self:getLimit() then error "insufficient funds" end self.balance = self.balance - v end function SpecialAccount:getLimit() return self.limit or 0 end newAccount = SpecialAccount:new{limit=1000.00} newAccount:deposit(100.00) newAccount:withdraw(200.00) newAccount:print() local limit = newAccount:getLimit() print("Account limit is " .. limit)
Exercise on Object Oriented Programming
Take the code in folder objectOriented from the sample code and do the following 1. Use the code for classAccount.lua as an account 2. Modify the code for classSpecialAccount so that classSpecialAccount.lua is a module 3. Create a new file called createSpecialAccount.lua that will create a specialAccount from the specialAccount module and package 4. Exercise the specialAccount to prove that it works 5. Modify the specialAccount so that it has a printLimit method 6. Modify the specialAccount so that it has a changeLimit method that can change the limit on the special account
A walkthrough of the Lua Standard Libraries
Base Library
- The base library is used without a library name modifier
- It contains a set of commonly used functions
I/O Library
References
The Simple I/O Model
- Commonly uses stdin (input) and stdout (output)
- This can be changed using io.input(filename) and io.output(filename)
Writing to stdout or a file
io.write("this is an output string to the console\n") io.output("C:\\lua Programming\\myOutputFile.txt") io.write("this is an output string to a file\n") io.write("this is an second output string to a file") io.flush() io.close() stdoutFile = io.stdout --reset to stdout io.output(stdoutFile) io.write("This is another output string to the console")
Reading from stdin or a file
- The io.read command takes one argument
- They control what to read
"*a" - read the whole file "*l" - read the next line does not return the newline "*L" - read the next line returns the newline also "*n" - read a number num - read a string with up to num characters
- The following is an example of reading
- First from stdin with a prompt
- Then from a file after switching to a file
- Then back to stdin in with a prompt after switching back to stdin
print('Enter your First Name:') local firstName = io.read('*l') io.input("C:\\lua Programming\\myInputFile.txt") local theString = io.read('*L') io.write(theString) theString = io.read('*l') io.write(theString) stdinFile = io.stdin --reset to stdout io.input(stdinFile) print('\n\nEnter your Last Name:') local lastName = io.read('*L') io.write("Thanks for the input " .. firstName .. " " .. lastName)
The Complete I/O Model
- based on file handles
- An open file with a current position
- Open a file using the io.open function
- Plus a mode string 'r' or 'w' or 'a'
- An open file with a current position
- read, write, append
- Returns a new handle for the file
- In case of error returns nil
Input
local file = assert(io.open(filename, "r")) local text = file:read("*a") -- read the entire file file:close()
Output
local file = assert(io.open(filename, "w")) file:write("something") file:flush() file:close()
An example of Reading a file
local BUFSIZE = 2^5 local filePath = "C:\\lua Programming\\wordcount.txt" local file = io.input(filePath) local cc, lc, wc = 0, 0, 0 -- character line and word counts for lines, rest in io.lines(arg[1], BUFSIZE, "*L") do if rest then lines = lines .. rest end cc = cc + #lines -- count words local _, t = string.gsub(lines,"%S+","") wc = wc + t -- count newlines _, t = string.gsub(lines,"\n","\n") lc = lc + t end file:close() print(lc, wc, cc)
References
Table Library
table.insert(table, index, element) or insert(table, element) inserts an element into the table at index or at the end if index is not given table.remove(table, index) removes the element at index from the table returns the element table.sort(theTable) sorts the table by in order by index table.concat(theTable) for strings concatenates the table entries returns a string
String Library
Some of the commonly used string functions are given below
string.len(s) returns the length of a string string.rep(s, n) returns a string repeated n times string.lower(s) returns the string in lower case string.upper(s) converts to upper case string.sub(s,i,j) returns a portion of the string from index i to j string.byte(s, i) returns the numeric representation of the ith character of the string string.byte(s, i, j) returns the numeric representation of the ith to jth characters string.char(number) returns the character representation of the number in ASCII For a complete listing see the references in the Contents and get the Short Reference or a Reference Card
Patterns
- Lua does not implement full regular expressions.
- It implements a smaller subset.
See the reference card or short reference for the pattern matching syntax
Pattern Matching
string.find - searches for a pattern inside a given string string.find(originalString, pattern) returns the starting index and ending index of the matched portion string.match - searches for a pattern inside a given string string.match(originalString, pattern) returns the portion of a string that matches a pattern string.gsub - substitutes a replacement string for a matching string string.gsub(originalString, pattern, replacementString, numberOfSubstitutions) returns a new string with the replacements and the total number of replacements made string.gmatch - iterates over all occurrences of a pattern in a string string.gmatch(originalString, pattern) returns the iterator, can be used with the generic for loop
An example using string.match
local theString = "This is the test string that will be used to work with patterns in Lua. This should be interesting" print(string.match(theString,"t[%a%s]+g"))
results are: the test string
- The pattern was "t[%a%s]+g"
- Starts with character t
- has one or more letter or whitespace
- ends with character g
An example using gsub
local newString = string.gsub(theString,"[tT][eshia]+[st]","zonk") print(newString)
results are: zonk is the zonk string zonk will be used to work with patterns in Lua. zonk should be interesting
- The pattern was [tT][eshia]+[st]
- The starting character is t or T
- One or more letters or spaces follow
- The ending character is s or t
- Replace all words matching the pattern with the word "zonk"
An example using string.gmatch
for word in string.gmatch(theString,"[tT][eshia]+[st]") do print(word) end
results are: This test that This
- The pattern was [tT][eshia]+[st]
- The starting character is t or T
- One or more letters or spaces follow
- The ending character is s or t
Capture
Parentheses are used for capture in patterns
An example of capture in a pattern using gmatch
theString = "This is the test string that will be used to work with patterns in Lua. This should be interesting" -- work with capture print("\n\n") for word in string.gmatch(theString,"[tT]([eshia]+)[st]") do print(word) end
results are: hi es ha hi
- Captures only the letters between the start and end letters
Exercise using Strings and Pattern Matching (in teams of three)
Use the following text for this exercise (use a long string to input it or read it from a file) "Cheese is a food derived from milk that is produced in a wide range of flavors, textures, and forms by coagulation of the milk protein casein. It comprises proteins and fat from milk, usually the milk of cows, buffalo, goats, or sheep. During production, the milk is usually acidified, and adding the enzyme rennet causes coagulation. The solids are separated and pressed into final form.[1] Some cheeses have molds on the rind or throughout. Most cheeses melt at cooking temperature. Hundreds of types of cheese from various countries are produced. Their styles, textures and flavors depend on the origin of the milk (including the animal's diet), whether they have been pasteurized, the butterfat content, the bacteria and mold, the processing, and aging. Herbs, spices, or wood smoke may be used as flavoring agents. The yellow to red color of many cheeses, such as Red Leicester, is produced by adding annatto. Other ingredients may be added to some cheeses, such as black pepper, garlic, chives or cranberries. For a few cheeses, the milk is curdled by adding acids such as vinegar or lemon juice. Most cheeses are acidified to a lesser degree by bacteria, which turn milk sugars into lactic acid, then the addition of rennet completes the curdling. Vegetarian alternatives to rennet are available; most are produced by fermentation of the fungus Mucor miehei, but others have been extracted from various species of the Cynara thistle family. Cheesemakers near a dairy region may benefit from fresher, lower priced milk, and lower shipping costs." Do the following: 1. Replace all of the words "cheese" with the work "air" 2. Replace all of the words that begin with an "m" and end with a "k", "r" or "i" with the word "boom" 3. Make three or more other replacements that would be interesting using complex patterns (Not simple word patterns) 4. Decide with those around you which is the best modification of the text 5. The best modification of the text should be shared with the class
The Operating System Library
os.time() - returns the current date and time as a number in seconds os.time(table) - returns the date number represented by the table i.e. os.time{year=2001, month=9, day=5, hour=14, min=7, sec=54} os.date(format, time) can format the date as a string or put it into a table
An example using time and date
print(os.time{year=2001, month=9, day=5, hour=14, min=7, sec=54}) print(os.time({year=2001, month=9, day=5, hour=14, min=7, sec=54})) local theTime = os.time({year=2001, month=9, day=5, hour=14, min=7, sec=54}) print(os.date("%B %w, %Y ",theTime))
Compilation
- Lua precompiles code when it is run
- Precompiled code can also be distributed
- Precompiling is done using luac
- These are called binary chunks
Garbage collection
- Lua uses garbage collection.
- It uses a mark and sweep garbage collector
- The collector as of version 5.0 runs interleaved with the interpreter
Mark and Sweep
Mark and sweep performs garbage collection is three stages: mark, cleaning, and sweep Mark - all reachable objects are marked as alive Cleaning - Looks at all objects with a finalizer looking for non-marked objects. These are put into a separate list to be handled. It also looks at all weak tables and eliminates any entries that are not marked. Weak tables are discussed below Sweep - all objects are sweeped and those not marked are collected. Lua also calls the finalizers of any objects put in the finalizer list
Weak Tables
- Tables can have entries that are used by multiple other objects
- They can have objects as keys or values
- The objects referencing the table eventually get garbage collected
- How to garbage collect the entry once it is no longer referenced
The __mode metamethod is used to declare that the keys or the values or both are weak setmetatable(results, {__mode = "k"}) -- make the values weak setmetatable(results, {__mode = "v"}) -- make the keys weak setmetatable(results, {__mode = "kv"}) -- make both weak If the values or keys are weak once all references to them are gone in the code The garbage collector will not mark them as having references during mark They will then be collected during cleaning
Finalizers
- Tables to be destroyed can have entries in multiple places
- In this case a finalizer can be provided on the table by implementing the __gc metamethod
Lua bytecode and virtual machine
- The Lua compiler luac converts Lua code to bytecode
- The Lua interpreter runs the bytecode
For those that are interested here are some references to explore References
Calling C modules from Lua
- To call C from Lua is fairly easy
- Just put the correct requires statement into the code
- It is much harder to get a useful set of extensions in C and
- Install them
- Set up environment variables correctly
- Configure Lua Development Tools (Eclipse) correctly
LuaRocks
- LuaRocks is one of the main distribution methods for C code callable from Lua
- LuaRocks that are useful to extend Lua
- Networking
- Database
- JSON
- and many others
- LuaRocks that are useful to extend Lua
References
Exercise Setting up LuaRocks within LDT
- The Lua Development Tools (LDT) is the Eclipse environment for Lua
- There are a number of steps required to setup a LuaRock with LDT
- A C compiler is required
- Make is required
- LuaRock executable must be downloaded and "make" run to install it
- Environment variables for LUA_PATH and LUA_CPATH must be set up
- Mingw should be installed on a Windows computer (makes it easier)
- A version of Lua interpreter must be installed
- The appropriate LuaRoak must be downloaded and installed using LuaRocks executable
- LDT must be configured with a new interpreter and an Execution Environment
The following PDF is a walk through of this process that installs LuaRocks and LuaSockets
- LuaSockets is a socket library written in C used by Lua
References on Software used for Lua and C
- Lua Development Tools (Eclipse)
- Eclipse for C
- MinGW mingw-get-setup.exe
- Lua 5.1.5 lua-5.1.5.tar.gz
- LDT Execution Environment
- LuaRocks luarocks-2.3.0-rc2-win32.zip
- LuaSocket luasocket-master.zip
Networking with Coroutines Example using LuaSockets
To use networking a networking rock must be installed
- Luasockets is a good one to use
- To bring the code into Eclipse a local Lua interpreter must be set up
- The steps to install luasockets for use in Eclipse are the following
- Install a Lua 5.1 interpreter on the computer
- LuaRocks seems to want a 5.1 interpreter, although a 5.2 should be tested to see if it works
- Update the environment variables
- Install LuaRocks on the computer
- Update the environment variables
- Install luasockets using LuaRock
- They will be installed in the local interpreter folder that was installed above
- Make the local Lua interpreter the project interpreter for a networking project in Eclipse
- Install in Eclipse an Execution Environment that has the docs for luasockets for auto-completion
- Code the project and try it
- Install a Lua 5.1 interpreter on the computer
socket = require 'socket' local host = "www.w3.org" local threads = {} -- declare the table in which to put ids of coroutines that yield function download (host, file) local connection = assert (socket.connect(host, 80)) local count = 0 connection:send("GET " .. file .. " HTTP/1.0\r\n\r\n") while true do local data, status = receive(connection) count = count + #data if status == "closed" then break end end connection:close() print (file, count) end function receive (connection) connection:settimeout(0) local data, status, partialData = connection:receive(2^10) if status == "timeout" then coroutine.yield(connection) end return data or partialData, status end function get (host, file) local co = coroutine.create( function () download(host, file) end) table.insert(threads,co) end function dispatch() local i = 1 local timedout = {} -- table will hold threads that timed out so they can be restared while true do if threads[i] == nil then -- at end of threads list? if threads[1] == nil then break end -- are all threads finished? i = 1 -- restart at first thread timedout = {} -- reset the timedout table to go through them again end local status, res = coroutine.resume(threads[i]) if not res then -- thread finished its task? table.remove(threads,i) -- remove from the table and change table size else i = i +1 timedout[#timedout + 1] = res -- thread timedout so put it in timedout table if #timedout == #threads then -- are all threads blocked? socket.select(timedout) -- if so then wait until the state of one of them changes end end end end -- now start up several coroutines to get pages over the web get(host, "/Consortium/") get(host, "/participate/") get(host, "/") get(host, "/Consortium/membership") dispatch() -- start the main loop in the dispatcher
Exercise on LuaSockets
1. Get the prior code on Coroutines and LuaSockets running 2. Trace though the code using the debugger 3. Prepare the following for discussion What is the purpose of the line connection:send("GET " .. file .. " HTTP/1.0\r\n\r\n") ? What sets up the connection to the server. How does the code synchronize using coroutines? Discuss the relationship between coroutines and threads. What is the : after the connection identifier signify? Discuss the purpose of the threads table 4. Implement the following Get the HTML of the page retrieved as well as the size of page and display both in the console
Create C Modules, Call from Lua
First a C module must be coded with functions to be called from Lua
#include <stdio.h> #include <string.h> #include "lua.h" #include "lauxlib.h" #include "lualib.h" static int l_sin(lua_State *L) { double d = lua_tonumber(L, 1); //here is all of the code lua_pushnumber(L, sin(d)); return 1; /*number of results*/ } static const struct luaL_Reg mylib[] = { { "sin", l_sin }, { NULL, NULL } /*sentinel*/ }; int luaopen_mylib(lua_State *L) { //luaL_register(L, "mylib", mylib); /* lua 5.1*/ luaL_newlib(L, mylib); return 1; }
- All functions must get one parameter a pointer to lua_State
- Usually called L
- Functions return the number of values that they placed on the Lua stack
- Values are placed on the stack using lua_pushnumber etc.
- A struct of type luaL_Reg is created which has the for of an array of name, function pointer pairs
- The array last member must end in NULL, NULL as a sentinel
- The struct is passed into the luaL_newlib function along with the lua_State
- The registers the functions so they can be used by lua.
- The program is compiled and built into a dll.
- The dll is placed on the known lua cpath so that it can be found
- The dll must have the same name as the module (what goes into the require statement)
- The dll must be placed in a valid location on the lua cpath in the environment variables
References
Calling Lua from C
- Lua can be called from C
- The usual steps are:
- Start a new Lua state
- Open any desired Lua standard libraries
- Push a function and parameters onto the "Stack"
- Execute the function
- Pop results from the stack
- close the Lua state
An example of calling Lua from C
#include <stdio.h> #include <string.h> #include "lua.h" #include "lauxlib.h" #include "lualib.h" int main (void) { char buff[256]; int error; lua_State *L = luaL_newstate(); /* opens Lua */ luaL_openlibs(L); /* opens the standard libraries */ while (fgets(buff, sizeof(buff), stdin) != NULL) { error = luaL_loadstring(L, buff) || lua_pcall(L, 0, 0, 0); if (error) { fprintf(stderr, "%s\n", lua_tostring(L, -1)); lua_pop(L, 1); /* pop error message from the stack */ } } lua_close(L); return 0; }
- To make the above code run the following steps are needed
- Download an Eclipse C programming IDE
- Install a C compiler on Windows it is easiest to use Minimalist GNU for Windows (mingw)
- Configure Eclipse so that it uses an installed C compiler (i.e. mingw gcc)
- Create a project
- Configure the project so that it uses the libLua.a library
- By setting project Properties/C C++ General/Paths and Symbols/Library Paths/Add
- Add the liblua.a library in Paths and Symbols/library add it as lua (remove lib and a)
- Configure the project with the correct C header files by copying and pasting them
- Or by specifying the path to them in Paths and Symbols
- Create the source code and build it
- Run the project
References
The Stack
- A stack is used to interface between C and Lua
- Values and functions can be pushed onto it and then executed
Various Stack Operations are Shown in the Code Below
#include <stdio.h> #include <string.h> #include "lua.h" #include "lauxlib.h" #include "lualib.h" static void stackDump(lua_State *L) { int i; int top = lua_gettop(L); /* get the stack depth */ for (i = 1; i <= top; i++) { int t = lua_type(L, i); switch (t) { case LUA_TSTRING: { /* strings */ printf("'%s' ", lua_tostring(L, i)); break; } case LUA_TBOOLEAN: { /* boolean */ printf(lua_toboolean(L, i) ? "true " : "false "); /* print them as strings */ break; } case LUA_TNUMBER: { /* number */ printf("%g ", lua_tonumber(L, i)); break; } default: { /* other values */ printf("%s ", lua_typename(L, t)); break; } } } } int main(void) { lua_State *L = luaL_newstate(); /* opens Lua */ lua_pushboolean(L, 1); /* item 1 also item -4 on the stack after 4 pushes */ lua_pushnumber(L, 10); lua_pushnil(L); lua_pushstring(L, "hello"); stackDump(L); printf("%s", "\n"); lua_pushvalue(L, -4); stackDump(L); /* push a copy of element at index -4 */ printf("%s", "\n"); lua_replace(L, 3); stackDump(L); /* get the top and put it at index 3 */ printf("%s", "\n"); lua_settop(L, 6); stackDump(L); /* set the stack size to 6, put nils */ printf("%s", "\n"); lua_remove(L, -3); stackDump(L); /* remove the item at -3 */ printf("%s", "\n"); lua_settop(L, -5); stackDump(L); /* pop -()n -1 or 4 elements from the stack */ printf("%s", "\n"); return 0; }
Exercise on the Stack
Perform the following operations on the Lua Stack lua_pushnumber(L, 5.678); lua_pushboolean(L, 0); lua_pushvalue(L, -2); lua_pushnil(L); lua_remove(L, 1); lua_insert(L, -2) Determine what the resulting stack should contain Check your answer with stackDump
Accessing Lua Tables from C
- Tables in Lua are accessed through the stack
- Generally a Lua file is loaded and then the stack accessed
The rules for accessing a table are as follows: 1. The assumption is that the table is global in Lua and can be accessed using lua_getglobal(L, tablename); 2. lua_getglobal will cause the table to be put on the stack 3. test that the top element on the stack is a table 4. push a table key onto the stack, ends up on the stack top 5. do a lua_gettable which will take the key and access the table 6. do a lua_pop of the top element to remove the key 7. do the same thing for other keys
An Example of accessing a table in Lua through the Stack
#include <stdio.h> #include <string.h> #include "lua.h" #include "lauxlib.h" #include "lualib.h" void load (lua_State *L, const char *fname, int *red, int *green, int *blue); int getcolorfield(lua_State *L, const char *key); #define MAX_COLOR 255 int main(void) { lua_State *L = luaL_newstate(); /* opens Lua */ luaL_openlibs(L); /* opens the standard libraries */ char filename[] = "C:/Lua C and C++/workspace/configTable.lua"; int red = 0; int green = 0; int blue = 0; load(L, filename, &red, &green, &blue); printf("red is %d\n", red); printf("green is %d\n", green); printf("blue is %d", blue); return 0; } void load (lua_State *L, const char *fname, int *red, int *green, int *blue) { if (luaL_loadfile(L, fname) || lua_pcall(L, 0, 0, 0)) { luaL_error(L, "cannot run config file: %s", lua_tostring(L, -1)); } lua_getglobal(L, "background"); if (!lua_istable(L, -1)) { luaL_error(L, "'background' is not a table\n"); } int r = getcolorfield(L, "r"); int g = getcolorfield(L, "g"); int b = getcolorfield(L, "b"); *red = r; *green = g; *blue = b; } int getcolorfield(lua_State *L, const char *key) { int result; lua_pushstring(L, key); lua_gettable(L, -2); if (!lua_isnumber(L, -1)) { luaL_error(L, "invalid component in background color"); } result = (int)(lua_tonumber(L, -1) * MAX_COLOR); lua_pop(L, 1); return result; }
The Lua file with the table
background = {r=0.30, g=0.10, b=0} BLUE = {r=0, g=0, b=1.0} background1 = BLUE
Accessing Functions through the Stack
- Functions from a lua file can be accessed through the stack
- The file with the functions is loaded first
The basic steps to access a function are the following 1. Load the file with the function 2. Use lua_getglobal(L, "functionName") to load the function reference onto the stack 3. Push the arguments onto the stack with the first argument going on first etc. Note that this means that the first argument is the furthest down on the stack if several arguments are being passed 4. Call lua_pcall(L, ParameterNum, resultsNum, 0) which executes the function and pushes the results onto the stack 5. Test that the correct result types are on the stack using lua_isnumber etc. 6. Get the results from the stack using lua_tostring etc. 7. Pop the stack using lua_pop to put the function and arguments from the stack
An example of the code to execute a Lua function from C code
#include <stdio.h> #include <stdlib.h> #include <string.h> #include "lua.h" #include "lauxlib.h" #include "lualib.h" #define MAX_COLOR 255 char *load (lua_State *L, const char *fname, int *number, int x, int y, char* myString); int main(void) { lua_State *L = luaL_newstate(); /* opens Lua */ luaL_openlibs(L); /* opens the standard libraries */ char filename[] = "C:/Lua C and C++/workspace/functionFile.lua"; int number = 0; int x = 5; int y = 6; char *a = malloc(100); strcpy(a, "Hello to "); char * resultString = load(L, filename, &number, x, y, a); printf("number is %d\n", number); printf("The String is %s\n", resultString); free(a); free(resultString); return 0; } char *load (lua_State *L, const char *fname, int *number, int x, int y, char* myString) { if (luaL_loadfile(L, fname) || lua_pcall(L, 0, 0, 0)) { luaL_error(L, "cannot run config file: %s", lua_tostring(L, -1)); } lua_getglobal(L, "modifyData"); lua_pushnumber(L, x); lua_pushnumber(L, y); lua_pushstring(L, myString); if (lua_pcall(L, 3, 2, 0) != 0) { luaL_error(L, "error in function "); } int z = lua_tointeger(L, -2); printf("the number is %d\n", z); *number = z; myString = lua_tostring(L, -1); printf("the string is %s\n", myString); lua_pop(L, 1); lua_pop(L, 1); return myString; }
The Lua file with a function looked like the following
function modifyData(x, y, theString) theString = theString .. "the world" return x*y , theString end
- Errors can be handled either by the C code or by calling LuaL_error(L, errorMessage);
Continuations
References
Memory management
Allocators GC API
Threads in Lua
- Lua does not support multithreading because of:
- Performance penalties related to synchronization, blocking, and deadlocking
- Hard to find bugs related to threads
- Each operating systems has varying threading and synchronization primitives
- Co-routines are provided instead
- Threading can be done through C
- However the coding and results are then operating system dependent