Troubleshooters.Com and Code Corner Present

Litt's Lua Laboratory:
Lua Variables
(With Snippets)

Copyright (C) 2011 by Steve Litt



Debug like a Ninja

Contents


Introduction

Lua variables come in the following types:
We won't discuss the last two -- that's beyond the scope of this page.

Any time you want to get the type of a variable, use the type() function, which returns the type as a lower case string. For instance:
var = "hello"
print(type(var))
The preceding would print the word "string".

Numbers

Lua doesn't distinguish between short and long, signed or unsigned, int or float. They're all type number, and they can all be added, subtracted, multiplied and divided. Lua also converts strings to numbers if the string is such that it can represent a number and it's used in an arithmetic operation. Likewise, Lua converts a number to a string if the number is used in a string operation such as the .. concatenation operator.
#!/usr/bin/lua 

a = 1 -- this is a number
b = 10 -- this is a number
c = "100" --[[ this is a string representation
of a number ]]

print ("\nNumber below...")
print(a)
print(type(a))
print ("\nNumber below...")
print(b)
print(type(b))
print ("\nString below...")
print(c)
print(type(c))
print ("\nNumber + number below...")
print(a+b)
print(type(a+b))
print ("\nNumber + string below...")
print(b+c)
print(type(b+c))
print ("\nNumber concatted to string below...")
print(b..c)
print(type(a..c))
The preceding prints out the following, which pretty much proves all that's been said in this section:
slitt@mydesk:~$ ./test.lua

Number below...
1
number

Number below...
10
number

String below...
100
string

Number + number below...
11
number

Number + string below...
110
number

Number concatted to string below...
10100
string
slitt@mydesk:~$

Strings

The following are strings:
first="Steve"               -- string, value "Steve"
last="Litt" -- string, value "Litt"
whole=first .. last -- string, value "SteveLitt"
whole=first .. " " .. last -- string, value "Steve Litt"
In the Numbers section you learned that the concatination operator is .. and that Lua converts "3004" to a number if it's used in a numeric expression. There's a function called tostring() that converts its argument to a string. This is useful in print statements when you're not sure whether something will be a string or nil. Therefore it's especially useful in diagnostic prints.

Another way you can create a string is to use string.format(), which, as you can see, is a part of the string library. It's a lot like C's sprintf(). For instance here's code to format a string and a number that happens to be fractional into a string:
#!/usr/bin/lua 
price_earnings_ratio = 15.521825348912
company = "Troubleshooters.Com"
mystring = string.format("The price to earnings ratio of %s is %f.",
company, price_earnings_ratio)
print(mystring)
mystring = string.format("The price to earnings ratio of %s is %.2f.",
company, price_earnings_ratio)
print(mystring)
mystring = string.format("The price to earnings ratio of %s is %8.2f.",
company, price_earnings_ratio)
print(mystring)
mystring = string.format("The price to earnings ratio of %s is %08.2f.",
company, price_earnings_ratio)
print(mystring)
mystring = string.format("The price to earnings ratio of %s is %03.2f.",
company, price_earnings_ratio)
print(mystring)


The preceding code produces the following output:
slitt@mydesk:~$ ./test.lua
The price to earnings ratio of Troubleshooters.Com is 15.521825.
The price to earnings ratio of Troubleshooters.Com is 15.52.
The price to earnings ratio of Troubleshooters.Com is 15.52.
The price to earnings ratio of Troubleshooters.Com is 00015.52.
The price to earnings ratio of Troubleshooters.Com is 15.52.
slitt@mydesk:~$
You can see that the number you put between the % and the f controls the formatting of the number (or string or anything else). For instance, 8.2 means use two places after the decimal point, but have the whole number take up exactly 8 spaces including the decimal point. The last example in the preceding code shows that if doing so would cut significant digits off the whole part of the number, then it print the number using more than 8 spaces. Here's a brief and incomplete list of substitution characters:
%s

string
                
%e
or
%E

Scientific
notation
%d

integer
(signed
decimal)

%o
octal
(signed)
%f

floating
point

%u
unsigned
decimal
%c
character
(use the
ascii value)

%x
or
%X

unsigned
hex

You have the string library available, including:

string.sub(strng, startpos, endpos)

This is the substring command. If endpos isn't given, the returned string goes to the end of the original string. Negative numbers start from the end instead of the beginning. The following program and its output make it pretty clear:
mystring="123456789"

print(string.sub(mystring, 1, 3)) -- print 1st 3 characters
print(string.sub(mystring, -3)) -- print last 3 characters
print(string.sub(mystring, 2)) -- eliminate first character
print(string.sub(mystring, 4, -4)) -- eliminate the first 3 and last 3 chars
Output looks as expected:
slitt@mydesk:~$ ./test.lua
123
789
23456789
456
slitt@mydesk:~$
Remember, you use string.sub() to get substrings by position. To get them with matching, you use string.match(), string.find(), or string.gsub(). string.gsub() actually changes strings, and can transfer found strings to the replace string just like in Perl.

The string library has more functions than these. Take a look in the Lua docuementation. And remember to look at this subsite's Lua Regex page.

nil

Nil is the absense of value. It means nothing has been assigned to a variable, or else the variable has deliberately been assigned nil. See this:
local string1        -- Declare it local but don't assign. It's nil
local string2 = "hi" -- Not nil
string2 = nil -- It's as if nothing had ever been assigned to it
You might wonder why you'd ever want to assign nil to a variable.
Lua tables are groups of key/value pairs. Any key never specifically assigned a value will have a value of nil. So will a key specifically assigned a "value" of nil. There is no difference between these two. No table iterator will ever find keys with nil "values", whether those values were placed there or just never got assigned.

There are exactly two Lua entities that test as false: The boolean false and nil. Unlike C, 0 tests as true, not false. Because nil tests as false, you can do things like this:
if not myvar
print("ERROR: myvar is nil, aborting!")
os.exit(1)
end
Or this:
assert(myvar, "ERROR: myvar is nil, aborting!")
Or you can use short circuit logic available in C, Perl and many other languages:
print(string.format("Myvar=>%s<",myvar or "nil value"))
The more generic way to do the preceding is this:
print(string.format("Myvar=>%s<",tostring(myvar)))
The preceding is better because if myvar happened to be a number or boolean it would print the number or either true or false. If myvar were a table it would print the table address, and if it were a function it would print the function address. In other words, it would not abort, which is often what you want, especially in debugging.

Booleans

A variable becomes a boolean when you assign it either true, false, or the return of a function that returns a boolean, or a conditional. Examples follow:
myvar = true         -- myvar is true
myvar = false -- myvar is false
myvar = 2 < 3 -- myvar is true
myvar = 2 > 3 -- myvar is false

You test a boolean by simply naming the variable. For instance, let's say you have a boolean called should_continue. You could decide whether to skip over code in a loop as shown in the following pseudocode:
should_continue = true
while should_continue do
whatever()
if should_continue then
do_work_done_only_when_not_done()
should_continue = determine_whether_to_continue()
end
end
You can put together booleans with and, or and the like:
if should_continue and found
    whatever()
end

if found or first_time
whichever()
end
Remember, only boolean false and nil test false -- everything else tests true.

Tables

First let me say this. 50% to 90% of Lua data involves tables, so it's a big, big subject covered much more extensively on this subsite's Lua Tables page.

Here's how you make an empty table:
mytable = {"one", "two", "three"}        -- integer indexed keys starting at 1
mytable = {s="Sam, a="Al", d2="Don"}     -- Indexed by random strings
					 -- But not numbers
mytable = {}                             -- Empty table
You set or add table key/value elements like this:
mytable[numbervar] = value       -- Key is a number
mytable["stringvar"] = value -- Key is a string
mytable.stringvar = value -- Exactly the same as above
You can also add with table.insert(), which has a two and three argument variety:

table.insert(mytable, newelement)       -- Inserts new element at the "end"
table.insert(mytable, pos, newelement)  -- Inserts the new element at position pos,
                                        --   pushing everything else up
Did you notice I quoted the word "end" in the comment? Lua tables are not guaranteed to come out sorted by key, or sorted by the order they went in, or by anything else. There's no universally reliable way to find the "end" of a table, nor a universally reliable way to find out how many elements it has, without resorting to iterating the whole table.

CAUTION!
Do not rely on things like table.maxn(), #tablename and the like for getting a table's number of elements or its "end". Don't assume a table is sorted. table.maxn() and #tablename work only if you make sure all entries have integer keys and the keys start at 1 and end at, let's say 20, and there are no nil values between 1 and 20. If that's too much to assume, and it probably is, then don't assume it.

This is not a problem because Lua gives you many ways to iterate all elements of a table, so there's little reason you need the highest numerical value. And even if you do, there are ways an application programmer can handle that. This will be discussed more on the Lua Tables page of this subsite, but I just want to say here this is not much of a limitation at all, as long as you make no assumptions.


Functions

Try this:
#!/usr/bin/lua 
hello = function() print("Hello World") end
hello()
As you can guess, it prints out "Hello World". Look what happened. We made a function with no name, and assigned it to a variable called hello. A function is just another piece of data you can assign all over the place. Watch this:
#!/usr/bin/lua 
hello = function() print("Hello World") end -- assign a function to hello
hello() -- execute that function
goodbye = "See ya later" -- goodbye is a string
print(goodbye) -- print the string
goodbye = hello -- assign the hello function to goodbye
goodbye() -- run the new goodbye as a function
Here's the result:
slitt@mydesk:~$ ./test.lua
Hello World
See ya later
Hello World
slitt@mydesk:~$
A function is just a piece of data. This is SUCH a powerful feature, making complex tasks simple. You can pass functions as arguments to functions, pass them back as return values, assign them to variables, and use them as elements of tables. Consider the following:
#!/usr/bin/lua 
mypoint={x=3, y=9, show=function(tab) print(string.format("x=%d,y=%d",tab.x,tab.y)) end}
mypoint.show(mypoint)
Results:
slitt@mydesk:~$ ./test.lua
x=3,y=9
slitt@mydesk:~$
What happened there? You created table mypoint with three keys: x, y and show. The show key was assigned a value that was a function. Hey, that's fair, functions are data. If the above is too ugly for you, do this:
#!/usr/bin/lua 
mypoint={x=3, y=9}
mypoint.show=function(tab) print(string.format("x=%d,y=%d",tab.x,tab.y)) end
mypoint.show(mypoint)
Same thing, except now you're creating the show key after the fact, not during the declaration of mypoint. Consider what you'd have if you added mypoint.setx, mypoint.sety, mypoint.getx, and mypoint.gety. Data and functions all wrapped into one. Wouldn't that be kind of like an object? That would be the most primative OOP like idiom available in Lua, but Lua can get A LOT more OOP than that. See the Lua OOP page of this subsite.

Now let me show you some Lua syntactic sugar. Syntactic sugar is when a language puts in an easier way to write the same thing. Remember this:
mypoint.show(mypoint)
Is it just me, or is that redundant and kind of stupid? No problem, you can use Lua syntactic sugar and change the dot to a colon, and then remove the redundant mypoint argument:
mypoint.show(mypoint)       -- Shows x and y 
mypoint:show() -- Does EXACTLY the same thing the same way
So in other words, objectname.methodname(objectname) is the same as objectname:methodname().

Now be a little careful, because later you'll see that sometimes objectname isn't needed as an argument, in which case objectname.methodname() is the correct syntax.

By the way, in the preceding discussion, if you don't like me using OOP terms (technically Lua doesn't have OOP, but you can do all the same things with Lua as with "OOP" languages):

So in other words, tablename.functionname(tablename) is the same as tablename:functionname().

And sometimes, if the function doesn't require the argument of the table it's in, you do it like this:
tablename.functionname()

NOTE

Lua purists will flame me to death over saying Lua has OOP, because technically that's not true -- it has tables with functions inside of them. And it has metatables to implement something just like OOP inheritance and something very similar to polymorphism and even operator overloading.

But don't call it OOP :-)

One more piece of syntactic sugar. You know all the times I've written:
functname = function(args) do_something() end
I could have written that more simply like this:
function functname(args) do_something() end
Or, in the case of a function in a table (or object :-))
function tablename.functname(args) do_anotherthing() end
Also, the syntactic sugar version works correctly during recursion, which the other version doesn't do automatically. The reason I introduced the other version first was to demonstrate that functions are just data.

All but the smallest functions are written on multiple lines. Thus you might write:
function whichbigger(num1, num2)
local rtrn
if num1 > num2 then
rtrn = 1
elseif num1 < num2 then
rtrn = -1
elseif num1 == num2 then
rtrn = 0
else --internal error condition
print("FATAL internal error: neither bigger, smaller or same")
os.exit(1)
end
Arguments to functions are treated like local variables visible only inside the function but be careful, tables are passed by reference so any changes to table arguments inside the function affect the table outside the function. If you want a variable local to the function, add the word "local" as seen on the second line of the preceding function. Local variables are local to their enclosing block, which could be a loop, if statement, or even a small block deliberately made with do and end to make a tiny scope.

This section has barely scratched the surface. You haven't yet learned about closures, object makers, iterator makers, simulation of classes, or anything. These subjects are all detailed in the Lua Functions page and the Lua OOP page of this subsite.

But before learning the details of functions, there are several other things you need to learn.

Local and Global Variables

Global variables are usually the kiss of death in any language. Any code anywhere can have its way with them, and there's no reasonable way to track down what code made what change. You know, maybe config options used by almost every subroutine and object should be global, but just about everything else should be local.

Unfortunately (in my opinion), Lua variables are global by default. To make them local you need to add the local keyword when declaring them:
local age = 99
local height
height = "7 feet, 7 inches"
You can declare a local with or without an initializer. If without, its value is nil. To find the scope of a local, go down from the local's declaration until you find the first end , elseif or until keyword. The local goes out of scope there. local's scope. Then go back up to the keyword matched by the end, elseif or until keyword. That will probably be a then, do, or elseif. Note that you might think of it as an if or while, but those conditionals are terminated by then and do respectively, and the code they run is terminated by an end, elseif or until.

Make all numbers, strings, booleans, table instances and "object" instances local unless you have a darned good reason to do otherwise.


 [ Troubleshooters.com| Code Corner | Email Steve Litt ]

Copyright (C) 2011 by Steve Litt --Legal